diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 49ffda2..6e2f75e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@babel/runtime": "^7.16.3", + "@turf/bbox": "^6.5.0", "classnames": "^2.3.1", "downloadjs": "^1.4.7", "fomantic-ui-less": "^2.8.8", @@ -2201,6 +2202,37 @@ "react-dom": "^16.0.0 || ^17.0.0" } }, + "node_modules/@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/eslint": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", @@ -10645,6 +10677,28 @@ "prop-types": "^15.6.2" } }, + "@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==" + }, + "@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, "@types/eslint": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index eea7f63..58f3bc0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "@babel/runtime": "^7.16.3", + "@turf/bbox": "^6.5.0", "classnames": "^2.3.1", "downloadjs": "^1.4.7", "fomantic-ui-less": "^2.8.8", diff --git a/frontend/src/pages/MapPage.tsx b/frontend/src/pages/MapPage.tsx index 0cb5ae3..ee28e2f 100644 --- a/frontend/src/pages/MapPage.tsx +++ b/frontend/src/pages/MapPage.tsx @@ -1,6 +1,7 @@ import React from 'react' -import ReactMapGl, {AttributionControl, NavigationControl, Layer, Source} from 'react-map-gl' +import ReactMapGl, {WebMercatorViewport, AttributionControl, NavigationControl, Layer, Source} from 'react-map-gl' +import turfBbox from '@turf/bbox' import {Page} from 'components' import {useConfig, Config} from 'config' @@ -14,11 +15,11 @@ function parseHash(v) { if (!v) return null const m = v.match(/^#([0-9\.]+)\/([0-9\.]+)\/([0-9\.]+)$/) if (!m) return null - return { - zoom: Number.parseFloat(m[1]), - latitude: Number.parseFloat(m[2]), - longitude: Number.parseFloat(m[3]), - } + return { + zoom: Number.parseFloat(m[1]), + latitude: Number.parseFloat(m[2]), + longitude: Number.parseFloat(m[3]), + } } const EMPTY_VIEWPORT = {longitude: 0, latitude: 0, zoom: 0} @@ -30,36 +31,66 @@ function useViewportFromUrl() { const history = useHistory() const location = useLocation() const value = React.useMemo(() => parseHash(location.hash), [location.hash]) - const setter = React.useCallback((v) => { - history.replace({ - hash: buildHash(v) - }) - }, [history]) + const setter = React.useCallback( + (v) => { + history.replace({ + hash: buildHash(v), + }) + }, + [history] + ) return [value || EMPTY_VIEWPORT, setter] } -export function CustomMap({viewportFromUrl, children, boundsFromJson}: {viewportFromUrl?: boolean, children: React.ReactNode}) { +export function CustomMap({ + viewportFromUrl, + children, + boundsFromJson, +}: { + viewportFromUrl?: boolean + children: React.ReactNode + boundsFromJson: GeoJSON.Geometry +}) { const [viewportState, setViewportState] = React.useState(EMPTY_VIEWPORT) const [viewportUrl, setViewportUrl] = useViewportFromUrl() const [viewport, setViewport] = viewportFromUrl ? [viewportUrl, setViewportUrl] : [viewportState, setViewportState] - const config = useConfig() React.useEffect(() => { - if (config?.mapHome && viewport.zoom === 0) { + if (config?.mapHome && viewport.zoom === 0 && !boundsFromJson) { setViewport(config.mapHome) } - }, [config]) + }, [config, boundsFromJson]) + + React.useEffect(() => { + if (boundsFromJson) { + const [minX, minY, maxX, maxY] = turfBbox(boundsFromJson) + const vp = new WebMercatorViewport({width: 1000, height: 800}).fitBounds( + [ + [minX, minY], + [maxX, maxY], + ], + { + padding: 20, + offset: [0, -100], + } + ) + setViewport(_.pick(vp, ['zoom', 'latitude', 'longitude'])) + } + }, [boundsFromJson]) return ( - © OpenStreetMap contributors', - '© OpenMapTiles', - '© OpenBikeSensor', - ]} /> - + © OpenStreetMap contributors', + '© OpenMapTiles', + '© OpenBikeSensor', + ]} + /> + {children} @@ -70,7 +101,7 @@ export function RoadsMap() { const {obsMapSource} = useConfig() || {} if (!obsMapSource) { - return null; + return null } return ( diff --git a/frontend/src/pages/TrackPage/TrackMap.tsx b/frontend/src/pages/TrackPage/TrackMap.tsx index 0ecbcea..d9ccf02 100644 --- a/frontend/src/pages/TrackPage/TrackMap.tsx +++ b/frontend/src/pages/TrackPage/TrackMap.tsx @@ -24,7 +24,7 @@ export default function TrackMap({ return (
- + {showTrack && (