@independo/leaflet-independo-maps
Version:
Leaflet plugin for displaying points of interest as pictograms.
3 lines (2 loc) • 10.8 kB
JavaScript
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("leaflet")):"function"==typeof define&&define.amd?define(["exports","leaflet"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).LeafletIndependoMaps={},t.L)}(this,(function(t,e){"use strict";class i extends e.Layer{getLatLng(){return this._latlng}constructor(t,i,o,r){var n,a,s,l;super(),this.map=null,this._latlng=e.latLng(t),this._pictogram=i,this._pointOfInterest=o,this.addDescription=null!==(n=null==r?void 0:r.addDescription)&&void 0!==n&&n,this.bringToFrontOnClick=null===(a=null==r?void 0:r.bringToFrontOnClick)||void 0===a||a,this.bringToFrontOnHover=null===(s=null==r?void 0:r.bringToFrontOnHover)||void 0===s||s,this.bringToFrontOnFocus=null===(l=null==r?void 0:r.bringToFrontOnFocus)||void 0===l||l,this.onClick=null==r?void 0:r.onClick}onAdd(t){this.map=t,this.container=e.DomUtil.create("div","pictogram-marker-container"),this.container.style.position="absolute",this.container.style.transform="translate(-50%, -100%)",this.box=e.DomUtil.create("div","pictogram-marker-box",this.container);const i=e.DomUtil.create("div","pictogram-marker-img-wrapper",this.box),o=document.createElement("img");o.src=this._pictogram.url,o.alt="",o.setAttribute("aria-hidden","true"),o.setAttribute("role","presentation"),i.appendChild(o);if(e.DomUtil.create("div","pictogram-marker-label",this.box).textContent=this._pictogram.displayText,this.addDescription&&this._pictogram.description){const t=e.DomUtil.create("div","pictogram-marker-description",this.box);t.textContent=this._pictogram.description;const i=`pmd-${Math.random().toString(36).substring(7)}`;t.id=i,this.box.setAttribute("aria-describedby",i),t.setAttribute("aria-hidden","true")}return e.DomUtil.create("div","pictogram-marker-pointer",this.container),this.onClick&&(this.box.addEventListener("click",(()=>{var t;return null===(t=this.onClick)||void 0===t?void 0:t.call(this,this._pictogram,this._pointOfInterest)})),this.box.setAttribute("role","button"),this.box.tabIndex=0),t.getPanes().overlayPane.appendChild(this.container),this.updatePosition(),t.on("zoomend moveend",this.updatePosition,this),this.setupInteractions(),this}onRemove(t){return this.container&&t.getPanes().overlayPane.removeChild(this.container),t.off("zoomend moveend",this.updatePosition,this),this}updatePosition(){if(!this.map||!this.container)return;const t=this.map.latLngToLayerPoint(this._latlng);this.container.style.left=`${t.x}px`,this.container.style.top=`${t.y}px`}setupInteractions(){if(!this.container)return;if(!this.box)return;const t=this.container,e=this.box;this.bringToFrontOnClick&&(t.addEventListener("click",(()=>this.toggleInFront(t))),t.addEventListener("keydown",(e=>{"Enter"===e.key&&this.toggleInFront(t)}))),this.bringToFrontOnHover&&(t.addEventListener("mouseenter",(()=>this.toggleInFront(t))),t.addEventListener("mouseleave",(()=>this.toggleInFront(t)))),this.bringToFrontOnFocus&&(e.addEventListener("focus",(()=>this.toggleInFront(t))),e.addEventListener("blur",(()=>this.toggleInFront(t)))),this.onClick&&(t.addEventListener("click",(()=>{var t;return null===(t=this.onClick)||void 0===t?void 0:t.call(this,this._pictogram,this._pointOfInterest)})),t.addEventListener("keypress",(t=>{var e;"Enter"===t.key&&(null===(e=this.onClick)||void 0===e||e.call(this,this._pictogram,this._pointOfInterest))})))}toggleInFront(t){"1000"===t.style.zIndex?t.style.zIndex="auto":t.style.zIndex="1000"}}function o(t){return t.split("_").map((t=>t.charAt(0).toUpperCase()+t.slice(1))).join(" ")}class r{constructor(t){var e,i;this.apiUrl=(null==t?void 0:t.apiUrl)||"https://overpass-api.de/api/interpreter",this.defaultTypes=(null==t?void 0:t.defaultTypes)||["shop","leisure"],this.osmTypes=(null==t?void 0:t.osmTypes)||["node"],this.defaultLimit=(null==t?void 0:t.defaultLimit)||25,this.maxRetries=(null==t?void 0:t.maxRetries)||3,this.retryDelay=(null==t?void 0:t.retryDelay)||1e3,this.timeout=(null==t?void 0:t.timeout)||25,this.deriveNames=null===(e=null==t?void 0:t.deriveNames)||void 0===e||e,this.filterOutNoName=null===(i=null==t?void 0:t.filterOutNoName)||void 0===i||i}async getPointsOfInterest(t,e){let{types:i,limit:o=this.defaultLimit}=e||{};if(0===(null==i?void 0:i.length))return[];void 0===i&&(i=this.defaultTypes);const r=`${t.getSouth()},${t.getWest()},${t.getNorth()},${t.getEast()}`,n=i.map((t=>`${this.osmTypes.map((e=>`${e}["${t}"](${r});`)).join("")}`)),a=`\n [out:json][timeout:${this.timeout}];\n (${n.join("")});\n out center ${o};\n `;return this.fetchWithRetry(a,this.maxRetries)}async fetchWithRetry(t,e){try{const i=await fetch(`${this.apiUrl}?data=${encodeURIComponent(t)}`);if(!i.ok){if([409,504].includes(i.status)&&e>0)return console.warn(`Rate limit or server error, retrying in ${this.retryDelay}ms...`),await this.delay(this.retryDelay),this.fetchWithRetry(t,e-1);throw new Error(`Failed to fetch data from Overpass API: ${i.statusText}`)}const o=await i.json();return this.processResults(o.elements)}catch(t){return console.error("Error fetching POI data from Overpass API:",t),[]}}processResults(t){return t.filter((t=>{var e;return t.lat||(null===(e=t.center)||void 0===e?void 0:e.lat)})).filter((t=>{var e;return t.lon||(null===(e=t.center)||void 0===e?void 0:e.lon)})).map((t=>{var e,i,o,r,n;return{id:t.id.toString(),name:(null===(e=t.tags)||void 0===e?void 0:e.name)||"Unknown",type:(null===(i=t.tags)||void 0===i?void 0:i.amenity)||(null===(o=t.tags)||void 0===o?void 0:o.shop)||"Unknown",latitude:t.lat||(null===(r=t.center)||void 0===r?void 0:r.lat),longitude:t.lon||(null===(n=t.center)||void 0===n?void 0:n.lon),address:this.buildAddress(t.tags),metadata:t||{}}})).map((t=>this.deriveNames?("Unknown"===t.name&&"Unknown"!==t.type&&(t.name=o(t.type)),t):t)).filter((t=>!this.filterOutNoName||"Unknown"!==t.name))}buildAddress(t){var e,i,o,r;const n=null!==(e=null==t?void 0:t["addr:street"])&&void 0!==e?e:"",a=null!==(i=null==t?void 0:t["addr:housenumber"])&&void 0!==i?i:"",s=null!==(o=null==t?void 0:t["addr:city"])&&void 0!==o?o:"",l=`${n} ${a}, ${null!==(r=null==t?void 0:t["addr:postcode"])&&void 0!==r?r:""} ${s}`.trim();return","===l?void 0:l}delay(t){return new Promise((e=>setTimeout(e,t)))}}class n{constructor(t){var e;this.apiUrl=(null==t?void 0:t.apiUrl)||"https://globalsymbols.com/api/v1/labels/search",this.symbolSet=(null==t?void 0:t.symbolSet)||"arasaac",this.includeTypeInDisplayText=(null==t?void 0:t.includeTypeInDisplayText)||!1,this.includeTypeInAriaLabel=null===(e=null==t?void 0:t.includeTypeInAriaLabel)||void 0===e||e,this.cacheStrategy=(null==t?void 0:t.cacheStrategy)||"local-storage",this.cacheExpiration=(null==t?void 0:t.cacheExpiration)||6048e5,this.cachePrefix=(null==t?void 0:t.cachePrefix)||"global-symbols-pictogram-service",this.memoryCache=new Map,"local-storage"===this.cacheStrategy&&this.cleanupLocalStorage()}async getPictogram(t){const e=t.type,i=this.getCacheKey(e),o=this.getFromCache(i);if(o)return this.constructPictogram(t,o);const r=new URLSearchParams({query:e,language:"eng",language_iso_format:"639-3",limit:"1",symbolSet:this.symbolSet});try{const e=await fetch(`${this.apiUrl}?${r.toString()}`);if(!e.ok)throw new Error(`Failed to fetch pictogram: ${e.statusText}`);const o=await e.json();return this.saveToCache(i,o),this.constructPictogram(t,o)}catch(t){throw console.error(`Error fetching pictogram for POI: ${e}`,t),t}}constructPictogram(t,e){return e.length>0?{id:e[0].id.toString(),url:e[0].picto.image_url,displayText:this.includeTypeInDisplayText?`${o(t.type)}: ${t.name}`:t.name,ariaLabel:this.includeTypeInAriaLabel?`${o(t.type)}: ${t.name}`:t.name,description:e[0].description,metadata:e[0]}:void 0}getCacheKey(t){return`${this.cachePrefix}:${this.symbolSet}:${t}`}getFromCache(t){if("in-memory"===this.cacheStrategy){const e=this.memoryCache.get(t);if(e&&Date.now()-e.timestamp<this.cacheExpiration)return e.data;this.memoryCache.delete(t)}else if("local-storage"===this.cacheStrategy){const e=localStorage.getItem(t);if(e){const i=JSON.parse(e);if(Date.now()-i.timestamp<this.cacheExpiration)return i.data;localStorage.removeItem(t)}}}saveToCache(t,e){const i={timestamp:Date.now(),data:e};"in-memory"===this.cacheStrategy?this.memoryCache.set(t,i):"local-storage"===this.cacheStrategy&&localStorage.setItem(t,JSON.stringify(i))}cleanupLocalStorage(){const t=[];for(let e=0;e<localStorage.length;e++){const i=localStorage.key(e);if(!i)continue;if(!i.startsWith(this.cachePrefix))continue;const o=localStorage.getItem(i);if(o){const e=JSON.parse(o);Date.now()-e.timestamp>=this.cacheExpiration&&t.push(i)}}t.forEach((t=>localStorage.removeItem(t)))}}class a{constructor(t){this.lr=(null==t?void 0:t.lr)||"lr",this.tb=(null==t?void 0:t.tb)||"tb",this.rowThreshold=(null==t?void 0:t.rowThreshold)||64}async sortMarkers(t,e){const i=t.map((t=>{const i=t.getLatLng();return{marker:t,point:e.latLngToLayerPoint(i)}})),o=[];return i.forEach((t=>{let e=o.find((e=>Math.abs(e.y-t.point.y)<this.rowThreshold));e||(e={y:t.point.y,markers:[]},o.push(e)),e.markers.push(t)})),o.sort(((t,e)=>"tb"===this.tb?t.y-e.y:e.y-t.y)),o.forEach((t=>{t.markers.sort(((t,e)=>"lr"===this.lr?t.point.x-e.point.x:e.point.x-t.point.x))})),o.map((t=>t.markers.map((t=>t.marker)))).reduce(((t,e)=>t.concat(e)),[])}}class s{constructor(t,i){this.debounceInterval=(null==i?void 0:i.debounceInterval)||300,this.defaultPictogram=null==i?void 0:i.defaultPictogram,this.map=t,this.poiLayerGroup=new e.LayerGroup,this.poiService=(null==i?void 0:i.poiService)||new r(null==i?void 0:i.overpassServiceOptions),this.pictogramService=(null==i?void 0:i.pictogramService)||new n(null==i?void 0:i.globalSymbolsServiceOptions),this.markerSortingService=(null==i?void 0:i.markerSortingService)||new a(null==i?void 0:i.gridSortServiceOptions),this.pictogramMarkerOptions=null==i?void 0:i.pictogramMarkerOptions,this.map.addLayer(this.poiLayerGroup);const o=function(t,e){let i;return(...o)=>{void 0!==i&&clearTimeout(i),i=window.setTimeout((()=>{t(...o).catch((t=>console.error("Debounced function error:",t)))}),e)}}(this.updateMap.bind(this),this.debounceInterval);this.map.on("moveend zoomend",o),this.updateMap()}async updateMap(){const t=this.map.getBounds(),o=(await this.poiService.getPointsOfInterest(t)).map((async t=>{const o=e.latLng(t.latitude,t.longitude),r=await this.pictogramService.getPictogram(t);return r?function(t,e,o,r){return new i(t,e,o,r)}(o,r,t,this.pictogramMarkerOptions):this.defaultPictogram}));let r=(await Promise.all(o)).filter((t=>void 0!==t));r=await this.markerSortingService.sortMarkers(r,this.map),this.poiLayerGroup.clearLayers(),r.forEach((t=>this.poiLayerGroup.addLayer(t)))}}t.PictogramMarker=i,t.initIndependoMaps=function(t,e){return new s(t,e)}}));
//# sourceMappingURL=leaflet-independo-maps.min.js.map