Network Analysis

Whitebox Next Gen R brings the same deep network-analysis engine as the Python API: topology auditing, point-to-point routing, service areas, closest facility, OD cost matrices, location-allocation, accessibility metrics, sensitivity analysis, multimodal transit modelling, map matching, and fleet dispatch optimization. This chapter walks through those capabilities in order, mirroring the prepare-then-query-then-scale-up workflow used in practice.

Capability Note (Open Tier)

The workflows in this chapter target the open-tier engine and include advanced network controls such as turn/u-turn penalties, node-entry costs, and optional time-dependent edge profiles (temporal_cost_profile + departure_time) for scenario and peak-period analysis.


Session Setup

library(whiteboxworkflows)

s <- wbw_session()
setwd('/data/network')

Core Concepts You Should Know First

Before running tools, it helps to align on a few core terms used throughout this chapter.

  • Network: A graph made of edges (road or transit segments) and nodes (intersections, stops, junctions).
  • Cost / impedance: The value minimized during routing. This can be distance, travel time, generalized cost, or another friction metric.
  • Origin / destination (OD): Origins are trip start points; destinations are trip end points.
  • OD matrix: A table of costs from many origins to many destinations. This is the standard structure for accessibility, market access, and assignment analyses.
  • Shortest path: The minimum-cost path between one origin and one destination.
  • K-shortest paths: The best k distinct alternatives between the same OD pair, useful for resilience and choice modelling.
  • Service area (isochrone): The portion of the network reachable from an origin within a cost threshold (for example 10 minutes).
  • Closest facility: For each incident or demand point, the least-cost route to the nearest candidate facility on the network.
  • Location-allocation: Selecting facility sites that optimize a demand objective, such as minimizing total travel cost or maximizing coverage.
  • Connectivity: Whether all required origins and destinations are in the same connected component. Disconnected components cause failed routes.
  • Node degree: The number of edges touching a node. Degree supports basic network centrality interpretation and QA for odd junction structure.
  • Multimodal routing: Pathfinding across multiple transport modes (walk/bus/rail) with transfer penalties and mode constraints.
  • Map matching: Snapping GPS trajectories to the most plausible sequence of network edges.

If you keep these definitions in mind, each workflow step below becomes easier to interpret and validate.

Modeling Intersection Delay With Node Costs

Network tools in this chapter support optional node-entry cost modeling for intersections, gates, crossings, or turn-heavy junctions:

  • node_cost_points: point layer containing node-cost observations.
  • node_cost_field: numeric field in node_cost_points with non-negative entry cost values.
  • node_cost_snap_distance: optional max assignment distance from each node-cost point to the nearest network node.

Use node costs when edge impedance alone underestimates urban delay at intersections.


Step 1 — Prepare and Audit the Network

Every routing workflow should begin with a topology check.

Topology Audit

network_topology_audit scans a line network for dead ends, pseudo-nodes, overshoots, and isolated islands, writing each flagged location as a point feature and optionally producing a text report.

wbw_network_topology_audit(i             = 'roads.shp',
  output        = 'topology_errors.shp',
  snap_tolerance = 0.5,
  one_way_field  = 'ONEWAY',
  report         = 'topology_report.txt')

Review topology_report.txt to understand the error count and class distribution before continuing.

Connected Components

network_connected_components assigns a component identifier to every edge so you can identify and resolve disconnected islands before running OD queries.

wbw_network_connected_components(i              = 'roads.shp',
  output         = 'roads_components.shp',
  snap_tolerance = 0.5)
# Edges not in the dominant component are candidates for removal or bridging.

Node Degree

network_node_degree writes the degree of every node as a point layer. Degree-1 nodes are dead ends; unusually high-degree nodes may be duplicates.

wbw_network_node_degree(i              = 'roads.shp',
  output         = 'node_degree.shp',
  snap_tolerance = 0.5)

Building Network Topology

If your raw network lacks proper topology (node points, edge connectivity structure), use build_network_topology() to construct nodes and validate edges. This is essential before running advanced analysis like service areas or facility location.

result <- wbw_build_network_topology(i=roads, snap_tolerance=0.5)
edges <- result$edges
nodes <- result$nodes
wbw_write_vector(edges, 'roads_noded.shp')
wbw_write_vector(nodes, 'network_nodes.shp')

Snapping Facilities and Demand Points

Before routing from facilities or demand points, snap them to the nearest network location. This ensures routing queries don't fail on "off-network" origins.

facilities <- wbw_read_vector('fire_stations.shp')

snapped <- wbw_snap_points_to_network(
  network=edges,
  points=facilities,
  snap_distance=50.0  # meters
)
wbw_write_vector(snapped, 'fire_stations_snapped.shp')
# Output includes SNAP_DIST (offset to network) and snapped geometry.

