2026-02-05 12:03:22 +00:00
# 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 |
2026-02-05 15:07:33 +00:00
| `showTraffic` | `boolean` | `false` | Enable traffic layer visualization |
| `trafficApiKey` | `string` | `''` | HERE API key for traffic data |
| `trafficProvider` | `ITrafficProvider` | `null` | Custom traffic data provider |
2026-02-05 17:50:45 +00:00
| `enableGuidance` | `boolean` | `false` | Enable voice-guided navigation |
| `voiceConfig` | `Partial<IVoiceConfig>` | `{}` | Voice synthesis configuration |
2026-02-05 12:03:22 +00:00
### Drawing Tools (TDrawTool)
- `point` - Draw points
- `linestring` - Draw lines
- `polygon` - Draw polygons
- `rectangle` - Draw rectangles
- `circle` - Draw circles
- `freehand` - Freehand drawing
- `select` - Select and edit features
- `static` - Pan/zoom only (no drawing)
### Events
- `map-ready` - Fired when map is initialized
- `map-move` - Fired on pan/zoom with center and zoom
- `draw-change` - Fired on any feature change
- `draw-finish` - Fired when a shape is completed
- `address-selected` - Fired when a search result is selected
- `route-calculated` - Fired when a navigation route is calculated (includes route, startPoint, endPoint, mode)
2026-02-05 15:07:33 +00:00
- `traffic-updated` - Fired when traffic data is refreshed
2026-02-05 17:50:45 +00:00
- `guidance-event` - Fired during voice-guided navigation (includes type, position, stepIndex, instruction)
2026-02-05 12:03:22 +00:00
### Public Methods
- `getFeatures()` - Get all drawn features
- `getGeoJson()` - Get features as FeatureCollection
- `loadGeoJson(geojson)` - Load features from GeoJSON
- `clearAllFeatures()` - Remove all features
- `setTool(tool)` - Set active drawing tool
- `flyTo(center, zoom?)` - Animate to location
- `fitToFeatures(padding?)` - Fit view to all features
- `setProjection(projection)` - Set map projection ('mercator' or 'globe')
- `getMap()` - Get underlying MapLibre instance
- `getTerraDraw()` - Get TerraDraw instance
- `calculateRoute()` - Calculate route between start and end points
- `setNavigationStart(coords, address?)` - Set navigation start point
- `setNavigationEnd(coords, address?)` - Set navigation end point
- `clearNavigation()` - Clear all navigation state
2026-02-05 15:07:33 +00:00
- `enableTraffic()` - Enable traffic visualization
- `disableTraffic()` - Disable traffic visualization
- `toggleTraffic()` - Toggle traffic visualization
- `refreshTraffic()` - Refresh traffic data
- `setTrafficProvider(provider)` - Set custom traffic provider
- `supportsTrafficRouting()` - Check if traffic-aware routing is available
- `getTrafficController()` - Get the TrafficController instance
2026-02-05 17:50:45 +00:00
- `setPosition(coords, heading?, speed?)` - Set current GPS position for navigation guidance
- `startGuidance()` - Start voice-guided navigation for the current route
- `stopGuidance()` - Stop voice-guided navigation
- `setVoiceEnabled(enabled)` - Enable/disable voice guidance
- `isVoiceEnabled()` - Check if voice guidance is enabled
- `getGuidanceState()` - Get current navigation guidance state
- `isNavigating()` - Check if actively navigating
- `createMockGPSSimulator(config?)` - Create a mock GPS simulator for testing/demo
- `getMockGPSSimulator()` - Get the mock GPS simulator instance
- `getGuidanceController()` - Get the NavigationGuideController instance
- `setNavigationFollowPosition(enabled)` - Enable/disable camera following GPS position during navigation
- `setNavigationFollowBearing(enabled)` - Enable/disable camera rotating with heading during navigation
- `setNavigationPitch(pitch)` - Set navigation camera pitch (0-85 degrees, default 60)
- `setNavigationZoom(zoom)` - Set navigation zoom level (default 17)
- `getNavigationCameraConfig()` - Get full navigation camera configuration
- `setNavigationCameraConfig(config)` - Set navigation camera configuration
2026-02-05 12:03:22 +00:00
### 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
2026-02-05 15:07:33 +00:00
- **Show Traffic** - Toggle traffic layer (requires configured traffic provider)
2026-02-05 12:03:22 +00:00
- **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
2026-02-05 15:49:07 +00:00
- **Click on step**: Click any turn-by-turn step to fly/pan the map to that location
2026-02-05 12:03:22 +00:00
- **API**: Uses free OSRM API (https://router.project-osrm.org) with fair-use rate limit
2026-02-05 15:07:33 +00:00
- **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)
```typescript
// 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)
```typescript
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
2026-02-05 12:03:22 +00:00
## Development
- `pnpm install` - Install dependencies
- `pnpm 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
2026-02-05 15:07:33 +00:00
├── dees-geo-map.ts # Main component
2026-02-05 12:03:22 +00:00
├── dees-geo-map.demo.ts # Demo function
2026-02-05 15:07:33 +00:00
├── 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
2026-02-05 17:50:45 +00:00
├── 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
2026-02-05 12:03:22 +00:00
```
## 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
2026-02-05 15:07:33 +00:00
- 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
2026-02-05 12:03:22 +00:00
2026-02-05 17:50:45 +00:00
### 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()` or `setPosition()`
- 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()`
2026-02-05 12:03:22 +00:00
### Usage of Controllers
```typescript
// 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
2026-02-05 15:07:33 +00:00
### UI Layout
2026-02-05 15:49:07 +00:00
The component uses a CSS Grid layout with sidebars that **push ** the map (not overlay):
2026-02-05 15:07:33 +00:00
```
┌──────────────────────────────────────────────────────────────────────┐
│ HEADER TOOLBAR │
2026-02-05 15:49:07 +00:00
│ [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] │
└───────────────┴──────────────────────────────┴───────────────────────┘
2026-02-05 15:07:33 +00:00
```
2026-02-05 15:49:07 +00:00
**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
2026-02-05 15:07:33 +00:00
**Header Toolbar Sections:**
2026-02-05 15:49:07 +00:00
- **Left**: Navigation panel toggle button
2026-02-05 15:07:33 +00:00
- **Center**: Search bar (expandable width)
2026-02-05 15:49:07 +00:00
- **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}`
2026-02-05 15:07:33 +00:00
2026-02-05 15:49:07 +00:00
**Map Overlays (remaining):**
2026-02-05 15:07:33 +00:00
- 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)
2026-02-05 15:49:07 +00:00
- `z-index: 10` - Header toolbar
- `z-index: 5` - Map overlays (traffic legend, feature count)
2026-02-05 15:07:33 +00:00
2026-02-05 16:50:51 +00:00
### 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)
2026-02-05 12:03:22 +00:00
### 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's `dragPan` and `dragRotate` are disabled
- When `static` or `select` mode is active, dragging is re-enabled
- The `TerraDrawMapLibreGLAdapter` does NOT accept a `lib` parameter - only `map` is required
2026-02-05 17:50:45 +00:00
### Voice-Guided Navigation Feature
Real-time GPS tracking with voice-guided turn-by-turn instructions:
#### How to Use
1. Calculate a route using the navigation panel (set start and end points)
2. Call `startGuidance()` to begin voice-guided navigation
3. Update position with `setPosition([lng, lat], heading?, speed?)` or use `createMockGPSSimulator()` for testing
4. Listen to `guidance-event` for navigation updates
5. Call `stopGuidance()` when done
#### Example Usage
```typescript
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/maneuver
- `execute-maneuver` - At the maneuver point (urgent)
- `step-change` - Advanced to next route step
- `off-route` - Deviated more than 50m from route
- `arrived` - Within 30m of destination
- `position-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:
```typescript
// 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,
});
```