by Intelliverse X

Geo platform — self-hosted maps, geocoding & routing (zero Google keys)

Every map-touching feature on the platform — fleet-map pins, set-location by address, pin-move reverse geocoding, Location Scout's venue research, route drive times, and the map tiles themselves — runs on the platform's own OpenStreetMap-based services. There are no Google Maps, Places, or Geocoding API calls anywhere, and no keys to provision or leak.

Geocoding finds map features; it does not validate postal delivery or prove that a cabinet is installed there. The machine address workflow adds structured completeness checks, explicit candidate review and a separate operator installation attestation. Existing geocoded pins remain unverified.

What runs where

Service Software Data Endpoint
Tiles + style TileServer-GL Protomaps US basemap (daily planet build, ODbL) https://tiles.kiosk-x.ai
Geocoder Photon (komoot) GraphHopper US index (weekly) in-cluster, proxied by this API
Routing OSRM (car profile) Geofabrik Texas extract in-cluster, surfaced in route plans

Manifests, decision matrix, and the data-refresh runbook live in intelli-verse-kube-infra/kiosk-x-geo/README.md. Attribution required on any rendered map: © OpenStreetMap contributors © Protomaps.

Tiles (map rendering)

new maplibregl.Map({container: 'map',
  style: 'https://tiles.kiosk-x.ai/styles/kioskx-dark/style.json'})
https://tiles.kiosk-x.ai/styles/kioskx-dark/256/{z}/{x}/{y}{r}.png

kioskx-dark is the Protomaps "black" flavor retinted to the Kiosk-X design tokens, so both surfaces match the apps' deep-space palette out of the box. Raw vector tiles (/data/us/{z}/{x}/{y}.pbf), glyphs, and sprites are served from the same host. No token, no key.

Forward geocoding

GET /api/v1/geo/geocode?address=... — scope machines:read

{"lat": 30.245, "lng": -97.7508,
 "formattedAddress": "1720 South Congress Avenue, Austin, Texas 78704",
 "placeId": "osm:W158834536"}

The same resolver runs when PATCH /api/v1/machines/{no} receives an address (see the Machines guide). placeId is an OSM-derived identifier (osm:<type><id>).

formattedAddress leads with the venue name when the geocoder returns one, because that is how an operator recognises their own placement in a fleet list, and then orders the address components the way the country writes them — Sydney Opera House, 2 Macquarie Street, Sydney New South Wales 2000, Brandenburg Gate, Pariser Platz 1, 10117 Berlin, Meguro Museum of Art, 4-36, Tokyo, 東京都 153-0063. The ordering table in app/address_recipes.py is generated from OpenCage's address-formatting templates by scripts/derive_address_templates.py; a country it does not cover falls back to comma-separated components. US labels are unchanged. Nothing relabels addresses already stored — a machine picks up the newer label the next time its address is resolved or retried.

Reverse geocoding

GET /api/v1/geo/reverse?lat=..&lng=.. — scope machines:read

lat/lng → the nearest address, same shape as forward geocoding. Built for pin-move flows: drag a pin, label the machine with a human-readable address.

GET /api/v1/geo/search?type=school&near=32.7767,-96.797&radius=4.8 — scope machines:read

Answers "how many schools within 3 miles of this corner?" from the self-hosted OSM index. type accepts the Location Scout venue taxonomy (school, gym, cafe, office, ... — the error message lists them all) or a raw OSM tag (amenity:library). radius is km (max 80), limit ≤ 50.

{"type": "school", "center": {"lat": 32.7767, "lng": -96.797},
 "radiusKm": 4.8, "count": 3, "dataSource": "osm-photon",
 "venues": [{"name": "Travis Heights Elementary", "lat": 30.246,
             "lng": -97.75, "distanceKm": 0.3,
             "address": "2010 Alameda Drive, Austin, Texas",
             "osmTag": "amenity=school"}]}

Caveat: Photon indexes named OSM features. That covers schools, gyms, cafes, and everything the venue taxonomy targets; anonymous street furniture (unnamed vending machines, benches) is thin — Location Scout double-checks empty answers against public Overpass mirrors for exactly this reason.

Routing (drive times)

The Operator OS route optimizer (route_runner, territory_balance, ...) orders stops by real OSRM drive times when the deployment sets KIOSKX_OSRM_URL — leg minutes/km in plan steps come from the road network, not straight lines. Coverage today is the Texas extract (the operator demo region); any pin outside it falls back to haversine estimates wholesale, so plans never mix real and estimated legs. Turn-by-turn navigation stays on the operator's phone via native deep links (Google/Apple Maps URLs — no keys needed for those).

Error semantics

Situation Response
Deployment has no KIOSKX_GEOCODER_URL 503 with instructions
Geocoder configured but unreachable 502 with the transport error
Address / area yields no match 422 naming the input
Bad near/radius/type input 422 (or the app-wide 400 envelope for query-type violations)

Configuration

Env var Production value Empty means
KIOSKX_GEOCODER_URL http://kiosk-x-photon.aicart.svc.cluster.local:2322 geocode paths return 503, scout falls back to Overpass/model
KIOSKX_OSRM_URL http://kiosk-x-osrm.aicart.svc.cluster.local:5000 route plans use haversine estimates
KIOSKX_GEOCODE_TIMEOUT 8.0 s
KIOSKX_OSRM_TIMEOUT 4.0 s

No secrets. That is the point.