# 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` | `{}` | Voice synthesis configuration | ### 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) - `traffic-updated` - Fired when traffic data is refreshed - `guidance-event` - Fired during voice-guided navigation (includes type, position, stepIndex, instruction) ### 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 - `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 - `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 ### 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) ```typescript // Using API key property // 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 ## 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 ├── 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()` 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()` ### 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 ### 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 toolbar - `z-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'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 ### 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, }); ```