import { useRef, useCallback } from "react"; import { StyleSheet, View, Text } from "react-native"; import MapLibreGL from "@maplibre/maplibre-react-native"; import type { Waypoint } from "@trails-cool/types"; import type { RouteSegment } from "./use-route-editor"; const OSM_STYLE = { version: 8 as const, sources: { osm: { type: "raster" as const, tiles: ["https://tile.openstreetmap.org/{z}/{x}/{y}.png"], tileSize: 256, maxzoom: 19, }, }, layers: [ { id: "osm", type: "raster" as const, source: "osm", }, ], }; interface RouteMapProps { waypoints: Waypoint[]; segments: RouteSegment[]; computing: boolean; onLongPress: (lat: number, lon: number) => void; onWaypointDragEnd: (index: number, lat: number, lon: number) => void; onWaypointPress: (index: number) => void; } export function RouteMap({ waypoints, segments, computing, onLongPress, onWaypointDragEnd: _onWaypointDragEnd, onWaypointPress, }: RouteMapProps) { const cameraRef = useRef(null); const handleLongPress = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (event: any) => { const coords = event?.geometry?.coordinates; if (Array.isArray(coords) && coords.length >= 2) { onLongPress(coords[1] as number, coords[0] as number); } }, [onLongPress], ); // Build route GeoJSON from segments const routeGeojson = { type: "FeatureCollection" as const, features: segments.map((seg) => ({ type: "Feature" as const, properties: {}, geometry: { type: "LineString" as const, coordinates: seg.coordinates, }, })), }; // Compute bounds for initial camera const bounds = computeBounds(waypoints, segments); return ( {bounds && ( )} {!bounds && ( )} {/* Route line */} {/* Waypoint markers */} {waypoints.map((wp, i) => ( onWaypointPress(i)} /> ))} {computing && ( Computing route... )} ); } function WaypointMarker({ index, isDayBreak, onPress, }: { index: number; isDayBreak?: boolean; onPress: () => void; }) { return ( {index + 1} ); } function computeBounds( waypoints: Waypoint[], segments: RouteSegment[], ): { ne: [number, number]; sw: [number, number] } | null { const points: [number, number][] = [ ...waypoints.map((w) => [w.lon, w.lat] as [number, number]), ...segments.flatMap((s) => s.coordinates), ]; if (points.length === 0) return null; let minLon = Infinity, maxLon = -Infinity; let minLat = Infinity, maxLat = -Infinity; for (const [lon, lat] of points) { if (lon < minLon) minLon = lon; if (lon > maxLon) maxLon = lon; if (lat < minLat) minLat = lat; if (lat > maxLat) maxLat = lat; } return { ne: [maxLon, maxLat], sw: [minLon, minLat], }; } const styles = StyleSheet.create({ container: { flex: 1 }, map: { flex: 1 }, computingBanner: { position: "absolute", top: 8, alignSelf: "center", backgroundColor: "rgba(0,0,0,0.7)", borderRadius: 16, paddingVertical: 6, paddingHorizontal: 14, }, computingText: { color: "#fff", fontSize: 13 }, marker: { width: 28, height: 28, borderRadius: 14, backgroundColor: "#4A6B40", justifyContent: "center", alignItems: "center", borderWidth: 2, borderColor: "#fff", shadowColor: "#000", shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.3, shadowRadius: 2, elevation: 3, }, markerOvernight: { backgroundColor: "#f97316", }, markerText: { color: "#fff", fontSize: 12, fontWeight: "700", }, });