@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
JavaScript
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())];
},
};
}