--- title: Map Emergency Service Coverage | Plaza Docs description: A Python script that finds all fire stations in a city, generates 5-minute driving isochrones from each, and reports what percentage of the city is covered. --- Can a fire truck reach your house in 5 minutes? This script pulls every fire station from Plaza, generates a 5-minute driving isochrone from each one, calculates what percentage of the city is covered, and renders an interactive Folium map showing the gaps. ## What you’ll use - **Geocoding** to find the city center - **PlazaQL** to locate all fire stations (nodes, ways, and relations) - **Isochrone API** to generate 5-minute driving polygons from each station - Grid sampling with ray casting to estimate coverage percentage ## Key code A PlazaQL query finds every fire station in a 12km radius. Stations can be mapped as nodes, ways, or relations, so all types are queried: ``` import plaza client = plaza.Client() query = ( '$$ = search(amenity: "fire_station")' f'.around(distance: 12000, geometry: point({lat}, {lng}));' ) fc = client.v1.query(data=query) ``` For each station, a driving isochrone shows everywhere reachable within the NFPA 1710 standard of 5 minutes. This accounts for speed limits, turn restrictions, and one-way streets: ``` for station in stations: iso_fc = client.v1.isochrone(center={"type": "Point", "coordinates": [station["lng"], station["lat"]]}, contours_minutes="5", mode="auto") polygons.extend(extract_polygons(iso_fc)) ``` Coverage is calculated by sampling a grid of points across the city bounds and checking which fall inside any isochrone polygon using ray casting: ``` def calculate_coverage(bounds, polygons, step=0.003): covered, uncovered = [], [] for lat, lng in generate_grid(bounds, step): if point_in_any_isochrone(lat, lng, polygons): covered.append((lat, lng)) else: uncovered.append((lat, lng)) pct = len(covered) / (len(covered) + len(uncovered)) * 100 return pct, covered, uncovered ``` The uncovered points become red cells on the Folium map, making gaps immediately visible: ``` m = folium.Map(location=[center_lat, center_lng], tiles="cartodbpositron") for iso_fc in isochrone_geojsons: folium.GeoJson(iso_fc, style_function=lambda f: { "fillColor": "#22c55e", "fillOpacity": 0.25 }).add_to(m) for lat, lng in uncovered_points: folium.Rectangle( bounds=[[lat - half, lng - half], [lat + half, lng + half]], fill_color="#ef4444", fill_opacity=0.3, ).add_to(m) ``` ## How it works **The 5-minute standard** comes from NFPA 1710, which specifies a 90th-percentile response time for urban fire departments. This tool measures geographic coverage (can a truck *get there* in 5 minutes), not actual response time (which adds dispatch and turnout delays). It’s a useful proxy for identifying underserved areas. **Grid sampling** approximates continuous coverage with discrete points at \~300m intervals. The ray casting point-in-polygon test checks each point against every isochrone. This is simple but effective — for a typical city you’ll sample 2,000-5,000 points, which runs in seconds. ## Full source The complete Python project with modular coverage calculation, Folium map builder, and CLI is on GitHub: [**plazafyi/example-apps/emergency-coverage**](https://github.com/plazafyi/example-apps/tree/main/emergency-coverage) Stack: Python, Folium (interactive maps), Plaza SDK. Run with `python main.py "Portland, Oregon"`. Pass a second argument for custom response times: `python main.py "Austin, TX" 8`. ## Variations to try **Other emergency services.** Swap `amenity=fire_station` for `amenity=police` or `amenity=hospital`. Hospitals have different response time standards (the “golden hour” for trauma), but the same isochrone technique works. **Multi-contour analysis.** Generate 3-minute, 5-minute, and 8-minute isochrones from each station in a single request to see tiered coverage rings. **Optimal new station placement.** Find the largest contiguous cluster of uncovered cells. Its centroid is a reasonable candidate for a new station — a simplified facility location analysis. **Population-weighted coverage.** Raw geographic coverage treats empty fields the same as dense apartment blocks. Weight each grid cell by census population to surface the gaps that matter most.