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 (