diff --git a/src/open-mapping/components/LayerPanel.tsx b/src/open-mapping/components/LayerPanel.tsx index afd56c7..b7354c2 100644 --- a/src/open-mapping/components/LayerPanel.tsx +++ b/src/open-mapping/components/LayerPanel.tsx @@ -24,12 +24,18 @@ interface LayerPanelProps { export function LayerPanel({ layers, onLayerToggle, - onLayerOpacity, - onLayerReorder, - onLayerAdd, - onLayerRemove, - onLayerEdit, + onLayerOpacity: _onLayerOpacity, + onLayerReorder: _onLayerReorder, + onLayerAdd: _onLayerAdd, + onLayerRemove: _onLayerRemove, + onLayerEdit: _onLayerEdit, }: LayerPanelProps) { + // Suppress unused variable warnings for future implementation + void _onLayerOpacity; + void _onLayerReorder; + void _onLayerAdd; + void _onLayerRemove; + void _onLayerEdit; // TODO: Implement layer panel UI // This will be implemented in Phase 2 diff --git a/src/open-mapping/components/RouteLayer.tsx b/src/open-mapping/components/RouteLayer.tsx index dd65bf5..115d8b6 100644 --- a/src/open-mapping/components/RouteLayer.tsx +++ b/src/open-mapping/components/RouteLayer.tsx @@ -34,16 +34,25 @@ const DEFAULT_PROFILE_COLORS: Record = { }; export function RouteLayer({ - routes, - selectedRouteId, - showAlternatives = true, - showElevation = false, - onRouteSelect, - onRouteEdit, + routes: _routes, + selectedRouteId: _selectedRouteId, + showAlternatives: _showAlternatives = true, + showElevation: _showElevation = false, + onRouteSelect: _onRouteSelect, + onRouteEdit: _onRouteEdit, profileColors = {}, }: RouteLayerProps) { const colors = { ...DEFAULT_PROFILE_COLORS, ...profileColors }; + // Suppress unused variable warnings for future implementation + void _routes; + void _selectedRouteId; + void _showAlternatives; + void _showElevation; + void _onRouteSelect; + void _onRouteEdit; + void colors; + // TODO: Implement route rendering with MapLibre GL JS // This will be implemented in Phase 2 diff --git a/src/open-mapping/components/WaypointMarker.tsx b/src/open-mapping/components/WaypointMarker.tsx index f65680f..6599492 100644 --- a/src/open-mapping/components/WaypointMarker.tsx +++ b/src/open-mapping/components/WaypointMarker.tsx @@ -23,20 +23,10 @@ interface WaypointMarkerProps { onDelete?: (waypointId: string) => void; } -export function WaypointMarker({ - waypoint, - index, - isSelected = false, - isDraggable = true, - showLabel = true, - showTime = false, - showBudget = false, - onSelect, - onDragEnd, - onDelete, -}: WaypointMarkerProps) { +export function WaypointMarker(_props: WaypointMarkerProps) { // TODO: Implement marker rendering with MapLibre GL JS - // This will be implemented in Phase 1 + // Props will be used in Phase 1 implementation + void _props; return null; // Markers are rendered directly on the map } diff --git a/src/open-mapping/conics/geometry.ts b/src/open-mapping/conics/geometry.ts index dd172cb..ea8b107 100644 --- a/src/open-mapping/conics/geometry.ts +++ b/src/open-mapping/conics/geometry.ts @@ -636,3 +636,81 @@ function findOrthogonalVector(v: SpaceVector): SpaceVector { return normalize(result); } + +// ============================================================================= +// Convenience Aliases (for backwards compatibility with index.ts exports) +// ============================================================================= + +export const vectorAdd = addVectors; +export const vectorSubtract = subtractVectors; +export const vectorScale = scaleVector; +export const vectorDot = dotProduct; +export const vectorNorm = magnitude; +export const vectorNormalize = normalize; +export const vectorCross3D = crossProduct; + +/** + * Calculate angle from cone axis to a point + */ +export function angleFromAxis(point: SpacePoint, cone: PossibilityCone): number { + const toPoint = subtractVectors(pointToVector(point), pointToVector(cone.apex)); + const toPointNorm = normalize(toPoint); + const dot = dotProduct(toPointNorm, cone.axis); + return Math.acos(Math.max(-1, Math.min(1, dot))); +} + +/** + * Combine two cones (union/intersection) + */ +export function combineCones( + cone1: PossibilityCone, + cone2: PossibilityCone, + operation: 'union' | 'intersection' = 'intersection' +): PossibilityCone { + // For intersection, take the narrower aperture + // For union, take the wider aperture + const aperture = operation === 'intersection' + ? Math.min(cone1.aperture, cone2.aperture) + : Math.max(cone1.aperture, cone2.aperture); + + // Average the apex positions + const apex: SpacePoint = { + coordinates: cone1.apex.coordinates.map((c, i) => + (c + (cone2.apex.coordinates[i] ?? 0)) / 2 + ), + }; + + // Average the axes (normalized) + const avgAxis = normalize(addVectors(cone1.axis, cone2.axis)); + + return { + id: `${cone1.id}-${cone2.id}-${operation}`, + apex, + axis: avgAxis, + aperture, + direction: cone1.direction, + extent: cone1.extent && cone2.extent + ? Math.min(cone1.extent, cone2.extent) + : cone1.extent ?? cone2.extent, + constraints: [...cone1.constraints, ...cone2.constraints], + sourceConstraints: [ + ...(cone1.sourceConstraints ?? []), + ...(cone2.sourceConstraints ?? []), + ], + metadata: { ...cone1.metadata, ...cone2.metadata }, + }; +} + +/** + * Slice a cone with a hyperplane to get a conic section + */ +export function sliceConeWithPlane( + cone: PossibilityCone, + planeNormal: SpaceVector, + planePoint: SpacePoint +): ConicSection { + // Calculate plane offset as distance from origin along normal + const planeOffset = dotProduct(pointToVector(planePoint), planeNormal); + + return createConicSection(cone, planeNormal, planeOffset); +} diff --git a/src/open-mapping/conics/optimization.ts b/src/open-mapping/conics/optimization.ts index 6c8fe46..83e6853 100644 --- a/src/open-mapping/conics/optimization.ts +++ b/src/open-mapping/conics/optimization.ts @@ -745,3 +745,13 @@ export function createPathOptimizer( ): PathOptimizer { return new PathOptimizer(bounds, config); } + +// Re-export config types from types.ts for convenience +export { DEFAULT_OPTIMIZATION_CONFIG } from './types'; +export type { OptimizationConfig } from './types'; + +/** + * Alias for backwards compatibility with index.ts + */ +export const DEFAULT_OPTIMIZER_CONFIG = DEFAULT_OPTIMIZATION_CONFIG; +export type OptimizerConfig = OptimizationConfig; diff --git a/src/open-mapping/conics/types.ts b/src/open-mapping/conics/types.ts index 33c4acc..747e1d1 100644 --- a/src/open-mapping/conics/types.ts +++ b/src/open-mapping/conics/types.ts @@ -100,6 +100,9 @@ export interface PossibilityCone { /** Constraints that shaped this cone */ constraints: string[]; + /** Source constraints (for combined cones) */ + sourceConstraints?: string[]; + /** Metadata */ metadata: Record; } diff --git a/src/open-mapping/privacy/geohash.ts b/src/open-mapping/privacy/geohash.ts index ce640a9..c049571 100644 --- a/src/open-mapping/privacy/geohash.ts +++ b/src/open-mapping/privacy/geohash.ts @@ -427,3 +427,16 @@ export function precisionForRadius(radiusMeters: number): number { } return 1; } + +// ============================================================================= +// Convenience Aliases (for backwards compatibility) +// ============================================================================= + +/** Alias for encode() */ +export const encodeGeohash = encode; + +/** Alias for decode() */ +export const decodeGeohash = decode; + +/** Alias for decodeBounds() */ +export const getGeohashBounds = decodeBounds; diff --git a/src/open-mapping/privacy/types.ts b/src/open-mapping/privacy/types.ts index 84f3f3c..06d5d90 100644 --- a/src/open-mapping/privacy/types.ts +++ b/src/open-mapping/privacy/types.ts @@ -4,6 +4,8 @@ * Types for privacy-preserving location sharing protocol */ +// Re-export GeohashPrecision so consumers can import from types +export type { GeohashPrecision } from './geohash'; import type { GeohashPrecision } from './geohash'; // ============================================================================= @@ -49,6 +51,9 @@ export interface LocationCommitment { /** Optional: the geohash prefix that is publicly revealed */ revealedPrefix?: string; + + /** The geohash string (at the given precision) */ + geohash?: string; } /** @@ -72,6 +77,11 @@ export interface SignedCommitment extends LocationCommitment { signerPublicKey: string; } +/** + * Alias for LocationCommitment (used by discovery module) + */ +export type GeohashCommitment = LocationCommitment; + // ============================================================================= // Trust Circle Types // ============================================================================= diff --git a/src/open-mapping/services/OptimizationService.ts b/src/open-mapping/services/OptimizationService.ts index bd77554..93201c2 100644 --- a/src/open-mapping/services/OptimizationService.ts +++ b/src/open-mapping/services/OptimizationService.ts @@ -4,6 +4,16 @@ import type { Waypoint, Coordinate, OptimizationServiceConfig } from '../types'; +// VROOM API response type +interface VROOMResponse { + code: number; + error?: string; + summary: { distance: number; duration: number }; + routes: Array<{ + steps: Array<{ type: string; job?: number }>; + }>; +} + export interface OptimizationResult { orderedWaypoints: Waypoint[]; totalDistance: number; @@ -50,10 +60,10 @@ export class OptimizationService { const vehicles = [{ id: 0, start: [waypoints[0].coordinate.lng, waypoints[0].coordinate.lat] }]; try { const res = await fetch(this.config.baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jobs, vehicles }) }); - const data = await res.json(); - if (data.code !== 0) throw new Error(data.error); - const indices = data.routes[0].steps.filter((s: any) => s.type === 'job').map((s: any) => s.job); - return { orderedWaypoints: indices.map((i: number) => waypoints[i]), totalDistance: data.summary.distance, totalDuration: data.summary.duration, estimatedCost: this.estimateCosts(data.summary.distance, data.summary.duration) }; + const data = await res.json() as VROOMResponse; + if (data.code !== 0) throw new Error(data.error ?? 'Unknown VROOM error'); + const indices = data.routes[0].steps.filter((s) => s.type === 'job').map((s) => s.job!); + return { orderedWaypoints: indices.map((i) => waypoints[i]), totalDistance: data.summary.distance, totalDuration: data.summary.duration, estimatedCost: this.estimateCosts(data.summary.distance, data.summary.duration) }; } catch { return this.nearestNeighbor(waypoints); } } diff --git a/src/open-mapping/services/RoutingService.ts b/src/open-mapping/services/RoutingService.ts index 5dcff66..4fb3bd6 100644 --- a/src/open-mapping/services/RoutingService.ts +++ b/src/open-mapping/services/RoutingService.ts @@ -5,6 +5,26 @@ import type { Waypoint, Route, RoutingOptions, RoutingServiceConfig, Coordinate, RoutingProfile } from '../types'; +// Response types for routing APIs +interface OSRMResponse { + code: string; + message?: string; + routes: Array<{ + distance: number; + duration: number; + geometry: GeoJSON.LineString; + legs: Array<{ distance: number; duration: number }>; + }>; +} + +interface ValhallaResponse { + error?: string; + trip: { + summary: { length: number; time: number }; + legs: Array<{ summary: { length: number; time: number } }>; + }; +} + export class RoutingService { private config: RoutingServiceConfig; @@ -34,9 +54,9 @@ export class RoutingService { const url = `${this.config.baseUrl}/trip/v1/driving/${coords}?roundtrip=false&source=first&destination=last`; try { const res = await fetch(url); - const data = await res.json(); - if (data.code !== 'Ok') return waypoints; - return data.waypoints.map((wp: { waypoint_index: number }) => waypoints[wp.waypoint_index]); + const data = await res.json() as { code: string; waypoints?: Array<{ waypoint_index: number }> }; + if (data.code !== 'Ok' || !data.waypoints) return waypoints; + return data.waypoints.map((wp) => waypoints[wp.waypoint_index]); } catch { return waypoints; } } @@ -56,8 +76,8 @@ export class RoutingService { url.searchParams.set('steps', 'true'); if (options?.alternatives) url.searchParams.set('alternatives', 'true'); const res = await fetch(url.toString()); - const data = await res.json(); - if (data.code !== 'Ok') throw new Error(`OSRM error: ${data.message || data.code}`); + const data = await res.json() as OSRMResponse; + if (data.code !== 'Ok') throw new Error(`OSRM error: ${data.message ?? data.code}`); return this.parseOSRMResponse(data, profile); } @@ -65,27 +85,27 @@ export class RoutingService { const costing = profile === 'bicycle' ? 'bicycle' : profile === 'foot' ? 'pedestrian' : 'auto'; const body = { locations: coords.map((c) => ({ lat: c.lat, lon: c.lng })), costing, alternates: options?.alternatives ?? 0 }; const res = await fetch(`${this.config.baseUrl}/route`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); - const data = await res.json(); + const data = await res.json() as ValhallaResponse; if (data.error) throw new Error(`Valhalla error: ${data.error}`); return this.parseValhallaResponse(data, profile); } - private parseOSRMResponse(data: any, profile: RoutingProfile): Route { + private parseOSRMResponse(data: OSRMResponse, profile: RoutingProfile): Route { const r = data.routes[0]; return { id: `route-${Date.now()}`, waypoints: [], geometry: r.geometry, profile, summary: { distance: r.distance, duration: r.duration }, - legs: r.legs.map((leg: any, i: number) => ({ startWaypoint: `wp-${i}`, endWaypoint: `wp-${i + 1}`, distance: leg.distance, duration: leg.duration, geometry: { type: 'LineString', coordinates: [] } })), - alternatives: data.routes.slice(1).map((alt: any) => this.parseOSRMResponse({ routes: [alt] }, profile)), + legs: r.legs.map((leg, i) => ({ startWaypoint: `wp-${i}`, endWaypoint: `wp-${i + 1}`, distance: leg.distance, duration: leg.duration, geometry: { type: 'LineString' as const, coordinates: [] } })), + alternatives: data.routes.slice(1).map((alt) => this.parseOSRMResponse({ code: 'Ok', routes: [alt] }, profile)), }; } - private parseValhallaResponse(data: any, profile: RoutingProfile): Route { + private parseValhallaResponse(data: ValhallaResponse, profile: RoutingProfile): Route { const trip = data.trip; return { - id: `route-${Date.now()}`, waypoints: [], geometry: { type: 'LineString', coordinates: [] }, profile, + id: `route-${Date.now()}`, waypoints: [], geometry: { type: 'LineString' as const, coordinates: [] }, profile, summary: { distance: trip.summary.length * 1000, duration: trip.summary.time }, - legs: trip.legs.map((leg: any, i: number) => ({ startWaypoint: `wp-${i}`, endWaypoint: `wp-${i + 1}`, distance: leg.summary.length * 1000, duration: leg.summary.time, geometry: { type: 'LineString', coordinates: [] } })), + legs: trip.legs.map((leg, i) => ({ startWaypoint: `wp-${i}`, endWaypoint: `wp-${i + 1}`, distance: leg.summary.length * 1000, duration: leg.summary.time, geometry: { type: 'LineString' as const, coordinates: [] } })), }; } } diff --git a/tsconfig.json b/tsconfig.json index 1f8f9c6..8b6e302 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,9 @@ }, "include": ["src", "worker", "src/client"], "exclude": [ - "src/open-mapping/**" + "src/open-mapping/discovery/**", + "src/open-mapping/components/CollaborativeMap.tsx", + "src/open-mapping/components/MapCanvas.tsx" ], "references": [{ "path": "./tsconfig.node.json" }] }