diff --git a/.gitignore b/.gitignore index 393215552..2a30c7d44 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,4 @@ /test-results/ .idea/ *.iml -test.html \ No newline at end of file +test.html diff --git a/package-lock.json b/package-lock.json index 00b33245e..3a3810702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@maps4html/mapml", - "version": "0.15.0", + "version": "0.16.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@maps4html/mapml", - "version": "0.15.0", + "version": "0.16.0", "hasInstallScript": true, "license": "W3C", "devDependencies": { diff --git a/playwright.config.js b/playwright.config.js index 7b9d3f897..0d739b668 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -11,6 +11,7 @@ export default defineConfig({ use: { headless: true, browserName: 'chromium', - baseURL: 'http://localhost:30001/' + baseURL: 'http://localhost:30001/', + ignoreHTTPSErrors: true } -}); \ No newline at end of file +}); diff --git a/src/layer.js b/src/layer.js index dd16c5e2b..6c1e3ed11 100644 --- a/src/layer.js +++ b/src/layer.js @@ -1,7 +1,8 @@ import { setOptions, DomUtil, bounds, point } from 'leaflet'; import { Util } from './mapml/utils/Util.js'; -import { MapMLLayer, mapMLLayer } from './mapml/layers/MapMLLayer.js'; +import { MapLayer, mapLayer } from './mapml/layers/MapLayer.js'; +import { MapTileLayer } from './mapml/layers/MapTileLayer.js'; import { createLayerControlHTML } from './mapml/elementSupport/layers/createLayerControlForLayer.js'; export class BaseLayerElement extends HTMLElement { @@ -72,7 +73,7 @@ export class BaseLayerElement extends HTMLElement { get extent() { // calculate the bounds of all content, return it. - if (this._layer && !this._layer.bounds) { + if (this._layer) { this._layer._calculateBounds(); } return this._layer @@ -296,7 +297,7 @@ export class BaseLayerElement extends HTMLElement { this.selectAlternateOrChangeProjection(); }) .then(() => { - this._layer = mapMLLayer(new URL(this.src, base).href, this, { + this._layer = mapLayer(new URL(this.src, base).href, this, { projection: this.getProjection(), opacity: this.opacity }); @@ -333,7 +334,7 @@ export class BaseLayerElement extends HTMLElement { this.selectAlternateOrChangeProjection(); }) .then(() => { - this._layer = mapMLLayer(null, this, { + this._layer = mapLayer(null, this, { projection: this.getProjection(), opacity: this.opacity }); @@ -467,7 +468,7 @@ export class BaseLayerElement extends HTMLElement { * Runs the effects of the mutation observer, which is to add map-features' and * map-extents' leaflet layer implementations to the appropriate container in * the map-layer._layer: either as a sub-layer directly in the LayerGroup - * (MapMLLayer._layer) or as a sub-layer in the MapMLLayer._mapmlvectors + * (MapLayer._layer) or as a sub-layer in the MapLayer._mapmlvectors * FeatureGroup */ _runMutationObserver(elementsGroup) { @@ -633,6 +634,19 @@ export class BaseLayerElement extends HTMLElement { } _validateDisabled() { + const countTileLayers = () => { + let totalCount = 0; + let disabledCount = 0; + + this._layer.eachLayer((layer) => { + if (layer instanceof MapTileLayer) { + totalCount++; + if (!layer.isVisible()) disabledCount++; + } + }); + + return { totalCount, disabledCount }; + }; // setTimeout is necessary to make the validateDisabled happen later than the moveend operations etc., // to ensure that the validated result is correct setTimeout(() => { @@ -671,10 +685,15 @@ export class BaseLayerElement extends HTMLElement { if (mapExtents[i]._validateDisabled()) disabledExtentCount++; } - } else if (layer[type]) { - // not a templated layer + } else if (type === '_mapmlvectors') { + // inline / static features totalExtentCount++; if (!layer[type].isVisible()) disabledExtentCount++; + } else { + // inline tiles + const tileLayerCounts = countTileLayers(); + totalExtentCount += tileLayerCounts.totalCount; + disabledExtentCount += tileLayerCounts.disabledCount; } } } diff --git a/src/map-extent.js b/src/map-extent.js index cdc1f0395..1941c4ad5 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -1,7 +1,7 @@ import { bounds as Lbounds, point as Lpoint } from 'leaflet'; import { Util } from './mapml/utils/Util.js'; -import { extentLayer } from './mapml/layers/ExtentLayer.js'; +import { mapExtentLayer } from './mapml/layers/MapExtentLayer.js'; import { createLayerControlExtentHTML } from './mapml/elementSupport/extents/createLayerControlForExtent.js'; /* global M */ @@ -251,7 +251,7 @@ export class HTMLExtentElement extends HTMLElement { // when projection is changed, the parent map-layer._layer is created (so whenReady is fulfilled) but then removed, // then the map-extent disconnectedCallback will be triggered by map-layer._onRemove() (clear the shadowRoot) // even before connectedCallback is finished - // in this case, the microtasks triggered by the fulfillment of the removed MapMLLayer should be stopped as well + // in this case, the microtasks triggered by the fulfillment of the removed MapLayer should be stopped as well // !this.isConnected <=> the disconnectedCallback has run before if (!this.isConnected) return; /* jshint ignore:start */ @@ -263,7 +263,7 @@ export class HTMLExtentElement extends HTMLElement { // this._opacity is used to record the current opacity value (with or without updates), // the initial value of this._opacity should be set as opacity attribute value, if exists, or the default value 1.0 this._opacity = this.opacity || 1.0; - this._extentLayer = extentLayer({ + this._extentLayer = mapExtentLayer({ opacity: this.opacity, crs: M[this.units], extentZIndex: Array.from( @@ -432,7 +432,7 @@ export class HTMLExtentElement extends HTMLElement { _handleChange() { // add _extentLayer to map if map-extent is checked, otherwise remove it if (this.checked && !this.disabled && this.parentLayer._layer) { - // can be added to mapmllayer layerGroup no matter map-layer is checked or not + // can be added to MapLayer LayerGroup no matter map-layer is checked or not this._extentLayer.addTo(this.parentLayer._layer); this._extentLayer.setZIndex( Array.from( diff --git a/src/map-feature.js b/src/map-feature.js index 5ae172fd4..338c05769 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -1,5 +1,7 @@ -import { bounds, point } from 'leaflet'; +import { bounds, point, extend } from 'leaflet'; +import { featureLayer } from './mapml/layers/FeatureLayer.js'; +import { featureRenderer } from './mapml/features/featureRenderer.js'; import { Util } from './mapml/utils/Util.js'; import proj4 from 'proj4'; @@ -180,6 +182,9 @@ export class HTMLFeatureElement extends HTMLElement { this._parentEl.parentElement?.hasAttribute('data-moving') ) return; + if (this._parentEl.nodeName === 'MAP-LINK') { + this._createOrGetFeatureLayer(); + } // use observer to monitor the changes in mapFeature's subtree // (i.e. map-properties, map-featurecaption, map-coordinates) this._observer = new MutationObserver((mutationList) => { @@ -256,17 +261,97 @@ export class HTMLFeatureElement extends HTMLElement { addFeature(layerToAddTo) { this._featureLayer = layerToAddTo; - let parentLayer = this.getLayerEl(); // "synchronize" the event handlers between map-feature and if (!this.querySelector('map-geometry')) return; let fallbackCS = this._getFallbackCS(); - let content = parentLayer.src ? parentLayer.shadowRoot : parentLayer; this._geometry = layerToAddTo.createGeometry(this, fallbackCS); // side effect: extends `this` with this._groupEl if successful, points to svg g element that renders to map SD if (!this._geometry) return; + this._geometry._layerEl = this.getLayerEl(); layerToAddTo.addLayer(this._geometry); this._setUpEvents(); } + isFirst() { + // Get the previous element sibling + const prevSibling = this.previousElementSibling; + + // If there's no previous sibling, return true + if (!prevSibling) { + return true; + } + + // Compare the node names (tag names) - return true if they're different + return this.nodeName !== prevSibling.nodeName; + } + getPrevious() { + // Check if this is the first element of a sequence + if (this.isFirst()) { + return null; // No previous element available + } + + // Since we know it's not the first, we can safely return the previous element sibling + return this.previousElementSibling; + } + _createOrGetFeatureLayer() { + if (this.isFirst() && this._parentEl._templatedLayer) { + const parentElement = this._parentEl; + let map = parentElement.getMapEl()._map; + + // Create a new FeatureLayer + this._featureLayer = featureLayer(null, { + // pass the vector layer a renderer of its own, otherwise leaflet + // puts everything into the overlayPane + renderer: featureRenderer(), + // pass the vector layer the container for the parent into which + // it will append its own container for rendering into + pane: parentElement._templatedLayer.getContainer(), + // the bounds will be static, fixed, constant for the lifetime of the layer + layerBounds: parentElement.getBounds(), + zoomBounds: this._getZoomBounds(), + projection: map.options.projection, + mapEl: parentElement.getMapEl(), + onEachFeature: function (properties, geometry) { + if (properties) { + const popupOptions = { + autoClose: false, + autoPan: true, + maxHeight: map.getSize().y * 0.5 - 50, + maxWidth: map.getSize().x * 0.7, + minWidth: 165 + }; + var c = document.createElement('div'); + c.classList.add('mapml-popup-content'); + c.insertAdjacentHTML('afterbegin', properties.innerHTML); + geometry.bindPopup(c, popupOptions); + } + } + }); + // this is used by DebugOverlay testing "multipleExtents.test.js + // but do we really need or want each feature to have the bounds of the + // map link? tbd + extend(this._featureLayer.options, { + _leafletLayer: Object.assign(this._featureLayer, { + _layerEl: this.getLayerEl() + }) + }); + + this.addFeature(this._featureLayer); + + // add featureLayer to TemplatedFeaturesOrTilesLayer of the parentElement + if ( + parentElement._templatedLayer && + parentElement._templatedLayer.addLayer + ) { + parentElement._templatedLayer.addLayer(this._featureLayer); + } + } else { + // get the previous feature's layer + this._featureLayer = this.getPrevious()?._featureLayer; + if (this._featureLayer) { + this.addFeature(this._featureLayer); + } + } + } _setUpEvents() { ['click', 'focus', 'blur', 'keyup', 'keydown'].forEach((name) => { // when is clicked / focused / blurred diff --git a/src/map-link.js b/src/map-link.js index 600b2f423..83ed24ba7 100644 --- a/src/map-link.js +++ b/src/map-link.js @@ -10,7 +10,7 @@ import { import { Util } from './mapml/utils/Util.js'; import { templatedImageLayer } from './mapml/layers/TemplatedImageLayer.js'; import { templatedTileLayer } from './mapml/layers/TemplatedTileLayer.js'; -import { templatedFeaturesLayer } from './mapml/layers/TemplatedFeaturesLayer.js'; +import { templatedFeaturesOrTilesLayer } from './mapml/layers/TemplatedFeaturesOrTilesLayer.js'; import { templatedPMTilesLayer } from './mapml/layers/TemplatedPMTilesLayer.js'; /* global M */ @@ -436,7 +436,8 @@ export class HTMLLinkElement extends HTMLElement { // be loaded as part of a templated layer processing i.e. on moveend // and the generated that implements this should be located // in the parent ._templatedLayer.container root node if - // the _templatedLayer is an instance of TemplatedTileLayer or TemplatedFeaturesLayer + // the _templatedLayer is an instance of TemplatedTileLayer or + // TemplatedFeaturesOrTilesLayer // // if the parent node (or the host of the shadow root parent node) is map-layer, the link should be created in the _layer // container @@ -551,12 +552,15 @@ export class HTMLLinkElement extends HTMLElement { if (!this.shadowRoot) { this.attachShadow({ mode: 'open' }); } - this._templatedLayer = templatedFeaturesLayer(this._templateVars, { + // Use the FeaturesTilesLayerGroup to handle both map-feature and map-tile elements + this._templatedLayer = templatedFeaturesOrTilesLayer(this._templateVars, { zoomBounds: this.getZoomBounds(), extentBounds: this.getBounds(), zIndex: this.zIndex, pane: this.parentExtent._extentLayer.getContainer(), - linkEl: this + linkEl: this, + projection: this.mapEl._map.options.projection, + renderer: this.mapEl._map.options.renderer }).addTo(this.parentExtent._extentLayer); } else if (this.rel === 'query') { if (!this.shadowRoot) { diff --git a/src/map-select.js b/src/map-select.js index 56451839d..c4eae4091 100644 --- a/src/map-select.js +++ b/src/map-select.js @@ -39,7 +39,7 @@ export class HTMLSelectElement extends HTMLElement { this._extentEl = this.parentElement; // TODO make the layer redraw after map-select change event // origin of this block was in _initTemplateVars from map-extent, which was - // originally part of MapMLLayer... + // originally part of MapLayer... // // use a throwaway div to parse the input from MapML into HTML this._createLayerControlForSelect(); diff --git a/src/map-tile.js b/src/map-tile.js new file mode 100644 index 000000000..5dca12045 --- /dev/null +++ b/src/map-tile.js @@ -0,0 +1,281 @@ +import { bounds as Lbounds, point as Lpoint } from 'leaflet'; + +import { Util } from './mapml/utils/Util.js'; +import { mapTileLayer } from './mapml/layers/MapTileLayer.js'; + +/* global M */ + +export class HTMLTileElement extends HTMLElement { + static get observedAttributes() { + return ['row', 'col', 'zoom', 'src']; + } + /* jshint ignore:start */ + #hasConnected; + /* jshint ignore:end */ + get row() { + return +(this.hasAttribute('row') ? this.getAttribute('row') : 0); + } + set row(val) { + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal)) { + this.setAttribute('row', parsedVal); + } + } + get col() { + return +(this.hasAttribute('col') ? this.getAttribute('col') : 0); + } + set col(val) { + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal)) { + this.setAttribute('col', parsedVal); + } + } + get zoom() { + // for templated or queried features ** native zoom is only used for zoomTo() ** + let meta = {}, + metaEl = this.getMeta('zoom'); + if (metaEl) + meta = Util._metaContentToObject(metaEl.getAttribute('content')); + if (this._parentElement.nodeName === 'MAP-LINK') { + // nativeZoom = zoom attribute || (sd.map-meta zoom 'value' || 'max') || this._initialZoom + return +(this.hasAttribute('zoom') + ? this.getAttribute('zoom') + : meta.value + ? meta.value + : meta.max + ? meta.max + : this._initialZoom); + } else { + // for "static" features + // nativeZoom zoom attribute || this._initialZoom + // NOTE we don't use map-meta here, because the map-meta is the minimum + // zoom bounds for the layer, and is extended by additional features + // if added / removed during layer lifetime + return +(this.hasAttribute('zoom') + ? this.getAttribute('zoom') + : this._initialZoom); + } + } + set zoom(val) { + var parsedVal = parseInt(val, 10); + if (!isNaN(parsedVal) && parsedVal >= 0 && parsedVal <= 25) { + this.setAttribute('zoom', parsedVal); + } + } + get src() { + return this.hasAttribute('src') ? this.getAttribute('src') : ''; + } + set src(val) { + if (val) { + this.setAttribute('src', val); + } + } + get extent() { + if (!this._extent) { + this._calculateExtent(); + } + return this._extent; + } + constructor() { + // Always call super first in constructor + super(); + } + async connectedCallback() { + // initialization is done in connectedCallback, attribute initialization + // calls (which happen first) are effectively ignored, so we should be able + // to rely on them being all correctly set by this time e.g. zoom, row, col + // all now have a value that together identify this tiled bit of space + /* jshint ignore:start */ + this.#hasConnected = true; + /* jshint ignore:end */ + // set the initial zoom of the map when features connected + // used for fallback zoom getter for static features + // ~copied from map-feature.js + this._initialZoom = this.getMapEl().zoom; + // Get parent element to determine how to handle the tile + // Need to handle shadow DOM correctly like map-feature does + this._parentElement = + this.parentNode.nodeName.toUpperCase() === 'MAP-LAYER' || + this.parentNode.nodeName.toUpperCase() === 'LAYER-' || + this.parentNode.nodeName.toUpperCase() === 'MAP-LINK' + ? this.parentNode + : this.parentNode.host; + + // in the case of that is rendered but never connected, this won't + // matter, but it speeds up rendering for tiles that go through here... + const imgObj = new Image(); + imgObj.src = this.getAttribute('src'); + + await this._createOrGetTileLayer(); + } + + disconnectedCallback() { + // If this is a map-tile connected to a tile layer, remove it from the layer + if (this._tileLayer) { + this._tileLayer.removeMapTile(this); + + // If this was the last tile in the layer, clean up the layer + if (this._tileLayer._mapTiles && this._tileLayer._mapTiles.length === 0) { + this._tileLayer = null; + delete this._tileLayer; + } + } + } + isFirst() { + // Get the previous element sibling + const prevSibling = this.previousElementSibling; + + // If there's no previous sibling, return true + if (!prevSibling) { + return true; + } + + // Compare the node names (tag names) - return true if they're different + return this.nodeName !== prevSibling.nodeName; + } + getPrevious() { + // Check if this is the first element of a sequence + if (this.isFirst()) { + return null; // No previous element available + } + + // Since we know it's not the first, we can safely return the previous element sibling + return this.previousElementSibling; + } + zoomTo() { + let extent = this.extent; + let map = this.getMapEl()._map, + xmin = extent.topLeft.pcrs.horizontal, + xmax = extent.bottomRight.pcrs.horizontal, + ymin = extent.bottomRight.pcrs.vertical, + ymax = extent.topLeft.pcrs.vertical, + bounds = Lbounds(Lpoint(xmin, ymin), Lpoint(xmax, ymax)), + center = map.options.crs.unproject(bounds.getCenter(true)), + maxZoom = extent.zoom.maxZoom, + minZoom = extent.zoom.minZoom; + map.setView(center, Util.getMaxZoom(bounds, map, minZoom, maxZoom), { + animate: false + }); + } + getMapEl() { + return Util.getClosest(this, 'mapml-viewer,map[is=web-map]'); + } + getLayerEl() { + return Util.getClosest(this, 'map-layer,layer-'); + } + attributeChangedCallback(name, oldValue, newValue) { + if (this.#hasConnected /* jshint ignore:line */) { + switch (name) { + case 'src': + case 'row': + case 'col': + case 'zoom': + if (oldValue !== newValue) { + // If we've already calculated an extent, recalculate it + if (this._extent) { + this._calculateExtent(); + } + + // If this tile is connected to a tile layer, update it + if (this._tileLayer) { + // Remove and re-add to update the tile's position + this._tileLayer.removeMapTile(this); + this._tileLayer.addMapTile(this); + } + } + break; + } + } + } + // ~copied/reimplemented from map-feature.js + getMeta(metaName) { + let name = metaName.toLowerCase(); + if (name !== 'cs' && name !== 'zoom' && name !== 'projection') return; + let sdMeta = this._parentElement.shadowRoot.querySelector( + `map-meta[name=${name}][content]` + ); + if (this._parentElement.nodeName === 'MAP-LINK') { + // sd.map-meta || map-extent meta || layer meta + return sdMeta || this._parentElement.parentElement.getMeta(metaName); + } else { + return this._parentElement.src + ? this._parentElement.shadowRoot.querySelector( + `map-meta[name=${name}][content]` + ) + : this._parentElement.querySelector(`map-meta[name=${name}][content]`); + } + } + async _createOrGetTileLayer() { + await this._parentElement.whenReady(); + if (this.isFirst()) { + const parentElement = this._parentElement; + + // Create a new MapTileLayer + this._tileLayer = mapTileLayer({ + projection: this.getMapEl().projection, + opacity: 1, + // used by map-link and map-layer, both have containers + pane: + parentElement._templatedLayer?.getContainer() || + parentElement._layer.getContainer() + }); + this._tileLayer.addMapTile(this); + + // add MapTileLayer to TemplatedFeaturesOrTilesLayer of the MapLink + if (parentElement._templatedLayer?.addLayer) { + parentElement._templatedLayer.addLayer(this._tileLayer); + } else { + // OR to the MapLayer's layer + parentElement._layer.addLayer(this._tileLayer); + } + } else { + // get the previous tile's layer + this._tileLayer = this.getPrevious()?._tileLayer; + if (this._tileLayer) { + this._tileLayer.addMapTile(this); + } + } + } + _calculateExtent() { + const mapEl = this.getMapEl(); + + if (!mapEl || !mapEl._map) { + // Can't calculate extent without a map + return; + } + + const map = mapEl._map; + const projection = map.options.projection; + const tileSize = M[projection].options.crs.tile.bounds.max.x; + + // Convert tile coordinates to pixel bounds + const pixelX = this.col * tileSize; + const pixelY = this.row * tileSize; + const pixelBounds = Lbounds( + Lpoint(pixelX, pixelY), + Lpoint(pixelX + tileSize, pixelY + tileSize) + ); + + // Convert pixel bounds to PCRS bounds + const pcrsBounds = Util.pixelToPCRSBounds( + pixelBounds, + this.zoom, + projection + ); + + // Format the extent similar to feature extents + this._extent = Util._convertAndFormatPCRS( + pcrsBounds, + map.options.crs, + projection + ); + + // Add zoom information + this._extent.zoom = { + minZoom: this.zoom, + maxZoom: this.zoom, + minNativeZoom: this.zoom, + maxNativeZoom: this.zoom + }; + } +} diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index b22cbfe51..826c8fa60 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -14,6 +14,7 @@ import { HTMLLayerElement } from './map-layer.js'; import { LayerDashElement } from './layer-.js'; import { HTMLMapCaptionElement } from './map-caption.js'; import { HTMLFeatureElement } from './map-feature.js'; +import { HTMLTileElement } from './map-tile.js'; import { HTMLExtentElement } from './map-extent.js'; import { HTMLInputElement } from './map-input.js'; import { HTMLSelectElement } from './map-select.js'; @@ -1491,6 +1492,7 @@ window.customElements.define('map-layer', HTMLLayerElement); window.customElements.define('layer-', LayerDashElement); window.customElements.define('map-caption', HTMLMapCaptionElement); window.customElements.define('map-feature', HTMLFeatureElement); +window.customElements.define('map-tile', HTMLTileElement); window.customElements.define('map-extent', HTMLExtentElement); window.customElements.define('map-input', HTMLInputElement); window.customElements.define('map-select', HTMLSelectElement); @@ -1501,6 +1503,7 @@ export { HTMLLayerElement, HTMLMapCaptionElement, HTMLFeatureElement, + HTMLTileElement, HTMLExtentElement, HTMLInputElement, HTMLSelectElement, diff --git a/src/mapml/control/LayerControl.js b/src/mapml/control/LayerControl.js index 3e1c3a6b0..d4304e68e 100644 --- a/src/mapml/control/LayerControl.js +++ b/src/mapml/control/LayerControl.js @@ -119,7 +119,7 @@ export var LayerControl = Control.Layers.extend({ // <----------- MODIFICATION from the default _update method // sort the layercontrol layers object based on the zIndex - // provided by MapMLLayer + // provided by MapLayer if (this.options.sortLayers) { this._layers.sort((a, b) => this.options.sortFunction(a.layer, b.layer, a.name, b.name) diff --git a/src/mapml/handlers/AnnounceMovement.js b/src/mapml/handlers/AnnounceMovement.js index 5d0c06bf5..0c8d7f0b8 100644 --- a/src/mapml/handlers/AnnounceMovement.js +++ b/src/mapml/handlers/AnnounceMovement.js @@ -126,7 +126,7 @@ export var AnnounceMovement = Handler.extend({ }, totalBounds: function (e) { - // don't bother with non-MapMLLayer layers... + // don't bother with non-MapLayer layers... if (!e.layer._layerEl) return; let map = this.options.mapEl; map.whenLayersReady().then(() => { diff --git a/src/mapml/index.js b/src/mapml/index.js index 92d55728a..2548596c4 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -44,6 +44,7 @@ import { HTMLMapmlViewerElement } from '../mapml-viewer.js'; import { HTMLLayerElement } from '../mapml-viewer.js'; import { HTMLMapCaptionElement } from '../mapml-viewer.js'; import { HTMLFeatureElement } from '../mapml-viewer.js'; +import { HTMLTileElement } from '../mapml-viewer.js'; import { HTMLExtentElement } from '../mapml-viewer.js'; import { HTMLInputElement } from '../mapml-viewer.js'; import { HTMLSelectElement } from '../mapml-viewer.js'; @@ -57,6 +58,7 @@ window.MapML = { HTMLLayerElement, HTMLMapCaptionElement, HTMLFeatureElement, + HTMLTileElement, HTMLExtentElement, HTMLInputElement, HTMLSelectElement, diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index d3a516439..5d72cac8f 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -14,7 +14,7 @@ export var FeatureLayer = FeatureGroup.extend({ /* * M.MapML turns any MapML feature data into a Leaflet layer. Based on L.GeoJSON. * - * Used by MapMLLayer to create _mapmlvectors property, used to render features + * Used by MapLayer to create _mapmlvectors property, used to render features */ initialize: function (mapml, options) { /* diff --git a/src/mapml/layers/ExtentLayer.js b/src/mapml/layers/MapExtentLayer.js similarity index 55% rename from src/mapml/layers/ExtentLayer.js rename to src/mapml/layers/MapExtentLayer.js index 6fd95f14d..64881063b 100644 --- a/src/mapml/layers/ExtentLayer.js +++ b/src/mapml/layers/MapExtentLayer.js @@ -1,7 +1,35 @@ import { LayerGroup, DomUtil } from 'leaflet'; import { renderStyles } from '../elementSupport/layers/renderStyles.js'; - -export var ExtentLayer = LayerGroup.extend({ +/** + * Leaflet layer implementing map-extent elements + * Extends LayerGroup to create a single layer containing "templated" layers + * from child map-link[@tref] elements + * + * Similar in intent to MapFeatureLayer and MapTileLayer, which are LayerGroup or + * GridLayer for map-feature and map-tile elements' leaflet layer object, respectively. + * + * This layer will be inserted into the LayerGroup hosted by the + * immediately after creation, so that its index within the _layers array of + * that LayerGroup will be equal to its z-index within the LayerGroup's container + * + * LayerGroup._layers[0] <- each *set* of adjacent tiles + * LayerGroup._layers[0] <- is a *single* MapTileLayer + * diff --git a/test/e2e/layers/featureLayer.test.js b/test/e2e/layers/featureLayer.test.js index b51851ef7..9932b22de 100644 --- a/test/e2e/layers/featureLayer.test.js +++ b/test/e2e/layers/featureLayer.test.js @@ -100,7 +100,7 @@ test.describe('Playwright featureLayer (Static Features) Layer Tests', () => { horizontal: 79.6961805581841, vertical: -60.79110984572508 }); - // corrected logic for MapMLLayer._calculateBounds min/maxNativeZoom + // corrected logic for MapLayer._calculateBounds min/maxNativeZoom // there are a bunch of features loaded at map zoom=2. Two have default // (no) zoom attribute, all the others have zoom=0. So, the minNativeZoom // should be 0, while the maxNativeZoom should be 2. diff --git a/test/e2e/layers/multipleExtents.html b/test/e2e/layers/multipleExtents.html index 6701d2f63..f0d774f56 100644 --- a/test/e2e/layers/multipleExtents.html +++ b/test/e2e/layers/multipleExtents.html @@ -25,19 +25,20 @@ - + - + - + +