|
1 | 1 | import { bounds, point, extend } from 'leaflet';
|
2 | 2 |
|
3 |
| -import { featureLayer } from './mapml/layers/FeatureLayer.js'; |
| 3 | +import { MapFeatureLayer } from './mapml/layers/MapFeatureLayer.js'; |
4 | 4 | import { featureRenderer } from './mapml/features/featureRenderer.js';
|
5 | 5 | import { Util } from './mapml/utils/Util.js';
|
6 | 6 | import proj4 from 'proj4';
|
| 7 | +import { calculatePosition } from './mapml/elementSupport/layers/calculatePosition.js'; |
7 | 8 |
|
8 | 9 | export class HTMLFeatureElement extends HTMLElement {
|
9 | 10 | static get observedAttributes() {
|
10 | 11 | return ['zoom', 'min', 'max'];
|
11 | 12 | }
|
12 | 13 |
|
13 | 14 | /* jshint ignore:start */
|
14 |
| - #hasConnected; |
| 15 | + #hasConnected; // prevents attributeChangedCallback before connectedCallback |
15 | 16 | /* jshint ignore:end */
|
16 | 17 | get zoom() {
|
17 | 18 | // for templated or queried features ** native zoom is only used for zoomTo() **
|
@@ -138,6 +139,9 @@ export class HTMLFeatureElement extends HTMLElement {
|
138 | 139 | return this._getFeatureExtent();
|
139 | 140 | }
|
140 | 141 | }
|
| 142 | + get position() { |
| 143 | + return calculatePosition(this); |
| 144 | + } |
141 | 145 | getMapEl() {
|
142 | 146 | return Util.getClosest(this, 'mapml-viewer,map[is=web-map]');
|
143 | 147 | }
|
@@ -172,17 +176,21 @@ export class HTMLFeatureElement extends HTMLElement {
|
172 | 176 | // used for fallback zoom getter for static features
|
173 | 177 | this._initialZoom = this.getMapEl().zoom;
|
174 | 178 | this._parentEl =
|
175 |
| - this.parentNode.nodeName.toUpperCase() === 'MAP-LAYER' || |
176 |
| - this.parentNode.nodeName.toUpperCase() === 'LAYER-' || |
177 |
| - this.parentNode.nodeName.toUpperCase() === 'MAP-LINK' |
| 179 | + this.parentNode.nodeName === 'MAP-LAYER' || |
| 180 | + this.parentNode.nodeName === 'LAYER-' || |
| 181 | + this.parentNode.nodeName === 'MAP-LINK' |
178 | 182 | ? this.parentNode
|
179 | 183 | : this.parentNode.host;
|
180 | 184 | if (
|
181 | 185 | this.getLayerEl().hasAttribute('data-moving') ||
|
182 | 186 | this._parentEl.parentElement?.hasAttribute('data-moving')
|
183 | 187 | )
|
184 | 188 | return;
|
185 |
| - if (this._parentEl.nodeName === 'MAP-LINK') { |
| 189 | + if ( |
| 190 | + this._parentEl.nodeName === 'MAP-LAYER' || |
| 191 | + this._parentEl.nodeName === 'LAYER-' || |
| 192 | + this._parentEl.nodeName === 'MAP-LINK' |
| 193 | + ) { |
186 | 194 | this._createOrGetFeatureLayer();
|
187 | 195 | }
|
188 | 196 | // use observer to monitor the changes in mapFeature's subtree
|
@@ -215,6 +223,17 @@ export class HTMLFeatureElement extends HTMLElement {
|
215 | 223 | this._observer.disconnect();
|
216 | 224 | if (this._featureLayer) {
|
217 | 225 | this.removeFeature(this._featureLayer);
|
| 226 | + // If this was the last feature in the layer, clean up the layer |
| 227 | + if (this._featureLayer.getLayers().length === 0) { |
| 228 | + if (this._featureLayer.options.renderer) { |
| 229 | + // since this is the last reference to the MapFeatureLayer, we need to also |
| 230 | + // manually remove the shared renderer |
| 231 | + this._featureLayer.options.renderer.remove(); |
| 232 | + } |
| 233 | + this._featureLayer.remove(); |
| 234 | + this._featureLayer = null; |
| 235 | + delete this._featureLayer; |
| 236 | + } |
218 | 237 | }
|
219 | 238 | }
|
220 | 239 |
|
@@ -292,65 +311,80 @@ export class HTMLFeatureElement extends HTMLElement {
|
292 | 311 | return this.previousElementSibling;
|
293 | 312 | }
|
294 | 313 | _createOrGetFeatureLayer() {
|
295 |
| - if (this.isFirst() && this._parentEl._templatedLayer) { |
296 |
| - const parentElement = this._parentEl; |
297 |
| - |
298 |
| - let map = parentElement.getMapEl()._map; |
299 |
| - |
300 |
| - // Create a new FeatureLayer |
301 |
| - this._featureLayer = featureLayer(null, { |
302 |
| - // pass the vector layer a renderer of its own, otherwise leaflet |
303 |
| - // puts everything into the overlayPane |
304 |
| - renderer: featureRenderer(), |
305 |
| - // pass the vector layer the container for the parent into which |
306 |
| - // it will append its own container for rendering into |
307 |
| - pane: parentElement._templatedLayer.getContainer(), |
308 |
| - // the bounds will be static, fixed, constant for the lifetime of the layer |
309 |
| - layerBounds: parentElement.getBounds(), |
310 |
| - zoomBounds: this._getZoomBounds(), |
311 |
| - projection: map.options.projection, |
312 |
| - mapEl: parentElement.getMapEl(), |
313 |
| - onEachFeature: function (properties, geometry) { |
314 |
| - if (properties) { |
315 |
| - const popupOptions = { |
316 |
| - autoClose: false, |
317 |
| - autoPan: true, |
318 |
| - maxHeight: map.getSize().y * 0.5 - 50, |
319 |
| - maxWidth: map.getSize().x * 0.7, |
320 |
| - minWidth: 165 |
321 |
| - }; |
322 |
| - var c = document.createElement('div'); |
323 |
| - c.classList.add('mapml-popup-content'); |
324 |
| - c.insertAdjacentHTML('afterbegin', properties.innerHTML); |
325 |
| - geometry.bindPopup(c, popupOptions); |
| 314 | + // Wait for parent layer to be ready before proceeding |
| 315 | + this._parentEl |
| 316 | + .whenReady() |
| 317 | + .then(() => { |
| 318 | + // Detect parent context and get the appropriate layer container |
| 319 | + const isMapLink = this._parentEl.nodeName === 'MAP-LINK'; |
| 320 | + const parentLayer = isMapLink |
| 321 | + ? this._parentEl._templatedLayer |
| 322 | + : this._parentEl._layer; |
| 323 | + |
| 324 | + if (this.isFirst() && parentLayer) { |
| 325 | + const parentElement = this._parentEl; |
| 326 | + |
| 327 | + let map = parentElement.getMapEl()._map; |
| 328 | + |
| 329 | + this._featureLayer = new MapFeatureLayer(null, { |
| 330 | + // pass the vector layer a renderer of its own, otherwise leaflet |
| 331 | + // puts everything into the overlayPane |
| 332 | + // with this feature creating its own MapFeatureLayer for each |
| 333 | + // sub-sequence of features, it means that there may be > 1 <svg> |
| 334 | + // container (one per renderer) in the pane... |
| 335 | + renderer: featureRenderer(), |
| 336 | + // pass the vector layer the container for the parent into which |
| 337 | + // it will append its own container for rendering into |
| 338 | + pane: parentLayer.getContainer(), |
| 339 | + // the bounds will be static, fixed, constant for the lifetime of a (templated) layer |
| 340 | + ...(isMapLink && parentElement.getBounds() |
| 341 | + ? { layerBounds: parentElement.getBounds() } |
| 342 | + : {}), |
| 343 | + ...(isMapLink ? { zoomBounds: this._getZoomBounds() } : {}), |
| 344 | + ...(isMapLink ? {} : { _leafletLayer: parentElement._layer }), |
| 345 | + zIndex: this.position, |
| 346 | + projection: map.options.projection, |
| 347 | + mapEl: parentElement.getMapEl(), |
| 348 | + onEachFeature: function (properties, geometry) { |
| 349 | + if (properties) { |
| 350 | + const popupOptions = { |
| 351 | + autoClose: false, |
| 352 | + autoPan: true, |
| 353 | + maxHeight: map.getSize().y * 0.5 - 50, |
| 354 | + maxWidth: map.getSize().x * 0.7, |
| 355 | + minWidth: 165 |
| 356 | + }; |
| 357 | + var c = document.createElement('div'); |
| 358 | + c.classList.add('mapml-popup-content'); |
| 359 | + c.insertAdjacentHTML('afterbegin', properties.innerHTML); |
| 360 | + geometry.bindPopup(c, popupOptions); |
| 361 | + } |
| 362 | + } |
| 363 | + }); |
| 364 | + // this is used by DebugOverlay testing "multipleExtents.test.js |
| 365 | + // but do we really need or want each feature to have the bounds of the |
| 366 | + // map link? tbd |
| 367 | + extend(this._featureLayer.options, { |
| 368 | + _leafletLayer: Object.assign(this._featureLayer, { |
| 369 | + _layerEl: this.getLayerEl() |
| 370 | + }) |
| 371 | + }); |
| 372 | + |
| 373 | + this.addFeature(this._featureLayer); |
| 374 | + |
| 375 | + // add MapFeatureLayer to appropriate parent layer |
| 376 | + parentLayer.addLayer(this._featureLayer); |
| 377 | + } else { |
| 378 | + // get the previous feature's layer |
| 379 | + this._featureLayer = this.getPrevious()?._featureLayer; |
| 380 | + if (this._featureLayer) { |
| 381 | + this.addFeature(this._featureLayer); |
326 | 382 | }
|
327 | 383 | }
|
| 384 | + }) |
| 385 | + .catch((error) => { |
| 386 | + console.log('Error waiting for parent layer to be ready:', error); |
328 | 387 | });
|
329 |
| - // this is used by DebugOverlay testing "multipleExtents.test.js |
330 |
| - // but do we really need or want each feature to have the bounds of the |
331 |
| - // map link? tbd |
332 |
| - extend(this._featureLayer.options, { |
333 |
| - _leafletLayer: Object.assign(this._featureLayer, { |
334 |
| - _layerEl: this.getLayerEl() |
335 |
| - }) |
336 |
| - }); |
337 |
| - |
338 |
| - this.addFeature(this._featureLayer); |
339 |
| - |
340 |
| - // add featureLayer to TemplatedFeaturesOrTilesLayer of the parentElement |
341 |
| - if ( |
342 |
| - parentElement._templatedLayer && |
343 |
| - parentElement._templatedLayer.addLayer |
344 |
| - ) { |
345 |
| - parentElement._templatedLayer.addLayer(this._featureLayer); |
346 |
| - } |
347 |
| - } else { |
348 |
| - // get the previous feature's layer |
349 |
| - this._featureLayer = this.getPrevious()?._featureLayer; |
350 |
| - if (this._featureLayer) { |
351 |
| - this.addFeature(this._featureLayer); |
352 |
| - } |
353 |
| - } |
354 | 388 | }
|
355 | 389 | _setUpEvents() {
|
356 | 390 | ['click', 'focus', 'blur', 'keyup', 'keydown'].forEach((name) => {
|
|
0 commit comments