From a7fa3666351b8cfc025203f2a07f10e404ede6ff Mon Sep 17 00:00:00 2001 From: James Lyne Date: Thu, 29 Jul 2021 17:46:11 +0100 Subject: [PATCH] Create marker labels lazily when showLabels is false --- src/leaflet/icon/GenericIcon.ts | 57 +++++++++++++++++------- src/leaflet/layer/LiveAtlasLayerGroup.ts | 56 ++++++++++++++++------- src/leaflet/marker/GenericMarker.ts | 24 ++++++++-- src/util/markers.ts | 11 +---- 4 files changed, 103 insertions(+), 45 deletions(-) diff --git a/src/leaflet/icon/GenericIcon.ts b/src/leaflet/icon/GenericIcon.ts index a94a57c..40f71e2 100644 --- a/src/leaflet/icon/GenericIcon.ts +++ b/src/leaflet/icon/GenericIcon.ts @@ -47,8 +47,14 @@ export class GenericIcon extends DivIcon { // @ts-ignore options: GenericIconOptions; - _image?: HTMLImageElement; - _label?: HTMLSpanElement; + + private _image?: HTMLImageElement; + private _label?: HTMLSpanElement; + private _container?: HTMLDivElement; + private _labelCreated: boolean = false; + private _onHover: EventListener = () => { + this.createLabel(); + }; constructor(options: GenericIconOptions) { super(Object.assign(GenericIcon.defaultOptions, options)); @@ -64,14 +70,41 @@ export class GenericIcon extends DivIcon { size = point(this.options.iconSize as PointExpression); this._image = markerIcon.cloneNode(false) as HTMLImageElement; - this._label = markerLabel.cloneNode(false) as HTMLSpanElement; - - const sizeClass = [size.x, size.y].join('x'); this._image.width = size.x; this._image.height = size.y; this._image.src = url; + // @ts-ignore + Icon.prototype._setIconStyles.call(this, div, 'icon'); + + div.appendChild(this._image); + div.classList.add('marker'); + + if(this.options.className) { + div.classList.add(this.options.className); + } + + //Create label lazily on hover + this._image.addEventListener('mouseover', this._onHover); + + this._container = div; + + return div; + } + + createLabel() { + if(!this._container || this._labelCreated) { + return; + } + + this._image?.removeEventListener('mouseover', this._onHover); + + const size = point(this.options.iconSize as PointExpression), + sizeClass = [size.x, size.y].join('x'); + + this._label = markerLabel.cloneNode(false) as HTMLSpanElement; + this._label.classList.add(/*'markerName_' + set.id,*/ `marker__label--${sizeClass}`); if (this.options.isHtml) { @@ -80,18 +113,8 @@ export class GenericIcon extends DivIcon { this._label.textContent = this.options.label; } - // @ts-ignore - Icon.prototype._setIconStyles.call(this, div, 'icon'); - - div.appendChild(this._image); - div.appendChild(this._label); - div.classList.add('marker'); - - if(this.options.className) { - div.classList.add(this.options.className); - } - - return div; + this._container!.appendChild(this._label); + this._labelCreated = true; } update(options: GenericIconOptions) { diff --git a/src/leaflet/layer/LiveAtlasLayerGroup.ts b/src/leaflet/layer/LiveAtlasLayerGroup.ts index 776e2fe..6b975e5 100644 --- a/src/leaflet/layer/LiveAtlasLayerGroup.ts +++ b/src/leaflet/layer/LiveAtlasLayerGroup.ts @@ -15,6 +15,7 @@ */ import {Layer, Map as LeafletMap, LayerGroup, LayerOptions, Util, Marker, Path} from "leaflet"; +import {GenericMarker} from "@/leaflet/marker/GenericMarker"; export interface LiveAtlasLayerGroupOptions extends LayerOptions { id: string; //Added to the name of layer group panes @@ -29,11 +30,11 @@ export interface LiveAtlasLayerGroupOptions extends LayerOptions { export default class LiveAtlasLayerGroup extends LayerGroup { // @ts-ignore options: LiveAtlasLayerGroupOptions; - _zoomLimitedLayers: Set; //Layers which are zoom limited and should be checked on zoom + private _zoomLimitedLayers: Set; //Layers which are zoom limited and should be checked on zoom _layers: any; _markerPane?: HTMLElement; - _zoomEndCallback = () => this._updateLayerVisibility(); + private _zoomEndCallback = () => this._updateLayerVisibility(); constructor(options: LiveAtlasLayerGroupOptions) { super([], options); @@ -80,7 +81,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup { layer.options.pane = `vectors`; } - const zoomLimited = this._isLayerZoomLimited(layer); + const zoomLimited = LiveAtlasLayerGroup._isLayerZoomLimited(layer); if (zoomLimited) { this._zoomLimitedLayers.add(layer); @@ -89,11 +90,11 @@ export default class LiveAtlasLayerGroup extends LayerGroup { if (this._map) { //If layer is zoom limited, only add to map if it should be visible if (zoomLimited) { - if (this._isLayerVisible(layer, this._map.getZoom())) { - this._map.addLayer(layer); + if (LiveAtlasLayerGroup._isLayerVisible(layer, this._map.getZoom())) { + this._addToMap(layer); } } else { - this._map.addLayer(layer); + this._addToMap(layer); } } @@ -106,7 +107,19 @@ export default class LiveAtlasLayerGroup extends LayerGroup { } update(options: LiveAtlasLayerGroupOptions) { - this.options.showLabels = options.showLabels; + if(this.options.showLabels !== options.showLabels) { + //Create labels if they are now always visible + //TODO: This will be slow when many markers exist. Is it worth doing? + if(options.showLabels) { + this.eachLayer((layer) => { + if(layer instanceof GenericMarker) { + (layer as GenericMarker).createLabel(); + } + }); + } + + this.options.showLabels = options.showLabels; + } if(this._markerPane) { this._markerPane.classList.toggle('leaflet-pane--show-labels', options.showLabels); @@ -128,7 +141,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup { } } - _updateLayerVisibility(onAdd?: boolean) { + private _updateLayerVisibility(onAdd?: boolean) { if(!this._map) { return; } @@ -142,34 +155,34 @@ export default class LiveAtlasLayerGroup extends LayerGroup { this.eachLayer((layer) => { //Per marker zoom limits take precedence, if present if(this._zoomLimitedLayers.has(layer)) { - this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer); + LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer); } else { //Otherwise apply group zoom limit - visible ? this._map.addLayer(layer) : this._map.removeLayer(layer); + visible ? this._addToMap(layer) : this._removeFromMap(layer); } }, this); //Group isn't zoom limited, but some individual markers are } else if(this._zoomLimitedLayers.size) { this._zoomLimitedLayers.forEach((layer) => { - this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer); + LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer); }); //Nothing is zoom limited, but we've just been added to the map } else if(onAdd) { - this.eachLayer(this._map.addLayer, this._map); + this.eachLayer((layer: Layer) => this._addToMap(layer), this._map); } } //Returns if this layer group has zoom limits defined - _isZoomLimited() { + private _isZoomLimited() { return this.options.maxZoom !== undefined || this.options.minZoom !== undefined; } //Returns if the given layer has its own zoom limits defined - _isLayerZoomLimited(layer: Layer) { + private static _isLayerZoomLimited(layer: Layer) { return ((layer as any).options && (layer as any).options.minZoom !== undefined) && ((layer as any).options && (layer as any).options.maxZoom !== undefined); } - _isLayerVisible(layer: Layer, currentZoom: number) { + private static _isLayerVisible(layer: Layer, currentZoom: number) { let minZoom = -Infinity, maxZoom = Infinity; @@ -183,4 +196,17 @@ export default class LiveAtlasLayerGroup extends LayerGroup { return currentZoom >= minZoom && currentZoom <= maxZoom; } + + private _addToMap(layer: Layer) { + this._map.addLayer(layer) + + //Create marker label immediately if labels are visible by default + if(layer instanceof GenericMarker && this.options.showLabels) { + (layer as GenericMarker).createLabel(); + } + } + + private _removeFromMap(layer: Layer) { + this._map.removeLayer(layer) + } } diff --git a/src/leaflet/marker/GenericMarker.ts b/src/leaflet/marker/GenericMarker.ts index e34b4be..1eed0f4 100644 --- a/src/leaflet/marker/GenericMarker.ts +++ b/src/leaflet/marker/GenericMarker.ts @@ -15,16 +15,30 @@ */ import {MarkerOptions, Marker, Util, LatLngExpression, Icon} from 'leaflet'; +import {LiveAtlasMarker} from "@/index"; +import {GenericIcon} from "@/leaflet/icon/GenericIcon"; export interface GenericMarkerOptions extends MarkerOptions { + icon: GenericIcon; minZoom?: number; maxZoom?: number; } export class GenericMarker extends Marker { - constructor(latLng: LatLngExpression, options: GenericMarkerOptions) { - super(latLng, options); - Util.setOptions(this, options); + declare options: GenericMarkerOptions; + + constructor(latLng: LatLngExpression, options: LiveAtlasMarker) { + super(latLng, {}); + + this.options.icon = new GenericIcon({ + icon: options.icon, + label: options.label, + iconSize: options.dimensions, + isHtml: options.isLabelHTML, + }); + + this.options.maxZoom = options.maxZoom; + this.options.minZoom = options.maxZoom; } // noinspection JSUnusedGlobalSymbols @@ -35,4 +49,8 @@ export class GenericMarker extends Marker { getIcon(): Icon.Default { return this.options.icon as Icon.Default; } + + createLabel(): void { + this.options.icon.createLabel(); + } } diff --git a/src/util/markers.ts b/src/util/markers.ts index 761908a..082a48d 100644 --- a/src/util/markers.ts +++ b/src/util/markers.ts @@ -23,16 +23,7 @@ import {GenericMarker} from "@/leaflet/marker/GenericMarker"; import {LiveAtlasMarker} from "@/index"; export const createMarker = (options: LiveAtlasMarker, converter: Function): Marker => { - const marker = new GenericMarker(converter(options.location), { - icon: new GenericIcon({ - icon: options.icon, - label: options.label, - iconSize: options.dimensions, - isHtml: options.isLabelHTML, - }), - maxZoom: options.maxZoom, - minZoom: options.minZoom, - }); + const marker = new GenericMarker(converter(options.location), options); marker.on('click', (e: LeafletMouseEvent) => { e.target._map.panTo(e.target.getLatLng());