Step 2 — Shortest Path and Alternatives

Single Shortest Path

shortest_path_network finds the minimum-cost path between two coordinates using Dijkstra's algorithm. Supply edge_cost_field for travel-time routing; omit it to route by Euclidean arc length.

wbw_shortest_path_network(i               = 'roads.shp',
  output          = 'route_shortest.shp',
  start_x         = 454230.0,
  start_y         = 4823150.0,
  end_x           = 458900.0,
  end_y           = 4819700.0,
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES',
  one_way_field   = 'ONEWAY')

Turn penalties model the real cost of left, right, and U-turns in dense urban networks.

wbw_shortest_path_network(i               = 'roads.shp',
  output          = 'route_with_turns.shp',
  start_x         = 454230.0,
  start_y         = 4823150.0,
  end_x           = 458900.0,
  end_y           = 4819700.0,
  snap_tolerance  = 20.0,
  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.5,
  u_turn_penalty  = 3.0,
  forbid_u_turns  = TRUE)

K-Shortest Alternative Paths

k_shortest_paths_network returns the k least-cost distinct paths for the same endpoints — useful for resilience analysis and presenting alternatives to planners.

wbw_k_shortest_paths_network(i               = 'roads.shp',
  output          = 'routes_k3.shp',
  start_x         = 454230.0,
  start_y         = 4823150.0,
  end_x           = 458900.0,
  end_y           = 4819700.0,
  k               = 3L,
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES',
  one_way_field   = 'ONEWAY')
# Each feature carries a PATH_RANK attribute (1 = shortest).

Step 3 — Service Areas

network_service_area delineates every part of the network reachable within a cost threshold from one or more origins. Typical uses include drive-time catchments for emergency services, walking isochrones around transit stops, and delivery zones.

wbw_network_service_area(i                    = 'roads.shp',
  origins              = 'fire_stations.shp',
  output               = 'fire_catchment_5min.shp',
  max_cost             = 5.0,
  snap_tolerance       = 20.0,
  output_mode          = 'polygon',
  polygon_merge_origins = TRUE,
  edge_cost_field      = 'MINUTES',
  one_way_field        = 'ONEWAY')

To model rush-hour conditions, pass a temporal speed profile and apply turn penalties.

wbw_network_service_area(i                      = 'roads.shp',
  origins                = 'fire_stations.shp',
  output                 = 'fire_catchment_5min_am_peak.shp',
  max_cost               = 5.0,
  snap_tolerance         = 20.0,
  output_mode            = 'polygon',
  polygon_merge_origins  = TRUE,
  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  = 'rush_hour_profiles.csv',
  temporal_edge_id_field = 'EDGE_ID',
  departure_time         = '2024-06-15T08:00:00Z',
  temporal_mode          = 'multiplier',
  temporal_fallback      = 'static_cost')

Use output_mode = 'edges' to retain actual road arcs inside the catchment rather than fill a polygon — more appropriate when the network is sparse.


Step 4 — Closest Facility

closest_facility_network routes each incident point to its nearest facility, measuring cost along the network rather than in straight-line distance. This is the core tool for emergency-response siting, healthcare access studies, and school-catchment delineation.

wbw_closest_facility_network(i               = 'roads.shp',
  incidents       = 'accidents.shp',
  facilities      = 'hospitals.shp',
  output          = 'routes_to_hospital.shp',
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES',
  one_way_field   = 'ONEWAY')
# Output carries INCIDENT_FID, FACILITY_FID, and COST fields per route.

For peak-hour response-time analysis, combine turn penalties with a temporal speed profile.

wbw_closest_facility_network(i                      = 'roads.shp',
  incidents              = 'accidents.shp',
  facilities             = 'hospitals.shp',
  output                 = 'routes_to_hospital_am_peak.shp',
  snap_tolerance         = 20.0,
  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.5,
  u_turn_penalty         = 3.0,
  forbid_u_turns         = TRUE,
  temporal_cost_profile  = 'rush_hour_profiles.csv',
  temporal_edge_id_field = 'EDGE_ID',
  departure_time         = '2024-06-15T08:00:00Z',
  temporal_mode          = 'multiplier',
  temporal_fallback      = 'static_cost')

Step 5 — OD Cost Matrix and Batch Route Export

OD Cost Matrix

network_od_cost_matrix solves all pairwise paths and writes results to a CSV. Each row contains an origin identifier, a destination identifier, and the network cost between them.

wbw_network_od_cost_matrix(i               = 'roads.shp',
  origins         = 'schools.shp',
  destinations    = 'libraries.shp',
  output          = 'od_costs.csv',
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES',
  one_way_field   = 'ONEWAY')

