From e0b128cc916d9ac052f8c75159fa1a7e8de35dcd Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 15 Mar 2023 15:51:09 -0400 Subject: [PATCH 01/36] geolocation button --- Gruntfile.js | 10 +- package.json | 7 +- src/mapml-viewer.js | 16 ++- src/mapml/control/GeolocationButton.js | 4 + src/mapml/index.js | 3 + test/e2e/layers/multipleExtents.html | 154 ++++++++++++++++--------- 6 files changed, 131 insertions(+), 63 deletions(-) create mode 100644 src/mapml/control/GeolocationButton.js diff --git a/Gruntfile.js b/Gruntfile.js index 83d80fd06..f84a54e80 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -9,7 +9,7 @@ module.exports = function(grunt) { }, combine: { files: { - 'dist/mapml.css': ['node_modules/leaflet/dist/leaflet.css', 'src/mapml.css'] + 'dist/mapml.css': ['node_modules/leaflet/dist/leaflet.css', 'node_modules/leaflet.locatecontrol/dist/L.Control.Locate.css', 'node_modules/leaflet.locatecontrol/dist/L.Control.Locate.mapbox.css', 'src/mapml.css'] } } }, @@ -86,6 +86,14 @@ module.exports = function(grunt) { filter: 'isFile', src: ['index.html'], dest: 'dist/' + }, + { + expand: true, + cwd: 'node_modules/leaflet.locatecontrol/src/', + flatten: true, + filter: 'isFile', + src: ['L.Control.Locate.js'], + dest: 'dist/' } ], options: { diff --git a/package.json b/package.json index 6f9e2b7d7..ee347b8db 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "jest": "jest --verbose --noStackTrace" }, "devDependencies": { + "@playwright/test": "^1.24.2", "diff": "^5.1.0", "express": "^4.17.1", "grunt": "^1.4.0", @@ -54,7 +55,6 @@ "jest-playwright-preset": "^1.7.0", "leaflet": "^1.9.3", "path": "^0.12.7", - "@playwright/test": "^1.24.2", "playwright": "^1.24.2", "proj4": "^2.6.2", "proj4leaflet": "^1.0.2", @@ -63,5 +63,8 @@ "files": [ "dist", "*.md" - ] + ], + "dependencies": { + "leaflet.locatecontrol": "^0.79.0" + } } diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 6e6a1bf47..22c4b0fed 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -27,7 +27,7 @@ export class MapViewer extends HTMLElement { return this.hasAttribute('controlslist') ? this.getAttribute("controlslist") : ""; } set controlslist(val) { - let options = ["nofullscreen", "nozoom", "nolayer", "noreload"], + let options = ["nofullscreen", "nozoom", "nolayer", "noreload", "geolocation"], lowerVal = val.toLowerCase(); if (this.controlslist.includes(lowerVal) || !options.includes(lowerVal))return; this.setAttribute("controlslist", this.controlslist+` ${lowerVal}`); @@ -284,8 +284,8 @@ export class MapViewer extends HTMLElement { setControls(isToggle, toggleShow, setup){ if (this.controls && this._map) { - let controls = ["_zoomControl", "_reloadButton", "_fullScreenControl", "_layerControl"], - options = ["nozoom", "noreload", "nofullscreen", 'nolayer'], + let controls = ["_zoomControl", "_reloadButton", "_fullScreenControl", "_layerControl", "_geolocationButton"], + options = ["nozoom", "noreload", "nofullscreen", 'nolayer', 'geolocation'], mapSize = this._map.getSize().y, totalSize = 0; @@ -324,6 +324,16 @@ export class MapViewer extends HTMLElement { totalSize += 49; this._fullScreenControl = M.fullscreenButton().addTo(this._map); } + if (this.controlslist.toLowerCase().includes("geolocation") && !this._geolocationButton && (totalSize + 49) <= mapSize){ + totalSize += 49; + this._geolocationButton = M.geolocationButton({ + showPopup: false, + position: "bottomright", + locateOptions: { + maxZoom: 16 + }, + },this._map); + } //removes any control layers that are not needed, either by the toggling or by the controlslist attribute for(let i in options){ if(this[controls[i]] && (this.controlslist.toLowerCase().includes(options[i]) || (isToggle && !toggleShow ))){ diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js new file mode 100644 index 000000000..412dc2958 --- /dev/null +++ b/src/mapml/control/GeolocationButton.js @@ -0,0 +1,4 @@ +import '../../../dist/L.Control.Locate'; +export var geolocationButton = function (options, map) { + L.control.locate(options).addTo(map); +}; \ No newline at end of file diff --git a/src/mapml/index.js b/src/mapml/index.js index 938a47deb..8ac91423f 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -54,6 +54,7 @@ import { Util } from './utils/Util'; import { ReloadButton, reloadButton } from './control/ReloadButton'; import { FullscreenButton, fullscreenButton } from './control/FullscreenButton'; import {attributionControl} from "./control/AttributionControl"; +import {geolocationButton} from "./control/GeolocationButton"; import { Crosshair, crosshair } from "./layers/Crosshair"; import { Feature, feature } from "./features/feature"; import { FeatureRenderer, featureRenderer } from './features/featureRenderer'; @@ -659,6 +660,8 @@ M.fullscreenButton = fullscreenButton; M.attributionControl = attributionControl; +M.geolocationButton = geolocationButton; + M.StaticTileLayer = StaticTileLayer; M.staticTileLayer = staticTileLayer; diff --git a/test/e2e/layers/multipleExtents.html b/test/e2e/layers/multipleExtents.html index a531d2230..058d693b9 100644 --- a/test/e2e/layers/multipleExtents.html +++ b/test/e2e/layers/multipleExtents.html @@ -1,59 +1,99 @@ - + - - - Multiple Extents Test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + index-map.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 9484cdb3bd6d94caa96f9611b6dc2c1a1b8c9967 Mon Sep 17 00:00:00 2001 From: Jacky0299 <83443908+Jacky0299@users.noreply.github.com> Date: Wed, 15 Mar 2023 15:55:14 -0400 Subject: [PATCH 02/36] Delete index.js --- src/mapml/index.js | 686 --------------------------------------------- 1 file changed, 686 deletions(-) delete mode 100644 src/mapml/index.js diff --git a/src/mapml/index.js b/src/mapml/index.js deleted file mode 100644 index 8ac91423f..000000000 --- a/src/mapml/index.js +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright 2015-2016 Canada Centre for Mapping and Earth Observation, - * Earth Sciences Sector, Natural Resources Canada. - * - * License - * - * By obtaining and/or copying this work, you (the licensee) agree that you have - * read, understood, and will comply with the following terms and conditions. - * - * Permission to copy, modify, and distribute this work, with or without - * modification, for any purpose and without fee or royalty is hereby granted, - * provided that you include the following on ALL copies of the work or portions - * thereof, including modifications: - * - * The full text of this NOTICE in a location viewable to users of the - * redistributed or derivative work. - * - * Any pre-existing intellectual property disclaimers, notices, or terms and - * conditions. If none exist, the W3C Software and Document Short Notice should - * be included. - * - * Notice of any changes or modifications, through a copyright statement on the - * new code or document such as "This software or document includes material - * copied from or derived from [title and URI of the W3C document]. - * Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." - * - * Disclaimers - * - * THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS - * OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF - * MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE - * SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, - * TRADEMARKS OR OTHER RIGHTS. - * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR - * CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. - * - * The name and trademarks of copyright holders may NOT be used in advertising or - * publicity pertaining to the work without specific, written prior permission. - * Title to copyright in this work will at all times remain with copyright holders. - */ -import { StaticTileLayer, staticTileLayer } from './layers/StaticTileLayer'; -import { LayerControl, layerControl } from './control/LayerControl'; -import { FeatureLayer, featureLayer } from './layers/FeatureLayer'; -import { TemplatedTileLayer, templatedTileLayer } from './layers/TemplatedTileLayer'; -import { TemplatedLayer, templatedLayer } from './layers/TemplatedLayer'; -import { TemplatedFeaturesLayer, templatedFeaturesLayer } from './layers/TemplatedFeaturesLayer'; -import { TemplatedImageLayer, templatedImageLayer } from './layers/TemplatedImageLayer'; -import { ImageLayer, imageLayer } from './layers/ImageLayer'; -import { MapMLLayer, mapMLLayer } from './layers/MapMLLayer'; -import { DebugOverlay, debugOverlay} from './layers/DebugOverlay'; -import { QueryHandler } from './handlers/QueryHandler'; -import { ContextMenu } from './handlers/ContextMenu'; -import { Util } from './utils/Util'; -import { ReloadButton, reloadButton } from './control/ReloadButton'; -import { FullscreenButton, fullscreenButton } from './control/FullscreenButton'; -import {attributionControl} from "./control/AttributionControl"; -import {geolocationButton} from "./control/GeolocationButton"; -import { Crosshair, crosshair } from "./layers/Crosshair"; -import { Feature, feature } from "./features/feature"; -import { FeatureRenderer, featureRenderer } from './features/featureRenderer'; -import { FeatureGroup, featureGroup} from './features/featureGroup'; -import {AnnounceMovement} from "./handlers/AnnounceMovement"; -import { FeatureIndex } from "./handlers/FeatureIndex"; -import { Options } from "./options"; -import "./keyboard"; -import {featureIndexOverlay, FeatureIndexOverlay} from "./layers/FeatureIndexOverlay"; - -/* global L, Node */ -(function (window, document, undefined) { - -var M = {}; -window.M = M; - -(function () { - M.detectImagePath = function (container) { - // this relies on the CSS style leaflet-default-icon-path containing a - // relative url() that leads to a valid icon file. Since that depends on - // how all of this stuff is deployed (i.e. custom element or as leaflet-plugin) - // also, because we're using 'shady DOM' api, the container must be - // a shady dom container, because the custom element tags it with added - // style-scope ... and related classes. - var el = L.DomUtil.create('div', 'leaflet-default-icon-path', container); - var path = L.DomUtil.getStyle(el, 'background-image') || - L.DomUtil.getStyle(el, 'backgroundImage'); // IE8 - - container.removeChild(el); - - if (path === null || path.indexOf('url') !== 0) { - path = ''; - } else { - path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, ''); - } - - return path; - }; - M.mime = "text/mapml"; - - let mapOptions = window.document.head.querySelector("map-options"); - M.options = Options; - if (mapOptions) M.options = Object.assign(M.options, JSON.parse(mapOptions.innerHTML)); - - // see https://leafletjs.com/reference-1.5.0.html#crs-l-crs-base - // "new classes can't inherit from (L.CRS), and methods can't be added - // to (L.CRS.anything) with the include function - // so we'll use the options property as a way to integrate needed - // properties and methods... - M.WGS84 = new L.Proj.CRS('EPSG:4326','+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs ', { - origin: [-180,+90], - bounds: L.bounds([[-180,-90],[180,90]]), - resolutions: [ - 0.703125, - 0.3515625, - 0.17578125, - 0.087890625, - 0.0439453125, - 0.02197265625, - 0.010986328125, - 0.0054931640625, - 0.00274658203125, - 0.001373291015625, - 0.0006866455078125, - 0.0003433227539062, - 0.0001716613769531, - 0.0000858306884766, - 0.0000429153442383, - 0.0000214576721191, - 0.0000107288360596, - 0.0000053644180298, - 0.0000026822090149, - 0.0000013411045074, - 0.0000006705522537, - 0.0000003352761269 - ], - crs: { - tcrs: { - horizontal: { - name: "x", - min: 0, - max: zoom => (Math.round(M.WGS84.options.bounds.getSize().x / M.WGS84.options.resolutions[zoom])) - }, - vertical: { - name: "y", - min:0, - max: zoom => (Math.round(M.WGS84.options.bounds.getSize().y / M.WGS84.options.resolutions[zoom])) - }, - bounds: zoom => L.bounds([M.WGS84.options.crs.tcrs.horizontal.min, - M.WGS84.options.crs.tcrs.vertical.min], - [M.WGS84.options.crs.tcrs.horizontal.max(zoom), - M.WGS84.options.crs.tcrs.vertical.max(zoom)]) - }, - pcrs: { - horizontal: { - name: "longitude", - get min() {return M.WGS84.options.crs.gcrs.horizontal.min;}, - get max() {return M.WGS84.options.crs.gcrs.horizontal.max;} - }, - vertical: { - name: "latitude", - get min() {return M.WGS84.options.crs.gcrs.vertical.min;}, - get max() {return M.WGS84.options.crs.gcrs.vertical.max;} - }, - get bounds() {return M.WGS84.options.bounds;} - }, - gcrs: { - horizontal: { - name: "longitude", - // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - min: -180.0, - max: 180.0 - }, - vertical: { - name: "latitude", - // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - min: -90.0, - max: 90.0 - }, - get bounds() {return L.latLngBounds( - [M.WGS84.options.crs.gcrs.vertical.min,M.WGS84.options.crs.gcrs.horizontal.min], - [M.WGS84.options.crs.gcrs.vertical.max,M.WGS84.options.crs.gcrs.horizontal.max]);} - }, - map: { - horizontal: { - name: "i", - min: 0, - max: map => map.getSize().x - }, - vertical: { - name: "j", - min: 0, - max: map => map.getSize().y - }, - bounds: map => L.bounds(L.point([0,0]),map.getSize()) - }, - tile: { - horizontal: { - name: "i", - min: 0, - max: 256 - }, - vertical: { - name: "j", - min: 0, - max: 256 - }, - get bounds() {return L.bounds( - [M.WGS84.options.crs.tile.horizontal.min,M.WGS84.options.crs.tile.vertical.min], - [M.WGS84.options.crs.tile.horizontal.max,M.WGS84.options.crs.tile.vertical.max]);} - }, - tilematrix: { - horizontal: { - name: "column", - min: 0, - max: zoom => (Math.round(M.WGS84.options.crs.tcrs.horizontal.max(zoom) / M.WGS84.options.crs.tile.bounds.getSize().x)) - }, - vertical: { - name: "row", - min: 0, - max: zoom => (Math.round(M.WGS84.options.crs.tcrs.vertical.max(zoom) / M.WGS84.options.crs.tile.bounds.getSize().y)) - }, - bounds: zoom => L.bounds( - [M.WGS84.options.crs.tilematrix.horizontal.min, - M.WGS84.options.crs.tilematrix.vertical.min], - [M.WGS84.options.crs.tilematrix.horizontal.max(zoom), - M.WGS84.options.crs.tilematrix.vertical.max(zoom)]) - } - } - }); - M.CBMTILE = new L.Proj.CRS('EPSG:3978', - '+proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs', { - origin: [-34655800, 39310000], - bounds: L.bounds([[-34655800,-39000000],[10000000,39310000]]), - resolutions: [ - 38364.660062653464, - 22489.62831258996, - 13229.193125052918, - 7937.5158750317505, - 4630.2175937685215, - 2645.8386250105837, - 1587.5031750063501, - 926.0435187537042, - 529.1677250021168, - 317.50063500127004, - 185.20870375074085, - 111.12522225044451, - 66.1459656252646, - 38.36466006265346, - 22.48962831258996, - 13.229193125052918, - 7.9375158750317505, - 4.6302175937685215, - 2.6458386250105836, - 1.5875031750063502, - 0.92604351875370428, - 0.52916772500211673, - 0.31750063500127002, - 0.18520870375074083, - 0.11112522225044451, - 0.066145965625264591 - ], - crs: { - tcrs: { - horizontal: { - name: "x", - min: 0, - max: zoom => (Math.round(M.CBMTILE.options.bounds.getSize().x / M.CBMTILE.options.resolutions[zoom])) - }, - vertical: { - name: "y", - min:0, - max: zoom => (Math.round(M.CBMTILE.options.bounds.getSize().y / M.CBMTILE.options.resolutions[zoom])) - }, - bounds: zoom => L.bounds([M.CBMTILE.options.crs.tcrs.horizontal.min, - M.CBMTILE.options.crs.tcrs.vertical.min], - [M.CBMTILE.options.crs.tcrs.horizontal.max(zoom), - M.CBMTILE.options.crs.tcrs.vertical.max(zoom)]) - }, - pcrs: { - horizontal: { - name: "easting", - get min() {return M.CBMTILE.options.bounds.min.x;}, - get max() {return M.CBMTILE.options.bounds.max.x;} - }, - vertical: { - name: "northing", - get min() {return M.CBMTILE.options.bounds.min.y;}, - get max() {return M.CBMTILE.options.bounds.max.y;} - }, - get bounds() {return M.CBMTILE.options.bounds;} - }, - gcrs: { - horizontal: { - name: "longitude", - // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - min: -141.01, - max: -47.74 - }, - vertical: { - name: "latitude", - // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - min: 40.04, - max: 86.46 - }, - get bounds() {return L.latLngBounds( - [M.CBMTILE.options.crs.gcrs.vertical.min,M.CBMTILE.options.crs.gcrs.horizontal.min], - [M.CBMTILE.options.crs.gcrs.vertical.max,M.CBMTILE.options.crs.gcrs.horizontal.max]);} - }, - map: { - horizontal: { - name: "i", - min: 0, - max: map => map.getSize().x - }, - vertical: { - name: "j", - min: 0, - max: map => map.getSize().y - }, - bounds: map => L.bounds(L.point([0,0]),map.getSize()) - }, - tile: { - horizontal: { - name: "i", - min: 0, - max: 256 - }, - vertical: { - name: "j", - min: 0, - max: 256 - }, - get bounds() {return L.bounds( - [M.CBMTILE.options.crs.tile.horizontal.min,M.CBMTILE.options.crs.tile.vertical.min], - [M.CBMTILE.options.crs.tile.horizontal.max,M.CBMTILE.options.crs.tile.vertical.max]);} - }, - tilematrix: { - horizontal: { - name: "column", - min: 0, - max: zoom => (Math.round(M.CBMTILE.options.crs.tcrs.horizontal.max(zoom) / M.CBMTILE.options.crs.tile.bounds.getSize().x)) - }, - vertical: { - name: "row", - min: 0, - max: zoom => (Math.round(M.CBMTILE.options.crs.tcrs.vertical.max(zoom) / M.CBMTILE.options.crs.tile.bounds.getSize().y)) - }, - bounds: zoom => L.bounds([0,0], - [M.CBMTILE.options.crs.tilematrix.horizontal.max(zoom), - M.CBMTILE.options.crs.tilematrix.vertical.max(zoom)]) - } - } - }); - M.APSTILE = new L.Proj.CRS('EPSG:5936', - '+proj=stere +lat_0=90 +lat_ts=50 +lon_0=-150 +k=0.994 +x_0=2000000 +y_0=2000000 +datum=WGS84 +units=m +no_defs', { - origin: [-2.8567784109255E7, 3.2567784109255E7], - bounds: L.bounds([[-28567784.109254867,-28567784.109254755],[32567784.109255023,32567784.10925506]]), - resolutions: [ - 238810.813354, - 119405.406677, - 59702.7033384999, - 29851.3516692501, - 14925.675834625, - 7462.83791731252, - 3731.41895865639, - 1865.70947932806, - 932.854739664032, - 466.427369832148, - 233.213684916074, - 116.606842458037, - 58.3034212288862, - 29.1517106145754, - 14.5758553072877, - 7.28792765351156, - 3.64396382688807, - 1.82198191331174, - 0.910990956788164, - 0.45549547826179 - ], - crs: { - tcrs: { - horizontal: { - name: "x", - min: 0, - max: zoom => (Math.round(M.APSTILE.options.bounds.getSize().x / M.APSTILE.options.resolutions[zoom])) - }, - vertical: { - name: "y", - min:0, - max: zoom => (Math.round(M.APSTILE.options.bounds.getSize().y / M.APSTILE.options.resolutions[zoom])) - }, - bounds: zoom => L.bounds([M.APSTILE.options.crs.tcrs.horizontal.min, - M.APSTILE.options.crs.tcrs.vertical.min], - [M.APSTILE.options.crs.tcrs.horizontal.max(zoom), - M.APSTILE.options.crs.tcrs.vertical.max(zoom)]) - }, - pcrs: { - horizontal: { - name: "easting", - get min() {return M.APSTILE.options.bounds.min.x;}, - get max() {return M.APSTILE.options.bounds.max.x;} - }, - vertical: { - name: "northing", - get min() {return M.APSTILE.options.bounds.min.y;}, - get max() {return M.APSTILE.options.bounds.max.y;} - }, - get bounds() {return M.APSTILE.options.bounds;} - }, - gcrs: { - horizontal: { - name: "longitude", - // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - min: -180.0, - max: 180.0 - }, - vertical: { - name: "latitude", - // set min/max axis values from EPSG registry area of use, retrieved 2019-07-25 - min: 60.0, - max: 90.0 - }, - get bounds() {return L.latLngBounds( - [M.APSTILE.options.crs.gcrs.vertical.min,M.APSTILE.options.crs.gcrs.horizontal.min], - [M.APSTILE.options.crs.gcrs.vertical.max,M.APSTILE.options.crs.gcrs.horizontal.max]);} - }, - map: { - horizontal: { - name: "i", - min: 0, - max: map => map.getSize().x - }, - vertical: { - name: "j", - min: 0, - max: map => map.getSize().y - }, - bounds: map => L.bounds(L.point([0,0]),map.getSize()) - }, - tile: { - horizontal: { - name: "i", - min: 0, - max: 256 - }, - vertical: { - name: "j", - min: 0, - max: 256 - }, - get bounds() {return L.bounds( - [M.APSTILE.options.crs.tile.horizontal.min,M.APSTILE.options.crs.tile.vertical.min], - [M.APSTILE.options.crs.tile.horizontal.max,M.APSTILE.options.crs.tile.vertical.max]);} - }, - tilematrix: { - horizontal: { - name: "column", - min: 0, - max: zoom => (Math.round(M.APSTILE.options.crs.tcrs.horizontal.max(zoom) / M.APSTILE.options.crs.tile.bounds.getSize().x)) - }, - vertical: { - name: "row", - min: 0, - max: zoom => (Math.round(M.APSTILE.options.crs.tcrs.vertical.max(zoom) / M.APSTILE.options.crs.tile.bounds.getSize().y)) - }, - bounds: zoom => L.bounds([0,0], - [M.APSTILE.options.crs.tilematrix.horizontal.max(zoom), - M.APSTILE.options.crs.tilematrix.vertical.max(zoom)]) - } - } - }); - M.OSMTILE = L.CRS.EPSG3857; - L.setOptions(M.OSMTILE, { - origin: [-20037508.342787, 20037508.342787], - bounds: L.bounds([[-20037508.342787, -20037508.342787],[20037508.342787, 20037508.342787]]), - resolutions: [ - 156543.0339, - 78271.51695, - 39135.758475, - 19567.8792375, - 9783.93961875, - 4891.969809375, - 2445.9849046875, - 1222.9924523438, - 611.49622617188, - 305.74811308594, - 152.87405654297, - 76.437028271484, - 38.218514135742, - 19.109257067871, - 9.5546285339355, - 4.7773142669678, - 2.3886571334839, - 1.1943285667419, - 0.59716428337097, - 0.29858214168549, - 0.14929107084274, - 0.074645535421371, - 0.03732276771068573, - 0.018661383855342865, - 0.009330691927671432495 - ], - crs: { - tcrs: { - horizontal: { - name: "x", - min: 0, - max: zoom => (Math.round(M.OSMTILE.options.bounds.getSize().x / M.OSMTILE.options.resolutions[zoom])) - }, - vertical: { - name: "y", - min:0, - max: zoom => (Math.round(M.OSMTILE.options.bounds.getSize().y / M.OSMTILE.options.resolutions[zoom])) - }, - bounds: zoom => L.bounds([M.OSMTILE.options.crs.tcrs.horizontal.min, - M.OSMTILE.options.crs.tcrs.vertical.min], - [M.OSMTILE.options.crs.tcrs.horizontal.max(zoom), - M.OSMTILE.options.crs.tcrs.vertical.max(zoom)]) - }, - pcrs: { - horizontal: { - name: "easting", - get min() {return M.OSMTILE.options.bounds.min.x;}, - get max() {return M.OSMTILE.options.bounds.max.x;} - }, - vertical: { - name: "northing", - get min() {return M.OSMTILE.options.bounds.min.y;}, - get max() {return M.OSMTILE.options.bounds.max.y;} - }, - get bounds() {return M.OSMTILE.options.bounds;} - }, - gcrs: { - horizontal: { - name: "longitude", - get min() {return M.OSMTILE.unproject(M.OSMTILE.options.bounds.min).lng;}, - get max() {return M.OSMTILE.unproject(M.OSMTILE.options.bounds.max).lng;} - }, - vertical: { - name: "latitude", - get min() {return M.OSMTILE.unproject(M.OSMTILE.options.bounds.min).lat;}, - get max() {return M.OSMTILE.unproject(M.OSMTILE.options.bounds.max).lat;} - }, - get bounds() {return L.latLngBounds( - [M.OSMTILE.options.crs.gcrs.vertical.min,M.OSMTILE.options.crs.gcrs.horizontal.min], - [M.OSMTILE.options.crs.gcrs.vertical.max,M.OSMTILE.options.crs.gcrs.horizontal.max]);} - }, - map: { - horizontal: { - name: "i", - min: 0, - max: map => map.getSize().x - }, - vertical: { - name: "j", - min: 0, - max: map => map.getSize().y - }, - bounds: map => L.bounds(L.point([0,0]),map.getSize()) - }, - tile: { - horizontal: { - name: "i", - min: 0, - max: 256 - }, - vertical: { - name: "j", - min: 0, - max: 256 - }, - get bounds() {return L.bounds( - [M.OSMTILE.options.crs.tile.horizontal.min,M.OSMTILE.options.crs.tile.vertical.min], - [M.OSMTILE.options.crs.tile.horizontal.max,M.OSMTILE.options.crs.tile.vertical.max]);} - }, - tilematrix: { - horizontal: { - name: "column", - min: 0, - max: zoom => (Math.round(M.OSMTILE.options.crs.tcrs.horizontal.max(zoom) / M.OSMTILE.options.crs.tile.bounds.getSize().x)) - }, - vertical: { - name: "row", - min: 0, - max: zoom => (Math.round(M.OSMTILE.options.crs.tcrs.vertical.max(zoom) / M.OSMTILE.options.crs.tile.bounds.getSize().y)) - }, - bounds: zoom => L.bounds([0,0], - [M.OSMTILE.options.crs.tilematrix.horizontal.max(zoom), - M.OSMTILE.options.crs.tilematrix.vertical.max(zoom)]) - } - } - }); -}()); - -M._handleLink = Util._handleLink; -M.convertPCRSBounds = Util.convertPCRSBounds; -M.axisToXY = Util.axisToXY; -M.csToAxes = Util.csToAxes; -M._convertAndFormatPCRS = Util._convertAndFormatPCRS; -M.axisToCS = Util.axisToCS; -M._parseNumber = Util._parseNumber; -M._extractInputBounds = Util._extractInputBounds; -M._splitCoordinate = Util._splitCoordinate; -M.boundsToPCRSBounds = Util.boundsToPCRSBounds; -M.pixelToPCRSBounds = Util.pixelToPCRSBounds; -M._metaContentToObject = Util._metaContentToObject; -M._coordsToArray = Util._coordsToArray; -M._parseStylesheetAsHTML = Util._parseStylesheetAsHTML; -M.pointToPCRSPoint = Util.pointToPCRSPoint; -M.pixelToPCRSPoint = Util.pixelToPCRSPoint; -M._gcrsToTileMatrix = Util._gcrsToTileMatrix; -M._pasteLayer = Util._pasteLayer; -M._properties2Table = Util._properties2Table; -M._updateExtent = Util._updateExtent; -M.geojson2mapml = Util.geojson2mapml; -M._breakArray = Util._breakArray; -M._table2properties = Util._table2properties; -M._geometry2geojson = Util._geometry2geojson; -M._pcrsToGcrs = Util._pcrsToGcrs; -M.mapml2geojson = Util.mapml2geojson; - -M.QueryHandler = QueryHandler; -M.ContextMenu = ContextMenu; -M.AnnounceMovement = AnnounceMovement; -M.FeatureIndex = FeatureIndex; - -// see https://leafletjs.com/examples/extending/extending-3-controls.html#handlers -L.Map.addInitHook('addHandler', 'query', M.QueryHandler); -L.Map.addInitHook('addHandler', 'contextMenu', M.ContextMenu); -L.Map.addInitHook('addHandler', 'announceMovement', M.AnnounceMovement); -L.Map.addInitHook('addHandler', 'featureIndex', M.FeatureIndex); - -M.MapMLLayer = MapMLLayer; -M.mapMLLayer = mapMLLayer; - -M.ImageLayer = ImageLayer; -M.imageLayer = imageLayer; - -M.TemplatedImageLayer = TemplatedImageLayer; -M.templatedImageLayer = templatedImageLayer; - -M.TemplatedFeaturesLayer = TemplatedFeaturesLayer; -M.templatedFeaturesLayer = templatedFeaturesLayer; - -M.TemplatedLayer = TemplatedLayer; -M.templatedLayer = templatedLayer; - -M.TemplatedTileLayer = TemplatedTileLayer; -M.templatedTileLayer = templatedTileLayer; - -M.FeatureLayer = FeatureLayer; -M.featureLayer = featureLayer; - -M.LayerControl = LayerControl; -M.layerControl = layerControl; - -M.ReloadButton = ReloadButton; -M.reloadButton = reloadButton; - -M.FullscreenButton = FullscreenButton; -M.fullscreenButton = fullscreenButton; - -M.attributionControl = attributionControl; - -M.geolocationButton = geolocationButton; - -M.StaticTileLayer = StaticTileLayer; -M.staticTileLayer = staticTileLayer; - -M.DebugOverlay = DebugOverlay; -M.debugOverlay = debugOverlay; - -M.Crosshair = Crosshair; -M.crosshair = crosshair; - -M.FeatureIndexOverlay = FeatureIndexOverlay; -M.featureIndexOverlay = featureIndexOverlay; - -M.Feature = Feature; -M.feature = feature; - -M.FeatureRenderer = FeatureRenderer; -M.featureRenderer = featureRenderer; - -M.FeatureGroup = FeatureGroup; -M.featureGroup = featureGroup; - -}(window, document)); From 49b3487345fc7462d6bd7a685ba9618555fddae0 Mon Sep 17 00:00:00 2001 From: Jacky0299 <83443908+Jacky0299@users.noreply.github.com> Date: Wed, 15 Mar 2023 15:55:59 -0400 Subject: [PATCH 03/36] Delete multipleExtents.html --- test/e2e/layers/multipleExtents.html | 99 ---------------------------- 1 file changed, 99 deletions(-) delete mode 100644 test/e2e/layers/multipleExtents.html diff --git a/test/e2e/layers/multipleExtents.html b/test/e2e/layers/multipleExtents.html deleted file mode 100644 index 058d693b9..000000000 --- a/test/e2e/layers/multipleExtents.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - index-map.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From aadc794bcd8cf3913cbb5b3523807d847b197cb4 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 15 Mar 2023 16:15:13 -0400 Subject: [PATCH 04/36] some changes to the file --- src/mapml/index.js | 2 +- test/e2e/layers/multipleExtents.html | 154 ++++++++++----------------- 2 files changed, 58 insertions(+), 98 deletions(-) diff --git a/src/mapml/index.js b/src/mapml/index.js index 8ac91423f..df601a660 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -53,7 +53,7 @@ import { ContextMenu } from './handlers/ContextMenu'; import { Util } from './utils/Util'; import { ReloadButton, reloadButton } from './control/ReloadButton'; import { FullscreenButton, fullscreenButton } from './control/FullscreenButton'; -import {attributionControl} from "./control/AttributionControl"; +import {attributionControl} from "./control/AttributionControl"; import {geolocationButton} from "./control/GeolocationButton"; import { Crosshair, crosshair } from "./layers/Crosshair"; import { Feature, feature } from "./features/feature"; diff --git a/test/e2e/layers/multipleExtents.html b/test/e2e/layers/multipleExtents.html index 058d693b9..a531d2230 100644 --- a/test/e2e/layers/multipleExtents.html +++ b/test/e2e/layers/multipleExtents.html @@ -1,99 +1,59 @@ - + - - - - index-map.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + Multiple Extents Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From af8505872b5b4aed3e43f577672b8e42a2526533 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Thu, 16 Mar 2023 10:17:05 -0400 Subject: [PATCH 05/36] button name change --- src/mapml-viewer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 22c4b0fed..5cacbc5bf 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -328,6 +328,9 @@ export class MapViewer extends HTMLElement { totalSize += 49; this._geolocationButton = M.geolocationButton({ showPopup: false, + strings: { + title: "Locate Button" + }, position: "bottomright", locateOptions: { maxZoom: 16 From b86c0d4ef537a2a7e8b1e3d9f646c645805c5011 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Mon, 20 Mar 2023 09:51:18 -0400 Subject: [PATCH 06/36] minor edits --- package-lock.json | 10 +++++----- package.json | 8 +++----- src/mapml/control/GeolocationButton.js | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index e331ba6ed..71079f776 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "@maps4html/web-map-custom-element", "version": "0.10.1", "license": "W3C", - "dependencies": { - "leaflet.locatecontrol": "^0.79.0" - }, "devDependencies": { "@playwright/test": "^1.24.2", "diff": "^5.1.0", @@ -30,6 +27,7 @@ "jest-environment-node": "^26.6.1", "jest-playwright-preset": "^1.7.0", "leaflet": "^1.9.3", + "leaflet.locatecontrol": "^0.79.0", "path": "^0.12.7", "playwright": "^1.24.2", "proj4": "^2.6.2", @@ -8648,7 +8646,8 @@ "node_modules/leaflet.locatecontrol": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/leaflet.locatecontrol/-/leaflet.locatecontrol-0.79.0.tgz", - "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==" + "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==", + "dev": true }, "node_modules/leven": { "version": "3.1.0", @@ -18898,7 +18897,8 @@ "leaflet.locatecontrol": { "version": "0.79.0", "resolved": "https://registry.npmjs.org/leaflet.locatecontrol/-/leaflet.locatecontrol-0.79.0.tgz", - "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==" + "integrity": "sha512-h64QIHFkypYdr90lkSfjKvPvvk8/b8UnP3m9WuoWdp5p2AaCWC0T1NVwyuj4rd5U4fBW3tQt4ppmZ2LceHMIDg==", + "dev": true }, "leven": { "version": "3.1.0", diff --git a/package.json b/package.json index d393f5380..012eb29ac 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "jest": "jest --verbose --noStackTrace" }, "devDependencies": { - "@playwright/test": "^1.24.2", "diff": "^5.1.0", "express": "^4.17.1", "grunt": "^1.4.0", @@ -54,7 +53,9 @@ "jest-environment-node": "^26.6.1", "jest-playwright-preset": "^1.7.0", "leaflet": "^1.9.3", + "leaflet.locatecontrol": "^0.79.0", "path": "^0.12.7", + "@playwright/test": "^1.24.2", "playwright": "^1.24.2", "proj4": "^2.6.2", "proj4leaflet": "^1.0.2", @@ -63,8 +64,5 @@ "files": [ "dist", "*.md" - ], - "dependencies": { - "leaflet.locatecontrol": "^0.79.0" - } + ] } diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index 412dc2958..dd19bdb62 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,4 +1,4 @@ import '../../../dist/L.Control.Locate'; export var geolocationButton = function (options, map) { L.control.locate(options).addTo(map); -}; \ No newline at end of file +}; From 387ffda8c56704694638b253af588a255089769b Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Mon, 20 Mar 2023 13:15:38 -0400 Subject: [PATCH 07/36] geolocation sync with controlslist api --- src/mapml-viewer.js | 15 ++++++++++++--- src/mapml/control/GeolocationButton.js | 2 +- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index b63e4c73f..390b3b6eb 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -134,7 +134,7 @@ export class MapViewer extends HTMLElement { this._controlsList = new DOMTokenList( this.getAttribute("controlslist"), this, "controlslist", - ["noreload","nofullscreen","nozoom","nolayer"] + ["noreload","nofullscreen","nozoom","nolayer","geolocation"] ); // the dimension attributes win, if they're there. A map does not @@ -354,8 +354,7 @@ export class MapViewer extends HTMLElement { totalSize += 49; this._fullScreenControl = M.fullscreenButton().addTo(this._map); } - if (this.getAttribute("controlslist") === "geolocation" && !this._geolocationButton && (totalSize + 49) <= mapSize){ - totalSize += 49; + if (!this._geolocationButton){ this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { @@ -385,12 +384,14 @@ export class MapViewer extends HTMLElement { this._setControlsVisibility("layercontrol",true); this._setControlsVisibility("reload",true); this._setControlsVisibility("zoom",true); + this._setControlsVisibility("geolocation",false); } _showControls() { this._setControlsVisibility("fullscreen",false); this._setControlsVisibility("layercontrol",false); this._setControlsVisibility("reload",false); this._setControlsVisibility("zoom",false); + this._setControlsVisibility("geolocation",true); // prune the controls shown if necessary // this logic could be embedded in _showControls @@ -411,6 +412,9 @@ export class MapViewer extends HTMLElement { case 'nozoom': this._setControlsVisibility("zoom",true); break; + case 'geolocation': + this._setControlsVisibility("geolocation",false); + break; } }); } @@ -444,6 +448,11 @@ export class MapViewer extends HTMLElement { container = this._layerControl._container; } break; + case "geolocation": + if (this._geolocationButton) { + container = this._geolocationButton._container; + } + break; } if (container) { if (hide) { diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index dd19bdb62..ad632c6b5 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,4 +1,4 @@ import '../../../dist/L.Control.Locate'; export var geolocationButton = function (options, map) { - L.control.locate(options).addTo(map); + return L.control.locate(options).addTo(map); }; From 8cd9162e0df4a204a59f226a74d7764ccf387592 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Mon, 20 Mar 2023 16:36:18 -0400 Subject: [PATCH 08/36] tests fixed and syncing web-map --- src/mapml-viewer.js | 2 +- src/web-map.js | 24 +++++++++++++++++++++++- test/e2e/api/domApi-mapml-viewer.test.js | 17 +++++++++++++++++ test/e2e/api/domApi-web-map.test.js | 17 +++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 390b3b6eb..bc68d87cf 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -384,7 +384,7 @@ export class MapViewer extends HTMLElement { this._setControlsVisibility("layercontrol",true); this._setControlsVisibility("reload",true); this._setControlsVisibility("zoom",true); - this._setControlsVisibility("geolocation",false); + this._setControlsVisibility("geolocation",true); } _showControls() { this._setControlsVisibility("fullscreen",false); diff --git a/src/web-map.js b/src/web-map.js index f987ff0a4..2bbdcf699 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -138,7 +138,7 @@ export class WebMap extends HTMLMapElement { this._controlsList = new DOMTokenList( this.getAttribute("controlslist"), this, "controlslist", - ["noreload","nofullscreen","nozoom","nolayer"] + ["noreload","nofullscreen","nozoom","nolayer","geolocation"] ); // the dimension attributes win, if they're there. A map does not @@ -397,6 +397,18 @@ export class WebMap extends HTMLMapElement { totalSize += 49; this._fullScreenControl = M.fullscreenButton().addTo(this._map); } + if (!this._geolocationButton){ + this._geolocationButton = M.geolocationButton({ + showPopup: false, + strings: { + title: "Locate" + }, + position: "bottomright", + locateOptions: { + maxZoom: 16 + }, + },this._map); + } } // Sets controls by hiding/unhiding them based on the map attribute @@ -415,12 +427,14 @@ export class WebMap extends HTMLMapElement { this._setControlsVisibility("layercontrol",true); this._setControlsVisibility("reload",true); this._setControlsVisibility("zoom",true); + this._setControlsVisibility("geolocation",true); } _showControls() { this._setControlsVisibility("fullscreen",false); this._setControlsVisibility("layercontrol",false); this._setControlsVisibility("reload",false); this._setControlsVisibility("zoom",false); + this._setControlsVisibility("geolocation",true); // prune the controls shown if necessary // this logic could be embedded in _showControls @@ -441,6 +455,9 @@ export class WebMap extends HTMLMapElement { case 'nozoom': this._setControlsVisibility("zoom",true); break; + case 'geolocation': + this._setControlsVisibility("geolocation",false); + break; } }); } @@ -474,6 +491,11 @@ export class WebMap extends HTMLMapElement { container = this._layerControl._container; } break; + case "geolocation": + if (this._geolocationButton) { + container = this._geolocationButton._container; + } + break; } if (container) { if (hide) { diff --git a/test/e2e/api/domApi-mapml-viewer.test.js b/test/e2e/api/domApi-mapml-viewer.test.js index b5547b91f..3d256f73e 100644 --- a/test/e2e/api/domApi-mapml-viewer.test.js +++ b/test/e2e/api/domApi-mapml-viewer.test.js @@ -300,7 +300,24 @@ test.describe("mapml-viewer DOM API Tests", () => { expect(fullscreenHidden).toEqual(false); expect(layerControlHidden).toEqual(true); }); + test("controlslist geolocations test", async () => { + const viewerHandle = await page.evaluateHandle(() => document.querySelector('mapml-viewer')); + await page.evaluate( viewer => viewer.setAttribute("controlslist","geolocation"), viewerHandle); + let hascontrolslist = await page.evaluate( viewer => viewer.getAttribute("controlslist"), viewerHandle); + expect(hascontrolslist).toEqual('geolocation'); + + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(false); + //remove geolocation + await page.evaluate( viewer => viewer.controlsList = "noreload", viewerHandle); + hascontrolslist = await page.evaluate( viewer => viewer.getAttribute("controlslist"), viewerHandle); + expect(hascontrolslist).toEqual('noreload'); + let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); + geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); + expect(reloadHidden).toEqual(true); + }); test("controlslist removeAttribute", async () => { const viewerHandle = await page.evaluateHandle(() => document.querySelector('mapml-viewer')); // removeAttribute diff --git a/test/e2e/api/domApi-web-map.test.js b/test/e2e/api/domApi-web-map.test.js index f7ed8385a..744e64318 100644 --- a/test/e2e/api/domApi-web-map.test.js +++ b/test/e2e/api/domApi-web-map.test.js @@ -304,7 +304,24 @@ test.describe("web-map DOM API Tests", () => { expect(fullscreenHidden).toEqual(false); expect(layerControlHidden).toEqual(true); }); + test("controlslist geolocations test", async () => { + const viewerHandle = await page.evaluateHandle(() => document.querySelector('map')); + await page.evaluate( map => map.setAttribute("controlslist","geolocation"), viewerHandle); + let hascontrolslist = await page.evaluate( map => map.getAttribute("controlslist"), viewerHandle); + expect(hascontrolslist).toEqual('geolocation'); + + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(false); + //remove geolocation + await page.evaluate( map => map.controlsList = "noreload", viewerHandle); + hascontrolslist = await page.evaluate( viewer => viewer.getAttribute("controlslist"), viewerHandle); + expect(hascontrolslist).toEqual('noreload'); + let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); + geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); + expect(reloadHidden).toEqual(true); + }); test("controlslist removeAttribute", async () => { const mapHandle = await page.evaluateHandle(() => document.querySelector('map')); // removeAttribute From 94c69e1d6f665b465f1c4bd6c000b9d9973f81ab Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Tue, 21 Mar 2023 15:08:06 -0400 Subject: [PATCH 09/36] Peter suggested changes --- src/mapml-viewer.js | 4 ++-- src/web-map.js | 4 ++-- test/e2e/api/domApi-mapml-viewer.test.js | 18 ++++++++++++++++++ test/e2e/api/domApi-web-map.test.js | 18 ++++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index bc68d87cf..4003da8e0 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -358,11 +358,11 @@ export class MapViewer extends HTMLElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: "Locate" + title: "Show my location" }, position: "bottomright", locateOptions: { - maxZoom: 16 + maxZoom: 24 }, },this._map); } diff --git a/src/web-map.js b/src/web-map.js index 2bbdcf699..81b446958 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -401,11 +401,11 @@ export class WebMap extends HTMLMapElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: "Locate" + title: "Show my location" }, position: "bottomright", locateOptions: { - maxZoom: 16 + maxZoom: 24 }, },this._map); } diff --git a/test/e2e/api/domApi-mapml-viewer.test.js b/test/e2e/api/domApi-mapml-viewer.test.js index 3d256f73e..a0bfca99c 100644 --- a/test/e2e/api/domApi-mapml-viewer.test.js +++ b/test/e2e/api/domApi-mapml-viewer.test.js @@ -96,6 +96,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -135,6 +137,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -153,6 +157,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -168,6 +174,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(false); expect(reloadHidden).toEqual(false); expect(fullscreenHidden).toEqual(false); @@ -231,6 +239,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(false); expect(fullscreenHidden).toEqual(true); @@ -250,6 +260,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(false); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(false); @@ -264,6 +276,8 @@ test.describe("mapml-viewer DOM API Tests", () => { reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(false); @@ -281,6 +295,8 @@ test.describe("mapml-viewer DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -295,6 +311,8 @@ test.describe("mapml-viewer DOM API Tests", () => { reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(false); diff --git a/test/e2e/api/domApi-web-map.test.js b/test/e2e/api/domApi-web-map.test.js index 744e64318..a0791a3fe 100644 --- a/test/e2e/api/domApi-web-map.test.js +++ b/test/e2e/api/domApi-web-map.test.js @@ -97,6 +97,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -137,6 +139,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -155,6 +159,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -170,6 +176,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(false); expect(reloadHidden).toEqual(false); expect(fullscreenHidden).toEqual(false); @@ -235,6 +243,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(false); expect(fullscreenHidden).toEqual(true); @@ -254,6 +264,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(false); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(false); @@ -268,6 +280,8 @@ test.describe("web-map DOM API Tests", () => { reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(false); @@ -285,6 +299,8 @@ test.describe("web-map DOM API Tests", () => { let reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); let fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); let layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + let geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(true); @@ -299,6 +315,8 @@ test.describe("web-map DOM API Tests", () => { reloadHidden = await page.$eval(".leaflet-top.leaflet-left > .mapml-reload-button", (div) => div.hidden); fullscreenHidden = await page.$eval(".leaflet-top.leaflet-left > .leaflet-control-fullscreen", (div) => div.hidden); layerControlHidden = await page.$eval(".leaflet-top.leaflet-right > .leaflet-control-layers", (div) => div.hidden); + geolocation = await page.$eval(".leaflet-bottom.leaflet-right > .leaflet-control-locate", (div) => div.hidden); + expect(geolocation).toEqual(true); expect(zoomHidden).toEqual(true); expect(reloadHidden).toEqual(true); expect(fullscreenHidden).toEqual(false); From f13c8f0821fb4d9f233cc0f69282224cebe3c470 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Thu, 23 Mar 2023 09:52:03 -0400 Subject: [PATCH 10/36] locate api --- src/mapml-viewer.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 4003da8e0..cc66c3cc3 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -547,6 +547,24 @@ export class MapViewer extends HTMLElement { {target: this}})); } }); + this._map.on('locationfound', + function (e) { + this.dispatchEvent(new CustomEvent('maplocationfound', {detail: + {latlng: e.latlng, radius: e.accuracy} + })); + },this); + this.addEventListener('maplocationfound', function(e) { + //'Location found:', e.detail.latlng, 'Radius:', e.detail.radius + }); + this._map.on('locationerror', + function (e) { + this.dispatchEvent(new CustomEvent('locationerror', {detail: + {error:e.message} + })); + },this); + this.addEventListener('locationerror', function(e) { + //error:e.detail.error + }); this._map.on('load', function () { this.dispatchEvent(new CustomEvent('load', {detail: {target: this}})); @@ -678,6 +696,20 @@ export class MapViewer extends HTMLElement { } }); } + locate(options){ + if (this._map) { + if (options) { + if (options.zoomTo) { + options.setView = options.zoomTo; + delete options.zoomTo; + } + this._map.locate(options); + } else { + this._map.locate({setView: true, maxZoom: 24}); + } + } + } + toggleDebug(){ if(this._debug){ this._debug.remove(); From f155ba02a486c8e29f419d30762b9ad468ed0270 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Fri, 24 Mar 2023 13:24:21 -0400 Subject: [PATCH 11/36] locateapi and locate button test --- src/mapml-viewer.js | 4 +- test/e2e/api/locateApi.html | 85 ++++++++++++++++++++++++++++++++++ test/e2e/api/locateApi.test.js | 57 +++++++++++++++++++++++ 3 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 test/e2e/api/locateApi.html create mode 100644 test/e2e/api/locateApi.test.js diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index cc66c3cc3..dc4942682 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -362,7 +362,7 @@ export class MapViewer extends HTMLElement { }, position: "bottomright", locateOptions: { - maxZoom: 24 + maxZoom: 16 }, },this._map); } @@ -705,7 +705,7 @@ export class MapViewer extends HTMLElement { } this._map.locate(options); } else { - this._map.locate({setView: true, maxZoom: 24}); + this._map.locate({setView: true, maxZoom: 16}); } } } diff --git a/test/e2e/api/locateApi.html b/test/e2e/api/locateApi.html new file mode 100644 index 000000000..100f3140a --- /dev/null +++ b/test/e2e/api/locateApi.html @@ -0,0 +1,85 @@ + + + + + + locateApi.html + + + + + + + + + + + diff --git a/test/e2e/api/locateApi.test.js b/test/e2e/api/locateApi.test.js new file mode 100644 index 000000000..d3ab6de8a --- /dev/null +++ b/test/e2e/api/locateApi.test.js @@ -0,0 +1,57 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.use({ + geolocation: { longitude: -73.56766530667056, latitude: 45.5027789304487 }, + permissions: ['geolocation'], +}); + +test.describe("Locate API Test", () => { + let page; + let context; + test.beforeAll(async function() { + context = await chromium.launchPersistentContext(''); + page = context.pages().find((page) => page.url() === 'about:blank') || await context.newPage(); + await context.grantPermissions(['geolocation']); + await page.goto("locateApi.html"); + }); + test.afterAll(async function () { + await context.close(); + }); + + test("Using locate API and locate button to find myself", async () => { + await context.grantPermissions(['geolocation']); + await page.$eval("body > mapml-viewer",(viewer) => viewer.locate()); + + let locateAPI_lat = await page.$eval("body > mapml-viewer", (viewer) => viewer.lat); + let locateAPI_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); + //rounding to three decimal places + locateAPI_lat = parseFloat(locateAPI_lat).toFixed(3); + locateAPI_lng = parseFloat(locateAPI_lng).toFixed(3); + + await page.reload(); + + await page.click("body > mapml-viewer"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + + let locateButton_lat = await page.$eval("body > mapml-viewer", (viewer) => viewer.lat); + let locateButton_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); + locateButton_lat = parseFloat(locateButton_lat).toFixed(3); + locateButton_lng = parseFloat(locateButton_lng).toFixed(3); + + expect(locateButton_lat).toEqual(locateAPI_lat); + expect(locateButton_lng).toEqual(locateAPI_lng); + expect(locateButton_lat).toEqual("45.503"); + expect(locateButton_lng).toEqual("-73.568"); + + }); +}); + + + + \ No newline at end of file From 495c551f7daf4fe09aa9a368484e72216d46ced8 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Mon, 27 Mar 2023 15:07:54 -0400 Subject: [PATCH 12/36] tests --- test/e2e/api/locateApi.html | 6 -- test/e2e/api/locateApi.test.js | 59 +++++++++------- test/e2e/mapml-viewer/locateButton.html | 79 ++++++++++++++++++++++ test/e2e/mapml-viewer/locateButton.test.js | 44 ++++++++++++ 4 files changed, 158 insertions(+), 30 deletions(-) create mode 100644 test/e2e/mapml-viewer/locateButton.html create mode 100644 test/e2e/mapml-viewer/locateButton.test.js diff --git a/test/e2e/api/locateApi.html b/test/e2e/api/locateApi.html index 100f3140a..bfce47af1 100644 --- a/test/e2e/api/locateApi.html +++ b/test/e2e/api/locateApi.html @@ -75,11 +75,5 @@ - diff --git a/test/e2e/api/locateApi.test.js b/test/e2e/api/locateApi.test.js index d3ab6de8a..8c04e7bdf 100644 --- a/test/e2e/api/locateApi.test.js +++ b/test/e2e/api/locateApi.test.js @@ -18,8 +18,7 @@ test.describe("Locate API Test", () => { await context.close(); }); - test("Using locate API and locate button to find myself", async () => { - await context.grantPermissions(['geolocation']); + test("Using locate API to find myself", async () => { await page.$eval("body > mapml-viewer",(viewer) => viewer.locate()); let locateAPI_lat = await page.$eval("body > mapml-viewer", (viewer) => viewer.lat); @@ -27,28 +26,40 @@ test.describe("Locate API Test", () => { //rounding to three decimal places locateAPI_lat = parseFloat(locateAPI_lat).toFixed(3); locateAPI_lng = parseFloat(locateAPI_lng).toFixed(3); - - await page.reload(); - - await page.click("body > mapml-viewer"); - await page.keyboard.press("Tab"); - await page.keyboard.press("Tab"); - await page.keyboard.press("Tab"); - await page.keyboard.press("Tab"); - await page.keyboard.press("Tab"); - await page.keyboard.press("Tab"); - await page.keyboard.press("Enter"); - - let locateButton_lat = await page.$eval("body > mapml-viewer", (viewer) => viewer.lat); - let locateButton_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); - locateButton_lat = parseFloat(locateButton_lat).toFixed(3); - locateButton_lng = parseFloat(locateButton_lng).toFixed(3); - - expect(locateButton_lat).toEqual(locateAPI_lat); - expect(locateButton_lng).toEqual(locateAPI_lng); - expect(locateButton_lat).toEqual("45.503"); - expect(locateButton_lng).toEqual("-73.568"); - + + expect(locateAPI_lat).toEqual("45.503"); + expect(locateAPI_lng).toEqual("-73.568"); + }); + + test("Testing maplocationfound event", async () => { + const latlng = await page.evaluate(() => { + const viewer = document.querySelector('body > mapml-viewer'); + return new Promise((resolve) => { + viewer.addEventListener('maplocationfound', (e) => { + resolve(e.detail.latlng); + }, { once: true }); + viewer.locate(); + }); + }); + expect(latlng.lat).toEqual(45.5027789304487); + expect(latlng.lng).toEqual(-73.56766530667056); + }); + + test("Testing locationerror event", async () => { + const error = await page.evaluate(() => { + const viewer = document.querySelector('body > mapml-viewer'); + return new Promise((resolve) => { + viewer.addEventListener('locationerror', (e) => { + resolve(e.detail.error); + }, { once: true }); + const errorEvent = new CustomEvent('locationerror', { + detail: { error: 'Your location could not be determined.' } + }); + viewer.dispatchEvent(errorEvent); + viewer.locate(); + }); + }); + expect(error).toEqual("Your location could not be determined."); }); }); diff --git a/test/e2e/mapml-viewer/locateButton.html b/test/e2e/mapml-viewer/locateButton.html new file mode 100644 index 000000000..bfce47af1 --- /dev/null +++ b/test/e2e/mapml-viewer/locateButton.html @@ -0,0 +1,79 @@ + + + + + + locateApi.html + + + + + + + + + + diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js new file mode 100644 index 000000000..e5ad44b1b --- /dev/null +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -0,0 +1,44 @@ +import { test, expect, chromium } from '@playwright/test'; + +test.use({ + geolocation: { longitude: -73.56766530667056, latitude: 45.5027789304487 }, + permissions: ['geolocation'], +}); + +test.describe("Locate Button Test", () => { + let page; + let context; + test.beforeAll(async function() { + context = await chromium.launchPersistentContext(''); + page = context.pages().find((page) => page.url() === 'about:blank') || await context.newPage(); + await context.grantPermissions(['geolocation']); + await page.goto("locateApi.html"); + }); + test.afterAll(async function () { + await context.close(); + }); + + test("Using locate button to find myself", async () => { + await page.click("body > mapml-viewer"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + + let locateButton_lat = await page.$eval("body > mapml-viewer", (viewer) => viewer.lat); + let locateButton_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); + locateButton_lat = parseFloat(locateButton_lat).toFixed(3); + locateButton_lng = parseFloat(locateButton_lng).toFixed(3); + + expect(locateButton_lat).toEqual("45.503"); + expect(locateButton_lng).toEqual("-73.568"); + + }); +}); + + + + \ No newline at end of file From d4bcb2caf74c1475f07fc9b236f2672f86223c30 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Tue, 28 Mar 2023 16:55:57 -0400 Subject: [PATCH 13/36] locate button title changes when its state changes --- src/mapml-viewer.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index dc4942682..2fbc98365 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -358,7 +358,7 @@ export class MapViewer extends HTMLElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: "Show my location" + title: 'Show my location - location off', }, position: "bottomright", locateOptions: { @@ -547,6 +547,21 @@ export class MapViewer extends HTMLElement { {target: this}})); } }); + var locateControl = this._geolocationButton._container; + var observer = new MutationObserver(function(mutations) { + // Check the current state of the control + if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { + locateControl.firstChild.title = "Show my location control - location tracking mode"; + } else if (locateControl.classList.contains('active')) { + locateControl.firstChild.title = "Show my location - last known location mode"; + } else { + locateControl.firstChild.title = "Show my location - location off"; + } + }); + // Configure the observer to watch for changes to the class name + var observerConfig = { attributes: true, attributeFilter: ['class'] }; + observer.observe(locateControl, observerConfig); + this._map.on('locationfound', function (e) { this.dispatchEvent(new CustomEvent('maplocationfound', {detail: From f0220f3ba22e5bcf81996da9219da826b77839da Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 29 Mar 2023 09:52:16 -0400 Subject: [PATCH 14/36] locatebutton states test --- test/e2e/mapml-viewer/locateButton.html | 6 ++-- test/e2e/mapml-viewer/locateButton.test.js | 32 +++++++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/test/e2e/mapml-viewer/locateButton.html b/test/e2e/mapml-viewer/locateButton.html index bfce47af1..5a256a095 100644 --- a/test/e2e/mapml-viewer/locateButton.html +++ b/test/e2e/mapml-viewer/locateButton.html @@ -24,8 +24,8 @@ max-width: 100%; /* Full viewport. */ - width: 300px; - height: 300px; + width: 100%; + height: 100%; /* Remove default (native-like) border. */ border: none; @@ -72,7 +72,7 @@ - + diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index e5ad44b1b..4ed6f493e 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -12,7 +12,7 @@ test.describe("Locate Button Test", () => { context = await chromium.launchPersistentContext(''); page = context.pages().find((page) => page.url() === 'about:blank') || await context.newPage(); await context.grantPermissions(['geolocation']); - await page.goto("locateApi.html"); + await page.goto("locateButton.html"); }); test.afterAll(async function () { await context.close(); @@ -37,6 +37,36 @@ test.describe("Locate Button Test", () => { expect(locateButton_lng).toEqual("-73.568"); }); + + test("Locate button state changes", async () => { + await page.click("body > mapml-viewer"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + + let locateButton_title1 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); + + expect(locateButton_title1).toEqual("Show my location - location off"); + await page.keyboard.press("Enter"); + + let locateButton_title2 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); + expect(locateButton_title2).toEqual("Show my location control - location tracking mode"); + + await page.click("body > mapml-viewer"); + + await page.mouse.move(600, 300); + await page.mouse.down(); + await page.mouse.move(1200, 450, {steps: 5}); + await page.mouse.up(); + await page.click("body > mapml-viewer"); + await page.pause(); + let locateButton_title3 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); + expect(locateButton_title3).toEqual("Show my location - last known location mode"); + }); }); From 709a288c7b187c421e7e475cf61645790542fd67 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 29 Mar 2023 09:58:07 -0400 Subject: [PATCH 15/36] web-map syncing --- src/web-map.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/web-map.js b/src/web-map.js index 81b446958..12ebdd0e1 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -401,11 +401,11 @@ export class WebMap extends HTMLMapElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: "Show my location" + title: "Show my location - location off" }, position: "bottomright", locateOptions: { - maxZoom: 24 + maxZoom: 16 }, },this._map); } @@ -590,6 +590,39 @@ export class WebMap extends HTMLMapElement { {target: this}})); } }); + var locateControl = this._geolocationButton._container; + var observer = new MutationObserver(function(mutations) { + // Check the current state of the control + if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { + locateControl.firstChild.title = "Show my location control - location tracking mode"; + } else if (locateControl.classList.contains('active')) { + locateControl.firstChild.title = "Show my location - last known location mode"; + } else { + locateControl.firstChild.title = "Show my location - location off"; + } + }); + // Configure the observer to watch for changes to the class name + var observerConfig = { attributes: true, attributeFilter: ['class'] }; + observer.observe(locateControl, observerConfig); + + this._map.on('locationfound', + function (e) { + this.dispatchEvent(new CustomEvent('maplocationfound', {detail: + {latlng: e.latlng, radius: e.accuracy} + })); + },this); + this.addEventListener('maplocationfound', function(e) { + //'Location found:', e.detail.latlng, 'Radius:', e.detail.radius + }); + this._map.on('locationerror', + function (e) { + this.dispatchEvent(new CustomEvent('locationerror', {detail: + {error:e.message} + })); + },this); + this.addEventListener('locationerror', function(e) { + //error:e.detail.error + }); this._map.on('load', function () { this.dispatchEvent(new CustomEvent('load', {detail: {target: this}})); @@ -721,6 +754,19 @@ export class WebMap extends HTMLMapElement { } }); } + locate(options){ + if (this._map) { + if (options) { + if (options.zoomTo) { + options.setView = options.zoomTo; + delete options.zoomTo; + } + this._map.locate(options); + } else { + this._map.locate({setView: true, maxZoom: 16}); + } + } + } toggleDebug(){ if(this._debug){ this._debug.remove(); From 88c449e5de76bee6ae63db2e5030588aade7586d Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 29 Mar 2023 10:19:20 -0400 Subject: [PATCH 16/36] Remove the word "control" from button title --- src/mapml-viewer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 2fbc98365..5e66190c0 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -551,7 +551,7 @@ export class MapViewer extends HTMLElement { var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = "Show my location control - location tracking mode"; + locateControl.firstChild.title = "Show my location - location tracking mode"; } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = "Show my location - last known location mode"; } else { From df109dfdeed172411587a3bb670d57bc66e9abf2 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 29 Mar 2023 10:23:10 -0400 Subject: [PATCH 17/36] Remove the word "control" from button title --- src/web-map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web-map.js b/src/web-map.js index 12ebdd0e1..c5a8007d7 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -594,7 +594,7 @@ export class WebMap extends HTMLMapElement { var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = "Show my location control - location tracking mode"; + locateControl.firstChild.title = "Show my location - location tracking mode"; } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = "Show my location - last known location mode"; } else { From c91d0c46e099b8fd310c35d4ed5f4dc20c652faf Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 29 Mar 2023 10:26:47 -0400 Subject: [PATCH 18/36] Fix test --- test/e2e/mapml-viewer/locateButton.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index 4ed6f493e..c56a4fcc1 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -54,7 +54,7 @@ test.describe("Locate Button Test", () => { await page.keyboard.press("Enter"); let locateButton_title2 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title2).toEqual("Show my location control - location tracking mode"); + expect(locateButton_title2).toEqual("Show my location - location tracking mode"); await page.click("body > mapml-viewer"); From 5b2b6ffec04f1b9ab273b3ab9d1b7d653bc16a1b Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 29 Mar 2023 11:50:38 -0400 Subject: [PATCH 19/36] grunt file change --- Gruntfile.js | 5 +++-- src/mapml/control/GeolocationButton.js | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 4e19409eb..306b22607 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -31,7 +31,8 @@ module.exports = function(grunt) { 'dist/layer.js': ['src/layer.js'], 'dist/leaflet.js': ['dist/leaflet-src.js', 'dist/proj4-src.js', - 'dist/proj4leaflet.js'] + 'dist/proj4leaflet.js', + 'dist/L.Control.Locate.js'] } } }, @@ -145,7 +146,7 @@ module.exports = function(grunt) { }, clean: { dist: ['dist'], - tidyup: ['dist/leaflet-src.js','dist/proj4-src.js','dist/proj4leaflet.js'], + tidyup: ['dist/leaflet-src.js','dist/proj4-src.js','dist/proj4leaflet.js','dist/L.Control.Locate.js'], experiments: { options: {force: true}, src: ['../experiments/dist'] diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index ad632c6b5..fddeea770 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,4 +1,3 @@ -import '../../../dist/L.Control.Locate'; export var geolocationButton = function (options, map) { return L.control.locate(options).addTo(map); }; From 9452f0ee73a145c014705e78fa272a1c546a1328 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 29 Mar 2023 13:06:48 -0400 Subject: [PATCH 20/36] Finalize text strings prior to localization --- src/mapml-viewer.js | 8 ++++---- src/web-map.js | 8 ++++---- test/e2e/mapml-viewer/locateButton.test.js | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 5e66190c0..01a6e5210 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -358,7 +358,7 @@ export class MapViewer extends HTMLElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: 'Show my location - location off', + title: 'Show my location - location tracking off', }, position: "bottomright", locateOptions: { @@ -551,11 +551,11 @@ export class MapViewer extends HTMLElement { var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = "Show my location - location tracking mode"; + locateControl.firstChild.title = "Show my location - location tracking on"; } else if (locateControl.classList.contains('active')) { - locateControl.firstChild.title = "Show my location - last known location mode"; + locateControl.firstChild.title = "Show my location - last known location shown"; } else { - locateControl.firstChild.title = "Show my location - location off"; + locateControl.firstChild.title = "Show my location - location tracking off"; } }); // Configure the observer to watch for changes to the class name diff --git a/src/web-map.js b/src/web-map.js index c5a8007d7..fb2ee6838 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -401,7 +401,7 @@ export class WebMap extends HTMLMapElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: "Show my location - location off" + title: "Show my location - location tracking off" }, position: "bottomright", locateOptions: { @@ -594,11 +594,11 @@ export class WebMap extends HTMLMapElement { var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = "Show my location - location tracking mode"; + locateControl.firstChild.title = "Show my location - location tracking on"; } else if (locateControl.classList.contains('active')) { - locateControl.firstChild.title = "Show my location - last known location mode"; + locateControl.firstChild.title = "Show my location - last known location shown"; } else { - locateControl.firstChild.title = "Show my location - location off"; + locateControl.firstChild.title = "Show my location - location tracking off"; } }); // Configure the observer to watch for changes to the class name diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index c56a4fcc1..ee5a2e03f 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -50,11 +50,11 @@ test.describe("Locate Button Test", () => { let locateButton_title1 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title1).toEqual("Show my location - location off"); + expect(locateButton_title1).toEqual("Show my location - location tracking off"); await page.keyboard.press("Enter"); let locateButton_title2 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title2).toEqual("Show my location - location tracking mode"); + expect(locateButton_title2).toEqual("Show my location - location tracking on"); await page.click("body > mapml-viewer"); @@ -65,7 +65,7 @@ test.describe("Locate Button Test", () => { await page.click("body > mapml-viewer"); await page.pause(); let locateButton_title3 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title3).toEqual("Show my location - last known location mode"); + expect(locateButton_title3).toEqual("Show my location - last known location shown"); }); }); From 1de354d25e4dc674b885eb10afe3ecadd5273673 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 29 Mar 2023 13:28:37 -0400 Subject: [PATCH 21/36] Localize text strings for location control button states --- src/mapml-viewer.js | 8 ++++---- src/mapml/options.js | 3 +++ src/web-map.js | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 01a6e5210..339cc3240 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -358,7 +358,7 @@ export class MapViewer extends HTMLElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: 'Show my location - location tracking off', + title: M.options.locale.btnLocTrackOff }, position: "bottomright", locateOptions: { @@ -551,11 +551,11 @@ export class MapViewer extends HTMLElement { var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = "Show my location - location tracking on"; + locateControl.firstChild.title = M.options.locale.btnLocTrackOn; } else if (locateControl.classList.contains('active')) { - locateControl.firstChild.title = "Show my location - last known location shown"; + locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; } else { - locateControl.firstChild.title = "Show my location - location tracking off"; + locateControl.firstChild.title = M.options.locale.btnLocTrackOff; } }); // Configure the observer to watch for changes to the class name diff --git a/src/mapml/options.js b/src/mapml/options.js index 85b40cc55..327437dc2 100644 --- a/src/mapml/options.js +++ b/src/mapml/options.js @@ -22,6 +22,9 @@ export var Options = { btnZoomOut: "Zoom out", btnFullScreen: "View Fullscreen", btnExitFullScreen: "Exit Fullscreen", + btnLocTrackOn: "Show my location - location tracking on", + btnLocTrackOff: "Show my location - location tracking off", + btnLocTrackLastKnown: "Show my location - last known location shown", amZoom: "zoom level", amColumn: "column", amRow: "row", diff --git a/src/web-map.js b/src/web-map.js index fb2ee6838..02e8a8a21 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -401,7 +401,7 @@ export class WebMap extends HTMLMapElement { this._geolocationButton = M.geolocationButton({ showPopup: false, strings: { - title: "Show my location - location tracking off" + title: M.options.locale.btnLocTrackOff }, position: "bottomright", locateOptions: { @@ -594,11 +594,11 @@ export class WebMap extends HTMLMapElement { var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = "Show my location - location tracking on"; + locateControl.firstChild.title = M.options.locale.btnLocTrackOn; } else if (locateControl.classList.contains('active')) { - locateControl.firstChild.title = "Show my location - last known location shown"; + locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; } else { - locateControl.firstChild.title = "Show my location - location tracking off"; + locateControl.firstChild.title = M.options.locale.btnLocTrackOff; } }); // Configure the observer to watch for changes to the class name From 4697f548b5d481c7d80822c14015817dc3cda74e Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 29 Mar 2023 17:44:11 -0400 Subject: [PATCH 22/36] custom marker --- src/mapml-viewer.js | 11 +++++++++++ src/marker.png | Bin 0 -> 16928 bytes 2 files changed, 11 insertions(+) create mode 100644 src/marker.png diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 339cc3240..f71bacec5 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -355,7 +355,18 @@ export class MapViewer extends HTMLElement { this._fullScreenControl = M.fullscreenButton().addTo(this._map); } if (!this._geolocationButton){ + var CustomMarker = L.Marker.extend({ + options: { + icon: new L.Icon({ + iconUrl: '../src/marker.png', + iconSize: [20, 20], + }), + alt: '', + title: 'My Location', + } + }); this._geolocationButton = M.geolocationButton({ + markerClass: CustomMarker, showPopup: false, strings: { title: M.options.locale.btnLocTrackOff diff --git a/src/marker.png b/src/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..ebfec5aa6c06f87e3b42406300403f9e1f0ea140 GIT binary patch literal 16928 zcmV)vK$X9VP)wzi&$m^&>}NlFzb|9|h=0UC;veyk_|p(BRWn{x#)R|5OeiQ}{9-v1N~`{l z&;29*%y74LFrm7Z(V26Me)9rz*ETXwO~Zc(-c!@agqlW1ue{6ni8D;NRQnJ1|2HAj zH!^;U9zWe}HaiWccHaRXAh&&ON`!ln_k$Gkvi-n$nXx`Ag zpO;?x4L@`K4FCK@6Myf+G5*?HGyJ!&%<*SmwDYHbYvWJ9ILn`Zb(((nb`O91<1+r) zsrUGq;=6fubFR=k8f*s^;r$cLGd#`cfv0_h(6i*OekygrX>FSyFS7)fG4KCPI90;_%Y~<-li}G17&X)}Pg|Q0N(+5>TL(jB zCG&Lk=y`p^6TGCfk$?R)=$*Gg&pZu!_={0=R7A*;7gZzcxIl5=dSk9x}BhdH-YYb9Q-$LjPdtRd?8$@ zT<-w>2mj<86H^-h;PfJw!_7RU^-TEUzfk@cT3eW>tDD)TZHzZJEpqquGgOr@PkV=% z*EhV*OH1eY*I$9&egpK(uRQ$G2VDH#J3ai4n>gKk9r)3!!4K~P?cWXByA!l)J81h> zqOF_3H*O@pem&8ewV;)&L3t}dx%r^1Jn+nA#50x&JY%_==d5+n#=YQ2?*aei3*c{l zRwbM++wB7WML1r>aPzOZ6Iz;maOU^|YH1SR z593XZFYu}gC!Ie7`sh8P-#zc+1dZ$2# zZU=wt1@O1NC>2hZuSeuY=4l;Z{Piyuy6ZZbaK4HOADv-;cj4*jVN+rWS9F%~=EjAt z-hPIsZezT)<$7LUJI>3BL8p!rfB#K4f9YAyfAw?X58g%m_8W*_e;9P+0MX(7#IN2@ zboBw^hsDChq2frFrZ2Z{FV2j97uXzOmGjoU$MHWMvhN0hY^JUtJT zl0!Ttmv~w}@vLOw;#KhXzW&Hv-KM6`E->U@zra)4ut4~#m!vuW@?*N#c**M|9Us zL^m7;UArIrn*BuA8~|N=fau5}(2=Vz>k#M~5&i+t)%!t*_RZ_ye$YX2QauPda7Y{k z(C!1E9eas3?F6maN|d*rXz40YS{`^(4pH(l;_0i2=WKD)=4%AH{c-T$elQ^%zqlI@ zzsg){=L7yFwHp`(p?`umx8 z@+>c0h>8+%5RUMsI)T1}{xQ*O&k_IS{X}-FQz_1wqoZz_b=MlX2x;1EOM*FO063laK*ys-vUcZu`T^B#Wu1EQCnBL2`_ zpj)m5_kz9*{MtRB16x4bR)AKegJ;EpQX)XHTJUHkxG4-gG88-_1Uy_+Fv$1^g@BBq zpm6c$a!{laWY&PJMo^*!JTnD6KToWHz_;xJZQVit8r*fA3RhDvbG?3Iq4PH%Gv{M(vA>z{bagPHy@h#tdl*U!7NX!J z6Iz=dzTN?ZY+vYv!xdq~)PqtkQK6L7uoajUbwUE$~-WsVQdEOb2g zF>}29Is2OkUSG{T1L8`wauF(uMd)|(`byF1E-&=Y-zEO+V?;l@33SsTqU%MM?;$$0 z6|^lMJUck2(Nu zjSL=b00eCixGn@-F9#1-fy_owavUf(7kvFD@J&0x*X#h#-bj?Pnt0+W2&o&%lXrmJ zTW@u`Zg>LD-+of>8lUiYgP8NfGd_-YPBF*n(*IoOR9V7!eH9ZLY8DDj4NPcl-o|S$ zfoe)Uyu5(ZXYUYy=I6xkxdC+JLE=aEg09&?w0jL`O*(jHwAbO>9QJSS)?aqCN;VH# zgsbp_GfV=9+z*aW3GAT~*h2hZ3;j3Q<)ZxIko&_K<`1Vr>Kzc5IuLG65Clyy1YH<- zxEkCV2}(-=<*x*--$o*Q%H2#nZ5@Q<2B_oR9OUGexB3k?+`ux2=RNa2ft}Q z(RI5)hqe%H71K&0cuF|PssxV|bM_yl61B|h>Ur3HUdVGH63hlmFcU1nbdUs7LB9W9 z;`^6>E`DzA@}Ss5#etB)sR)Ei83?yJ2p&x+1ib=6lpZ`j20UA=j5ZOi*$%#JD+%eF z$ep?w?#$ifT6;Y?cHHB%-T6Fh@11^n_KA1d?B2(GrnfxECRosa4tU<(&V-gmAJ_0O zb4<_vl-HEkQdt3b**P~Ie~A4V!)EAN4}#H|`~V?KaS!wM6SOz_To%1ab0|gGU5`8${?baJ3X}r5_w&e%}EX zoje{Wg7w8vfG-9DBQtP@?o%nQBnYk{Bsh|^1t z6Wx0h{D$4c4{rhOUPZJv9eioD7djUw(1;+AApl$_gP;~C$N3yR8zR9}urI~}=K&4| zO3)YRhn`>=xtemoYY(bF0r>bu*iO`P07+cvpWva}5r9d)l+Wk9m1vIh{KW z{_V#Cz5W#O2W}vK!!F`iZvyRFIS*ZQdZJj9%Rye~GH|^N9*sYoNM#bkTC#U zFTD(1bf6!mLM0dvmS8x@5BYGWkG?cQG}t%U_h(Mj5f0ct>KaA&}z}GQld{DjKN?TMuYq@9_)`v5q_`;KL9qR z2tOEhO&FXy4P22CaK|OWotY0${x)*w?}c;OL9*vv3)|Y8X?E9r!qjz7VDgz`HIow~ z|IZ}ACJQgI$)fq~#J^bZ((_EHt6Jchm}Z`#;Q(IvO&fjj7Wn%wc=+RY5Z$y3bYLyf zj^#w_Gl+7;6{rzpRe(o{t4y(a5uGlDQ{|62g+Hd`ei#e!!%&Dn`sIP>QHP-`!hjB| z1zoXLwCdC-^Y_IG-^F-0RE{?+F?elh4qnY)jaQei#w+Pd@k*2#Zv_S6vn#Jek)IzL zHCnVsnbBqpN4HjkK4lmNLxV6HES-lRa@qM|fv_oqVOPuH(5m1x7~nER!swHM@>_iQi(lc*{8jjUKp@U7UW^v47H#1X=+f!XuU29xEEr?K zGE9WXFcl)hOlTnH6v42ou;s$ zUH5t>u6-QikH1wrT3Eh#q_U2Uz466@(P!TK%fWZoT=E{3aJp~>Kl6DDz4;{gZypfn z?gPXRtO9M#Cfbk=%1PQO1MuPg!>BlQ9%G|Za61KXmjI951=pMd&KVmV)0Z`4hjZ2e+l(E9 zeM2bz>;&H0vK{aEN>CXPhz_IC3x80f!iXXS;~@c<3=NnE9~Oc+r5tv(5)Q2v4r4f+ zQ5HC3Q{YNp0q3%~?Xb@|VV`q(Wt(+kZpMz8$vI4q&tPg~0@K6em>r$MP|qN~ zdHHp`Y>Y(Vm5b4-)1gPJ#gImYF-0gQ7ZOtUUCYdNdfhgMwo)!U$yIjRYiLHPj zcnkqvx7*ZGOe>`rSI97=3P!&{gYIY(y37$M@Vx@ZHf%vhZL`!6)BjgAc#xGx+nD*x-XN{BYofqSMS%a@NN^G{D@= zwGsUH&rHyLhrn;x!uhtPM5_|P^J0l|tl;TUpd>xG==P`(ZzUsy2g0qF!J(C5RwKit zQic&#AO`eG^q9lZZHYpm?@#gJwp|$P9)Nq+2HWH;9CLO!ZO(bv4gpTDMDXrE0q^vJ zpPsT|YJ3J0Ba;{%7{y?FFGkvWP<{3yUQJ2Ir7IU>K%>R5R*MOZ8dJ(J%qZoUQz&3l zsbE)Y;qZcwgd@rdhb0+~glyQ;*TSB=i{@5bL(^+-qRI6))7aMAJ;R3{#=spl19?_9uTh@V(**tq)#YL|?qcgp=CCZ8h zWtu=K;o$KakVOHC3IUG{gvThlJpeYX3^QsO##I3r*2>WrX+Tem8C5c0e6Vae`r3Qo znwrJj#55dpqRX9Lz)sQOf9iJoAA`4h!B0)uF)=ZR@zH6F4NYLAZv=hKofxcdL(zwy z;~kw59sd3p*6J{>(P2uh#*8uya|$JFDzz89U1x;d7zsy&1@`DfIN~y3PsxXE=@!i8 z9iW-j*JE<+4K%j?Mqy~jUFg5&;f9VUU;bw=r`w-c(09wf{xHxzpT5D|=T9&w*@7xy zf$+wYAM@MAU7}nUUzLmz-SZ2+d!0T|N+ zW6-EZk0lCiYB@gE>CjkM3g`4JW=1Dr7xTH8%WYzM`8U}e9@y+|ujXtn%wBf;teEGg zZJ3&z2R}A8gVEth4EK*=uyX+2wJqqmRFC5iJciSYeK8^r!`D%g}7 z*wwl}0&j)GoB+FZ3G4~Euq|1K+3a1IS#d2*uD+heRv)3E^*8Xo9e3Ef4*V>$^YHzw zd*3|^dv@IM!+@s;4l&`)rx$oCi<#&2XCeIYTRUjKxUH5fP>#6O76D2NC!T5qPd3cA z1*}StN$jnMKnM?lOCJckJ^-`Y08DBEF`^Gcf0O~e<_MgXEXL`_o`7v?7SqFH-mC48 zCeitn;{JEQz0hq=%*;A4Eox>y%}h?(Ffl%hvC(Oa3{PUHe+&cNL+EYpLf54_)SWns zV`)p#cI9GBXmps=>M^6yVor3sN(;MM4~I4!c4HJA;TAYd39wsIVT;egT=GiHW^AFE zyn{5k>Ig>juco2=L$2Pn*P(OsjdypgJ;J)z9$D0U^dZ*1^_K7H_Lu&Zd7gcU@rRBq z5PMdFV5C zOwHIZIW>pL$+`J7Bf5Qb8p9&^0TKK#dRu$ZQPqg{^JVzzjt6mWu>_N$3QXt>n9&$8 zr`E!z(!s6~!AHPuG{F&Wg+1yr_}EO$CFEf?bv>rDchhA4VT>+2K!aJkT|K$`(4K$r z1M$G@NX&)>Eo-k~ZL1G|Kk)pKn;3uKI!1@`eds_I;}0A;Lc6lSbIoo_GY}OIE=xt>4Pw>55j~&j-f~$`c2^|lq|;CM}LXw(Q!-; z3}I$`3NuqPn4OvRrVp`N@m4HxD}wd-+{_W;A0^;*Tkf5wjX2=G6Mj;Eix-BVjk1VgCd8=v3HZ zGBFpIik{$=v-kn!N`DOSX8rmux{>`qsLJ?CsK;mHSw8*7gN0No&6!csj6> z(T*%eS$ZG7fH69}_Bp;H2DC))rUVsujDo~ko#NEq3Dv6@m3PmSB10iWhQJjW;srlv z2*!*)1mi{}hD=6utK~SU*P-Fe1xyVNV`5+!Q)5$@o}9+a)GTJEY~H;62Ukx`|KU3C z@QJC}?|_d@Ok-?p3ZtV_7#W$u(9i@12FB1YPOe=;=xOUkM{OHgE>xiQ{S)|X`Fb>8 zaTR8jddz4eFsC)bt~S7-35P=)1&1*jj__F6qY`1aq|Jkm&B083KBiLE)5Ox9G?KX! z14-+6cl>HT7{)s{Jhy5921L8MeL+Wu#H-N`0J!F-O zbvltHjKrNJic^6^w_E4I^C&soQK9qT!$UA<48@F5j`0XJhD_mTmignW6)Vx(+>Y_? zK8z2HU}9tfljD<^nwY}W#0;h;Xa5~!FKDkW!ycc;=-3oSMkg^mGJ)aY2@Hu&9~eVl z-w1kphS1&7kFM4pwAZ$wxv&EDADzO9eb=LGu|MYIYRu>&VH4Af+BgqhXM)3MfjuG) z4pS2B(P^+*GcgyFjhVP*m`YrYiL}i$vSbVT;#X0ZWtpeNl8d_d{OtPZEY@Vn_GyUA zXZ1;|zaM<}=B3PW^By+!_U{%6IpzwAQ-DW@2s|oCtXhbhLdjb?icYuAgBNqW$1H~@ zN)A_q9F7P%Y!PzIL?|#GsljlR0acR4`1Zz|Fx1tDk&bSR^$%fucnsqs*8I|d7j#z>^YxbUcZ31gFr1;dEGg)11&Yut6nV-O%Yf{;>N4HHgm6esr|;qP@8bt@Z6_u4qEt`7+cVJBc%QJ%&?qEv5tH znA4eH7hSH2ghOkBLvMj2JPwX1(e0_QTQgv@W?|Nvg=uRJCS#UiEN&$Y#jm1Xb2d7R zsqSWDGHT7~w>8QmSd%`%r_PklYNIp%eDID86SHSo*zD?5HoHFaN>{9Mn1=_58yTDo zQnB$x;>HFyg@P=~dB^_&yjheAF0%?wvj%pv2D8yROh)N3qF13bz#oObeg=ar?HFio z!%%l0hWiFFJTQ#m!4ZrMi5kVo&^U&N#=VaAg8n1e{R1QD>l;E}?-2TW2GG+zfUd3q zbhP)Ot+fZO&0T12Xh&0ZGwMrgQFFQo)$e?Xb9es|Unz~4lF4Dynqk+Oz0gIsJB_h$ zMkK;vN`XCk3GCLTu*GC!){>2BOExAgIT*F((V%r1^+ctkL!abs(kGzCl=e_#kd`%S zV|;3((pZgo$)68?F2%^^(jwVZZXBCjp1jx@Egu#1QgEFgalIc%FC{XHt`7khCsfh( z7BzUZxR0rZ+oFccqJ`6HfFs6;xtMTFne-UbDRC*l9~XZ0YxLJOp|7C{107u$=#Yt5-re$HUYoom`cj_!~8bs&`a7HD=VO|1k`~e`gqis(;jV>Dp`}-;!_)y!fKMo&czdaCQt*V2x@j&AgK^`XDJ4}CrT=f=yrS@Lj`OwAg#R-fAFG*)X(XWuKv zv*Ov{nrzm6WdA?Sn!*~KT7S6Jeu7&qfuQv#K_??|bt+ad+@kVA7h-h~V)XFD8R1DZ z!Ic~X=aNL&mn2{|CK6KyC7S%M#JATRL3?2dx-V6tr@jfjE$!%O??z8YH+njI(B0LG z9sC@4eu$pVzWe;k$1 zzl|>s-hu*I7@TrF?4rvf6XA+XhAS!+uILQ7tXXiz=D-oR4EDG@*sQsjHD_bel!>vZ zbPPqLQ=c(~I(3O?)yBCS4DqOm&A6jZZe;bk7@yjhC9EcX>7NhX!5ACblh4j|SFn@7 zf@!njtW7I!+erlRWPn@cPadt5Jcd9BV%;ttFpbfI$Lb-(heJq=f+xiacX|@sS?O?Q zXJ9VDg6VKIdi}1%H+joZ|HW76yjX(Hs#ORMb+mV(qpfQm zbXzCdS~}6%(s3Dh8yda9o6%6yh`OqJR9~t^RY?^tT_{8O=|Yr!b{b`Ge2j{xUd2c2 zcA?rg2rgA5>=6lYnU}zA$%H#53$EB4xZ?BRjL(NNVFm07`LM<0VaA+=$*2sBMl8W# zcq;W6lF+V;M~gPr-4Kz0+NA7#b;cN09}(|UmypS7Q*!@&@Dut#HYvZ7&HDY=hl{T* znm1;>2oTw?id?ODQ3@#vg7&06gKr5KIaPRxNm(0YYLVgk%dmOA_E&x&)qOxp3z# zha)u+Gm$!s2T5>VuSLmkUPSx1=g?MMhPKLTwAVJEy`dRxO|58eYC~J|yjm}-rK!ye zx~Z|%3%jwQ8TEBdsIO^6ZB;#LDr!+xR)vbGCM8qTD7*%DV`E@l~~O<5R^Ovi9|D)k$Z zs9T?ac5N(g*2Z}1O-ZOp&&#ceO=ESI6rbAU99EmL;?D;^k{rcG6T;bqM(Q&j_%CcE z&Tz-1E)a7{KetUOA&1IO+}t3SPDXA+khg9Z#2g=Q1Wz)7r^Q0ZOo5QI6rSbc&i!gQ zGcz$~Heyy8j9T9-aq7TfG=B6Mn!i1V=Au$GU#dh)buC(I>d{=+h~~N`G}q6oslEw~ zb&Y5gLD!0~8&FqWkJ`#QR9~t=Re3ckN-9x)u?(f>ic#{-1r(h)gQ5>lpyc%rQTfd4 zIC1?w_&h|5xnOadF9ELDY~Hf?j;-GS+Wr>QE40COkM|D;wsF>EW=b(CdML` zV91byUVS2UYU9wViQ!G!7^;s>nWZcXGS66HiZNZwLghFODKw)|{5cv=pGD(^A~ckgp`pAI4HZ>rsH{PKRW0hP z>QGlzi@HitHK?hmM)jp?RFzkuvaAZ1iYw-U7nGp*%taKPI**ITzs1FmPN4YrAEWGr zw^06z=keyU^{81K2&Y)hn9|@8531&Fhmf-qo}3-aEl8kG1Z9RmnMPdW`Zxz z1J7RtA#WW#Iji7GOv0Q|<2~C_ebtpXv3WacUwj3%AAW+mli#5B%z4yaC_+t932KVV zP*YNl>e5T7D!qitl6h5>l;cuS8Okq~qU=I3O3xRe_{;?qeRB>Mzxo!1pMQ4gl57duqph>p_0L=3xq2o6mE+aLXv47 zeBM&<70V&4SPh=H7M|=iaHlSX(;R_0O(-V)7vofz0_ShI6*bSjh{|`4q58AqsQl^_ zDo>w9#o6PCF4DQ%?I1Fmo6+!4-{1bd)vZCbnyHWMzGbn%M4P1Kf z7|K5W3}s&&N6GP%DEaCXicfxnqRYDY)i)?SaS9i{{2B#coJ7H=$8q7KFHmsoQxv}a z5sF`b4<#?YiSnmjMCrp%;^mbaaptE1m{Wwq9+B+L=X}eJpj~%>cHRNL{dNdjZh^4= zdbn3y4QJLa*pk*^COQ|B5$PB)B%@zD@Ag);g__hBUazsBCOT=dDtAqE)#@#*Dr31% z)spYgqxsY5j16avC8}ZnCP&7&hE57Y`!&31gv`;Vb>frjn9F8vti`DBM;Z zgj6f|vP{tGm7vvY!SmOGXRn5kmJd%tI^3ox*tKDBgi6u0_zE10OG3%bx1s8>$5Hz1 zb0~fJRTRJZHj3UohKt8OMB#@YJq6(6H6UxE48RBXi!@`wK^-RV$%x5$57Nr z6+V?4cCyO#yMH+F9sg^-#oVEgF6;>PW9??`+g-{4^o96&hC(D53-Q9IY4HH6N(zTQ z2(Cy4-0_hRvQj{+^1;`w2d&-!zHBXo^n3`3+3;8sy@y5Z>QK0X{n2pc6?n&F!MUri zMa}&Wq5QGOQT)`?D0=P%Tzv6m6#nj2TzKsbTzKtG6ukBpE{J;dO%%TJ1}?t*8j64W zDoUPv8RbvCfXZJ!jk9+@gy-|t;NzLG>WPU$+Icd;@s;N(hNL5Ui>2n5^)K@KvF3%L6eX zxe{M5UW`{G&G=~DMig9k6qR@0jq3X!MAbt-N97}rq4LpRqVmzlQ6=h;U*gh(kK+6t zKf|$ohw*fJHhw1yL-iF>%!V0Y7oBd+g0O54_^!L=K_7S+bl_p|Ll1jj3wwSB&!$`8 zT7C$&?p6C>Pe5wX8lqGNVx&N-e>DLT9jq_XhdWV6X(EA$}r!no-GM z(+0sAu7Eo(3PMhX*Y&h%8~EC-pk?d9(^i1TXF;%}LWmOK8{kqa;8F&|9ps13pZ)}& zUwIW?Qfly0QVQN#wHlvo--)mGAH>&(4&$pshw%`2)_Rz(Ef+P4?YaK`eE?>4?x&)7u;))!jZWXv(|j? zggUBEM!zPGx>QzbRYs#xX{I`rNvKwvamk!GT%NaDQFci-Q zhTsMNK)m%+e|-HfzG(cZ6q7Oqoa#t8!{gvIr$b0x2fpT7(5}0|_dNi<_d(FUhrka! z0=oL=UdIc2?uC2PEpRS709(RZOh;v5OrL^5O+0n0VyI0SjV7g;>Xjy{QJLHo1}n-E z(|=K>4rk@Ms6}NDJ(q9E3bt;WwSTMj2lxiq)nh6RSY_m z7HUyNqd|nOGzpa&6Uw6#JIXh0^DDjfI#!mqdO>N<_vp*~|BWhD5UUE6uxf*1QDvAv zt1|2FuT}=4KG4tA94Mjo015SoH;V;IX!K9vn{;reCO}xZ!kcnv%Wm)uyTDg?pDzHO zD!M;+egL9VAVkGOh!9hd2`+sET>5ahbw+q};qd4qz3;ojEpSD|!EH*05Ss;_vIabN zH~9MNLEG*G?fe<|u6sed?g#A=(~JoH(RsHYco3eQcf+;z1~@Xr6tf%?;Y%>AO{6|` z3_3;VN;4XjCaP7Ms7e*(E(?!AN$S%5CDsI18ke%LG+_xVP5B-z;QyPbV2qW=M6!#I z-OEa^+4Z9eqq3|jM2fmViMuJlm)d0#bYF%);)O58j4A*&T_~I;J>02@5LT=J7vXQ+ z4cfSS{toNB&7h_0z|&TOCoTt1SOy-S4IaA`JSG#|nhtJV0>PRNAtn<-Ts8#pb7|{9 zS=&G>4$K4JavNy-UEtgA0pBqXT}(0F3DxWLpMxKK5T0H4z_sBfII{L)E_M~B#JsLc zLccl|-O6ZcQ<%}HFj1WdT^Z%Nq%)&5Ci%<90g=Ii(#^YAi8g}$)uJ-N%qk*OtUN-! zsMM@u6k2c^7E&UhoZjz*p}CtAjIV&QHQ!`mf!jgrhX*sN9)h6~=jZ4}PR?iqK z4V5k|HK~Xv03xx_#iQ_kyq53%+t6_^Jcos}F&%ISjt;TJQ}=!8eJp zZvo$S8~Bbp!FSvRzVjZ?u6x1v+~>{T-dg?8fL8l63q^s0uL2*+0;o|sIRvckjRPqx)R&0u6|0z+nKA)9uU(bpe zThIqAC^o83mWE1E5$NZxmPybclTfS77o9R+>I;xyD98`v!BR|x1z=Vc1e;C?hbbJc z5kb)si z9Q7)#=unuxgI=SE6rsCIbtV+WCck@;F;?SL^e~S{cft4o5u;OT= zPtodJRQh64RC7A(btTuePdnAON( zGiuUbqyTZyJ1h+=$%NtPB*4tSQC#vFLX1tC`@Pw z6QM`UL)S#1C_28maLq=)bGO{eiqe)YC|I{ex&+lga*XBu7ms`=KZO#Ak3&jF{f3+7GZ=V z#tLW361cMS;a<53p7pySY(5A<9DtoSfbYH;eDAH``)&vAzY~1_UEurg{!YT)J0a}8 z1D+kX!n5T@xYu6?*UCe1=In+&eGBFi*I?F?i>b&AuhWOM@#s@osZ(L5*03lvghf(K zSR_>{B84(lBo#%)%oe6+#b30!2FG1iV)1G6`0d&VT+7}-5Lu= zVj7(3%izjg3-^jGaIe_~&xZZ*Y`z+vE!RTWdL2C5j>5C`C_J05gJ7ka|%7>qQZar7gqGwtYZGZqR<-2%5T1&wF9i+pB5}U#~3>wp?%>(XaI@= zB<@m~gepY%{=R7N_oWu81f5a|i4T7m3i8KDhz#TMAWSMlFr`vpMytWB(SW&#DA>#~ zu*W9Do|FMc+A=sZR>GOJ7S5cFaOQ6I-VW)?-3n*U7C4t~h9h$$981>2p0Wn^q?MS9 zTaLNQ=VGQ!nV5`7n}@FRLZ>c;30~;(NYsW!P^BV*%9P>mi$*gpB&I%cK^Dvk)rLiX zcm``>#t+zU{~@9<&diFoZDQw>;})C~-GANDAD=U*P80-7aWO#RE|E#-lGK-~{e9uh z`~DJiNF~%G^P_=4DTYG=F)9znxI&Iel>$>*HKuhs%oxHk8*ajElofO4c+6RnVT(-1W=2tAz2lo9TWdJ`_hCA}%$C||^wPr=G{tmyY2vcl{iU`y{W5f>~GqSM(q zYoyP)jAV8$J!$b-t)k+5kUt7#^YF{1zEtUd73!qE)a37rcBus2GJo_3$uJZWgb{fN z#=_*7P%1H@R%24UNla(Nv_29u1{0>kqcI&BgPEu}%$O1|Yf8e5DH*e-6wI1aF=I-_ zOjIhSqf#*yDJlh%;mMdVBw_%9dUDve8hOvzKtC`1 zBB?Kx`TJ6Z|5d2*^QA_A30kH8)D<8_Z%_aRLV__A7K#x?7)F&!jH%S#`;{iN22AL} zF=>dzq|t=Q@MuhiTQC)F#Z*KrrXu1n6%miAhy+YVBw{i=5tGJ5j2jX$rcc0#K5ibm za^C4}@(5~_hhK(n6rsB>Y9mo#i7P#uzxv8^dHJj$JiyD6kHAwx?Dw9%YfDAoBf#?qj z!Ju4@A%y}XN)<-b8jNam7}XjurVYoKE&}8FD2(e(7}rN*++e}D!HRK148{$y7&pXW zOdp3aLmWm7aTwOcVo+nHK9yxYr;F(&G#s^|#>>!+?u(iToR5xcKAV##JG(T8olnhJ zRIp?zD@aUbe^+tFsA1pyPHPyf3(PC)Dakf?%-hbg@&SE9)^K1B?c903@f!5R_QRJHegf}j!{hnMzv8G)tNA= zHDgp4jZwV?qk1bw^f6wEz=yOk7*Jc$tBR&hMHE^?Bj%yYjZ`83W9Tvc=a#KdpN&mm z=i?H8bUr$koli(%f0uD`A!8?h#Mr4ozeT5%A?%yzh|S+9f^kOXhXR?OP$>1I5~&|9 z`TL<->QD7DDVhTU&=wSguHX>#gvik=SD;^^!hk}90hJbmDm{kO1`KJ!F|3WikTw#- z+9(X`Oc>ThV@QAyAYD8_Qfhy$&DpMHT7d7EHXO0;; zo3kwPOk4sx7agwKBN zP$;_$U+Rx4sg&wuGBgDQQd>|kI)X#d6&i*fxe~o$D)cF|=vQjduhL^cWx#+c90TeI z45}kBsE)#b+Jpg(C^P!hX7s8})UAv{hdcr;p+;&9F`_m^gs!L3FoXM|+KBTe>*U#` zxv^(r29nJpXH2_NS3Rfv>*9c8%wlw5@G&(DkSrT$bR^`}cx zDXOG0s*?qvF(8OqgF?_AEJtUk0$p+?x)o~lD7ENO>Hc?n=knXeamVo=X~|Ap2ey^O z*3Q0{%O!W0FYzJCmMw{*?6~zI2#}&h(X>G??LUx1Z@na!9FjvX?IDL8duv+TBA47< z$|m)&C{v;(S(as4u`jt42$C2!CC#rGGJTu^6is5;@dx-~jFM_dMzBoYAXA=KY4z{ z%?^Q|O!s`m}~{Y&^eKwwHZ0mX#%`%*`K|K2p=_G(-|o$K$%Mn6ZJ z9HA}3(=}eCw*?vRNGfj1A-t>TcuzI(o*Kbz&BSfZ!tIca+o2e4hvJ>w3niFvysO3O zh7$W)x{1r;*U}Ypr>5KJr!rr7g>$2BE(cyb)$1xDaCrruIh<4sDi<$6xiA)(V)Dr= zP>h>@C@B(pgxl@P=ja^A;Q~jn$?>#BL|heQdP`LBwxr>PtmCE<#!c10yIK^tG!wTp ztD8^^?`k$~YB9W{#_+acB?{(#qooPb$>KvhS9Ki<1(2h?ZrdwU<`N@1GlLgmK z^sw^~Dt)iQGf!z`7)ob`p>+0CXKLZn@uN_TS?@bt`McAE->Vaz&T~9AIDs~aL|eQ} zR|JKwiW;s-I^L3XT$c@8S0cEsL~&g)S?IbFrE9W@tD;G7iV<8AB6LxV(0M7cQ`0Q0 zCeshSd||xPz1Z>I@~bDo2^@eQ45e%eN~Z^*IG72(jtIrRV@&>sjv}MS^E+30VXw{! zbe66O5j9+ubX=2+ZsZ8A$x&RDBFI|!O)*MaOuA^$Mpt^fu9@g3 z(qDP`!USvSOgj6GQU1{p@Xvk6%QE&G&d4b!4&|UYm~SCn-}9#9PX|se3r;o*P9_t){M+9FI|jwE ztdCuZ$(PzSBJa&|GMys|&hsi>=QX;(hwyqgbe`AgEM&Q{F8}}mx=BPqR3CbLP1Lb! zCO-E^^J9K|0Q|7o%K^3lN&bXSu_^0`b5Hs<_b26C4$4gaKss1_>sR0%Jj7c5pDU7z zRZiNyM&z9tqR?5Q(mA5x9MM=wXGv(cPPE5YMIC)BvE_{y(rz*ho?-Pc=}w^2%RJ|s zC=U&TlgWZJI28Qk%{QQQ_$4S=v3JXoiasaeRZer> zRsX_8p_0mgXU2QnlRUVAL*H26bN&k_oq60=} z_si&WDte01_UzOL?$3YmqC1)gFOlx4^qqwA!NcGe_Sw(rr=sN#4MTZsJa7eq!6{yb zE9pUHDr~-Al=WMVp#8&<^{sN)`lyo347*2;f&amW;17)iJu?oT9(~fc@buygpN8^l z7l3ULUX(*Umy;QoI&>H==O>O7&*n*SbmHg?dr0vC@Luje;Efdm-tY)`1Gy)C=}$k- zsWYAOFHF7}50+j!9{k&xvv4VD!{y8nTm=YJ4EsR!jh})yG6r7yx%6+9qWzy0T*X)l r96J?otRy&--MN;EmHc)q{Eqo2KSle=K}Q?700000NkvXXu0mjfCC{2* literal 0 HcmV?d00001 From 9bf5f7b10ba119358858575ee9eeedb2cbd754ea Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Thu, 30 Mar 2023 10:30:10 -0400 Subject: [PATCH 23/36] small changes and tests --- src/mapml-viewer.js | 22 +--------------- src/mapml/control/GeolocationButton.js | 24 ++++++++++++++++-- src/web-map.js | 11 +------- test/e2e/api/locateApi.html | 6 ++--- test/e2e/api/locateApi.test.js | 29 ++++++++++++++++++++++ test/e2e/mapml-viewer/locateButton.test.js | 2 +- 6 files changed, 57 insertions(+), 37 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index f71bacec5..00bc8303b 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -355,27 +355,7 @@ export class MapViewer extends HTMLElement { this._fullScreenControl = M.fullscreenButton().addTo(this._map); } if (!this._geolocationButton){ - var CustomMarker = L.Marker.extend({ - options: { - icon: new L.Icon({ - iconUrl: '../src/marker.png', - iconSize: [20, 20], - }), - alt: '', - title: 'My Location', - } - }); - this._geolocationButton = M.geolocationButton({ - markerClass: CustomMarker, - showPopup: false, - strings: { - title: M.options.locale.btnLocTrackOff - }, - position: "bottomright", - locateOptions: { - maxZoom: 16 - }, - },this._map); + this._geolocationButton = M.geolocationButton(this._map); } } diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index fddeea770..39795c599 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,3 +1,23 @@ -export var geolocationButton = function (options, map) { - return L.control.locate(options).addTo(map); +export var geolocationButton = function (map) { + var CustomMarker = L.Marker.extend({ + options: { + icon: new L.Icon({ + iconUrl: '../src/marker.png', + iconSize: [20, 20], + }), + alt: '', + title: 'My Location', + } + }); + return L.control.locate({ + markerClass: CustomMarker, + showPopup: false, + strings: { + title: M.options.locale.btnLocTrackOff + }, + position: "bottomright", + locateOptions: { + maxZoom: 16 + }, + }).addTo(map); }; diff --git a/src/web-map.js b/src/web-map.js index 02e8a8a21..a9edfbfd1 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -398,16 +398,7 @@ export class WebMap extends HTMLMapElement { this._fullScreenControl = M.fullscreenButton().addTo(this._map); } if (!this._geolocationButton){ - this._geolocationButton = M.geolocationButton({ - showPopup: false, - strings: { - title: M.options.locale.btnLocTrackOff - }, - position: "bottomright", - locateOptions: { - maxZoom: 16 - }, - },this._map); + this._geolocationButton = M.geolocationButton(this._map); } } diff --git a/test/e2e/api/locateApi.html b/test/e2e/api/locateApi.html index bfce47af1..5a256a095 100644 --- a/test/e2e/api/locateApi.html +++ b/test/e2e/api/locateApi.html @@ -24,8 +24,8 @@ max-width: 100%; /* Full viewport. */ - width: 300px; - height: 300px; + width: 100%; + height: 100%; /* Remove default (native-like) border. */ border: none; @@ -72,7 +72,7 @@ - + diff --git a/test/e2e/api/locateApi.test.js b/test/e2e/api/locateApi.test.js index 8c04e7bdf..83b9817f8 100644 --- a/test/e2e/api/locateApi.test.js +++ b/test/e2e/api/locateApi.test.js @@ -61,6 +61,35 @@ test.describe("Locate API Test", () => { }); expect(error).toEqual("Your location could not be determined."); }); + + test("Testing API when the button is used", async () => { + await page.reload(); + await page.click("body > mapml-viewer"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + + await page.keyboard.press("Enter"); + + await page.mouse.move(600, 300); + await page.mouse.down(); + await page.mouse.move(1200, 450, {steps: 5}); + await page.mouse.up(); + await page.$eval("body > mapml-viewer",(viewer) => viewer.locate()); + + let locateAPI_lat = await page.$eval("body > mapml-viewer", (viewer) => viewer.lat); + let locateAPI_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); + + locateAPI_lat = parseFloat(locateAPI_lat).toFixed(1); + locateAPI_lng = parseFloat(locateAPI_lng).toFixed(1); + + expect(locateAPI_lat).toEqual("45.5"); + expect(locateAPI_lng).toEqual("-73.6"); + }); }); diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index ee5a2e03f..a68d2bc89 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -63,7 +63,7 @@ test.describe("Locate Button Test", () => { await page.mouse.move(1200, 450, {steps: 5}); await page.mouse.up(); await page.click("body > mapml-viewer"); - await page.pause(); + let locateButton_title3 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); expect(locateButton_title3).toEqual("Show my location - last known location shown"); }); From 2fc5334bfbe25087308a8a12aee850b509eeb15a Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Thu, 30 Mar 2023 14:07:41 -0400 Subject: [PATCH 24/36] locate APi bug fix --- src/mapml-viewer.js | 3 +++ src/web-map.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 00bc8303b..a6845aa89 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -704,6 +704,9 @@ export class MapViewer extends HTMLElement { } locate(options){ if (this._map) { + if (this._geolocationButton) { + this._geolocationButton.stop(); + } if (options) { if (options.zoomTo) { options.setView = options.zoomTo; diff --git a/src/web-map.js b/src/web-map.js index a9edfbfd1..11b81fb90 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -747,6 +747,9 @@ export class WebMap extends HTMLMapElement { } locate(options){ if (this._map) { + if (this._geolocationButton) { + this._geolocationButton.stop(); + } if (options) { if (options.zoomTo) { options.setView = options.zoomTo; From 98c769baed934a087d77d350ace5103e7c1da17f Mon Sep 17 00:00:00 2001 From: AliyanH Date: Thu, 30 Mar 2023 15:32:24 -0400 Subject: [PATCH 25/36] minor styling fix --- src/mapml-viewer.js | 27 ++++++++++----------- src/web-map.js | 28 +++++++++++----------- test/e2e/api/locateApi.test.js | 4 ---- test/e2e/mapml-viewer/locateButton.test.js | 4 ---- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index a6845aa89..eaf4b0fa4 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -354,7 +354,7 @@ export class MapViewer extends HTMLElement { totalSize += 49; this._fullScreenControl = M.fullscreenButton().addTo(this._map); } - if (!this._geolocationButton){ + if (!this._geolocationButton) { this._geolocationButton = M.geolocationButton(this._map); } } @@ -702,20 +702,19 @@ export class MapViewer extends HTMLElement { } }); } - locate(options){ - if (this._map) { - if (this._geolocationButton) { - this._geolocationButton.stop(); - } - if (options) { - if (options.zoomTo) { - options.setView = options.zoomTo; - delete options.zoomTo; - } - this._map.locate(options); - } else { - this._map.locate({setView: true, maxZoom: 16}); + + locate(options) { + if (this._geolocationButton) { + this._geolocationButton.stop(); + } + if (options) { + if (options.zoomTo) { + options.setView = options.zoomTo; + delete options.zoomTo; } + this._map.locate(options); + } else { + this._map.locate({setView: true, maxZoom: 16}); } } diff --git a/src/web-map.js b/src/web-map.js index 11b81fb90..91d9d6fd5 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -397,7 +397,7 @@ export class WebMap extends HTMLMapElement { totalSize += 49; this._fullScreenControl = M.fullscreenButton().addTo(this._map); } - if (!this._geolocationButton){ + if (!this._geolocationButton) { this._geolocationButton = M.geolocationButton(this._map); } } @@ -745,22 +745,22 @@ export class WebMap extends HTMLMapElement { } }); } - locate(options){ - if (this._map) { - if (this._geolocationButton) { - this._geolocationButton.stop(); - } - if (options) { - if (options.zoomTo) { - options.setView = options.zoomTo; - delete options.zoomTo; - } - this._map.locate(options); - } else { - this._map.locate({setView: true, maxZoom: 16}); + + locate(options) { + if (this._geolocationButton) { + this._geolocationButton.stop(); + } + if (options) { + if (options.zoomTo) { + options.setView = options.zoomTo; + delete options.zoomTo; } + this._map.locate(options); + } else { + this._map.locate({setView: true, maxZoom: 16}); } } + toggleDebug(){ if(this._debug){ this._debug.remove(); diff --git a/test/e2e/api/locateApi.test.js b/test/e2e/api/locateApi.test.js index 83b9817f8..efaba4fb2 100644 --- a/test/e2e/api/locateApi.test.js +++ b/test/e2e/api/locateApi.test.js @@ -91,7 +91,3 @@ test.describe("Locate API Test", () => { expect(locateAPI_lng).toEqual("-73.6"); }); }); - - - - \ No newline at end of file diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index a68d2bc89..7d4a73676 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -68,7 +68,3 @@ test.describe("Locate Button Test", () => { expect(locateButton_title3).toEqual("Show my location - last known location shown"); }); }); - - - - \ No newline at end of file From 830c888acdbfae5005a7eb81740ce1cad678d9d4 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Sun, 2 Apr 2023 22:37:40 -0400 Subject: [PATCH 26/36] marker image made to svg --- src/mapml/control/GeolocationButton.js | 75 +++++++++++++++++++++++-- src/marker.png | Bin 16928 -> 0 bytes 2 files changed, 70 insertions(+), 5 deletions(-) delete mode 100644 src/marker.png diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index 39795c599..c92991df9 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,12 +1,77 @@ export var geolocationButton = function (map) { var CustomMarker = L.Marker.extend({ options: { - icon: new L.Icon({ - iconUrl: '../src/marker.png', - iconSize: [20, 20], - }), - alt: '', title: 'My Location', + }, + initialize(latlng, options) { + L.Util.setOptions(this, options); + this._latlng = latlng; + this.createIcon(); + }, + /** + * Create a styled circle location marker + */ + createIcon() { + const opt = this.options; + + let style = ""; + + if (opt.color !== undefined) { + style += `stroke:${opt.color};`; + } + if (opt.weight !== undefined) { + style += `stroke-width:${opt.weight};`; + } + if (opt.fillColor !== undefined) { + style += `fill:${opt.fillColor};`; + } + if (opt.fillOpacity !== undefined) { + style += `fill-opacity:${opt.fillOpacity};`; + } + if (opt.opacity !== undefined) { + style += `opacity:${opt.opacity};`; + } + + const icon = this._getIconSVG(opt, style); + + this._locationIcon = L.divIcon({ + className: icon.className, + html: icon.svg, + iconSize: [icon.w, icon.h] + }); + + this.setIcon(this._locationIcon); + }, + + /** + * Return the raw svg for the shape + * + * Split so can be easily overridden + */ + _getIconSVG(options, style) { + const r = options.radius; + const w = options.weight; + const s = r + w; + const s2 = s * 2; + const svg = + `` + + '' + + ""; + return { + className: "leaflet-control-locate-location", + svg, + w: s2, + h: s2 + }; + }, + + setStyle(style) { + L.Util.setOptions(this, style); + this.createIcon(); } }); return L.control.locate({ diff --git a/src/marker.png b/src/marker.png deleted file mode 100644 index ebfec5aa6c06f87e3b42406300403f9e1f0ea140..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16928 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 From 0cdff5d8df9684057327899dd2b3d00916e33224 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Mon, 3 Apr 2023 09:15:31 -0400 Subject: [PATCH 27/36] Alliyan suggested changes --- src/mapml-viewer.js | 10 ++-------- src/web-map.js | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index eaf4b0fa4..d95c1a3f7 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -556,21 +556,15 @@ export class MapViewer extends HTMLElement { this._map.on('locationfound', function (e) { this.dispatchEvent(new CustomEvent('maplocationfound', {detail: - {latlng: e.latlng, radius: e.accuracy} + {latlng: e.latlng, accuracy: e.accuracy} })); },this); - this.addEventListener('maplocationfound', function(e) { - //'Location found:', e.detail.latlng, 'Radius:', e.detail.radius - }); this._map.on('locationerror', function (e) { this.dispatchEvent(new CustomEvent('locationerror', {detail: {error:e.message} })); },this); - this.addEventListener('locationerror', function(e) { - //error:e.detail.error - }); this._map.on('load', function () { this.dispatchEvent(new CustomEvent('load', {detail: {target: this}})); @@ -703,7 +697,7 @@ export class MapViewer extends HTMLElement { }); } - locate(options) { + locate(options) { //options: https://leafletjs.com/reference.html#locate-options if (this._geolocationButton) { this._geolocationButton.stop(); } diff --git a/src/web-map.js b/src/web-map.js index 91d9d6fd5..96bb53bd5 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -599,21 +599,15 @@ export class WebMap extends HTMLMapElement { this._map.on('locationfound', function (e) { this.dispatchEvent(new CustomEvent('maplocationfound', {detail: - {latlng: e.latlng, radius: e.accuracy} + {latlng: e.latlng, accuracy: e.accuracy} })); },this); - this.addEventListener('maplocationfound', function(e) { - //'Location found:', e.detail.latlng, 'Radius:', e.detail.radius - }); this._map.on('locationerror', function (e) { this.dispatchEvent(new CustomEvent('locationerror', {detail: {error:e.message} })); },this); - this.addEventListener('locationerror', function(e) { - //error:e.detail.error - }); this._map.on('load', function () { this.dispatchEvent(new CustomEvent('load', {detail: {target: this}})); @@ -746,7 +740,7 @@ export class WebMap extends HTMLMapElement { }); } - locate(options) { + locate(options) { //options: https://leafletjs.com/reference.html#locate-options if (this._geolocationButton) { this._geolocationButton.stop(); } From c993c2ce38baeab070910af4a6398ad60fa2d423 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Mon, 3 Apr 2023 09:58:30 -0400 Subject: [PATCH 28/36] locateapi test bug fix --- test/e2e/api/locateApi.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/api/locateApi.test.js b/test/e2e/api/locateApi.test.js index efaba4fb2..887b5dddb 100644 --- a/test/e2e/api/locateApi.test.js +++ b/test/e2e/api/locateApi.test.js @@ -72,8 +72,6 @@ test.describe("Locate API Test", () => { await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); await page.keyboard.press("Enter"); - - await page.keyboard.press("Enter"); await page.mouse.move(600, 300); await page.mouse.down(); From 31d640205b77a9d67bb8db271fabd1473280b798 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Mon, 3 Apr 2023 16:06:16 -0400 Subject: [PATCH 29/36] marker title changes depends on statr --- src/mapml-viewer.js | 3 +++ src/mapml/control/GeolocationButton.js | 4 +--- src/web-map.js | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index d95c1a3f7..21c25e7f3 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -539,12 +539,15 @@ export class MapViewer extends HTMLElement { } }); var locateControl = this._geolocationButton._container; + var button = this._geolocationButton; var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { locateControl.firstChild.title = M.options.locale.btnLocTrackOn; + button._marker.bindTooltip("My location shown on map"); } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; + button._marker.bindTooltip("My most recent location on map"); } else { locateControl.firstChild.title = M.options.locale.btnLocTrackOff; } diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index c92991df9..38c65c59e 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,12 +1,10 @@ export var geolocationButton = function (map) { var CustomMarker = L.Marker.extend({ - options: { - title: 'My Location', - }, initialize(latlng, options) { L.Util.setOptions(this, options); this._latlng = latlng; this.createIcon(); + this.bindTooltip("My location"); }, /** * Create a styled circle location marker diff --git a/src/web-map.js b/src/web-map.js index 96bb53bd5..edab1d0c9 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -582,12 +582,15 @@ export class WebMap extends HTMLMapElement { } }); var locateControl = this._geolocationButton._container; + var button = this._geolocationButton; var observer = new MutationObserver(function(mutations) { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { locateControl.firstChild.title = M.options.locale.btnLocTrackOn; + button._marker.bindTooltip("My location shown on map"); } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; + button._marker.bindTooltip("My most recent location on map"); } else { locateControl.firstChild.title = M.options.locale.btnLocTrackOff; } From c06ef45b3a3cbed893d48bd925186be07c61c9c1 Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Mon, 3 Apr 2023 17:02:55 -0400 Subject: [PATCH 30/36] Localize text strings for my current or cached location marker --- src/mapml-viewer.js | 4 ++-- src/mapml/control/GeolocationButton.js | 2 +- src/mapml/options.js | 4 +++- src/web-map.js | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 21c25e7f3..8f822b21c 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -544,10 +544,10 @@ export class MapViewer extends HTMLElement { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { locateControl.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip("My location shown on map"); + button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn); } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; - button._marker.bindTooltip("My most recent location on map"); + button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); } else { locateControl.firstChild.title = M.options.locale.btnLocTrackOff; } diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index 38c65c59e..443a2b78a 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -4,7 +4,7 @@ export var geolocationButton = function (map) { L.Util.setOptions(this, options); this._latlng = latlng; this.createIcon(); - this.bindTooltip("My location"); + this.bindTooltip(M.options.locale.btnMyLocTrackOn); }, /** * Create a styled circle location marker diff --git a/src/mapml/options.js b/src/mapml/options.js index 327437dc2..3c19251b6 100644 --- a/src/mapml/options.js +++ b/src/mapml/options.js @@ -23,8 +23,10 @@ export var Options = { btnFullScreen: "View Fullscreen", btnExitFullScreen: "Exit Fullscreen", btnLocTrackOn: "Show my location - location tracking on", + btnMyLocTrackOn: "My current location, shown on map", btnLocTrackOff: "Show my location - location tracking off", btnLocTrackLastKnown: "Show my location - last known location shown", + btnMyLastKnownLocTrackOn: "My last known location, shown on map", amZoom: "zoom level", amColumn: "column", amRow: "row", @@ -47,6 +49,6 @@ export var Options = { kbdZoom: "Zoom in/out 3 levels", kbdPrevFeature: "Previous feature", kbdNextFeature: "Next feature", - dfLayer: "Layer", + dfLayer: "Layer" } }; \ No newline at end of file diff --git a/src/web-map.js b/src/web-map.js index edab1d0c9..a4a7257a5 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -587,10 +587,10 @@ export class WebMap extends HTMLMapElement { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { locateControl.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip("My location shown on map"); + button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn); } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; - button._marker.bindTooltip("My most recent location on map"); + button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); } else { locateControl.firstChild.title = M.options.locale.btnLocTrackOff; } From 76bfb7591fdb218144cb9458f120183468a7f5ae Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Tue, 4 Apr 2023 08:50:45 -0400 Subject: [PATCH 31/36] Update test to use variables for geolocation button states --- test/e2e/mapml-viewer/locateButton.html | 2 +- test/e2e/mapml-viewer/locateButton.test.js | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/test/e2e/mapml-viewer/locateButton.html b/test/e2e/mapml-viewer/locateButton.html index 5a256a095..69a8d1406 100644 --- a/test/e2e/mapml-viewer/locateButton.html +++ b/test/e2e/mapml-viewer/locateButton.html @@ -72,7 +72,7 @@ - + diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index 7d4a73676..2bf567b32 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -2,10 +2,10 @@ import { test, expect, chromium } from '@playwright/test'; test.use({ geolocation: { longitude: -73.56766530667056, latitude: 45.5027789304487 }, - permissions: ['geolocation'], + permissions: ['geolocation'] }); -test.describe("Locate Button Test", () => { +test.describe("Geolocation control tests", () => { let page; let context; test.beforeAll(async function() { @@ -18,7 +18,7 @@ test.describe("Locate Button Test", () => { await context.close(); }); - test("Using locate button to find myself", async () => { + test("Using geolocation control to control map", async () => { await page.click("body > mapml-viewer"); await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); @@ -38,7 +38,7 @@ test.describe("Locate Button Test", () => { }); - test("Locate button state changes", async () => { + test("Geolocation control state changes when pressed", async () => { await page.click("body > mapml-viewer"); await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); @@ -47,14 +47,18 @@ test.describe("Locate Button Test", () => { await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); await page.keyboard.press("Enter"); + + let locationOnText = await page.evaluate(()=>M.options.locale.btnLocTrackOn); + let locationOffText = await page.evaluate(()=>M.options.locale.btnLocTrackOff); + let lastKnownLocationText = await page.evaluate(()=>M.options.locale.btnLocTrackLastKnown); let locateButton_title1 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title1).toEqual("Show my location - location tracking off"); + expect(locateButton_title1).toEqual(locationOffText); await page.keyboard.press("Enter"); let locateButton_title2 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title2).toEqual("Show my location - location tracking on"); + expect(locateButton_title2).toEqual(locationOnText); await page.click("body > mapml-viewer"); @@ -65,6 +69,6 @@ test.describe("Locate Button Test", () => { await page.click("body > mapml-viewer"); let locateButton_title3 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); - expect(locateButton_title3).toEqual("Show my location - last known location shown"); + expect(locateButton_title3).toEqual(lastKnownLocationText); }); }); From 76191b6998ca9d982661f14acf8758648c76a8b9 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Tue, 4 Apr 2023 12:30:48 -0400 Subject: [PATCH 32/36] tooltip focus --- src/mapml-viewer.js | 2 +- src/web-map.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 8f822b21c..b4a75bf19 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -544,7 +544,7 @@ export class MapViewer extends HTMLElement { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { locateControl.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn); + button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn,{permanent:true}); } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); diff --git a/src/web-map.js b/src/web-map.js index a4a7257a5..8a5271b54 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -587,7 +587,7 @@ export class WebMap extends HTMLMapElement { // Check the current state of the control if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { locateControl.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn); + button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn,{permanent:true}); } else if (locateControl.classList.contains('active')) { locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); From f7b0341ed84b72c3d3b51250ea564017f9f99d44 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Tue, 4 Apr 2023 16:53:13 -0400 Subject: [PATCH 33/36] tooltip test added --- test/e2e/mapml-viewer/locateButton.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index 2bf567b32..41242478e 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -32,9 +32,11 @@ test.describe("Geolocation control tests", () => { let locateButton_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); locateButton_lat = parseFloat(locateButton_lat).toFixed(3); locateButton_lng = parseFloat(locateButton_lng).toFixed(3); + let tooltip = await page.$eval("body > mapml-viewer", (viewer) => viewer._geolocationButton._marker._tooltip._content); expect(locateButton_lat).toEqual("45.503"); expect(locateButton_lng).toEqual("-73.568"); + expect(tooltip).toEqual("My current location, shown on map"); }); @@ -66,6 +68,8 @@ test.describe("Geolocation control tests", () => { await page.mouse.down(); await page.mouse.move(1200, 450, {steps: 5}); await page.mouse.up(); + let tooltip = await page.$eval("body > mapml-viewer", (viewer) => viewer._geolocationButton._marker._tooltip._content); + expect(tooltip).toEqual("My last known location, shown on map"); await page.click("body > mapml-viewer"); let locateButton_title3 = await page.$eval("div > div.leaflet-control-container > div.leaflet-bottom.leaflet-right > div > a", (button) => button.title); From 0deb72b9a37b321c1056c139395b86dfef463708 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 5 Apr 2023 10:34:35 -0400 Subject: [PATCH 34/36] geolocationbutton code refactored --- src/mapml-viewer.js | 19 +--- src/mapml/control/GeolocationButton.js | 126 +++++++++---------------- src/web-map.js | 19 +--- 3 files changed, 44 insertions(+), 120 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index b4a75bf19..05b3090e2 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -355,7 +355,7 @@ export class MapViewer extends HTMLElement { this._fullScreenControl = M.fullscreenButton().addTo(this._map); } if (!this._geolocationButton) { - this._geolocationButton = M.geolocationButton(this._map); + this._geolocationButton = M.geolocationButton().addTo(this._map); } } @@ -538,23 +538,6 @@ export class MapViewer extends HTMLElement { {target: this}})); } }); - var locateControl = this._geolocationButton._container; - var button = this._geolocationButton; - var observer = new MutationObserver(function(mutations) { - // Check the current state of the control - if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn,{permanent:true}); - } else if (locateControl.classList.contains('active')) { - locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; - button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); - } else { - locateControl.firstChild.title = M.options.locale.btnLocTrackOff; - } - }); - // Configure the observer to watch for changes to the class name - var observerConfig = { attributes: true, attributeFilter: ['class'] }; - observer.observe(locateControl, observerConfig); this._map.on('locationfound', function (e) { diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index 443a2b78a..a2d732df1 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,86 +1,44 @@ -export var geolocationButton = function (map) { - var CustomMarker = L.Marker.extend({ - initialize(latlng, options) { - L.Util.setOptions(this, options); - this._latlng = latlng; - this.createIcon(); - this.bindTooltip(M.options.locale.btnMyLocTrackOn); - }, - /** - * Create a styled circle location marker - */ - createIcon() { - const opt = this.options; - - let style = ""; - - if (opt.color !== undefined) { - style += `stroke:${opt.color};`; - } - if (opt.weight !== undefined) { - style += `stroke-width:${opt.weight};`; - } - if (opt.fillColor !== undefined) { - style += `fill:${opt.fillColor};`; - } - if (opt.fillOpacity !== undefined) { - style += `fill-opacity:${opt.fillOpacity};`; - } - if (opt.opacity !== undefined) { - style += `opacity:${opt.opacity};`; - } - - const icon = this._getIconSVG(opt, style); - - this._locationIcon = L.divIcon({ - className: icon.className, - html: icon.svg, - iconSize: [icon.w, icon.h] - }); - - this.setIcon(this._locationIcon); - }, - - /** - * Return the raw svg for the shape - * - * Split so can be easily overridden - */ - _getIconSVG(options, style) { - const r = options.radius; - const w = options.weight; - const s = r + w; - const s2 = s * 2; - const svg = - `` + - '' + - ""; - return { - className: "leaflet-control-locate-location", - svg, - w: s2, - h: s2 - }; - }, - - setStyle(style) { - L.Util.setOptions(this, style); - this.createIcon(); - } - }); - return L.control.locate({ - markerClass: CustomMarker, - showPopup: false, - strings: { - title: M.options.locale.btnLocTrackOff - }, - position: "bottomright", - locateOptions: { - maxZoom: 16 - }, +export var GeolocationButton = L.Control.extend({ + options: { + position: 'bottomright', + }, + + onAdd: function (map) { + let locateControl = L.control.locate({ + showPopup: false, + strings: { + title: M.options.locale.btnLocTrackOff + }, + position: this.options.position, + locateOptions: { + maxZoom: 16 + }, }).addTo(map); + + var container = locateControl._container; + var button = locateControl; + var observer = new MutationObserver(function(mutations) { + if (container.classList.contains('active') && container.classList.contains('following')) { + container.firstChild.title = M.options.locale.btnLocTrackOn; + button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn,{permanent:true}); + } else if (container.classList.contains('active')) { + container.firstChild.title = M.options.locale.btnLocTrackLastKnown; + button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); + } else { + container.firstChild.title = M.options.locale.btnLocTrackOff; + } + }); + var observerConfig = { attributes: true, attributeFilter: ['class'] }; + observer.observe(container, observerConfig); + + return container; + }, + + onRemove: function (map) { + // + }, +}); + +export var geolocationButton = function (options) { + return new GeolocationButton(options); }; diff --git a/src/web-map.js b/src/web-map.js index 8a5271b54..56da275a3 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -398,7 +398,7 @@ export class WebMap extends HTMLMapElement { this._fullScreenControl = M.fullscreenButton().addTo(this._map); } if (!this._geolocationButton) { - this._geolocationButton = M.geolocationButton(this._map); + this._geolocationButton = M.geolocationButton().addTo(this._map); } } @@ -581,23 +581,6 @@ export class WebMap extends HTMLMapElement { {target: this}})); } }); - var locateControl = this._geolocationButton._container; - var button = this._geolocationButton; - var observer = new MutationObserver(function(mutations) { - // Check the current state of the control - if (locateControl.classList.contains('active') && locateControl.classList.contains('following')) { - locateControl.firstChild.title = M.options.locale.btnLocTrackOn; - button._marker.bindTooltip(M.options.locale.btnMyLocTrackOn,{permanent:true}); - } else if (locateControl.classList.contains('active')) { - locateControl.firstChild.title = M.options.locale.btnLocTrackLastKnown; - button._marker.bindTooltip(M.options.locale.btnMyLastKnownLocTrackOn); - } else { - locateControl.firstChild.title = M.options.locale.btnLocTrackOff; - } - }); - // Configure the observer to watch for changes to the class name - var observerConfig = { attributes: true, attributeFilter: ['class'] }; - observer.observe(locateControl, observerConfig); this._map.on('locationfound', function (e) { From ba0052f208b7f34844de8781ead286ed2858a4c5 Mon Sep 17 00:00:00 2001 From: Jacky0299 Date: Wed, 5 Apr 2023 12:08:34 -0400 Subject: [PATCH 35/36] fixed tests and small fixes --- src/mapml/control/GeolocationButton.js | 10 +++++++--- test/e2e/mapml-viewer/locateButton.test.js | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index a2d732df1..c48b17951 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -4,7 +4,7 @@ export var GeolocationButton = L.Control.extend({ }, onAdd: function (map) { - let locateControl = L.control.locate({ + this.locateControl = L.control.locate({ showPopup: false, strings: { title: M.options.locale.btnLocTrackOff @@ -15,8 +15,8 @@ export var GeolocationButton = L.Control.extend({ }, }).addTo(map); - var container = locateControl._container; - var button = locateControl; + var container = this.locateControl._container; + var button = this.locateControl; var observer = new MutationObserver(function(mutations) { if (container.classList.contains('active') && container.classList.contains('following')) { container.firstChild.title = M.options.locale.btnLocTrackOn; @@ -37,6 +37,10 @@ export var GeolocationButton = L.Control.extend({ onRemove: function (map) { // }, + + stop: function () { + return this.locateControl.stop(); + }, }); export var geolocationButton = function (options) { diff --git a/test/e2e/mapml-viewer/locateButton.test.js b/test/e2e/mapml-viewer/locateButton.test.js index 41242478e..c8949d4a5 100644 --- a/test/e2e/mapml-viewer/locateButton.test.js +++ b/test/e2e/mapml-viewer/locateButton.test.js @@ -32,7 +32,7 @@ test.describe("Geolocation control tests", () => { let locateButton_lng = await page.$eval("body > mapml-viewer", (viewer) => viewer.lon); locateButton_lat = parseFloat(locateButton_lat).toFixed(3); locateButton_lng = parseFloat(locateButton_lng).toFixed(3); - let tooltip = await page.$eval("body > mapml-viewer", (viewer) => viewer._geolocationButton._marker._tooltip._content); + let tooltip = await page.$eval("body > mapml-viewer", (viewer) => viewer._geolocationButton.locateControl._marker._tooltip._content); expect(locateButton_lat).toEqual("45.503"); expect(locateButton_lng).toEqual("-73.568"); @@ -68,7 +68,7 @@ test.describe("Geolocation control tests", () => { await page.mouse.down(); await page.mouse.move(1200, 450, {steps: 5}); await page.mouse.up(); - let tooltip = await page.$eval("body > mapml-viewer", (viewer) => viewer._geolocationButton._marker._tooltip._content); + let tooltip = await page.$eval("body > mapml-viewer", (viewer) => viewer._geolocationButton.locateControl._marker._tooltip._content); expect(tooltip).toEqual("My last known location, shown on map"); await page.click("body > mapml-viewer"); From 451a6d2266f21b9cc9a6bf0afc6779367f486c7c Mon Sep 17 00:00:00 2001 From: Peter Rushforth Date: Wed, 5 Apr 2023 14:18:20 -0400 Subject: [PATCH 36/36] Formatting --- src/mapml/control/GeolocationButton.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/mapml/control/GeolocationButton.js b/src/mapml/control/GeolocationButton.js index c48b17951..1ba633aa8 100644 --- a/src/mapml/control/GeolocationButton.js +++ b/src/mapml/control/GeolocationButton.js @@ -1,6 +1,6 @@ export var GeolocationButton = L.Control.extend({ options: { - position: 'bottomright', + position: 'bottomright' }, onAdd: function (map) { @@ -12,7 +12,7 @@ export var GeolocationButton = L.Control.extend({ position: this.options.position, locateOptions: { maxZoom: 16 - }, + } }).addTo(map); var container = this.locateControl._container; @@ -34,13 +34,9 @@ export var GeolocationButton = L.Control.extend({ return container; }, - onRemove: function (map) { - // - }, - stop: function () { return this.locateControl.stop(); - }, + } }); export var geolocationButton = function (options) {