From 4cff6eaf34bf773e3cde2c33b0efda99ac9a48f5 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 24 May 2023 17:16:40 -0400 Subject: [PATCH 001/136] initial map-input setup Conflicts: index.html --- Gruntfile.js | 2 + src/map-input.js | 66 +++++++++++++++++-- src/mapml-viewer.js | 2 + .../elementSupport/inputs/heightInput.js | 12 ++++ .../elementSupport/inputs/hiddenInput.js | 13 ++++ .../elementSupport/inputs/locationInput.js | 22 +++++++ src/mapml/elementSupport/inputs/widthInput.js | 12 ++++ src/mapml/elementSupport/inputs/zoomInput.js | 19 ++++++ 8 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 src/mapml/elementSupport/inputs/heightInput.js create mode 100644 src/mapml/elementSupport/inputs/hiddenInput.js create mode 100644 src/mapml/elementSupport/inputs/locationInput.js create mode 100644 src/mapml/elementSupport/inputs/widthInput.js create mode 100644 src/mapml/elementSupport/inputs/zoomInput.js diff --git a/Gruntfile.js b/Gruntfile.js index 41145842b..ef794e121 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -29,6 +29,8 @@ module.exports = function(grunt) { 'dist/map-caption.js': ['src/map-caption.js'], 'dist/map-feature.js': ['src/map-feature.js'], 'dist/map-extent.js': ['src/map-extent.js'], + 'dist/map-input.js': ['src/map-input.js'], + 'dist/zoomInput.js': ['src/mapml/elementSupport/inputs/zoomInput.js'], 'dist/map-area.js': ['src/map-area.js'], 'dist/layer.js': ['src/layer.js'], 'dist/leaflet.js': ['dist/leaflet-src.js', diff --git a/src/map-input.js b/src/map-input.js index bd92e01b1..bd0a0db32 100644 --- a/src/map-input.js +++ b/src/map-input.js @@ -1,4 +1,4 @@ -import { MapLink } from './map-link.js'; +import { ZoomInput } from './zoomInput.js'; export class MapInput extends HTMLElement { static get observedAttributes() { @@ -89,7 +89,7 @@ export class MapInput extends HTMLElement { } } get step() { - return this.getAttribute('step'); + return this.getAttribute('step') || 1; } set step(val) { if (val) { @@ -97,6 +97,7 @@ export class MapInput extends HTMLElement { } } attributeChangedCallback(name, oldValue, newValue) { + console.log(name); switch (name) { case 'name': if (oldValue !== newValue) { @@ -154,7 +155,64 @@ export class MapInput extends HTMLElement { // Always call super first in constructor super(); } - connectedCallback() {} + connectedCallback() { + switch (this.type) { + case 'zoom': + // input will store the input Class specific to the input type + this.input = new ZoomInput( + this.name, + this.min, + this.max, + this.value, + this.step + ); + break; + case 'location': + //this.input = ... + break; + case 'width': + //this.input = ... + break; + case 'height': + //this.input = ... + break; + case 'hidden': + //this.input = ... + break; + } + } disconnectedCallback() {} + + //https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/checkValidity + checkValidity() { + if (this.input.validateInput()) { + return true; + } else { + const evt = new Event('invalid', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(evt); + return false; + } + } + + //https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/reportValidity + reportValidity() { + if (this.input.validateInput()) { + return true; + } else { + const evt = new Event('invalid', { + bubbles: true, + cancelable: true, + composed: true + }); + this.dispatchEvent(evt); + //if the event isn't canceled reports the problem to the user. + // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#dom-cva-reportvalidity-dev + console.log("Input type='" + this.type + "' is not valid!"); + return false; + } + } } -window.customElements.define('map-input', MapInput); diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 4fe1e7ebe..aaf4a9874 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -5,6 +5,7 @@ import { MapLayer } from './layer.js'; import { MapCaption } from './map-caption.js'; import { MapFeature } from './map-feature.js'; import { MapExtent } from './map-extent.js'; +import { MapInput } from './map-input.js'; export class MapViewer extends HTMLElement { static get observedAttributes() { @@ -1389,3 +1390,4 @@ window.customElements.define('layer-', MapLayer); window.customElements.define('map-caption', MapCaption); window.customElements.define('map-feature', MapFeature); window.customElements.define('map-extent', MapExtent); +window.customElements.define('map-input', MapInput); diff --git a/src/mapml/elementSupport/inputs/heightInput.js b/src/mapml/elementSupport/inputs/heightInput.js new file mode 100644 index 000000000..26d2034a4 --- /dev/null +++ b/src/mapml/elementSupport/inputs/heightInput.js @@ -0,0 +1,12 @@ +export class HeightInput { + constructor(name) { + this.name = name; + } + + validateInput() { + // name is required + // + // checks here... + return; + } +} diff --git a/src/mapml/elementSupport/inputs/hiddenInput.js b/src/mapml/elementSupport/inputs/hiddenInput.js new file mode 100644 index 000000000..8fffbf86e --- /dev/null +++ b/src/mapml/elementSupport/inputs/hiddenInput.js @@ -0,0 +1,13 @@ +export class HiddenInput { + constructor(name, value) { + this.name = name; + this.value = value; + } + + validateInput() { + // name is required + // value is required + // checks here... + return; + } +} diff --git a/src/mapml/elementSupport/inputs/locationInput.js b/src/mapml/elementSupport/inputs/locationInput.js new file mode 100644 index 000000000..34f920eb3 --- /dev/null +++ b/src/mapml/elementSupport/inputs/locationInput.js @@ -0,0 +1,22 @@ +export class LocationInput { + constructor(name, position, axis, cs, min, max, rel) { + this.name = name; + this.position = position; + this.axis = axis; + this.cs = cs; // units + this.min = min; + this.max = max; + this.rel = rel; + } + + validateInput() { + // name is required + // axis is required + // cs is required + // position is not required, will default to top-left + // min max fallbacks, map-meta -> projection + // rel not required, default is image/extent + // checks here... + return; + } +} diff --git a/src/mapml/elementSupport/inputs/widthInput.js b/src/mapml/elementSupport/inputs/widthInput.js new file mode 100644 index 000000000..13cc1087c --- /dev/null +++ b/src/mapml/elementSupport/inputs/widthInput.js @@ -0,0 +1,12 @@ +export class WidthInput { + constructor(name) { + this.name = name; + } + + validateInput() { + // name is required + // + // checks here... + return; + } +} diff --git a/src/mapml/elementSupport/inputs/zoomInput.js b/src/mapml/elementSupport/inputs/zoomInput.js new file mode 100644 index 000000000..4944f0fd3 --- /dev/null +++ b/src/mapml/elementSupport/inputs/zoomInput.js @@ -0,0 +1,19 @@ +export class ZoomInput { + constructor(name, min, max, value, step) { + this.name = name; + this.min = min; + this.max = max; + this.value = value; + this.step = step; + } + + validateInput() { + // name is required + // min and max can not be present + // fallback would be layer's meta, -> projection min, max + // don't need value, map-meta max value, -> fallback is max zoom of projection + // don't need step, defaults to 1 + // checks here... + return; + } +} From 90f1d79dae1f648a3ce1334359f1bfdb93a867f6 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Fri, 26 May 2023 14:52:21 -0400 Subject: [PATCH 002/136] implement Classes for different types of map-input types Conflicts: index.html --- Gruntfile.js | 4 + index.html | 26 ++++- src/map-input.js | 95 +++++++++++++--- .../elementSupport/inputs/heightInput.js | 26 +++-- .../elementSupport/inputs/hiddenInput.js | 28 +++-- .../elementSupport/inputs/locationInput.js | 104 ++++++++++++++---- src/mapml/elementSupport/inputs/widthInput.js | 26 +++-- src/mapml/elementSupport/inputs/zoomInput.js | 23 ++-- 8 files changed, 258 insertions(+), 74 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index ef794e121..5073aa0b6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -31,6 +31,10 @@ module.exports = function(grunt) { 'dist/map-extent.js': ['src/map-extent.js'], 'dist/map-input.js': ['src/map-input.js'], 'dist/zoomInput.js': ['src/mapml/elementSupport/inputs/zoomInput.js'], + 'dist/hiddenInput.js': ['src/mapml/elementSupport/inputs/hiddenInput.js'], + 'dist/widthInput.js': ['src/mapml/elementSupport/inputs/widthInput.js'], + 'dist/heightInput.js': ['src/mapml/elementSupport/inputs/heightInput.js'], + 'dist/locationInput.js': ['src/mapml/elementSupport/inputs/locationInput.js'], 'dist/map-area.js': ['src/map-area.js'], 'dist/layer.js': ['src/layer.js'], 'dist/leaflet.js': ['dist/leaflet-src.js', diff --git a/index.html b/index.html index 320c86c74..e80971f5c 100644 --- a/index.html +++ b/index.html @@ -23,9 +23,9 @@ /* Responsive map. */ max-width: 100%; - /* Full viewport. */ + /* Full viewport. width: 100%; - height: 100%; + height: 100%; */ /* Remove default (native-like) border. */ border: none; @@ -124,5 +124,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/map-input.js b/src/map-input.js index bd0a0db32..96f5a76ef 100644 --- a/src/map-input.js +++ b/src/map-input.js @@ -1,4 +1,8 @@ import { ZoomInput } from './zoomInput.js'; +import { HiddenInput } from './hiddenInput.js'; +import { WidthInput } from './widthInput.js'; +import { HeightInput } from './heightInput.js'; +import { LocationInput } from './locationInput.js'; export class MapInput extends HTMLElement { static get observedAttributes() { @@ -33,7 +37,7 @@ export class MapInput extends HTMLElement { } } get value() { - return this.getAttribute('value'); + return this.input.getValue(); } set value(val) { if (val) { @@ -73,7 +77,26 @@ export class MapInput extends HTMLElement { } } get min() { - return this.getAttribute('min'); + if ( + this.type === 'height' || + this.type === 'width' || + this.type === 'hidden' + ) { + return null; + } + if (this.getAttribute('min')) { + return this.getAttribute('min'); + } else if (this._layer._layerEl.querySelector('map-meta[name=zoom]')) { + // fallback map-meta on layer + return M._metaContentToObject( + this._layer._layerEl + .querySelector('map-meta[name=zoom]') + .getAttribute('content') + ).min; + } else { + // fallback map min + return this._layer._map.getMinZoom().toString(); + } } set min(val) { if (val) { @@ -81,7 +104,26 @@ export class MapInput extends HTMLElement { } } get max() { - return this.getAttribute('max'); + if ( + this.type === 'height' || + this.type === 'width' || + this.type === 'hidden' + ) { + return null; + } + if (this.getAttribute('max')) { + return this.getAttribute('max'); + } else if (this._layer._layerEl.querySelector('map-meta[name=zoom]')) { + // fallback map-meta on layer + return M._metaContentToObject( + this._layer._layerEl + .querySelector('map-meta[name=zoom]') + .getAttribute('content') + ).max; + } else { + // fallback map max + return this._layer._map.getMaxZoom().toString(); + } } set max(val) { if (val) { @@ -89,7 +131,11 @@ export class MapInput extends HTMLElement { } } get step() { - return this.getAttribute('step') || 1; + if (this.type !== 'zoom') { + return null; + } else { + return this.getAttribute('step') || '1'; + } } set step(val) { if (val) { @@ -97,11 +143,13 @@ export class MapInput extends HTMLElement { } } attributeChangedCallback(name, oldValue, newValue) { - console.log(name); switch (name) { case 'name': if (oldValue !== newValue) { - // handle side effects + // update associated class value on attribute change + if (oldValue !== null) { + this.input.name = newValue; + } } break; case 'type': @@ -111,7 +159,11 @@ export class MapInput extends HTMLElement { break; case 'value': if (oldValue !== newValue) { - // handle side effects + if (oldValue !== null) { + this.input.value = newValue; + } else { + this._initValue = newValue; + } } break; case 'axis': @@ -156,6 +208,9 @@ export class MapInput extends HTMLElement { super(); } connectedCallback() { + if (this.parentElement.nodeName === 'MAP-EXTENT') { + this._layer = this.parentElement._layer; + } switch (this.type) { case 'zoom': // input will store the input Class specific to the input type @@ -163,21 +218,35 @@ export class MapInput extends HTMLElement { this.name, this.min, this.max, - this.value, - this.step + this._initValue, + this.step, + this._layer ); break; case 'location': - //this.input = ... + // input will store the input Class specific to the input type + this.input = new LocationInput( + this.name, + this.position, + this.axis, + this.units, + this.min, + this.max, + this.rel, + this._layer + ); break; case 'width': - //this.input = ... + // input will store the input Class specific to the input type + this.input = new WidthInput(this.name, this._layer); break; case 'height': - //this.input = ... + // input will store the input Class specific to the input type + this.input = new HeightInput(this.name, this._layer); break; case 'hidden': - //this.input = ... + // input will store the input Class specific to the input type + this.input = new HiddenInput(this.name, this._initValue); break; } } diff --git a/src/mapml/elementSupport/inputs/heightInput.js b/src/mapml/elementSupport/inputs/heightInput.js index 26d2034a4..3629b0ddd 100644 --- a/src/mapml/elementSupport/inputs/heightInput.js +++ b/src/mapml/elementSupport/inputs/heightInput.js @@ -1,12 +1,18 @@ export class HeightInput { - constructor(name) { - this.name = name; - } - - validateInput() { - // name is required - // - // checks here... - return; - } + constructor(name, layer) { + this.name = name; + this.layer = layer; + } + + validateInput() { + // name is required + if (!this.name) { + return false; + } + return true; + } + + getValue() { + return this.layer._map.getSize().y; + } } diff --git a/src/mapml/elementSupport/inputs/hiddenInput.js b/src/mapml/elementSupport/inputs/hiddenInput.js index 8fffbf86e..0919ef90f 100644 --- a/src/mapml/elementSupport/inputs/hiddenInput.js +++ b/src/mapml/elementSupport/inputs/hiddenInput.js @@ -1,13 +1,19 @@ export class HiddenInput { - constructor(name, value) { - this.name = name; - this.value = value; - } - - validateInput() { - // name is required - // value is required - // checks here... - return; - } + constructor(name, value) { + this.name = name; + this.value = value; + } + + validateInput() { + // name is required + // value is required + if (!this.name || !this.value) { + return false; + } + return true; + } + + getValue() { + return this.value; + } } diff --git a/src/mapml/elementSupport/inputs/locationInput.js b/src/mapml/elementSupport/inputs/locationInput.js index 34f920eb3..906690721 100644 --- a/src/mapml/elementSupport/inputs/locationInput.js +++ b/src/mapml/elementSupport/inputs/locationInput.js @@ -1,22 +1,86 @@ export class LocationInput { - constructor(name, position, axis, cs, min, max, rel) { - this.name = name; - this.position = position; - this.axis = axis; - this.cs = cs; // units - this.min = min; - this.max = max; - this.rel = rel; - } - - validateInput() { - // name is required - // axis is required - // cs is required - // position is not required, will default to top-left - // min max fallbacks, map-meta -> projection - // rel not required, default is image/extent - // checks here... - return; - } + constructor(name, position, axis, units, min, max, rel, layer) { + this.name = name; + this.position = position; + this.axis = axis; + this.units = units; // cs + this.min = min; + this.max = max; + this.rel = rel; + this.layer = layer; + } + + validateInput() { + // name is required + // axis is required + // cs/units is required + if (!this.name || !this.axis || !this.units) { + return false; + } + // check if axis match the cs + let axisCS = M.axisToCS(this.axis); + if ( + (typeof axisCS === 'string' && axisCS !== this.units) || + (typeof axisCS === 'object' && + (axisCS[0] !== this.units || axisCS[1] !== this.units)) + ) { + return false; + } + // position is not required, will default to top-left + // min max fallbacks, map-meta -> projection + // rel not required, default is image/extent + return true; + } + + _TCRSToPCRS(coords, zoom) { + // TCRS pixel point to Projected CRS point (in meters, presumably) + var map = this.layer._map, + crs = map.options.crs, + loc = crs.transformation.untransform(coords, crs.scale(zoom)); + return loc; + } + + getValue(zoom = undefined, bounds = undefined) { + // units = cs + // + if (zoom === undefined) zoom = this.layer._map.getZoom(); + if (bounds === undefined) bounds = this.layer._map.getPixelBounds(); + + if (this.units === 'pcrs' || this.units === 'gcrs') { + switch (this.axis) { + case 'longitude': + case 'easting': + if (this.position) { + if (this.position.match(/.*?-left/i)) { + return this._TCRSToPCRS(bounds.min, zoom).x; + } else if (this.position.match(/.*?-right/i)) { + return this._TCRSToPCRS(bounds.max, zoom).x; + } + } else { + // position is not required, will default to top-left + } + break; + case 'latitude': + case 'northing': + if (this.position) { + if (this.position.match(/top-.*?/i)) { + return this._TCRSToPCRS(bounds.min, zoom).y; + } else if (this.position.match(/bottom-.*?/i)) { + return this._TCRSToPCRS(bounds.max, zoom).y; + } + } else { + // position is not required, will default to top-left + } + break; + } + } else if ( + this.units === 'tilematrix' || + this.units === 'tcrs' || + this.units === 'tile' || + this.units === 'map' + ) { + // TODO: What happens here... + } + return; + } } diff --git a/src/mapml/elementSupport/inputs/widthInput.js b/src/mapml/elementSupport/inputs/widthInput.js index 13cc1087c..756f72ffe 100644 --- a/src/mapml/elementSupport/inputs/widthInput.js +++ b/src/mapml/elementSupport/inputs/widthInput.js @@ -1,12 +1,18 @@ export class WidthInput { - constructor(name) { - this.name = name; - } - - validateInput() { - // name is required - // - // checks here... - return; - } + constructor(name, layer) { + this.name = name; + this.layer = layer; + } + + validateInput() { + // name is required + if (!this.name) { + return false; + } + return true; + } + + getValue() { + return this.layer._map.getSize().x; + } } diff --git a/src/mapml/elementSupport/inputs/zoomInput.js b/src/mapml/elementSupport/inputs/zoomInput.js index 4944f0fd3..5ac511675 100644 --- a/src/mapml/elementSupport/inputs/zoomInput.js +++ b/src/mapml/elementSupport/inputs/zoomInput.js @@ -1,19 +1,26 @@ export class ZoomInput { - constructor(name, min, max, value, step) { + constructor(name, min, max, value, step, layer) { this.name = name; this.min = min; this.max = max; this.value = value; this.step = step; + this.layer = layer; } validateInput() { - // name is required - // min and max can not be present - // fallback would be layer's meta, -> projection min, max - // don't need value, map-meta max value, -> fallback is max zoom of projection - // don't need step, defaults to 1 - // checks here... - return; + // name is required + if (!this.name) { + return false; + } + // min and max can not be present + // fallback would be layer's meta, -> projection min, max + // don't need value, map-meta max value, -> fallback is max zoom of projection + // don't need step, defaults to 1 + return true; + } + + getValue() { + return this.layer._map.options.mapEl.zoom; } } From fddb89df726b5e9e7bd8990dab71849817188761 Mon Sep 17 00:00:00 2001 From: AliyanH Date: Wed, 31 May 2023 12:03:23 -0400 Subject: [PATCH 003/136] Update map-input implementation + Add support for tilematrix type=locataion Class --- index.html | 34 +++++++++++ src/map-input.js | 32 ++++++---- .../elementSupport/inputs/locationInput.js | 60 ++++++++++++++----- 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/index.html b/index.html index e80971f5c..51ad36233 100644 --- a/index.html +++ b/index.html @@ -146,5 +146,39 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/map-input.js b/src/map-input.js index 96f5a76ef..d97ca3c2d 100644 --- a/src/map-input.js +++ b/src/map-input.js @@ -95,7 +95,7 @@ export class MapInput extends HTMLElement { ).min; } else { // fallback map min - return this._layer._map.getMinZoom().toString(); + return this._layer._layerEl.extent.zoom.minZoom.toString(); } } set min(val) { @@ -122,7 +122,7 @@ export class MapInput extends HTMLElement { ).max; } else { // fallback map max - return this._layer._map.getMaxZoom().toString(); + return this._layer._layerEl.extent.zoom.maxZoom.toString(); } } set max(val) { @@ -155,6 +155,7 @@ export class MapInput extends HTMLElement { case 'type': if (oldValue !== newValue) { // handle side effects + // not allowed to change 'type' } break; case 'value': @@ -162,43 +163,50 @@ export class MapInput extends HTMLElement { if (oldValue !== null) { this.input.value = newValue; } else { - this._initValue = newValue; + this.initialValue = newValue; } } break; case 'axis': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.axis = newValue; } break; case 'units': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.units = newValue; } break; case 'position': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.position = newValue; } break; case 'rel': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.rel = newValue; } break; case 'min': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.min = newValue; } break; case 'max': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.max = newValue; } break; case 'step': - if (oldValue !== newValue) { + if (oldValue !== newValue && this.input) { // handle side effects + this.input.step = newValue; } break; } @@ -218,7 +226,7 @@ export class MapInput extends HTMLElement { this.name, this.min, this.max, - this._initValue, + this.initialValue, this.step, this._layer ); @@ -246,7 +254,7 @@ export class MapInput extends HTMLElement { break; case 'hidden': // input will store the input Class specific to the input type - this.input = new HiddenInput(this.name, this._initValue); + this.input = new HiddenInput(this.name, this.initialValue); break; } } diff --git a/src/mapml/elementSupport/inputs/locationInput.js b/src/mapml/elementSupport/inputs/locationInput.js index 906690721..fb4e7b880 100644 --- a/src/mapml/elementSupport/inputs/locationInput.js +++ b/src/mapml/elementSupport/inputs/locationInput.js @@ -3,7 +3,12 @@ export class LocationInput { this.name = name; this.position = position; this.axis = axis; - this.units = units; // cs + // if unit/cs not present, find it + if (!units && axis && !['i', 'j'].includes(axis)) { + this.units = M.axisToCS(axis).toLowerCase(); + } else { + this.units = units; // cs + } this.min = min; this.max = max; this.rel = rel; @@ -13,19 +18,26 @@ export class LocationInput { validateInput() { // name is required // axis is required - // cs/units is required - if (!this.name || !this.axis || !this.units) { + if (!this.name || !this.axis) { return false; } - // check if axis match the cs - let axisCS = M.axisToCS(this.axis); + // cs/units is only required when the axis is i/j. To differentiate between the units/cs if ( - (typeof axisCS === 'string' && axisCS !== this.units) || - (typeof axisCS === 'object' && - (axisCS[0] !== this.units || axisCS[1] !== this.units)) + (this.axis === 'i' || this.axis === 'j') && + !['map', 'tile'].includes(this.units) ) { return false; } + // check if axis match the units/cs + if (this.units) { + let axisCS = M.axisToCS(this.axis); + if ( + typeof axisCS === 'string' && + axisCS.toUpperCase() !== this.units.toUpperCase() + ) { + return false; + } + } // position is not required, will default to top-left // min max fallbacks, map-meta -> projection // rel not required, default is image/extent @@ -58,6 +70,7 @@ export class LocationInput { } } else { // position is not required, will default to top-left + return this._TCRSToPCRS(bounds.min, zoom).x; } break; case 'latitude': @@ -70,16 +83,33 @@ export class LocationInput { } } else { // position is not required, will default to top-left + return this._TCRSToPCRS(bounds.min, zoom).y; } break; } - } else if ( - this.units === 'tilematrix' || - this.units === 'tcrs' || - this.units === 'tile' || - this.units === 'map' - ) { - // TODO: What happens here... + } else if (this.units === 'tilematrix') { + // Value is retrieved from the createTile method of TemplatedTileLayer, on move end. + // Different values for each tile when filling in the map tiles on the map. + // Currently storing all x,y,z within one object, + // TODO: change return value as needed based on usage by map-input + // https://github.com/Leaflet/Leaflet/blob/6994baf25f267db1c8b720c28a61e0700d0aa0e8/src/layer/tile/GridLayer.js#L652 + const center = this.layer._map.getCenter(); + const templatedTileLayer = this.layer._templatedLayer._templates[0].layer; + const pixelBounds = templatedTileLayer._getTiledPixelBounds(center); + const tileRange = templatedTileLayer._pxBoundsToTileRange(pixelBounds); + let obj = []; + for (let j = tileRange.min.y; j <= tileRange.max.y; j++) { + for (let i = tileRange.min.x; i <= tileRange.max.x; i++) { + const coords = new L.Point(i, j); + coords.z = templatedTileLayer._tileZoom; + obj.push(coords); + } + } + return obj; + } else if (this.units === 'tile' || this.units === 'map') { + // used for query handler on map enter or click. + // mapi, tilei, mapj, tilej used for query handling, value is derived from the mouse click event + // or center of the map when used with keyboard. } return; } From cef052b5a5e870c57f9f5673c815da98a5cf2e8e Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Thu, 8 Jun 2023 13:18:34 -0400 Subject: [PATCH 004/136] initial map-link setup Conflicts: index.html --- Gruntfile.js | 2 ++ index.html | 6 +++--- src/map-link.js | 27 ++++++++++++++++++++++++++- src/mapml-viewer.js | 2 ++ src/web-map.js | 4 ++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 5073aa0b6..e4edb69fe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -30,6 +30,8 @@ module.exports = function(grunt) { 'dist/map-feature.js': ['src/map-feature.js'], 'dist/map-extent.js': ['src/map-extent.js'], 'dist/map-input.js': ['src/map-input.js'], + 'dist/map-link.js': ['src/map-link.js'], + // temporary, will be bundled into mapml.js possibly 'dist/zoomInput.js': ['src/mapml/elementSupport/inputs/zoomInput.js'], 'dist/hiddenInput.js': ['src/mapml/elementSupport/inputs/hiddenInput.js'], 'dist/widthInput.js': ['src/mapml/elementSupport/inputs/widthInput.js'], diff --git a/index.html b/index.html index 51ad36233..dc8419974 100644 --- a/index.html +++ b/index.html @@ -123,7 +123,7 @@ - + --> @@ -148,7 +148,7 @@ - @@ -179,6 +179,6 @@ tref="https://cwfis.cfs.nrcan.gc.ca/geoserver/public/wms?i={i}&j={j}&service=WMS&version=1.3.0&request=GetFeatureInfo&layers=public:fdr_current&QUERY_LAYERS=current:fdr_current&styles=&bbox={xmin},{ymin},{xmax},{ymax}&width={w}&height={h}&srs=EPSG:3978&INFO_FORMAT=text/html&m4h=t" > - + --> diff --git a/src/map-link.js b/src/map-link.js index 969bf7a41..2ebf0ed70 100644 --- a/src/map-link.js +++ b/src/map-link.js @@ -149,5 +149,30 @@ export class MapLink extends HTMLElement { } connectedCallback() {} disconnectedCallback() {} + + // Resolve the templated URL with info from the sibling map-input's + resolve() { + if (this.tref) { + let obj = {}; + const inputs = this.parentElement.querySelectorAll('map-input'); + if (this.rel === 'image') { + // image/map + for (let i = 0; i < inputs.length; i++) { + const inp = inputs[i]; + obj[inp.name] = inp.value; + } + console.log(obj); // DEBUGGING + return L.Util.template(this.tref, obj); + } else if (this.rel === 'tile') { + // TODO. Need to get tile coords from moveend + // should be done/called from the TemplatedTilelayer.js file + return obj; + } else if (this.rel === 'query') { + // TODO. Need to get the click coords from click event + // should be done/called from the templatedlayer.js file + } else if (this.rel === 'features') { + // TODO. + } + } + } } -window.customElements.define('map-link', MapLink); diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index aaf4a9874..431fccf0b 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -6,6 +6,7 @@ import { MapCaption } from './map-caption.js'; import { MapFeature } from './map-feature.js'; import { MapExtent } from './map-extent.js'; import { MapInput } from './map-input.js'; +import { MapLink } from './map-link.js'; export class MapViewer extends HTMLElement { static get observedAttributes() { @@ -1391,3 +1392,4 @@ window.customElements.define('map-caption', MapCaption); window.customElements.define('map-feature', MapFeature); window.customElements.define('map-extent', MapExtent); window.customElements.define('map-input', MapInput); +window.customElements.define('map-link', MapLink); diff --git a/src/web-map.js b/src/web-map.js index 2d1e24056..ba508e853 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -6,6 +6,8 @@ import { MapArea } from './map-area.js'; import { MapCaption } from './map-caption.js'; import { MapFeature } from './map-feature.js'; import { MapExtent } from './map-extent.js'; +import { MapInput } from './map-input.js'; +import { MapLink } from './map-link.js'; export class WebMap extends HTMLMapElement { static get observedAttributes() { @@ -1456,3 +1458,5 @@ window.customElements.define('map-area', MapArea, { extends: 'area' }); window.customElements.define('map-caption', MapCaption); window.customElements.define('map-feature', MapFeature); window.customElements.define('map-extent', MapExtent); +window.customElements.define('map-input', MapInput); +window.customElements.define('map-link', MapLink); From dc565fc6906c31330c84c109c26f82e689b5e92f Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 26 Sep 2023 16:11:24 -0400 Subject: [PATCH 005/136] Suspend CI for now --- .github/workflows/ci-testing.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index 07c378cf4..1e379c16b 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -13,12 +13,12 @@ jobs: uses: actions/setup-node@v3 with: node-version: '18.x' - - run: sudo apt-get install xvfb - - run: npm install --legacy-peer-deps - - run: npx playwright install - - run: npm install -g grunt-cli - - run: grunt default - - run: xvfb-run --auto-servernum -- npm test +# - run: sudo apt-get install xvfb +# - run: npm install --legacy-peer-deps +# - run: npx playwright install +# - run: npm install -g grunt-cli +# - run: grunt default +# - run: xvfb-run --auto-servernum -- npm test # - run: xvfb-run --auto-servernum -- npm run jest env: CI: true \ No newline at end of file From 87fe02724097c3437606c7ed7c8dc4802b5e06d9 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 26 Sep 2023 16:55:00 -0400 Subject: [PATCH 006/136] Make map-input.connectedCallback async, await on extent.whenReady() for setup --- index.html | 61 ++++++++---------------------------------------- src/map-input.js | 27 ++++++++++++++++++++- 2 files changed, 36 insertions(+), 52 deletions(-) diff --git a/index.html b/index.html index dc8419974..8ec787ac5 100644 --- a/index.html +++ b/index.html @@ -72,56 +72,15 @@ - - A pleasing map of Canada - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
code1200020
accuracy26
valdate1995
image - - -
themeFO
type2
elevation61
altiaccu5
-
- - - -75.705278 45.397778 - - -
+ @@ -142,7 +101,7 @@ + tref="https://maps.geogratis.gc.ca/wms/toporama_en?SERVICE=WMS&REQUEST=GetMap&FORMAT=image/jpeg&TRANSPARENT=FALSE&STYLES=&VERSION=1.3.0&LAYERS=WMS-Toporama&WIDTH={w}&HEIGHT={h}&CRS=EPSG:3978&BBOX={xmin},{ymin},{xmax},{ymax}&m4h=t" >
diff --git a/src/map-input.js b/src/map-input.js index d97ca3c2d..adfb61c96 100644 --- a/src/map-input.js +++ b/src/map-input.js @@ -215,7 +215,8 @@ export class MapInput extends HTMLElement { // Always call super first in constructor super(); } - connectedCallback() { + async connectedCallback() { + await this.parentElement.whenReady(); if (this.parentElement.nodeName === 'MAP-EXTENT') { this._layer = this.parentElement._layer; } @@ -292,4 +293,28 @@ export class MapInput extends HTMLElement { return false; } } + whenReady() { + return new Promise((resolve, reject) => { + let interval, failureTimer; + if (this.input) { + resolve(); + } else { + let inputElement = this; + interval = setInterval(testForInput, 300, inputElement); + failureTimer = setTimeout(inputNotDefined, 10000); + } + function testForInput(inputElement) { + if (inputElement.input) { + clearInterval(interval); + clearTimeout(failureTimer); + resolve(); + } + } + function inputNotDefined() { + clearInterval(interval); + clearTimeout(failureTimer); + reject('Timeout reached waiting for input to be ready'); + } + }); + } } From 2bf9170e0c147287c5de820b567ca3024419a703 Mon Sep 17 00:00:00 2001 From: prushfor Date: Wed, 27 Sep 2023 10:02:07 -0400 Subject: [PATCH 007/136] Update version to 0.12.0 in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 717ff2fbf..edb54ae85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@maps4html/web-map-custom-element", - "version": "0.11.0", + "version": "0.12.0", "description": "web-map customized built-in HTML or custom ", "keywords": [ "mapml-viewer", From 68139cd43e9f6abdeac704fd01bd5fa0b367e51d Mon Sep 17 00:00:00 2001 From: prushfor Date: Tue, 3 Oct 2023 11:17:13 -0400 Subject: [PATCH 008/136] WIP on migrating extent initialization from MapMLLayer to map-extent.js --- src/map-extent.js | 480 ++++++++++++++++++++++++++++++++- src/mapml/layers/MapMLLayer.js | 10 + 2 files changed, 480 insertions(+), 10 deletions(-) diff --git a/src/map-extent.js b/src/map-extent.js index 5808586a5..0303ca321 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -67,25 +67,485 @@ export class MapExtent extends HTMLElement { // Always call super first in constructor super(); } - connectedCallback() { + async connectedCallback() { if ( this.querySelector('map-link[rel=query], map-link[rel=features]') && !this.shadowRoot ) { this.attachShadow({ mode: 'open' }); } - let parentLayer = + this.parentLayer = this.parentNode.nodeName.toUpperCase() === 'LAYER-' ? this.parentNode : this.parentNode.host; - parentLayer - .whenReady() - .then(() => { - this._layer = parentLayer._layer; - }) - .catch(() => { - throw new Error('Layer never became ready'); - }); + await this.parentLayer.whenReady(); + this._layer = this.parentLayer._layer; + // this code comes from MapMLLayer._initialize.processExtents + this._templateVars = this._initTemplateVars( + // mapml is the layer- element OR the mapml- document root + this.parentLayer.querySelector('map-meta[name=extent]'), + this.units, + this._layer._content, + this._layer.getBase(), + this.units === this._layer.options.mapprojection + ); + this._layerControlHTML = createLayerControlExtentHTML(this); + } + function _initTemplateVars( + metaExtent, + projection, + mapml, + base, + projectionMatch + ) { + var templateVars = []; + // set up the URL template and associated inputs (which yield variable values when processed) + var tlist = this.querySelectorAll( + 'map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query]' + ), + varNamesRe = new RegExp('(?:{)(.*?)(?:})', 'g'), + zoomInput = this.querySelector('map-input[type="zoom" i]'), + includesZoom = false, + extentFallback = {}; + + extentFallback.zoom = 0; + if (metaExtent) { + let content = M._metaContentToObject( + metaExtent.getAttribute('content') + ), + cs; + + extentFallback.zoom = content.zoom || extentFallback.zoom; + + let metaKeys = Object.keys(content); + for (let i = 0; i < metaKeys.length; i++) { + if (!metaKeys[i].includes('zoom')) { + cs = M.axisToCS(metaKeys[i].split('-')[2]); + break; + } + } + let axes = M.csToAxes(cs); + extentFallback.bounds = M.boundsToPCRSBounds( + L.bounds( + L.point( + +content[`top-left-${axes[0]}`], + +content[`top-left-${axes[1]}`] + ), + L.point( + +content[`bottom-right-${axes[0]}`], + +content[`bottom-right-${axes[1]}`] + ) + ), + extentFallback.zoom, + projection, + cs + ); + } else { + // for custom projections, M[projection] may not be loaded, so uses M['OSMTILE'] as backup, this code will need to get rerun once projection is changed and M[projection] is available + // TODO: This is a temporary fix, _initTemplateVars (or processinitialextent) should not be called when projection of the layer and map do not match, this should be called/reinitialized once the layer projection matches with the map projection + let fallbackProjection = M[projection] || M.OSMTILE; + extentFallback.bounds = fallbackProjection.options.crs.pcrs.bounds; + } + + for (var i = 0; i < tlist.length; i++) { + var t = tlist[i], + template = t.getAttribute('tref'); + t.zoomInput = zoomInput; + if (!template) { + template = BLANK_TT_TREF; + let blankInputs = mapml.querySelectorAll('map-input'); + for (let i of blankInputs) { + template += `{${i.getAttribute('name')}}`; + } + } + + var v, + title = t.hasAttribute('title') + ? t.getAttribute('title') + : 'Query this layer', + vcount = template.match(varNamesRe), + trel = + !t.hasAttribute('rel') || + t.getAttribute('rel').toLowerCase() === 'tile' + ? 'tile' + : t.getAttribute('rel').toLowerCase(), + ttype = !t.hasAttribute('type') + ? 'image/*' + : t.getAttribute('type').toLowerCase(), + inputs = [], + tms = t && t.hasAttribute('tms'); + var zoomBounds = mapml.querySelector('map-meta[name=zoom]') + ? M._metaContentToObject( + mapml + .querySelector('map-meta[name=zoom]') + .getAttribute('content') + ) + : undefined; + while ((v = varNamesRe.exec(template)) !== null) { + var varName = v[1], + inp = this.querySelector( + 'map-input[name=' + + varName + + '],map-select[name=' + + varName + + ']' + ); + if (inp) { + if ( + inp.hasAttribute('type') && + inp.getAttribute('type') === 'location' && + (!inp.hasAttribute('min') || !inp.hasAttribute('max')) && + inp.hasAttribute('axis') && + !['i', 'j'].includes(inp.getAttribute('axis').toLowerCase()) + ) { + if ( + zoomInput && + template.includes(`{${zoomInput.getAttribute('name')}}`) + ) { + zoomInput.setAttribute('value', extentFallback.zoom); + } + let axis = inp.getAttribute('axis'), + axisBounds = M.convertPCRSBounds( + extentFallback.bounds, + extentFallback.zoom, + projection, + M.axisToCS(axis) + ); + inp.setAttribute('min', axisBounds.min[M.axisToXY(axis)]); + inp.setAttribute('max', axisBounds.max[M.axisToXY(axis)]); + } + + inputs.push(inp); + includesZoom = + includesZoom || + (inp.hasAttribute('type') && + inp.getAttribute('type').toLowerCase() === 'zoom'); + if (inp.tagName.toLowerCase() === 'map-select') { + // use a throwaway div to parse the input from MapML into HTML + var div = document.createElement('div'); + div.insertAdjacentHTML('afterbegin', inp.outerHTML); + // parse + inp.htmlselect = div.querySelector('map-select'); + inp.htmlselect = transcribe(inp.htmlselect); + + // this goes into the layer control, so add a listener + L.DomEvent.on(inp.htmlselect, 'change', this._layer.redraw, this._layer); + if (!this._layer._userInputs) { + this._layer._userInputs = []; + } + this._layer._userInputs.push(inp.htmlselect); + } + // TODO: if this is an input@type=location + // get the TCRS min,max attribute values at the identified zoom level + // save this information as properties of the mapExtent, + // perhaps as a bounds object so that it can be easily used + // later by the layer control to determine when to enable + // disable the layer for drawing. + } else { + console.log( + 'input with name=' + + varName + + ' not found for template variable of same name' + ); + // no match found, template won't be used + break; + } + } + if ( + (template && vcount.length === inputs.length) || + template === BLANK_TT_TREF + ) { + if (trel === 'query') { + this._layer.queryable = true; + } + if (!includesZoom && zoomInput) { + inputs.push(zoomInput); + } + let step = zoomInput ? zoomInput.getAttribute('step') : 1; + if (!step || step === '0' || isNaN(step)) step = 1; + // template has a matching input for every variable reference {varref} + templateVars.push({ + template: decodeURI(new URL(template, base)), + linkEl: t, + title: title, + rel: trel, + type: ttype, + values: inputs, + zoomBounds: zoomBounds, + extentPCRSFallback: { bounds: extentFallback.bounds }, + projectionMatch: projectionMatch, + projection: + this.getAttribute('units') || FALLBACK_PROJECTION, + tms: tms, + step: step + }); + } + } + return templateVars; + } + function createLayerControlExtentHTML() { + var extent = L.DomUtil.create('fieldset', 'mapml-layer-extent'), + extentProperties = L.DomUtil.create( + 'div', + 'mapml-layer-item-properties', + extent + ), + extentSettings = L.DomUtil.create( + 'div', + 'mapml-layer-item-settings', + extent + ), + extentLabel = L.DomUtil.create( + 'label', + 'mapml-layer-item-toggle', + extentProperties + ), + input = L.DomUtil.create('input'), + svgExtentControlIcon = L.SVG.create('svg'), + extentControlPath1 = L.SVG.create('path'), + extentControlPath2 = L.SVG.create('path'), + extentNameIcon = L.DomUtil.create('span'), + extentItemControls = L.DomUtil.create( + 'div', + 'mapml-layer-item-controls', + extentProperties + ), + opacityControl = L.DomUtil.create( + 'details', + 'mapml-layer-item-opacity', + extentSettings + ), + extentOpacitySummary = L.DomUtil.create( + 'summary', + '', + opacityControl + ), + mapEl = this._layerEl.parentNode, + layerEl = this._layerEl, + opacity = L.DomUtil.create('input', '', opacityControl); + extentSettings.hidden = true; + extent.setAttribute('aria-grabbed', 'false'); + if (!this.hasAttribute('label')) { + // if a label attribute is not present, set it to hidden in layer control + extent.setAttribute('hidden', ''); + this.hidden = true; + } + + // append the svg paths + svgExtentControlIcon.setAttribute('viewBox', '0 0 24 24'); + svgExtentControlIcon.setAttribute('height', '22'); + svgExtentControlIcon.setAttribute('width', '22'); + extentControlPath1.setAttribute('d', 'M0 0h24v24H0z'); + extentControlPath1.setAttribute('fill', 'none'); + extentControlPath2.setAttribute( + 'd', + 'M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' + ); + svgExtentControlIcon.appendChild(extentControlPath1); + svgExtentControlIcon.appendChild(extentControlPath2); + + let removeExtentButton = L.DomUtil.create( + 'button', + 'mapml-layer-item-remove-control', + extentItemControls + ); + removeExtentButton.type = 'button'; + removeExtentButton.title = 'Remove Sub Layer'; + removeExtentButton.innerHTML = + ""; + removeExtentButton.classList.add('mapml-button'); + L.DomEvent.on(removeExtentButton, 'click', L.DomEvent.stop); + L.DomEvent.on( + removeExtentButton, + 'click', + (e) => { + let allRemoved = true; + e.target.checked = false; + this.removed = true; + this.checked = false; + if (this._layerEl.checked) this._changeExtent(e, this); + this._layerControlHTML.parentNode.removeChild( + this._layerControlHTML + ); + for (let j = 0; j < this._properties._mapExtents.length; j++) { + if (!this._properties._mapExtents[j].removed) allRemoved = false; + } + if (allRemoved) + this._layerItemSettingsHTML.removeChild( + this._propertiesGroupAnatomy + ); + }, + this + ); + + let extentsettingsButton = L.DomUtil.create( + 'button', + 'mapml-layer-item-settings-control', + extentItemControls + ); + extentsettingsButton.type = 'button'; + extentsettingsButton.title = 'Extent Settings'; + extentsettingsButton.setAttribute('aria-expanded', false); + extentsettingsButton.classList.add('mapml-button'); + L.DomEvent.on( + extentsettingsButton, + 'click', + (e) => { + if (extentSettings.hidden === true) { + extentsettingsButton.setAttribute('aria-expanded', true); + extentSettings.hidden = false; + } else { + extentsettingsButton.setAttribute('aria-expanded', false); + extentSettings.hidden = true; + } + }, + this + ); + + extentNameIcon.setAttribute('aria-hidden', true); + extentLabel.appendChild(input); + extentsettingsButton.appendChild(extentNameIcon); + extentNameIcon.appendChild(svgExtentControlIcon); + extentOpacitySummary.innerText = 'Opacity'; + extentOpacitySummary.id = + 'mapml-layer-item-opacity-' + L.stamp(extentOpacitySummary); + opacity.setAttribute('type', 'range'); + opacity.setAttribute('min', '0'); + opacity.setAttribute('max', '1.0'); + opacity.setAttribute('step', '0.1'); + opacity.setAttribute( + 'aria-labelledby', + 'mapml-layer-item-opacity-' + L.stamp(extentOpacitySummary) + ); + let opacityValue = this.hasAttribute('opacity') + ? this.getAttribute('opacity') + : '1.0'; + this._templateVars.opacity = opacityValue; + opacity.setAttribute('value', opacityValue); + opacity.value = opacityValue; + L.DomEvent.on(opacity, 'change', this._changeExtentOpacity, this); + + var extentItemNameSpan = L.DomUtil.create( + 'span', + 'mapml-layer-item-name', + extentLabel + ); + input.defaultChecked = this ? true : false; + this.checked = input.defaultChecked; + input.type = 'checkbox'; + extentItemNameSpan.innerHTML = this.getAttribute('label'); + L.DomEvent.on(input, 'change', (e) => { + this._changeExtent(e, this); + }); + extentItemNameSpan.id = + 'mapml-extent-item-name-{' + L.stamp(extentItemNameSpan) + '}'; + extent.setAttribute('aria-labelledby', extentItemNameSpan.id); + extentItemNameSpan.extent = this; + + extent.ontouchstart = extent.onmousedown = (downEvent) => { + if ( + (downEvent.target.parentElement.tagName.toLowerCase() === 'label' && + downEvent.target.tagName.toLowerCase() !== 'input') || + downEvent.target.tagName.toLowerCase() === 'label' + ) { + downEvent.stopPropagation(); + downEvent = + downEvent instanceof TouchEvent + ? downEvent.touches[0] + : downEvent; + + let control = extent, + controls = extent.parentNode, + moving = false, + yPos = downEvent.clientY; + + document.body.ontouchmove = document.body.onmousemove = ( + moveEvent + ) => { + moveEvent.preventDefault(); + moveEvent = + moveEvent instanceof TouchEvent + ? moveEvent.touches[0] + : moveEvent; + + // Fixes flickering by only moving element when there is enough space + let offset = moveEvent.clientY - yPos; + moving = Math.abs(offset) > 5 || moving; + if ( + (controls && !moving) || + (controls && controls.childElementCount <= 1) || + controls.getBoundingClientRect().top > + control.getBoundingClientRect().bottom || + controls.getBoundingClientRect().bottom < + control.getBoundingClientRect().top + ) { + return; + } + + controls.classList.add('mapml-draggable'); + control.style.transform = 'translateY(' + offset + 'px)'; + control.style.pointerEvents = 'none'; + + let x = moveEvent.clientX, + y = moveEvent.clientY, + root = + mapEl.tagName === 'MAPML-VIEWER' + ? mapEl.shadowRoot + : mapEl.querySelector('.mapml-web-map').shadowRoot, + elementAt = root.elementFromPoint(x, y), + swapControl = + !elementAt || !elementAt.closest('fieldset') + ? control + : elementAt.closest('fieldset'); + + swapControl = + Math.abs(offset) <= swapControl.offsetHeight + ? control + : swapControl; + + control.setAttribute('aria-grabbed', 'true'); + control.setAttribute('aria-dropeffect', 'move'); + if (swapControl && controls === swapControl.parentNode) { + swapControl = + swapControl !== control.nextSibling + ? swapControl + : swapControl.nextSibling; + if (control !== swapControl) { + yPos = moveEvent.clientY; + control.style.transform = null; + } + controls.insertBefore(control, swapControl); + } + }; + + document.body.ontouchend = document.body.onmouseup = () => { + control.setAttribute('aria-grabbed', 'false'); + control.removeAttribute('aria-dropeffect'); + control.style.pointerEvents = null; + control.style.transform = null; + let controlsElems = controls.children, + zIndex = 0; + for (let c of controlsElems) { + let extentEl = c.querySelector('span').extent; + + extentEl.setAttribute('data-moving', ''); + layerEl.insertAdjacentElement('beforeend', extentEl); + extentEl.removeAttribute('data-moving'); + + extentEl.extentZIndex = zIndex; + extentEl.templatedLayer.setZIndex(zIndex); + zIndex++; + } + controls.classList.remove('mapml-draggable'); + document.body.ontouchmove = + document.body.onmousemove = + document.body.ontouchend = + document.body.onmouseup = + null; + }; + } + }; + return extent; } disconnectedCallback() {} whenReady() { diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 015fa08f0..0f9cd2490 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -842,6 +842,16 @@ export var MapMLLayer = L.Layer.extend({ } return this._mapmlLayerItem; }, + getBase: function () { + return new URL( + this._content.querySelector('map-base') + ? this._content.querySelector('map-base').getAttribute('href') + : this._content.nodeName === 'LAYER-' + ? this._content.baseURI + : this._href, + this._href + ).href; + }, _initialize: function (content) { if (!this._href && !content) { return; From bb15b34aaf2edb0af6a04d3192d52215eb193160 Mon Sep 17 00:00:00 2001 From: prushfor Date: Tue, 3 Oct 2023 16:51:00 -0400 Subject: [PATCH 009/136] WIP on migrating extent initialization from MapMLLayer to map-extent.js --- src/map-extent.js | 195 ++++++----- src/mapml/layers/MapMLLayer.js | 578 +-------------------------------- src/mapml/utils/Util.js | 6 +- 3 files changed, 116 insertions(+), 663 deletions(-) diff --git a/src/map-extent.js b/src/map-extent.js index 0303ca321..3260a8ff3 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -52,9 +52,15 @@ export class MapExtent extends HTMLElement { } break; case 'checked': - if (oldValue !== newValue) { - // handle side effects - } + this.whenReady().then(() => { + if (typeof newValue === 'string') { + // handle side effects, such as adding / removing from map + this._templatedLayer.addTo(this._map); + } else { + // remove from map + this._templatedLayer.remove(); + } + }); break; case 'opacity': if (oldValue !== newValue) { @@ -80,6 +86,8 @@ export class MapExtent extends HTMLElement { : this.parentNode.host; await this.parentLayer.whenReady(); this._layer = this.parentLayer._layer; + let viewer = this.closest('mapml-viewer') || this.closest('map'); + this._map = viewer._map; // this code comes from MapMLLayer._initialize.processExtents this._templateVars = this._initTemplateVars( // mapml is the layer- element OR the mapml- document root @@ -89,15 +97,30 @@ export class MapExtent extends HTMLElement { this._layer.getBase(), this.units === this._layer.options.mapprojection ); - this._layerControlHTML = createLayerControlExtentHTML(this); + this._layerControlHTML = this.createLayerControlExtentHTML(this); + this._templatedLayer = M.templatedLayer(this._templateVars, { + pane: this._layer._container, + opacity: this._templateVars.opacity, + _leafletLayer: this._layer, + crs: this._layer._properties.crs, + extentZIndex: Array.from( + this.parentLayer.querySelectorAll('map-extent') + ).indexOf(this), + // when a migrates from a remote mapml file and attaches to the shadow of + // this._properties._mapExtents[i] refers to the in remote mapml + extentEl: this._DOMnode || this + }); + if (this._templatedLayer._queries) { + if (!this._layer._properties._queries) + this._layer._properties._queries = []; + this._layer._properties._queries = + this._layer._properties._queries.concat(this._templatedLayer._queries); + } + if (this.hasAttribute('opacity')) { + this._templatedLayer.changeOpacity(this.getAttribute('opacity')); + } } - function _initTemplateVars( - metaExtent, - projection, - mapml, - base, - projectionMatch - ) { + _initTemplateVars(metaExtent, projection, mapml, base, projectionMatch) { var templateVars = []; // set up the URL template and associated inputs (which yield variable values when processed) var tlist = this.querySelectorAll( @@ -106,16 +129,14 @@ export class MapExtent extends HTMLElement { varNamesRe = new RegExp('(?:{)(.*?)(?:})', 'g'), zoomInput = this.querySelector('map-input[type="zoom" i]'), includesZoom = false, - extentFallback = {}; + boundsFallback = {}; - extentFallback.zoom = 0; + boundsFallback.zoom = 0; if (metaExtent) { - let content = M._metaContentToObject( - metaExtent.getAttribute('content') - ), + let content = M._metaContentToObject(metaExtent.getAttribute('content')), cs; - extentFallback.zoom = content.zoom || extentFallback.zoom; + boundsFallback.zoom = content.zoom || boundsFallback.zoom; let metaKeys = Object.keys(content); for (let i = 0; i < metaKeys.length; i++) { @@ -125,7 +146,7 @@ export class MapExtent extends HTMLElement { } } let axes = M.csToAxes(cs); - extentFallback.bounds = M.boundsToPCRSBounds( + boundsFallback.bounds = M.boundsToPCRSBounds( L.bounds( L.point( +content[`top-left-${axes[0]}`], @@ -136,7 +157,7 @@ export class MapExtent extends HTMLElement { +content[`bottom-right-${axes[1]}`] ) ), - extentFallback.zoom, + boundsFallback.zoom, projection, cs ); @@ -144,7 +165,7 @@ export class MapExtent extends HTMLElement { // for custom projections, M[projection] may not be loaded, so uses M['OSMTILE'] as backup, this code will need to get rerun once projection is changed and M[projection] is available // TODO: This is a temporary fix, _initTemplateVars (or processinitialextent) should not be called when projection of the layer and map do not match, this should be called/reinitialized once the layer projection matches with the map projection let fallbackProjection = M[projection] || M.OSMTILE; - extentFallback.bounds = fallbackProjection.options.crs.pcrs.bounds; + boundsFallback.bounds = fallbackProjection.options.crs.pcrs.bounds; } for (var i = 0; i < tlist.length; i++) { @@ -176,19 +197,13 @@ export class MapExtent extends HTMLElement { tms = t && t.hasAttribute('tms'); var zoomBounds = mapml.querySelector('map-meta[name=zoom]') ? M._metaContentToObject( - mapml - .querySelector('map-meta[name=zoom]') - .getAttribute('content') + mapml.querySelector('map-meta[name=zoom]').getAttribute('content') ) : undefined; while ((v = varNamesRe.exec(template)) !== null) { var varName = v[1], inp = this.querySelector( - 'map-input[name=' + - varName + - '],map-select[name=' + - varName + - ']' + 'map-input[name=' + varName + '],map-select[name=' + varName + ']' ); if (inp) { if ( @@ -202,12 +217,12 @@ export class MapExtent extends HTMLElement { zoomInput && template.includes(`{${zoomInput.getAttribute('name')}}`) ) { - zoomInput.setAttribute('value', extentFallback.zoom); + zoomInput.setAttribute('value', boundsFallback.zoom); } let axis = inp.getAttribute('axis'), axisBounds = M.convertPCRSBounds( - extentFallback.bounds, - extentFallback.zoom, + boundsFallback.bounds, + boundsFallback.zoom, projection, M.axisToCS(axis) ); @@ -229,7 +244,12 @@ export class MapExtent extends HTMLElement { inp.htmlselect = transcribe(inp.htmlselect); // this goes into the layer control, so add a listener - L.DomEvent.on(inp.htmlselect, 'change', this._layer.redraw, this._layer); + L.DomEvent.on( + inp.htmlselect, + 'change', + this._layer.redraw, + this._layer + ); if (!this._layer._userInputs) { this._layer._userInputs = []; } @@ -272,10 +292,9 @@ export class MapExtent extends HTMLElement { type: ttype, values: inputs, zoomBounds: zoomBounds, - extentPCRSFallback: { bounds: extentFallback.bounds }, + boundsFallbackPCRS: { bounds: boundsFallback.bounds }, projectionMatch: projectionMatch, - projection: - this.getAttribute('units') || FALLBACK_PROJECTION, + projection: this.units || FALLBACK_PROJECTION, tms: tms, step: step }); @@ -283,7 +302,7 @@ export class MapExtent extends HTMLElement { } return templateVars; } - function createLayerControlExtentHTML() { + createLayerControlExtentHTML() { var extent = L.DomUtil.create('fieldset', 'mapml-layer-extent'), extentProperties = L.DomUtil.create( 'div', @@ -315,13 +334,9 @@ export class MapExtent extends HTMLElement { 'mapml-layer-item-opacity', extentSettings ), - extentOpacitySummary = L.DomUtil.create( - 'summary', - '', - opacityControl - ), - mapEl = this._layerEl.parentNode, - layerEl = this._layerEl, + extentOpacitySummary = L.DomUtil.create('summary', '', opacityControl), + mapEl = this.parentLayer.parentNode, + layerEl = this.parentLayer, opacity = L.DomUtil.create('input', '', opacityControl); extentSettings.hidden = true; extent.setAttribute('aria-grabbed', 'false'); @@ -351,32 +366,13 @@ export class MapExtent extends HTMLElement { ); removeExtentButton.type = 'button'; removeExtentButton.title = 'Remove Sub Layer'; - removeExtentButton.innerHTML = - ""; + removeExtentButton.innerHTML = ""; removeExtentButton.classList.add('mapml-button'); - L.DomEvent.on(removeExtentButton, 'click', L.DomEvent.stop); - L.DomEvent.on( - removeExtentButton, - 'click', - (e) => { - let allRemoved = true; - e.target.checked = false; - this.removed = true; - this.checked = false; - if (this._layerEl.checked) this._changeExtent(e, this); - this._layerControlHTML.parentNode.removeChild( - this._layerControlHTML - ); - for (let j = 0; j < this._properties._mapExtents.length; j++) { - if (!this._properties._mapExtents[j].removed) allRemoved = false; - } - if (allRemoved) - this._layerItemSettingsHTML.removeChild( - this._propertiesGroupAnatomy - ); - }, - this - ); + removeExtentButton.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.remove(); + }); let extentsettingsButton = L.DomUtil.create( 'button', @@ -423,7 +419,7 @@ export class MapExtent extends HTMLElement { this._templateVars.opacity = opacityValue; opacity.setAttribute('value', opacityValue); opacity.value = opacityValue; - L.DomEvent.on(opacity, 'change', this._changeExtentOpacity, this); + opacity.addEventListener('change', this._changeOpacity); var extentItemNameSpan = L.DomUtil.create( 'span', @@ -434,9 +430,7 @@ export class MapExtent extends HTMLElement { this.checked = input.defaultChecked; input.type = 'checkbox'; extentItemNameSpan.innerHTML = this.getAttribute('label'); - L.DomEvent.on(input, 'change', (e) => { - this._changeExtent(e, this); - }); + input.addEventListener('change', this._changeExtent); extentItemNameSpan.id = 'mapml-extent-item-name-{' + L.stamp(extentItemNameSpan) + '}'; extent.setAttribute('aria-labelledby', extentItemNameSpan.id); @@ -450,23 +444,17 @@ export class MapExtent extends HTMLElement { ) { downEvent.stopPropagation(); downEvent = - downEvent instanceof TouchEvent - ? downEvent.touches[0] - : downEvent; + downEvent instanceof TouchEvent ? downEvent.touches[0] : downEvent; let control = extent, controls = extent.parentNode, moving = false, yPos = downEvent.clientY; - document.body.ontouchmove = document.body.onmousemove = ( - moveEvent - ) => { + document.body.ontouchmove = document.body.onmousemove = (moveEvent) => { moveEvent.preventDefault(); moveEvent = - moveEvent instanceof TouchEvent - ? moveEvent.touches[0] - : moveEvent; + moveEvent instanceof TouchEvent ? moveEvent.touches[0] : moveEvent; // Fixes flickering by only moving element when there is enough space let offset = moveEvent.clientY - yPos; @@ -547,11 +535,50 @@ export class MapExtent extends HTMLElement { }; return extent; } - disconnectedCallback() {} + _changeExtent(e) { + if (e.target.checked) { + this.checked = true; + if (this.parentLayer.checked) { + this.templatedLayer = M.templatedLayer(this._templateVars, { + pane: this._layer._container, + opacity: this._templateVars.opacity, + _leafletLayer: this._layer, + crs: this._layer.crs, + extentZIndex: this.extentZIndex, + extentEl: this._DOMnode || this + }).addTo(this._layer._map); + this.templatedLayer.setZIndex(); + this._layer._setLayerElExtent(); + } + } else { + L.DomEvent.stopPropagation(e); + this.checked = false; + if (this.parentLayer.checked) this._map.removeLayer(this.templatedLayer); + this._layer._setLayerElExtent(); + } + } + disconnectedCallback() { + let extentsFieldset = this._layerControlHTML.closest( + 'fieldset.mapml-layer-grouped-extents' + ); + // remove layer control for map-extent from layer control DOM + this._layerControlHTML.remove(); + // remove the map-extent from DOM + if (this.parentLayer.querySelectorAll('map-extent').length === 0) { + extentsFieldset.remove(); + } + } + _changeOpacity(e) { + if (e && e.target && e.target.value >= 0 && e.target.value <= 1.0) { + this.templatedLayer.changeOpacity(e.target.value); + this._templateVars.opacity = e.target.value; + } + } + whenReady() { return new Promise((resolve, reject) => { let interval, failureTimer; - if (this._layer) { + if (this._templatedLayer) { resolve(); } else { let extentElement = this; @@ -559,7 +586,7 @@ export class MapExtent extends HTMLElement { failureTimer = setTimeout(extentNotDefined, 10000); } function testForExtent(extentElement) { - if (extentElement._layer) { + if (extentElement._templatedLayer) { clearInterval(interval); clearTimeout(failureTimer); resolve(); diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 0f9cd2490..2ed9f841d 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -101,34 +101,6 @@ export var MapMLLayer = L.Layer.extend({ this._layerEl._opacity = opacity; if (this.opacityEl) this.opacityEl.value = opacity; }, - _changeExtentOpacity: function (e) { - if (e && e.target && e.target.value >= 0 && e.target.value <= 1.0) { - this.templatedLayer.changeOpacity(e.target.value); - this._templateVars.opacity = e.target.value; - } - }, - _changeExtent: function (e, extentEl) { - if (e.target.checked) { - extentEl.checked = true; - if (this._layerEl.checked) { - extentEl.templatedLayer = M.templatedLayer(extentEl._templateVars, { - pane: this._container, - opacity: extentEl._templateVars.opacity, - _leafletLayer: this, - crs: extentEl.crs, - extentZIndex: extentEl.extentZIndex, - extentEl: extentEl._DOMnode || extentEl - }).addTo(this._map); - extentEl.templatedLayer.setZIndex(); - this._setLayerElExtent(); - } - } else { - L.DomEvent.stopPropagation(e); - extentEl.checked = false; - if (this._layerEl.checked) this._map.removeLayer(extentEl.templatedLayer); - this._setLayerElExtent(); - } - }, titleIsReadOnly() { return !!this._titleIsReadOnly; }, @@ -166,15 +138,8 @@ export var MapMLLayer = L.Layer.extend({ map.addLayer(this._staticTileLayer); } - const createAndAdd = createAndAddTemplatedLayers.bind(this); - // if the extent has been initialized and received, update the map, - if ( - this._properties && - this._properties._mapExtents && - this._properties._mapExtents[0]._templateVars - ) { - createAndAdd(); - } + this._mapExtents = L.layerGroup(); + this._setLayerElExtent(); this.setZIndex(this.options.zIndex); @@ -183,50 +148,6 @@ export var MapMLLayer = L.Layer.extend({ map.fire('checkdisabled'); }, 0); map.on('popupopen', this._attachSkipButtons, this); - - function createAndAddTemplatedLayers() { - if (this._properties && this._properties._mapExtents) { - for (let i = 0; i < this._properties._mapExtents.length; i++) { - if ( - this._properties._mapExtents[i]._templateVars && - this._properties._mapExtents[i].checked - ) { - if (!this._properties._mapExtents[i].extentZIndex) - this._properties._mapExtents[i].extentZIndex = i; - this._templatedLayer = M.templatedLayer( - this._properties._mapExtents[i]._templateVars, - { - pane: this._container, - opacity: this._properties._mapExtents[i]._templateVars.opacity, - _leafletLayer: this, - crs: this._properties.crs, - extentZIndex: this._properties._mapExtents[i].extentZIndex, - // when a migrates from a remote mapml file and attaches to the shadow of - // this._properties._mapExtents[i] refers to the in remote mapml - extentEl: - this._properties._mapExtents[i]._DOMnode || - this._properties._mapExtents[i] - } - ).addTo(map); - this._properties._mapExtents[i].templatedLayer = - this._templatedLayer; - if (this._templatedLayer._queries) { - if (!this._properties._queries) this._properties._queries = []; - this._properties._queries = this._properties._queries.concat( - this._templatedLayer._queries - ); - } - } - if (this._properties._mapExtents[i].hasAttribute('opacity')) { - let opacity = - this._properties._mapExtents[i].getAttribute('opacity'); - this._properties._mapExtents[i].templatedLayer.changeOpacity( - opacity - ); - } - } - } - } }, _validProjection: function (map) { @@ -876,8 +797,6 @@ export var MapMLLayer = L.Layer.extend({ determineLayerProjection(); // requires that layer._properties.projection be set if (selectMatchingAlternateProjection()) return; - // set layer._properties._mapExtents and layer._properties._templateVars - if (layer._properties.crs) processExtents(); layer._styles = getAlternateStyles(); parseLicenseAndLegend(); setLayerTitle(); @@ -967,499 +886,6 @@ export var MapMLLayer = L.Layer.extend({ } catch (error) {} return false; } - // initialize layer._properties._mapExtents (and associated/derived/convenience property _templateVars - function processExtents() { - let projectionMatch = - layer._properties.projection === layer.options.mapprojection; - let extents = mapml.querySelectorAll('map-extent[units]'); - if (extents.length === 0) { - return; - } - layer._properties._mapExtents = []; // stores all the map-extent elements in the layer - layer._properties._templateVars = []; // stores all template variables coming from all extents - for (let j = 0; j < extents.length; j++) { - if ( - extents[j].querySelector( - 'map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query]' - ) - ) { - extents[j]._templateVars = _initTemplateVars.call( - layer, - extents[j], - mapml.querySelector('map-meta[name=extent]'), - layer._properties.projection, - mapml, - base, - projectionMatch - ); - extents[j].extentAnatomy = createLayerControlExtentHTML.call( - layer, - extents[j] - ); - layer._properties._mapExtents.push(extents[j]); - // get rid of layer._properties._templateVars, TBD. - layer._properties._templateVars = - layer._properties._templateVars.concat(extents[j]._templateVars); - } - } - } - function createLayerControlExtentHTML(mapExtent) { - var extent = L.DomUtil.create('fieldset', 'mapml-layer-extent'), - extentProperties = L.DomUtil.create( - 'div', - 'mapml-layer-item-properties', - extent - ), - extentSettings = L.DomUtil.create( - 'div', - 'mapml-layer-item-settings', - extent - ), - extentLabel = L.DomUtil.create( - 'label', - 'mapml-layer-item-toggle', - extentProperties - ), - input = L.DomUtil.create('input'), - svgExtentControlIcon = L.SVG.create('svg'), - extentControlPath1 = L.SVG.create('path'), - extentControlPath2 = L.SVG.create('path'), - extentNameIcon = L.DomUtil.create('span'), - extentItemControls = L.DomUtil.create( - 'div', - 'mapml-layer-item-controls', - extentProperties - ), - opacityControl = L.DomUtil.create( - 'details', - 'mapml-layer-item-opacity', - extentSettings - ), - extentOpacitySummary = L.DomUtil.create( - 'summary', - '', - opacityControl - ), - mapEl = this._layerEl.parentNode, - layerEl = this._layerEl, - opacity = L.DomUtil.create('input', '', opacityControl); - extentSettings.hidden = true; - extent.setAttribute('aria-grabbed', 'false'); - if (!mapExtent.hasAttribute('label')) { - // if a label attribute is not present, set it to hidden in layer control - extent.setAttribute('hidden', ''); - mapExtent.hidden = true; - } - - // append the svg paths - svgExtentControlIcon.setAttribute('viewBox', '0 0 24 24'); - svgExtentControlIcon.setAttribute('height', '22'); - svgExtentControlIcon.setAttribute('width', '22'); - extentControlPath1.setAttribute('d', 'M0 0h24v24H0z'); - extentControlPath1.setAttribute('fill', 'none'); - extentControlPath2.setAttribute( - 'd', - 'M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' - ); - svgExtentControlIcon.appendChild(extentControlPath1); - svgExtentControlIcon.appendChild(extentControlPath2); - - let removeExtentButton = L.DomUtil.create( - 'button', - 'mapml-layer-item-remove-control', - extentItemControls - ); - removeExtentButton.type = 'button'; - removeExtentButton.title = 'Remove Sub Layer'; - removeExtentButton.innerHTML = - ""; - removeExtentButton.classList.add('mapml-button'); - L.DomEvent.on(removeExtentButton, 'click', L.DomEvent.stop); - L.DomEvent.on( - removeExtentButton, - 'click', - (e) => { - let allRemoved = true; - e.target.checked = false; - mapExtent.removed = true; - mapExtent.checked = false; - if (this._layerEl.checked) this._changeExtent(e, mapExtent); - mapExtent.extentAnatomy.parentNode.removeChild( - mapExtent.extentAnatomy - ); - for (let j = 0; j < this._properties._mapExtents.length; j++) { - if (!this._properties._mapExtents[j].removed) allRemoved = false; - } - if (allRemoved) - this._layerItemSettingsHTML.removeChild( - this._propertiesGroupAnatomy - ); - }, - this - ); - - let extentsettingsButton = L.DomUtil.create( - 'button', - 'mapml-layer-item-settings-control', - extentItemControls - ); - extentsettingsButton.type = 'button'; - extentsettingsButton.title = 'Extent Settings'; - extentsettingsButton.setAttribute('aria-expanded', false); - extentsettingsButton.classList.add('mapml-button'); - L.DomEvent.on( - extentsettingsButton, - 'click', - (e) => { - if (extentSettings.hidden === true) { - extentsettingsButton.setAttribute('aria-expanded', true); - extentSettings.hidden = false; - } else { - extentsettingsButton.setAttribute('aria-expanded', false); - extentSettings.hidden = true; - } - }, - this - ); - - extentNameIcon.setAttribute('aria-hidden', true); - extentLabel.appendChild(input); - extentsettingsButton.appendChild(extentNameIcon); - extentNameIcon.appendChild(svgExtentControlIcon); - extentOpacitySummary.innerText = 'Opacity'; - extentOpacitySummary.id = - 'mapml-layer-item-opacity-' + L.stamp(extentOpacitySummary); - opacity.setAttribute('type', 'range'); - opacity.setAttribute('min', '0'); - opacity.setAttribute('max', '1.0'); - opacity.setAttribute('step', '0.1'); - opacity.setAttribute( - 'aria-labelledby', - 'mapml-layer-item-opacity-' + L.stamp(extentOpacitySummary) - ); - let opacityValue = mapExtent.hasAttribute('opacity') - ? mapExtent.getAttribute('opacity') - : '1.0'; - mapExtent._templateVars.opacity = opacityValue; - opacity.setAttribute('value', opacityValue); - opacity.value = opacityValue; - L.DomEvent.on(opacity, 'change', this._changeExtentOpacity, mapExtent); - - var extentItemNameSpan = L.DomUtil.create( - 'span', - 'mapml-layer-item-name', - extentLabel - ); - input.defaultChecked = mapExtent ? true : false; - mapExtent.checked = input.defaultChecked; - input.type = 'checkbox'; - extentItemNameSpan.innerHTML = mapExtent.getAttribute('label'); - L.DomEvent.on(input, 'change', (e) => { - this._changeExtent(e, mapExtent); - }); - extentItemNameSpan.id = - 'mapml-extent-item-name-{' + L.stamp(extentItemNameSpan) + '}'; - extent.setAttribute('aria-labelledby', extentItemNameSpan.id); - extentItemNameSpan.extent = mapExtent; - - extent.ontouchstart = extent.onmousedown = (downEvent) => { - if ( - (downEvent.target.parentElement.tagName.toLowerCase() === 'label' && - downEvent.target.tagName.toLowerCase() !== 'input') || - downEvent.target.tagName.toLowerCase() === 'label' - ) { - downEvent.stopPropagation(); - downEvent = - downEvent instanceof TouchEvent - ? downEvent.touches[0] - : downEvent; - - let control = extent, - controls = extent.parentNode, - moving = false, - yPos = downEvent.clientY; - - document.body.ontouchmove = document.body.onmousemove = ( - moveEvent - ) => { - moveEvent.preventDefault(); - moveEvent = - moveEvent instanceof TouchEvent - ? moveEvent.touches[0] - : moveEvent; - - // Fixes flickering by only moving element when there is enough space - let offset = moveEvent.clientY - yPos; - moving = Math.abs(offset) > 5 || moving; - if ( - (controls && !moving) || - (controls && controls.childElementCount <= 1) || - controls.getBoundingClientRect().top > - control.getBoundingClientRect().bottom || - controls.getBoundingClientRect().bottom < - control.getBoundingClientRect().top - ) { - return; - } - - controls.classList.add('mapml-draggable'); - control.style.transform = 'translateY(' + offset + 'px)'; - control.style.pointerEvents = 'none'; - - let x = moveEvent.clientX, - y = moveEvent.clientY, - root = - mapEl.tagName === 'MAPML-VIEWER' - ? mapEl.shadowRoot - : mapEl.querySelector('.mapml-web-map').shadowRoot, - elementAt = root.elementFromPoint(x, y), - swapControl = - !elementAt || !elementAt.closest('fieldset') - ? control - : elementAt.closest('fieldset'); - - swapControl = - Math.abs(offset) <= swapControl.offsetHeight - ? control - : swapControl; - - control.setAttribute('aria-grabbed', 'true'); - control.setAttribute('aria-dropeffect', 'move'); - if (swapControl && controls === swapControl.parentNode) { - swapControl = - swapControl !== control.nextSibling - ? swapControl - : swapControl.nextSibling; - if (control !== swapControl) { - yPos = moveEvent.clientY; - control.style.transform = null; - } - controls.insertBefore(control, swapControl); - } - }; - - document.body.ontouchend = document.body.onmouseup = () => { - control.setAttribute('aria-grabbed', 'false'); - control.removeAttribute('aria-dropeffect'); - control.style.pointerEvents = null; - control.style.transform = null; - let controlsElems = controls.children, - zIndex = 0; - for (let c of controlsElems) { - let extentEl = c.querySelector('span').extent; - - extentEl.setAttribute('data-moving', ''); - layerEl.insertAdjacentElement('beforeend', extentEl); - extentEl.removeAttribute('data-moving'); - - extentEl.extentZIndex = zIndex; - extentEl.templatedLayer.setZIndex(zIndex); - zIndex++; - } - controls.classList.remove('mapml-draggable'); - document.body.ontouchmove = - document.body.onmousemove = - document.body.ontouchend = - document.body.onmouseup = - null; - }; - } - }; - return extent; - } - function _initTemplateVars( - serverExtent, - metaExtent, - projection, - mapml, - base, - projectionMatch - ) { - var templateVars = []; - // set up the URL template and associated inputs (which yield variable values when processed) - var tlist = serverExtent.querySelectorAll( - 'map-link[rel=tile],map-link[rel=image],map-link[rel=features],map-link[rel=query]' - ), - varNamesRe = new RegExp('(?:{)(.*?)(?:})', 'g'), - zoomInput = serverExtent.querySelector('map-input[type="zoom" i]'), - includesZoom = false, - extentFallback = {}; - - extentFallback.zoom = 0; - if (metaExtent) { - let content = M._metaContentToObject( - metaExtent.getAttribute('content') - ), - cs; - - extentFallback.zoom = content.zoom || extentFallback.zoom; - - let metaKeys = Object.keys(content); - for (let i = 0; i < metaKeys.length; i++) { - if (!metaKeys[i].includes('zoom')) { - cs = M.axisToCS(metaKeys[i].split('-')[2]); - break; - } - } - let axes = M.csToAxes(cs); - extentFallback.bounds = M.boundsToPCRSBounds( - L.bounds( - L.point( - +content[`top-left-${axes[0]}`], - +content[`top-left-${axes[1]}`] - ), - L.point( - +content[`bottom-right-${axes[0]}`], - +content[`bottom-right-${axes[1]}`] - ) - ), - extentFallback.zoom, - projection, - cs - ); - } else { - // for custom projections, M[projection] may not be loaded, so uses M['OSMTILE'] as backup, this code will need to get rerun once projection is changed and M[projection] is available - // TODO: This is a temporary fix, _initTemplateVars (or processinitialextent) should not be called when projection of the layer and map do not match, this should be called/reinitialized once the layer projection matches with the map projection - let fallbackProjection = M[projection] || M.OSMTILE; - extentFallback.bounds = fallbackProjection.options.crs.pcrs.bounds; - } - - for (var i = 0; i < tlist.length; i++) { - var t = tlist[i], - template = t.getAttribute('tref'); - t.zoomInput = zoomInput; - if (!template) { - template = BLANK_TT_TREF; - let blankInputs = mapml.querySelectorAll('map-input'); - for (let i of blankInputs) { - template += `{${i.getAttribute('name')}}`; - } - } - - var v, - title = t.hasAttribute('title') - ? t.getAttribute('title') - : 'Query this layer', - vcount = template.match(varNamesRe), - trel = - !t.hasAttribute('rel') || - t.getAttribute('rel').toLowerCase() === 'tile' - ? 'tile' - : t.getAttribute('rel').toLowerCase(), - ttype = !t.hasAttribute('type') - ? 'image/*' - : t.getAttribute('type').toLowerCase(), - inputs = [], - tms = t && t.hasAttribute('tms'); - var zoomBounds = mapml.querySelector('map-meta[name=zoom]') - ? M._metaContentToObject( - mapml - .querySelector('map-meta[name=zoom]') - .getAttribute('content') - ) - : undefined; - while ((v = varNamesRe.exec(template)) !== null) { - var varName = v[1], - inp = serverExtent.querySelector( - 'map-input[name=' + - varName + - '],map-select[name=' + - varName + - ']' - ); - if (inp) { - if ( - inp.hasAttribute('type') && - inp.getAttribute('type') === 'location' && - (!inp.hasAttribute('min') || !inp.hasAttribute('max')) && - inp.hasAttribute('axis') && - !['i', 'j'].includes(inp.getAttribute('axis').toLowerCase()) - ) { - if ( - zoomInput && - template.includes(`{${zoomInput.getAttribute('name')}}`) - ) { - zoomInput.setAttribute('value', extentFallback.zoom); - } - let axis = inp.getAttribute('axis'), - axisBounds = M.convertPCRSBounds( - extentFallback.bounds, - extentFallback.zoom, - projection, - M.axisToCS(axis) - ); - inp.setAttribute('min', axisBounds.min[M.axisToXY(axis)]); - inp.setAttribute('max', axisBounds.max[M.axisToXY(axis)]); - } - - inputs.push(inp); - includesZoom = - includesZoom || - (inp.hasAttribute('type') && - inp.getAttribute('type').toLowerCase() === 'zoom'); - if (inp.tagName.toLowerCase() === 'map-select') { - // use a throwaway div to parse the input from MapML into HTML - var div = document.createElement('div'); - div.insertAdjacentHTML('afterbegin', inp.outerHTML); - // parse - inp.htmlselect = div.querySelector('map-select'); - inp.htmlselect = transcribe(inp.htmlselect); - - // this goes into the layer control, so add a listener - L.DomEvent.on(inp.htmlselect, 'change', layer.redraw, layer); - if (!layer._userInputs) { - layer._userInputs = []; - } - layer._userInputs.push(inp.htmlselect); - } - // TODO: if this is an input@type=location - // get the TCRS min,max attribute values at the identified zoom level - // save this information as properties of the serverExtent, - // perhaps as a bounds object so that it can be easily used - // later by the layer control to determine when to enable - // disable the layer for drawing. - } else { - console.log( - 'input with name=' + - varName + - ' not found for template variable of same name' - ); - // no match found, template won't be used - break; - } - } - if ( - (template && vcount.length === inputs.length) || - template === BLANK_TT_TREF - ) { - if (trel === 'query') { - layer.queryable = true; - } - if (!includesZoom && zoomInput) { - inputs.push(zoomInput); - } - let step = zoomInput ? zoomInput.getAttribute('step') : 1; - if (!step || step === '0' || isNaN(step)) step = 1; - // template has a matching input for every variable reference {varref} - templateVars.push({ - template: decodeURI(new URL(template, base)), - linkEl: t, - title: title, - rel: trel, - type: ttype, - values: inputs, - zoomBounds: zoomBounds, - extentPCRSFallback: { bounds: extentFallback.bounds }, - projectionMatch: projectionMatch, - projection: - serverExtent.getAttribute('units') || FALLBACK_PROJECTION, - tms: tms, - step: step - }); - } - } - return templateVars; - } function transcribe(element) { var select = document.createElement('select'); var elementAttrNames = element.getAttributeNames(); diff --git a/src/mapml/utils/Util.js b/src/mapml/utils/Util.js index 455b0a1cc..4685f7b10 100644 --- a/src/mapml/utils/Util.js +++ b/src/mapml/utils/Util.js @@ -169,10 +169,10 @@ export var Util = { }; if ( !locInputs && - template.extentPCRSFallback && - template.extentPCRSFallback.bounds + template.boundsFallbackPCRS && + template.boundsFallbackPCRS.bounds ) { - bounds = template.extentPCRSFallback.bounds; + bounds = template.boundsFallbackPCRS.bounds; } else if (locInputs) { bounds = this.boundsToPCRSBounds(bounds, value, projection, boundsUnit); } else { From dcf54a59307746013ac8be946488ee6fccb6858d Mon Sep 17 00:00:00 2001 From: prushfor Date: Wed, 4 Oct 2023 16:07:51 -0400 Subject: [PATCH 010/136] Initial working migration of map-extent initialization from MapMLLayer to map-extent.js --- index.html | 55 ++++++++---- src/layer.js | 58 ++++--------- src/map-extent.js | 89 +++++++++++++++----- src/mapml/layers/MapMLLayer.js | 147 +++++++++++---------------------- 4 files changed, 173 insertions(+), 176 deletions(-) diff --git a/index.html b/index.html index 8ec787ac5..e5c9ac24d 100644 --- a/index.html +++ b/index.html @@ -23,9 +23,8 @@ /* Responsive map. */ max-width: 100%; - /* Full viewport. width: 100%; - height: 100%; */ + height: 100%; /* Remove default (native-like) border. */ border: none; @@ -86,24 +85,44 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/layer.js b/src/layer.js index e792f090d..2f5889082 100644 --- a/src/layer.js +++ b/src/layer.js @@ -310,8 +310,9 @@ export class MapLayer extends HTMLElement { let layer = this._layer, map = layer?._map; if (map) { - let count = 0, - total = 0, + const mapExtents = this.querySelectorAll('map-extent'); + let disabledExtentCount = 0, + totalExtentCount = 0, layerTypes = [ '_staticTileLayer', '_imageLayer', @@ -321,51 +322,28 @@ export class MapLayer extends HTMLElement { if (layer.validProjection) { for (let j = 0; j < layerTypes.length; j++) { let type = layerTypes[j]; - if (this.checked && layer[type]) { + if (this.checked && mapExtents.length > 0) { if (type === '_templatedLayer') { - for (let i = 0; i < layer._properties._mapExtents.length; i++) { - for ( - let j = 0; - j < - layer._properties._mapExtents[i].templatedLayer._templates - .length; - j++ - ) { - if ( - layer._properties._mapExtents[i].templatedLayer - ._templates[j].rel === 'query' - ) - continue; - total++; - layer._properties._mapExtents[i].removeAttribute( - 'disabled' - ); - layer._properties._mapExtents[i].disabled = false; - if ( - !layer._properties._mapExtents[i].templatedLayer - ._templates[j].layer.isVisible - ) { - count++; - layer._properties._mapExtents[i].setAttribute( - 'disabled', - '' - ); - layer._properties._mapExtents[i].disabled = true; - } - } + for (let i = 0; i < mapExtents.length; i++) { + totalExtentCount++; + if (mapExtents[i]._validateDisabled()) disabledExtentCount++; } - } else { - total++; - if (!layer[type].isVisible) count++; + } else if (layer[type]) { + // not a templated layer + totalExtentCount++; + if (!layer[type].isVisible) disabledExtentCount++; } } } } else { - count = 1; - total = 1; + disabledExtentCount = 1; + totalExtentCount = 1; } - - if (count === total && count !== 0) { + // if all extents are not visible / disabled, set layer to disabled + if ( + disabledExtentCount === totalExtentCount && + disabledExtentCount !== 0 + ) { this.setAttribute('disabled', ''); //set a disabled attribute on the layer element this.disabled = true; } else { diff --git a/src/map-extent.js b/src/map-extent.js index 3260a8ff3..1328770ee 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -53,13 +53,7 @@ export class MapExtent extends HTMLElement { break; case 'checked': this.whenReady().then(() => { - if (typeof newValue === 'string') { - // handle side effects, such as adding / removing from map - this._templatedLayer.addTo(this._map); - } else { - // remove from map - this._templatedLayer.remove(); - } + this._changeExtent(); }); break; case 'opacity': @@ -74,6 +68,7 @@ export class MapExtent extends HTMLElement { super(); } async connectedCallback() { + if (this.hasAttribute('data-moving')) return; if ( this.querySelector('map-link[rel=query], map-link[rel=features]') && !this.shadowRoot @@ -120,7 +115,58 @@ export class MapExtent extends HTMLElement { this._templatedLayer.changeOpacity(this.getAttribute('opacity')); } } + getLayerControlHTML() { + return this._layerControlHTML; + } + _validateDisabled() { + if (!this._templatedLayer) return; + let totalTemplateCount = this._templatedLayer._templates.length, + disabledTemplateCount = 0; + + for (let j = 0; j < this._templatedLayer._templates.length; j++) { + if (!this._templatedLayer._templates[j].layer.isVisible) { + disabledTemplateCount++; + } + } + if (totalTemplateCount === disabledTemplateCount) { + this.setAttribute('disabled', ''); + this.disabled = true; + } else { + this.removeAttribute('disabled'); + this.disabled = false; + } + return this.disabled; + } _initTemplateVars(metaExtent, projection, mapml, base, projectionMatch) { + function transcribe(element) { + var select = document.createElement('select'); + var elementAttrNames = element.getAttributeNames(); + + for (let i = 0; i < elementAttrNames.length; i++) { + select.setAttribute( + elementAttrNames[i], + element.getAttribute(elementAttrNames[i]) + ); + } + + var options = element.children; + + for (let i = 0; i < options.length; i++) { + var option = document.createElement('option'); + var optionAttrNames = options[i].getAttributeNames(); + + for (let j = 0; j < optionAttrNames.length; j++) { + option.setAttribute( + optionAttrNames[j], + options[i].getAttribute(optionAttrNames[j]) + ); + } + + option.innerHTML = options[i].innerHTML; + select.appendChild(option); + } + return select; + } var templateVars = []; // set up the URL template and associated inputs (which yield variable values when processed) var tlist = this.querySelectorAll( @@ -430,7 +476,10 @@ export class MapExtent extends HTMLElement { this.checked = input.defaultChecked; input.type = 'checkbox'; extentItemNameSpan.innerHTML = this.getAttribute('label'); - input.addEventListener('change', this._changeExtent); + const changeCheck = function () { + this.checked = !this.checked; + }; + input.addEventListener('change', changeCheck.bind(this)); extentItemNameSpan.id = 'mapml-extent-item-name-{' + L.stamp(extentItemNameSpan) + '}'; extent.setAttribute('aria-labelledby', extentItemNameSpan.id); @@ -521,7 +570,7 @@ export class MapExtent extends HTMLElement { extentEl.removeAttribute('data-moving'); extentEl.extentZIndex = zIndex; - extentEl.templatedLayer.setZIndex(zIndex); + extentEl._templatedLayer.setZIndex(zIndex); zIndex++; } controls.classList.remove('mapml-draggable'); @@ -535,29 +584,29 @@ export class MapExtent extends HTMLElement { }; return extent; } - _changeExtent(e) { - if (e.target.checked) { - this.checked = true; + _changeExtent() { + if (this.checked) { if (this.parentLayer.checked) { - this.templatedLayer = M.templatedLayer(this._templateVars, { + this._templatedLayer = M.templatedLayer(this._templateVars, { pane: this._layer._container, opacity: this._templateVars.opacity, _leafletLayer: this._layer, - crs: this._layer.crs, - extentZIndex: this.extentZIndex, + crs: this._layer._properties.crs, + extentZIndex: Array.from( + this.parentLayer.querySelectorAll('map-extent') + ).indexOf(this), extentEl: this._DOMnode || this }).addTo(this._layer._map); - this.templatedLayer.setZIndex(); + this._templatedLayer.setZIndex(); this._layer._setLayerElExtent(); } } else { - L.DomEvent.stopPropagation(e); - this.checked = false; - if (this.parentLayer.checked) this._map.removeLayer(this.templatedLayer); + if (this.parentLayer.checked) this._map.removeLayer(this._templatedLayer); this._layer._setLayerElExtent(); } } disconnectedCallback() { + if (this.hasAttribute('data-moving')) return; let extentsFieldset = this._layerControlHTML.closest( 'fieldset.mapml-layer-grouped-extents' ); @@ -570,7 +619,7 @@ export class MapExtent extends HTMLElement { } _changeOpacity(e) { if (e && e.target && e.target.value >= 0 && e.target.value <= 1.0) { - this.templatedLayer.changeOpacity(e.target.value); + this._templatedLayer.changeOpacity(e.target.value); this._templateVars.opacity = e.target.value; } } diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 2ed9f841d..6bcbed149 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -119,6 +119,7 @@ export var MapMLLayer = L.Layer.extend({ }, onAdd: function (map) { + // probably don't need it except for layer context menu usage if (this._properties && !this._validProjection(map)) { this.validProjection = false; return; @@ -138,24 +139,20 @@ export var MapMLLayer = L.Layer.extend({ map.addLayer(this._staticTileLayer); } - this._mapExtents = L.layerGroup(); - this._setLayerElExtent(); this.setZIndex(this.options.zIndex); this.getPane().appendChild(this._container); - setTimeout(() => { - map.fire('checkdisabled'); - }, 0); map.on('popupopen', this._attachSkipButtons, this); }, _validProjection: function (map) { + const mapExtents = this._layerEl.querySelectorAll('map-extent'); let noLayer = false; - if (this._properties && this._properties._mapExtents) { - for (let i = 0; i < this._properties._mapExtents.length; i++) { - if (this._properties._mapExtents[i]._templateVars) { - for (let template of this._properties._mapExtents[i]._templateVars) + if (this._properties && mapExtents.length > 0) { + for (let i = 0; i < mapExtents.length; i++) { + if (mapExtents[i]._templateVars) { + for (let template of mapExtents[i]._templateVars) if ( !template.projectionMatch && template.projection !== map.options.projection @@ -189,76 +186,59 @@ export var MapMLLayer = L.Layer.extend({ '_templatedLayer' ]; layerTypes.forEach((type) => { + const mapExtents = this._layerEl.querySelectorAll('map-extent'); if (this[type]) { if (type === '_templatedLayer') { - for (let i = 0; i < this._properties._mapExtents.length; i++) { - for ( - let j = 0; - j < this._properties._mapExtents[i]._templateVars.length; - j++ - ) { + for (let i = 0; i < mapExtents.length; i++) { + for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { let inputData = M._extractInputBounds( - this._properties._mapExtents[i]._templateVars[j] + mapExtents[i]._templateVars[j] ); - this._properties._mapExtents[i]._templateVars[ - j - ].tempExtentBounds = inputData.bounds; - this._properties._mapExtents[i]._templateVars[ - j - ].extentZoomBounds = inputData.zoomBounds; + mapExtents[i]._templateVars[j].tempExtentBounds = + inputData.bounds; + mapExtents[i]._templateVars[j].extentZoomBounds = + inputData.zoomBounds; } } - for (let i = 0; i < this._properties._mapExtents.length; i++) { - if (this._properties._mapExtents[i].checked) { - for ( - let j = 0; - j < this._properties._mapExtents[i]._templateVars.length; - j++ - ) { + for (let i = 0; i < mapExtents.length; i++) { + if (mapExtents[i].checked) { + for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { if (!bounds) { - bounds = - this._properties._mapExtents[i]._templateVars[j] - .tempExtentBounds; + bounds = mapExtents[i]._templateVars[j].tempExtentBounds; zoomMax = - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.maxZoom; + mapExtents[i]._templateVars[j].extentZoomBounds.maxZoom; zoomMin = - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.minZoom; + mapExtents[i]._templateVars[j].extentZoomBounds.minZoom; maxNativeZoom = - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.maxNativeZoom; + mapExtents[i]._templateVars[j].extentZoomBounds + .maxNativeZoom; minNativeZoom = - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.minNativeZoom; + mapExtents[i]._templateVars[j].extentZoomBounds + .minNativeZoom; } else { bounds.extend( - this._properties._mapExtents[i]._templateVars[j] - .tempExtentBounds.min + mapExtents[i]._templateVars[j].tempExtentBounds.min ); bounds.extend( - this._properties._mapExtents[i]._templateVars[j] - .tempExtentBounds.max + mapExtents[i]._templateVars[j].tempExtentBounds.max ); zoomMax = Math.max( zoomMax, - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.maxZoom + mapExtents[i]._templateVars[j].extentZoomBounds.maxZoom ); zoomMin = Math.min( zoomMin, - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.minZoom + mapExtents[i]._templateVars[j].extentZoomBounds.minZoom ); maxNativeZoom = Math.max( maxNativeZoom, - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.maxNativeZoom + mapExtents[i]._templateVars[j].extentZoomBounds + .maxNativeZoom ); minNativeZoom = Math.min( minNativeZoom, - this._properties._mapExtents[i]._templateVars[j] - .extentZoomBounds.minNativeZoom + mapExtents[i]._templateVars[j].extentZoomBounds + .minNativeZoom ); } } @@ -271,10 +251,9 @@ export var MapMLLayer = L.Layer.extend({ this._properties.zoomBounds = zoomBounds; this._properties.layerBounds = bounds; // assign each template the layer and zoom bounds - for (let i = 0; i < this._properties._mapExtents.length; i++) { - this._properties._mapExtents[i].templatedLayer.layerBounds = bounds; - this._properties._mapExtents[i].templatedLayer.zoomBounds = - zoomBounds; + for (let i = 0; i < mapExtents.length; i++) { + mapExtents[i].templatedLayer.layerBounds = bounds; + mapExtents[i].templatedLayer.zoomBounds = zoomBounds; } } else if (type === '_staticTileLayer') { if (this[type].layerBounds) { @@ -412,7 +391,6 @@ export var MapMLLayer = L.Layer.extend({ if (this._properties && this._properties._mapExtents) this._removeExtents(map); - map.fire('checkdisabled'); map.off('popupopen', this._attachSkipButtons); }, getAttribution: function () { @@ -747,19 +725,21 @@ export var MapMLLayer = L.Layer.extend({ } // if there are extents, add them to the layer control - if (this._properties && this._properties._mapExtents) { - var allHidden = true; - this._layerItemSettingsHTML = layerItemSettings; - this._propertiesGroupAnatomy = extentsFieldset; - extentsFieldset.setAttribute('aria-label', 'Sublayers'); - for (let j = 0; j < this._properties._mapExtents.length; j++) { - extentsFieldset.appendChild( - this._properties._mapExtents[j].extentAnatomy - ); - if (!this._properties._mapExtents[j].hidden) allHidden = false; + const mapExtents = this._layerEl.querySelectorAll('map-extent'); + const promises = Array.from(mapExtents).map((e) => e.whenReady()); + Promise.allSettled(promises).then(() => { + if (this._properties && mapExtents.length) { + var allHidden = true; + this._layerItemSettingsHTML = layerItemSettings; + this._propertiesGroupAnatomy = extentsFieldset; + extentsFieldset.setAttribute('aria-label', 'Sublayers'); + for (let j = 0; j < mapExtents.length; j++) { + extentsFieldset.appendChild(mapExtents[j].getLayerControlHTML()); + if (!mapExtents[j].hidden) allHidden = false; + } + if (!allHidden) layerItemSettings.appendChild(extentsFieldset); } - if (!allHidden) layerItemSettings.appendChild(extentsFieldset); - } + }); } return this._mapmlLayerItem; }, @@ -886,35 +866,6 @@ export var MapMLLayer = L.Layer.extend({ } catch (error) {} return false; } - function transcribe(element) { - var select = document.createElement('select'); - var elementAttrNames = element.getAttributeNames(); - - for (let i = 0; i < elementAttrNames.length; i++) { - select.setAttribute( - elementAttrNames[i], - element.getAttribute(elementAttrNames[i]) - ); - } - - var options = element.children; - - for (let i = 0; i < options.length; i++) { - var option = document.createElement('option'); - var optionAttrNames = options[i].getAttributeNames(); - - for (let j = 0; j < optionAttrNames.length; j++) { - option.setAttribute( - optionAttrNames[j], - options[i].getAttribute(optionAttrNames[j]) - ); - } - - option.innerHTML = options[i].innerHTML; - select.appendChild(option); - } - return select; - } function setZoomInOrOutLinks() { var zoomin = mapml.querySelector('map-link[rel=zoomin]'), zoomout = mapml.querySelector('map-link[rel=zoomout]'); From 77dc9b18f1b738859537444b8273ce86087f09a4 Mon Sep 17 00:00:00 2001 From: prushfor Date: Wed, 4 Oct 2023 17:11:49 -0400 Subject: [PATCH 011/136] WIP updating and adding and removing layer control extent HTML current problem is that this._layerControlHTML is not connected when disconnecting the map-extent from the DOM (manually), so unable to hide/unhide the extents' root fieldset line 620 map-extent.js --- src/map-extent.js | 5 ++++- src/mapml/layers/MapMLLayer.js | 35 +++++++++++----------------------- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/map-extent.js b/src/map-extent.js index 1328770ee..3b821964c 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -92,7 +92,10 @@ export class MapExtent extends HTMLElement { this._layer.getBase(), this.units === this._layer.options.mapprojection ); + // this._layerControlHTML is the fieldset for the extent in the LayerControl this._layerControlHTML = this.createLayerControlExtentHTML(this); + if (!this.hidden) + this._layer.addExtentToLayerControl(this._layerControlHTML); this._templatedLayer = M.templatedLayer(this._templateVars, { pane: this._layer._container, opacity: this._templateVars.opacity, @@ -614,7 +617,7 @@ export class MapExtent extends HTMLElement { this._layerControlHTML.remove(); // remove the map-extent from DOM if (this.parentLayer.querySelectorAll('map-extent').length === 0) { - extentsFieldset.remove(); + extentsFieldset.setAttribute('hidden', ''); } } _changeOpacity(e) { diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 6bcbed149..15323a9a2 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -724,22 +724,11 @@ export var MapMLLayer = L.Layer.extend({ layerItemSettings.appendChild(frag); } - // if there are extents, add them to the layer control - const mapExtents = this._layerEl.querySelectorAll('map-extent'); - const promises = Array.from(mapExtents).map((e) => e.whenReady()); - Promise.allSettled(promises).then(() => { - if (this._properties && mapExtents.length) { - var allHidden = true; - this._layerItemSettingsHTML = layerItemSettings; - this._propertiesGroupAnatomy = extentsFieldset; - extentsFieldset.setAttribute('aria-label', 'Sublayers'); - for (let j = 0; j < mapExtents.length; j++) { - extentsFieldset.appendChild(mapExtents[j].getLayerControlHTML()); - if (!mapExtents[j].hidden) allHidden = false; - } - if (!allHidden) layerItemSettings.appendChild(extentsFieldset); - } - }); + this._layerItemSettingsHTML = layerItemSettings; + this._propertiesGroupAnatomy = extentsFieldset; + extentsFieldset.setAttribute('aria-label', 'Sublayers'); + extentsFieldset.setAttribute('hidden', ''); + layerItemSettings.appendChild(extentsFieldset); } return this._mapmlLayerItem; }, @@ -753,6 +742,11 @@ export var MapMLLayer = L.Layer.extend({ this._href ).href; }, + addExtentToLayerControl: function (contents) { + this._propertiesGroupAnatomy.appendChild(contents); + // remove hidden attribute, if it exists + this._propertiesGroupAnatomy.removeAttribute('hidden'); + }, _initialize: function (content) { if (!this._href && !content) { return; @@ -764,14 +758,7 @@ export var MapMLLayer = L.Layer.extend({ // referred to by this._content), we should use that content. _processContent.call(this, content, this._href ? false : true); function _processContent(mapml, local) { - var base = new URL( - mapml.querySelector('map-base') - ? mapml.querySelector('map-base').getAttribute('href') - : local - ? mapml.baseURI - : layer._href, - layer._href - ).href; + var base = layer.getBase(); layer._properties = {}; // sets layer._properties.projection determineLayerProjection(); From 90087e10f5058179ae23da5c94db6d1a5f3d951a Mon Sep 17 00:00:00 2001 From: prushfor Date: Wed, 4 Oct 2023 17:17:50 -0400 Subject: [PATCH 012/136] Add comment re: current problem --- src/map-extent.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/map-extent.js b/src/map-extent.js index 3b821964c..1eadaae77 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -610,6 +610,10 @@ export class MapExtent extends HTMLElement { } disconnectedCallback() { if (this.hasAttribute('data-moving')) return; + // PROBLEM 'this' has already been disconnected, so the 'closest' function + // won't work. Need a direct reference to the extentsFieldset, which + // is available from the MapMLLayer._propertiesGroupAnatomy, which should + // have its own getter. let extentsFieldset = this._layerControlHTML.closest( 'fieldset.mapml-layer-grouped-extents' ); From 366409a7ebea207a76c15b30b6f764af23353aa7 Mon Sep 17 00:00:00 2001 From: prushfor Date: Thu, 5 Oct 2023 12:10:53 -0400 Subject: [PATCH 013/136] WIP on map-extent hidden API --- src/map-extent.js | 64 +++++++++++++++++++++++++++------- src/mapml/layers/MapMLLayer.js | 3 ++ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/map-extent.js b/src/map-extent.js index 1eadaae77..19b52d0a4 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -1,6 +1,6 @@ export class MapExtent extends HTMLElement { static get observedAttributes() { - return ['units', 'checked', 'label', 'opacity']; + return ['units', 'checked', 'label', 'opacity', 'hidden']; } get units() { return this.getAttribute('units'); @@ -39,6 +39,17 @@ export class MapExtent extends HTMLElement { if (+val > 1 || +val < 0) return; this.setAttribute('opacity', val); } + get hidden() { + return this.hasAttribute('hidden'); + } + + set hidden(val) { + if (val) { + this.setAttribute('hidden', ''); + } else { + this.removeAttribute('hidden'); + } + } attributeChangedCallback(name, oldValue, newValue) { switch (name) { case 'units': @@ -61,6 +72,36 @@ export class MapExtent extends HTMLElement { // handle side effects } break; + case 'hidden': + if (oldValue !== newValue) { + this.whenReady().then(() => { + let extentsRootFieldset = this._layer.getLayerUserControlsHTML(); + let position = Array.from( + this.parentLayer.querySelectorAll('map-extent:not([hidden])') + ).indexOf(this); + if (newValue !== null) { + // remove from layer control (hide from user) + this._layerControlHTML.remove(); + } else { + // insert the extent fieldset into the layer control container in + // the calculated position + if (position === 0) { + extentsRootFieldset.insertAdjacentElement( + 'afterbegin', + this._layerControlHTML + ); + } else if (position > 0) { + this.querySelectorAll('map-extent:not([hidden])')[ + position - 1 + ]._layerControlHTML.insertAdjacentElement( + 'afterend', + this._layerControlHTML + ); + } + } + }); + } + break; } } constructor() { @@ -610,19 +651,18 @@ export class MapExtent extends HTMLElement { } disconnectedCallback() { if (this.hasAttribute('data-moving')) return; - // PROBLEM 'this' has already been disconnected, so the 'closest' function - // won't work. Need a direct reference to the extentsFieldset, which - // is available from the MapMLLayer._propertiesGroupAnatomy, which should - // have its own getter. - let extentsFieldset = this._layerControlHTML.closest( - 'fieldset.mapml-layer-grouped-extents' - ); - // remove layer control for map-extent from layer control DOM - this._layerControlHTML.remove(); - // remove the map-extent from DOM - if (this.parentLayer.querySelectorAll('map-extent').length === 0) { + let extentsFieldset = this._layer.getLayerControlExtentContainer(); + if ( + this.parentLayer.querySelectorAll('map-extent:not([hidden])').length === 0 + ) { extentsFieldset.setAttribute('hidden', ''); } + // remove layer control for map-extent from layer control DOM + this._layerControlHTML.remove(); + + this._map.removeLayer(this._templatedLayer); + delete this._templatedLayer; + this._layer._setLayerElExtent(); } _changeOpacity(e) { if (e && e.target && e.target.value >= 0 && e.target.value <= 1.0) { diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 15323a9a2..71ebdfec9 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -732,6 +732,9 @@ export var MapMLLayer = L.Layer.extend({ } return this._mapmlLayerItem; }, + getLayerControlExtentContainer: function () { + return this._propertiesGroupAnatomy; + }, getBase: function () { return new URL( this._content.querySelector('map-base') From bd183af25e7b0bce27c676934c85dc9ac48a2db5 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Thu, 5 Oct 2023 13:58:03 -0400 Subject: [PATCH 014/136] Initial working hidden API for map-extent --- index.html | 72 ++++------------------------------------------- src/map-extent.js | 26 +++++++++++------ 2 files changed, 23 insertions(+), 75 deletions(-) diff --git a/index.html b/index.html index e5c9ac24d..a4378d80b 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,7 @@ /* Responsive map. */ max-width: 100%; + /* Full viewport. */ width: 100%; height: 100%; @@ -71,92 +72,31 @@ - - - - - + + - + - + - - - - + diff --git a/src/map-extent.js b/src/map-extent.js index 19b52d0a4..766840f44 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -75,7 +75,8 @@ export class MapExtent extends HTMLElement { case 'hidden': if (oldValue !== newValue) { this.whenReady().then(() => { - let extentsRootFieldset = this._layer.getLayerUserControlsHTML(); + let extentsRootFieldset = + this._layer.getLayerControlExtentContainer(); let position = Array.from( this.parentLayer.querySelectorAll('map-extent:not([hidden])') ).indexOf(this); @@ -91,14 +92,15 @@ export class MapExtent extends HTMLElement { this._layerControlHTML ); } else if (position > 0) { - this.querySelectorAll('map-extent:not([hidden])')[ - position - 1 - ]._layerControlHTML.insertAdjacentElement( - 'afterend', - this._layerControlHTML - ); + this.parentLayer + .querySelectorAll('map-extent:not([hidden])') + [position - 1]._layerControlHTML.insertAdjacentElement( + 'afterend', + this._layerControlHTML + ); } } + this._validateLayerControlContainerHidden(); }); } break; @@ -137,6 +139,7 @@ export class MapExtent extends HTMLElement { this._layerControlHTML = this.createLayerControlExtentHTML(this); if (!this.hidden) this._layer.addExtentToLayerControl(this._layerControlHTML); + this._validateLayerControlContainerHidden(); this._templatedLayer = M.templatedLayer(this._templateVars, { pane: this._layer._container, opacity: this._templateVars.opacity, @@ -649,14 +652,19 @@ export class MapExtent extends HTMLElement { this._layer._setLayerElExtent(); } } - disconnectedCallback() { - if (this.hasAttribute('data-moving')) return; + _validateLayerControlContainerHidden() { let extentsFieldset = this._layer.getLayerControlExtentContainer(); if ( this.parentLayer.querySelectorAll('map-extent:not([hidden])').length === 0 ) { extentsFieldset.setAttribute('hidden', ''); + } else { + extentsFieldset.removeAttribute('hidden'); } + } + disconnectedCallback() { + if (this.hasAttribute('data-moving')) return; + this._validateLayerControlContainerHidden(); // remove layer control for map-extent from layer control DOM this._layerControlHTML.remove(); From f6373dffa71054436693b7b79fdd1c937330ef16 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Thu, 5 Oct 2023 17:13:04 -0400 Subject: [PATCH 015/136] WIP on map-extent Get checkbox working via API map-extent.checked. Change TemplatedLayer and related child layer types. --- index.html | 44 ++++++++++------------ src/map-extent.js | 25 ++++++------ src/mapml/layers/TemplatedFeaturesLayer.js | 4 +- src/mapml/layers/TemplatedImageLayer.js | 5 ++- src/mapml/layers/TemplatedLayer.js | 4 +- src/mapml/layers/TemplatedTileLayer.js | 6 ++- 6 files changed, 45 insertions(+), 43 deletions(-) diff --git a/index.html b/index.html index a4378d80b..9ead41724 100644 --- a/index.html +++ b/index.html @@ -73,30 +73,24 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + All cuisines + African + Asian + Cajun + Indian + Italian + Mexican + + + + + diff --git a/src/map-extent.js b/src/map-extent.js index 766840f44..ec51b8c52 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -526,6 +526,8 @@ export class MapExtent extends HTMLElement { const changeCheck = function () { this.checked = !this.checked; }; + // save for later access by API + this._layerControlCheckbox = input; input.addEventListener('change', changeCheck.bind(this)); extentItemNameSpan.id = 'mapml-extent-item-name-{' + L.stamp(extentItemNameSpan) + '}'; @@ -634,23 +636,20 @@ export class MapExtent extends HTMLElement { _changeExtent() { if (this.checked) { if (this.parentLayer.checked) { - this._templatedLayer = M.templatedLayer(this._templateVars, { - pane: this._layer._container, - opacity: this._templateVars.opacity, - _leafletLayer: this._layer, - crs: this._layer._properties.crs, - extentZIndex: Array.from( - this.parentLayer.querySelectorAll('map-extent') - ).indexOf(this), - extentEl: this._DOMnode || this - }).addTo(this._layer._map); - this._templatedLayer.setZIndex(); - this._layer._setLayerElExtent(); + this._templatedLayer.addTo(this._layer._map); + this._templatedLayer.setZIndex( + Array.from(this.parentLayer.querySelectorAll('map-extent')).indexOf( + this + ) + ); } } else { if (this.parentLayer.checked) this._map.removeLayer(this._templatedLayer); - this._layer._setLayerElExtent(); } + // change the checkbox in the layer control to match map-extent.checked + // doesn't trigger the event handler because it's not user-caused AFAICT + this._layerControlCheckbox = this.checked; + this._layer._setLayerElExtent(); } _validateLayerControlContainerHidden() { let extentsFieldset = this._layer.getLayerControlExtentContainer(); diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index ab4e74268..061055ba1 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -7,7 +7,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ this.isVisible = true; this._template = template; this._extentEl = options.extentEl; - this._container = L.DomUtil.create('div', 'leaflet-layer', options.pane); + this._container = L.DomUtil.create('div', 'leaflet-layer'); L.extend(options, this.zoomBounds); L.DomUtil.addClass(this._container, 'mapml-features-container'); delete options.opacity; @@ -23,6 +23,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ return events; }, onAdd: function () { + this.options.pane.appendChild(this._container); this._map._addZoomLimit(this); var opacity = this.options.opacity || 1, container = this._container, @@ -221,6 +222,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ } }, onRemove: function () { + L.DomUtil.remove(this._container); this._map.removeLayer(this._features); }, _getfeaturesUrl: function (zoom, bounds) { diff --git a/src/mapml/layers/TemplatedImageLayer.js b/src/mapml/layers/TemplatedImageLayer.js index e98747160..734d53cd2 100644 --- a/src/mapml/layers/TemplatedImageLayer.js +++ b/src/mapml/layers/TemplatedImageLayer.js @@ -1,7 +1,7 @@ export var TemplatedImageLayer = L.Layer.extend({ initialize: function (template, options) { this._template = template; - this._container = L.DomUtil.create('div', 'leaflet-layer', options.pane); + this._container = L.DomUtil.create('div', 'leaflet-layer'); L.DomUtil.addClass(this._container, 'mapml-image-container'); let inputData = M._extractInputBounds(template); this.zoomBounds = inputData.zoomBounds; @@ -22,6 +22,7 @@ export var TemplatedImageLayer = L.Layer.extend({ return events; }, onAdd: function () { + this.options.pane.appendChild(this._container); this._map._addZoomLimit(this); //used to set the zoom limit of the map this.setZIndex(this.options.zIndex); this._onAdd(); @@ -159,9 +160,9 @@ export var TemplatedImageLayer = L.Layer.extend({ } }, onRemove: function (map) { + L.DomUtil.remove(this._container); this._clearLayer(); map._removeZoomLimit(this); - this._container = null; }, getImageUrl: function (pixelBounds, zoom) { var obj = {}; diff --git a/src/mapml/layers/TemplatedLayer.js b/src/mapml/layers/TemplatedLayer.js index 23923718f..e2c17e14f 100644 --- a/src/mapml/layers/TemplatedLayer.js +++ b/src/mapml/layers/TemplatedLayer.js @@ -2,7 +2,7 @@ export var TemplatedLayer = L.Layer.extend({ initialize: function (templates, options) { this._templates = templates; L.setOptions(this, options); - this._container = L.DomUtil.create('div', 'leaflet-layer', options.pane); + this._container = L.DomUtil.create('div', 'leaflet-layer'); this._container.style.opacity = this.options.opacity; L.DomUtil.addClass(this._container, 'mapml-templatedlayer-container'); @@ -257,6 +257,8 @@ export var TemplatedLayer = L.Layer.extend({ } }, onAdd: function (map) { + // add to this.options.pane + this.options.pane.appendChild(this._container); for (var i = 0; i < this._templates.length; i++) { if (this._templates[i].rel !== 'query') { map.addLayer(this._templates[i].layer); diff --git a/src/mapml/layers/TemplatedTileLayer.js b/src/mapml/layers/TemplatedTileLayer.js index 6daea770d..98e3d748c 100644 --- a/src/mapml/layers/TemplatedTileLayer.js +++ b/src/mapml/layers/TemplatedTileLayer.js @@ -27,14 +27,18 @@ export var TemplatedTileLayer = L.TileLayer.extend({ L.TileLayer.prototype.initialize.call( this, template.template, - L.extend(options, { pane: this._container }) + L.extend(options, { pane: this.options.pane }) ); }, onAdd: function () { + this.options.pane.appendChild(this._container); this._map._addZoomLimit(this); L.TileLayer.prototype.onAdd.call(this, this._map); this._handleMoveEnd(); }, + onRemove: function () { + L.DomUtil.remove(this._container); + }, getEvents: function () { let events = L.TileLayer.prototype.getEvents.call(this, this._map); From 5ffd8257e3c6f155713effb5c24abc2ff59f21a4 Mon Sep 17 00:00:00 2001 From: prushfor Date: Fri, 6 Oct 2023 17:05:29 -0400 Subject: [PATCH 016/136] Fix TemplatedFeaturesLayer checking/unchecking behaviour. Prevent excessive disconnection / reconnection of layers and map-extents when clicking on layer control entries, by upping the 'moving' px threshold and calculating if the entry has changed position through the dragging event (do disconnect/reconnect) or not (do not do dis/re). In map-feature, look for data-moving on layer or map-extent element, regardless of whether it's a local or remote layer (light dom vs shadow dom). --- src/map-extent.js | 66 ++++++++++------- src/map-feature.js | 82 ++++++++++++---------- src/mapml/layers/MapMLLayer.js | 44 +++++++----- src/mapml/layers/TemplatedFeaturesLayer.js | 11 +-- 4 files changed, 119 insertions(+), 84 deletions(-) diff --git a/src/map-extent.js b/src/map-extent.js index ec51b8c52..3089d8d59 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -111,21 +111,26 @@ export class MapExtent extends HTMLElement { super(); } async connectedCallback() { - if (this.hasAttribute('data-moving')) return; + // this.parentNode.host returns the layer- element when parentNode is + // the shadow root + this.parentLayer = + this.parentNode.nodeName.toUpperCase() === 'LAYER-' + ? this.parentNode + : this.parentNode.host; + if ( + this.hasAttribute('data-moving') || + this.parentLayer.hasAttribute('data-moving') + ) + return; if ( this.querySelector('map-link[rel=query], map-link[rel=features]') && !this.shadowRoot ) { this.attachShadow({ mode: 'open' }); } - this.parentLayer = - this.parentNode.nodeName.toUpperCase() === 'LAYER-' - ? this.parentNode - : this.parentNode.host; await this.parentLayer.whenReady(); this._layer = this.parentLayer._layer; - let viewer = this.closest('mapml-viewer') || this.closest('map'); - this._map = viewer._map; + this._map = this._layer._map; // this code comes from MapMLLayer._initialize.processExtents this._templateVars = this._initTemplateVars( // mapml is the layer- element OR the mapml- document root @@ -547,7 +552,10 @@ export class MapExtent extends HTMLElement { let control = extent, controls = extent.parentNode, moving = false, - yPos = downEvent.clientY; + yPos = downEvent.clientY, + originalPosition = Array.from( + extent.parentElement.querySelectorAll('fieldset') + ).indexOf(extent); document.body.ontouchmove = document.body.onmousemove = (moveEvent) => { moveEvent.preventDefault(); @@ -556,7 +564,7 @@ export class MapExtent extends HTMLElement { // Fixes flickering by only moving element when there is enough space let offset = moveEvent.clientY - yPos; - moving = Math.abs(offset) > 5 || moving; + moving = Math.abs(offset) > 15 || moving; if ( (controls && !moving) || (controls && controls.childElementCount <= 1) || @@ -605,22 +613,27 @@ export class MapExtent extends HTMLElement { }; document.body.ontouchend = document.body.onmouseup = () => { + let newPosition = Array.from( + extent.parentElement.querySelectorAll('fieldset') + ).indexOf(extent); control.setAttribute('aria-grabbed', 'false'); control.removeAttribute('aria-dropeffect'); control.style.pointerEvents = null; control.style.transform = null; - let controlsElems = controls.children, - zIndex = 0; - for (let c of controlsElems) { - let extentEl = c.querySelector('span').extent; - - extentEl.setAttribute('data-moving', ''); - layerEl.insertAdjacentElement('beforeend', extentEl); - extentEl.removeAttribute('data-moving'); - - extentEl.extentZIndex = zIndex; - extentEl._templatedLayer.setZIndex(zIndex); - zIndex++; + if (originalPosition !== newPosition) { + let controlsElems = controls.children, + zIndex = 0; + for (let c of controlsElems) { + let extentEl = c.querySelector('span').extent; + + extentEl.setAttribute('data-moving', ''); + layerEl.insertAdjacentElement('beforeend', extentEl); + extentEl.removeAttribute('data-moving'); + + extentEl.extentZIndex = zIndex; + extentEl._templatedLayer.setZIndex(zIndex); + zIndex++; + } } controls.classList.remove('mapml-draggable'); document.body.ontouchmove = @@ -648,13 +661,14 @@ export class MapExtent extends HTMLElement { } // change the checkbox in the layer control to match map-extent.checked // doesn't trigger the event handler because it's not user-caused AFAICT - this._layerControlCheckbox = this.checked; + this._layerControlCheckbox.checked = this.checked; this._layer._setLayerElExtent(); } _validateLayerControlContainerHidden() { let extentsFieldset = this._layer.getLayerControlExtentContainer(); + let nodeToSearch = this.parentLayer.shadowRoot || this.parentLayer; if ( - this.parentLayer.querySelectorAll('map-extent:not([hidden])').length === 0 + nodeToSearch.querySelectorAll('map-extent:not([hidden])').length === 0 ) { extentsFieldset.setAttribute('hidden', ''); } else { @@ -662,7 +676,11 @@ export class MapExtent extends HTMLElement { } } disconnectedCallback() { - if (this.hasAttribute('data-moving')) return; + if ( + this.hasAttribute('data-moving') || + this.parentLayer.hasAttribute('data-moving') + ) + return; this._validateLayerControlContainerHidden(); // remove layer control for map-extent from layer control DOM this._layerControlHTML.remove(); diff --git a/src/map-feature.js b/src/map-feature.js index 032e790bc..5745c977e 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -100,38 +100,51 @@ export class MapFeature extends HTMLElement { } connectedCallback() { - // if mapFeature element is not connected to layer- or layer-'s shadowroot, - // or the parent layer- element has a "data-moving" attribute - if ( - (this.parentNode.nodeType !== document.DOCUMENT_FRAGMENT_NODE && - this.parentNode.nodeName.toLowerCase() !== 'layer-') || - (this.parentNode.nodeType === document.DOCUMENT_FRAGMENT_NODE && - this.parentNode.host.hasAttribute('data-moving')) || - (this.parentNode.nodeName.toLowerCase() === 'layer-' && - this.parentNode.hasAttribute('data-moving')) - ) { - return; - } - // set up the map-feature object properties - this._addFeature(); - // use observer to monitor the changes in mapFeature's subtree - // (i.e. map-properties, map-featurecaption, map-coordinates) - this._observer = new MutationObserver((mutationList) => { - for (let mutation of mutationList) { - // the attributes changes of element should be handled by attributeChangedCallback() - if (mutation.type === 'attributes' && mutation.target === this) { - return; - } - // re-render feature if there is any observed change - this._reRender(); + this._parentEl = + this.parentNode.nodeName.toUpperCase() === 'LAYER-' || + this.parentNode.nodeName.toUpperCase() === 'MAP-EXTENT' + ? this.parentNode + : this.parentNode.host; + this._parentEl.whenReady().then(() => { + this._layer = this._parentEl._layer; + if ( + this._layer._layerEl.hasAttribute('data-moving') || + this._parentEl.hasAttribute('data-moving') + ) + return; + // if mapFeature element is not connected to layer- or layer-'s shadowroot, + // or the parent layer- element has a "data-moving" attribute + if ( + (this.parentNode.nodeType !== document.DOCUMENT_FRAGMENT_NODE && + this.parentNode.nodeName.toLowerCase() !== 'layer-') || + (this.parentNode.nodeType === document.DOCUMENT_FRAGMENT_NODE && + this.parentNode.host.hasAttribute('data-moving')) || + (this.parentNode.nodeName.toLowerCase() === 'layer-' && + this.parentNode.hasAttribute('data-moving')) + ) { + return; } - }); - this._observer.observe(this, { - childList: true, - subtree: true, - attributes: true, - attributeOldValue: true, - characterData: true + // set up the map-feature object properties + this._addFeature(); + // use observer to monitor the changes in mapFeature's subtree + // (i.e. map-properties, map-featurecaption, map-coordinates) + this._observer = new MutationObserver((mutationList) => { + for (let mutation of mutationList) { + // the attributes changes of element should be handled by attributeChangedCallback() + if (mutation.type === 'attributes' && mutation.target === this) { + return; + } + // re-render feature if there is any observed change + this._reRender(); + } + }); + this._observer.observe(this, { + childList: true, + subtree: true, + attributes: true, + attributeOldValue: true, + characterData: true + }); }); } @@ -196,18 +209,11 @@ export class MapFeature extends HTMLElement { } _addFeature() { - this._parentEl = - this.parentNode.nodeName.toUpperCase() === 'LAYER-' || - this.parentNode.nodeName.toUpperCase() === 'MAP-EXTENT' - ? this.parentNode - : this.parentNode.host; - this._parentEl.whenReady().then(() => { let parentLayer = this._parentEl.nodeName.toUpperCase() === 'LAYER-' ? this._parentEl : this._parentEl.parentElement || this._parentEl.parentNode.host; - this._layer = parentLayer._layer; this._map = this._layer._map; let mapmlvectors = this._layer._mapmlvectors; // "synchronize" the event handlers between map-feature and diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 71ebdfec9..2915f0f15 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -583,7 +583,10 @@ export var MapMLLayer = L.Layer.extend({ let control = fieldset, controls = fieldset.parentNode, moving = false, - yPos = downEvent.clientY; + yPos = downEvent.clientY, + originalPosition = Array.from( + controls.querySelectorAll('fieldset') + ).indexOf(fieldset); document.body.ontouchmove = document.body.onmousemove = ( moveEvent @@ -596,7 +599,7 @@ export var MapMLLayer = L.Layer.extend({ // Fixes flickering by only moving element when there is enough space let offset = moveEvent.clientY - yPos; - moving = Math.abs(offset) > 5 || moving; + moving = Math.abs(offset) > 15 || moving; if ( (controls && !moving) || (controls && controls.childElementCount <= 1) || @@ -645,27 +648,32 @@ export var MapMLLayer = L.Layer.extend({ }; document.body.ontouchend = document.body.onmouseup = () => { + let newPosition = Array.from( + controls.querySelectorAll('fieldset') + ).indexOf(fieldset); control.setAttribute('aria-grabbed', 'false'); control.removeAttribute('aria-dropeffect'); control.style.pointerEvents = null; control.style.transform = null; - let controlsElems = controls.children, - zIndex = 1; - // re-order layer elements DOM order - for (let c of controlsElems) { - let layerEl = c.querySelector('span').layer._layerEl; - layerEl.setAttribute('data-moving', ''); - mapEl.insertAdjacentElement('beforeend', layerEl); - layerEl.removeAttribute('data-moving'); - } - // update zIndex of all layer- elements - let layers = mapEl.querySelectorAll('layer-'); - for (let i = 0; i < layers.length; i++) { - let layer = layers[i]._layer; - if (layer.options.zIndex !== zIndex) { - layer.setZIndex(zIndex); + if (originalPosition !== newPosition) { + let controlsElems = controls.children, + zIndex = 1; + // re-order layer elements DOM order + for (let c of controlsElems) { + let layerEl = c.querySelector('span').layer._layerEl; + layerEl.setAttribute('data-moving', ''); + mapEl.insertAdjacentElement('beforeend', layerEl); + layerEl.removeAttribute('data-moving'); + } + // update zIndex of all layer- elements + let layers = mapEl.querySelectorAll('layer-'); + for (let i = 0; i < layers.length; i++) { + let layer = layers[i]._layer; + if (layer.options.zIndex !== zIndex) { + layer.setZIndex(zIndex); + } + zIndex++; } - zIndex++; } controls.classList.remove('mapml-draggable'); document.body.ontouchmove = diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index 061055ba1..2263bdefb 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -23,6 +23,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ return events; }, onAdd: function () { + // this causes the layer (this._features) to actually render... this.options.pane.appendChild(this._container); this._map._addZoomLimit(this); var opacity = this.options.opacity || 1, @@ -50,10 +51,16 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ geometry.bindPopup(c, { autoClose: false, minWidth: 108 }); } }); + } else { + // if this._features exists add the layer back + this._map.addLayer(this._features); } map.fire('moveend'); // TODO: replace with moveend handler for layer and not entire map }, + onRemove: function () { + this._map.removeLayer(this._features); + }, redraw: function () { this._onMoveEnd(); }, @@ -221,10 +228,6 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ this._container.style.zIndex = this.options.zIndex; } }, - onRemove: function () { - L.DomUtil.remove(this._container); - this._map.removeLayer(this._features); - }, _getfeaturesUrl: function (zoom, bounds) { if (zoom === undefined) zoom = this._map.getZoom(); if (bounds === undefined) bounds = this._map.getPixelBounds(); From 5d3901a9b6c4aaa0cf543880caa25a8cc59c3657 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 10 Oct 2023 15:57:00 -0400 Subject: [PATCH 017/136] Set 'Sub-layer' as default label attribute, add to options messages Synchronize opacity attribute, property and slider in layer control (note: works slightly differently than layer.opacity, which doesn't "sprout" the opacity attribute when set via layer control) Fix bug when setting map-extent.hidden in shadow dom of layer element --- src/map-extent.js | 49 ++++++++++++++++++++------------------------ src/mapml/options.js | 1 + 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/map-extent.js b/src/map-extent.js index 3089d8d59..ed06adf1e 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -24,7 +24,9 @@ export class MapExtent extends HTMLElement { } } get label() { - return this.hasAttribute('label') ? this.getAttribute('label') : ''; + return this.hasAttribute('label') + ? this.getAttribute('label') + : M.options.locale.dfExtent; } set label(val) { if (val) { @@ -32,7 +34,7 @@ export class MapExtent extends HTMLElement { } } get opacity() { - return this._opacity; + return this.getAttribute('opacity') ? this.getAttribute('opacity') : 1.0; } set opacity(val) { @@ -69,7 +71,9 @@ export class MapExtent extends HTMLElement { break; case 'opacity': if (oldValue !== newValue) { - // handle side effects + this.whenReady().then(() => { + this._changeOpacity(); + }); } break; case 'hidden': @@ -78,7 +82,7 @@ export class MapExtent extends HTMLElement { let extentsRootFieldset = this._layer.getLayerControlExtentContainer(); let position = Array.from( - this.parentLayer.querySelectorAll('map-extent:not([hidden])') + this.parentNode.querySelectorAll('map-extent:not([hidden])') ).indexOf(this); if (newValue !== null) { // remove from layer control (hide from user) @@ -92,7 +96,7 @@ export class MapExtent extends HTMLElement { this._layerControlHTML ); } else if (position > 0) { - this.parentLayer + this.parentNode .querySelectorAll('map-extent:not([hidden])') [position - 1]._layerControlHTML.insertAdjacentElement( 'afterend', @@ -163,9 +167,6 @@ export class MapExtent extends HTMLElement { this._layer._properties._queries = this._layer._properties._queries.concat(this._templatedLayer._queries); } - if (this.hasAttribute('opacity')) { - this._templatedLayer.changeOpacity(this.getAttribute('opacity')); - } } getLayerControlHTML() { return this._layerControlHTML; @@ -438,11 +439,6 @@ export class MapExtent extends HTMLElement { opacity = L.DomUtil.create('input', '', opacityControl); extentSettings.hidden = true; extent.setAttribute('aria-grabbed', 'false'); - if (!this.hasAttribute('label')) { - // if a label attribute is not present, set it to hidden in layer control - extent.setAttribute('hidden', ''); - this.hidden = true; - } // append the svg paths svgExtentControlIcon.setAttribute('viewBox', '0 0 24 24'); @@ -511,23 +507,21 @@ export class MapExtent extends HTMLElement { 'aria-labelledby', 'mapml-layer-item-opacity-' + L.stamp(extentOpacitySummary) ); - let opacityValue = this.hasAttribute('opacity') - ? this.getAttribute('opacity') - : '1.0'; - this._templateVars.opacity = opacityValue; - opacity.setAttribute('value', opacityValue); - opacity.value = opacityValue; - opacity.addEventListener('change', this._changeOpacity); + const changeOpacity = function () { + this.opacity = opacity.value; + }; + opacity.setAttribute('value', this.opacity); + opacity.value = this.opacity; + opacity.addEventListener('change', changeOpacity.bind(this)); + this._opacitySlider = opacity; var extentItemNameSpan = L.DomUtil.create( 'span', 'mapml-layer-item-name', extentLabel ); - input.defaultChecked = this ? true : false; - this.checked = input.defaultChecked; input.type = 'checkbox'; - extentItemNameSpan.innerHTML = this.getAttribute('label'); + extentItemNameSpan.innerHTML = this.label; const changeCheck = function () { this.checked = !this.checked; }; @@ -689,10 +683,11 @@ export class MapExtent extends HTMLElement { delete this._templatedLayer; this._layer._setLayerElExtent(); } - _changeOpacity(e) { - if (e && e.target && e.target.value >= 0 && e.target.value <= 1.0) { - this._templatedLayer.changeOpacity(e.target.value); - this._templateVars.opacity = e.target.value; + _changeOpacity() { + if (this.opacity >= 0 && this.opacity <= 1.0) { + this._templatedLayer.changeOpacity(this.opacity); + this._templateVars.opacity = this.opacity; + this._opacitySlider.value = this.opacity; } } diff --git a/src/mapml/options.js b/src/mapml/options.js index 008ff476f..78a17981d 100644 --- a/src/mapml/options.js +++ b/src/mapml/options.js @@ -52,6 +52,7 @@ export var Options = { kbdPrevFeature: 'Previous feature', kbdNextFeature: 'Next feature', dfLayer: 'Layer', + dfExtent: 'Sub-layer', popupZoom: 'Zoom to here', dfPastedLayer: 'Pasted layer', fIndexNoFeatures: 'No features found' From 83d64fdd9164a9cc2e71f3754e1331bc28503059 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 10 Oct 2023 17:07:01 -0400 Subject: [PATCH 018/136] Move/ refactor MapMLLayer._setLayerElExtent into layer.js layer-.extent /layer-._calculateExtent(). To Do: make it efficient / memoize extent value until extent actually changes. --- index.html | 13 ++- src/layer.js | 149 +++++++++++++++++++++++++++++++-- src/map-extent.js | 2 - src/map-feature.js | 4 - src/mapml/layers/MapMLLayer.js | 145 +------------------------------- src/mapml/utils/Util.js | 5 -- 6 files changed, 156 insertions(+), 162 deletions(-) diff --git a/index.html b/index.html index 9ead41724..ff693ffa3 100644 --- a/index.html +++ b/index.html @@ -74,11 +74,18 @@ - + + + + + + + + - + All cuisines African @@ -88,7 +95,7 @@ Italian Mexican - + diff --git a/src/layer.js b/src/layer.js index 2f5889082..809e322b8 100644 --- a/src/layer.js +++ b/src/layer.js @@ -57,6 +57,11 @@ export class MapLayer extends HTMLElement { this.setAttribute('opacity', val); } + get extent() { + // calculate the bounds of all content, return it. + return this._calculateExtent(); + } + constructor() { // Always call super first in constructor super(); @@ -405,19 +410,153 @@ export class MapLayer extends HTMLElement { this.checked = this._layer._map.hasLayer(this._layer); } } + //sets the elements .bounds property + _calculateExtent() { + let bounds, + zoomMax, + zoomMin, + maxNativeZoom, + minNativeZoom, + zoomBounds = { + minZoom: 0, + maxZoom: 0, + maxNativeZoom: 0, + minNativeZoom: 0 + }; + let layerTypes = [ + '_staticTileLayer', + '_imageLayer', + '_mapmlvectors', + '_templatedLayer' + ]; + layerTypes.forEach((type) => { + const mapExtents = this.querySelectorAll('map-extent').length + ? this.querySelectorAll('map-extent') + : this.shadowRoot.querySelectorAll('map-extent'); + if (type === '_templatedLayer' && mapExtents.length) { + for (let i = 0; i < mapExtents.length; i++) { + for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { + let inputData = M._extractInputBounds( + mapExtents[i]._templateVars[j] + ); + mapExtents[i]._templateVars[j].tempExtentBounds = inputData.bounds; + mapExtents[i]._templateVars[j].extentZoomBounds = + inputData.zoomBounds; + } + } + for (let i = 0; i < mapExtents.length; i++) { + if (mapExtents[i].checked) { + for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { + if (!bounds) { + bounds = mapExtents[i]._templateVars[j].tempExtentBounds; + zoomMax = + mapExtents[i]._templateVars[j].extentZoomBounds.maxZoom; + zoomMin = + mapExtents[i]._templateVars[j].extentZoomBounds.minZoom; + maxNativeZoom = + mapExtents[i]._templateVars[j].extentZoomBounds.maxNativeZoom; + minNativeZoom = + mapExtents[i]._templateVars[j].extentZoomBounds.minNativeZoom; + } else { + bounds.extend( + mapExtents[i]._templateVars[j].tempExtentBounds.min + ); + bounds.extend( + mapExtents[i]._templateVars[j].tempExtentBounds.max + ); + zoomMax = Math.max( + zoomMax, + mapExtents[i]._templateVars[j].extentZoomBounds.maxZoom + ); + zoomMin = Math.min( + zoomMin, + mapExtents[i]._templateVars[j].extentZoomBounds.minZoom + ); + maxNativeZoom = Math.max( + maxNativeZoom, + mapExtents[i]._templateVars[j].extentZoomBounds.maxNativeZoom + ); + minNativeZoom = Math.min( + minNativeZoom, + mapExtents[i]._templateVars[j].extentZoomBounds.minNativeZoom + ); + } + } + } + } + zoomBounds.minZoom = zoomMin; + zoomBounds.maxZoom = zoomMax; + zoomBounds.minNativeZoom = minNativeZoom; + zoomBounds.maxNativeZoom = maxNativeZoom; + this._layer._properties.zoomBounds = zoomBounds; + this._layer._properties.layerBounds = bounds; + // assign each template the layer and zoom bounds + for (let i = 0; i < mapExtents.length; i++) { + mapExtents[i]._templatedLayer.layerBounds = bounds; + mapExtents[i]._templatedLayer.zoomBounds = zoomBounds; + } + } else if (type === '_staticTileLayer' && this._layer._staticTileLayer) { + if (this._layer[type].layerBounds) { + if (!bounds) { + bounds = this._layer[type].layerBounds; + zoomBounds = this._layer[type].zoomBounds; + } else { + bounds.extend(this._layer[type].layerBounds.min); + bounds.extend(this._layer[type].layerBounds.max); + } + } + } else if (type === '_imageLayer' && this._layer._imageLayer) { + if (this._layer[type].layerBounds) { + if (!bounds) { + bounds = this._layer[type].layerBounds; + zoomBounds = this._layer[type].zoomBounds; + } else { + bounds.extend(this._layer[type].layerBounds.min); + bounds.extend(this._layer[type].layerBounds.max); + } + } + } else if ( + // only process extent if mapmlvectors is not empty + type === '_mapmlvectors' && + this._layer._mapmlvectors && + Object.keys(this._layer[type]._layers).length !== 0 + ) { + if (this._layer[type].layerBounds) { + if (!bounds) { + bounds = this._layer[type].layerBounds; + zoomBounds = this._layer[type].zoomBounds; + } else { + bounds.extend(this._layer[type].layerBounds.min); + bounds.extend(this._layer[type].layerBounds.max); + } + } + } + }); + if (bounds) { + //assigns the formatted extent object to .extent and spreads the zoom ranges to .extent also + return Object.assign( + M._convertAndFormatPCRS( + bounds, + this._layer._properties.crs, + this._layer._properties.projection + ), + { zoom: zoomBounds } + ); + } + } zoomTo() { - if (!this.extent) return; let map = this.parentElement._map, - tL = this.extent.topLeft.pcrs, - bR = this.extent.bottomRight.pcrs, + extent = this.extent, + tL = extent.topLeft.pcrs, + bR = extent.bottomRight.pcrs, layerBounds = L.bounds( L.point(tL.horizontal, tL.vertical), L.point(bR.horizontal, bR.vertical) ), center = map.options.crs.unproject(layerBounds.getCenter(true)); - let maxZoom = this.extent.zoom.maxZoom, - minZoom = this.extent.zoom.minZoom; + let maxZoom = extent.zoom.maxZoom, + minZoom = extent.zoom.minZoom; map.setView(center, M.getMaxZoom(layerBounds, map, minZoom, maxZoom), { animate: false }); diff --git a/src/map-extent.js b/src/map-extent.js index ed06adf1e..c07530c60 100644 --- a/src/map-extent.js +++ b/src/map-extent.js @@ -656,7 +656,6 @@ export class MapExtent extends HTMLElement { // change the checkbox in the layer control to match map-extent.checked // doesn't trigger the event handler because it's not user-caused AFAICT this._layerControlCheckbox.checked = this.checked; - this._layer._setLayerElExtent(); } _validateLayerControlContainerHidden() { let extentsFieldset = this._layer.getLayerControlExtentContainer(); @@ -681,7 +680,6 @@ export class MapExtent extends HTMLElement { this._map.removeLayer(this._templatedLayer); delete this._templatedLayer; - this._layer._setLayerElExtent(); } _changeOpacity() { if (this.opacity >= 0 && this.opacity <= 1.0) { diff --git a/src/map-feature.js b/src/map-feature.js index 5745c977e..fa9455694 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -168,7 +168,6 @@ export class MapFeature extends HTMLElement { .addTo(this._map); placeholder.replaceWith(this._featureGroup.options.group); // TODO: getBounds() should dynamically update the layerBounds and zoomBounds - this._layer._setLayerElExtent(); delete this._getFeatureExtent; this._setUpEvents(); } @@ -242,9 +241,6 @@ export class MapFeature extends HTMLElement { } } - if (Object.keys(mapmlvectors._layers).length === 1) { - this._layer._setLayerElExtent(); - } this._setUpEvents(); }); } diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 2915f0f15..97f4ea5dc 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -139,8 +139,6 @@ export var MapMLLayer = L.Layer.extend({ map.addLayer(this._staticTileLayer); } - this._setLayerElExtent(); - this.setZIndex(this.options.zIndex); this.getPane().appendChild(this._container); map.on('popupopen', this._attachSkipButtons, this); @@ -166,145 +164,6 @@ export var MapMLLayer = L.Layer.extend({ return !(noLayer || this.getProjection() !== map.options.projection); }, - //sets the elements .bounds property - _setLayerElExtent: function () { - let bounds, - zoomMax, - zoomMin, - maxNativeZoom, - minNativeZoom, - zoomBounds = { - minZoom: 0, - maxZoom: 0, - maxNativeZoom: 0, - minNativeZoom: 0 - }; - let layerTypes = [ - '_staticTileLayer', - '_imageLayer', - '_mapmlvectors', - '_templatedLayer' - ]; - layerTypes.forEach((type) => { - const mapExtents = this._layerEl.querySelectorAll('map-extent'); - if (this[type]) { - if (type === '_templatedLayer') { - for (let i = 0; i < mapExtents.length; i++) { - for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { - let inputData = M._extractInputBounds( - mapExtents[i]._templateVars[j] - ); - mapExtents[i]._templateVars[j].tempExtentBounds = - inputData.bounds; - mapExtents[i]._templateVars[j].extentZoomBounds = - inputData.zoomBounds; - } - } - for (let i = 0; i < mapExtents.length; i++) { - if (mapExtents[i].checked) { - for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { - if (!bounds) { - bounds = mapExtents[i]._templateVars[j].tempExtentBounds; - zoomMax = - mapExtents[i]._templateVars[j].extentZoomBounds.maxZoom; - zoomMin = - mapExtents[i]._templateVars[j].extentZoomBounds.minZoom; - maxNativeZoom = - mapExtents[i]._templateVars[j].extentZoomBounds - .maxNativeZoom; - minNativeZoom = - mapExtents[i]._templateVars[j].extentZoomBounds - .minNativeZoom; - } else { - bounds.extend( - mapExtents[i]._templateVars[j].tempExtentBounds.min - ); - bounds.extend( - mapExtents[i]._templateVars[j].tempExtentBounds.max - ); - zoomMax = Math.max( - zoomMax, - mapExtents[i]._templateVars[j].extentZoomBounds.maxZoom - ); - zoomMin = Math.min( - zoomMin, - mapExtents[i]._templateVars[j].extentZoomBounds.minZoom - ); - maxNativeZoom = Math.max( - maxNativeZoom, - mapExtents[i]._templateVars[j].extentZoomBounds - .maxNativeZoom - ); - minNativeZoom = Math.min( - minNativeZoom, - mapExtents[i]._templateVars[j].extentZoomBounds - .minNativeZoom - ); - } - } - } - } - zoomBounds.minZoom = zoomMin; - zoomBounds.maxZoom = zoomMax; - zoomBounds.minNativeZoom = minNativeZoom; - zoomBounds.maxNativeZoom = maxNativeZoom; - this._properties.zoomBounds = zoomBounds; - this._properties.layerBounds = bounds; - // assign each template the layer and zoom bounds - for (let i = 0; i < mapExtents.length; i++) { - mapExtents[i].templatedLayer.layerBounds = bounds; - mapExtents[i].templatedLayer.zoomBounds = zoomBounds; - } - } else if (type === '_staticTileLayer') { - if (this[type].layerBounds) { - if (!bounds) { - bounds = this[type].layerBounds; - zoomBounds = this[type].zoomBounds; - } else { - bounds.extend(this[type].layerBounds.min); - bounds.extend(this[type].layerBounds.max); - } - } - } else if (type === '_imageLayer') { - if (this[type].layerBounds) { - if (!bounds) { - bounds = this[type].layerBounds; - zoomBounds = this[type].zoomBounds; - } else { - bounds.extend(this[type].layerBounds.min); - bounds.extend(this[type].layerBounds.max); - } - } - } else if ( - // only process extent if mapmlvectors is not empty - type === '_mapmlvectors' && - Object.keys(this[type]._layers).length !== 0 - ) { - if (this[type].layerBounds) { - if (!bounds) { - bounds = this[type].layerBounds; - zoomBounds = this[type].zoomBounds; - } else { - bounds.extend(this[type].layerBounds.min); - bounds.extend(this[type].layerBounds.max); - } - } - } - } - }); - if (bounds) { - //assigns the formatted extent object to .extent and spreads the zoom ranges to .extent also - this._layerEl.extent = Object.assign( - M._convertAndFormatPCRS( - bounds, - this._properties.crs, - this._properties.projection - ), - { zoom: zoomBounds } - ); - } - }, - addTo: function (map) { map.addLayer(this); return this; @@ -316,8 +175,8 @@ export var MapMLLayer = L.Layer.extend({ // for now, only redraw templated layers. if (this._properties._mapExtents) { for (let i = 0; i < this._properties._mapExtents.length; i++) { - if (this._properties._mapExtents[i].templatedLayer) { - this._properties._mapExtents[i].templatedLayer.redraw(); + if (this._properties._mapExtents[i]._templatedLayer) { + this._properties._mapExtents[i]._templatedLayer.redraw(); } } } diff --git a/src/mapml/utils/Util.js b/src/mapml/utils/Util.js index 4685f7b10..e3330119a 100644 --- a/src/mapml/utils/Util.js +++ b/src/mapml/utils/Util.js @@ -551,11 +551,6 @@ export var Util = { // the layer is newly created, so have to wait until it's fully init'd // before setting properties. layer.whenReady().then(() => { - // TODO refactor _setLayerElExtent so that it's invoked automatically - // by layer.extent getter TBD. - if (!layer.extent) { - layer._layer._setLayerElExtent(); - } // if the map projection isnt' changed by link traversal, it's necessary // to perform pan/zoom operations after the layer is ready if (!link.inPlace && zoomTo) From b6b43d395a60aa5e5314a74405e9326341a54f49 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 10 Oct 2023 17:47:04 -0400 Subject: [PATCH 019/136] Fix bug in _calculateExtent, add whenReady() to layer.zoomTo --- src/layer.js | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/src/layer.js b/src/layer.js index 809e322b8..65d3b996a 100644 --- a/src/layer.js +++ b/src/layer.js @@ -429,10 +429,12 @@ export class MapLayer extends HTMLElement { '_mapmlvectors', '_templatedLayer' ]; + const mapExtents = this.querySelectorAll('map-extent').length + ? this.querySelectorAll('map-extent') + : this.shadowRoot + ? this.shadowRoot.querySelectorAll('map-extent') + : []; layerTypes.forEach((type) => { - const mapExtents = this.querySelectorAll('map-extent').length - ? this.querySelectorAll('map-extent') - : this.shadowRoot.querySelectorAll('map-extent'); if (type === '_templatedLayer' && mapExtents.length) { for (let i = 0; i < mapExtents.length; i++) { for (let j = 0; j < mapExtents[i]._templateVars.length; j++) { @@ -545,20 +547,22 @@ export class MapLayer extends HTMLElement { } } zoomTo() { - let map = this.parentElement._map, - extent = this.extent, - tL = extent.topLeft.pcrs, - bR = extent.bottomRight.pcrs, - layerBounds = L.bounds( - L.point(tL.horizontal, tL.vertical), - L.point(bR.horizontal, bR.vertical) - ), - center = map.options.crs.unproject(layerBounds.getCenter(true)); + this.whenReady().then(() => { + let map = this.parentElement._map, + extent = this.extent, + tL = extent.topLeft.pcrs, + bR = extent.bottomRight.pcrs, + layerBounds = L.bounds( + L.point(tL.horizontal, tL.vertical), + L.point(bR.horizontal, bR.vertical) + ), + center = map.options.crs.unproject(layerBounds.getCenter(true)); - let maxZoom = extent.zoom.maxZoom, - minZoom = extent.zoom.minZoom; - map.setView(center, M.getMaxZoom(layerBounds, map, minZoom, maxZoom), { - animate: false + let maxZoom = extent.zoom.maxZoom, + minZoom = extent.zoom.minZoom; + map.setView(center, M.getMaxZoom(layerBounds, map, minZoom, maxZoom), { + animate: false + }); }); } mapml2geojson(options = {}) { From a171a1068e87450c60aa8ae8e288e62ba18d47e9 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 10 Oct 2023 23:04:55 -0400 Subject: [PATCH 020/136] WIP get tests configured with new map-extent attributes. Get rid of (some) use of live map tiles from NRCan. Add temporary todo list of still-failing tests --- test/e2e/api/domApi-mapml-viewer.test.js | 5 +- test/e2e/api/domApi-web-map.test.js | 4 +- test/e2e/api/locateApi.html | 2 +- test/e2e/core/ArrowKeyNavContextMenu.html | 2 +- test/e2e/core/debugMode.html | 6 +- test/e2e/core/featureIndexOverlay.html | 4 +- test/e2e/core/featureNavigation.html | 2 +- .../core/fullscreenControlMapmlViewer.html | 4 +- test/e2e/core/fullscreenControlWebMap.html | 4 +- test/e2e/core/layerContextMenu.html | 2 +- test/e2e/core/mapContextMenu.html | 2 +- test/e2e/core/mapElement.html | 2 +- test/e2e/core/mapSpan.html | 4 +- test/e2e/core/metaDefault.html | 2 +- test/e2e/core/missingMetaParameters.html | 4 +- test/e2e/core/popupTabNavigation.html | 2 +- test/e2e/core/reticle.html | 2 +- test/e2e/core/styleParsing.html | 4 +- test/e2e/core/tms.html | 2 +- test/e2e/data/cbmt.mapml | 2 +- test/e2e/data/cbmtgeom.mapml | 2 +- test/e2e/data/cbmtile-cbmt.mapml | 2 +- test/e2e/data/fdi.mapml | 2 +- test/e2e/data/osm.mapml | 2 +- test/e2e/data/tiles/cbmt/DouglasFir.mapml | 2 +- .../tiles/cbmt/cbmt-changeProjection.mapml | 2 +- test/e2e/data/tiles/cbmt/cbmt.mapml | 2 +- .../e2e/data/tiles/cbmt/missing_min_max.mapml | 2 +- .../tiles/cbmt/osm-changeProjection.mapml | 2 +- test/e2e/data/tiles/cbmt/templatedImage.mapml | 2 +- .../data/tiles/wgs84/vector-tile-test.mapml | 2 +- test/e2e/layers/clientTemplatedTileLayer.html | 2 +- test/e2e/layers/layerOpacityAttribute.html | 2 +- test/e2e/layers/multipleExtents.html | 6 +- test/e2e/layers/multipleExtentsOpacity.html | 6 +- .../multipleHeterogeneousQueryExtents.html | 6 +- test/e2e/layers/multipleQueryExtents.html | 8 +-- test/e2e/layers/queryLink.html | 2 +- test/e2e/layers/queryableMapExtent.mapml | 2 +- .../step/templatedFeaturesLayerStep.html | 2 +- .../layers/step/templatedImageLayerStep.html | 2 +- .../layers/step/templatedTileLayerStep.html | 2 +- test/e2e/layers/templatedFeatures.html | 4 +- test/e2e/layers/templatedFeaturesFilter.html | 2 +- test/e2e/layers/templatedImageLayer.html | 2 +- test/e2e/layers/templatedTileLayer.html | 4 +- test/e2e/mapml-viewer/cssDomination.html | 2 +- test/e2e/mapml-viewer/customTCRS.html | 4 +- test/e2e/mapml-viewer/locateButton.html | 2 +- test/e2e/mapml-viewer/mapml-viewer.html | 2 +- .../e2e/mapml-viewer/mapml-viewerCaption.html | 2 +- .../mapml-viewerHeightAndWidthAttributes.html | 2 +- test/e2e/mapml-viewer/noWidthAndHeight.html | 2 +- test/e2e/mapml-viewer/staticAttribute.html | 2 +- test/e2e/mapml-viewer/windowSizeChange.html | 2 +- test/e2e/web-map/map.html | 2 +- test/e2e/web-map/mapCaption.html | 2 +- test/e2e/web-map/mapCssNoDomination.html | 2 +- .../web-map/mapHeightAndWidthAttributes.html | 2 +- test/e2e/web-map/mapNoWidthAndHeight.html | 2 +- test/e2e/web-map/mapStatic.html | 2 +- test/e2e/web-map/mapWindowSizeChange.html | 2 +- todo | 69 +++++++++++++++++++ 63 files changed, 153 insertions(+), 85 deletions(-) create mode 100644 todo diff --git a/test/e2e/api/domApi-mapml-viewer.test.js b/test/e2e/api/domApi-mapml-viewer.test.js index 36b019dfe..981e1fab1 100644 --- a/test/e2e/api/domApi-mapml-viewer.test.js +++ b/test/e2e/api/domApi-mapml-viewer.test.js @@ -96,7 +96,7 @@ test.describe('mapml-viewer DOM API Tests', () => { (layer) => layer.setAttribute( 'src', - 'http://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' + 'tiles/cbmt/cbmt.mapml' ), layerHandle ); @@ -131,7 +131,6 @@ test.describe('mapml-viewer DOM API Tests', () => { }); test('Remove mapml-viewer from DOM, add it back in', async () => { - await page.pause(); // check for error messages in console let errorLogs = []; page.on('pageerror', (err) => { @@ -339,7 +338,7 @@ test.describe('mapml-viewer DOM API Tests', () => { (layer) => layer.setAttribute( 'src', - 'http://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' + 'tiles/cbmt/cbmt.mapml' ), layerHandle ); diff --git a/test/e2e/api/domApi-web-map.test.js b/test/e2e/api/domApi-web-map.test.js index 65a528912..816d51bcd 100644 --- a/test/e2e/api/domApi-web-map.test.js +++ b/test/e2e/api/domApi-web-map.test.js @@ -91,7 +91,7 @@ test.describe('web-map DOM API Tests', () => { (layer) => layer.setAttribute( 'src', - 'http://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' + 'tiles/cbmt/cbmt.mapml' ), layerHandle ); @@ -319,7 +319,7 @@ test.describe('web-map DOM API Tests', () => { (layer) => layer.setAttribute( 'src', - 'http://geogratis.gc.ca/mapml/en/cbmtile/cbmt/' + 'tiles/cbmt/cbmt.mapml' ), layerHandle ); diff --git a/test/e2e/api/locateApi.html b/test/e2e/api/locateApi.html index 5a256a095..b329ccbdb 100644 --- a/test/e2e/api/locateApi.html +++ b/test/e2e/api/locateApi.html @@ -73,7 +73,7 @@ - + diff --git a/test/e2e/core/ArrowKeyNavContextMenu.html b/test/e2e/core/ArrowKeyNavContextMenu.html index 1ee2bcd90..004cc26ee 100644 --- a/test/e2e/core/ArrowKeyNavContextMenu.html +++ b/test/e2e/core/ArrowKeyNavContextMenu.html @@ -73,7 +73,7 @@ - + diff --git a/test/e2e/core/debugMode.html b/test/e2e/core/debugMode.html index aef015a17..8b4bbe74d 100644 --- a/test/e2e/core/debugMode.html +++ b/test/e2e/core/debugMode.html @@ -24,7 +24,7 @@ - + - + - +