Maps¶
Tomoda renders maps through the MapLibre rendering stack. The same shared types and marker spec drive both surfaces, but each platform uses its native renderer:
| Platform | Renderer | Package |
|---|---|---|
| Web | MapLibre GL JS | maplibre-gl |
| iOS / Android | MapLibre Native (Metal / OpenGL ES) | @maplibre/maplibre-react-native |
No WebView. No JS bridge for tile rendering. Markers on native are real React Native views composited by the platform.
Where the code lives¶
| Path | Role |
|---|---|
frontend/components/map/MapView.tsx |
Shared types: MapMarker, MapRegion, MapControls, MapViewProps. |
frontend/components/map/MapView.web.tsx |
Web shim. Mounts <MapEngine> inline. |
frontend/components/map/MapView.native.tsx |
Native shim. Wraps @maplibre/maplibre-react-native <Map> + <Marker> per marker. |
frontend/components/map/engine/MapEngine.tsx |
Web-only MapLibre GL JS wrapper. Owns the JS map instance, marker DOM diff loop, style swap, event emission. |
frontend/components/map/engine/styles.ts |
Shared style descriptors (CARTO raster source, light/dark). |
frontend/components/map/engine/markers/ |
Web-only HTML marker components (divs + CSS for hover/glass-card expansion). |
frontend/components/map/native/MarkerOverlay.tsx |
Native marker components (RN primitives: View, Image, Text, react-native-svg). |
frontend/components/map/MapAttribution.tsx |
Shared compact ⓘ pill (hidden by default; tap/hover reveals OSM + CARTO credit). |
Tiles¶
Both platforms consume CARTO raster basemaps (basemaps.cartocdn.com). Free, no API key. Light + dark variants selected via the mapTheme prop.
To move to vector tiles or self-host, edit engine/styles.ts. The descriptor is passed through to both renderers identically.
Marker API¶
Callsites pass a flat markers: MapMarker[] array. The union is discriminated by type:
| Variant | Visual |
|---|---|
pin |
Generic single-point pin. Used by LocationPicker. |
user |
Current user's avatar tile. |
friend |
Friend avatar tile with optional online/active status dots. |
cluster |
Stacked-avatar cluster bubble with count. |
discovery |
Unified marker for event, location, moment, geo_cluster sub-kinds. |
onMarkerPress(id) fires when any marker is tapped. Marker ids are caller-controlled.
Adding a new marker variant¶
- Extend the
MapMarkerunion inMapView.tsx. - Add a web case in
engine/markers/index.tsx(HTML primitives). - Add a native case in
native/MarkerOverlay.tsx(RN primitives). - Add styling for both. Reuse
MARKER_COLORS+getEventIcon/getLocationIconfromcomponents/ui/categoryIcons.tsfor visual consistency.
Map controls¶
onMapReady receives MapControls once the map loads:
| Method | Web | Native |
|---|---|---|
zoomIn() / zoomOut() |
map.zoomIn() / map.zoomOut() |
cameraRef.zoomTo(zoom ± 1) |
animateTo(lat, lng, zoom?) |
map.flyTo |
cameraRef.flyTo |
panTo(lat, lng, padding?) |
map.easeTo({ padding }) |
cameraRef.easeTo({ padding }) |
canZoomIn() / canZoomOut() |
Compare with getMaxZoom / getMinZoom |
Always returns true |
panTo is the right call when a UI panel covers part of the viewport — the padding rect tells the map which area to centre within.
Why this stack¶
- No WebView on native — tile rendering, gestures, and marker compositing all run on the GPU via Metal / OpenGL ES. Pan and zoom stay smooth even with hundreds of markers in view.
- Same
MapMarkerschema on both platforms — adding a marker kind is a coordinated change but the data model + visual spec live in one place. - No tile-server infra — CARTO raster basemaps are free; a future migration to self-hosted vector tiles only changes
engine/styles.ts.