Skip to content

feat(map): add place/address geocoder search to the map view - #353

Open
jirhiker wants to merge 2 commits into
stagingfrom
claude/geocoder-map-view-632abf
Open

feat(map): add place/address geocoder search to the map view#353
jirhiker wants to merge 2 commits into
stagingfrom
claude/geocoder-map-view-632abf

Conversation

@jirhiker

@jirhiker jirhiker commented Aug 21, 2026

Copy link
Copy Markdown
Member

What

Adds a geocoder (place / address / ZIP search) to the map view.

  • src/utils/geocode.ts — forward geocoding against Photon, komoot's OpenStreetMap-backed search service. Photon returns address components rather than a formatted string, and reports extent as [minLon, maxLat, maxLon, minLat], so this module composes the display label and reorders the box into the [west, south, east, north] order fitBounds expects. US filtering is client-side because Photon has no country parameter.
  • src/components/MapGeocoderSearch.tsx — MUI search box: 300 ms debounce, 3-char minimum, react-query cache (5 min), result dropdown, loading spinner, clear button, Enter selects the first result, Esc clears. Credits OpenStreetMap in the dropdown footer, per ODbL.
  • src/pages/ocotillo/map/list.tsx — new panel at the top of the existing top-left panel stack (Base Maps and Layers shift down by its measured height). Selecting a result fits the map to the result bbox (maxZoom 14) or eases to its center (zoom >= 13), drops a marker, clears any open popup, and captures a map_geocoder_result_selected event. Results are biased toward the current viewport center.

Why Photon

Photon needs no account or API token, which keeps this consistent with src/basemaps.ts — the MapLibre migration deliberately made every tile and data source key-free, and a geocoder with a token would have reintroduced exactly what that migration removed. Photon is also explicitly built for search-as-you-type, unlike Nominatim, whose usage policy forbids autocomplete.

The trade-off: photon.komoot.io is a community instance with no SLA or documented rate limit. The debounce and 3-char minimum keep request volume modest; if it ever becomes load-bearing, self-hosting Photon is the escape hatch.

Note on history

This branch was originally cut from production and first implemented the search against the Mapbox Places API — that was written before I saw that #336 had already landed the MapLibre migration on staging, removing settings.mapboxToken and deleting the unused GeocoderControl.jsx wrapper. The branch is now rebased onto origin/staging as a single commit, per AGENTS.md, and the geocoder is Photon-based. Nothing Mapbox-related remains in the diff.

Testing

  • src/test/utils/geocode.test.ts — 4 unit tests: label composition, extent reordering, duplicate-component dedup, and rejection of non-US / coordinate-less / bad input. Passing.
  • npm run typecheck — clean.
  • Biome reports no new findings in the changed files; the noExplicitAny warnings in map/list.tsx are pre-existing and unchanged in count.
  • Full vitest run could not be trusted locally: this worktree has no installed node_modules, so the contract tests fail on the absent Prism mock server and three suites fail to resolve @mui/icons-material from the parent checkout. CI is authoritative for those.
  • Not yet exercised in a running browser — worth a manual pass on panel stacking and fly-to behavior.

🤖 Generated with Claude Code

Adds a debounced search box to the top-left panel stack on the map view.
Selecting a result fits the map to the result's bounding box (or eases to
its center) and drops a marker; results are biased toward the current
viewport center.

Geocoding goes to Photon, komoot's OpenStreetMap-backed service. Photon
needs no account or token, which keeps the map free of API keys the way
basemaps.ts already does, and unlike Nominatim its usage policy permits
search-as-you-type. Photon returns address components rather than a
formatted label and reports `extent` as [minLon, maxLat, maxLon, minLat],
so utils/geocode.ts composes the display label and reorders the box into
the [west, south, east, north] order fitBounds expects. Results are
filtered to the US client side, since Photon has no country parameter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jirhiker
jirhiker force-pushed the claude/geocoder-map-view-632abf branch from f54981a to d0ed57f Compare August 21, 2026 21:10
@github-actions

Copy link
Copy Markdown

Preview Deployment

Preview URL: https://preview-claude-geocoder-map-view-632abf-auejgdbofq-uc.a.run.app

Note: This preview uses the staging API endpoints.

A query like "socorro" matches both the city and the county relation, and
both compose to "Socorro, New Mexico" — two identical rows in the dropdown
that fly to different extents. Where a label repeats, Photon's own type
classification is now appended to each of the colliding rows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Preview Deployment

Preview URL: https://preview-claude-geocoder-map-view-632abf-auejgdbofq-uc.a.run.app

Note: This preview uses the staging API endpoints.

@jirhiker
jirhiker requested a review from lizalino August 21, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant