feat: fit to bounds when loading track page

This commit is contained in:
Paul Bienkowski 2021-11-30 21:50:56 +01:00
parent 38b1b92210
commit fe3aa7a8f6
4 changed files with 109 additions and 23 deletions

View file

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.16.3", "@babel/runtime": "^7.16.3",
"@turf/bbox": "^6.5.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fomantic-ui-less": "^2.8.8", "fomantic-ui-less": "^2.8.8",
@ -2201,6 +2202,37 @@
"react-dom": "^16.0.0 || ^17.0.0" "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": { "node_modules/@types/eslint": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz",
@ -10645,6 +10677,28 @@
"prop-types": "^15.6.2" "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": { "@types/eslint": {
"version": "8.2.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.2.0.tgz",

View file

@ -8,6 +8,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.16.3", "@babel/runtime": "^7.16.3",
"@turf/bbox": "^6.5.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"downloadjs": "^1.4.7", "downloadjs": "^1.4.7",
"fomantic-ui-less": "^2.8.8", "fomantic-ui-less": "^2.8.8",

View file

@ -1,6 +1,7 @@
import React from 'react' 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 {Page} from 'components'
import {useConfig, Config} from 'config' import {useConfig, Config} from 'config'
@ -30,36 +31,66 @@ function useViewportFromUrl() {
const history = useHistory() const history = useHistory()
const location = useLocation() const location = useLocation()
const value = React.useMemo(() => parseHash(location.hash), [location.hash]) const value = React.useMemo(() => parseHash(location.hash), [location.hash])
const setter = React.useCallback((v) => { const setter = React.useCallback(
(v) => {
history.replace({ history.replace({
hash: buildHash(v) hash: buildHash(v),
}) })
}, [history]) },
[history]
)
return [value || EMPTY_VIEWPORT, setter] 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 [viewportState, setViewportState] = React.useState(EMPTY_VIEWPORT)
const [viewportUrl, setViewportUrl] = useViewportFromUrl() const [viewportUrl, setViewportUrl] = useViewportFromUrl()
const [viewport, setViewport] = viewportFromUrl ? [viewportUrl, setViewportUrl] : [viewportState, setViewportState] const [viewport, setViewport] = viewportFromUrl ? [viewportUrl, setViewportUrl] : [viewportState, setViewportState]
const config = useConfig() const config = useConfig()
React.useEffect(() => { React.useEffect(() => {
if (config?.mapHome && viewport.zoom === 0) { if (config?.mapHome && viewport.zoom === 0 && !boundsFromJson) {
setViewport(config.mapHome) 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 ( return (
<ReactMapGl mapStyle={basemap} width="100%" height="100%" onViewportChange={setViewport} {...viewport}> <ReactMapGl mapStyle={basemap} width="100%" height="100%" onViewportChange={setViewport} {...viewport}>
<AttributionControl style={{right: 0, bottom: 0}} customAttribution={[ <AttributionControl
style={{right: 0, bottom: 0}}
customAttribution={[
'<a href="https://openstreetmap.org/copyright" target="_blank" rel="nofollow noopener noreferrer">© OpenStreetMap contributors</a>', '<a href="https://openstreetmap.org/copyright" target="_blank" rel="nofollow noopener noreferrer">© OpenStreetMap contributors</a>',
'<a href="https://openmaptiles.org/" target="_blank" rel="nofollow noopener noreferrer">© OpenMapTiles</a>', '<a href="https://openmaptiles.org/" target="_blank" rel="nofollow noopener noreferrer">© OpenMapTiles</a>',
'<a href="https://openbikesensor.org/" target="_blank" rel="nofollow noopener noreferrer">© OpenBikeSensor</a>', '<a href="https://openbikesensor.org/" target="_blank" rel="nofollow noopener noreferrer">© OpenBikeSensor</a>',
]} /> ]}
<NavigationControl style={{left: 0, top: 0}} /> />
<NavigationControl style={{left: 10, top: 10}} />
{children} {children}
</ReactMapGl> </ReactMapGl>
@ -70,7 +101,7 @@ export function RoadsMap() {
const {obsMapSource} = useConfig() || {} const {obsMapSource} = useConfig() || {}
if (!obsMapSource) { if (!obsMapSource) {
return null; return null
} }
return ( return (

View file

@ -24,7 +24,7 @@ export default function TrackMap({
return ( return (
<div style={props.style}> <div style={props.style}>
<CustomMap> <CustomMap boundsFromJson={trackData.track}>
{showTrack && ( {showTrack && (
<Source id="route" type="geojson" data={trackData.track}> <Source id="route" type="geojson" data={trackData.track}>
<Layer id="route" {...trackLayer} /> <Layer id="route" {...trackLayer} />