Service Area Planning and Coverage Optimization
Purpose
Service Area Planning and Coverage Optimization delineates areas reachable from facilities (ambulance stations, distribution centers, cell towers) within time, distance, or cost thresholds. Identifies coverage gaps, overlap, and optimization opportunities.
Use this tool for coverage diagnostics around an existing facility network. For candidate site generation/selection workflows (for example top-N new-site selection), use market_access_and_site_intelligence_workflow.
Typical Questions This Tool Helps Answer
- Which neighborhoods are reachable from our current facilities within 5, 10, and 15 minutes?
- Where are the uncovered demand gaps after applying realistic travel-time, turn, and impedance constraints?
- If one or more facilities close, how much coverage do we lose and where?
Background
Service-area planning estimates reachability from facilities under impedance constraints, then uses demand overlays to evaluate coverage quality and candidate expansion strategies.
For a demand location $i$ and facility set $F$, a common reachability criterion is:
$$\min_{f\in F} c(i,f) \le \tau$$
Where $c(i,f)$ is network travel cost and $\tau$ is a service threshold (for example response-time target).
Coverage and Equity Framing
Coverage optimization can be represented as maximizing covered demand mass under budget/facility constraints:
$$\max \sum_i d_i z_i, \quad z_i\in{0,1}$$
With optional equity controls to prevent improvements that systematically bypass low-access populations.
Methodological Considerations
- Validate impedance and turn restrictions before catchment construction.
- Evaluate multiple threshold values ($\tau$) to expose sensitivity of underserved-area mapping.
- Compare baseline and candidate-network states under disruption scenarios, not just normal operating conditions.
Practical Interpretation Pitfalls
Common mistakes include reading coverage gain without equity context, ignoring threshold sensitivity, and over-trusting edge-of-network catchments where data quality is weaker.
Inputs
| Parameter | Type | Required | Description |
|---|---|---|---|
| network | Vector (LineString or MultiLineString) | Yes | Network layer used to derive service areas |
| facilities | Vector (Point or MultiPoint) | Yes | Facility origin points |
| demand_points | Vector (Point, MultiPoint, Polygon, or MultiPolygon) | No | Optional demand layer used for uncovered-demand and KPI calculations. Polygon features are converted to interior representative points. |
| ring_costs | Array | Yes | Positive cost thresholds, e.g., [5, 10, 15] |
| scenarios | CSV | No | Optional scenario variants for open/closed facilities |
| service_area_merge_origins | Boolean | No | If true, dissolve overlapping origin polygons into merged coverage per ring in service_areas output. Default: false. |
| optimization_mode | String | No | One of existing (default) or expansion. |
| candidate_mode | String | No | Expansion candidate source: generated (default) or user_supplied. Ignored in existing mode. |
| candidate_sites | Vector (Point or MultiPoint) | No | Required when optimization_mode=expansion and candidate_mode=user_supplied. |
| num_sites_to_select | Integer | No | Number of expansion sites to select (default 1). |
| selection_strategy | String | No | Expansion selection strategy: single_best (default) or top_k_greedy. |
| min_new_site_separation | Number | No | Minimum spacing between selected expansion sites (map units). |
| max_generated_candidates | Integer | No | Candidate cap used when candidate_mode=generated (default 250). |
| ranked_candidates_vector | String path | No | Optional output vector path for ranked candidate sites with coverage-gain metrics. |
| edge_cost_field | String | No | Numeric line field used as impedance. If omitted, Euclidean arc length is used. |
| mode_field | String | No | Field identifying transport mode per edge (e.g. walk, bus, car). |
| default_mode_speed | Number | No | Default speed (km/h) for mode-speed conversion when no mode-specific override matches. |
| mode_speed_overrides | String | No | Comma-separated mode:speed overrides, e.g. walk:5,bus:30. |
| allowed_modes | String | No | Comma-separated list of permitted modes, e.g. "walk,bus". |
| one_way_field | String | No | Field encoding one-way restrictions; accepts FT/TF/B codes and legacy boolean values. |
| node_cost_points | Vector (Point or MultiPoint) | No | Optional point layer containing node/intersection entry-cost observations. |
| node_cost_field | String | No | Numeric field in node_cost_points containing non-negative node entry costs. |
| node_cost_snap_distance | Number | No | Optional max assignment distance from each node-cost point to the nearest network node. |
| turn_penalty | Number | No | Cost added for each standard turn (use the same units as edge_cost_field). |
| u_turn_penalty | Number | No | Additional cost for U-turns. |
| forbid_u_turns | Boolean | No | If true, U-turns are forbidden at all nodes. |
| forbid_left_turns | Boolean | No | If true, all left turns are forbidden. |
| forbid_right_turns | Boolean | No | If true, all right turns are forbidden. |
| turn_restrictions_csv | CSV | No | Per-turn transition table with columns prev_x,prev_y,node_x,node_y,next_x,next_y; optional forbidden and turn_cost (or penalty/cost/extra_cost). |
| temporal_cost_profile | CSV | No | Time-of-day speed profile CSV for scheduled routing. |
| temporal_edge_id_field | String | No | Network edge ID field that matches profile keys. |
| departure_time | String | No | ISO-8601 departure time for temporal routing, e.g. "2024-06-15T08:00:00Z". |
| temporal_mode | String | No | One of multiplier or absolute. |
| temporal_fallback | String | No | Fallback when no matching profile entry exists: static_cost or error. |
Input Requirements
networkmust contain line geometries.facilitiesmust contain point geometries.- If CRS is known, projected CRS is required.
ring_costsmust be numeric and strictly greater than zero.scenariosfile path must exist when provided.candidate_sitesis required whenoptimization_mode=expansionandcandidate_mode=user_supplied.
scenarios CSV Schema (Authoritative)
Expected columns (in order):
scenario_id,facility_id,is_open,capacity
capacity is optional.
Accepted truthy values for is_open: 1, true, yes, y, open.
Example:
scenario_id,facility_id,is_open,capacity
all_open,1,true,100
all_open,2,true,100
one_open,1,true,100
one_open,2,false,0
Temporal Profile CSV Schema (Time-of-Day Routing)
The temporal_cost_profile CSV uses one row per edge and time window.
Expected columns:
edge_id,dow,start_minute,end_minute,value
Column definitions:
edge_id: Edge key matchingtemporal_edge_id_fieldin the network layer.dow: Day of week (1..7, Monday=1).start_minute: Inclusive minute-of-day (0..1439).end_minute: Exclusive minute-of-day (1..1440, must be greater thanstart_minute).value: Positive temporal modifier.
temporal_mode determines how value is interpreted:
multiplier: appliesvalueas a multiplier to static edge cost.absolute: treatsvalueas the full edge cost for that time window.
Example (weekday AM peak slowdown on selected edges):
edge_id,dow,start_minute,end_minute,value
A_B,1,420,600,1.8
B_C,1,420,600,1.6
A_B,2,420,600,1.8
B_C,2,420,600,1.6
Set temporal_fallback to:
static_costto use non-temporal impedance when no matching row exists.errorto fail fast when a temporal row is missing.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| service_areas | String path | Yes | Output vector path for service area polygons |
| uncovered_demand | String path | Yes | Output vector path for uncovered demand points |
| scenario_summary_csv | String path | Yes | Output CSV path for scenario KPIs |
| ranked_candidates_csv | String path | Yes | Output CSV path for candidate ranking |
| html_report | String path | No | Optional HTML summary report output |
Output Path Behavior
- Vector format is inferred from extension for
service_areasanduncovered_demand. - Unsupported vector extensions fail validation.
html_reportis emitted only when path is provided.
Outputs
Output artifact keys below are runtime outputs, not input parameters.
| Artifact | Runtime Output Key | Type | Description |
|---|---|---|---|
| Service areas layer | service_areas | Vector (Polygon/MultiPolygon) | Network-derived multi-ring service areas |
| Uncovered demand layer | uncovered_demand | Vector (Point) | Demand points outside baseline service areas |
| Scenario summary report | scenario_summary_csv | CSV | Scenario-level coverage/accessibility KPIs |
| Ranked candidates report | ranked_candidates_csv | CSV | Candidate ranking by coverage gain and travel cost |
| Summary payload | summary | JSON (result payload) | Output path summary for orchestration |
| Optional report | html_report | HTML | Optional HTML summary report output when requested |
uncovered_demand Schema
Geometry type: Point
Attributes:
demand_id
scenario_summary_csv Schema
scenario_id,total_demand_covered_pct,avg_accessibility,outlier_count
Notes:
- Includes a
baselinerow. total_demand_covered_pctis coverage percentage.avg_accessibilityis currently populated from the largest configured ring cost.
ranked_candidates_csv Schema
candidate_id,coverage_gain_pct,avg_network_travel_cost,rank
Result Payload Keys
service_areasuncovered_demandscenario_summary_csvranked_candidates_csvsummaryhtml_report(when requested)
Example
import whitebox_workflows as wbw
wbe = wbw.WbEnvironment()
wbe.service_area_planning_and_coverage_optimization(
network="road_network.shp",
facilities="fire_stations.shp",
demand_points="residential_blocks.shp",
ring_costs=[4.0, 8.0, 12.0],
service_area_merge_origins=True,
scenarios="coverage_scenarios.csv",
service_areas="out/service_areas.geojson",
uncovered_demand="out/uncovered_demand.geojson",
scenario_summary_csv="out/scenario_summary.csv",
ranked_candidates_csv="out/ranked_candidates.csv",
html_report="out/service_area_report.html"
)
The example below adds time-of-day routing and turn penalties for a peak-hour coverage analysis.
wbe.service_area_planning_and_coverage_optimization(
network="road_network.shp",
facilities="fire_stations.shp",
demand_points="residential_blocks.shp",
ring_costs=[4.0, 8.0, 12.0],
scenarios="coverage_scenarios.csv",
edge_cost_field="MINUTES",
one_way_field="ONEWAY",
node_cost_points="intersection_delay_points.shp",
node_cost_field="DELAY_MIN",
node_cost_snap_distance=25.0,
turn_penalty=0.3,
u_turn_penalty=2.0,
forbid_u_turns=True,
temporal_cost_profile="am_peak_profiles.csv",
temporal_edge_id_field="EDGE_ID",
departure_time="2024-06-15T08:00:00Z",
temporal_mode="multiplier",
temporal_fallback="static_cost",
service_areas="out/service_areas_am_peak.geojson",
uncovered_demand="out/uncovered_demand_am_peak.geojson",
scenario_summary_csv="out/scenario_summary_am_peak.csv",
ranked_candidates_csv="out/ranked_candidates_am_peak.csv",
html_report="out/service_area_report_am_peak.html"
)
Any WbEnvironment instance name is valid (wbe above is only an example).
Isochrone Map Recipe (Pro Tier)
To produce map-ready drive-time isochrones from facilities:
- Set
ring_coststo your target bands (for example[5, 10, 15]minutes). - Use a travel-time edge impedance field (for example
edge_cost_field="MINUTES"). - Write
service_areasto a polygon-capable output format (.gpkg,.geojson,.shp). - Optionally add
turn_penalty,u_turn_penalty, andnode_cost_pointsfor realistic intersection behavior. - Optionally add
temporal_cost_profile+departure_timefor time-of-day isochrones.
Minimal multi-band isochrone example:
wbe.service_area_planning_and_coverage_optimization(
network="road_network.gpkg",
facilities="clinics.gpkg",
ring_costs=[5.0, 10.0, 15.0],
edge_cost_field="MINUTES",
one_way_field="ONEWAY",
service_areas="out/isochrones_5_10_15.gpkg",
uncovered_demand="out/uncovered.gpkg",
scenario_summary_csv="out/scenario_summary.csv",
ranked_candidates_csv="out/ranked_candidates.csv"
)
References
- Church, R., & ReVelle, C. (1974). "The Maximal Covering Location Problem." Papers and Proceedings of the Regional Science Association 32(1), 101–118.
Parameter Interaction Notes
For Service Area Planning and Coverage Optimization, output quality is most sensitive to parameter combinations rather than single values in isolation.
- Wider
ring_costsraise coverage but can reduce discriminative power in candidate ranking. - Sparse facilities can produce high uncovered-demand counts even with large rings when network topology is fragmented.
- Scenario toggles in
scenarioscan shift rank ordering substantially; compare againstbaselinebefore acting.
QA and Acceptance Criteria
Use a staged acceptance approach for Service Area Planning and Coverage Optimization:
- Input integrity: validate projected CRS, network/facility geometry types, and positive ring costs.
- Method validity: verify baseline coverage against known served and unserved demand points.
- Output plausibility: confirm uncovered demand points are outside generated service polygons.
- Reproducibility: rerun with identical inputs and confirm stable scenario and ranking outputs.
Recommended acceptance checks:
- All four required output artifacts are present and readable.
scenario_summary_csvincludesbaselineand any scenario IDs supplied.ranked_candidates_csvranks candidates from 1..N with no missing IDs.uncovered_demandfeature count aligns with expected coverage gaps.
Advanced Operational Guidance
For production deployment of Service Area Planning and Coverage Optimization:
- Maintain fixed ring profiles (e.g., tactical, planning, policy) and avoid ad hoc threshold drift.
- Store run metadata with input hashes, parameter JSON, and timestamp for auditability.
- Use scenario IDs that map directly to planning decisions (for example, depot closure simulations).
- Gate relocation recommendations on consistent baseline-vs-scenario deltas.
Implementation Patterns
Common implementation patterns with Service Area Planning and Coverage Optimization:
- Baseline run: produce current-state service area and uncovered-demand diagnostics.
- Scenario run: toggle facilities via
scenariosCSV for closure/expansion analysis. - Candidate run: compare rank shifts using
ranked_candidates_csv. - Release run: publish decision package with baseline + scenario evidence.
Related Tools
Use Service Area Planning and Coverage Optimization together with upstream conditioning and downstream validation tools in the same bundle to ensure end-to-end consistency and stronger decision confidence.
When To Use This Workflow
Service Area Planning and Coverage Optimization is most valuable when teams need defendable service-coverage decisions across baseline and what-if facility scenarios.
What this workflow helps you achieve:
- Converts network accessibility into scenario-ready planning evidence.
- Quantifies uncovered demand to support expansion or relocation decisions.
- Creates a repeatable coverage governance workflow.
Results Delivery Checklist
Before sharing results with stakeholders:
- Validate network topology and impedance attributes for all served areas.
- Confirm ring-cost profile aligns with policy and service-level commitments.
- Document baseline and scenario assumptions in the deliverable package.
- Provide uncovered-demand deltas and candidate rank changes.
- Include at least one stress scenario (closure or demand shift).
Common Questions
Q: Why did candidate ranking change so much after a small ring-cost change?
A: Candidate ranking is sensitive to threshold cutoffs; small ring adjustments can move many demand points across covered/uncovered boundaries.
Q: What does avg_accessibility represent in the scenario summary?
A: In current implementation it is populated from the largest configured ring-cost threshold and should be interpreted as a planning proxy, not a per-demand mean travel-time statistic.
Q: Why do we still have uncovered demand with large service rings?
A: Uncovered points often indicate network disconnects or facility placement limitations, not just threshold selection.