Project Hints - dees-catalog-geo
Overview
Geospatial web components library using MapLibre GL JS for map rendering and terra-draw for drawing capabilities.
Key Dependencies
- maplibre-gl (v5.x): WebGL-based vector map library
- terra-draw (v1.24.0): Modern drawing library with support for multiple map libraries
- terra-draw-maplibre-gl-adapter (v1.x): Adapter connecting terra-draw with MapLibre
Component: dees-geo-map
Properties
| Property | Type | Default | Description |
|---|---|---|---|
center |
[number, number] |
[0, 0] |
Map center as [lng, lat] |
zoom |
number |
2 |
Initial zoom level |
mapStyle |
string |
'osm' |
Map style ('osm' or custom URL) |
activeTool |
TDrawTool |
'static' |
Active drawing tool |
geoJson |
GeoJSON.FeatureCollection |
{...} |
Initial features |
showToolbar |
boolean |
true |
Show/hide drawing toolbar |
projection |
'mercator' | 'globe' |
'mercator' |
Map projection type |
showSearch |
boolean |
false |
Show address search input |
showNavigation |
boolean |
false |
Show A-to-B navigation panel |
navigationMode |
'driving' | 'walking' | 'cycling' |
'driving' |
Transport mode for routing |
showTraffic |
boolean |
false |
Enable traffic layer visualization |
trafficApiKey |
string |
'' |
HERE API key for traffic data |
trafficProvider |
ITrafficProvider |
null |
Custom traffic data provider |
enableGuidance |
boolean |
false |
Enable voice-guided navigation |
voiceConfig |
Partial<IVoiceConfig> |
{} |
Voice synthesis configuration |
Drawing Tools (TDrawTool)
point- Draw pointslinestring- Draw linespolygon- Draw polygonsrectangle- Draw rectanglescircle- Draw circlesfreehand- Freehand drawingselect- Select and edit featuresstatic- Pan/zoom only (no drawing)
Events
map-ready- Fired when map is initializedmap-move- Fired on pan/zoom with center and zoomdraw-change- Fired on any feature changedraw-finish- Fired when a shape is completedaddress-selected- Fired when a search result is selectedroute-calculated- Fired when a navigation route is calculated (includes route, startPoint, endPoint, mode)traffic-updated- Fired when traffic data is refreshedguidance-event- Fired during voice-guided navigation (includes type, position, stepIndex, instruction)
Public Methods
getFeatures()- Get all drawn featuresgetGeoJson()- Get features as FeatureCollectionloadGeoJson(geojson)- Load features from GeoJSONclearAllFeatures()- Remove all featuressetTool(tool)- Set active drawing toolflyTo(center, zoom?)- Animate to locationfitToFeatures(padding?)- Fit view to all featuressetProjection(projection)- Set map projection ('mercator' or 'globe')getMap()- Get underlying MapLibre instancegetTerraDraw()- Get TerraDraw instancecalculateRoute()- Calculate route between start and end pointssetNavigationStart(coords, address?)- Set navigation start pointsetNavigationEnd(coords, address?)- Set navigation end pointclearNavigation()- Clear all navigation stateenableTraffic()- Enable traffic visualizationdisableTraffic()- Disable traffic visualizationtoggleTraffic()- Toggle traffic visualizationrefreshTraffic()- Refresh traffic datasetTrafficProvider(provider)- Set custom traffic providersupportsTrafficRouting()- Check if traffic-aware routing is availablegetTrafficController()- Get the TrafficController instancesetPosition(coords, heading?, speed?)- Set current GPS position for navigation guidancestartGuidance()- Start voice-guided navigation for the current routestopGuidance()- Stop voice-guided navigationsetVoiceEnabled(enabled)- Enable/disable voice guidanceisVoiceEnabled()- Check if voice guidance is enabledgetGuidanceState()- Get current navigation guidance stateisNavigating()- Check if actively navigatingcreateMockGPSSimulator(config?)- Create a mock GPS simulator for testing/demogetMockGPSSimulator()- Get the mock GPS simulator instancegetGuidanceController()- Get the NavigationGuideController instancesetNavigationFollowPosition(enabled)- Enable/disable camera following GPS position during navigationsetNavigationFollowBearing(enabled)- Enable/disable camera rotating with heading during navigationsetNavigationPitch(pitch)- Set navigation camera pitch (0-85 degrees, default 60)setNavigationZoom(zoom)- Set navigation zoom level (default 17)getNavigationCameraConfig()- Get full navigation camera configurationsetNavigationCameraConfig(config)- Set navigation camera configuration
Context Menu
Right-click on the map to access a context menu with the following options:
- Drag to Draw - Toggle between drag mode (click-drag for circles/rectangles) and two-click mode
- Globe View - Toggle between globe (3D sphere) and Mercator (flat) projection
- Show Traffic - Toggle traffic layer (requires configured traffic provider)
- Clear All Features - Remove all drawn features from the map
- Fit to Features - Zoom and pan to show all drawn features
Navigation Feature
The navigation panel (showNavigation={true}) provides A-to-B routing using OSRM (Open Source Routing Machine):
- Transport modes: Driving, Walking, Cycling
- Point selection: Type an address or click on the map
- Route display: Blue line overlay with turn-by-turn directions
- Click on step: Click any turn-by-turn step to fly/pan the map to that location
- API: Uses free OSRM API (https://router.project-osrm.org) with fair-use rate limit
- Traffic-aware routing: When a traffic provider is configured, shows duration with/without traffic
Traffic Feature
Live traffic visualization with pluggable provider architecture:
HERE Traffic Provider (Recommended)
// Using API key property
<dees-geo-map trafficApiKey="YOUR_HERE_API_KEY" showTraffic></dees-geo-map>
// Or programmatically
import { HereTrafficProvider } from '@design.estate/dees-catalog-geo';
const map = document.querySelector('dees-geo-map');
const provider = new HereTrafficProvider();
provider.configure({ apiKey: 'YOUR_HERE_API_KEY' });
map.setTrafficProvider(provider);
map.enableTraffic();
Free Tier: 250,000 transactions/month (no credit card required) Sign up at: https://developer.here.com
Valhalla Traffic Provider (Self-Hosted)
import { ValhallaTrafficProvider } from '@design.estate/dees-catalog-geo';
const provider = new ValhallaTrafficProvider();
provider.configure({
serverUrl: 'https://your-valhalla-server.com',
trafficDataUrl: 'https://your-traffic-data-endpoint.com' // optional
});
map.setTrafficProvider(provider);
Traffic Color Legend
- 🟢 Green - Free flow
- 🟡 Yellow - Light congestion
- 🟠 Orange - Moderate congestion
- 🔴 Red - Heavy congestion
- 🔴 Dark Red - Severe/stopped
Development
pnpm install- Install dependenciespnpm watch- Start development server (port 3002)pnpm build- Build for production
File Structure
ts_web/
├── index.ts # Main exports
├── 00_commitinfo_data.ts # Auto-generated
└── elements/
├── index.ts # Elements barrel
├── 00colors.ts # Color definitions
├── 00componentstyles.ts # Shared styles
└── 00group-map/
├── index.ts
└── dees-geo-map/
├── index.ts # Exports main + modules
├── dees-geo-map.ts # Main component
├── dees-geo-map.demo.ts # Demo function
├── geo-map.icons.ts # Icon SVG definitions
├── geo-map.search.ts # SearchController class
├── geo-map.navigation.ts # NavigationController class
├── geo-map.traffic.ts # TrafficController class
├── geo-map.traffic.providers.ts # Traffic provider implementations
├── geo-map.voice.ts # VoiceSynthesisManager class
├── geo-map.navigation-guide.ts # NavigationGuideController class
└── geo-map.mock-gps.ts # MockGPSSimulator class
Modular Architecture
The component was refactored for better maintainability:
geo-map.icons.ts
Contains all SVG icon definitions as a GEO_MAP_ICONS record and a renderIcon(name) helper function.
geo-map.search.ts
SearchController class encapsulating Nominatim geocoding search:
- Reusable for standalone search or within navigation
- Debounced API calls
- Keyboard navigation support
- Customizable via
ISearchControllerConfig
geo-map.navigation.ts
NavigationController class for A-to-B routing:
- OSRM routing API integration
- Start/end point management with markers
- Map click mode for point selection
- Turn-by-turn directions rendering
- Route overlay on map
- Traffic-aware routing integration (shows congestion level and delay)
geo-map.traffic.ts
TrafficController class for live traffic visualization:
- Traffic layer rendering with color-coded congestion
- Auto-refresh logic with configurable interval
- Supports pluggable traffic providers
geo-map.traffic.providers.ts
Traffic data provider implementations:
HereTrafficProvider- HERE Traffic API v7 (freemium)ValhallaTrafficProvider- Self-hosted Valhalla server
geo-map.voice.ts
VoiceSynthesisManager class for voice-guided navigation:
- Uses Web Speech API for text-to-speech
- Queue-based speech with interrupt capability for urgent instructions
- Configurable language, rate, pitch, volume
- Navigation-specific methods:
speakApproach(),speakManeuver(),speakArrival(),speakOffRoute() - Graceful fallback if speech synthesis not supported
geo-map.navigation-guide.ts
NavigationGuideController class for real-time GPS navigation guidance:
- Position tracking with GPS updates via
updatePosition()orsetPosition() - Step progression tracking along route
- Distance thresholds for voice announcements (500m, 200m, 50m, at maneuver)
- Off-route detection (>50m from route line)
- Arrival detection (within 30m of destination)
- Renders GPS position marker on map with heading indicator
- Emits guidance events:
approach-maneuver,execute-maneuver,step-change,off-route,arrived - Camera following: Automatically moves camera to follow GPS position with smooth transitions
- Camera configuration: Configurable pitch (60° default for 3D view), zoom (17 default), and bearing following
geo-map.mock-gps.ts
MockGPSSimulator class for testing/demo:
- Interpolates positions along route geometry
- Speed presets: Walking (5 km/h), Cycling (20 km/h), City (50 km/h), Highway (100 km/h)
- Configurable update interval and GPS jitter
- Calculates heading between consecutive points
- Methods:
start(),pause(),stop(),jumpToProgress()
Usage of Controllers
// SearchController is reusable
const search = new SearchController(
{ placeholder: 'Search...' },
{
onResultSelected: (result, coords, zoom) => { /* handle */ },
onRequestUpdate: () => this.requestUpdate(),
}
);
// NavigationController manages all navigation state
const nav = new NavigationController({
onRouteCalculated: (event) => { /* dispatch */ },
onRequestUpdate: () => this.requestUpdate(),
getMap: () => this.map,
});
Notes
- MapLibre CSS is loaded dynamically from CDN
- Terra-draw requires the separate maplibre-gl-adapter package
- The component uses Shadow DOM for style encapsulation
UI Layout
The component uses a CSS Grid layout with sidebars that push the map (not overlay):
┌──────────────────────────────────────────────────────────────────────┐
│ HEADER TOOLBAR │
│ [Nav Toggle] | [Search Bar] | [Draw Toggle] [Traffic] [+/-] │
├───────────────┬──────────────────────────────┬───────────────────────┤
│ LEFT SIDEBAR │ MAP │ RIGHT SIDEBAR │
│ (Navigation) │ │ (Draw Tools) │
│ │ │ │
│ - Mode select │ │ [Point] [Line] │
│ - Start input │ │ [Polygon][Rect] │
│ - End input │ │ [Circle] [Free] │
│ - Route steps │ │ ───────────────── │
│ │ [Traffic Legend] │ [Select & Edit] │
│ │ [Feature Count] │ [Clear All] │
└───────────────┴──────────────────────────────┴───────────────────────┘
CSS Grid Layout:
- Grid columns:
var(--left-panel-width) 1fr var(--right-panel-width) - Grid rows:
auto 1fr(header + content) - Sidebars push the map area, not overlay it
Header Toolbar Sections:
- Left: Navigation panel toggle button
- Center: Search bar (expandable width)
- Right: Draw panel toggle, Traffic toggle, Zoom in/out buttons
Left Sidebar (300px when open):
- Contains NavigationController render output
- Slides in/out with 0.25s ease transition
- Default open when
showNavigation={true}
Right Sidebar (180px when open):
- Draw tools panel with 2-column grid layout
- Tool buttons with icons AND labels
- Select & Edit and Clear All actions
- Slides in/out with 0.25s ease transition
- Default open when
showToolbar={true}
Map Overlays (remaining):
- Traffic legend: Bottom-left overlay (when traffic enabled)
- Feature count: Bottom-left overlay (when features exist)
Z-index hierarchy:
z-index: 20- Dropdowns (search results, nav search results)z-index: 10- Header toolbarz-index: 5- Map overlays (traffic legend, feature count)
Dark/Light Theme Support
The component fully supports automatic dark/light theme switching:
UI Elements Theming:
- All UI elements (toolbar, panels, search, navigation) use
cssManager.bdTheme()for automatic color switching - Pattern:
cssManager.bdTheme('lightValue', 'darkValue')- first arg is light theme, second is dark
Map Tiles Theming:
- When
mapStyle="osm"(default), the map automatically switches between:- Light theme: CartoDB Voyager GL (vector tiles) - clean, detailed style
- Dark theme: CartoDB Dark Matter GL (vector tiles) - sleek dark style
- Theme changes are detected via
domtools.themeManager.themeObservable - No API key required for CartoDB basemaps
Semantic Colors (unchanged by theme):
- Navigation markers: Green (#22c55e) for start, Red (#ef4444) for end
- Route line: Blue (#3b82f6) with dark outline (#1e40af)
- Traffic congestion: Green → Yellow → Orange → Red → Dark Red (universal traffic colors)
Shadow DOM & Terra-Draw Drawing Fix
Terra-draw's event listeners normally intercept map events through MapLibre's canvas element. In Shadow DOM contexts, these events are scoped locally and don't propagate correctly, causing terra-draw handlers to fail while MapLibre's drag handlers continue working.
Solution: Manual drag coordination in setTool():
- When a drawing tool is active (
polygon,rectangle,point,linestring,circle,freehand), MapLibre'sdragPananddragRotateare disabled - When
staticorselectmode is active, dragging is re-enabled - The
TerraDrawMapLibreGLAdapterdoes NOT accept alibparameter - onlymapis required
Voice-Guided Navigation Feature
Real-time GPS tracking with voice-guided turn-by-turn instructions:
How to Use
- Calculate a route using the navigation panel (set start and end points)
- Call
startGuidance()to begin voice-guided navigation - Update position with
setPosition([lng, lat], heading?, speed?)or usecreateMockGPSSimulator()for testing - Listen to
guidance-eventfor navigation updates - Call
stopGuidance()when done
Example Usage
const map = document.querySelector('dees-geo-map');
// 1. Set up route
map.setNavigationStart([8.68, 50.11], 'Frankfurt');
map.setNavigationEnd([8.75, 50.08], 'Sachsenhausen');
await map.calculateRoute();
// 2. Listen to guidance events
map.addEventListener('guidance-event', (e) => {
console.log(e.detail.type, e.detail.instruction);
});
// 3. Start guidance with mock GPS simulation
const simulator = map.createMockGPSSimulator({ speed: 'city' });
map.startGuidance();
simulator.start();
// 4. Or use real GPS input
navigator.geolocation.watchPosition((pos) => {
map.setPosition([pos.coords.longitude, pos.coords.latitude], pos.coords.heading, pos.coords.speed);
});
Voice Announcements
- 500m: "In 500 meters, turn left onto Main Street"
- 200m: "In 200 meters, turn left"
- 50m: "Turn left ahead"
- At maneuver: "Turn left now"
- Arrival: "You have arrived at your destination"
- Off-route: "You are off route. Recalculating."
Guidance Event Types
approach-maneuver- Approaching a turn/maneuverexecute-maneuver- At the maneuver point (urgent)step-change- Advanced to next route stepoff-route- Deviated more than 50m from routearrived- Within 30m of destinationposition-updated- Position was updated
Camera Following
During navigation, the map camera automatically:
- Follows position: Centers on current GPS location with smooth continuous transitions
- Follows bearing: Rotates map to match driving direction (heading)
- 3D tilt: Uses 60° pitch by default for immersive driving view
- Street-level zoom: Uses zoom level 17 for optimal route visibility
- Smooth animation: Animation duration dynamically matches GPS update interval for fluid movement (no jerky pauses)
Camera behavior can be customized:
// Disable camera following (user can pan freely)
map.setNavigationFollowPosition(false);
// Disable bearing rotation (map stays north-up)
map.setNavigationFollowBearing(false);
// Use flat (2D) view instead of tilted
map.setNavigationPitch(0);
// Zoom out for wider view
map.setNavigationZoom(15);
// Or set multiple options at once
map.setNavigationCameraConfig({
followPosition: true,
followBearing: true,
pitch: 60,
zoom: 17,
});