diff --git a/index.html b/index.html index ddfa9d00d..320c86c74 100644 --- a/index.html +++ b/index.html @@ -76,7 +76,6 @@ A pleasing map of Canada - @@ -117,7 +116,7 @@
- + -75.705278 45.397778 diff --git a/src/map-feature.js b/src/map-feature.js index 075a36633..f54d94f5a 100644 --- a/src/map-feature.js +++ b/src/map-feature.js @@ -291,14 +291,32 @@ export class MapFeature extends HTMLElement { _getNativeZoomAndCS(content) { // content: referred to if the has inline , or // referred to remote mapml if the has a src attribute, and the fetched mapml contains + // referred to [map-meta, ...] if it is query // referred to null otherwise (i.e. has fetched in shadow, the attaches to 's shadow) let nativeZoom, nativeCS; if (this._extentEl) { // feature attaches to extent's shadow if (this._extentEl.querySelector('map-link[rel=query]')) { // for query, fallback zoom is the current map zoom level that the query is returned - nativeZoom = this._map.getZoom(); - nativeCS = 'pcrs'; + let metaZoom, metaCS; + if (content) { + metaZoom = M._metaContentToObject( + Array.prototype.filter + .call(content, function (elem) { + return elem.matches('map-meta[name=zoom]'); + })[0] + ?.getAttribute('content') + ).content; + metaCS = M._metaContentToObject( + Array.prototype.filter + .call(content, function (elem) { + return elem.matches('map-meta[name=cs]'); + })[0] + ?.getAttribute('content') + ).content; + } + nativeZoom = metaZoom || this._map.getZoom(); + nativeCS = metaCS || 'gcrs'; } else if (this._extentEl.querySelector('map-link[rel=features]')) { // for templated feature, read fallback from the fetched mapml's map-meta[name=zoom / cs] nativeZoom = this._extentEl._nativeZoom; @@ -326,7 +344,7 @@ export class MapFeature extends HTMLElement { csLength = csMeta?.length; nativeCS = csLength ? csMeta[csLength - 1].getAttribute('content') - : 'pcrs'; + : 'gcrs'; return { zoom: nativeZoom, cs: nativeCS }; } } @@ -347,7 +365,9 @@ export class MapFeature extends HTMLElement { // calculate feature extent let map = this._map, geometry = this.querySelector('map-geometry'), - native = this._getNativeZoomAndCS(this._layer._content), + native = this._getNativeZoomAndCS( + this._layer._content || this._layer.metas + ), cs = geometry.getAttribute('cs') || native.cs, // zoom level that the feature rendered at zoom = this.zoom || native.zoom, diff --git a/src/mapml/handlers/QueryHandler.js b/src/mapml/handlers/QueryHandler.js index 666a563c5..b0a494503 100644 --- a/src/mapml/handlers/QueryHandler.js +++ b/src/mapml/handlers/QueryHandler.js @@ -109,6 +109,12 @@ export var QueryHandler = L.Handler.extend({ features = Array.prototype.slice.call( mapmldoc.querySelectorAll('map-feature') ); + // elements + layer.metas = Array.prototype.slice.call( + mapmldoc.querySelectorAll( + 'map-meta[name=cs], map-meta[name=zoom], map-meta[name=projection]' + ) + ); if (features.length) layer._mapmlFeatures = layer._mapmlFeatures.concat(features); } else { @@ -246,6 +252,7 @@ export var QueryHandler = L.Handler.extend({ } } function displayFeaturesPopup(features, loc) { + if (features.length === 0) return; let f = M.featureLayer(features, { // pass the vector layer a renderer of its own, otherwise leaflet // puts everything into the overlayPane @@ -279,7 +286,7 @@ export var QueryHandler = L.Handler.extend({ layer.on('popupclose', function () { map.removeLayer(f); }); - f.showPaginationFeature({ i: 0, popup: layer._popup }); + f.showPaginationFeature({ i: 0, popup: layer._popup, meta: layer.metas }); } } }); diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index 8fee20358..0a0956683 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -88,14 +88,26 @@ export var FeatureLayer = L.FeatureGroup.extend({ showPaginationFeature: function (e) { if (this.options.query && this._mapmlFeatures[e.i]) { let feature = this._mapmlFeatures[e.i]; - // remove the prev / next one from shadow if there is any - feature._extentEl.shadowRoot.firstChild?.remove(); + if (e.type === 'featurepagination') { + // remove map-feature only (keep meta's) when paginating + feature._extentEl.shadowRoot.querySelector('map-feature')?.remove(); + } else { + // empty the map-extent shadowRoot + // remove the prev / next one and 's from shadow if there is any + feature._extentEl.shadowRoot.replaceChildren(); + } this.clearLayers(); feature._featureGroup = this.addData( feature, this.options.nativeCS, this.options.nativeZoom ); + // append all map-meta from mapml document + if (e.meta) { + for (let i = 0; i < e.meta.length; i++) { + feature._extentEl.shadowRoot.appendChild(e.meta[i]); + } + } feature._extentEl.shadowRoot.appendChild(feature); e.popup._navigationBar.querySelector('p').innerText = e.i + 1 + '/' + this.options._leafletLayer._totalFeatureCount; @@ -116,21 +128,51 @@ export var FeatureLayer = L.FeatureGroup.extend({ } }, + // _getNativeVariables: returns an object with the native zoom and CS, + // based on the map-metas that are available within + // the layer or the fallback default values. + // _getNativeVariables: mapml-||layer-||null||[map-feature,...] -> {zoom: _, val: _} + // mapml can be a mapml- element, layer- element, null, or an array of map-features _getNativeVariables: function (mapml) { - let nativeZoom = - (mapml.querySelector && - mapml.querySelector('map-meta[name=zoom]') && - +M._metaContentToObject( - mapml.querySelector('map-meta[name=zoom]').getAttribute('content') - ).value) || - 0; - let nativeCS = - (mapml.querySelector && - mapml.querySelector('map-meta[name=cs]') && - M._metaContentToObject( - mapml.querySelector('map-meta[name=cs]').getAttribute('content') - ).content) || - 'PCRS'; + let nativeZoom, nativeCS; + // when mapml is an array of features provided by the query + if ( + mapml.length && + mapml[0].parentElement.parentElement && + mapml[0].parentElement.parentElement.tagName === 'mapml-' + ) { + let mapmlEl = mapml[0].parentElement.parentElement; + nativeZoom = + (mapmlEl.querySelector && + mapmlEl.querySelector('map-meta[name=zoom]') && + +M._metaContentToObject( + mapmlEl.querySelector('map-meta[name=zoom]').getAttribute('content') + ).value) || + 0; + nativeCS = + (mapmlEl.querySelector && + mapmlEl.querySelector('map-meta[name=cs]') && + M._metaContentToObject( + mapmlEl.querySelector('map-meta[name=cs]').getAttribute('content') + ).content) || + 'GCRS'; + } else { + // when mapml is null or a layer-/mapml- element + nativeZoom = + (mapml.querySelector && + mapml.querySelector('map-meta[name=zoom]') && + +M._metaContentToObject( + mapml.querySelector('map-meta[name=zoom]').getAttribute('content') + ).value) || + 0; + nativeCS = + (mapml.querySelector && + mapml.querySelector('map-meta[name=cs]') && + M._metaContentToObject( + mapml.querySelector('map-meta[name=cs]').getAttribute('content') + ).content) || + 'GCRS'; + } return { zoom: nativeZoom, cs: nativeCS }; }, diff --git a/src/mapml/layers/MapMLLayer.js b/src/mapml/layers/MapMLLayer.js index 2d49c14b8..48cfaa398 100644 --- a/src/mapml/layers/MapMLLayer.js +++ b/src/mapml/layers/MapMLLayer.js @@ -1401,11 +1401,21 @@ export var MapMLLayer = L.Layer.extend({ layer._content = mapml; if (!this.responseXML && this.responseText) mapml = new DOMParser().parseFromString(this.responseText, 'text/xml'); + + // if everything is ok, continue with the processing if ( this.readyState === this.DONE && mapml.querySelector && !mapml.querySelector('parsererror') ) { + // Get layer's title/label + if (mapml.querySelector('map-title')) { + layer._title = mapml.querySelector('map-title').textContent.trim(); + layer._titleIsReadOnly = true; + } else if (mapml instanceof Element && mapml.hasAttribute('label')) { + layer._title = mapml.getAttribute('label').trim(); + } + var serverExtent = mapml.querySelectorAll('map-extent'), projection, projectionMatch, @@ -1441,6 +1451,15 @@ export var MapMLLayer = L.Layer.extend({ projectionMatch = projection && projection === layer.options.mapprojection; } + } else { + // default projection set to parent projection when no map-meta projection element present + projection = layer.options.mapprojection; + projectionMatch = true; + serverMeta = projection; + console.log( + `A projection was not assigned to the '${layer._title}' Layer. Please specify a projection for that layer using a map-meta element. See more here - https://maps4html.org/web-map-doc/docs/elements/meta/` + ); + // TODO: Add a more obvious warning. } var metaExtent = mapml.querySelector('map-meta[name=extent]'), @@ -1509,7 +1528,13 @@ export var MapMLLayer = L.Layer.extend({ } } } else { - layer._extent = serverMeta; + if (typeof serverMeta === 'string') { + // when map-meta projection not present for layer + layer._extent = { serverMeta }; + } else { + // when map-meta projection present for layer + layer._extent = serverMeta; + } } layer._parseLicenseAndLegend(mapml, layer, projection); @@ -1626,12 +1651,6 @@ export var MapMLLayer = L.Layer.extend({ layer._styles = stylesControl; } - if (mapml.querySelector('map-title')) { - layer._title = mapml.querySelector('map-title').textContent.trim(); - layer._titleIsReadOnly = true; - } else if (mapml instanceof Element && mapml.hasAttribute('label')) { - layer._title = mapml.getAttribute('label').trim(); - } if (layer._map) { layer._validateExtent(); // if the layer is checked in the layer control, force the addition @@ -1742,7 +1761,7 @@ export var MapMLLayer = L.Layer.extend({ let extent = this._extent._mapExtents ? this._extent._mapExtents[0] : this._extent; // the projections for each extent eould be the same (as) validated in _validProjection, so can use mapExtents[0] - if (!extent) return FALLBACK_PROJECTION; + if (extent.serverMeta) return extent.serverMeta; switch (extent.tagName.toUpperCase()) { case 'MAP-EXTENT': if (extent.hasAttribute('units')) diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index 54e160ee2..2f2e3c3c1 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -159,7 +159,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ .querySelector('map-meta[name=cs]') .getAttribute('content') ).content) || - 'PCRS'); + 'GCRS'); features.addData(mapml, nativeCS, nativeZoom); // "migrate" to extent's shadow // make a clone, prevent the elements from being removed from mapml file diff --git a/test/e2e/core/metaDefault.html b/test/e2e/core/metaDefault.html index 3996838c6..81e240a2c 100644 --- a/test/e2e/core/metaDefault.html +++ b/test/e2e/core/metaDefault.html @@ -34,6 +34,16 @@ tref="http://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" > + + + + + -79.477626 43.764814 + + + + + diff --git a/test/e2e/core/metaDefault.test.js b/test/e2e/core/metaDefault.test.js index 5f89c8d84..7a74ff10b 100644 --- a/test/e2e/core/metaDefault.test.js +++ b/test/e2e/core/metaDefault.test.js @@ -90,4 +90,40 @@ test.describe('Playwright Missing Min Max Attribute, Meta Default Tests', () => expectedGCRSSecondLayer.bottomRight ); }); + test("Layer with no map-meta's is rendered on map", async () => { + const viewer = await page.evaluateHandle(() => + document.querySelector('mapml-viewer') + ); + const layerSVG = await ( + await page.evaluateHandle( + (map) => + map.shadowRoot + .querySelectorAll('.mapml-layer')[2] + .querySelector('path') + .getAttribute('d'), + viewer + ) + ).jsonValue(); + expect(layerSVG).toEqual( + 'M190 311 L177.5 281 C177.5 261, 202.5 261, 202.5 281 L190 311z' + ); + }); + test("Fetched layer with no map-meta's is rendered on map", async () => { + const viewer = await page.evaluateHandle(() => + document.querySelector('mapml-viewer') + ); + const layerSVG = await ( + await page.evaluateHandle( + (map) => + map.shadowRoot + .querySelectorAll('.mapml-layer')[3] + .querySelector('path') + .getAttribute('d'), + viewer + ) + ).jsonValue(); + expect(layerSVG).toEqual( + 'M243 255 L230.5 225 C230.5 205, 255.5 205, 255.5 225 L243 255z' + ); + }); }); diff --git a/test/e2e/data/noMapMeta.mapml b/test/e2e/data/noMapMeta.mapml new file mode 100644 index 000000000..cf3551f97 --- /dev/null +++ b/test/e2e/data/noMapMeta.mapml @@ -0,0 +1,18 @@ + + + Capital of Canada + + + + + + Ottawa +

Ottawa

+ + + -75.715027 45.424721 + + +
+
+
diff --git a/test/e2e/geojson/mapml2geojson.html b/test/e2e/geojson/mapml2geojson.html index b5bc08458..8de57da49 100644 --- a/test/e2e/geojson/mapml2geojson.html +++ b/test/e2e/geojson/mapml2geojson.html @@ -14,11 +14,9 @@ - Point - - + -75.6916809 45.4186964 @@ -28,11 +26,9 @@ - Line - - + -75.6168365 45.471929 -75.6855011 45.458445 -75.7016373 45.4391764 -75.7030106 45.4259255 -75.7236099 45.4208652 -75.7565689 45.4117074 -75.7833481 45.384225 -75.8197403 45.3714435 -75.8516693 45.377714 @@ -42,11 +38,9 @@ - Polygon - - + -75.5859375 45.4656690 -75.6813812 45.4533876 -75.6961441 45.4239978 -75.7249832 45.4083331 -75.7792282 45.3772317 -75.7534790 45.3294614 -75.5831909 45.3815724 -75.6024170 45.4273712 -75.5673981 45.4639834 -75.5859375 45.4656690 -75.6596588 45.4211062 -75.6338958 45.4254436 -75.6277127 45.4066458 -75.6572542 45.4097792 -75.6596588 45.4211062 @@ -57,11 +51,9 @@ - MultiPoint - - + -75.7016373 45.4391764 -75.7236099 45.4208652 -75.7833481 45.384225 @@ -71,11 +63,9 @@ - MultiLineString - - + -75.6168365 45.471929 -75.6855011 45.458445 -75.7016373 45.4391764 -75.7030106 45.4259255 -75.7565689 45.4117074 -75.7833481 45.384225 -75.8197403 45.3714435 -75.8516693 45.377714 @@ -86,11 +76,9 @@ - MultiPolygon - - + -75.5859375 45.4656690 -75.6813812 45.4533876 -75.6961441 45.4239978 -75.7249832 45.4083331 -75.7792282 45.3772317 -75.7534790 45.3294614 -75.5831909 45.3815724 -75.6024170 45.4273712 -75.5673981 45.4639834 -75.5859375 45.4656690 @@ -110,11 +98,9 @@ - Polygon - - + -75.5859375 45.4656690 -75.6813812 45.4533876 -75.6961441 45.4239978 -75.7249832 45.4083331 -75.7792282 45.3772317 -75.7534790 45.3294614 -75.5831909 45.3815724 -75.6024170 45.4273712 -75.5673981 45.4639834 -75.5859375 45.4656690 @@ -124,8 +110,7 @@ Line - - + -75.6168365 45.471929 -75.6855011 45.458445 -75.7016373 45.4391764 -75.7030106 45.4259255 -75.7236099 45.4208652 -75.7565689 45.4117074 -75.7833481 45.384225 -75.8197403 45.3714435 -75.8516693 45.377714 @@ -135,8 +120,7 @@ Point - - + -75.6916809 45.4186964 @@ -146,8 +130,7 @@ MultiPoint - - + -75.7016373 45.4391764 -75.7236099 45.4208652 -75.7833481 45.384225 @@ -157,8 +140,7 @@ MultiLineString - - + -75.6168365 45.471929 -75.6855011 45.458445 -75.7016373 45.4391764 -75.7030106 45.4259255 -75.7565689 45.4117074 -75.7833481 45.384225 -75.8197403 45.3714435 -75.8516693 45.377714 @@ -169,8 +151,7 @@ MultiPolygon - - + -75.5859375 45.4656690 -75.6813812 45.4533876 -75.6961441 45.4239978 -75.7249832 45.4083331 -75.7792282 45.3772317 -75.7534790 45.3294614 -75.5831909 45.3815724 -75.6024170 45.4273712 -75.5673981 45.4639834 -75.5859375 45.4656690 @@ -186,8 +167,7 @@ Polygon with holes - - + -75.5859375 45.4656690 -75.6813812 45.4533876 -75.6961441 45.4239978 -75.7249832 45.4083331 -75.7792282 45.3772317 -75.7534790 45.3294614 -75.5831909 45.3815724 -75.6024170 45.4273712 -75.5673981 45.4639834 -75.5859375 45.4656690 -75.6467062 45.4215881 -75.6889363 45.4049585 -75.6693647 45.3767494 -75.6270640 45.3924229 -75.6467062 45.4215881 @@ -198,8 +178,6 @@ - - @@ -235,12 +213,10 @@ - Point - - + -75.6916809 45.4186964 @@ -252,12 +228,10 @@

This is
bolded and emphasized
.

- Point - - + -75.6916809 45.4186964 @@ -282,9 +256,7 @@

This is
bolded and emphasized
.

- -

Test

@@ -304,8 +276,7 @@

This is
bolded and emphasized
.

Geometry Collection - - + -75.5859375 45.4656690 -75.6813812 45.4533876 -75.6961441 45.4239978 -75.7249832 45.4083331 @@ -444,4 +415,4 @@

This is
bolded and emphasized
.

- \ No newline at end of file + diff --git a/test/e2e/layers/queryLink.test.js b/test/e2e/layers/queryLink.test.js index ba74d85d0..6686305cf 100644 --- a/test/e2e/layers/queryLink.test.js +++ b/test/e2e/layers/queryLink.test.js @@ -130,7 +130,8 @@ test.describe('Playwright Query Link Tests', () => { const featureCount = await page.evaluate( `document.querySelector('mapml-viewer > layer- > map-extent').shadowRoot.children.length` ); - expect(featureCount).toEqual(1); + // shadowRoot contains 1 map-feature and 3 map-meta (cs, zoom, projection) + expect(featureCount).toEqual(4); const property = await page.evaluate( `document.querySelector('mapml-viewer > layer- > map-extent').shadowRoot.querySelector('map-properties').innerText.trim()` ); @@ -163,7 +164,8 @@ test.describe('Playwright Query Link Tests', () => { const featureCount = await page.evaluate( `document.querySelector('mapml-viewer > layer- > map-extent').shadowRoot.children.length` ); - expect(featureCount).toEqual(1); + // shadowRoot contains 1 map-feature and 3 map-meta (cs, zoom, projection) + expect(featureCount).toEqual(4); const property = await page.evaluate( `document.querySelector('mapml-viewer > layer- > map-extent').shadowRoot.querySelector('map-properties').innerText.trim()` ); @@ -196,7 +198,8 @@ test.describe('Playwright Query Link Tests', () => { const featureCount = await page.evaluate( `document.querySelector('mapml-viewer > layer- > map-extent').shadowRoot.children.length` ); - expect(featureCount).toEqual(1); + // shadowRoot contains 1 map-feature and 3 map-meta (cs, zoom, projection) + expect(featureCount).toEqual(4); const property = await page.evaluate( `document.querySelector('mapml-viewer > layer- > map-extent').shadowRoot.querySelector('map-properties').innerText.trim()` ); diff --git a/test/e2e/mapml-viewer/customTCRS.test.js b/test/e2e/mapml-viewer/customTCRS.test.js index d8059d206..731e68a92 100644 --- a/test/e2e/mapml-viewer/customTCRS.test.js +++ b/test/e2e/mapml-viewer/customTCRS.test.js @@ -71,7 +71,7 @@ test.describe('Playwright Custom TCRS Tests', () => { expect(featureThree).toEqual('M382 -28L809 -28L809 399L382 399z'); expect(featureFour).toEqual( - 'M55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231L55 231z' + 'M150 429L171 426L175 438L181 457L183 461L185 463L185 465L187 465L185 468L185 470L184 472L186 477L186 481L188 485L182 486L154 490L154 492L157 494L157 497L158 498L156 501L154 501L151 499L150 495L149 495L148 498L148 501L144 501L141 477L141 448L141 431L139 430L150 429z' ); expect(staticFeatures).toEqual(false); diff --git a/test/server.js b/test/server.js index df2359a93..88bfd261d 100644 --- a/test/server.js +++ b/test/server.js @@ -59,6 +59,17 @@ app.get('/data/query/DouglasFir', (req, res, next) => { } ); }); +app.get('/data/noMapMeta', (req, res, next) => { + res.sendFile( + __dirname + '/e2e/data/noMapMeta.mapml', + { headers: { 'Content-Type': 'text/mapml' } }, + (err) => { + if (err) { + res.status(403).send('Error.'); + } + } + ); +}); app.use('/data', express.static(path.join(__dirname, 'e2e/data/tiles/cbmt'))); app.use('/data', express.static(path.join(__dirname, 'e2e/data/tiles/wgs84')));