UNPKG

@panoramax/web-viewer

Version:

Panoramax web viewer for geolocated pictures

149 lines (134 loc) 4.11 kB
// 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" @result=${this._onSelect.bind(this)} > ${this._geolocate} </pnx-search-bar> `; } } customElements.define("pnx-widget-geosearch", GeoSearch);