od <- read.csv('od_costs.csv')
# Minimum travel time from each school to any library:
print(aggregate(COST ~ ORIGIN_FID, data = od, FUN = min))

For a time-of-day comparison, run a second matrix at AM-peak departure.

wbw_network_od_cost_matrix(i                      = 'roads.shp',
  origins                = 'schools.shp',
  destinations           = 'libraries.shp',
  output                 = 'od_costs_am_peak.csv',
  snap_tolerance         = 20.0,
  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.5,
  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')

Materializing OD Routes as Geometry

network_routes_from_od creates the actual path lines between OD pairs.

wbw_network_routes_from_od(i               = 'roads.shp',
  origins         = 'schools.shp',
  destinations    = 'libraries.shp',
  output          = 'od_routes.shp',
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES',
  one_way_field   = 'ONEWAY')

Step 6 — Location-Allocation

location_allocation_network solves the p-median problem: given candidate facility locations and weighted demand points, which p facilities minimise total travel cost? Directly supports clinic siting, school consolidation, and warehouse network design.

wbw_location_allocation_network(i                    = 'roads.shp',
  demand_points        = 'demand_points.shp',
  facilities           = 'candidate_sites.shp',
  output               = 'selected_facilities.shp',
  facility_count       = 4L,
  solver_mode          = 'minimize_impedance',
  demand_weight_field  = 'POP',
  snap_tolerance       = 20.0,
  edge_cost_field      = 'MINUTES')
# SELECTED == 1 on the four chosen candidate sites.
# Demand points carry ASSIGNED_FID linking each to its nearest selected site.

Supported solver modes: minimize_impedance (p-median), maximize_coverage, and maximize_attendance.

To optimise under peak-hour travel times, pass a temporal profile.

wbw_location_allocation_network(i                      = 'roads.shp',
  demand_points          = 'demand_points.shp',
  facilities             = 'candidate_sites.shp',
  output                 = 'selected_facilities_am_peak.shp',
  facility_count         = 4L,
  solver_mode            = 'minimize_impedance',
  demand_weight_field    = 'POP',
  snap_tolerance         = 20.0,
  edge_cost_field        = 'MINUTES',
  node_cost_points       = 'intersection_delay_points.shp',
  node_cost_field        = 'DELAY_MIN',
  node_cost_snap_distance = 25.0,
  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')

Step 7 — Network Accessibility Metrics

compute_network_accessibility() is exposed as a typed facade wrapper. It computes a gravity-model or cumulative-opportunity accessibility score per origin — a standard indicator in transport equity analysis.

residents    <- wbw_read_vector('resident_centroids.shp', session = s)
supermarkets <- wbw_read_vector('supermarkets.shp', session = s)

result <- s$compute_network_accessibility(
  input              = 'roads.shp',
  origins            = residents$file_path(),
  destinations       = supermarkets$file_path(),
  output             = 'food_accessibility.shp',
  edge_cost_field    = 'MINUTES',
  node_cost_points   = 'intersection_delay_points.shp',
  node_cost_field    = 'DELAY_MIN',
  node_cost_snap_distance = 25.0,
  impedance_cutoff   = 30.0,
  decay_function     = 'negative_exponential',
  decay_parameter    = 0.1
)
# Each origin point carries an ACCESS_SCORE field.

Step 8 — OD Sensitivity Analysis

analyze_od_cost_sensitivity() quantifies how stable OD costs are under stochastic perturbation of edge weights — useful for stress-testing a routing model against travel-time uncertainty or hypothetical road-closure scenarios.

result <- s$analyze_od_cost_sensitivity(
  input                       = 'roads.shp',
  origins                     = 'schools.shp',
  destinations                = 'libraries.shp',
  output                      = 'od_sensitivity.shp',
  edge_cost_field             = 'MINUTES',
  node_cost_points            = 'intersection_delay_points.shp',
  node_cost_field             = 'DELAY_MIN',
  node_cost_snap_distance     = 25.0,
  impedance_disturbance_range = 0.2,  # ±20 % perturbation
  monte_carlo_samples         = 500L
)

Step 9 — Multimodal Analysis

Whitebox Next Gen supports networks with a MODE field on each edge (e.g. walk, cycle, bus, rail). Multimodal tools honour mode-specific speeds, transfer penalties, and time-of-day profiles.

Multimodal OD Scenarios

analyze_multimodal_od_scenarios() runs a batch of named scenarios from a CSV, each with different mode allowances, speed overrides, or departure times.

transit_net  <- wbw_read_vector('transit_network.shp', session = s)
bus_stops    <- wbw_read_vector('bus_stops.shp', session = s)
destinations <- wbw_read_vector('key_destinations.shp', session = s)

result <- s$analyze_multimodal_od_scenarios(
  input               = transit_net$file_path(),
  origins             = bus_stops$file_path(),
  destinations        = destinations$file_path(),
  output              = 'multimodal_od_scenarios.shp',
  mode_field          = 'MODE',
  allowed_modes       = 'walk,bus,rail',
  transfer_penalty    = 3.0,
  edge_cost_field     = 'MINUTES',
  scenario_bundle_csv = 'scenarios.csv',
  departure_time      = '08:00',
  temporal_mode       = 'scheduled'
)

Exporting Multimodal Route Geometry

export_multimodal_routes_for_od_pairs() materializes the optimal multimodal route for each OD pair with per-segment mode attributes.

result <- s$export_multimodal_routes_for_od_pairs(
  input            = transit_net$file_path(),
  origins          = bus_stops$file_path(),
  destinations     = destinations$file_path(),
  output           = 'multimodal_routes.shp',
  mode_field       = 'MODE',
  allowed_modes    = 'walk,bus,rail',
  transfer_penalty = 3.0,
  edge_cost_field  = 'MINUTES'
)

Step 10 — Map Matching

map_matching_v1 snaps a raw GPS trajectory to the most plausible sequence of network edges using a hidden Markov model with candidate expansion. It is the first step in any floating-vehicle data or probe-data workflow.

wbw_map_matching_v1(i                     = 'roads.shp',
  trajectory_points     = 'gps_probe_points.shp',
  timestamp_field       = 'TIMESTAMP',
  output                = 'matched_route.shp',
  search_radius         = 30.0,
  candidate_k           = 5L,
  snap_tolerance        = 10.0,
  edge_cost_field       = 'MINUTES',
  matched_points_output = 'matched_points.shp',
  match_report          = 'match_report.txt')

Step 11 — Fleet and Vehicle Routing (Pro)

fleet_routing_and_dispatch_optimizer solves CVRP and VRPTW problems: given a fleet of vehicles and a set of service or delivery stops, it assigns and sequences routes to minimise total cost subject to capacity and time-window constraints.

result <- s$run_tool(
  'fleet_routing_and_dispatch_optimizer',
  list(
    network               = 'roads.shp',
    depots                = 'depots.shp',
    stops                 = 'delivery_stops.shp',
    vehicles_csv          = 'fleet.csv',
    route_output          = 'fleet_routes.shp',
    route_kpis_csv_output = 'fleet_kpis.csv',
    edge_cost_field       = 'MINUTES',
    one_way_field         = 'ONEWAY',
    vrp_mode              = 'VRPTW'
  )
)

kpis <- read.csv('fleet_kpis.csv')
print(kpis)

Note: This tool requires a session initialised with a valid Pro licence.


Complete Workflow: Emergency Response Planning

library(whiteboxworkflows)

s <- wbw_session()
setwd('/data/emergency_planning')

# 1. Audit topology.
wbw_network_topology_audit(i = 'roads.shp', output = 'topology_errors.shp',
  snap_tolerance = 0.5, report = 'topology_report.txt')

# 2. Five-minute drive catchments from existing stations.
wbw_network_service_area(i                    = 'roads.shp',
  origins              = 'fire_stations.shp',
  output               = 'existing_catchment_5min.shp',
  max_cost             = 5.0,
  output_mode          = 'polygon',
  polygon_merge_origins = TRUE,
  edge_cost_field      = 'MINUTES',
  snap_tolerance       = 20.0)

# 3. Route historical incidents to nearest existing station.
wbw_closest_facility_network(i               = 'roads.shp',
  incidents       = 'historical_incidents.shp',
  facilities      = 'fire_stations.shp',
  output          = 'incident_routes.shp',
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES')

# 4. Find two new station sites that maximise coverage.
wbw_location_allocation_network(i               = 'roads.shp',
  demand_points   = 'historical_incidents.shp',
  facilities      = 'candidate_stations.shp',
  output          = 'new_station_sites.shp',
  facility_count  = 2L,
  solver_mode     = 'maximize_coverage',
  snap_tolerance  = 20.0,
  edge_cost_field = 'MINUTES')

Tips

  • Always run network_topology_audit first — even one disconnected segment can cause a path query to return no result without an explicit error.
  • Use network_connected_components to confirm all origins and destinations belong to the same component before running OD queries.
  • Supply edge_cost_field pointing to a travel-time column for realistic routing; omit it only for pure geometric distance problems.
  • For scheduled transit, pass temporal_cost_profile and departure_time to load time-of-day speeds.
  • The fleet_routing_and_dispatch_optimizer Pro tool requires a session initialised with a valid Pro licence.