diff --git a/changelog.xml b/changelog.xml
index 8050208..05dde6d 100644
--- a/changelog.xml
+++ b/changelog.xml
@@ -39,6 +39,9 @@
4.0.6
- Tiding up language files
+ - Add possiblility for layer control with satellite in frontend
+ - Make separators more visible in the backend
+ - Correct Panel Layer
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/js/aggpxtrack.js b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/js/aggpxtrack.js
index 64942b0..3212f55 100644
--- a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/js/aggpxtrack.js
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/js/aggpxtrack.js
@@ -7,6 +7,8 @@ document.addEventListener('DOMContentLoaded', function () {
var unique = element.getAttribute('data-unique');
var scrollwheelzoom = element.getAttribute('data-scrollwheelzoom');
var maptype = element.getAttribute('data-maptype');
+ var layertree = element.getAttribute('data-layertree');
+
var thunderforestkey = element.getAttribute('data-thunderforestkey');
var mapboxkey = element.getAttribute('data-mapboxkey');
var geoportailfrancekey = element.getAttribute('data-geoportailfrancekey');
@@ -204,37 +206,42 @@ document.addEventListener('DOMContentLoaded', function () {
}
// Load the correct map
+ // Base layer url
+ var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ maxZoom: 18,
+ attribution: '© OpenStreetMap'
+ });
if (maptype === "osm")
{
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '© OpenStreetMap'
- }).addTo(window['mymap' + unique]);
+ });
} else if (maptype === "osm_bw")
{
- L.tileLayer('http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', {
+ tileLayer = L.tileLayer('http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '© OpenStreetMap'
- }).addTo(window['mymap' + unique]);
+ });
} else if (maptype === 'thunderforest')
{
- L.tileLayer('https://{s}.tile.thunderforest.com/' + thunderforestmaptype + '/{z}/{x}/{y}.png?apikey={apikey}', {
+ tileLayer = L.tileLayer('https://{s}.tile.thunderforest.com/' + thunderforestmaptype + '/{z}/{x}/{y}.png?apikey={apikey}', {
maxZoom: 22,
apikey: thunderforestkey,
attribution: '© Thunderforest, © OpenStreetMap'
- }).addTo(window['mymap' + unique]);
+ });
} else if (maptype === 'mapbox')
{
- L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=' + mapboxkey, {
+ tileLayer = L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=' + mapboxkey, {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
- }).addTo(window['mymap' + unique]);
+ });
} else if (maptype === 'geoportailfrance')
{
- L.tileLayer('https://wxs.ign.fr/{apikey}/geoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE={style}&TILEMATRIXSET=PM&FORMAT={format}&LAYER={variant}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}', {
+ tileLayer = L.tileLayer('https://wxs.ign.fr/{apikey}/geoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE={style}&TILEMATRIXSET=PM&FORMAT={format}&LAYER={variant}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}', {
attribution: 'Geoportail France',
bounds: [[-75, -180], [81, 180]],
minZoom: 2,
@@ -245,25 +252,40 @@ document.addEventListener('DOMContentLoaded', function () {
format: 'image/png',
style: 'normal',
variant: 'GEOGRAPHICALGRIDSYSTEMS.PLANIGNV2'
- }).addTo(window['mymap' + unique]);
+ });
} else if (maptype === 'opentopomap')
{
- L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
+ tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: © OpenStreetMap, SRTM | Map style: © OpenTopoMap (CC-BY-SA)'
- }).addTo(window['mymap' + unique]);
+ });
} else if (maptype === 'google')
{
- L.gridLayer.googleMutant({
+ tileLayer = L.gridLayer.googleMutant({
type: googlemapstype,
- }).addTo(window['mymap' + unique]);
+ });
} else
{
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: ''
- }).addTo(window['mymap' + unique]);
+ });
}
+ tileLayer.addTo(window['mymap' + unique]);
+
+ if (layertree == '1') {
+ var tileLayer2 = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
+ maxZoom: 16,
+ attribution: 'Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
+ id: ''
+ });
+ var baseMaps = {
+ "Map": tileLayer,
+ "Satellit": tileLayer2
+ };
+ L.control.layers(baseMaps).addTo(window['mymap' + unique]);
+ }
+
// KML Overlay
if (show_omnivore === "1")
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.eslintrc b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.eslintrc
new file mode 100644
index 0000000..2e16c4a
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.eslintrc
@@ -0,0 +1,35 @@
+{
+ "globals": {
+ "define": true,
+ "module": true,
+ "L": true
+ },
+ "plugins": [
+ "html"
+ ],
+ "settings": {
+ "html/indent": "space",
+ "html/report-bad-indent": 2
+ },
+ "rules": {
+ "camelcase": "error",
+ "comma-spacing": ["error", {"after": true}],
+ "comma-style": "error",
+ "indent": "error",
+ "key-spacing": "error",
+ "keyword-spacing": "error",
+ "no-console": "error",
+ "no-constant-condition": "off",
+ "no-lonely-if": "error",
+ "no-multi-spaces": "error",
+ "no-shadow": "off",
+ "no-trailing-spaces": "error",
+ "no-underscore-dangle": "off",
+ "object-curly-spacing": "error",
+ "quotes": ["error", "single", "avoid-escape"],
+ "space-before-blocks": "error",
+ "space-before-function-paren": ["error", "never"],
+ "space-in-parens": "error",
+ "strict": "off"
+ }
+}
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.github/workflows/main.yml b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.github/workflows/main.yml
new file mode 100644
index 0000000..bfbc421
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.github/workflows/main.yml
@@ -0,0 +1,52 @@
+name: CI
+
+on:
+ # Triggers the workflow on push or pull request events but only for the master branch
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [ 14.x ]
+ working-directory: [ '.', './test-types' ]
+
+ steps:
+ - name: Checkout branch
+ uses: actions/checkout@v2
+
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v1
+ with:
+ node-version: ${{ matrix.node-version }}
+
+ - name: Cache node modules
+ uses: actions/cache@v2
+ env:
+ cache-name: cache-node-modules-${{ matrix.working-directory }}
+ with:
+ # npm cache files are stored in `~/.npm` on Linux/macOS
+ path: ~/.npm
+ key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-build-${{ env.cache-name }}-
+ ${{ runner.os }}-build-
+ ${{ runner.os }}-
+
+ - name: Install dependencies
+ run: npm ci
+ working-directory: ${{ matrix.working-directory }}
+
+ - name: Run tests
+ run: npm run test
+ working-directory: ${{ matrix.working-directory }}
+ timeout-minutes: 2
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.github/workflows/npm-publish.yml b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.github/workflows/npm-publish.yml
new file mode 100644
index 0000000..a1b63b3
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.github/workflows/npm-publish.yml
@@ -0,0 +1,29 @@
+name: npm-publish
+
+on:
+ push:
+ branches: [ master ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ npm-publish:
+ name: npm-publish
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ - name: Publish if version has been updated
+ uses: pascalgn/npm-publish-action@1.3.7
+ with: # All of theses inputs are optional
+ tag_name: "v%s"
+ tag_message: "v%s"
+ create_tag: "true"
+ commit_pattern: "^Release (\\S+)"
+ workspace: "."
+ publish_command: "yarn"
+ publish_args: "--non-interactive"
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated
+ NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # You need to set this in your repo settings
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.npmignore b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.npmignore
new file mode 100644
index 0000000..b57d3d0
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/.npmignore
@@ -0,0 +1,7 @@
+.github
+.travis.yml
+.vscode
+bower.json
+examples
+test
+test-types
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.css b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.css
new file mode 100644
index 0000000..93909d7
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.css
@@ -0,0 +1,61 @@
+.leaflet-control-layers-toggle.leaflet-layerstree-named-toggle {
+ margin: 2px 5px;
+ width: auto;
+ height: auto;
+ background-image: none;
+}
+
+.leaflet-layerstree-node {
+}
+
+.leaflet-layerstree-header input{
+ margin-left: 0px;
+}
+
+
+.leaflet-layerstree-header {
+}
+
+.leaflet-layerstree-header-pointer {
+ cursor: pointer;
+}
+
+.leaflet-layerstree-header label {
+ display: inline-block;
+ cursor: pointer;
+}
+
+.leaflet-layerstree-header-label {
+}
+
+.leaflet-layerstree-header-name {
+}
+
+.leaflet-layerstree-header-space {
+}
+
+.leaflet-layerstree-children {
+ padding-left: 10px;
+}
+
+.leaflet-layerstree-children-nopad {
+ padding-left: 0px;
+}
+
+.leaflet-layerstree-closed {
+}
+
+.leaflet-layerstree-opened {
+}
+
+.leaflet-layerstree-hide {
+ display: none;
+}
+
+.leaflet-layerstree-nevershow {
+ display: none;
+}
+
+.leaflet-layerstree-expand-collapse {
+ cursor: pointer;
+}
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.d.ts b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.d.ts
new file mode 100644
index 0000000..8f140a5
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.d.ts
@@ -0,0 +1,115 @@
+import * as L from 'leaflet';
+
+declare module 'leaflet' {
+ namespace Control {
+ namespace Layers {
+ interface TreeObject {
+ children?: TreeObject[];
+ collapsed?: boolean;
+ label: string;
+ layer?: L.Layer;
+ name?: string;
+ selectAllCheckbox?: boolean | string;
+ }
+
+ interface TreeOptions extends L.Control.LayersOptions {
+ /**
+ * Symbol displayed on a closed node.
+ * @default '+'
+ */
+ closedSymbol?: string;
+
+ /**
+ * Symbol displayed on an open node.
+ * @default '−' (−)
+ */
+ openedSymbol?: string;
+
+ /**
+ * Symbol between the closed or opened symbol, and the text.
+ * @default ' ' (a normal space)
+ */
+ spaceSymbol?: string;
+
+ /**
+ * Flag to indicate if the selector (+ or −) is after the text.
+ * @default false
+ */
+ selectorBack?: boolean;
+
+ /**
+ * Flag to replace the toggle image (box with the layers image)
+ * with the 'name' of the selected base layer.
+ * If the name field is not present in the tree for this layer,
+ * label is used.
+ * See that you can show a different name when control is
+ * collapsed than the one that appears in the tree
+ * when it is expanded. Your node in the tree can be
+ * { label: 'OSM', name: 'OpenStreetMap', layer: layer }.
+ * @default false
+ */
+ namedToggle?: boolean;
+
+ /**
+ * Text for an entry in control that collapses the tree
+ * (baselayers or overlays).
+ * If empty, no entry is created.
+ * @default ''.
+ */
+ collapseAll?: string;
+
+ /**
+ * Text for an entry in control that expands the tree.
+ * If empty, no entry is created.
+ * @default ''
+ */
+ expandAll?: string;
+
+ /**
+ * Controls if a label or only the checkbox/radio button can
+ * toggle layers.
+ * If set to both, overlay or base those labels can be clicked on
+ * to toggle the layer.
+ * @default 'both'.
+ */
+ labelIsSelector?: 'both' | 'overlay' | 'base' | string;
+ }
+
+ class Tree extends L.Control.Layers {
+ constructor(
+ baseTree?: TreeObject,
+ overlayTree?: TreeObject,
+ options?: TreeOptions
+ );
+
+ setBaseTree(baseTree: TreeObject): this;
+
+ setOverlayTree(overlayTree: TreeObject): this;
+
+ addBaseLayer(layer: L.Layer, name: string): never;
+
+ addOverlay(layer: L.Layer, name: string): never;
+
+ removeLayer(layer: L.Layer): never;
+
+ expandTree(isOverlay?: boolean): this;
+
+ collapseTree(isOverlay?: boolean): this;
+
+ expandSelected(isOverlay?: boolean): this;
+
+ options: TreeOptions;
+ }
+ }
+ }
+
+ namespace control {
+ namespace layers {
+ function tree(
+ baseTree?: Control.Layers.TreeObject,
+ overlayTree?: Control.Layers.TreeObject,
+ options?: Control.Layers.TreeOptions
+ ): Control.Layers.Tree;
+ }
+ }
+}
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.js b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.js
new file mode 100644
index 0000000..b65b294
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/L.Control.Layers.Tree.js
@@ -0,0 +1,521 @@
+/*
+ * Control like L.Control.Layers, but showing layers in a tree.
+ * Do not forget to include the css file.
+ */
+
+(function(L) {
+ if (typeof L === 'undefined') {
+ throw new Error('Leaflet must be included first');
+ }
+
+ /*
+ * L.Control.Layers.Tree extends L.Control.Layers because it reuses
+ * most of its functionality. Only the HTML creation is different.
+ */
+ L.Control.Layers.Tree = L.Control.Layers.extend({
+ options: {
+ closedSymbol: '+',
+ openedSymbol: '−',
+ spaceSymbol: ' ',
+ selectorBack: false,
+ namedToggle: false,
+ collapseAll: '',
+ expandAll: '',
+ labelIsSelector: 'both',
+ },
+
+ // Class names are error prone texts, so write them once here
+ _initClassesNames: function() {
+ this.cls = {
+ children: 'leaflet-layerstree-children',
+ childrenNopad: 'leaflet-layerstree-children-nopad',
+ hide: 'leaflet-layerstree-hide',
+ closed: 'leaflet-layerstree-closed',
+ opened: 'leaflet-layerstree-opened',
+ space: 'leaflet-layerstree-header-space',
+ pointer: 'leaflet-layerstree-header-pointer',
+ header: 'leaflet-layerstree-header',
+ neverShow: 'leaflet-layerstree-nevershow',
+ node: 'leaflet-layerstree-node',
+ name: 'leaflet-layerstree-header-name',
+ label: 'leaflet-layerstree-header-label',
+ selAllCheckbox: 'leaflet-layerstree-sel-all-checkbox',
+ };
+ },
+
+ initialize: function(baseTree, overlaysTree, options) {
+ this._scrollTop = 0;
+ this._initClassesNames();
+ this._baseTree = null;
+ this._overlaysTree = null;
+ L.Util.setOptions(this, options);
+ L.Control.Layers.prototype.initialize.call(this, null, null, options);
+ this._setTrees(baseTree, overlaysTree);
+ },
+
+ setBaseTree: function(tree) {
+ return this._setTrees(tree);
+ },
+
+ setOverlayTree: function(tree) {
+ return this._setTrees(undefined, tree);
+ },
+
+ addBaseLayer: function(layer, name) {
+ throw 'addBaseLayer is disabled';
+ },
+
+ addOverlay: function(layer, name) {
+ throw 'addOverlay is disabled';
+ },
+
+ removeLayer: function(layer) {
+ throw 'removeLayer is disabled';
+ },
+
+ collapse: function() {
+ this._scrollTop = this._sect().scrollTop;
+ return L.Control.Layers.prototype.collapse.call(this);
+ },
+
+ expand: function() {
+ var ret = L.Control.Layers.prototype.expand.call(this);
+ this._sect().scrollTop = this._scrollTop;
+ },
+
+ onAdd: function(map) {
+ function changeName(layer) {
+ if (layer._layersTreeName) {
+ toggle.innerHTML = layer._layersTreeName;
+ }
+ }
+
+ var ret = L.Control.Layers.prototype.onAdd.call(this, map);
+ if (this.options.namedToggle) {
+ var toggle = this._container.getElementsByClassName('leaflet-control-layers-toggle')[0];
+ L.DomUtil.addClass(toggle, 'leaflet-layerstree-named-toggle');
+ // Start with this value...
+ map.eachLayer(function(layer) {changeName(layer);});
+ // ... and change it whenever the baselayer is changed.
+ map.on('baselayerchange', function(e) {changeName(e.layer);}, this);
+ }
+ return ret;
+ },
+
+ // Expands the whole tree (base other overlays)
+ expandTree: function(overlay) {
+ var container = overlay ? this._overlaysList : this._baseLayersList;
+ if (container) {
+ this._applyOnTree(container, false);
+ }
+ return this._localExpand();
+ },
+
+ // Collapses the whole tree (base other overlays)
+ collapseTree: function(overlay) {
+ var container = overlay ? this._overlaysList : this._baseLayersList;
+ if (container) {
+ this._applyOnTree(container, true);
+ }
+ return this._localExpand();
+ },
+
+ // Expands the tree, only to show the selected inputs
+ expandSelected: function(overlay) {
+ function iter(el) {
+ // Function to iterate the whole DOM upwards
+ var p = el.parentElement;
+ if (p) {
+ if (L.DomUtil.hasClass(p, that.cls.children) &&
+ !L.DomUtil.hasClass(el, that.cls.childrenNopad)) {
+ L.DomUtil.removeClass(p, hide);
+ }
+
+ if (L.DomUtil.hasClass(p, that.cls.node)) {
+ var h = p.getElementsByClassName(that.cls.header)[0];
+ that._applyOnTree(h, false);
+ }
+ iter(p);
+ }
+ }
+
+ var that = this;
+ var container = overlay ? this._overlaysList : this._baseLayersList;
+ if (!container) return this;
+ var hide = this.cls.hide;
+ var inputs = this._layerControlInputs || container.getElementsByTagName('input');
+ for (var i = 0; i < inputs.length; i++) {
+ // Loop over every (valid) input.
+ var input = inputs[i];
+ if (this._getLayer && !!this._getLayer(input.layerId).overlay != !!overlay) continue
+ if (input.checked) {
+ // Get out of the header,
+ // to not open the posible (but rare) children
+ iter(input.parentElement.parentElement.parentElement.parentElement);
+ }
+ }
+ return this._localExpand();
+ },
+
+ // "private" methods, not exposed in the API
+ _sect: function() {
+ // to keep compatibility after 1.3 https://github.com/Leaflet/Leaflet/pull/6380
+ return this._section || this._form;
+ },
+
+ _setTrees: function(base, overlays) {
+ var id = 0; // to keep unique id
+ function iterate(tree, output, overlays) {
+ if (tree && tree.layer) {
+ if (!overlays) {
+ tree.layer._layersTreeName = tree.name || tree.label;
+ }
+ output[id++] = tree.layer;
+ }
+ if (tree && tree.children && tree.children.length) {
+ tree.children.forEach(function(child) {
+ iterate(child, output, overlays);
+ });
+ }
+ return output;
+ }
+
+ // We accept arrays, but convert into an object with children
+ function forArrays(input) {
+ if (Array.isArray(input)) {
+ return {noShow: true, children: input};
+ } else {
+ return input
+ }
+ }
+
+ // Clean everything, and start again.
+ if (this._layerControlInputs) {
+ this._layerControlInputs = [];
+ }
+ for (var i = 0; i < this._layers.length; ++i) {
+ this._layers[i].layer.off('add remove', this._onLayerChange, this);
+ }
+ this._layers = [];
+
+ if (base !== undefined) this._baseTree = forArrays(base);
+ if (overlays !== undefined) this._overlaysTree = forArrays(overlays);
+
+ var bflat = iterate(this._baseTree, {});
+ for (var i in bflat) {
+ this._addLayer(bflat[i], i);
+ }
+
+ var oflat = iterate(this._overlaysTree, {}, true);
+ for (i in oflat) {
+ this._addLayer(oflat[i], i, true);
+ }
+ return (this._map) ? this._update() : this;
+ },
+
+ // Used to update the vertical scrollbar
+ _localExpand: function() {
+ if (this._map && L.DomUtil.hasClass(this._container, 'leaflet-control-layers-expanded')) {
+ var top = this._sect().scrollTop;
+ this.expand();
+ this._sect().scrollTop = top; // to keep the scroll location
+ this._scrollTop = top;
+ }
+ return this;
+ },
+
+ // collapses or expands the tree in the containter.
+ _applyOnTree: function(container, collapse) {
+ var iters = [
+ {cls: this.cls.children, hide: collapse},
+ {cls: this.cls.opened, hide: collapse},
+ {cls: this.cls.closed, hide: !collapse},
+ ];
+ iters.forEach(function(it) {
+ var els = container.getElementsByClassName(it.cls);
+ for (var i = 0; i < els.length; i++) {
+ var el = els[i];
+ if (L.DomUtil.hasClass(el, this.cls.childrenNopad)) {
+ // do nothing
+ } else if (it.hide) {
+ L.DomUtil.addClass(el, this.cls.hide);
+ } else {
+ L.DomUtil.removeClass(el, this.cls.hide);
+ }
+ }
+ }, this);
+ },
+
+ // it is called in the original _update, and shouldn't do anything.
+ _addItem: function(obj) {
+ },
+
+ // overwrite _update function in Control.Layers
+ _update: function() {
+ if (!this._container) { return this; }
+ L.Control.Layers.prototype._update.call(this);
+ this._addTreeLayout(this._baseTree, false);
+ this._addTreeLayout(this._overlaysTree, true);
+ return this._localExpand();
+ },
+
+ // Create the DOM objects for the tree
+ _addTreeLayout: function(tree, overlay) {
+ if (!tree) return;
+ var container = overlay ? this._overlaysList : this._baseLayersList;
+ this._expandCollapseAll(overlay, this.options.collapseAll, this.collapseTree);
+ this._expandCollapseAll(overlay, this.options.expandAll, this.expandTree);
+ this._iterateTreeLayout(tree, container, overlay, [], tree.noShow)
+ if (this._checkDisabledLayers) {
+ // to keep compatibility
+ this._checkDisabledLayers();
+ }
+ },
+
+ // Create the "Collapse all" or expand, if needed.
+ _expandCollapseAll: function(overlay, text, fn, ctx) {
+ var container = overlay ? this._overlaysList : this._baseLayersList;
+ ctx = ctx ? ctx : this;
+ if (text) {
+ var o = document.createElement('div');
+ o.className = 'leaflet-layerstree-expand-collapse';
+ container.appendChild(o);
+ o.innerHTML = text;
+ o.tabIndex = 0;
+ L.DomEvent.on(o, 'click keydown', function(e) {
+ if (e.type !== 'keydown' || e.keyCode === 32) {
+ o.blur()
+ fn.call(ctx, overlay);
+ this._localExpand();
+ }
+ }, this);
+ }
+ },
+
+ // recursive function to create the DOM children
+ _iterateTreeLayout: function(tree, container, overlay, selAllNodes, noShow) {
+ if (!tree) return;
+ function creator(type, cls, append, innerHTML) {
+ var obj = L.DomUtil.create(type, cls, append);
+ if (innerHTML) obj.innerHTML = innerHTML;
+ return obj;
+ }
+
+ // create the header with it fields
+ var header = creator('div', this.cls.header, container);
+ var sel = creator('span');
+ var entry = creator('span');
+ var closed = creator('span', this.cls.closed, sel, this.options.closedSymbol);
+ var opened = creator('span', this.cls.opened, sel, this.options.openedSymbol);
+ var space = creator('span', this.cls.space, null, this.options.spaceSymbol);
+ if (this.options.selectorBack) {
+ sel.insertBefore(space, closed);
+ header.appendChild(entry);
+ header.appendChild(sel);
+ } else {
+ sel.appendChild(space);
+ header.appendChild(sel);
+ header.appendChild(entry);
+ }
+
+ function updateSelAllCheckbox(ancestor) {
+ var selector = ancestor.querySelector('input[type=checkbox]')
+ var selectedAll = true;
+ var selectedNone = true;
+ var inputs = ancestor.querySelectorAll('input[type=checkbox]');
+ [].forEach.call(inputs, function(inp) { // to work in node for tests
+ if (inp === selector) {
+ // ignore
+ } else if (inp.indeterminate) {
+ selectedAll = false;
+ selectedNone = false;
+ } else if (inp.checked) {
+ selectedNone = false;
+ } else if (!inp.checked) {
+ selectedAll = false;
+ }
+ });
+ if (selectedAll) {
+ selector.indeterminate = false
+ selector.checked = true;
+ } else if (selectedNone) {
+ selector.indeterminate = false
+ selector.checked = false;
+ } else {
+ selector.indeterminate = true;
+ selector.checked = false;
+ }
+ }
+
+ function manageSelectorsAll(input, ctx) {
+ selAllNodes.forEach(function(ancestor) {
+ L.DomEvent.on(input, 'click', function(ev) {
+ updateSelAllCheckbox(ancestor)
+ }, ctx)
+ }, ctx);
+ }
+
+ var selAll;
+ if (tree.selectAllCheckbox) {
+ selAll = this._createCheckboxElement(false);
+ selAll.className += ' ' + this.cls.selAllCheckbox;
+ }
+
+ var hide = this.cls.hide; // To toggle state
+ // create the children group, with the header event click
+ if (tree.children) {
+ var children = creator('div', this.cls.children, container);
+ var sensible = tree.layer ? sel : header;
+ L.DomUtil.addClass(sensible, this.cls.pointer);
+ sensible.tabIndex = 0;
+ L.DomEvent.on(sensible, 'click keydown', function(e) {
+ if (e.type === 'keydown' && e.keyCode !== 32) {
+ return
+ }
+ sensible.blur();
+
+ if (L.DomUtil.hasClass(opened, hide)) {
+ // it is not opened, so open it
+ L.DomUtil.addClass(closed, hide);
+ L.DomUtil.removeClass(opened, hide);
+ L.DomUtil.removeClass(children, hide);
+ } else {
+ // close it
+ L.DomUtil.removeClass(closed, hide);
+ L.DomUtil.addClass(opened, hide);
+ L.DomUtil.addClass(children, hide);
+ }
+ this._localExpand();
+ }, this);
+ if (selAll) {
+ selAllNodes.splice(0, 0, container);
+ }
+ tree.children.forEach(function(child) {
+ var node = creator('div', this.cls.node, children)
+ this._iterateTreeLayout(child, node, overlay, selAllNodes);
+ }, this);
+ if (selAll) {
+ selAllNodes.splice(0, 1);
+ }
+ } else {
+ // no children, so the selector makes no sense.
+ L.DomUtil.addClass(sel, this.cls.neverShow);
+ }
+
+ // make (or not) the label clickable to toggle the layer
+ var labelType;
+ if (tree.layer) {
+ if ((this.options.labelIsSelector === 'both') || // if option is set to both
+ (overlay && this.options.labelIsSelector === 'overlay') || // if an overlay layer and options is set to overlay
+ (!overlay && this.options.labelIsSelector === 'base')) { // if a base layer and option is set to base
+ labelType = 'label'
+ } else { // if option is set to something else
+ labelType = 'span'
+ }
+ } else {
+ labelType = 'span';
+ }
+ // create the input and label
+ var label = creator(labelType, this.cls.label, entry);
+ if (tree.layer) {
+ // now create the element like in _addItem
+ var checked = this._map.hasLayer(tree.layer)
+ var input;
+ var radioGroup = overlay ? tree.radioGroup : 'leaflet-base-layers_' + L.Util.stamp(this);
+ if (radioGroup) {
+ input = this._createRadioElement(radioGroup, checked);
+ } else {
+ input = this._createCheckboxElement(checked);
+ manageSelectorsAll(input, this);
+ }
+ if (this._layerControlInputs) {
+ // to keep compatibility with 1.0.3
+ this._layerControlInputs.push(input);
+ }
+ input.layerId = L.Util.stamp(tree.layer);
+ L.DomEvent.on(input, 'click', this._onInputClick, this);
+ label.appendChild(input);
+ }
+
+ function isText(variable) {
+ return (typeof variable === 'string' || variable instanceof String);
+ }
+
+ function isFunction(functionToCheck) {
+ return functionToCheck && {}.toString.call(functionToCheck) === '[object Function]';
+ }
+
+ function selectAllCheckboxes(select, ctx) {
+ var inputs = container.getElementsByTagName('input');
+ for (var i = 0; i < inputs.length; i++) {
+ var input = inputs[i];
+ if (input.type !== 'checkbox') continue;
+ input.checked = select;
+ input.indeterminate = false;
+ }
+ ctx._onInputClick();
+ }
+ if (tree.selectAllCheckbox) {
+ // selAll is already created
+ label.appendChild(selAll);
+ if (isText(tree.selectAllCheckbox)) {
+ selAll.title = tree.selectAllCheckbox;
+ }
+ L.DomEvent.on(selAll, 'click', function(ev) {
+ ev.stopPropagation();
+ selectAllCheckboxes(selAll.checked, this);
+ }, this);
+ updateSelAllCheckbox(container);
+ manageSelectorsAll(selAll, this);
+ }
+
+ var name = creator('span', this.cls.name, label, tree.label);
+
+ // hide the button which doesn't fit the collapsed state, then hide children conditionally
+ L.DomUtil.addClass(tree.collapsed ? opened : closed, hide);
+ tree.collapsed && children && L.DomUtil.addClass(children, hide);
+
+ if (noShow) {
+ L.DomUtil.addClass(header, this.cls.neverShow);
+ L.DomUtil.addClass(children, this.cls.childrenNopad);
+ }
+
+ var eventeds = tree.eventedClasses;
+ if (!(eventeds instanceof Array)) {
+ eventeds = [eventeds];
+ }
+
+ for (var e = 0; e < eventeds.length; e++) {
+ var evented = eventeds[e];
+ if (evented && evented.className) {
+ var obj = container.querySelector('.' + evented.className);
+ if (obj) {
+ L.DomEvent.on(obj, evented.event || 'click', (function(selectAll) {
+ return function(ev) {
+ ev.stopPropagation();
+ var select = isFunction(selectAll) ? selectAll(ev, container, tree, this._map) : selectAll;
+ if (select !== undefined && select !== null) {
+ selectAllCheckboxes(select, this);
+ }
+ }
+ })(evented.selectAll), this);
+ }
+ }
+ }
+ },
+
+ _createCheckboxElement: function(checked) {
+ var input = document.createElement('input');
+ input.type = 'checkbox';
+ input.className = 'leaflet-control-layers-selector';
+ input.defaultChecked = checked;
+ return input;
+ },
+
+ });
+
+ L.control.layers.tree = function(base, overlays, options) {
+ return new L.Control.Layers.Tree(base, overlays, options);
+ }
+
+})(L);
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/LICENSE b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/LICENSE
new file mode 100644
index 0000000..c44b57b
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/LICENSE
@@ -0,0 +1,29 @@
+BSD 3-Clause License
+
+Copyright (c) 2017, Javier Jimenez Shaw
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+* Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/README.md b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/README.md
new file mode 100644
index 0000000..0bace8e
--- /dev/null
+++ b/j4/pkg_aggpxtrack/src/media/plg_fields_aggpxtrack/layerstree/README.md
@@ -0,0 +1,191 @@
+# Leaflet.Control.Layers.Tree
+[![CI](https://github.com/jjimenezshaw/Leaflet.Control.Layers.Tree/actions/workflows/main.yml/badge.svg)](https://github.com/jjimenezshaw/Leaflet.Control.Layers.Tree/actions/workflows/main.yml)
+[![NPM version](https://img.shields.io/npm/v/leaflet.control.layers.tree.svg)](https://www.npmjs.com/package/leaflet.control.layers.tree)
+[![License](https://img.shields.io/badge/license-BSD--3--Clause-blue.svg?style=flat)](LICENSE)
+[![Leaflet 1.x compatible!](https://img.shields.io/badge/Leaflet%201.x-%E2%9C%93-1EB300.svg?style=flat)](http://leafletjs.com/reference.html)
+
+A Tree Layers Control for Leaflet.
+
+## Description
+This plugin extends [`Control.Layers`](http://leafletjs.com/reference-1.7.1.html#control-layers) allowing a tree structure for the layers layout. In `Control.Layers` you can only display a flat list of layers (baselayers and overlays), that is usually enough for small sets. If you have a long list of baselayers or overlays, and you want to organize them in a tree (allowing the user collapse and expand branches), this is a good option.
+
+[Some live examples here](https://jjimenezshaw.github.io/Leaflet.Control.Layers.Tree/examples/)
+
+## Installation
+Using npm for browserify `npm install leaflet.control.layers.tree` (and `require('leaflet.control.layers.tree')`), or just download `L.Control.Layers.Tree.js` and `L.Control.Layers.Tree.css` and add a script and link tag for it in your html.
+
+## Compatibility
+This plugin has been tested with Leaflet 1.0.3, 1.1.0, 1.2.0, 1.3.1., 1.4.0, 1.5.1, 1.6.0 and 1.7.1
+
+This plugin supports TypeScript. See file [L.Control.Layers.Tree.d.ts](L.Control.Layers.Tree.d.ts)
+
+## Usage
+1. Create your layers. Do this as usual.
+2. Create your layers tree, like the one just below.
+3. Create the control and add to the map: `L.control.layers.tree(baseTree, overlaysTree, options).addTo(map);`
+4. Voilà!
+```javascript
+var baseTree = {
+ label: 'Base Layers',
+ children: [
+ {
+ label: 'World 🗺',
+ children: [
+ { label: 'OpenStreetMap', layer: osm },
+ { label: 'Esri', layer: esri },
+ { label: 'Google Satellite', layer: g_s },
+ /* ... */
+ ]
+ },
+ {
+ label: 'Europe',
+ children: [
+ { label: 'France', layer: france },
+ { label: 'Germany', layer: germany },
+ { label: 'Spain', layer: spain },
+ /* ... */
+ ]
+ },
+ {
+ label: 'USA',
+ children: [
+ {
+ label: 'General',
+ children: [
+ { label: 'Nautical', layer: usa_naut },
+ { label: 'Satellite', layer: usa_sat },
+ { label: 'Topographical', layer: usa_topo },
+ ]
+ },
+ {
+ label: 'States',
+ children: [
+ { label: 'CA', layer: usa_ca },
+ { label: 'NY', layer: usa_ny },
+ /* ... */
+ ]
+ }
+ ]
+ },
+ ]
+};
+```
+![small tree sample](smalltree.png)
+
+```javascript
+var overlaysTree = {
+ label: 'Points of Interest',
+ selectAllCheckbox: 'Un/select all',
+ children: [
+ {
+ label: 'Europe',
+ selectAllCheckbox: true,
+ children: [
+ {
+ label: 'France',
+ selectAllCheckbox: true,
+ children: [
+ { label: 'Tour Eiffel', layer: L.marker([48.8582441, 2.2944775]) },
+ { label: 'Notre Dame', layer: L.marker([48.8529540, 2.3498726]) },
+ { label: 'Louvre', layer: L.marker([48.8605847, 2.3376267]) },
+ ]
+ }, {
+ label: 'Germany',
+ selectAllCheckbox: true,
+ children: [
+ { label: 'Branderburger Tor', layer: L.marker([52.5162542, 13.3776805])},
+ { label: 'Kölner Dom', layer: L.marker([50.9413240, 6.9581201])},
+ ]
+ }, {label: 'Spain',
+ selectAllCheckbox: 'De/seleccionar todo',
+ children: [
+ { label: 'Palacio Real', layer: L.marker([40.4184145, -3.7137051])},
+ { label: 'La Alhambra', layer: L.marker([37.1767829, -3.5892795])},
+ ]
+ }
+ ]
+ }, {
+ label: 'Asia',
+ selectAllCheckbox: true,
+ children: [
+ {
+ label: 'Jordan',
+ selectAllCheckbox: true,
+ children: [
+ { label: 'Petra', layer: L.marker([30.3292215, 35.4432464]) },
+ { label: 'Wadi Rum', layer: L.marker([29.6233486, 35.4390656]) }
+ ]
+ }, {
+ /* ... */
+ }
+ ]
+ }
+ ]
+}
+```
+![smalloverlay sample](smalloverlay.png)
+
+## API
+### `L.Control.Layers.Tree`
+The main (and only) 'class' involved in this plugin. It exteds `L.Control.Layers`, so most of its methods are available. `addBaseLayer`, `addOverlay` and `removeLayer` are non usable in `L.Control.Layers.Tree`.
+#### `L.control.layers.tree(baseTree, overlayTree, options)`
+Creates the control. The arguments are:
+* `baseTree`: `