diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml
deleted file mode 100644
index ee8a5b8ff..000000000
--- a/.github/workflows/ci-testing.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Continuous Testing
-
-on: [pull_request]
-
-jobs:
- build:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
- - name: Use Node.js
- uses: actions/setup-node@v4
- with:
- node-version: latest
- - run: sudo apt-get install xvfb
- - run: npm install
- - run: npx playwright install --with-deps
- - run: npm install -g grunt-cli
- - run: grunt default
- - run: xvfb-run --auto-servernum -- npx playwright test --grep-invert="popupTabNavigation\.test\.js|layerContextMenuKeyboard\.test\.js" --workers=1 --retries=3
-# - run: xvfb-run --auto-servernum -- npx playwright test --grep="popupTabNavigation\.test\.js|layerContextMenuKeyboard\.test\.js" --workers=1 --retries=3
-# - run: xvfb-run --auto-servernum -- npm run jest
- env:
- CI: true
diff --git a/.gitignore b/.gitignore
index 393215552..a8cd4bdd2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@
/test-results/
.idea/
*.iml
-test.html
\ No newline at end of file
+test.html
+**/.claude/settings.local.json
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..8d1c0399e
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,24 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Build and Test Commands
+- Build project: `grunt default`
+- Format and lint: `grunt format`
+- Run tests: `npx playwright test`
+- Run single test: `npx playwright test test/e2e/path/to/test.test.js`
+- Start test server: `node test/server.js`
+- Test with specific browser: `npx playwright test --project=chromium`
+
+## Code Style Guidelines
+- JavaScript: ES6+, esversion 11
+- Formatting: Prettier with singleQuote: true, trailingComma: "none"
+- Testing: Playwright for E2E tests, Jest for unit tests
+- Style the code like existing files, following established patterns
+- Use jshint for linting
+- Components use custom HTML elements pattern
+- Prefer absolute paths over relative paths
+- MapML is a custom extension of HTML for maps
+- Prefer async/await in test files
+- Error handling should follow existing patterns in similar code
+- Include meaningful test descriptions
\ No newline at end of file
diff --git a/example-tiles-and-features.mapml.xml b/example-tiles-and-features.mapml.xml
new file mode 100644
index 000000000..9906fde11
--- /dev/null
+++ b/example-tiles-and-features.mapml.xml
@@ -0,0 +1,128 @@
+
+
+ sfdem, streams, roads, restricted, archsites, bugsites
+
+
+
+
+
+ .bbox {display:none} .capitals-r1-s1{r:48.0; stroke-opacity:1.0;
+ stroke-dashoffset:0; well-known-name:circle; stroke-width:2.0; opacity:1.0;
+ fill:#FFFFFF; fill-opacity:1.0; stroke:#000000; stroke-linecap:butt}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -11538847.8 5531019.6 -11538860.6 5531045.3 -11538871.2
+ 5531077.4 -11538896 5531123.7 -11538899.6 5531138 -11538917.3 5531170.1
+ -11539009.6 5531252.1 -11539119.7 5531316.3 -11539254.6 5531433.9
+ -11539300.8 5531483.8 -11539332.7 5531548 -11539329.1 5531637.1 -11539311.4
+ 5531683.4 -11539279.4 5531701.2 -11539130.2 5531722.6 -11539073.4 5531765.3
+ -11539041.4 5531843.7 -11538995.2 5531847.2 -11538959.7 5531829.4
+ -11538874.4 5531832.9 -11538847.8 5531846.3
+
+
+
+
+
+
+ -11543278.6 5529563.7 -11543275 5529602.9 -11543257.3 5529642
+ -11543236 5529656.3 -11543157.8 5529770.3 -11543125.9 5529852.3 -11543111.7
+ 5529941.4 -11543108.2 5530012.6 -11543093.9 5530119.5 -11543062 5530240.7
+ -11543026.5 5530294.1 -11543005.2 5530301.3 -11542969.7 5530297.7
+ -11542937.7 5530272.8 -11542905.7 5530226.4 -11542891.5 5530176.6
+ -11542838.3 5530098.2 -11542763.7 5530101.7 -11542593.2 5530190.8
+ -11542458.3 5530201.5 -11542397.9 5530233.6 -11542316.2 5530247.8
+ -11542270.1 5530369 -11542263 5530415.3 -11542220.3 5530483 -11542170.6
+ 5530500.8 -11542081.8 5530486.6 -11542039.2 5530461.6 -11541978.8 5530401
+ -11541964.7 5530369 -11541946.9 5530351.2 -11541922 5530301.3 -11541875.9
+ 5530247.8 -11541858.1 5530240.7 -11541801.3 5530247.8 -11541765.8 5530287
+ -11541662.8 5530426 -11541606 5530486.5 -11541531.4 5530497.2 -11541439
+ 5530458 -11541353.8 5530404.5 -11541197.6 5530265.5 -11541140.8 5530197.8
+ -11541101.7 5530180 -11541041.4 5530183.6 -11540956.1 5530229.9 -11540856.7
+ 5530222.7 -11540743.1 5530190.6 -11540672 5530204.9 -11540604.5 5530304.6
+ -11540579.7 5530318.9 -11540448.3 5530329.6 -11540224.5 5530415 -11540157
+ 5530429.2 -11540061.1 5530429.2 -11539951.1 5530350.8 -11539890.7 5530322.3
+ -11539787.8 5530233.2 -11539752.3 5530204.6 -11539667 5530204.6 -11539599.5
+ 5530222.4 -11539535.6 5530265.1 -11539496.5 5530318.6 -11539471.7 5530375.6
+ -11539425.4 5530518.1 -11539393.4 5530635.7 -11539372.1 5530678.5
+ -11539350.8 5530710.5 -11539283.3 5530739 -11539166.1 5530739 -11539080.9
+ 5530706.9 -11539041.8 5530660.5 -11539020.6 5530599.9 -11538985.1 5530557.1
+ -11538931.8 5530550 -11538896.3 5530560.7 -11538847.8 5530609.3
+
+
+
+
+
+
+ -11543097.4 5528690.3 -11543097.4 5528740.6 -11543118.7
+ 5528765.5 -11543186.2 5528797.6 -11543232.4 5528897.4 -11543235.9 5528918.7
+ -11543250.1 5528936.6 -11543260.8 5528965.1 -11543267.9 5529072 -11543243
+ 5529153.9 -11543161.4 5529275 -11543129.4 5529307.1 -11543129.4 5529381.9
+ -11543211.1 5529471 -11543260.8 5529513.8 -11543278.6 5529549.4 -11543278.6
+ 5529563.7
+
+
+
+
+
+
+ -1.154197545486741E7 5528690.2558668 -1.15419842E7 5528702.6
+ -1.15420779E7 5528948.8 -1.15421423E7 5529066.4 -1.15423138E7 5529238.3
+ -1.15423223E7 5529233.2 -1.15424015E7 5529256.6 -1.15424412E7 5529352.7
+ -1.15424537E7 5529429.8
+
+
+
+
+
+
+ -1.15432167E7 5529271.1 -1.1543219E7 5529347.4 -1.15432537E7
+ 5529412.6 -1.15433427E7 5529536.3 -1.15433579E7 5529585.2 -1.1543356E7
+ 5529639.1 -1.1543219E7 5529868.4 -1.15432153E7 5529937.8 -1.15432021E7
+ 5530059.4 -1.15431755E7 5530144.2 -1.15431196E7 5530299.9 -1.15431364E7
+ 5530354 -1.15431942E7 5530403 -1.15433533E7 5530481.5 -1.15434197E7
+ 5530475.7 -1.15435152E7 5530461.4 -1.15435587E7 5530450.4 -1.15435759E7
+ 5530410.2 -1.1543599E7 5530374.3 -1.15436169E7 5530375.2 -1.15436371E7
+ 5530437.8 -1.15436496E7 5530524.4 -1.15436859E7 5530638.5 -1.15436064E7
+ 5530707.6 -1.15436037E7 5530757.3 -1.1543651E7 5530900.6 -1.154373E7
+ 5530962.5 -1.15437836E7 5530985 -1.15438474E7 5531032.3
+
+
+
+
+
+
+ -1.15438474E7 5531032.3 -1.15438747E7 5531008.4
+ -1.154394996991142E7 5530977.68753614
+
+
+
+
+
\ No newline at end of file
diff --git a/index.html b/index.html
index 18f6bf4a3..0cfc4abee 100644
--- a/index.html
+++ b/index.html
@@ -1,173 +1,43 @@
-
-
-
+
index-map.html
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- All cuisines
- African
- Asian
- Cajun
- Indian
- Italian
- Mexican
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/newXMLDocument.xml b/newXMLDocument.xml
new file mode 100644
index 000000000..e4a0571e4
--- /dev/null
+++ b/newXMLDocument.xml
@@ -0,0 +1,24 @@
+
+
+ Spearfish
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/map-feature.js b/src/map-feature.js
index 5ae172fd4..785b64c6d 100644
--- a/src/map-feature.js
+++ b/src/map-feature.js
@@ -1,5 +1,7 @@
import { bounds, point } 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) => {
@@ -266,7 +271,79 @@ export class HTMLFeatureElement extends HTMLElement {
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.addFeature(this._featureLayer);
+
+ // add featureLayer to TemplatedFeaturesOrTilesLayerGroup 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..8edddaf3f 100644
--- a/src/map-link.js
+++ b/src/map-link.js
@@ -10,8 +10,8 @@ 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 { templatedPMTilesLayer } from './mapml/layers/TemplatedPMTilesLayer.js';
+import { templatedFeaturesOrTilesLayerGroup } from './mapml/layers/TemplatedFeaturesOrTilesLayerGroup.js';
/* global M */
export class HTMLLinkElement extends HTMLElement {
@@ -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
+ // TemplatedFeaturesOrTilesLayerGroup
//
// 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,13 +552,19 @@ export class HTMLLinkElement extends HTMLElement {
if (!this.shadowRoot) {
this.attachShadow({ mode: 'open' });
}
- this._templatedLayer = templatedFeaturesLayer(this._templateVars, {
- zoomBounds: this.getZoomBounds(),
- extentBounds: this.getBounds(),
- zIndex: this.zIndex,
- pane: this.parentExtent._extentLayer.getContainer(),
- linkEl: this
- }).addTo(this.parentExtent._extentLayer);
+ // Use the FeaturesTilesLayerGroup to handle both map-feature and map-tile elements
+ this._templatedLayer = templatedFeaturesOrTilesLayerGroup(
+ this._templateVars,
+ {
+ zoomBounds: this.getZoomBounds(),
+ extentBounds: this.getBounds(),
+ zIndex: this.zIndex,
+ pane: this.parentExtent._extentLayer.getContainer(),
+ 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) {
this.attachShadow({ mode: 'open' });
diff --git a/src/map-tile.js b/src/map-tile.js
new file mode 100644
index 000000000..5286100bd
--- /dev/null
+++ b/src/map-tile.js
@@ -0,0 +1,231 @@
+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() {
+ return +(this.hasAttribute('zoom') ? this.getAttribute('zoom') : 0);
+ }
+ 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();
+ }
+ 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 */
+
+ // 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;
+
+ this._createOrGetTileLayer();
+
+ // Calculate the extent
+ //this._calculateExtent();
+ }
+
+ 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) {
+ // Clean up happens in the map-link that created the layer
+ // The map-link handles this through FeaturesTilesLayerGroup
+ }
+ }
+ }
+ 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;
+ }
+ }
+ }
+ _createOrGetTileLayer() {
+ if (this.isFirst()) {
+ const parentElement = this._parentElement;
+
+ // Create a new MapTileLayer
+ this._tileLayer = mapTileLayer({
+ projection: this.getMapEl()._map.options.projection,
+ opacity: 1,
+ pane: parentElement._templatedLayer.getContainer()
+ });
+ this._tileLayer.addMapTile(this);
+
+ // add MapTileLayer to TemplatedFeaturesOrTilesLayerGroup of the parentElement
+ if (
+ parentElement._templatedLayer &&
+ parentElement._templatedLayer.addLayer
+ ) {
+ parentElement._templatedLayer.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/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/MapFeatureLayerGroup.js b/src/mapml/layers/MapFeatureLayerGroup.js
new file mode 100644
index 000000000..9628daddf
--- /dev/null
+++ b/src/mapml/layers/MapFeatureLayerGroup.js
@@ -0,0 +1,556 @@
+import {
+ FeatureGroup,
+ DomUtil,
+ bounds,
+ SVG,
+ Util as LeafletUtil,
+ Browser
+} from 'leaflet';
+import { Util } from '../utils/Util.js';
+import { path } from '../features/path.js';
+import { geometry } from '../features/geometry.js';
+
+export var MapFeatureLayerGroup = FeatureGroup.extend({
+ /*
+ * This is the feature equivalent of MapTileLayer. The intended use is to
+ * represent an adjacent sequence of elements found in a templated
+ * response, but MAYBE we'll be able to use it to represent such a sequence
+ * in a static document response, too, tbd.
+ *
+ * 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]
+ * LayerGroup._layers[0]
+ * LayerGroup._layers[1] <- each set of adjacent features
+ * LayerGroup._layers[1] <- is a single MapFeatureLayerGroup
+ * LayerGroup._layers[2]
+ * LayerGroup._layers[2]
+ * LayerGroup._layers[3]
+ * LayerGroup._layers[3]
+ * and so on
+ *
+ */
+ initialize: function (mapml, options) {
+ /*
+ mapml:
+ 1. for query: an array of map-feature elements that it fetches
+ 2. for static templated feature: null
+ 3. for non-templated feature: map-layer (with no src) or mapml file (with src)
+ */
+ FeatureGroup.prototype.initialize.call(this, null, options);
+ // this.options.static is false ONLY for tiled vector features
+ // this._staticFeature is ONLY true when not used by TemplatedFeaturesLayer
+ // this.options.query true when created by QueryHandler.js
+
+ if (!this.options.tiles) {
+ // not a tiled vector layer
+ this._container = null;
+ if (this.options.query) {
+ this._container = DomUtil.create(
+ 'div',
+ 'leaflet-layer',
+ this.options.pane
+ );
+ DomUtil.addClass(
+ this._container,
+ 'leaflet-pane mapml-vector-container'
+ );
+ } else if (this.options._leafletLayer) {
+ this._container = DomUtil.create(
+ 'div',
+ 'leaflet-layer',
+ this.options.pane
+ );
+ DomUtil.addClass(
+ this._container,
+ 'leaflet-pane mapml-vector-container'
+ );
+ } else {
+ // if the current featureLayer is a sublayer of templatedFeatureLayer,
+ // append