diff options
-rw-r--r-- | modern/.npmrc | 1 | ||||
-rw-r--r-- | modern/package.json | 1 | ||||
-rw-r--r-- | modern/src/main/MainPage.js | 2 | ||||
-rw-r--r-- | modern/src/map/geocoder/MapGeocoder.js | 56 | ||||
-rw-r--r-- | modern/src/map/geocoder/geocoder.css | 223 |
5 files changed, 283 insertions, 0 deletions
diff --git a/modern/.npmrc b/modern/.npmrc new file mode 100644 index 00000000..521a9f7c --- /dev/null +++ b/modern/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/modern/package.json b/modern/package.json index 6912fbb0..75a746e0 100644 --- a/modern/package.json +++ b/modern/package.json @@ -6,6 +6,7 @@ "@emotion/react": "^11.9.0", "@emotion/styled": "^11.8.1", "@mapbox/mapbox-gl-draw": "^1.3.0", + "@maplibre/maplibre-gl-geocoder": "^1.3.1", "@mui/icons-material": "^5.8.0", "@mui/lab": "^5.0.0-alpha.82", "@mui/material": "^5.8.0", diff --git a/modern/src/main/MainPage.js b/modern/src/main/MainPage.js index b93a3940..a3d39b15 100644 --- a/modern/src/main/MainPage.js +++ b/modern/src/main/MainPage.js @@ -34,6 +34,7 @@ import { useDeviceReadonly } from '../common/util/permissions'; import MapPositions from '../map/MapPositions'; import MapDirection from '../map/MapDirection'; import MapOverlay from '../map/overlay/MapOverlay'; +import MapGeocoder from '../map/geocoder/MapGeocoder'; const useStyles = makeStyles((theme) => ({ root: { @@ -213,6 +214,7 @@ const MainPage = () => { <PoiMap /> </MapView> <MapCurrentLocation /> + <MapGeocoder /> {desktop && <MapPadding left={parseInt(theme.dimensions.drawerWidthDesktop, 10)} />} <Button variant="contained" diff --git a/modern/src/map/geocoder/MapGeocoder.js b/modern/src/map/geocoder/MapGeocoder.js new file mode 100644 index 00000000..de1791e6 --- /dev/null +++ b/modern/src/map/geocoder/MapGeocoder.js @@ -0,0 +1,56 @@ +import './geocoder.css'; +import maplibregl from 'maplibre-gl'; +import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'; +import { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { map } from '../core/MapView'; +import { errorsActions } from '../../store'; + +const MapGeocoder = () => { + const dispatch = useDispatch(); + + useEffect(() => { + const geocoder = { + forwardGeocode: async (config) => { + const features = []; + try { + const request = `https://nominatim.openstreetmap.org/search?q=${config.query}&format=geojson&polygon_geojson=1&addressdetails=1`; + const response = await fetch(request); + const geojson = await response.json(); + geojson.features.forEach((feature) => { + const center = [ + feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2, + feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2, + ]; + features.push({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: center, + }, + place_name: feature.properties.display_name, + properties: feature.properties, + text: feature.properties.display_name, + place_type: ['place'], + center, + }); + }); + } catch (e) { + dispatch(errorsActions.push(e.message)); + } + return { features }; + }, + }; + + const control = new MaplibreGeocoder(geocoder, { + maplibregl, + collapsed: true, + }); + map.addControl(control); + return () => map.removeControl(control); + }, [dispatch]); + + return null; +}; + +export default MapGeocoder; diff --git a/modern/src/map/geocoder/geocoder.css b/modern/src/map/geocoder/geocoder.css new file mode 100644 index 00000000..86ebf6e5 --- /dev/null +++ b/modern/src/map/geocoder/geocoder.css @@ -0,0 +1,223 @@ +/* Basics */ +.maplibregl-ctrl-geocoder, +.maplibregl-ctrl-geocoder *, +.maplibregl-ctrl-geocoder *:after, +.maplibregl-ctrl-geocoder *:before { + box-sizing: border-box; +} + +.maplibregl-ctrl-geocoder { + font-size: 15px; + line-height: 20px; + font-family: "Open Sans", "Helvetica Neue", Arial, Helvetica, sans-serif; + position: relative; + background-color: #fff; + width: 100%; + min-width: 240px; + max-width: 360px; + z-index: 1; + border-radius: 4px; + transition: width 0.25s, min-width 0.25s; +} + +.maplibregl-ctrl-geocoder--input { + font: inherit; + width: 100%; + border: 0; + background-color: transparent; + margin: 0; + height: 29px; + color: #404040; /* fallback */ + color: rgba(0, 0, 0, 0.75); + padding: 6px 30px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.maplibregl-ctrl-geocoder--input:focus { + color: #404040; /* fallback */ + color: rgba(0, 0, 0, 0.75); + outline: 0; + box-shadow: none; + outline: thin dotted; +} + +.maplibregl-ctrl-geocoder .maplibregl-ctrl-geocoder--pin-right > * { + z-index: 2; + position: absolute; + right: 5px; + top: 5px; + display: none; +} + +.maplibregl-ctrl-geocoder, +.maplibregl-ctrl-geocoder .suggestions { + box-shadow: 0 0 0 2px rgb(0 0 0 / 10%); +} + +/* Collapsed */ +.maplibregl-ctrl-geocoder.maplibregl-ctrl-geocoder--collapsed { + width: 29px; + min-width: 29px; + transition: width 0.25s, min-width 0.25s; +} + +/* Suggestions */ +.maplibregl-ctrl-geocoder .suggestions { + background-color: #fff; + border-radius: 4px; + left: 0; + list-style: none; + margin: 0; + padding: 0; + position: absolute; + width: 100%; + top: 110%; /* fallback */ + top: calc(100% + 6px); + z-index: 1000; + overflow: hidden; + font-size: 13px; +} + +.maplibregl-ctrl-bottom-left .suggestions, +.maplibregl-ctrl-bottom-right .suggestions { + top: auto; + bottom: 100%; +} + +.maplibregl-ctrl-geocoder .suggestions > li > a { + cursor: default; + display: block; + padding: 6px 12px; + color: #404040; +} + +.maplibregl-ctrl-geocoder .suggestions > .active > a, +.maplibregl-ctrl-geocoder .suggestions > li > a:hover { + color: #404040; + background-color: #f3f3f3; + text-decoration: none; + cursor: pointer; +} + +.maplibregl-ctrl-geocoder--suggestion { + display: flex; + flex-direction: row; + align-items: center; +} + +.maplibre-ctrl-geocoder--suggestion-icon { + min-width: 30px; + min-height: 24px; + max-width: 30px; + max-height: 24px; + padding-right: 12px; +} + +.maplibregl-ctrl-geocoder--suggestion-info { + display: flex; + flex-direction: column; +} + +.maplibregl-ctrl-geocoder--suggestion-match { + font-weight: bold; +} + +.maplibregl-ctrl-geocoder--suggestion-title, +.maplibregl-ctrl-geocoder--suggestion-address { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +.maplibregl-ctrl-geocoder--result { + display: flex; + flex-direction: row; + align-items: center; +} + +.maplibre-ctrl-geocoder--result-icon { + min-width: 30px; + min-height: 24px; + max-width: 30px; + max-height: 24px; + padding-right: 12px; +} + +.maplibregl-ctrl-geocoder--result-title { + font-weight: bold; +} + +.maplibregl-ctrl-geocoder--result-title, +.maplibregl-ctrl-geocoder--result-address { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; +} + +/* Icons */ +.maplibregl-ctrl-geocoder--icon { + display: inline-block; + vertical-align: middle; +} + +.maplibregl-ctrl-geocoder--icon-search { + position: absolute; + top: 3px; + left: 3px; + width: 23px; + height: 23px; +} + +.maplibregl-ctrl-geocoder--button { + padding: 0; + margin: 0; + border: none; + cursor: pointer; + background: #fff; + line-height: 1; +} + +.maplibregl-ctrl-geocoder--icon-close { + width: 20px; + height: 20px; +} + +.maplibregl-ctrl-geocoder--icon-loading { + width: 20px; + height: 20px; + -moz-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); + -webkit-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); + animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); +} + +/* Animation */ +@-webkit-keyframes rotate { + from { + -webkit-transform: rotate(0); + transform: rotate(0); + } + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes rotate { + from { + -webkit-transform: rotate(0); + transform: rotate(0); + } + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.maplibre-gl-geocoder--error { + color: #909090; + padding: 6px 12px; + font-size: 16px; + text-align: center; +} |