Skip to content

πŸ—ΊοΈ Map & Geospatial ​

The map tab uses @rnmapbox/maps v10 with Turf.js for geospatial calculations.

Token Setup ​

Two Mapbox tokens are required:

TokenEnv VarTypeUsage
Public (pk.)EXPO_PUBLIC_MAPBOX_ACCESS_TOKENRuntimeMap rendering
Secret (sk.)RNMAPBOX_MAPS_DOWNLOAD_TOKENBuild-time onlyDownloads Mapbox SDK during pod install / Gradle

CAUTION

Never commit the secret sk. token. It is only needed in .env locally and in CI secrets β€” not in source code.

Layer Components (components/map/) ​

ComponentPurpose
map-viewRoot MapboxGL.MapView wrapper with camera, gesture config
fields-layerRenders company fields as filled polygons
field-drawing-layerLive preview layer while drawing a new field
notes-layerRenders note pins (location-tagged drafts)
parcels-layerRenders cadastral parcels from the API
map-controlsZoom, compass, and locate-me buttons
map-drawing-toolbarUndo, finish, cancel toolbar for polygon drawing

Tapping Features ​

Tapping a field, note, or parcel opens the corresponding action sheet via SheetManager:

tsx
// Simplified from fields-layer
onPress={(feature) => {
  SheetManager.show('field-action-sheet', {
    payload: { fieldId: feature.id },
  });
}}

Drawing Fields (hooks/use-drawing.ts) ​

Field drawing uses a useReducer state machine:

idle β†’ drawing β†’ (undo removes last vertex) β†’ confirming β†’ saved
                                                          ↓
                                                       cancelled β†’ idle

Key actions:

ts
const { state, addVertex, undo, finishDrawing, cancelDrawing } = useDrawing();

// state.vertices: [lng, lat][] β€” the polygon points so far
// state.mode: 'idle' | 'drawing' | 'confirming'

Parcel Loading ​

Parcels are fetched by map bounding box to avoid loading the entire country dataset. The useParcels(bbox) hook debounces bounds changes by 500ms to avoid hammering the API while the user pans.

ts
const [bbox, setBbox] = useState<BoundingBox | null>(null);
const { data: parcels } = useParcels(bbox);

// Update bbox in onMapIdle or onRegionDidChange (debounced externally)

The bbox parameter is [minLng, minLat, maxLng, maxLat].

Turf.js Utilities (lib/map/) ​

Common geospatial helpers used in the app:

ts
import * as turf from '@turf/turf';

// Check if a point is inside a polygon (e.g. note inside field)
turf.booleanPointInPolygon(point, polygon);

// Calculate field area in hectares
const area = turf.area(polygon) / 10_000; // mΒ² β†’ ha

// Get bounding box of a polygon
const [minLng, minLat, maxLng, maxLat] = turf.bbox(polygon);