Skip to content

Commit

Permalink
Copy location as <map-feature> (#804)
Browse files Browse the repository at this point in the history
* Change context menu to copy location as a map-feature.  
Change Util._pasteLayer to sniff for <map-feature, paste as a visible
layer.

* Copy map features as gcrs,tcrs,pcrs coordinates, other cs are noted as
not implemented yet, uses gcrs as substitute location for those.

* Fix and add tests

* Add localization support for Pasted Layer

* Fix tests

---------

Co-authored-by: prushfor <prushfor@L-BSC-A146390.nrn.nrcan.gc.ca>
  • Loading branch information
prushforth and prushfor authored Mar 28, 2023
1 parent d580797 commit b5f67fd
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 36 deletions.
164 changes: 135 additions & 29 deletions src/mapml/handlers/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ export var ContextMenu = L.Handler.extend({
.on(this._layerMenu, 'mousedown', L.DomEvent.stop)
.on(this._layerMenu, 'dblclick', L.DomEvent.stop)
.on(this._layerMenu, 'contextmenu', L.DomEvent.stop);

this.t = document.createElement('template');
this.t.innerHTML =
`<map-feature zoom="">
<map-featurecaption></map-featurecaption>
<map-properties>
<h2></h2>
<div style="text-align:center"></div>
</map-properties>
<map-geometry cs="">
<map-point>
<map-coordinates></map-coordinates>
</map-point>
</map-geometry>
</map-feature>`;
},

addHooks: function () {
Expand Down Expand Up @@ -298,52 +313,143 @@ export var ContextMenu = L.Handler.extend({
}
},

_copyGCRS: function(e){
_copyGCRS: function(e) {
let mapEl = this.options.mapEl,
click = this.contextMenu._clickEvent;
this.contextMenu._copyData(`lon :${click.latlng.lng.toFixed(6)}, lat:${click.latlng.lat.toFixed(6)}`);
},

_copyTCRS: function(e){
click = this.contextMenu._clickEvent,
projection = mapEl.projection,
feature = this.contextMenu.t.content.firstElementChild.cloneNode(true),
caption = feature.querySelector('map-featurecaption'),
h2 = feature.querySelector('h2'),
div = feature.querySelector('div'),
geom = feature.querySelector('map-geometry'),
coords = feature.querySelector('map-coordinates');

feature.setAttribute('zoom', mapEl.zoom);
geom.setAttribute('cs','gcrs');
caption.textContent = `Copied ${projection} gcrs location`;
h2.textContent = `Copied ${projection} gcrs location`;
div.textContent = `${click.latlng.lng.toFixed(6)} ${click.latlng.lat.toFixed(6)}`;
coords.textContent = `${click.latlng.lng.toFixed(6)} ${click.latlng.lat.toFixed(6)}`;
this.contextMenu._copyData(feature.outerHTML);
},

_copyTCRS: function(e) {
let mapEl = this.options.mapEl,
click = this.contextMenu._clickEvent,
point = mapEl._map.project(click.latlng);
this.contextMenu._copyData(`z:${mapEl.zoom}, x:${point.x}, y:${point.y}`);
},

_copyTileMatrix: function(e){
point = mapEl._map.project(click.latlng),
pt = {x:point.x.toFixed(),y:point.y.toFixed()},
projection = mapEl.projection,
feature = this.contextMenu.t.content.firstElementChild.cloneNode(true),
caption = feature.querySelector('map-featurecaption'),
h2 = feature.querySelector('h2'),
div = feature.querySelector('div'),
geom = feature.querySelector('map-geometry'),
coords = feature.querySelector('map-coordinates');

feature.setAttribute('zoom', mapEl.zoom);
geom.setAttribute('cs','tcrs');
caption.textContent = `Copied ${projection} tcrs location`;
h2.textContent = `Copied ${projection} tcrs location`;
div.textContent = `${pt.x} ${pt.y}`;
coords.textContent = `${pt.x} ${pt.y}`;
this.contextMenu._copyData(feature.outerHTML);
},

_copyTileMatrix: function(e) {
let mapEl = this.options.mapEl,
click = this.contextMenu._clickEvent,
point = mapEl._map.project(click.latlng),
tileSize = mapEl._map.options.crs.options.crs.tile.bounds.max.x;
this.contextMenu._copyData(`z:${mapEl.zoom}, column:${Math.trunc(point.x/tileSize)}, row:${Math.trunc(point.y/tileSize)}`);
},

_copyPCRS: function(e){
tileSize = mapEl._map.options.crs.options.crs.tile.bounds.max.x,
projection = mapEl.projection,
feature = this.contextMenu.t.content.firstElementChild.cloneNode(true),
caption = feature.querySelector('map-featurecaption'),
h2 = feature.querySelector('h2'),
div = feature.querySelector('div'),
geom = feature.querySelector('map-geometry'),
coords = feature.querySelector('map-coordinates');

feature.setAttribute('zoom', mapEl.zoom);
geom.setAttribute('cs','gcrs');
caption.textContent = `Copied ${projection} tilematrix location (not implemented yet)`;
h2.textContent = `Copied ${projection} tilematrix location (not implemented yet)`;
div.textContent = `${Math.trunc(point.x/tileSize)} ${Math.trunc(point.y/tileSize)}`;
coords.textContent = `${click.latlng.lng.toFixed(6)} ${click.latlng.lat.toFixed(6)}`;
this.contextMenu._copyData(feature.outerHTML);
},

_copyPCRS: function(e) {
let mapEl = this.options.mapEl,
click = this.contextMenu._clickEvent,
point = mapEl._map.project(click.latlng),
scale = mapEl._map.options.crs.scale(+mapEl.zoom),
pcrs = mapEl._map.options.crs.transformation.untransform(point,scale);
this.contextMenu._copyData(`easting:${Math.round(pcrs.x)}, northing:${Math.round(pcrs.y)}`);
},

_copyTile: function(e){
pcrs = mapEl._map.options.crs.transformation.untransform(point,scale).round(),
projection = mapEl.projection,
feature = this.contextMenu.t.content.firstElementChild.cloneNode(true),
caption = feature.querySelector('map-featurecaption'),
h2 = feature.querySelector('h2'),
div = feature.querySelector('div'),
geom = feature.querySelector('map-geometry'),
coords = feature.querySelector('map-coordinates');

feature.setAttribute('zoom', mapEl.zoom);
geom.setAttribute('cs','pcrs');
caption.textContent = `Copied ${projection} pcrs location`;
h2.textContent = `Copied ${projection} pcrs location`;
div.textContent = `${pcrs.x} ${pcrs.y}`;
coords.textContent = `${pcrs.x} ${pcrs.y}`;
this.contextMenu._copyData(feature.outerHTML);
},

_copyTile: function(e) {
let mapEl = this.options.mapEl,
click = this.contextMenu._clickEvent,
point = mapEl._map.options.crs.project(click.latlng),
// the _map.project method returns pixels, while the _map.crs.project
// method returns meters, confusingly:
// https://leafletjs.com/reference.html#map-project
// https://leafletjs.com/reference.html#crs-project
point = mapEl._map.project(click.latlng),
tileSize = mapEl._map.options.crs.options.crs.tile.bounds.max.x,
pointX = point.x % tileSize, pointY = point.y % tileSize;
if(pointX < 0) pointX+= tileSize;
if(pointY < 0) pointY+= tileSize;

this.contextMenu._copyData(`z:${mapEl.zoom}, i:${Math.trunc(pointX)}, j:${Math.trunc(pointY)}`);
pointX = point.x % tileSize, pointY = point.y % tileSize,
pt = L.point(pointX,pointY).trunc(),
projection = mapEl.projection,
feature = this.contextMenu.t.content.firstElementChild.cloneNode(true),
caption = feature.querySelector('map-featurecaption'),
h2 = feature.querySelector('h2'),
div = feature.querySelector('div'),
geom = feature.querySelector('map-geometry'),
coords = feature.querySelector('map-coordinates');

if(pt.x < 0) pt.x += tileSize;
if(pt.y < 0) pt.y += tileSize;

feature.setAttribute('zoom', mapEl.zoom);
geom.setAttribute('cs','gcrs');
caption.textContent = `Copied ${projection} tile location (not implemented yet)`;
h2.textContent = `Copied ${projection} tile location (not implemented yet)`;
div.textContent = `${pt.x} ${pt.y}`;
coords.textContent = `${click.latlng.lng.toFixed(6)} ${click.latlng.lat.toFixed(6)}`;
this.contextMenu._copyData(feature.outerHTML);
},

_copyMap: function(e){
let mapEl = this.options.mapEl,
click = this.contextMenu._clickEvent;
this.contextMenu._copyData(`z:${mapEl.zoom}, i:${Math.trunc(click.containerPoint.x)}, j:${Math.trunc(click.containerPoint.y)}`);
click = this.contextMenu._clickEvent,
mapPt = click.containerPoint.trunc(),
projection = mapEl.projection,
feature = this.contextMenu.t.content.firstElementChild.cloneNode(true),
caption = feature.querySelector('map-featurecaption'),
h2 = feature.querySelector('h2'),
div = feature.querySelector('div'),
geom = feature.querySelector('map-geometry'),
coords = feature.querySelector('map-coordinates');

feature.setAttribute('zoom', mapEl.zoom);
geom.setAttribute('cs','gcrs');
caption.textContent = `Copied ${projection} map location (not implemented yet)`;
h2.textContent = `Copied ${projection} map location (not implemented yet)`;
div.textContent = `${mapPt.x} ${mapPt.y}`;
coords.textContent = `${click.latlng.lng.toFixed(6)} ${click.latlng.lat.toFixed(6)}`;
this.contextMenu._copyData(feature.outerHTML);
},

_copyAllCoords: function(e){
Expand Down
1 change: 1 addition & 0 deletions src/mapml/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ export var Options = {
kbdPrevFeature: "Previous feature",
kbdNextFeature: "Next feature",
dfLayer: "Layer",
dfPastedLayer: "Pasted layer"
}
};
5 changes: 5 additions & 0 deletions src/mapml/utils/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,11 @@ export var Util = {
text = text.replace(/(<!--.*?-->)|(<!--[\S\s]+?-->)|(<!--[\S\s]*?$)/g, '').trim();
if ((text.slice(0,7) === "<layer-") && (text.slice(-9) === "</layer->")) {
mapEl.insertAdjacentHTML("beforeend", text);
} else if (text.slice(0,12) === "<map-feature" && text.slice(-14) === "</map-feature>") {
let layer = `<layer- label="${M.options.locale.dfPastedLayer}" checked>
<map-meta name='projection' content='${mapEl.projection}'></map-meta>`+text+
"</layer->";
mapEl.insertAdjacentHTML("beforeend", layer);
} else {
try {
mapEl.geojson2mapml(JSON.parse(text));
Expand Down
98 changes: 95 additions & 3 deletions test/e2e/core/mapContextMenu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,11 +332,11 @@ test.describe("Playwright Map Context Menu Tests", () => {
);
});

test("Submenu, copy location", async () => {
test("Submenu, copy map location in gcrs (default) coordinates", async () => {
currLocCS = await page.$eval(
"body > map",
(map) => (map._map.contextMenu.defLocCS)
)
);
// set cs to pcrs for copying location test
await page.$eval(
"body > map",
Expand All @@ -357,7 +357,18 @@ test.describe("Playwright Map Context Menu Tests", () => {
"body > textarea#coord",
(text) => text.value
);
const expected = "lon :-92.062002, lat:46.922393";
const expected = `<map-feature zoom="1">
<map-featurecaption>Copied CBMTILE gcrs location</map-featurecaption>
<map-properties>
<h2>Copied CBMTILE gcrs location</h2>
<div style="text-align:center">-92.062002 46.922393</div>
</map-properties>
<map-geometry cs="gcrs">
<map-point>
<map-coordinates>-92.062002 46.922393</map-coordinates>
</map-point>
</map-geometry>
</map-feature>`;
expect(copyValue).toEqual(expected);
await page.locator("body > textarea#coord").fill('');
await page.$eval(
Expand All @@ -368,7 +379,88 @@ test.describe("Playwright Map Context Menu Tests", () => {
currLocCS
);
});
test("Paste map-feature to map", async () => {
currLocCS = await page.$eval(
"body > map",
(map) => (map._map.contextMenu.defLocCS)
);
// set cs to pcrs for copying location test
await page.$eval(
"body > map",
(map) => {map._map.contextMenu.defLocCS = 'gcrs';}
);
await page.click("body > map");
await page.keyboard.press("Shift+F10");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.click("body > map");
await page.keyboard.press("Shift+F10");
await page.keyboard.press("p");

const layerLabel = await page.$eval(
"body > map",
(map) => map.layers[1].label
);
expect(layerLabel).toEqual("Pasted layer");
// clean up
await page.$eval("body > map",
(map) => map.removeChild(map.querySelector('[label="Pasted layer"]')));
});
test("Submenu, copy location in tilematrix coordinates, which is not implemented, so faked with gcrs", async () => {
// set the copy location coordinate system to the not-implemented tilematrix
await page.$eval(
"body > map",
(map) => {
map._map.contextMenu.defLocCS = 'tilematrix';
// ContextMenu.js double-checks the value of defLocCS against the
// M.options.defaultLocCoor when the context menu is shown,
// so have to ensure it has the value we want to test against.
M.options.defaultLocCoor = 'tilematrix';
}
);

await page.click("body > map");
await page.keyboard.press("Shift+F10");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.click("body > textarea#coord");
await page.keyboard.press("Control+v");
const copyValue = await page.$eval(
"body > textarea#coord",
(text) => text.value
);
const expected = `<map-feature zoom="1">
<map-featurecaption>Copied CBMTILE tilematrix location (not implemented yet)</map-featurecaption>
<map-properties>
<h2>Copied CBMTILE tilematrix location (not implemented yet)</h2>
<div style="text-align:center">6 6</div>
</map-properties>
<map-geometry cs="gcrs">
<map-point>
<map-coordinates>-92.062002 46.922393</map-coordinates>
</map-point>
</map-geometry>
</map-feature>`;
expect(copyValue).toEqual(expected);
await page.locator("body > textarea#coord").fill('');
await page.$eval(
"body > map",
(map, currLocCS) => {
map._map.contextMenu.defLocCS = currLocCS;
},
currLocCS
);
});
test("Paste valid Layer to map", async () => {
await page.click("body > textarea#layer");
await page.keyboard.press("Control+a");
Expand Down
Loading

0 comments on commit b5f67fd

Please sign in to comment.