Skip to content

Commit

Permalink
More map stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
OlliV committed Oct 26, 2023
1 parent 8860b40 commit bb5c394
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 8 deletions.
32 changes: 32 additions & 0 deletions components/ImportFileButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import InputLabel from '@mui/material/InputLabel';
import { OverridableStringUnion } from '@mui/types';
import Button, { ButtonPropsColorOverrides } from '@mui/material/Button';
import { ChangeEvent, ReactNode, useRef } from 'react';

export default function ImportFileButton({
children,
onFile,
color,
}: {
children?: ReactNode | ReactNode[];
onFile: (file: File) => void;
color?: OverridableStringUnion<
'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning',
ButtonPropsColorOverrides
>;
}) {
const uploadInputRef = useRef<HTMLInputElement | null>(null);

const onChange = (e: ChangeEvent<HTMLInputElement>) => {
onFile(e.target.files[0]);
};

return (
<InputLabel htmlFor="import-file" hidden>
<input ref={uploadInputRef} id="import-file" name="import-file" type="file" onChange={onChange} hidden />
<Button color={color || 'secondary'} variant="contained" component="span">
{children}
</Button>
</InputLabel>
);
}
4 changes: 2 additions & 2 deletions components/MapMarker.tsx → components/map/Marker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import L from 'leaflet';
import { Marker, Popup } from 'react-leaflet';
import { useEffect } from 'react';
import MarkerIcon from '../node_modules/leaflet/dist/images/marker-icon.png';
import MarkerShadow from '../node_modules/leaflet/dist/images/marker-shadow.png';
import MarkerIcon from '../../node_modules/leaflet/dist/images/marker-icon.png';
import MarkerShadow from '../../node_modules/leaflet/dist/images/marker-shadow.png';

export default function MapMarker({ map, position }) {
useEffect(() => {
Expand Down
File renamed without changes.
37 changes: 37 additions & 0 deletions components/map/Track.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useEffect, useState } from 'react';
import { Polyline } from 'react-leaflet';

const mintrackpointdelta = 0.0001;

export default function MapTrack({ map, track }) {
const { trackpoints } = track?.tracks[0]?.segments[0] || [];
const [polyline, setPolyline] = useState([]);

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]);
}
}
}

setPolyline(pointArray);
}, [trackpoints]);

return <Polyline pathOptions={{ color: 'black' }} positions={polyline} />;
}
57 changes: 57 additions & 0 deletions lib/gpx_parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export async function parseGpxFile2Document(file: File): Promise<Document> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(e.target.result as string, 'text/xml');
const errorNode = xmlDoc.querySelector('parsererror');
if (errorNode) {
reject(new Error('Failed to parse the GPX file'));
} else {
resolve(xmlDoc);
}
};
reader.readAsText(file);
});
}

function* elIter(el: HTMLCollectionOf<Element>, callback: (el: Element) => any) {
for (let i = 0; i < el.length; i++) {
yield callback(el[i]);
}
}

function getElValue(el: HTMLCollectionOf<Element>) {
return el[0].childNodes[0].nodeValue;
}

function parseTrackpoints(trackpoints: HTMLCollectionOf<Element>) {
return [
...elIter(trackpoints, (trackpoint) => ({
lon: parseFloat(trackpoint.getAttribute('lon')),
lat: parseFloat(trackpoint.getAttribute('lat')),
ele: getElValue(trackpoint.getElementsByTagName('ele')),
})),
];
}

function parseSegments(segments: HTMLCollectionOf<Element>) {
return [
...elIter(segments, (segment) => ({
trackpoints: parseTrackpoints(segment.getElementsByTagName('trkpt')),
})),
];
}

function parseTracks(tracks: HTMLCollectionOf<Element>) {
return [
...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')) };
}
39 changes: 34 additions & 5 deletions pages/ride/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ import Button from '@mui/material/Button';
import { useState } from 'react';
import MyHead from '../../../components/MyHead';
import Title from '../../../components/Title';
import OpenStreetMap from '../../../components/OpenStreetMap';
import MapMarker from '../../../components/MapMarker';
import OpenStreetMap from '../../../components/map/OpenStreetMap';
import MapMarker from '../../../components/map/Marker';
import Track from '../../../components/map/Track';
import ImportFileButton from '../../../components/ImportFileButton';
import { gpxDocument2obj, parseGpxFile2Document } from '../../../lib/gpx_parser';

type OpenStreetMapArg = Parameters<typeof OpenStreetMap>[0];
type MapMarkerArg = Parameters<typeof MapMarker>[0];
type TrackArg = Parameters<typeof Track>[0];

const DynamicMap = dynamic<OpenStreetMapArg>(() => import('../../../components/OpenStreetMap'), {
const DynamicMap = dynamic<OpenStreetMapArg>(() => import('../../../components/map/OpenStreetMap'), {
ssr: false,
});
const DynamicMapMarker = dynamic<MapMarkerArg>(() => import('../../../components/MapMarker'), {
const DynamicMapMarker = dynamic<MapMarkerArg>(() => import('../../../components/map/Marker'), {
ssr: false,
});
const DynamicTrack = dynamic<TrackArg>(() => import('../../../components/map/Track'), {
ssr: false,
});

Expand All @@ -38,9 +45,30 @@ function MyLocationButton({ setPosition }) {
);
}

function Tracks({ map, tracks }) {
return (
<>
{tracks.map((track, i: number) => {
return <DynamicTrack key={i} map={map} track={track} />;
})}
</>
);
}

export default function RideMap() {
const [map, setMap] = useState(null);
const [coord, setCoord] = useState([51.505, -0.09]);
const [tracks, setTracks] = useState([]); // TODO reducer?

const importGpx = (file: File) => {
parseGpxFile2Document(file)
.then((xmlDoc: Document) => {
setTracks([...tracks, gpxDocument2obj(xmlDoc)]);
})
.catch((err) => {
console.error('Would be nice to show this:', err);
});
};

return (
<Container maxWidth="md">
Expand All @@ -57,11 +85,12 @@ export default function RideMap() {
}}
>
<MyLocationButton setPosition={setCoord} />
<Button variant="contained">Load GPX</Button>
<ImportFileButton onFile={importGpx}>Import GPX</ImportFileButton>
</Stack>

<DynamicMap center={coord} setMap={setMap}>
<DynamicMapMarker map={map} position={coord} />
<Tracks map={map} tracks={tracks} />
</DynamicMap>

<Grid container direction="row" alignItems="center" spacing={2}></Grid>
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es2022",
"lib": [
"dom",
"dom.iterable",
Expand Down

1 comment on commit bb5c394

@vercel
Copy link

@vercel vercel bot commented on bb5c394 Oct 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

bfree – ./

bfree-olliv.vercel.app
bfree.vercel.app
bfree-git-master-olliv.vercel.app

Please sign in to comment.