Skip to content

Commit 5150ba3

Browse files
prushforthprushfor
andauthored
Add map-projectionchange event. (#911)
* Add map-projectionchange event. Delete map projection attrChgCallbk from triggering map-change event. Allow map-extent to react to map-projectionchange event, enable/disable according to projection match. * Change initialization logic for opacity, so that layer- and map-extent opacity value is maintained through map-projectionchange event. * Remove MapMLLayer.validProjection attribute and flawed logic. * Prettier formatting change only in multipleExtents.test.js * Remove use of validProjection by layer context menu, was wrong anyway(?) * Add waitfortimeout of 500ms in customTCRS.test.js (100ms was not enough) * Add test for simple projection change. * Add test for empty map history after map-projectionchange event * Add test for opacity on layer and map-extent after map-projectionchange * Bump timeout after map-projectionchange as opacity value wasn't updating in time. Skip test of history after map-projectionchange, pending full rewrite of history api tbd. * Add await for mapEl.whenProjectionDefined(this.units) to map-extent, so that invalid / undefined projections e.g. 'foo' will throw. * Add test of map-extent units attribute set to undefined projection * Optimize map-extent.test.js to speed it up a little --------- Co-authored by: aliyanh <@AliyanH> Co-authored by: Hanyu Y <@yhy0217> Co-authored-by: prushfor <[email protected]>
1 parent fde273c commit 5150ba3

File tree

12 files changed

+277
-152
lines changed

12 files changed

+277
-152
lines changed

src/layer.js

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class MapLayer extends HTMLElement {
108108
this._createLayerControlHTML = M._createLayerControlHTML.bind(this);
109109
// this._opacity is used to record the current opacity value (with or without updates),
110110
// the initial value of this._opacity should be set as opacity attribute value, if exists, or the default value 1.0
111-
this._opacity = +(this.getAttribute('opacity') || 1.0);
111+
this._opacity = this.opacity || 1.0;
112112
const doConnected = this._onAdd.bind(this);
113113
this.parentElement
114114
.whenReady()
@@ -345,25 +345,20 @@ export class MapLayer extends HTMLElement {
345345
'_mapmlvectors',
346346
'_templatedLayer'
347347
];
348-
if (layer.validProjection) {
349-
for (let j = 0; j < layerTypes.length; j++) {
350-
let type = layerTypes[j];
351-
if (this.checked) {
352-
if (type === '_templatedLayer' && mapExtents.length > 0) {
353-
for (let i = 0; i < mapExtents.length; i++) {
354-
totalExtentCount++;
355-
if (mapExtents[i]._validateDisabled()) disabledExtentCount++;
356-
}
357-
} else if (layer[type]) {
358-
// not a templated layer
348+
for (let j = 0; j < layerTypes.length; j++) {
349+
let type = layerTypes[j];
350+
if (this.checked) {
351+
if (type === '_templatedLayer' && mapExtents.length > 0) {
352+
for (let i = 0; i < mapExtents.length; i++) {
359353
totalExtentCount++;
360-
if (!layer[type].isVisible) disabledExtentCount++;
354+
if (mapExtents[i]._validateDisabled()) disabledExtentCount++;
361355
}
356+
} else if (layer[type]) {
357+
// not a templated layer
358+
totalExtentCount++;
359+
if (!layer[type].isVisible) disabledExtentCount++;
362360
}
363361
}
364-
} else {
365-
disabledExtentCount = 1;
366-
totalExtentCount = 1;
367362
}
368363
// if all extents are not visible / disabled, set layer to disabled
369364
if (

src/map-extent.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ export class MapExtent extends HTMLElement {
140140
this.attachShadow({ mode: 'open' });
141141
}
142142
await this.parentLayer.whenReady();
143+
this.mapEl = this.parentLayer.closest('mapml-viewer,map[is=web-map]');
144+
await this.mapEl.whenProjectionDefined(this.units).catch(() => {
145+
throw new Error('Undefined projection:' + this.units);
146+
});
143147
// when projection is changed, the parent layer-._layer is created (so whenReady is fulfilled) but then removed,
144148
// then the map-extent disconnectedCallback will be triggered by layer-._onRemove() (clear the shadowRoot)
145149
// even before connectedCallback is finished
@@ -168,9 +172,10 @@ export class MapExtent extends HTMLElement {
168172
);
169173
this._changeHandler = this._handleChange.bind(this);
170174
this.parentLayer.addEventListener('map-change', this._changeHandler);
175+
this.mapEl.addEventListener('map-projectionchange', this._changeHandler);
171176
// this._opacity is used to record the current opacity value (with or without updates),
172177
// the initial value of this._opacity should be set as opacity attribute value, if exists, or the default value 1.0
173-
this._opacity = +(this.getAttribute('opacity') || 1.0);
178+
this._opacity = this.opacity || 1.0;
174179
this._templatedLayer = M.templatedLayer(this._templateVars, {
175180
pane: this._layer._container,
176181
opacity: this.opacity,
@@ -522,6 +527,7 @@ export class MapExtent extends HTMLElement {
522527
this._layerControlHTML.remove();
523528
this._map.removeLayer(this._templatedLayer);
524529
this.parentLayer.removeEventListener('map-change', this._changeHandler);
530+
this.mapEl.removeEventListener('map-projectionchange', this._changeHandler);
525531
delete this._templatedLayer;
526532
delete this.parentLayer.bounds;
527533
}

src/mapml-viewer.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,13 @@ export class MapViewer extends HTMLElement {
377377
this.zoomTo(lat, lon, zoom);
378378
if (M.options.announceMovement)
379379
this._map.announceMovement.enable();
380-
this.querySelectorAll('layer-').forEach((layer) => {
381-
layer.dispatchEvent(new CustomEvent('map-change'));
382-
});
380+
// required to delay until map-extent.disabled is correctly set
381+
// which happens as a result of layer-._validateDisabled()
382+
// which happens so much we have to delay until they calls are
383+
// completed
384+
setTimeout(() => {
385+
this.dispatchEvent(new CustomEvent('map-projectionchange'));
386+
}, 0);
383387
});
384388
}
385389
};

src/mapml/handlers/ContextMenu.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,7 +798,6 @@ export var ContextMenu = L.Handler.extend({
798798
.closest('fieldset')
799799
.parentNode.parentNode.parentNode.querySelector('span')
800800
: elem.querySelector('span');
801-
if (!elem.layer.validProjection) return;
802801
this._layerClicked = elem;
803802
this._layerMenu.removeAttribute('hidden');
804803
this._showAtPoint(e.containerPoint, e, this._layerMenu);

src/mapml/layers/MapMLLayer.js

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,6 @@ export var MapMLLayer = L.Layer.extend({
4343
// OR use the extent of the content provided
4444

4545
this._initialize(local ? layerEl : mapml);
46-
47-
// a default extent can't be correctly set without the map to provide
48-
// its bounds , projection, zoom range etc, so if that stuff's not
49-
// established by metadata in the content, we should use map properties
50-
// to set the extent, but the map won't be available until the <layer>
51-
// element is attached to the <map> element, wait for that to happen.
52-
// weirdness. options is actually undefined here, despite the hardcoded
53-
// options above. If you use this.options, you see the options defined
54-
// above. Not going to change this, but failing to understand ATM.
55-
// may revisit some time.
56-
this.validProjection = true;
5746
},
5847
setZIndex: function (zIndex) {
5948
this.options.zIndex = zIndex;
@@ -98,11 +87,6 @@ export var MapMLLayer = L.Layer.extend({
9887
},
9988

10089
onAdd: function (map) {
101-
// probably don't need it except for layer context menu usage
102-
if (this._properties && !this._validProjection(map)) {
103-
this.validProjection = false;
104-
return;
105-
}
10690
this._map = map;
10791
if (this._mapmlvectors) map.addLayer(this._mapmlvectors);
10892

@@ -218,26 +202,6 @@ export var MapMLLayer = L.Layer.extend({
218202
}
219203
},
220204

221-
_validProjection: function (map) {
222-
const mapExtents = this._layerEl.querySelectorAll('map-extent');
223-
let noLayer = false;
224-
if (this._properties && mapExtents.length > 0) {
225-
for (let i = 0; i < mapExtents.length; i++) {
226-
if (mapExtents[i]._templateVars) {
227-
for (let template of mapExtents[i]._templateVars)
228-
if (
229-
!template.projectionMatch &&
230-
template.projection !== map.options.projection
231-
) {
232-
noLayer = true; // if there's a single template where projections don't match, set noLayer to true
233-
break;
234-
}
235-
}
236-
}
237-
}
238-
return !(noLayer || this.getProjection() !== map.options.projection);
239-
},
240-
241205
addTo: function (map) {
242206
map.addLayer(this);
243207
return this;

src/web-map.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,13 @@ export class WebMap extends HTMLMapElement {
422422
this.zoomTo(lat, lon, zoom);
423423
if (M.options.announceMovement)
424424
this._map.announceMovement.enable();
425-
this.querySelectorAll('layer-').forEach((layer) => {
426-
layer.dispatchEvent(new CustomEvent('map-change'));
427-
});
425+
// required to delay until map-extent.disabled is correctly set
426+
// which happens as a result of layer-._validateDisabled()
427+
// which happens so much we have to delay until they calls are
428+
// completed
429+
setTimeout(() => {
430+
this.dispatchEvent(new CustomEvent('map-projectionchange'));
431+
}, 0);
428432
});
429433
}
430434
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>map-projectionchange event</title>
6+
<!-- the layer in this map should continue to be visible when you change
7+
the viewer projection from OSMTILE to CBMTILE. -->
8+
<script type="module" src="/mapml-viewer.js"></script>
9+
</head>
10+
<body>
11+
<mapml-viewer zoom="2" lon="-75.703611" lat="45.411105" width="500" height="500" controls projection="OSMTILE">
12+
<layer- label="Projection changer" checked>
13+
<map-extent label="National Geographic" units="OSMTILE" checked >
14+
<map-input name="TileMatrix" type="zoom" value="18" min="0" max="18"></map-input>
15+
<map-input name="TileCol" type="location" units="tilematrix" axis="column" min="0" max="262144"></map-input>
16+
<map-input name="TileRow" type="location" units="tilematrix" axis="row" min="0" max="262144"></map-input>
17+
<map-link rel="tile" tref="https://server.arcgisonline.com/arcgis/rest/services/NatGeo_World_Map/MapServer/WMTS/tile/1.0.0/NatGeo_World_Map/default/default028mm/{TileMatrix}/{TileRow}/{TileCol}.jpg"></map-link>
18+
</map-extent>
19+
<map-extent label="Canada Base Map - Transportation" units="CBMTILE" checked >
20+
<map-input name="z" type="zoom" min="0" max="18"></map-input>
21+
<map-input name="y" type="location" units="tilematrix" axis="row"></map-input>
22+
<map-input name="x" type="location" units="tilematrix" axis="column"></map-input>
23+
<map-link rel="tile" tref="/tiles/cbmt/{z}/c{x}_r{y}.png" ></map-link>
24+
</map-extent>
25+
</layer->
26+
</mapml-viewer>
27+
<script>
28+
function changeProjection() {
29+
const prj = document.body.querySelector('mapml-viewer').projection;
30+
if (document.body.querySelector('mapml-viewer').projection === "OSMTILE") {
31+
document.body.querySelector('mapml-viewer').projection = "CBMTILE";
32+
} else {
33+
document.body.querySelector('mapml-viewer').projection = "OSMTILE";
34+
}
35+
}
36+
</script>
37+
</body>
38+
</html>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { test, expect, chromium } from '@playwright/test';
2+
3+
test.describe('map-projectionchange test ', () => {
4+
let page;
5+
let context;
6+
test.beforeAll(async () => {
7+
context = await chromium.launchPersistentContext('');
8+
page =
9+
context.pages().find((page) => page.url() === 'about:blank') ||
10+
(await context.newPage());
11+
});
12+
13+
test.beforeEach(async function () {
14+
await page.goto('events/map-projectionchange.html');
15+
await page.waitForTimeout(1000);
16+
});
17+
18+
test.afterAll(async function () {
19+
await context.close();
20+
});
21+
22+
test('Multiple map-extents in different projections adapt to map-projectionchange', async () => {
23+
const viewer = await page.locator('mapml-viewer');
24+
expect(await viewer.evaluate((v) => v.projection)).toEqual('OSMTILE');
25+
expect(
26+
await viewer.evaluate((v) => {
27+
return v.querySelector('map-extent[units=OSMTILE]').disabled;
28+
})
29+
).toBe(false);
30+
expect(
31+
await viewer.evaluate((v) => {
32+
return v.querySelector('map-extent[units=CBMTILE]').disabled;
33+
})
34+
).toBe(true);
35+
await viewer.evaluate(() => changeProjection());
36+
await page.waitForTimeout(500);
37+
expect(await viewer.evaluate((v) => v.projection)).toEqual('CBMTILE');
38+
expect(
39+
await viewer.evaluate((v) => {
40+
return v.querySelector('map-extent[units=OSMTILE]').disabled;
41+
})
42+
).toBe(true);
43+
expect(
44+
await viewer.evaluate((v) => {
45+
return v.querySelector('map-extent[units=CBMTILE]').disabled;
46+
})
47+
).toBe(false);
48+
});
49+
test.skip('History is empty after map-projectionchange', async () => {
50+
// history api needs a complete review; test can't pass without many
51+
// odd hacks, so skip for now.
52+
const viewer = await page.locator('mapml-viewer');
53+
expect(await viewer.evaluate((v) => v.projection)).toEqual('OSMTILE');
54+
await viewer.evaluate(() => changeProjection());
55+
await page.waitForTimeout(500);
56+
expect(await viewer.evaluate((v) => v.projection)).toEqual('CBMTILE');
57+
const reload = await page.getByLabel('Reload');
58+
expect(await reload.evaluate((button) => button.ariaDisabled)).toBe('true');
59+
});
60+
test('Opacity is maintained on layer- and map-extent after map-projectionchange', async () => {
61+
const viewer = await page.locator('mapml-viewer');
62+
const layer = await page.locator('layer-');
63+
await page.pause();
64+
await layer.evaluate((layer) => (layer.opacity = 0.5));
65+
expect(
66+
await layer.evaluate((layer) => {
67+
return layer.opacity;
68+
})
69+
).toBe(0.5);
70+
const osmtileExtent = await page.locator('map-extent[units=OSMTILE]');
71+
await osmtileExtent.evaluate((e) => (e.opacity = 0.4));
72+
const cbmtileExtent = await page.locator('map-extent[units=CBMTILE]');
73+
await cbmtileExtent.evaluate((e) => (e.opacity = 0.3));
74+
await viewer.evaluate(() => changeProjection());
75+
await page.waitForTimeout(1000);
76+
expect(await osmtileExtent.evaluate((e) => e.opacity)).toBe(0.4);
77+
expect(await cbmtileExtent.evaluate((e) => e.opacity)).toBe(0.3);
78+
expect(
79+
await layer.evaluate((layer) => {
80+
return layer.opacity;
81+
})
82+
).toBe(0.5);
83+
});
84+
});

test/e2e/elements/map-extent/map-extent.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
<map-input name="col" type="location" axis="column" units="tilematrix" min="14" max="19"></map-input>
3434
<map-link rel='tile' tref='/data/cbmt/{zoomLevel}/c{col}_r{row}.png'></map-link>
3535
</map-extent>
36+
<map-extent data-testid="ext4" label="User-generated label - 4" units="foo" checked>
37+
<map-input name="zoomLevel" type="zoom" value="3" min="0" max="3"></map-input>
38+
<map-input name="row" type="location" axis="row" units="tilematrix" min="14" max="21"></map-input>
39+
<map-input name="col" type="location" axis="column" units="tilematrix" min="14" max="19"></map-input>
40+
<map-link rel='tile' tref='/data/cbmt/{zoomLevel}/c{col}_r{row}.png'></map-link>
41+
</map-extent>
3642
</template>
3743
</head>
3844

0 commit comments

Comments
 (0)