""" FRS Facilities Utilities Pre-validated functions for querying EPA's Facility Registry Service (FRS) data from the SAWGraph knowledge graph. DO NOT MODIFY THIS FILE - Functions are validated against specific SPARQL endpoints. """ import json import requests import geopandas as gpd import sparql_dataframe from shapely import wkt from shapely.ops import linemerge from shapely.geometry import Point def load_FRS_facilities(state: str, naics_name: str, limit: int = 1000) -> gpd.GeoDataFrame: """ Load FRS facilities for a specified state and NAICS industry. This function queries the SAWGraph knowledge graph via SPARQL endpoint to retrieve EPA FRS facility data with geometries. Args: state: State name (e.g., "California", "Texas", "New York") naics_name: NAICS industry name - must be from ALLOWED_NAICS list: - Waste Treatment and Disposal - Converted Paper Manufacturing - Water Supply and Irrigation - Sewage Treatment - Plastics Product Manufacturing - Textile and Fabric Finishing and Coating - Basic Chemical Manufacturing - Paint, Coating, and Adhesive Manufacturing - Aerospace Product and Parts - Drycleaning and Laundry Services - Carpet and Upholstery Cleaning Services - Solid Waste Landfill limit: Maximum number of facilities to retrieve (default: 1000) Returns: GeoDataFrame with facility geometries and attributes including: - facilityName: Name of the facility - industryCodes: Industry classifications (comma-separated if multiple) - geometry: Point geometry in EPSG:4326 Raises: ValueError: If state or NAICS name is not in the allowed lists Example: >>> sewage = load_FRS_facilities("California", "Sewage Treatment") >>> print(f"Found {len(sewage)} facilities") >>> print(sewage.head()) """ # Validate inputs ALLOWED_NAICS = [ "Waste Treatment and Disposal", "Converted Paper Manufacturing", "Water Supply and Irrigation", "Sewage Treatment", "Plastics Product Manufacturing", "Textile and Fabric Finishing and Coating", "Basic Chemical Manufacturing", "Paint, Coating, and Adhesive Manufacturing", "Aerospace Product and Parts", "Drycleaning and Laundry Services", "Carpet and Upholstery Cleaning Services", "Solid Waste Landfill", ] if naics_name not in ALLOWED_NAICS: raise ValueError(f"Invalid NAICS '{naics_name}'. Allowed: {ALLOWED_NAICS}") # Map all US state names to FIPS codes STATE_FIPS = { "Alabama": "01", "Alaska": "02", "Arizona": "04", "Arkansas": "05", "California": "06", "Colorado": "08", "Connecticut": "09", "Delaware": "10", "Florida": "12", "Georgia": "13", "Hawaii": "15", "Idaho": "16", "Illinois": "17", "Indiana": "18", "Iowa": "19", "Kansas": "20", "Kentucky": "21", "Louisiana": "22", "Maine": "23", "Maryland": "24", "Massachusetts": "25", "Michigan": "26", "Minnesota": "27", "Mississippi": "28", "Missouri": "29", "Montana": "30", "Nebraska": "31", "Nevada": "32", "New Hampshire": "33", "New Jersey": "34", "New Mexico": "35", "New York": "36", "North Carolina": "37", "North Dakota": "38", "Ohio": "39", "Oklahoma": "40", "Oregon": "41", "Pennsylvania": "42", "Rhode Island": "44", "South Carolina": "45", "South Dakota": "46", "Tennessee": "47", "Texas": "48", "Utah": "49", "Vermont": "50", "Virginia": "51", "Washington": "53", "West Virginia": "54", "Wisconsin": "55", "Wyoming": "56", # Territories "District of Columbia": "11", "Puerto Rico": "72", "Virgin Islands": "78", "Guam": "66", "American Samoa": "60", "Northern Mariana Islands": "69" } if state not in STATE_FIPS: raise ValueError(f"Invalid state '{state}'. Must be a valid US state or territory name.") state_fips = STATE_FIPS[state] # Updated endpoint URL endpoint_url = "https://frink.apps.renci.org/fiokg/sparql" # Updated SPARQL query with new namespace prefixes and filtering logic # The double braces {{ }} are f-string escapes that become single braces in SPARQL query = f""" PREFIX kwgr: PREFIX kwg-ont: PREFIX rdf: PREFIX rdfs: PREFIX frs: PREFIX fio: PREFIX geo: SELECT DISTINCT ?facilityName (GROUP_CONCAT(DISTINCT ?industryCode; separator=", ") AS ?industryCodes) ?facilityWKT WHERE {{ ?facility a frs:FRS-Facility ; rdfs:label ?facilityName ; fio:ofIndustry/rdfs:label ?industryCode ; geo:hasGeometry/geo:asWKT ?facilityWKT; kwg-ont:sfWithin ?county . FILTER(CONTAINS(LCASE(?industryCode), LCASE("{naics_name}"))) . FILTER(STRSTARTS(STR(?county), "http://stko-kwg.geog.ucsb.edu/lod/resource/administrativeRegion.USA.{state_fips}")) . FILTER(STRLEN(STR(?county)) = 73) . }} GROUP BY ?facilityName ?facilityWKT ?industryCode LIMIT {limit} """ # Execute query and process results df = sparql_dataframe.get(endpoint_url, query) df = df.dropna(subset=["facilityWKT"]).copy() df["geometry"] = df["facilityWKT"].apply(wkt.loads) df = df.drop(columns=["facilityWKT"]) # Return as GeoDataFrame with WGS84 coordinate system return gpd.GeoDataFrame(df, geometry="geometry", crs="EPSG:4326")