diff --git a/components/map/Course.tsx b/components/map/Course.tsx index 9a327e6..8f1f823 100644 --- a/components/map/Course.tsx +++ b/components/map/Course.tsx @@ -1,37 +1,16 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Polyline } from 'react-leaflet'; +import {CourseData} from '../../lib/gpx_parser'; -const mintrackpointdelta = 0.0001; - -export default function MapCourse({ map, course }) { - const { trackpoints } = course?.tracks[0]?.segments[0] || []; - const [polyline, setPolyline] = useState([]); +export default function MapCourse({ map, course }: {map: any, course: CourseData}) { + const { trackpoints } = course?.tracks[0]?.segments[0] || {trackpoints: []}; + const polyline = useMemo(() => trackpoints.map(({lat, lon}) => [lat, lon]), [trackpoints]); useEffect(() => { - const pointArray = []; - - if (trackpoints.length > 0) { - let lastlon = parseFloat(trackpoints[0].lon); - let lastlat = parseFloat(trackpoints[0].lat); - - pointArray.push([lastlon, lastlat]); - - for (let i = 1; i < trackpoints.length; i++) { - const lon = parseFloat(trackpoints[i].lon); - const lat = parseFloat(trackpoints[i].lat); - - const latdiff = lat - lastlat; - const londiff = lon - lastlon; - if (Math.sqrt(latdiff * latdiff + londiff * londiff) > mintrackpointdelta) { - lastlon = lon; - lastlat = lat; - pointArray.push([lat, lon]); - } - } + if (map && polyline.length > 0) { + map.flyTo(polyline[0], map.getZoom()); } - - setPolyline(pointArray); - }, [trackpoints]); + }, [,map, polyline]); return ; } diff --git a/components/map/Marker.tsx b/components/map/Marker.tsx index 2273342..341ceec 100644 --- a/components/map/Marker.tsx +++ b/components/map/Marker.tsx @@ -8,7 +8,6 @@ import MarkerShadow from '../../node_modules/leaflet/dist/images/marker-shadow.p export default function MapMarker({ map, position }) { useEffect(() => { if (map) { - console.log('fly to ', position); map.flyTo(position, map.getZoom()); } }, [map, position]); diff --git a/lib/gpx_parser.ts b/lib/gpx_parser.ts index e892b75..0243e54 100644 --- a/lib/gpx_parser.ts +++ b/lib/gpx_parser.ts @@ -1,3 +1,25 @@ +type Coord = { + lat: number; + lon: number; +}; +type Trackpoint = Coord & { + ele: number; +}; +type Segment = { + trackpoints: Trackpoint[]; +}; +type Track = { + name?: string; + segments: Segment[]; +} +type Routepoint = Coord; +type Waypoint = Coord; +export type CourseData = { + tracks: Track[]; + routePoints: Routepoint[], + waypoints: Waypoint[]; +}; + export async function parseGpxFile2Document(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); @@ -15,7 +37,7 @@ export async function parseGpxFile2Document(file: File): Promise { }); } -function* elIter(el: HTMLCollectionOf, callback: (el: Element) => any) { +function* elIter(el: HTMLCollectionOf, callback: (el: Element) => T) { for (let i = 0; i < el.length; i++) { yield callback(el[i]); } @@ -25,17 +47,17 @@ function getElValue(el: HTMLCollectionOf) { return el[0].childNodes[0].nodeValue; } -function parseTrackpoints(trackpoints: HTMLCollectionOf) { +function parseTrackpoints(trackpoints: HTMLCollectionOf): Trackpoint[] { return [ - ...elIter(trackpoints, (trackpoint) => ({ - lon: parseFloat(trackpoint.getAttribute('lon')), + ...elIter(trackpoints, (trackpoint) => ({ lat: parseFloat(trackpoint.getAttribute('lat')), - ele: getElValue(trackpoint.getElementsByTagName('ele')), + lon: parseFloat(trackpoint.getAttribute('lon')), + ele: parseFloat(getElValue(trackpoint.getElementsByTagName('ele'))), })), ]; } -function parseSegments(segments: HTMLCollectionOf) { +function parseSegments(segments: HTMLCollectionOf): Segment[] { return [ ...elIter(segments, (segment) => ({ trackpoints: parseTrackpoints(segment.getElementsByTagName('trkpt')), @@ -43,15 +65,32 @@ function parseSegments(segments: HTMLCollectionOf) { ]; } -function parseTracks(tracks: HTMLCollectionOf) { +function parseTracks(tracks: HTMLCollectionOf): Track[] { return [ - ...elIter(tracks, (track) => ({ + ...elIter(tracks, (track) => ({ name: getElValue(track.getElementsByTagName('name')), segments: parseSegments(track.getElementsByTagName('trkseg')), })), ]; } -export function gpxDocument2obj(doc: Document) { - return { tracks: parseTracks(doc.documentElement.getElementsByTagName('trk')) }; +export function gpxDocument2obj(doc: Document): CourseData { + return { + tracks: parseTracks(doc.documentElement.getElementsByTagName('trk')), + routePoints: [], + waypoints: [], + }; +} + +export function getMapBounds(obj: CourseData) { + // TODO support all tracks and segments + const points = [...obj.tracks[0].segments[0].trackpoints, ...obj.routePoints, ...obj.waypoints]; + const lats = points.map(({lat}) => lat); + const lons = points.map(({lon}) => lon); + return { + minlat: Math.min(...lats), + maxlat: Math.max(...lats), + minlon: Math.min(...lons), + maxlon: Math.max(...lons), + }; } diff --git a/pages/ride/map/index.tsx b/pages/ride/map/index.tsx index 6af4550..5f0e2a8 100644 --- a/pages/ride/map/index.tsx +++ b/pages/ride/map/index.tsx @@ -4,14 +4,14 @@ import Container from '@mui/material/Container'; import Grid from '@mui/material/Grid'; import Stack from '@mui/material/Stack'; import Button from '@mui/material/Button'; -import { useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import MyHead from '../../../components/MyHead'; import Title from '../../../components/Title'; import OpenStreetMap from '../../../components/map/OpenStreetMap'; import MapMarker from '../../../components/map/Marker'; import Course from '../../../components/map/Course'; import ImportFileButton from '../../../components/ImportFileButton'; -import { gpxDocument2obj, parseGpxFile2Document } from '../../../lib/gpx_parser'; +import { CourseData, getMapBounds, gpxDocument2obj, parseGpxFile2Document } from '../../../lib/gpx_parser'; type OpenStreetMapArg = Parameters[0]; type MapMarkerArg = Parameters[0]; @@ -45,11 +45,10 @@ function MyLocationButton({ setPosition }) { ); } -function Courses({ map, courses }) { +function Courses({ map, courses }: { map: any, courses: CourseData[] }) { return ( <> - {courses.map((course, i: number) => { - return ; + {courses.map((course: CourseData, i: number) => { })} ); @@ -58,12 +57,19 @@ function Courses({ map, courses }) { export default function RideMap() { const [map, setMap] = useState(null); const [coord, setCoord] = useState([51.505, -0.09]); - const [courses, setCourses] = useState([]); // TODO reducer? + const [course, setCourse] = useState(); + const bounds = useMemo(() => course && getMapBounds(course), [course]); + + useEffect(() => { + if (bounds) { + map.fitBounds([[bounds.minlat, bounds.minlon], [bounds.maxlat, bounds.maxlon]]); + } + }, [bounds]); const importGpx = (file: File) => { parseGpxFile2Document(file) .then((xmlDoc: Document) => { - setCourses([...courses, gpxDocument2obj(xmlDoc)]); + setCourse(gpxDocument2obj(xmlDoc)); }) .catch((err) => { console.error('Would be nice to show this:', err); @@ -90,7 +96,7 @@ export default function RideMap() { - + { course ? : <> }