StreamMaps

StreamMaps is a map based web app that estimates how much upstream habitat each river/stream barrier blocks.


About the project

Stream restoration decisions, made by government officials and promoted by environmental activists, are constrained by time and limited field data. StreamMaps takes raw open map data and makes looking and analyzing stream barriers interactive. You can pan the map, analyze, see what’s blocking connectivity and share results.


What inspired us

We were inspired by the clarity and usability of the Apple Maps website: the map stays central, UI panels feel lightweight, and everything is discoverable without overwhelming the screen. We wanted that same “clean map product” feeling, but focused on stream barriers and restoration impact.


What we learned

  • How to integrate MapLibre GL into a React/Next.js app and keep the map stable while UI state changes.
  • How to implement custom HTML markers that stay perfectly aligned with the map (including selection states and zoom-based visibility).
  • How to build and style multiple GeoJSON layers (streams, accessible streams, barriers, reports, bounding box overlays).
  • How to query and cache OpenStreetMap data via Overpass and place search via Nominatim.
  • How to infer approximate flow direction using a DEM elevation API and apply it to stream graph orientation.
  • How to design a lightweight backend “job + polling” workflow for analysis.

How we built our project (pipeline)

  1. The frontend starts analysis with POST /analyze sending the current map viewport bbox.
  2. The server creates an in-memory job and immediately begins work:
    • Overpass fetch of waterways and barrier-tagged features
    • optional bbox tiling fallback if Overpass times out
    • DEM sampling of stream way endpoints using Open-Meteo elevation to infer downhill direction
  3. The server builds directed stream graph
    • finds outlet-like nodes
    • BFS upstream using reverse adjacency
    • snaps barriers to stream nodes (spatial hash within ~30m)
    • assigns each stream segment to the “first downstream barrier”
    • sums segment lengths, gain_km per barrier
  4. The server returns payload
    • streams_geojson
    • accessible_streams_geojson
    • barriers_geojson
    • rankings[] (sort by gain)
  5. Results are cached on server disk (./cache/...)

Community reporting

  1. Users can submit:
    • a location-based report (pick a point on the map), or
    • a barrier-based report (“incorrect barrier”, etc.)
  2. Reports are stored append-only in data/reports.jsonl.
  3. The server aggregates reports + feedback (confirm/renounce) into a confidence score.
  4. The frontend renders report pins and can incorporate report signals into barrier confidence.

Sharing deep links

  • Barrier details can be shared via a link containing a serialized barrier_payload.
  • Report details can be shared via a link containing report=<id> + metadata.
  • On load, the app reads the URL params, flies to the location, and opens the correct details view.

The challenges we faced

  • API rate limiting and timeouts We added:
    • on-disk caching for Overpass, search, DEM, and final results
    • fallback to multiple Overpass endpoints
    • bbox tiling fallback when large queries fail

    - limiting marker counts

how to run locally:

npm install
npm run dev

Built With

Share this project:

Updates