SGERP Map
Interactive Mapbox GL map component with state-based configuration
Live Example
Operations Location Map
Interactive map showing operations locations with hover tooltips.
Features
- Hover over locations to see details
- Change map style using the dropdown (top-left)
- Zoom and pan using mouse or touch
- Data automatically updates when collection changes
Overview
The SGERPMap component is a flexible Mapbox GL wrapper that uses a state-based configuration approach. Instead of imperatively managing map sources and layers, you define a MapStateConfig object, and the component handles initialization and updates automatically.
Prerequisites
- Install required dependencies:
npm install mapbox-gl
- Set your Mapbox token in
.env.local:
NEXT_PUBLIC_MAPBOX_TOKEN=your_mapbox_token_here
Basic Usage
import { SGERPMap } from '@/components/sgerp/map';
import type { MapStateConfig } from '@/components/sgerp/map';
const mapState: MapStateConfig = {
mapLayers: {
locations: {
source: {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
},
layers: [{
id: 'locations-layer',
type: 'circle',
source: 'locations',
paint: {
'circle-radius': 8,
'circle-color': '#3b82f6'
}
}],
interactions: [{
layer_id: 'locations-layer',
interaction_type: 'hover',
properties: ['name', 'code']
}]
}
}
};
export function MyMap() {
return <SGERPMap state={mapState} />;
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
state | MapStateConfig | Required | Map configuration with sources, layers, and interactions |
mapKey | string | 'sgerp-map' | Unique key for localStorage (style preference) |
initialCenter | [number, number] | [103.8198, 1.3521] | Initial map center [lng, lat] |
initialZoom | number | 12 | Initial zoom level |
className | string | '' | Additional CSS classes |
style | React.CSSProperties | {} | Inline styles for container |
MapStateConfig Structure
interface MapStateConfig {
mapBoundingBox?: [[number, number], [number, number]];
mapLayers: {
[key: string]: {
source: mapboxgl.AnySourceData;
layers: mapboxgl.AnyLayer[];
interactions?: Array<{
layer_id: string;
interaction_type: 'hover' | 'click';
properties: string | string[];
}>;
};
};
}
Using with Collections
OperationsLocation Example
'use client'
import { useEffect, useState, useMemo } from 'react';
import { useSGERP } from 'sgerp-frontend-lib';
import { SGERPMap } from '@/components/sgerp/map';
import { OperationsLocationMapState } from 'sgerp-frontend-lib/lib/sgerp/map-states';
export function LocationsMap() {
const api = useSGERP();
const [loaded, setLoaded] = useState(false);
// Fetch locations
useEffect(() => {
if (!api) return;
async function loadLocations() {
await api.collections.operationslocation.fetch({ limit: 1000 });
setLoaded(true);
}
loadLocations();
}, [api]);
// Create map state with GeoJSON data
const mapState = useMemo(() => {
if (!api || !loaded) return OperationsLocationMapState;
const geojson = api.collections.operationslocation.toGeoJSON();
return {
...OperationsLocationMapState,
mapLayers: {
locations: {
...OperationsLocationMapState.mapLayers.locations,
source: {
type: 'geojson',
data: geojson,
},
},
},
};
}, [api, loaded]);
return <SGERPMap state={mapState} />;
}
Predefined Map States
The library includes predefined map states for common use cases:
OperationsLocationMapState
Basic blue circle markers for locations:
import { OperationsLocationMapState } from 'sgerp-frontend-lib/lib/sgerp/map-states';
const mapState = {
...OperationsLocationMapState,
mapLayers: {
locations: {
...OperationsLocationMapState.mapLayers.locations,
source: {
type: 'geojson',
data: myGeoJSON,
},
},
},
};
OperationsLocationByProjectMapState
Color-coded by project ID:
import { OperationsLocationByProjectMapState } from 'sgerp-frontend-lib/lib/sgerp/map-states';
Custom Map States
Custom Colors
const customMapState: MapStateConfig = {
mapLayers: {
data: {
source: {
type: 'geojson',
data: geojson,
},
layers: [{
id: 'custom-layer',
type: 'circle',
source: 'data',
paint: {
'circle-radius': 10,
'circle-color': ['get', 'color'], // Use color from properties
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff',
},
}],
interactions: [{
layer_id: 'custom-layer',
interaction_type: 'hover',
properties: '*', // Show all properties
}],
},
},
};
Multiple Layers
const multiLayerState: MapStateConfig = {
mapLayers: {
locations: {
source: {
type: 'geojson',
data: locationsGeoJSON,
},
layers: [{
id: 'locations-circles',
type: 'circle',
source: 'locations',
paint: {
'circle-radius': 8,
'circle-color': '#3b82f6',
},
}],
},
routes: {
source: {
type: 'geojson',
data: routesGeoJSON,
},
layers: [{
id: 'routes-lines',
type: 'line',
source: 'routes',
paint: {
'line-color': '#ef4444',
'line-width': 3,
},
}],
},
},
};
Dynamic Updates
The map automatically updates when the state changes:
const [filter, setFilter] = useState<number | null>(null);
const mapState = useMemo(() => {
const geojson = api?.collections.operationslocation.toGeoJSON();
// Filter features based on project
if (filter && geojson) {
geojson.features = geojson.features.filter(
f => f.properties?.project_id === filter
);
}
return {
...OperationsLocationMapState,
mapLayers: {
locations: {
...OperationsLocationMapState.mapLayers.locations,
source: { type: 'geojson', data: geojson },
},
},
};
}, [api, filter]);
return (
<>
<select onChange={(e) => setFilter(Number(e.target.value))}>
<option value="">All Projects</option>
<option value="759">Project 759</option>
<option value="760">Project 760</option>
</select>
<SGERPMap state={mapState} />
</>
);
Interactions
Hover Tooltips
interactions: [{
layer_id: 'my-layer',
interaction_type: 'hover',
properties: ['name', 'code', 'address'], // Specific properties
}]
Show All Properties
interactions: [{
layer_id: 'my-layer',
interaction_type: 'hover',
properties: '*', // All properties
}]
Click Events
interactions: [{
layer_id: 'my-layer',
interaction_type: 'click',
properties: ['name'],
}]
Map Styles
The component includes 5 built-in map styles:
- Light - Clean light theme (default)
- Dark - Dark theme
- Satellite - Satellite imagery with labels
- Street - Detailed street map
- Outdoors - Terrain and outdoor features
Users can switch styles using the dropdown in the top-left corner. The selected style is saved to localStorage.
Bounding Box
Automatically fit map to data:
const mapState: MapStateConfig = {
mapBoundingBox: [
[minLng, minLat],
[maxLng, maxLat]
],
mapLayers: { ... }
};
Real-time Updates
Combine with live updates for dynamic maps:
import { useLiveUpdates } from 'sgerp-frontend-lib';
useLiveUpdates(api);
useEffect(() => {
if (!api) return;
return api.collections.operationslocation.onChange(() => {
// Map will automatically re-render with new data
forceUpdate();
});
}, [api]);
Styling
Custom Height
<SGERPMap
state={mapState}
style={{ height: '600px' }}
/>
Fullscreen
<SGERPMap
state={mapState}
className="h-screen"
/>