Why

Government officials, planners, and citizens all see wheelchair accessibility barriers, preventing wheelchair-bound citizens from more easily accessing healthcare and other essential services. However, they lack a shared and streamlined way to decide which barrier removals matter most. I built AccessMaps to help people make that decision, by visualizing and sorting the largest barriers to wheelchair accessibility.

AccessMaps directly helps solve the question: Which accessibility mobility barrier should be fixed next to unlock the most accessible routes and destinations?

What

AccessMaps delivers a simple, easy to understand workflow for our Why:

  • Search a location and analyze the current map area.
  • Detect and rank mobility blockers (stairs, raised kerbs, steep inclines, rough surfaces, wheelchair restrictions, and report-derived blockers).
  • Show per-blocker impact metrics: accessible unlock distance, blocked segment, confidence, and score deltas.
  • Visualize the analysis on an interactive map with selectable blockers and reports.
  • Submit new accessibility reports, confirm or renounce existing reports, and generate shareable links for barriers/reports.
  • And a bunch of other QOL features :)

Inspiration

I was inspired by the wheelchair accessibility filter on Google Maps for route finding as well as the Apple Maps UI design.

How (General Summary)

AccessMaps creates ranking by combining geospatial data, graph analysis, and community reports into one decision pipeline:

  • Use APIs for map and place data
  • Build a pedestrian accessibility graph and use union-find connected-component analysis to estimate how each barrier splits the network.
  • Simulate barrier removal and rank candidates by unlock distance, destination access improvements, and score deltas (NAS/OAS/General Index) using a use union-find.
  • Blend API barriers with user reports (including confirmations/renouncements) to score the confidence of each barrier actually existing.

The nitty gritty technical stuff

I built AccessMaps as a TypeScript web app:

  • Frontend: Next.js + React client state determines map interactions, analysis jobs, and report workflows. The map stack is MapLibre with managed GeoJSON sources/layers and custom marker overlays for blockers/reports.
  • Refresh-map BBox button: the top-center Refresh map control reads the current viewport BBox from the map controller, resets analysis state, and triggers AnalyzeLoading. That state posts POST /analyze with the exact BBox, then polls GET /result/:job_id until completion.
  • Nearby blockers: Nearby Blockers intentionally avoids triggering a new analysis run (OpenStreetMap AI requests). It loads previously computed rankings from /bootstrap (cache-backed), unless the refresh map button is pressed.
  • BBox: server-side schemas enforce normalized BBoxes and max area limits before processing (BBox too large for demo guardrails)
  • Overpass ingestion: Overpass requests are cache-keyed and, on timeout/failure for larger regions, recursively tiled into 4 sub-BBoxes (quadtree-style split) and merged with element de-duplication.
  • Graph construction: pedestrian ways are converted into an undirected accessibility graph (nodes/edges) with per-edge accessibility classification (PASS, LIMITED, BLOCKED) from OSM tags such as highway=steps, wheelchair=no, kerb=raised, incline/surface/smoothness, and access restrictions.
  • DSU for connectivity: a Disjoint Set Union (Union-Find) computes passable connected components efficiently. This is what I used to measure what network area and destinations are reachable before and after simulated fixes.
  • POI/anchor snapping: healthcare/essential POIs are snapped to graph nodes within configurable max distance thresholds; unsnapped POIs are tracked explicitly. Anchor points fall back to nearest graph node when direct snapping fails.
  • Counterfactual simulation: for each candidate blocking edge, we simulate 'fixing' it, recompute reachable component expansion from the base component, and estimate unlocked path meters + newly reachable destinations.
  • Scoring: rankings use explicit deltas for NAS/OAS/General Accessibility Index and a weighted score: score = 3 * delta_general + unlock_m / 750 + confidence_bonus - fix_cost_penalty.
  • Candidate reduction: blockers are grouped by component-connection key; only the highest-impact candidate per group is kept, then globally sorted and capped for UI responsiveness.
  • Reports: new barriers on the map can be created, called reports, which take confirmations/renouncements into their effective confidence. OSM API based barriers also support reporting which reduces confidence.
  • Caching layers: filesystem JSON caches store Overpass responses, final analysis payloads, DEM lookups, search responses, share payloads, report snap results, and report metric snapshots.
  • Data output: backend returns fully structured GeoJSON plus metadata (score components, assumptions, warnings, counts, provenance IDs).

What I learned

  • Real-world applications of graph theory and disjoint set unions.
  • How to cache viewport and BBox scope to prevent of API rate limiting.

Challenges I faced

  • Balancing the quality of the analysis output with API rate limits.
  • Snapping POIs and report coordinates to a usable pedestrian network.
  • Keeping map UX decently fast while rendering multiple data layers and offering multiple interaction modes.
Share this project:

Updates