nakas commited on
Commit
9f0a0b1
·
verified ·
1 Parent(s): 4dcae0f

Update air_quality_map.py

Browse files
Files changed (1) hide show
  1. air_quality_map.py +6 -385
air_quality_map.py CHANGED
@@ -561,390 +561,6 @@ class AirQualityApp:
561
  "city_name": "New York",
562
  "county_name": "New York",
563
  "state_name": "New York",
564
- "cbsa_name": "New York-Newark-Jersey City",
565
- "date_established": "1998-01-01",
566
- "last_sample_date": "2024-04-10"
567
- }
568
- ]
569
-
570
- # Texas monitors (South)
571
- tx_monitors = [
572
- {
573
- "state_code": "48",
574
- "county_code": "201",
575
- "site_number": "0024",
576
- "parameter_code": "88101",
577
- "parameter_name": "PM2.5 - Local Conditions",
578
- "poc": 1,
579
- "latitude": 29.7349,
580
- "longitude": -95.3063,
581
- "local_site_name": "Houston - Clinton Drive",
582
- "address": "9525 Clinton Drive",
583
- "city_name": "Houston",
584
- "county_name": "Harris",
585
- "state_name": "Texas",
586
- "cbsa_name": "Houston-The Woodlands-Sugar Land",
587
- "date_established": "1997-09-01",
588
- "last_sample_date": "2024-04-10"
589
- },
590
- {
591
- "state_code": "48",
592
- "county_code": "113",
593
- "site_number": "0050",
594
- "parameter_code": "44201",
595
- "parameter_name": "Ozone",
596
- "poc": 1,
597
- "latitude": 32.8198,
598
- "longitude": -96.8602,
599
- "local_site_name": "Dallas - Hinton Street",
600
- "address": "1415 Hinton Street",
601
- "city_name": "Dallas",
602
- "county_name": "Dallas",
603
- "state_name": "Texas",
604
- "cbsa_name": "Dallas-Fort Worth-Arlington",
605
- "date_established": "1998-01-01",
606
- "last_sample_date": "2024-04-10"
607
- }
608
- ]
609
-
610
- # Midwest monitors
611
- midwest_monitors = [
612
- {
613
- "state_code": "17",
614
- "county_code": "031",
615
- "site_number": "0076",
616
- "parameter_code": "88101",
617
- "parameter_name": "PM2.5 - Local Conditions",
618
- "poc": 1,
619
- "latitude": 41.7513,
620
- "longitude": -87.7130,
621
- "local_site_name": "Chicago - Ashburn",
622
- "address": "3535 West 110th Street",
623
- "city_name": "Chicago",
624
- "county_name": "Cook",
625
- "state_name": "Illinois",
626
- "cbsa_name": "Chicago-Naperville-Elgin",
627
- "date_established": "1999-01-01",
628
- "last_sample_date": "2024-04-10"
629
- },
630
- {
631
- "state_code": "26",
632
- "county_code": "163",
633
- "site_number": "0015",
634
- "parameter_code": "88101",
635
- "parameter_name": "PM2.5 - Local Conditions",
636
- "poc": 1,
637
- "latitude": 42.3328,
638
- "longitude": -83.1501,
639
- "local_site_name": "Detroit - W 7 Mile",
640
- "address": "10748 West 7 Mile Road",
641
- "city_name": "Detroit",
642
- "county_name": "Wayne",
643
- "state_name": "Michigan",
644
- "cbsa_name": "Detroit-Warren-Dearborn",
645
- "date_established": "1999-01-01",
646
- "last_sample_date": "2024-04-10"
647
- }
648
- ]
649
-
650
- # Combine all regions
651
- all_monitors.extend(ca_monitors)
652
- all_monitors.extend(ny_monitors)
653
- all_monitors.extend(tx_monitors)
654
- all_monitors.extend(midwest_monitors)
655
-
656
- # Filter by coordinates
657
- filtered_monitors = []
658
- for monitor in all_monitors:
659
- lat = float(monitor.get("latitude", 0))
660
- lon = float(monitor.get("longitude", 0))
661
- if min_lat <= lat <= max_lat and min_lon <= lon <= max_lon:
662
- filtered_monitors.append(monitor)
663
-
664
- # Filter by parameter code if provided
665
- if parameter_code:
666
- filtered_monitors = [m for m in filtered_monitors if m["parameter_code"] == parameter_code]
667
-
668
- return filtered_monitors
669
-
670
- def create_bound_event_handler(self):
671
- """Create a custom JavaScript element to handle map events and load data dynamically"""
672
-
673
- class BoundEventHandler(MacroElement):
674
- """
675
- An element that will be added to the map to handle events.
676
- This is where we'll add JavaScript to reload markers when the map view changes.
677
- """
678
-
679
- def __init__(self):
680
- super(BoundEventHandler, self).__init__()
681
- self._name = "BoundEventHandler"
682
-
683
- def render(self, **kwargs):
684
- """Render the JavaScript for handling bounds changes"""
685
- figure = self.get_root()
686
- assert isinstance(figure, Figure), "You must add this to a folium Figure (map)"
687
-
688
- # Add necessary JavaScript libraries
689
- figure.header.add_child(JavascriptLink("https://code.jquery.com/jquery-3.7.1.min.js"))
690
-
691
- # Create JavaScript to handle map bounds changes and load data
692
- script = """
693
- <script>
694
- // Wait for the map to be created and fully loaded
695
- $(document).ready(function() {
696
- // This function will run once the map is loaded
697
- setTimeout(function() {
698
- // Get the map instance
699
- var map = Object.values(window).find(function(v) {
700
- return v && v.layerManager && v.layerManager._map;
701
- });
702
-
703
- if (!map) {
704
- console.error("Could not find map instance");
705
- return;
706
- }
707
-
708
- var foliumMap = map.layerManager._map;
709
-
710
- // Function to get data when bounds change
711
- function loadDataForBounds() {
712
- // Get current map bounds
713
- var bounds = foliumMap.getBounds();
714
- var minLat = bounds.getSouth();
715
- var maxLat = bounds.getNorth();
716
- var minLng = bounds.getWest();
717
- var maxLng = bounds.getEast();
718
- var zoom = foliumMap.getZoom();
719
-
720
- // Only load data if zoom level is high enough
721
- // This prevents too many requests when viewing the whole country
722
- if (zoom < 6) {
723
- console.log("Zoom level too low, not loading data");
724
- return;
725
- }
726
-
727
- console.log("Map bounds changed. Zoom: " + zoom);
728
- console.log("Bounds: " + minLat + ", " + maxLat + ", " + minLng + ", " + maxLng);
729
-
730
- // This part would normally send a request to the server
731
- // In this demo, we'll just show a notification
732
-
733
- // Create a notification element
734
- var notificationId = "bounds-notification-" + Date.now();
735
- var notification = $('<div id="' + notificationId + '" style="position: absolute; z-index: 1000; background-color: white; padding: 10px; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.5); top: 10px; left: 50%; transform: translateX(-50%);">Loading air quality stations within view...</div>');
736
-
737
- // Add it to the map container
738
- $(foliumMap.getContainer()).append(notification);
739
-
740
- // Remove it after 2 seconds
741
- setTimeout(function() {
742
- $("#" + notificationId).fadeOut(500, function() {
743
- $(this).remove();
744
- });
745
- }, 2000);
746
-
747
- // In a real implementation, we would now:
748
- // 1. Send a request to the server to get data for these bounds
749
- // 2. The server would query the API for the data
750
- // 3. The server would return the data
751
- // 4. We would add markers to the map
752
- }
753
-
754
- // Add event listener for map moveend (happens after pan/zoom)
755
- foliumMap.on('moveend', loadDataForBounds);
756
-
757
- console.log("Bounds event handler attached successfully");
758
- }, 1000); // Wait 1 second for the map to fully initialize
759
- });
760
- </script>
761
- """
762
-
763
- figure.script.add_child(folium.Element(script))
764
- return self._template.render(**kwargs)
765
-
766
- return BoundEventHandler
767
-
768
- def create_air_quality_map_ui():
769
- """Create the Gradio interface for the Air Quality Map application"""
770
- app = AirQualityApp()
771
-
772
- def show_map(parameter=None):
773
- """Callback to generate and display the map"""
774
- # Extract code from parameter string if provided
775
- parameter_code = None
776
- if parameter and ":" in parameter:
777
- parameter_code = parameter.split(":")[0].strip()
778
-
779
- # Generate the map
780
- result = app.create_map(parameter_code=parameter_code)
781
-
782
- if isinstance(result, dict):
783
- # Combine map and legend HTML
784
- html_content = f"""
785
- <div>
786
- {result["map"]}
787
- {result["legend"]}
788
- </div>
789
- """
790
- return html_content
791
- else:
792
- # Return error message or whatever was returned
793
- return result
794
-
795
- # Create the UI
796
- with gr.Blocks(title="Air Quality Monitoring Stations") as interface:
797
- gr.Markdown("# NOAA Air Quality Monitoring Stations Map")
798
- gr.Markdown("""
799
- This application displays air quality monitoring stations in the United States.
800
-
801
- **Features:**
802
- - The map automatically loads air quality sites as you zoom or pan.
803
- - Stations are color-coded based on their most recent Air Quality Index (AQI) reading.
804
- - Click on a station marker to view detailed information and recent readings.
805
-
806
- **Note:** To use the actual EPA AQS API, you need to register for an API key and set
807
- `EPA_AQS_EMAIL` and `EPA_AQS_API_KEY` environment variables in your Hugging Face Space.
808
-
809
- For demonstration without an API key, the app shows sample data for major US regions.
810
- """)
811
-
812
- with gr.Row():
813
- with gr.Column(scale=1):
814
- # Parameter dropdown (pollutant type)
815
- parameter_dropdown = gr.Dropdown(
816
- choices=app.mock_get_parameters(),
817
- label="Filter by Pollutant (Optional)",
818
- allow_custom_value=True
819
- )
820
-
821
- # Instructions for using the map
822
- gr.Markdown("""
823
- ### How to use the map:
824
- 1. Zoom in or pan to an area of interest
825
- 2. Air quality monitoring stations will automatically load
826
- 3. Click on markers to view detailed information
827
-
828
- The map will only load stations when zoomed in to avoid
829
- displaying too many stations at once.
830
- """)
831
-
832
- # Button to refresh map
833
- map_button = gr.Button("Refresh Map")
834
-
835
- # HTML component to display the map in a larger column
836
- with gr.Column(scale=3):
837
- map_html = gr.HTML(label="Air Quality Monitoring Stations Map")
838
-
839
- # Set up event handlers
840
- map_button.click(
841
- fn=show_map,
842
- inputs=[parameter_dropdown],
843
- outputs=map_html
844
- )
845
-
846
- # Initialize the map on load
847
- interface.load(
848
- fn=show_map,
849
- inputs=[parameter_dropdown],
850
- outputs=map_html
851
- )
852
-
853
- return interface
854
-
855
- # Creating and launching the app in app.py
856
- def create_air_quality_map_ui():
857
- """Create the Gradio interface for the Air Quality Map application"""
858
- app = AirQualityApp()
859
-
860
- def show_map(parameter=None):
861
- """Callback to generate and display the map"""
862
- # Extract code from parameter string if provided
863
- parameter_code = None
864
- if parameter and ":" in parameter:
865
- parameter_code = parameter.split(":")[0].strip()
866
-
867
- # Generate the map
868
- result = app.create_map(parameter_code=parameter_code)
869
-
870
- if isinstance(result, dict):
871
- # Combine map and legend HTML
872
- html_content = f"""
873
- <div>
874
- {result["map"]}
875
- {result["legend"]}
876
- </div>
877
- """
878
- return html_content
879
- else:
880
- # Return error message or whatever was returned
881
- return result
882
-
883
- # Create the UI
884
- with gr.Blocks(title="Air Quality Monitoring Stations") as interface:
885
- gr.Markdown("# Air Quality Monitoring Stations Map")
886
- gr.Markdown("""
887
- This application displays air quality monitoring stations in the United States.
888
-
889
- **Features:**
890
- - The map automatically loads air quality sites as you zoom or pan.
891
- - Stations are color-coded based on their most recent Air Quality Index (AQI) reading.
892
- - Click on a station marker to view detailed information and recent readings.
893
-
894
- **Note:** To use the actual EPA AQS API, you need to register for an API key and set
895
- `EPA_AQS_EMAIL` and `EPA_AQS_API_KEY` environment variables in your Hugging Face Space.
896
-
897
- For demonstration without an API key, the app shows sample data for major US regions.
898
- """)
899
-
900
- with gr.Row():
901
- with gr.Column(scale=1):
902
- # Parameter dropdown (pollutant type)
903
- parameter_dropdown = gr.Dropdown(
904
- choices=app.mock_get_parameters(),
905
- label="Filter by Pollutant (Optional)",
906
- allow_custom_value=True
907
- )
908
-
909
- # Instructions for using the map
910
- gr.Markdown("""
911
- ### How to use the map:
912
- 1. Zoom in or pan to an area of interest
913
- 2. Air quality monitoring stations will automatically load
914
- 3. Click on markers to view detailed information
915
-
916
- The map will only load stations when zoomed in to avoid
917
- displaying too many stations at once.
918
- """)
919
-
920
- # Button to refresh map
921
- map_button = gr.Button("Refresh Map")
922
-
923
- # HTML component to display the map in a larger column
924
- with gr.Column(scale=3):
925
- map_html = gr.HTML(label="Air Quality Monitoring Stations Map")
926
-
927
- # Set up event handlers
928
- map_button.click(
929
- fn=show_map,
930
- inputs=[parameter_dropdown],
931
- outputs=map_html
932
- )
933
-
934
- # Initialize the map on load
935
- interface.load(
936
- fn=show_map,
937
- inputs=[parameter_dropdown],
938
- outputs=map_html
939
- )
940
-
941
- return interface
942
-
943
- # For direct execution and Hugging Face Spaces deployment
944
- if __name__ == "__main__":
945
- app = create_air_quality_map_ui()
946
- app.launch()
947
- York",
948
  "cbsa_name": "New York-Newark-Jersey City",
949
  "date_established": "1999-07-15",
950
  "last_sample_date": "2024-04-10"
@@ -962,4 +578,9 @@ if __name__ == "__main__":
962
  "address": "681 Kelly Street",
963
  "city_name": "Bronx",
964
  "county_name": "Bronx",
965
- "state_name": "New
 
 
 
 
 
 
561
  "city_name": "New York",
562
  "county_name": "New York",
563
  "state_name": "New York",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  "cbsa_name": "New York-Newark-Jersey City",
565
  "date_established": "1999-07-15",
566
  "last_sample_date": "2024-04-10"
 
578
  "address": "681 Kelly Street",
579
  "city_name": "Bronx",
580
  "county_name": "Bronx",
581
+ "state_name": "New York",
582
+ "cbsa_name": "New York-Newark-Jersey City",
583
+ "date_established": "1998-01-01",
584
+ "last_sample_date": "2024-04-10"
585
+ }
586
+ ]