@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
149 lines (134 loc) • 4.11 kB
JavaScript
// DO NOT REMOVE THE "!": bundled builds breaks otherwise !!!
import maplibregl from "!maplibre-gl";
import { LitElement, html } from "lit";
import { forwardGeocodingBAN, forwardGeocodingStandard, forwardGeocodingNominatim } from "../../../utils/geocoder";
import { onceParentAvailable } from "../../../utils/widgets";
import "./GeoSearch.css";
const GEOCODER_ENGINES = {
"ban": forwardGeocodingBAN,
"standard": forwardGeocodingStandard,
"nominatim": forwardGeocodingNominatim
};
/**
* Ready-to-use geocoder search bar.
* @class Panoramax.components.ui.widgets.GeoSearch
* @element pnx-widget-geosearch
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
* @example
* ```html
* <!-- Default geocoder -->
* <pnx-widget-geosearch _parent=${viewer} />
*
* <!-- Custom-URL geocoder -->
* <pnx-widget-geosearch geocoder="https://photon.komoot.io/api" _parent=${viewer} />
* ```
*/
export default class GeoSearch extends LitElement {
/**
* Component properties.
* @memberof Panoramax.components.ui.widgets.GeoSearch#
* @type {Object}
* @property {string} [geocoder=nominatim] The geocoder engine to use (nominatim, ban, or URL to a standard [GeocodeJSON-compliant](https://github.com/geocoders/geocodejson-spec/blob/master/draft/README.md) API)
*/
static properties = {
geocoder: {type: String},
_geolocate: {state: true},
};
constructor() {
super();
this.geocoder = "nominatim";
this._geolocateCtrl = new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true,
timeout: 60000, // Max 1 minute for first position
maximumAge: 300000, // Accepts 5 minutes old position
},
showAccuracyCircle: true,
showUserLocation: true,
trackUserLocation: true,
});
}
/** @private */
createRenderRoot() {
return this;
}
/** @private */
connectedCallback() {
super.connectedCallback();
this._geocoderEngine = GEOCODER_ENGINES[this.geocoder] || (config => GEOCODER_ENGINES.standard(config, this.geocoder));
onceParentAvailable(this).then(() => this._parent?.onceMapReady?.().then(() => {
this._geolocate = this._geolocateCtrl.onAdd(this._parent.map);
this._geolocate.setAttribute("slot", "pre");
}));
}
/** @private */
_onInput(query) {
const rgxCoords = /([-+]?\d{1,2}\.\d+),\s*([-+]?\d{1,3}\.\d+)/;
const coordsMatch = query.match(rgxCoords);
const rgxUuid = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/;
const uuidMatch = query.match(rgxUuid);
if(coordsMatch) {
const lat = parseFloat(coordsMatch[1]);
const lon = parseFloat(coordsMatch[2]);
this._parent.map.jumpTo({
center: [lon, lat],
zoom: 16,
});
return Promise.resolve(true);
}
else if(uuidMatch) {
this._parent.select(null, query);
return Promise.resolve(true);
}
else {
return this._geocoderEngine({
query,
limit: 5,
//bbox: this._parent.map.getBounds().toArray().map(d => d.join(",")).join(","),
proximity: this._parent.map.getCenter().lat+","+this._parent.map.getCenter().lng,
}).then(data => {
data = data.features.map(f => ({
title: f.place_name.split(",")[0],
subtitle: f.place_name.split(",").slice(1).join(", "),
data: f
}));
return data;
});
}
}
/** @private */
_onSelect(e) {
const entry = e.detail;
if(entry) {
if(entry.data.bounds) {
this._parent?.map.fitBounds(entry.data.bounds, {animate: false});
}
else {
this._parent?.map.jumpTo({
center: entry.data.center,
zoom: entry.data.zoom || 13,
});
}
}
}
/** @private */
render() {
const isSmall = this._parent?.isWidthSmall() || false;
return html`
<pnx-search-bar
id="pnx-widget-search-bar"
placeholder=${this._parent?._t.pnx.search_address}
._parent=${this._parent}
.searcher=${this._onInput.bind(this)}
.reduceable=${isSmall}
.reduced=${isSmall}
size="xxl"
class="pnx-print-hidden"
=${this._onSelect.bind(this)}
>
${this._geolocate}
</pnx-search-bar>
`;
}
}
customElements.define("pnx-widget-geosearch", GeoSearch);