UNPKG

@maptiler/geocoding-control

Version:

The Javascript & TypeScript Map Control component for MapTiler Geocoding service. Easy to be integrated into any JavaScript mapping application.

284 lines (283 loc) 10.9 kB
import { Feature as OlFeature } from "ol"; import { GeometryCollection as OlGeometryCollection, LineString as OlLineString, MultiLineString as OlMultiLineString, MultiPolygon as OlMultiPolygon, Point as OlPoint, Polygon as OlPolygon, } from "ol/geom"; import VectorLayer from "ol/layer/Vector"; import { fromLonLat, getUserProjection, toLonLat, transformExtent, } from "ol/proj"; import VectorSource from "ol/source/Vector"; import Fill from "ol/style/Fill"; import Icon from "ol/style/Icon"; import Stroke from "ol/style/Stroke"; import Style, {} from "ol/style/Style"; import Text from "ol/style/Text"; import { setMask } from "./mask"; const EPSG_4326 = "EPSG:4326"; function defaultStyle(feature) { const properties = feature.getProperties(); const { isMask } = properties; const type = feature.getGeometry()?.getType(); const weight = isMask ? 0 : type === "LineString" || type === "MultiLineString" ? 3 : 2; return new Style({ stroke: isMask ? undefined : new Stroke({ color: "#3170fe", lineDash: [weight, weight], width: weight, lineCap: "butt", }), fill: isMask ? new Fill({ color: "#00000020", }) : undefined, image: new Icon({ src: `/icons/marker_${properties.isReverse ? "reverse" : properties.isSelected ? "selected" : "unselected"}.svg`, anchor: [0.5, 1], }), zIndex: properties.isSelected ? 2 : properties.isReverse ? 0 : 1, text: properties.isSelected && properties.tooltip ? new Text({ backgroundFill: new Fill({ color: "white" }), text: properties.tooltip, offsetY: -40, backgroundStroke: new Stroke({ color: "white", lineJoin: "round", width: 3, }), padding: [2, 0, 0, 2], }) : undefined, }); } export function createOpenLayersMapController(map, flyToOptions = {}, flyToBounds = {}, fullGeometryStyle = defaultStyle) { let prevSelected = -1; let prevHovered; let eventHandler; let reverseMarker; let indicatingReverse = false; const vectorLayer = new VectorLayer({ updateWhileAnimating: true, }); map.addLayer(vectorLayer); const source = new VectorSource({}); vectorLayer.setSource(source); vectorLayer.setStyle(fullGeometryStyle); map.on("click", (e) => { map.forEachFeatureAtPixel(e.pixel, (feature) => { const id = feature.getId(); if (!id) { return; } e.stopPropagation(); eventHandler?.({ type: "markerClick", id }); return feature; }); }); map.on("pointermove", (e) => { const featureId = map.forEachFeatureAtPixel(e.pixel, (feature) => { return feature.getId(); }); if (prevHovered === featureId) { return; } if (prevHovered) { eventHandler?.({ type: "markerMouseLeave", id: prevHovered, }); } if (featureId) { eventHandler?.({ type: "markerMouseEnter", id: featureId, }); } map.getTargetElement().style.cursor = featureId ? "pointer" : indicatingReverse ? "crosshair" : ""; prevHovered = featureId; }); function getProjection() { return getUserProjection() ?? map.getView().getProjection(); } function fromWgs84(geometry) { return geometry.transform(EPSG_4326, getProjection()); } const handleMapClick = (e) => { eventHandler?.({ type: "mapClick", coordinates: toLonLat(e.coordinate, getProjection()), }); }; return { setEventHandler(handler) { if (handler) { eventHandler = handler; map.on("click", handleMapClick); } else { eventHandler = undefined; map.un("click", handleMapClick); } }, flyTo(center, zoom) { map.getView().animate({ center: fromLonLat(center, getProjection()), ...(zoom ? { zoom } : {}), duration: 2000, ...flyToOptions, }); }, fitBounds(bbox, padding, maxZoom) { map.getView().fit(transformExtent(bbox, EPSG_4326, getProjection()), { padding: [padding, padding, padding, padding], ...(maxZoom ? { maxZoom } : {}), duration: 2000, ...flyToBounds, }); }, indicateReverse(reverse) { indicatingReverse = reverse; map.getTargetElement().style.cursor = reverse ? "crosshair" : ""; }, setReverseMarker(coordinates) { if (reverseMarker) { if (!coordinates) { source.removeFeature(reverseMarker); reverseMarker.dispose(); reverseMarker = undefined; } else { reverseMarker.getGeometry().setCoordinates(fromLonLat(coordinates, getProjection())); } } else if (coordinates) { reverseMarker = new OlFeature(new OlPoint(fromLonLat(coordinates, getProjection()))); reverseMarker.setProperties({ isReverse: true }); source.addFeature(reverseMarker); } }, setFeatures(markedFeatures, picked, showPolygonMarker) { function setData(data) { if (!data) { return; } for (const f of data.features) { const geom = f.geometry.type === "Polygon" ? new OlPolygon(f.geometry.coordinates) : f.geometry.type === "MultiPolygon" ? new OlMultiPolygon(f.geometry.coordinates) : null; if (!geom) { continue; } source.addFeature(new OlFeature({ isMask: !!f.properties?.isMask, geometry: fromWgs84(geom), })); } } source.clear(); if (reverseMarker) { source.addFeature(reverseMarker); } block: if (picked) { let handled = false; if (picked.geometry.type === "GeometryCollection") { const geoms = picked.geometry.geometries .map((geometry) => geometry.type === "Polygon" ? new OlPolygon(geometry.coordinates) : geometry.type === "MultiPolygon" ? new OlMultiPolygon(geometry.coordinates) : null) .filter((a) => !!a); if (geoms.length > 0) { source.addFeature(new OlFeature(fromWgs84(new OlGeometryCollection(geoms)))); handled = true; } else { for (const geometry of picked.geometry.geometries) { if (geometry.type === "LineString") { source.addFeature(new OlFeature(fromWgs84(new OlLineString(geometry.coordinates)))); handled = true; } else if (geometry.type === "MultiLineString") { source.addFeature(new OlFeature(fromWgs84(new OlMultiLineString(geometry.coordinates)))); } handled = true; } } } if (handled) { // nothing } else if (picked.geometry.type === "Polygon") { setMask(picked, setData); } else if (picked.geometry.type === "MultiPolygon") { setMask(picked, setData); } else if (picked.geometry.type === "LineString") { source.addFeature(new OlFeature(fromWgs84(new OlLineString(picked.geometry.coordinates)))); break block; // no pin for (multi)linestrings } else if (picked.geometry.type === "MultiLineString") { source.addFeature(new OlFeature(fromWgs84(new OlMultiLineString(picked.geometry.coordinates)))); break block; // no pin for (multi)linestrings } if (!showPolygonMarker && !picked.geometry.type.endsWith("Point")) { break block; } source.addFeature(new OlFeature(fromWgs84(new OlPoint(picked.center)))); } for (const feature of markedFeatures ?? []) { if (feature === picked) { continue; } const marker = new OlFeature(new OlPoint(fromLonLat(feature.center, getProjection()))); marker.setId(feature.id); marker.setProperties({ fuzzy: !!feature.matching_text, tooltip: feature.place_type[0] === "reverse" ? feature.place_name : feature.place_name.replace(/,.*/, ""), }); source.addFeature(marker); } }, setSelectedMarker(index) { const features = source.getFeatures(); const offset = features[0]?.getProperties().isReverse ? 1 : 0; if (prevSelected > -1) { features[prevSelected + offset]?.setProperties({ isSelected: false, }); } if (index > -1) { features[index + offset]?.setProperties({ isSelected: true, }); } prevSelected = index; }, getCenterAndZoom() { const view = map.getView(); const center = view.getCenter(); const zoom = view.getZoom(); if (!center || zoom === undefined) { return undefined; } return [zoom, ...toLonLat(center, getProjection())]; }, }; }