From b569fc6a73c116ea5fbce8e6085af7a36fcfb2b8 Mon Sep 17 00:00:00 2001 From: matafokka Date: Tue, 19 Oct 2021 23:57:22 +0300 Subject: [PATCH 1/7] Reorganized code. Started working on linear aerial photography: made drawn paths connect to each other using a modification of vertex hull TSP solving algorithm. --- .../SynthBaseDrawLayer.js | 13 +- .../SynthBaseLayer.js | 40 ++- SynthLineLayer.js | 10 - SynthLineLayer/SynthLineLayer.js | 331 ++++++++++++++++++ SynthLineLayer/SynthLineSettings.js | 9 + SynthLineLayer/SynthLineWizard.js | 7 + .../SynthShapefileLayer.js | 0 .../SynthShapefileSettings.js | 0 .../SynthShapefileWizard.js | 0 main.js | 11 +- package-lock.json | 11 +- package.json | 4 +- 12 files changed, 398 insertions(+), 38 deletions(-) rename SynthBaseDrawLayer.js => SynthBase/SynthBaseDrawLayer.js (86%) rename SynthBaseLayer.js => SynthBase/SynthBaseLayer.js (84%) delete mode 100644 SynthLineLayer.js create mode 100644 SynthLineLayer/SynthLineLayer.js create mode 100644 SynthLineLayer/SynthLineSettings.js create mode 100644 SynthLineLayer/SynthLineWizard.js rename SynthShapefileLayer.js => SynthShapefileLayer/SynthShapefileLayer.js (100%) rename SynthShapefileSettings.js => SynthShapefileLayer/SynthShapefileSettings.js (100%) rename SynthShapefileWizard.js => SynthShapefileLayer/SynthShapefileWizard.js (100%) diff --git a/SynthBaseDrawLayer.js b/SynthBase/SynthBaseDrawLayer.js similarity index 86% rename from SynthBaseDrawLayer.js rename to SynthBase/SynthBaseDrawLayer.js index bb373de3..2b9dfdc7 100644 --- a/SynthBaseDrawLayer.js +++ b/SynthBase/SynthBaseDrawLayer.js @@ -2,18 +2,16 @@ L.ALS.SynthBaseDrawLayer = L.ALS.SynthBaseLayer.extend({ defaultName: "Draw Layer", - /** - * Leaflet.Draw controls to use - */ - drawControls: {}, - init: function (wizardResults, settings) { + /** + * Leaflet.Draw controls to use + */ + this.drawControls = this.drawControls || {} + this.addBaseParametersInputSection(); this.addBaseParametersOutputSection(); - L.ALS.SynthBaseLayer.prototype.init.call(this); - /** * Leaflet.Draw group * @type {L.FeatureGroup} @@ -39,6 +37,7 @@ L.ALS.SynthBaseDrawLayer = L.ALS.SynthBaseLayer.extend({ this.control = new L.Control.Draw(options); + L.ALS.SynthBaseLayer.prototype.init.call(this, wizardResults, settings); this.addEventListenerTo(this.map, "draw:created", "onDraw"); this.onNameChange(); }, diff --git a/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js similarity index 84% rename from SynthBaseLayer.js rename to SynthBase/SynthBaseLayer.js index 4171212c..16dd98de 100644 --- a/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -10,6 +10,8 @@ const turfHelpers = require("@turf/helpers"); */ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.prototype */{ + hasYOverlay: true, + init: function () { this.serializationIgnoreList.push("_airportMarker"); @@ -43,7 +45,12 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot new L.ALS.Widgets.Number("cameraWidth", "cameraWidth", this, "calculateParameters").setMin(1).setStep(1).setValue(17000), new L.ALS.Widgets.Number("cameraHeight", "cameraHeight", this, "calculateParameters").setMin(1).setStep(1).setValue(17000), new L.ALS.Widgets.Number("pixelWidth", "pixelWidth", this, "calculateParameters").setMin(0.1).setStep(0.1).setValue(5), - new L.ALS.Widgets.Number("overlayBetweenPaths", "overlayBetweenPaths", this, "calculateParameters").setMin(60).setMax(100).setStep(0.1).setValue(60), + ); + + if (this.hasYOverlay) + this.addWidget(new L.ALS.Widgets.Number("overlayBetweenPaths", "overlayBetweenPaths", this, "calculateParameters").setMin(60).setMax(100).setStep(0.1).setValue(60)); + + this.addWidgets( new L.ALS.Widgets.Number("overlayBetweenImages", "overlayBetweenImages", this, "calculateParameters").setMin(30).setMax(100).setStep(0.1).setValue(30), new L.ALS.Widgets.Number("focalLength", "focalLength", this, "calculateParameters").setMin(0.001).setStep(1).setValue(112), @@ -55,14 +62,21 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot }, addBaseParametersOutputSection: function () { + let yWidgets = []; + if (this.hasYOverlay) { + yWidgets = [ + new L.ALS.Widgets.ValueLabel("ly", "ly", "m"), + new L.ALS.Widgets.ValueLabel("Ly", "Ly", "m"), + new L.ALS.Widgets.ValueLabel("By", "By", "m"), + ]; + } + let valueLabels = [ new L.ALS.Widgets.ValueLabel("flightHeight", "flightHeight"), new L.ALS.Widgets.ValueLabel("lx", "lx", "m"), new L.ALS.Widgets.ValueLabel("Lx", "Lx", "m"), new L.ALS.Widgets.ValueLabel("Bx", "Bx", "m"), - new L.ALS.Widgets.ValueLabel("ly", "ly", "m"), - new L.ALS.Widgets.ValueLabel("Ly", "Ly", "m"), - new L.ALS.Widgets.ValueLabel("By", "By", "m"), + ...yWidgets, new L.ALS.Widgets.ValueLabel("GSI", "GSI", "m"), new L.ALS.Widgets.ValueLabel("IFOV", "IFOV", "μrad"), new L.ALS.Widgets.ValueLabel("GIFOV", "GIFOV", "m"), @@ -79,7 +93,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot calculateParameters: function () { let parameters = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "imageScale", "overlayBetweenPaths", "overlayBetweenImages", "aircraftSpeed"]; for (let param of parameters) - this[param] = this.getWidgetById(param).getValue(); + this[param] = this.getWidgetById(param)?.getValue(); this.flightHeight = this["imageScale"] * this["focalLength"]; let cameraParametersWarning = this.getWidgetById("cameraParametersWarning"); @@ -91,9 +105,11 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot let pixelWidth = this["pixelWidth"] * 1e-6; let focalLength = this["focalLength"] * 0.001; - this.ly = this["cameraWidth"] * pixelWidth; // Image size in meters - this.Ly = this.ly * this["imageScale"] // Image width on the ground - this.By = this.Ly * (100 - this["overlayBetweenPaths"]) / 100; // Distance between paths + if (this.hasYOverlay) { + this.ly = this["cameraWidth"] * pixelWidth; // Image size in meters + this.Ly = this.ly * this["imageScale"] // Image width on the ground + this.By = this.Ly * (100 - this["overlayBetweenPaths"]) / 100; // Distance between paths + } this.lx = this["cameraHeight"] * pixelWidth; // Image height this.Lx = this.lx * this["imageScale"]; // Image height on the ground @@ -110,11 +126,15 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot let names = ["flightHeight", "lx", "Lx", "Bx", "ly", "Ly", "By", "GSI", "IFOV", "GIFOV", "FOV", "GFOV",]; for (let name of names) { + const field = this[name]; + if (field === undefined) + continue; + let value; try { - value = this.toFixed(this[name]); + value = this.toFixed(field); } catch (e) { - value = this[name]; + value = field; } this.getWidgetById(name).setValue(value); } diff --git a/SynthLineLayer.js b/SynthLineLayer.js deleted file mode 100644 index 9d948985..00000000 --- a/SynthLineLayer.js +++ /dev/null @@ -1,10 +0,0 @@ -L.ALS.SynthLineLayer = L.ALS.SynthBaseDrawLayer.extend({ - defaultName: "Line Layer", - drawControls: { - polyline: { - shapeOptions: { - color: "#ff0000" - } - } - }, -}); \ No newline at end of file diff --git a/SynthLineLayer/SynthLineLayer.js b/SynthLineLayer/SynthLineLayer.js new file mode 100644 index 00000000..5850e3bb --- /dev/null +++ b/SynthLineLayer/SynthLineLayer.js @@ -0,0 +1,331 @@ +L.ALS.SynthLineLayer = L.ALS.SynthBaseDrawLayer.extend({ + defaultName: "Line Layer", + hasYOverlay: false, + hideCapturePoints: true, + hidePathsConnections: false, + + init: function (wizardResults, settings) { + + this.drawControls = { + polyline: { + shapeOptions: { + color: "#ff0000" + } + } + } + + this.addWidgets( + new L.ALS.Widgets.Checkbox("hideCapturePoints", "hideCapturePoints", this, "_hideCapturePoints").setValue(true), + new L.ALS.Widgets.Checkbox("hidePathsConnections", "hidePathsConnections", this, "_hidePathsConnections"), + new L.ALS.Widgets.Color("color", "lineLayerColor", this, "setColorByWidget").setValue(settings.color) + ); + + this.pointsGroup = L.featureGroup(); + this.connectionsGroup = L.featureGroup(); + + L.ALS.SynthBaseDrawLayer.prototype.init.call(this, wizardResults, settings); + this.setColor(settings.color); + this.addEventListenerTo(this.map, "draw:drawstart draw:editstart draw:deletestart", "onEditStart"); + this.addEventListenerTo(this.map, "draw:drawstop draw:editstop draw:deletestop", "updatePaths"); + }, + + onEditStart: function () { + this.map.removeLayer(this.connectionsGroup); + this.map.removeLayer(this.pointsGroup); + }, + + updatePaths: function () { + this.connectPaths(); + + if (!this.getWidgetById("hidePathsConnections").getValue()) + this.map.addLayer(this.connectionsGroup) + + if (!this.getWidgetById("hideCapturePoints").getValue()) + this.map.addLayer(this.pointsGroup) + }, + + _hideCapturePoints: function (widget) { + this.hideCapturePoints = widget.getValue(); + this.updatePaths(); + }, + _hidePathsConnections: function (widget) { + this.hidePathsConnections = widget.getValue(); + this.updatePaths(); + }, + + onMarkerDrag: function () { + L.ALS.SynthBaseLayer.prototype.onMarkerDrag.call(this); + this.updatePaths(); + }, + + /** + * Connects paths + */ + connectPaths: function () { + // Actual paths are fixed and thus can't be optimized, so we should care only about their endpoints. + // We'll call them paths for the rest of this program. We have to connect line segments in shortest way. + + // This problem is similar to TSP and VRP. VRP is subset of TSP, much harder to solve and might not even work. + // Trust me, I tried Clarke-Wright algorithm and it didn't work (looks like it leaves points for other routes). + // So we'll go with TSP. Convex hull algorithm fits well to our problem and is easy to modify for our needs. + + // Hull will miss some of the endpoints, so for now the only modifications are connecting the missing point + // and adjusting connections between paths to get the shortest hull. I'll explain how we'll do it later. + + // Sources: + // TSP algorithms overview: http://160592857366.free.fr/joe/ebooks/ShareData/Heuristics%20for%20the%20Traveling%20Salesman%20Problem%20By%20Christian%20Nillson.pdf + // Monotone chain implementation: https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#JavaScript + + this.connectionsGroup.clearLayers(); + + const points = [], layers = this.drawingGroup.getLayers(), airportPos = this._airportMarker.getLatLng(), + lineOptions = { + color: this.drawControls.polyline.shapeOptions.color, + dashArray: "4 8", + }; + + if (layers.length === 0) + return; + + if (layers.length === 1) { + const path = layers[0].getLatLngs(); + this.connectionsGroup.addLayer(L.polyline( + [path[0], airportPos, path[path.length - 1]], + lineOptions + )); + return; + } + + // Get points from each path and mark them as belonged to a current path + // TODO: Remove debug props before release + let debugProps = {color: "purple"}, paths = {}, pathsCount = layers.length; // Keep paths for merging + for (let path of layers) { + const layer = path.getLatLngs(), endPoints = [layer[0], layer[layer.length - 1]]; + //this.connectionsGroup.addLayer(L.polyline(endPoints, debugProps)); + + endPoints.connectionId = L.ALS.Helpers.generateID(); + paths[endPoints.connectionId] = endPoints; + + for (let p of endPoints) { + const newP = L.latLng(p); + newP.path = endPoints; + points.push(newP); + } + } + + // Find convex hull of the points using monotone chain. + points.sort((a, b) => { + return a.lng === b.lng ? a.lat - b.lat : a.lng - b.lng; + }); + + let lower = []; + for (let point of points) { + while (lower.length >= 2 && this.cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0) + lower.pop(); + lower.push(point); + } + + let upper = []; + for (let i = points.length - 1; i >= 0; i--) { + const point = points[i]; + while (upper.length >= 2 && this.cross(upper[upper.length - 2], upper[upper.length - 1], point) <= 0) + upper.pop(); + upper.push(point); + } + + lower.pop(); + upper.pop(); + + // Normalize path-connection order by appending other point of the first path, if it's not connected already. + let [p1, p2] = lower; + if (!p2) + p2 = upper[0]; + if (p1.path !== p2.path) { + for (let p of p1.path) { + if (p !== p1) { + p2 = p; + break; + } + } + lower = [p2, ...lower]; + } + + // Integrate missing end points of the paths into the hull and leave only connections. + // This points won't produce optimal path, but we'll fix it later. + let connections = [], passedPoints = {}, isFirstPoint = true; + for (let hull of [lower, upper]) { + for (let point of hull) { + if (isFirstPoint) { + isFirstPoint = false; + point.id = L.ALS.Helpers.generateID(); + connections.push(point); + passedPoints[point.id] = true; + continue; + } + + for (let p of point.path) { + if (!p.id) + p.id = L.ALS.Helpers.generateID(); + + if (passedPoints[p.id]) + continue; + + passedPoints[p.id] = true; + connections.push(p); + + // Remove point's path from the list because it'll be already connected + if (paths[p.path.connectionId]) { + delete paths[p.path.connectionId]; + pathsCount--; + } + } + } + } + + connections.push(connections[0]); // Connect endpoints of the hull + + // Optimize hull by rotating each path by 360 and checking if connections length has shortened + // Paths are always even, if counting from 0. + let optConnections = []; // We'll keep only optimal connections here and skip paths + for (let i = 2; i < connections.length - 1; i += 2) { + let pathP1 = connections[i], pathP2 = connections[i + 1], + prevPair = optConnections.pop(), // This won't exist for the first path + prevPoint = prevPair ? prevPair[0] : connections[i - 1], nextPoint = connections[i + 2]; + + optConnections.push(...this.getHullOptimalConnection(pathP1, pathP2, prevPoint, nextPoint)); + } + + // We haven't rotated the first path, so do it + let [toLast, toFirst] = this.getHullOptimalConnection(p1, p2, optConnections.pop()[0], optConnections[0][1]); + optConnections[0][0] = toFirst[0]; + optConnections.push(toLast); + + // Second path was built relying on unoptimized first line, so we have to rotate second line again. + let toSecond = optConnections[0], fromSecond = optConnections[1]; + let [optToSecond, optFromSecond] = this.getHullOptimalConnection(toSecond[1], fromSecond[0], toSecond[0], fromSecond[1]); + optConnections[0] = optToSecond; + optConnections[1] = optFromSecond; + + // Merge other paths with the hull by finding cheapest insertion and using it until no paths left + while (pathsCount > 0) { + let minLen = Infinity, insertion, insertAt, pathId; + for (let id in paths) { + if (!paths.hasOwnProperty(id)) + continue; + + let [p1, p2] = paths[id], pairs = [ + [p1, p2], + [p2, p1] + ]; + + for (let i = 0; i < optConnections.length; i++) { + let [conP1, conP2] = optConnections[i]; + + for (let pair of pairs) { + let [p1, p2] = pair, line1 = [p1, conP1], line2 = [p2, conP2], + len = this.getLineLength(line1) + this.getLineLength(line2); + + if (len < minLen) { + minLen = len; + insertion = [line1, line2]; + insertAt = i; + pathId = id; + } + } + } + } + optConnections = optConnections.slice(0, insertAt).concat(insertion, optConnections.slice(insertAt + 1, optConnections.length)); + delete paths[pathId]; + pathsCount--; + } + + // Add connections except one closest to the airport. We'll connect the airport to the paths using the shortest connection. + let minLen = Infinity, toRemove; + for (let pair of optConnections) { + let len = this.getLineLength([pair[0], airportPos]) + this.getLineLength([pair[1], airportPos]); + if (len > minLen) { + this.connectionsGroup.addLayer(L.polyline(pair, lineOptions)); + continue; + } + minLen = len; + if (toRemove) + this.connectionsGroup.addLayer(L.polyline(toRemove, lineOptions)); + toRemove = pair; + } + + this.connectionsGroup.addLayer(L.polyline([ + toRemove[0], airportPos, toRemove[1], // TODO: When implementing export, check if we need to add pairs instead of this + ], lineOptions)); + }, + + /** + * Rotates a path in hull and returns a pair of optimal connections + * @param pathP1 {LatLng|number[]} First point of path + * @param pathP2 {LatLng|number[]} Second point of path + * @param prevPoint {LatLng|number[]} Previous point + * @param nextPoint {LatLng|number[]} Next point + * @return A pair of optimal connections + */ + getHullOptimalConnection: function (pathP1, pathP2, prevPoint, nextPoint) { + let pairs = [ + [pathP1, pathP2], + [pathP2, pathP1], + ], + minLen = Infinity, optPair; + + for (let pair of pairs) { + let [p1, p2] = pair, + len = this.getLineLength([prevPoint, p1]) + this.getLineLength([nextPoint, p2]); + if (len < minLen) { + minLen = len; + optPair = pair; + } + } + return [[prevPoint, optPair[0]], [optPair[1], nextPoint]]; + }, + + cross: function (a, b, o) { + return (a.lng - o.lng) * (b.lat - o.lat) - (a.lat - o.lat) * (b.lng - o.lng); + }, + + /** + * Calculates length of a polyline + * @param line {L.Polyline|number[][]} Line to calculate length of. If array provided, lat-lng order doesn't matter as long as its consistent. + * @return {number} Line length + */ + getLineLength: function (line) { + const latLngs = line instanceof Array ? line : line.getLatLngs(); + let length = 0; + for (let i = 0; i < latLngs.length; i += 2) { + const pt1 = latLngs[i], pt2 = latLngs[i + 1]; + length += Math.sqrt( + ((pt1?.lat || pt1[0]) - (pt2.lat || pt2[0])) ** 2 + + ((pt1.lng || pt1[1]) - (pt2.lng || pt2[1])) ** 2 + ); + } + return length; + }, + + setColorByWidget: function (widget) { + this.setColor(widget.getValue()) + }, + + setColor: function (color) { + this.drawControls.polyline.shapeOptions.color = color; + for (let layer of this.drawingGroup.getLayers()) + layer.setStyle({color}); + }, + + onDraw: function (e) { + L.ALS.SynthBaseDrawLayer.prototype.onDraw.call(this, e); + e.layer.setStyle({ + color: this.getWidgetById("color").getValue(), + opacity: 1 + }); + }, + + statics: { + wizard: L.ALS.SynthLineWizard, + settings: new L.ALS.SynthLineSettings(), + } +}); \ No newline at end of file diff --git a/SynthLineLayer/SynthLineSettings.js b/SynthLineLayer/SynthLineSettings.js new file mode 100644 index 00000000..ac1c063e --- /dev/null +++ b/SynthLineLayer/SynthLineSettings.js @@ -0,0 +1,9 @@ +L.ALS.SynthLineSettings = L.ALS.Settings.extend({ + + initialize: function () { + L.ALS.Settings.prototype.initialize.call(this); + const color = "#ff0000"; + this.addWidget(new L.ALS.Widgets.Color("color", "settingsLineLayerColor").setValue(color), color); + } + +}) \ No newline at end of file diff --git a/SynthLineLayer/SynthLineWizard.js b/SynthLineLayer/SynthLineWizard.js new file mode 100644 index 00000000..e36a88e1 --- /dev/null +++ b/SynthLineLayer/SynthLineWizard.js @@ -0,0 +1,7 @@ +L.ALS.SynthLineWizard = L.ALS.Wizard.extend({ + displayName: "lineLayerName", + initialize: function () { + L.ALS.Wizard.prototype.initialize.call(this); + this.addWidget(new L.ALS.Widgets.SimpleLabel("lbl", "lineLayerWizardLabel").setStyle("message")); + } +}); \ No newline at end of file diff --git a/SynthShapefileLayer.js b/SynthShapefileLayer/SynthShapefileLayer.js similarity index 100% rename from SynthShapefileLayer.js rename to SynthShapefileLayer/SynthShapefileLayer.js diff --git a/SynthShapefileSettings.js b/SynthShapefileLayer/SynthShapefileSettings.js similarity index 100% rename from SynthShapefileSettings.js rename to SynthShapefileLayer/SynthShapefileSettings.js diff --git a/SynthShapefileWizard.js b/SynthShapefileLayer/SynthShapefileWizard.js similarity index 100% rename from SynthShapefileWizard.js rename to SynthShapefileLayer/SynthShapefileWizard.js diff --git a/main.js b/main.js index 60e36701..01f3d8e4 100644 --- a/main.js +++ b/main.js @@ -12,14 +12,16 @@ require("leaflet-advanced-layer-system"); L.ALS.Locales.AdditionalLocales.Russian(); require("./locales/English.js"); require("./locales/Russian.js"); -require("./SynthShapefileLayer.js"); -require("./SynthBaseLayer.js"); +require("./SynthShapefileLayer/SynthShapefileLayer.js"); +require("./SynthBase/SynthBaseLayer.js"); require("./SynthGridLayer/SynthGridLayer.js"); require("./node_modules/leaflet.coordinates/dist/Leaflet.Coordinates-0.1.5.min.js"); require("leaflet-draw"); -require("./SynthBaseDrawLayer.js"); +require("./SynthBase/SynthBaseDrawLayer.js"); require("./SynthPolygonLayer.js"); -require("./SynthLineLayer.js"); +require("./SynthLineLayer/SynthLineWizard.js"); +require("./SynthLineLayer/SynthLineSettings.js"); +require("./SynthLineLayer/SynthLineLayer.js"); L.ALS.System.initializeSystem(); @@ -93,5 +95,6 @@ for (let country of countries) { layerSystem.addBaseLayer(L.tileLayer(""), "Empty"); // Add layer types +layerSystem.addLayerType(L.ALS.SynthLineLayer); layerSystem.addLayerType(L.ALS.SynthGridLayer); layerSystem.addLayerType(L.ALS.SynthShapefileLayer); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2ae9d3fc..d16d73fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3124,9 +3124,9 @@ } }, "electron": { - "version": "10.1.5", - "resolved": "https://registry.npmjs.org/electron/-/electron-10.1.5.tgz", - "integrity": "sha512-fys/KnEfJq05TtMij+lFvLuKkuVH030CHYx03iZrW5DNNLwjE6cW3pysJ420lB0FRSfPjTHBMu2eVCf5TG71zQ==", + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/electron/-/electron-10.4.7.tgz", + "integrity": "sha512-je+AokZfKldI5GItXOx5pwBEAnbEqTrEPhaRUm2RN0OFBPXO+7wjJ3X+HvvlOHvKtfZrlU+57Dmkg1DseSFOPA==", "dev": true, "requires": { "@electron/get": "^1.0.1", @@ -4817,8 +4817,9 @@ "dev": true }, "leaflet-advanced-layer-system": { - "version": "file:https:/registry.npmjs.org/leaflet-advanced-layer-system/-/leaflet-advanced-layer-system-1.2.0.tgz", - "integrity": "sha512-5wDUtFV/MIlvvk/a55OU259RPuiWRHP40LMf1LLgYRQqq+8rjrI9qgd4MtMfuQ+Kc7fO4FeimaP0dU0jTSHnfw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/leaflet-advanced-layer-system/-/leaflet-advanced-layer-system-2.0.0.tgz", + "integrity": "sha512-IaZ5kpCNH6aEtN5Cmf5F6QQF/XBBcj+rL3bGYKxUgyoQd9ZSErANL55SIAZzzvQ7jwq5Qs+uw8KNx9Vps5LwTw==", "dev": true }, "leaflet-draw": { diff --git a/package.json b/package.json index 331768f8..138ccfb2 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "core-js": "^3.7.0", "cssnano": "^4.1.10", "debounce": "^1.2.1", - "electron": "^10.1.5", + "electron": "^10.4.7", "electron-packager": "^15.2.0", "fastestsmallesttextencoderdecoder": "^1.0.22", "fs-extra": "^9.0.1", @@ -80,7 +80,7 @@ "http-server": "^0.12.3", "keyboardevent-key-polyfill": "^1.1.0", "leaflet": "^1.7.1", - "leaflet-advanced-layer-system": "file:../leaflet-advanced-layer-system", + "leaflet-advanced-layer-system": "^2.0.0", "leaflet-draw": "^1.0.4", "leaflet.coordinates": "~0.1.5", "persistify": "^2.0.1", From 07bc892af37a0fe09e28cc660ab7bdb17517bfc6 Mon Sep 17 00:00:00 2001 From: matafokka Date: Wed, 17 Nov 2021 18:03:22 +0300 Subject: [PATCH 2/7] Did stuff... 1. Refactored layers: moved common logic between Grid Layer and upcoming layers to the base class. 1. Added base class for layers using Leaflet Draw. 1. Fixed polygon merging algorithm for Grid Layer. 1. Implemented multiple paths connection algorithms. 1. Now path length and flight time are calculated for each separate path. 1. Probably did more stuff, don't really remember what exactly. 1. Exports temporarily don't work, trying to fix them --- MathTools.js | 63 +- SynthBase/Hull.js | 401 ++ SynthBase/SynthBaseDrawLayer.js | 40 +- SynthBase/SynthBaseLayer.js | 340 +- SynthBase/SynthBaseSettings.js | 14 + SynthBase/calculateParameters.js | 53 + SynthBase/toGeoJSON.js | 124 + SynthGridLayer/SynthGridLayer.js | 98 +- SynthGridLayer/SynthGridSettings.js | 22 +- SynthGridLayer/calculateParameters.js | 2 - SynthGridLayer/drawPaths.js | 205 +- SynthGridLayer/misc.js | 252 +- SynthGridLayer/polygons.js | 29 +- SynthGridLayer/serialization.js | 2 +- SynthGridLayer/toGeoJSON.js | 42 +- SynthLineLayer/SynthLineLayer.js | 297 +- SynthLineLayer/SynthLineSettings.js | 8 +- build.js | 1 + locales/English.js | 20 +- locales/Russian.js | 20 +- main.js | 16 +- package-lock.json | 5171 ++++++++++++++++++++++++- package.json | 6 +- 23 files changed, 6416 insertions(+), 810 deletions(-) create mode 100644 SynthBase/Hull.js create mode 100644 SynthBase/SynthBaseSettings.js create mode 100644 SynthBase/calculateParameters.js create mode 100644 SynthBase/toGeoJSON.js diff --git a/MathTools.js b/MathTools.js index f6d26b37..6858aaf6 100644 --- a/MathTools.js +++ b/MathTools.js @@ -53,14 +53,15 @@ class MathTools { * @return {{intercept: number, slope: number}|undefined} Object containing slope and intercept or undefined, if it can't be found (when dx = 0); */ static getSlopeAndIntercept(line) { - let p1 = line[0], p2 = line[1]; - let p1x = p1[0], p1y = p1[1], p2x = p2[0], p2y = p2[1]; - let deltaX = p2x - p1x; + let [p1, p2] = line, [p1x, p1y] = p1, [p2x, p2y] = p2, + deltaX = p2x - p1x; + if (this.isEqual(deltaX, 0)) return undefined; - let m = (p2y - p1y) / deltaX; // Slope - let b = p1y - m * p1x; // Intercept + let m = (p2y - p1y) / deltaX, // Slope + b = p1y - m * p1x; // Intercept + return { slope: m, intercept: b @@ -95,15 +96,50 @@ class MathTools { * Determines whether given point lies in polygon including its edges. * @param point {number[]} Point in format [lng, lat] * @param polygon {number[][]} Polygon in format [[lng, lat], [lng, lat], ...] - * @return {boolean} True, if point lies in polygon or on one of its edges. Returns false otherwise. + * @return {boolean} True, if point lies in polygon or on one of its edges. */ static isPointInPolygon(point, polygon) { - let intersections = 0; - for (let edge of polygon) { - let ray = [point, [point[0], 1]]; - if (MathTools.linesIntersection(edge, ray)) + let intersections = 0, ray = [point, [Infinity, point[1]]]; + for (let i = 0; i < polygon.length - 1; i++) { + let edge = [polygon[i], polygon[i + 1]], isPointOnEdge = MathTools.isPointOnLine(point, edge); + + if (isPointOnEdge) + return true; + + let intersection = MathTools.linesIntersection(edge, ray); + if (!intersection) + continue; + + // There're two special cases: when ray lies on the edge and when intersection is at the point of the edge. + // In second case, we check if other point of the edge lies above (then we skip it) + // or below (then we count it) the ray. If point lies on the ray, we come to the first case that is solved + // by skipping the edge. + + // Source: http://alienryderflex.com/polygon/ + + if (intersection.length === 2) + continue; + + let intersectionPoint = intersection[0], notOnVertex = true; + for (let p of edge) { + if (!MathTools.arePointsEqual(p, intersectionPoint)) + continue; + + notOnVertex = false; + let otherP = p === edge[0] ? edge[1] : edge[0]; + if (otherP[1] < intersectionPoint[1]) + intersections++; + } + + if (notOnVertex) intersections++; + + if (MathTools.arePointsEqual(point, [-0.25, 51.5])) { + console.log(intersection) + map.addLayer(L.marker([intersection[0][1], intersection[0][0]])) + } } + //console.log(point, intersections); return (intersections % 2 !== 0); } @@ -282,7 +318,12 @@ class MathTools { } static arePointsEqual(p1, p2) { - return this.isEqual(p1[0], p2[0]) && this.isEqual(p1[1], p2[1]); + let x = 0, y = 1; + if (p1.lat !== undefined) { + x = "lng"; + y = "lat"; + } + return this.isEqual(p1[x], p2[x]) && this.isEqual(p1[y], p2[y]); } } diff --git a/SynthBase/Hull.js b/SynthBase/Hull.js new file mode 100644 index 00000000..88880868 --- /dev/null +++ b/SynthBase/Hull.js @@ -0,0 +1,401 @@ +const MathTools = require("../MathTools.js"); + +// Actual paths are fixed and thus can't be optimized, so we should care only about their endpoints. +// We'll call them paths for the rest of this program. We have to connect line segments in shortest way. + +// This problem is similar to TSP and VRP. VRP is subset of TSP, much harder to solve and might not even work. +// Trust me, I tried Clarke-Wright algorithm and it didn't work (looks like it leaves points for other routes). +// So we'll go with TSP. Convex hull algorithm fits well to our problem and is easy to modify for our needs. + +// Hull will miss some of the endpoints, so for now the only modifications are connecting the missing point +// and adjusting connections between paths to get the shortest hull. I'll explain how we'll do it later. + +// Sources: +// TSP algorithms overview: http://160592857366.free.fr/joe/ebooks/ShareData/Heuristics%20for%20the%20Traveling%20Salesman%20Problem%20By%20Christian%20Nillson.pdf +// Monotone chain implementation: https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#JavaScript + +/** + * Connects paths using Convex Hull method, i.e. merges all paths into one + * @param path Path to connect + * @param color {string} Line color + */ +L.ALS.SynthBaseLayer.prototype.buildHull = function (path, color) { + const {pathGroup, connectionsGroup, hullConnection} = path, + points = [], layers = pathGroup.getLayers(), + lineOptions = this.getConnectionLineOptions(color); + + connectionsGroup.clearLayers(); + + if (layers.length === 0) + return; + + if (layers.length === 1) { + const path = layers[0].getLatLngs(); + connectionsGroup.addLayer(L.polyline( + [path[0], path[path.length - 1]], + lineOptions + )); + connectionsGroup.addLayer(hullConnection); + return; + } + + // Get points from each path and mark them as belonged to a current path + let paths = {}, pathsCount = layers.length; // Keep paths for merging + for (let path of layers) { + const layer = path.getLatLngs(), endPoints = [layer[0], layer[layer.length - 1]]; + + endPoints.connectionId = L.ALS.Helpers.generateID(); + paths[endPoints.connectionId] = endPoints; + + for (let p of endPoints) { + p.path = endPoints; + p.actualLayer = layer; + points.push(p); + } + } + + let {lower, upper} = this.getConvexHull(points); + + // Normalize path-connection order by appending other point of the first path, if it's not connected already. + let [p1, p2] = lower; + if (!p2) + p2 = upper[0]; + if (p1.path !== p2.path) { + for (let p of p1.path) { + if (p !== p1) { + p2 = p; + break; + } + } + lower = [p2, ...lower]; + } + + // Integrate missing end points of the paths into the hull and leave only connections. + // This points won't produce optimal path, but we'll fix it later. + let connections = [], passedPoints = {}, isFirstPoint = true; + for (let hull of [lower, upper]) { + for (let point of hull) { + if (isFirstPoint) { + isFirstPoint = false; + point.id = L.ALS.Helpers.generateID(); + connections.push(point); + passedPoints[point.id] = true; + continue; + } + + for (let p of point.path) { + if (!p.id) + p.id = L.ALS.Helpers.generateID(); + + if (passedPoints[p.id]) + continue; + + passedPoints[p.id] = true; + connections.push(p); + + // Remove point's path from the list because it'll be already connected + if (paths[p.path.connectionId]) { + delete paths[p.path.connectionId]; + pathsCount--; + } + } + } + } + + connections.push(connections[0]); // Connect endpoints of the hull + + // Optimize hull by rotating each path by 360 and checking if connections length has shortened + // Paths are always even, if counting from 0. + let optConnections = []; // We'll keep only optimal connections here and skip paths + for (let i = 2; i < connections.length - 1; i += 2) { + let pathP1 = connections[i], pathP2 = connections[i + 1], + prevPair = optConnections.pop(), // This won't exist for the first path + prevPoint = prevPair ? prevPair[0] : connections[i - 1], nextPoint = connections[i + 2]; + + optConnections.push(...this.getHullOptimalConnection(pathP1, pathP2, prevPoint, nextPoint)); + } + + // We haven't rotated the first path, so do it + let [toLast, toFirst] = this.getHullOptimalConnection(p1, p2, optConnections.pop()[0], optConnections[0][1]); + optConnections[0][0] = toFirst[0]; + optConnections.push(toLast); + + // Second path was built using unoptimized first line, so we have to rotate second line again. + let toSecond = optConnections[0], fromSecond = optConnections[1]; + let [optToSecond, optFromSecond] = this.getHullOptimalConnection(toSecond[1], fromSecond[0], toSecond[0], fromSecond[1]); + optConnections[0] = optToSecond; + optConnections[1] = optFromSecond; + + // Merge other paths with the hull by finding cheapest insertion and using it until no paths left + while (pathsCount > 0) { + let minLen = Infinity, insertion, insertAt, pathId; + for (let id in paths) { + if (!paths.hasOwnProperty(id)) + continue; + + let [p1, p2] = paths[id], pairs = [ + [p1, p2], + [p2, p1] + ]; + + for (let i = 0; i < optConnections.length; i++) { + let [conP1, conP2] = optConnections[i]; + + for (let pair of pairs) { + let [p1, p2] = pair, line1 = [p1, conP1], line2 = [p2, conP2], + len = this.getLineLength(line1) + this.getLineLength(line2); + + if (len < minLen) { + minLen = len; + insertion = [line1, line2]; + insertAt = i; + pathId = id; + } + } + } + } + optConnections = optConnections.slice(0, insertAt).concat(insertion, optConnections.slice(insertAt + 1, optConnections.length)); + delete paths[pathId]; + pathsCount--; + } + + for (let pair of optConnections) + connectionsGroup.addLayer(L.polyline(pair, lineOptions)); + connectionsGroup.addLayer(hullConnection); + path.hullConnections = optConnections; +} + +/** + * Builds a convex hull around array of points + * @param points {L.LatLng[]} Array of points + * @return {{upper: L.LatLng[], lower: L.LatLng[]}} Convex hull of given points + */ +L.ALS.SynthBaseLayer.prototype.getConvexHull = function (points) { + // Find convex hull of the points using monotone chain. + points.sort((a, b) => { + return a.lng === b.lng ? a.lat - b.lat : a.lng - b.lng; + }); + + let lower = []; + for (let point of points) { + while (lower.length >= 2 && this.cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0) + lower.pop(); + lower.push(point); + } + + let upper = []; + for (let i = points.length - 1; i >= 0; i--) { + const point = points[i]; + while (upper.length >= 2 && this.cross(upper[upper.length - 2], upper[upper.length - 1], point) <= 0) + upper.pop(); + upper.push(point); + } + + lower.pop(); + upper.pop(); + return {upper, lower} +} + +/** + * Rotates a path in hull and returns a pair of optimal connections + * @param pathP1 {L.LatLng|number[]} First point of path + * @param pathP2 {L.LatLng|number[]} Second point of path + * @param prevPoint {L.LatLng|number[]} Previous point + * @param nextPoint {L.LatLng|number[]} Next point + * @return A pair of optimal connections + */ +L.ALS.SynthBaseLayer.prototype.getHullOptimalConnection = function (pathP1, pathP2, prevPoint, nextPoint) { + let pairs = [ + [pathP1, pathP2], + [pathP2, pathP1], + ], + minLen = Infinity, optPair; + + for (let pair of pairs) { + let [p1, p2] = pair, + len = this.getLineLength([prevPoint, p1]) + this.getLineLength([nextPoint, p2]); + + if (len < minLen) { + minLen = len; + optPair = pair; + } + } + return [[prevPoint, optPair[0]], [optPair[1], nextPoint]]; +} + +L.ALS.SynthBaseLayer.prototype.connectHullToAirport = function () { + let airportPos = this._airportMarker.getLatLng(); + + for (let i = 0; i < this.paths.length; i++) { + + let path = this.paths[i], + {connectionsGroup, hullConnection, previouslyRemovedConnection} = path, + layers = connectionsGroup.getLayers(), minLen = Infinity, toRemove, + totalLength = 0; + + for (let layer of layers) { + if (layer === hullConnection) + continue; + if (layer.pathLength === undefined) + layer.pathLength = this.lineLengthUsingFlightHeight(layer); + totalLength += layer.pathLength; + } + + for (let layer of layers) { + if (layer === hullConnection) + continue; + + let [p1, p2] = layer.getLatLngs(), + len = totalLength - layer.pathLength + this.lineLengthUsingFlightHeight([p1, airportPos]) + this.lineLengthUsingFlightHeight([p2, airportPos]); + + if (len < minLen) { + minLen = len; + toRemove = layer; + } + + } + + if (!toRemove) + return; + + if (toRemove !== previouslyRemovedConnection) { + if (previouslyRemovedConnection) + previouslyRemovedConnection.setStyle({opacity: 1}); + + path.previouslyRemovedConnection = toRemove; + toRemove.setStyle({opacity: 0}); + } + + let [p1, p2] = toRemove.getLatLngs(); + hullConnection.setLatLngs([p1, airportPos, p2]); + + // Update widgets + path.pathWidget.getWidgetById("pathLength").setValue(minLen); + path.pathWidget.getWidgetById("flightTime").setValue(this.getFlightTime(minLen)); + } +} + +/** + * Called when paths should be connected using hull algorithm. You should call {@link L.ALS.SynthBaseLayer#buildHull} here. + */ +L.ALS.SynthBaseLayer.prototype.connectHull = function () { + for (let i = 0; i < this.paths.length; i++) { + let path = this.paths[i]; + path.pathWidget = this._createPathWidget(1, path.toUpdateColors); + this.buildHull(path, this.getWidgetById(`color${i}`).getValue()); + } + this.connectHullToAirport(); +} + +/** + * Creates a cycle out of hull connection of given path + * @param path {Object} Path to create cycle of + * @return {LatLng[]} Cycle + */ +L.ALS.SynthBaseLayer.prototype.hullToCycles = function (path) { + // The idea is to start with the first connection, find the starting point in it and for each connection + // find the next point of a current connection and add points of its paths. We can do this because connections + // are ordered. We'll also compare instances of the points because they're copied from the path, and it'll + // prevent us from dealing with situation when two points are the same. + + let cycle = []; + + // Find a common path between first and last connection + let firstConnection = path.hullConnections[0], lastConnection = path.hullConnections[path.hullConnections.length - 1], + commonPath, prevPoint; + for (let p1 of firstConnection) { + for (let p2 of lastConnection) { + if (p1.path === p2.path) { + commonPath = p1.path; + break; + } + } + } + + // Find a common point of path and first connection and select it as a starting point + for (let p1 of commonPath) { + for (let p2 of firstConnection) { + if (p1 === p2) { + prevPoint = p1; + break; + } + } + } + + // Now we can add paths of the next point of each connection + for (let i = 0; i < path.hullConnections.length; i++) + prevPoint = this.getOrderedPathFromHull(prevPoint, path.hullConnections[i], cycle, i); + + cycle.push(cycle[0]); // Close the cycle + + // Now we need to add an airport and shift the points, so the first point will be an airport + let [hullP1, hullP2, hullP3] = path.hullConnection.getLatLngs(), connectionPairs = [[hullP1, hullP3], [hullP3, hullP1]], + beforeAirport = [], afterAirport = [], connectionFound = false; + + for (let i = 0; i < cycle.length - 1; i += 2) { + let p1 = cycle[i], p2 = cycle[i + 1]; + + if (connectionFound) { + afterAirport.push(p1, p2); + continue; + } + + for (let pair of connectionPairs) { + if (pair[0] === p1 && pair[1] === p2) { + connectionFound = true; + break; + } + } + + if (!connectionFound) + beforeAirport.push(p1, p2); + else { + beforeAirport.push(p1); + afterAirport.push(p2); + } + } + + cycle = [hullP2, ...afterAirport, ...beforeAirport, hullP2]; + + this.map.addLayer(L.polyline(cycle, {weight: 10, opacity: 0.5})); + return cycle; +} + +L.ALS.SynthBaseLayer.prototype.getOrderedPathFromHull = function (prevPoint, connection, copyTo, i = 0) { + + // Find the next point of given connection by checking previous point + let [connP1, connP2] = connection, nextPoint = connP1 === prevPoint ? connP2 : connP1, + start = -1, end = nextPoint.actualLayer.length, addition = 1; + + // If the next point is at the end of the path, i.e. points order is reversed, we have to start from the end + if (nextPoint !== nextPoint.actualLayer[0]) { + start = end; + end = -1; + addition = -1; + } + + // Copy the points to the cycle + for (let i = start + addition; i !== end; i += addition) + copyTo.push(nextPoint.actualLayer[i]); + + return copyTo[copyTo.length - 1]; // Return the last added point which will become a previous point for the next iteration +} + +/** + * Calculates length of a polyline + * @param line {L.Polyline || L.LatLng[]} Line to calculate length of + * @return {number} Line length + */ +L.ALS.SynthBaseLayer.prototype.getLineLength = function (line) { + const latLngs = line instanceof Array ? line : line.getLatLngs(); + let length = 0; + for (let i = 0; i < latLngs.length - 1; i++) { + const p1 = latLngs[i], p2 = latLngs[i + 1]; + length += Math.sqrt((p1.lng - p2.lng) ** 2 + (p1.lat - p2.lat) ** 2); + } + return length; +} + +L.ALS.SynthBaseLayer.prototype.cross = function (a, b, o) { + return (a.lng - o.lng) * (b.lat - o.lat) - (a.lat - o.lat) * (b.lng - o.lng); +} \ No newline at end of file diff --git a/SynthBase/SynthBaseDrawLayer.js b/SynthBase/SynthBaseDrawLayer.js index 2b9dfdc7..68cf9a56 100644 --- a/SynthBase/SynthBaseDrawLayer.js +++ b/SynthBase/SynthBaseDrawLayer.js @@ -1,26 +1,28 @@ +/** + * Base class for L.Draw layers + * @param settings {SettingsObject} Settings object + * @param colorLabel {string} Color widget label for paths group + */ L.ALS.SynthBaseDrawLayer = L.ALS.SynthBaseLayer.extend({ defaultName: "Draw Layer", - init: function (wizardResults, settings) { + init: function (settings, colorLabel) { /** * Leaflet.Draw controls to use */ this.drawControls = this.drawControls || {} - this.addBaseParametersInputSection(); - this.addBaseParametersOutputSection(); - /** * Leaflet.Draw group * @type {L.FeatureGroup} */ this.drawingGroup = L.featureGroup(); - this.addLayers(this.drawingGroup); + this.connectionsGroup = L.featureGroup(); - let options = { + this._drawOptions = { draw: {}, edit: { featureGroup: this.drawingGroup, @@ -31,27 +33,33 @@ L.ALS.SynthBaseDrawLayer = L.ALS.SynthBaseLayer.extend({ let toDisable = ["circle", "circlemarker", "marker", "polygon", "polyline", "rectangle", "simpleshape"]; for (let control of toDisable) - options.draw[control] = false; + this._drawOptions.draw[control] = false; + for (let control in this.drawControls) - options.draw[control] = this.drawControls[control]; + this._drawOptions.draw[control] = this.drawControls[control]; - this.control = new L.Control.Draw(options); + this.control = new L.Control.Draw(this._drawOptions); - L.ALS.SynthBaseLayer.prototype.init.call(this, wizardResults, settings); + L.ALS.SynthBaseLayer.prototype.init.call(this, settings, this.drawingGroup, this.connectionsGroup, colorLabel); + this.updateDrawThickness(); this.addEventListenerTo(this.map, "draw:created", "onDraw"); this.onNameChange(); + this.addControl(this.control, "top", "topleft"); }, - onSelect: function () { - this.map.addControl(this.control); + onDraw: function (e) { + this.drawingGroup.addLayer(e.layer); }, - onDeselect: function () { - this.map.removeControl(this.control); + setLineThickness: function (widget) { + L.ALS.SynthBaseLayer.prototype.setLineThickness.call(this, widget); + this.updateDrawThickness(); }, - onDraw: function (e) { - this.drawingGroup.addLayer(e.layer); + updateDrawThickness: function () { + try { + this._drawOptions.draw.polyline.shapeOptions.weight = this.lineThicknessValue; + } catch (e) {} } }) \ No newline at end of file diff --git a/SynthBase/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js index 16dd98de..3cff3676 100644 --- a/SynthBase/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -5,15 +5,87 @@ const turfHelpers = require("@turf/helpers"); * * Call {@link L.ALS.SynthBaseLayer#init} after you've created a menu! * + * @param settings {SettingsObject} Settings object + * @param pathGroup1 {L.FeatureGroup} First paths group (a group of polylines) + * @param connectionsGroup1 {L.FeatureGroup} First connections group + * @param colorLabel1 {string} Color widget label for paths group 1 + * @param path1AdditionalLayers {L.Layer[]} Additional layers related to path 1 that should have same color and thickness + * @param pathGroup2 {L.FeatureGroup} Second paths group (a group of polylines). If you have only one path, leave this one as undefined. + * @param connectionsGroup2 {L.FeatureGroup} Second connections group. If you have only one path, leave this one as undefined. + * @param colorLabel2 {string} Color widget label for paths group 2. If you have only one path, leave this one as undefined. + * @param path2AdditionalLayers {L.Layer[]} Additional layers related to path 2 that should have same color and thickness + * * @class * @extends L.ALS.Layer */ -L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.prototype */{ +L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.prototype */ { + + /** + * Parameter to pass to dashArray option + * @type {string} + */ + dashedLine: "4 4", + + init: function (settings, pathGroup1, connectionsGroup1, colorLabel1, path1AdditionalLayers = [], pathGroup2 = undefined, connectionsGroup2 = undefined, colorLabel2 = undefined, path2AdditionalLayers = []) { + + this._settings = settings; + + /** + * Array of paths widgets to remove whenever paths are updated + * @type {L.ALS.Widgets.Spoiler[]} + */ + this._pathsWidgets = []; - hasYOverlay: true, + /** + * For numbering paths widgets + * @type {number} + * @private + */ + this._pathsWidgetsNumber = 1; - init: function () { - this.serializationIgnoreList.push("_airportMarker"); + this.path1 = { + pathGroup: pathGroup1, + connectionsGroup: connectionsGroup1, + colorLabel: colorLabel1, + toUpdateColors: [pathGroup1, connectionsGroup1, ...path1AdditionalLayers] + } + + this.path2 = pathGroup2 ? { + pathGroup: pathGroup2, + connectionsGroup: connectionsGroup2, + colorLabel: colorLabel2, + toUpdateColors: [pathGroup2, connectionsGroup2, ...path2AdditionalLayers] + } : undefined; + + this.hasYOverlay = !!this.path2; + + this.paths = [this.path1]; + if (this.hasYOverlay) + this.paths.push(this.path2); + + /** + * Current line thickness value + * @type {number} + */ + this.lineThicknessValue = settings.lineThicknessValue; + + /** + * Groups to update line thickness of. To set double thickness, create thicknessMultiplier property in layer to control how thick should be line relative to thickness set by user. + * + * Already contains paths and connections groups + * + * @type {L.FeatureGroup[]} + */ + this.toUpdateThickness = [...path1AdditionalLayers, ...path2AdditionalLayers]; + + for (let i = 0; i < this.paths.length; i++) { + let path = this.paths[i]; + path.hullConnection = L.polyline([[0, 0], [0, 0]], this.getConnectionLineOptions(settings[`color${i}`])); + this.addLayers(path.pathGroup, path.connectionsGroup); + this.toUpdateThickness.push(path.pathGroup, path.connectionsGroup); + } + + this.serializationIgnoreList.push("_airportMarker", "toUpdateThickness"); // Add airport let icon = L.divIcon({ @@ -30,13 +102,24 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot // Set inputs' values to new ones on drag this.addEventListenerTo(this._airportMarker, "drag", "onMarkerDrag"); this.addLayers(this._airportMarker); - this._airportMarker.fire("drag"); // Just to set values }, addBaseParametersInputSection: function () { + this.addWidget( + new L.ALS.Widgets.Number("lineThickness", "lineThickness", this, "setLineThickness").setMin(1).setMax(20).setValue(this._settings.lineThicknessValue), + ); + + for (let i = 0; i < this.paths.length; i++) { + this.addWidget( + new L.ALS.Widgets.Color(`color${i}`, this.paths[i].colorLabel, this, "_setPathsColor").setValue(this._settings[`color${i}`]), + ); + } + this.addWidgets( new L.ALS.Widgets.Divider("div1"), + new L.ALS.Widgets.DropDownList("connectionMethod", "connectionMethod", this, "updatePathsMeta").addItems("allIntoOne", "oneFlightPerPath"), + new L.ALS.Widgets.Number("airportLat", "airportLat", this, "setAirportLatLng").setMin(-90).setMax(90).setStep(0.01), new L.ALS.Widgets.Number("airportLng", "airportLng", this, "setAirportLatLng").setMin(-180).setMax(180).setStep(0.01), new L.ALS.Widgets.Number("aircraftSpeed", "aircraftSpeed", this, "calculateParameters").setMin(1).setStep(1).setValue(350), @@ -59,6 +142,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot new L.ALS.Widgets.Divider("div2"), ); + this._airportMarker.fire("drag"); // Just to set values }, addBaseParametersOutputSection: function () { @@ -90,53 +174,30 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot } }, - calculateParameters: function () { - let parameters = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "imageScale", "overlayBetweenPaths", "overlayBetweenImages", "aircraftSpeed"]; - for (let param of parameters) - this[param] = this.getWidgetById(param)?.getValue(); - this.flightHeight = this["imageScale"] * this["focalLength"]; - - let cameraParametersWarning = this.getWidgetById("cameraParametersWarning"); - if (this["cameraHeight"] > this["cameraWidth"]) - cameraParametersWarning.setValue("errorCamHeight"); - else - cameraParametersWarning.setValue(""); - - let pixelWidth = this["pixelWidth"] * 1e-6; - let focalLength = this["focalLength"] * 0.001; + /** + * Sets line thickness of whatever's in {@link L.ALS.SynthBaseLayer#toUpdateThickness} + * @param widget {L.ALS.Widgets.Number} Widget that controls line thickness + */ + setLineThickness: function (widget) { + this.lineThicknessValue = widget.getValue(); + let doubleThickness = this.lineThicknessValue * 2; - if (this.hasYOverlay) { - this.ly = this["cameraWidth"] * pixelWidth; // Image size in meters - this.Ly = this.ly * this["imageScale"] // Image width on the ground - this.By = this.Ly * (100 - this["overlayBetweenPaths"]) / 100; // Distance between paths + for (let group of this.toUpdateThickness) { + group.eachLayer((layer) => { + if (layer instanceof L.Polyline || layer instanceof L.Polygon) + layer.setStyle({weight: (layer.thicknessMultiplier || group.thicknessMultiplier || 1) * this.lineThicknessValue}); + else if (layer instanceof L.CircleMarker) + layer.setRadius(doubleThickness); + }) } + }, - this.lx = this["cameraHeight"] * pixelWidth; // Image height - this.Lx = this.lx * this["imageScale"]; // Image height on the ground - this.Bx = this.Lx * (100 - this["overlayBetweenImages"]) / 100; // Capture basis, distance between images' centers - this.basis = turfHelpers.lengthToDegrees(this.Bx, "meters"); - - this.GSI = pixelWidth * this["imageScale"]; - this.IFOV = pixelWidth / focalLength * 1e6; - this.GIFOV = this.GSI; - this.FOV = this["cameraWidth"] * this.IFOV; - this.GFOV = this["cameraWidth"] * this.GSI; - - this.aircraftSpeedInMetersPerSecond = this["aircraftSpeed"] * 1 / 36; - - let names = ["flightHeight", "lx", "Lx", "Bx", "ly", "Ly", "By", "GSI", "IFOV", "GIFOV", "FOV", "GFOV",]; - for (let name of names) { - const field = this[name]; - if (field === undefined) - continue; - - let value; - try { - value = this.toFixed(field); - } catch (e) { - value = field; - } - this.getWidgetById(name).setValue(value); + _setPathsColor: function () { + for (let i = 0; i < this.paths.length; i++) { + let style = {color: this.getWidgetById(`color${i}`).getValue()}, + path = this.paths[i]; + for (let group of path.toUpdateColors) + group.setStyle(style); } }, @@ -145,12 +206,14 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot this.getWidgetById("airportLat").getValue(), this.getWidgetById("airportLng").getValue() ]); + this.connectToAirport(); }, onMarkerDrag: function () { let latLng = this._airportMarker.getLatLng(); this.getWidgetById("airportLat").setValue(latLng.lat.toFixed(5)); this.getWidgetById("airportLng").setValue(latLng.lng.toFixed(5)); + this.connectToAirport(); }, onNameChange: function () { @@ -158,6 +221,181 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot L.ALS.Locales.localizeElement(popup, "airportForLayer", "innerText"); popup.innerText += " " + this.getName(); this._airportMarker.bindPopup(popup); - } + }, + + connectToAirport: function () { + const value = this.getWidgetById("connectionMethod").getValue(); + if (value === "oneFlightPerPath") + this.connectOnePerFlightToAirport(); + else // allIntoOne + this.connectHullToAirport(); + }, + + updatePathsMeta: function () { + // Clear widgets + for (let widget of this._pathsWidgets) + this.removeWidget(widget.id); + + this._pathsWidgets = []; + this._pathsWidgetsNumber = 1; + + // Connect paths. Paths will be connected to the airport, their widgets will be added, and their parameters will be calculated at each of methods below. + const value = this.getWidgetById("connectionMethod").getValue(); + if (value === "oneFlightPerPath") + this.connectOnePerFlight(); + else// allIntoOne + this.connectHull(); + }, + + _createPathWidget: function (length, toFlash) { + let button = new L.ALS.Widgets.Button("flashPath", "flashPath", this, "flashPath"); + button.toFlash = toFlash; + + let widget = new L.ALS.Widgets.Spoiler(`pathWidget${this._pathsWidgetsNumber}`, `${L.ALS.locale.pathSpoiler} ${this._pathsWidgetsNumber}`) + .addWidgets( + button, + new L.ALS.Widgets.ValueLabel("pathLength", "pathLength", "m").setFormatNumbers(true).setNumberOfDigitsAfterPoint(0).setValue(length), + new L.ALS.Widgets.ValueLabel("flightTime", "flightTime", "h").setFormatNumbers(true).setValue(this.getFlightTime(length)), + ); + + this.addWidgets(widget); + this._pathsWidgetsNumber++; + this._pathsWidgets.push(widget); + return widget; + }, + + getFlightTime: function (length) { + return parseFloat((length / this.aircraftSpeedInMetersPerSecond / 3600).toFixed(2)); + }, + + /** + * Calculates line length using haversine formula with account of flight height + * @param line {L.Polyline|number[][]|LatLng[]} Line + * @return {number} Line length + */ + lineLengthUsingFlightHeight: function (line) { + let r = 6371000 + this.flightHeight, points = line instanceof Array ? line : line.getLatLngs(), distance = 0, x, y; + if (points.length === 0) + return 0; + + if (points[0].lat === undefined) { + x = "0"; + y = "1"; + } else { + x = "lng"; + y = "lat"; + } + + for (let i = 0; i < points.length - 1; i++) { + let p1 = points[i], p2 = points[i + 1], + f1 = turfHelpers.degreesToRadians(p1[y]), f2 = turfHelpers.degreesToRadians(p2[y]), + df = f2 - f1, + dl = turfHelpers.degreesToRadians(p2[x] - p1[x]), + a = Math.sin(df / 2) ** 2 + Math.cos(f1) * Math.cos(f2) * Math.sin(dl / 2) ** 2; + distance += r * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + } + return distance; + }, + + /** + * Called when there's one flight per each path. You should call {@link L.ALS.SynthBaseLayer#connectOnePerFlight} here. + */ + connectOnePerFlightToAirport: function () { + let airportPos = this._airportMarker.getLatLng(); + + for (let path of this.paths) { + let layers = path.connectionsGroup.getLayers(); + for (let layer of layers) { + layer.getLatLngs()[1] = airportPos; + layer.redraw(); + + let length = layer.pathLength + this.lineLengthUsingFlightHeight(layer); + layer.pathWidget.getWidgetById("pathLength").setValue(length); + layer.pathWidget.getWidgetById("flightTime").setValue(this.getFlightTime(length)); + } + } + + }, + + // This may be reused to connect VRP (if we'll add it), but we should replace paths with connections endpoints + + /** + * Builds connections per flight + */ + connectOnePerFlight: function () { + for (let i = 0; i < this.paths.length; i++) { + + let path = this.paths[i], {connectionsGroup, pathGroup} = path, layers = pathGroup.getLayers(), + lineOptions = this.getConnectionLineOptions(this.getWidgetById(`color${i}`).getValue()); + + connectionsGroup.clearLayers(); + + for (let layer of layers) { + layer.pathLength = this.lineLengthUsingFlightHeight(layer); + + let latLngs = layer.getLatLngs(), + connectionLine = L.polyline([latLngs[0], [0, 0], latLngs[latLngs.length - 1]], lineOptions); + connectionLine.pathLength = layer.pathLength; + let toFlash = [layer, connectionLine]; + if (layer.actualPaths) + toFlash.push(...layer.actualPaths) + + connectionLine.pathWidget = this._createPathWidget(1, toFlash); + connectionsGroup.addLayer(connectionLine); + } + } + this.connectOnePerFlightToAirport(); // So we'll end up with only one place that updates widgets + }, + + /** + * Creates an array of cycles of paths connected to the airport + * @param path {Object} Path to get cycles of + * @return {LatLng[][]} Cycles + */ + onePerFlightToCycles: function (path) { + let layers = path.pathGroup.getLayers(), cycles = [], airportPos = this._airportMarker.getLatLng(); + for (let layer of layers) + cycles.push([airportPos, ...layer.getLatLngs()], airportPos); + return cycles; + }, + + /** + * Returns connection line options + * @param color {string} Line color + * @return {Object} Line options + */ + getConnectionLineOptions: function (color) { + return { + color, + dashArray: this.dashedLine, + weight: this.lineThicknessValue + } + }, + + flashPath: function (widget) { + for (let group of widget.toFlash) { + let layers = group instanceof L.FeatureGroup ? group.getLayers() : [group]; + for (let layer of layers) + this.flashLine(layer); + } + }, + + flashLine: async function (line) { + if (line.isFlashing) + return; + + let color = line.options.color; + line.isFlashing = true; + for (let i = 0; i < 5; i++) { + line.setStyle({color: line.options.color === "white" ? "black" : "white"}); + await new Promise(resolve => setTimeout(resolve, 250)); + } + line.setStyle({color}); + line.isFlashing = false; + }, + +}); -}) \ No newline at end of file +require("./Hull.js"); +require("./calculateParameters.js"); +require("./toGeoJSON.js"); \ No newline at end of file diff --git a/SynthBase/SynthBaseSettings.js b/SynthBase/SynthBaseSettings.js new file mode 100644 index 00000000..5709b84f --- /dev/null +++ b/SynthBase/SynthBaseSettings.js @@ -0,0 +1,14 @@ +L.ALS.SynthBaseSettings = L.ALS.Settings.extend({ + + lineThickness: 2, + + initialize: function () { + L.ALS.Settings.prototype.initialize.call(this); + + this.addWidget( + new L.ALS.Widgets.Number("lineThicknessValue", "defaultLineThickness").setMin(1).setMax(20).setValue(this.lineThickness), + this.lineThickness + ); + } + +}) \ No newline at end of file diff --git a/SynthBase/calculateParameters.js b/SynthBase/calculateParameters.js new file mode 100644 index 00000000..c441d2f1 --- /dev/null +++ b/SynthBase/calculateParameters.js @@ -0,0 +1,53 @@ +const turfHelpers = require("@turf/helpers"); + +L.ALS.SynthBaseLayer.prototype.calculateParameters = function () { + + let parameters = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "imageScale", "overlayBetweenPaths", "overlayBetweenImages", "aircraftSpeed"]; + for (let param of parameters) + this[param] = this.getWidgetById(param)?.getValue(); + this.flightHeight = this["imageScale"] * this["focalLength"]; + + let cameraParametersWarning = this.getWidgetById("cameraParametersWarning"); + if (this["cameraHeight"] > this["cameraWidth"]) + cameraParametersWarning.setValue("errorCamHeight"); + else + cameraParametersWarning.setValue(""); + + let pixelWidth = this["pixelWidth"] * 1e-6; + let focalLength = this["focalLength"] * 0.001; + + if (this.hasYOverlay) { + this.ly = this["cameraWidth"] * pixelWidth; // Image size in meters + this.Ly = this.ly * this["imageScale"] // Image width on the ground + this.By = this.Ly * (100 - this["overlayBetweenPaths"]) / 100; // Distance between paths + } + + this.lx = this["cameraHeight"] * pixelWidth; // Image height + this.Lx = this.lx * this["imageScale"]; // Image height on the ground + this.Bx = this.Lx * (100 - this["overlayBetweenImages"]) / 100; // Capture basis, distance between images' centers + this.basis = turfHelpers.lengthToDegrees(this.Bx, "meters"); + + this.GSI = pixelWidth * this["imageScale"]; + this.IFOV = pixelWidth / focalLength * 1e6; + this.GIFOV = this.GSI; + this.FOV = this["cameraWidth"] * this.IFOV; + this.GFOV = this["cameraWidth"] * this.GSI; + + this.aircraftSpeedInMetersPerSecond = this["aircraftSpeed"] * 1 / 36; + + let names = ["flightHeight", "lx", "Lx", "Bx", "ly", "Ly", "By", "GSI", "IFOV", "GIFOV", "FOV", "GFOV",]; + for (let name of names) { + const field = this[name]; + if (field === undefined) + continue; + + let value; + try { + value = this.toFixed(field); + } catch (e) { + value = field; + } + this.getWidgetById(name).setValue(value); + } + +} \ No newline at end of file diff --git a/SynthBase/toGeoJSON.js b/SynthBase/toGeoJSON.js new file mode 100644 index 00000000..e6751113 --- /dev/null +++ b/SynthBase/toGeoJSON.js @@ -0,0 +1,124 @@ +const MathTools = require("../MathTools.js"); +const turfHelpers = require("@turf/helpers"); +const geojsonMerge = require("@mapbox/geojson-merge"); // Using this since turfHelpers.featureCollection() discards previously defined properties. + +L.ALS.SynthBaseLayer.prototype.toGeoJSON = function (path1Metadata, path2Metadata) { + // We need to merge paths and connections into one line. The task is to find cycles. + + /*let pushLatLngs = (latLngs, pushTo, isPath = false) => { + let toPush = []; + for (let p of latLngs) + toPush.push([p.lng, p.lat]); + toPush.isPath = isPath; + pushTo.push(toPush); + } + + let airportPos = this._airportMarker.getLatLng(), airportPosArr = [airportPos.lng, airportPos.lat], cycles = [], pathNumber = 1; + for (let path of this.paths) { + let groups = [], connectionLayers = path.connectionsGroup.getLayers(), pathLayers = path.pathGroup.getLayers(); + + // Copy connections. Connection to the airport contains 3 points which needs workaround. Also convert LatLng to array of lng-lat. + for (let layer of connectionLayers) { + if (layer === path.previouslyRemovedConnection) + continue; + + let latLngs = layer.getLatLngs(); + if (latLngs.length === 2) { + pushLatLngs(latLngs, groups); + continue; + } + pushLatLngs([latLngs[0], latLngs[1]], groups); + pushLatLngs([latLngs[1], latLngs[2]], groups); + } + + // Do the same for the paths + for (let layer of pathLayers) + pushLatLngs(layer.getLatLngs(), groups, true); + + let groupsLength = groups.length, layers = {}; + + for (let i = 0; i < groups.length; i++) + layers[i] = groups[i]; + + while (groupsLength > 0) { + let cycle = [], startPoint, prevPoint; + + // Find starting line + for (let i in layers) { + let [p1, p2] = layers[i]; // It'll always be a connection, so we can pick just two points + + if (MathTools.arePointsEqual(p1, airportPosArr)) { + startPoint = p1; + prevPoint = p2; + } + else if (MathTools.arePointsEqual(p2, airportPosArr)) { + startPoint = p2; + prevPoint = p1; + } else + continue; + + delete layers[i]; + groupsLength--; + cycle.push(startPoint, prevPoint); + break; + } + + // Keep finding and adding points to the cycle until we reach airport pos, i.e. until cycle closes + let isFinal = false, needPath = true; + while (!isFinal) { + for (let i in layers) { + let layer = layers[i], p1 = layer[0], p2 = layer[layer.length - 1], toAdd, + start = -1, end = layer.length, addition = 1; // To add points later + + if ((needPath && !layer.isPath) || (!needPath && layer.isPath)) + continue; + + if (MathTools.arePointsEqual(p1, prevPoint)) + toAdd = p2; + else if (MathTools.arePointsEqual(p2, prevPoint)) { + toAdd = p1; + start = end; + end = -1; + addition = -1; + } + else + continue; + + needPath = !needPath; + delete layers[i]; + cycle.pop(); + groupsLength--; + prevPoint = toAdd; + isFinal = MathTools.arePointsEqual(toAdd, airportPosArr); + + // Add other points from paths + for (let j = start + addition; j !== end; j += addition) + cycle.push(layer[j]); + + break; + } + } + let cycleJson = turfHelpers.lineString(cycle), metadata = pathNumber === 1 ? path1Metadata : path2Metadata; + for (let name in metadata) + cycleJson.properties[name] = metadata[name]; + + cycleJson.properties.length = this.lineLengthUsingFlightHeight(cycle); + cycleJson.properties.flightTime = this.getFlightTime(cycleJson.properties.length); + cycles.push(cycleJson); + } + pathNumber++; + }*/ + + this.hullToCycles(this.paths[1]); + + /*let isHull = this.getWidgetById("connectionMethod").getValue() === "allIntoOne"; + for (let path of this.paths) { + if (isHull) { + console.log(this.hullToCycles(path)); + } else { + + } + }*/ + + return geojsonMerge.merge(cycles); +} diff --git a/SynthGridLayer/SynthGridLayer.js b/SynthGridLayer/SynthGridLayer.js index 118ad1d0..71e50c68 100644 --- a/SynthGridLayer/SynthGridLayer.js +++ b/SynthGridLayer/SynthGridLayer.js @@ -28,11 +28,7 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa isDisplayed: true, _doHidePolygonWidgets: false, - _doHidePathsConnections: false, - _doHidePathsByMeridians: false, - _doHidePathsByParallels: false, _doHidePathsNumbers: false, - _areCapturePointsHidden: true, init: function (wizardResults, settings) { this.copySettingsToThis(settings); @@ -41,6 +37,53 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa this.selectedPolygonsWidgets = {}; this.serializationIgnoreList.push("selectedPolygons", "lngDistance", "latDistance", "_currentStandardScale"); + + // To optimize the grid and reduce visual clutter, let's: + // 1. Display only visible polygons. If we'll render the whole thing, user will need from couple of MBs to TBs of RAM. + // 2. Hide grid when it'll contain a lot of polygons and becomes messy + // Additional redrawing actually won't introduce any noticeable delay. + + // Create empty groups containing our stuff. Yeah, I hate copying too, but I want code completion :D + + this.polygonGroup = L.featureGroup(); + this.widgetsGroup = L.featureGroup(); + this.bordersGroup = L.featureGroup(); + this.bordersGroup.thicknessMultiplier = 4; + this.latPointsGroup = L.featureGroup(); + this.lngPointsGroup = L.featureGroup(); + this.labelsGroup = new L.LabelLayer(false); + + this.pathsByParallels = L.featureGroup(); + this.parallelsInternalConnections = L.featureGroup(); + this.parallelsExternalConnections = L.featureGroup(); + + this.pathsByMeridians = L.featureGroup(); + this.meridiansInternalConnections = L.featureGroup(); + this.meridiansExternalConnections = L.featureGroup(); + + this.addLayers(this.polygonGroup, this.widgetsGroup, this.bordersGroup, this.latPointsGroup, this.lngPointsGroup, this.labelsGroup, this.bordersGroup); + + L.ALS.SynthBaseLayer.prototype.init.call(this, settings, + this.parallelsInternalConnections, this.parallelsExternalConnections, "parallelsColor", [this.pathsByParallels], + this.meridiansInternalConnections, this.meridiansExternalConnections, "meridiansColor", [this.pathsByMeridians] + ); + + this.toUpdateThickness.push(this.polygonGroup, this.bordersGroup, this.latPointsGroup, this.lngPointsGroup); + + /** + * Contains polygons' names' IDs + * @type {string[]} + * @private + */ + this._namesIDs = []; + + /** + * Contains paths' labels' IDs + * @type {string[]} + * @private + */ + this._pathsLabelsIDs = []; + let DEMFilesLabel = "DEMFiles"; if (!GeoTIFFParser) DEMFilesLabel = "DEMFilesWhenGeoTIFFNotSupported"; @@ -48,17 +91,16 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa DEMFilesLabel = "DEMFilesIE9"; this.addWidgets( - new L.ALS.Widgets.Checkbox("hidePolygonWidgets", "hidePolygonWidgets", this, "_hidePolygonWidgets"), - new L.ALS.Widgets.Checkbox("hideNumbers", "hideNumbers", this, "_hidePointsNumbers"), - new L.ALS.Widgets.Checkbox("hideCapturePoints", "hideCapturePoints", this, "_hideCapturePoints").setValue(true), - new L.ALS.Widgets.Checkbox("hidePathsConnections", "hidePathsConnections", this, "_hidePathsConnections"), - new L.ALS.Widgets.Checkbox("hidePathsByMeridians", "hidePathsByMeridians", this, "_hidePathsByMeridians"), - new L.ALS.Widgets.Checkbox("hidePathsByParallels", "hidePathsByParallels", this, "_hidePathsByParallels"), - new L.ALS.Widgets.Number("lineThickness", "lineThickness", this, "_setLineThickness").setMin(1).setMax(20).setValue(this.lineThicknessValue), + new L.ALS.Widgets.Checkbox("hidePolygonWidgets", "hidePolygonWidgets", this, "_updateLayersVisibility"), + new L.ALS.Widgets.Checkbox("hideNumbers", "hideNumbers", this, "_updateLayersVisibility"), + new L.ALS.Widgets.Checkbox("hideCapturePoints", "hideCapturePoints", this, "_updateLayersVisibility").setValue(true), + new L.ALS.Widgets.Checkbox("hidePathsConnections", "hidePathsConnections", this, "_updateLayersVisibility"), + new L.ALS.Widgets.Checkbox("hidePathsByMeridians", "hidePathsByMeridians", this, "_updateLayersVisibility"), + new L.ALS.Widgets.Checkbox("hidePathsByParallels", "hidePathsByParallels", this, "_updateLayersVisibility") + ); + this.addWidgets( new L.ALS.Widgets.Color("gridBorderColor", "gridBorderColor", this, "_setColor").setValue(this.gridBorderColor), new L.ALS.Widgets.Color("gridFillColor", "gridFillColor", this, "_setColor").setValue(this.gridFillColor), - new L.ALS.Widgets.Color("meridiansColor", "meridiansColor", this, "_setColor").setValue(this.meridiansColor), - new L.ALS.Widgets.Color("parallelsColor", "parallelsColor", this, "_setColor").setValue(this.parallelsColor), ); this.addBaseParametersInputSection(); @@ -71,10 +113,6 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa let valueLabels = [ new L.ALS.Widgets.ValueLabel("lngPathsCount", "lngPathsCount"), new L.ALS.Widgets.ValueLabel("latPathsCount", "latPathsCount"), - new L.ALS.Widgets.ValueLabel("lngPathsLength", "lngPathsLength", "m"), - new L.ALS.Widgets.ValueLabel("latPathsLength", "latPathsLength", "m"), - new L.ALS.Widgets.ValueLabel("lngFlightTime", "lngFlightTime", "h"), - new L.ALS.Widgets.ValueLabel("latFlightTime", "latFlightTime", "h"), new L.ALS.Widgets.ValueLabel("lngCellSizeInMeters", "lngCellSizeInMeters", "m"), new L.ALS.Widgets.ValueLabel("latCellSizeInMeters", "latCellSizeInMeters", "m"), new L.ALS.Widgets.ValueLabel("selectedArea", "selectedArea", "sq.m."), @@ -105,36 +143,10 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa this._currentStandardScale = Infinity; this.calculateThreshold(settings); // Update hiding threshold - // To optimize the grid and reduce visual clutter, let's: - // 1. Display only visible polygons. If we'll render the whole thing, user will need from couple of MBs to TBs of RAM. - // 2. Hide grid when it'll contain a lot of polygons and becomes messy - // Additional redrawing actually won't introduce any noticeable delay. - - // Create empty groups containing our stuff. Yeah, I hate copying too, but I want code completion :D - - this.polygonGroup = L.featureGroup(); - this.widgetsGroup = L.featureGroup(); - this.bordersGroup = L.featureGroup(); - this.latPointsGroup = L.featureGroup(); - this.lngPointsGroup = L.featureGroup(); - this.pathsWithoutConnectionsGroup = L.featureGroup(); - this.labelsGroup = new L.LabelLayer(false); - - this.addLayers(this.polygonGroup, this.widgetsGroup, this.bordersGroup, this.pathsWithoutConnectionsGroup, this.latPointsGroup, this.lngPointsGroup, this.labelsGroup); - - /** - * Contains polygons' names IDs - * @type {string[]} - * @private - */ - this._namesIDs = []; - // Bind all the methods this.addEventListenerTo(this.map, "moveend resize", "_onMapPan"); this.addEventListenerTo(this.map, "zoomend", "_onMapZoom"); - L.ALS.SynthBaseLayer.prototype.init.call(this, wizardResults, settings); - this.updateGrid(); this.getWidgetById("hideCapturePoints").callCallback(); }, diff --git a/SynthGridLayer/SynthGridSettings.js b/SynthGridLayer/SynthGridSettings.js index 36d8e834..783127c6 100644 --- a/SynthGridLayer/SynthGridSettings.js +++ b/SynthGridLayer/SynthGridSettings.js @@ -4,43 +4,37 @@ * @class * @extends L.ALS.Settings */ -L.ALS.SynthGridSettings = L.ALS.Settings.extend( /** @lends L.ALS.SynthGridSettings.prototype */ { +L.ALS.SynthGridSettings = L.ALS.SynthBaseSettings.extend( /** @lends L.ALS.SynthGridSettings.prototype */ { gridBorderColor: "#6495ed", gridFillColor: "#6495ed", meridiansColor: "#ad0000", parallelsColor: "#007800", - lineThickness: 2, initialize: function () { - L.ALS.Settings.prototype.initialize.call(this); + L.ALS.SynthBaseSettings.prototype.initialize.call(this); this.addWidget( - (new L.ALS.Widgets.Color("gridBorderColor", "defaultGridBorderColor")).setValue(this.gridBorderColor), + new L.ALS.Widgets.Color("gridBorderColor", "defaultGridBorderColor").setValue(this.gridBorderColor), this.gridBorderColor ); this.addWidget( - (new L.ALS.Widgets.Color("gridFillColor", "defaultGridFillColor")).setValue(this.gridFillColor), + new L.ALS.Widgets.Color("gridFillColor", "defaultGridFillColor").setValue(this.gridFillColor), this.gridFillColor ); this.addWidget( - (new L.ALS.Widgets.Color("meridiansColor", "defaultMeridiansColor")).setValue(this.meridiansColor), - this.meridiansColor - ); - - this.addWidget( - (new L.ALS.Widgets.Color("parallelsColor", "defaultParallelsColor")).setValue(this.parallelsColor), + new L.ALS.Widgets.Color("color0", "defaultParallelsColor").setValue(this.parallelsColor), this.parallelsColor ); this.addWidget( - (new L.ALS.Widgets.Number("lineThicknessValue", "defaultLineThickness")).setMin(1).setMax(20).setValue(this.lineThickness), - this.lineThickness + new L.ALS.Widgets.Color("color1", "defaultMeridiansColor").setValue(this.meridiansColor), + this.meridiansColor ); - this.addWidget((new L.ALS.Widgets.Number("gridHidingFactor", "gridHidingFactor")).setMin(1).setMax(10).setValue(5), 5); + this.addWidget(new L.ALS.Widgets.Number("gridHidingFactor", "gridHidingFactor").setMin(1).setMax(10).setValue(5), 5); } }); \ No newline at end of file diff --git a/SynthGridLayer/calculateParameters.js b/SynthGridLayer/calculateParameters.js index 3a47b788..a7095882 100644 --- a/SynthGridLayer/calculateParameters.js +++ b/SynthGridLayer/calculateParameters.js @@ -16,7 +16,5 @@ L.ALS.SynthGridLayer.prototype.calculateParameters = function () { this.getWidgetById(sizeName).setValue(this.toFixed(cellSize)); this.getWidgetById(countName).setValue(pathsCount); } - - this._drawPaths(); this._calculatePolygonParameters(); } \ No newline at end of file diff --git a/SynthGridLayer/drawPaths.js b/SynthGridLayer/drawPaths.js index c238549e..cabf6e1c 100644 --- a/SynthGridLayer/drawPaths.js +++ b/SynthGridLayer/drawPaths.js @@ -4,24 +4,13 @@ const turfHelpers = require("@turf/helpers"); L.ALS.SynthGridLayer.prototype._drawPaths = function () { // Remove previously added paths - let params = [ - ["pathsByParallels", "parallels", this.parallelsColor], - ["pathsByMeridians", "meridians", this.meridiansColor] - ]; - - for (let param of params) { - let pathName = param[0]; - if (this[pathName] !== undefined) - this.removeLayers(this[pathName]); - this[pathName] = L.polyline([], { - color: param[2], - weight: this.lineThicknessValue - }); - } - - let groupsToClear = ["pathsWithoutConnectionsGroup", "latPointsGroup", "lngPointsGroup"]; + let groupsToClear = [this.pathsByParallels, this.pathsByMeridians, this.meridiansExternalConnections, this.meridiansInternalConnections, this.parallelsExternalConnections, this.parallelsInternalConnections, this.latPointsGroup, this.lngPointsGroup]; for (let group of groupsToClear) - this[group].clearLayers(); + group.clearLayers(); + + for (let id of this._pathsLabelsIDs) + this.labelsGroup.deleteLabel(id); + this._pathsLabelsIDs = []; // Validate parameters @@ -45,8 +34,13 @@ L.ALS.SynthGridLayer.prototype._drawPaths = function () { } errorLabel.setValue(""); + if (this.mergedPolygons.length === 0) + return; + this._drawPathsWorker(true) this._drawPathsWorker(false); + this.updatePathsMeta(); + this.labelsGroup.redraw(); } /** @@ -54,71 +48,63 @@ L.ALS.SynthGridLayer.prototype._drawPaths = function () { * @private */ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { - let pathName, nameForOutput, color, hideEverything; + + let pathName, nameForOutput, color, connectionsGroup, widgetId; if (isParallels) { pathName = "pathsByParallels"; + connectionsGroup = this.parallelsInternalConnections; nameForOutput = "lng"; - color = "parallelsColor"; - hideEverything = this._doHidePathsByParallels; + color = this["color0"]; + widgetId = "hidePathsByParallels"; } else { pathName = "pathsByMeridians"; + connectionsGroup = this.meridiansInternalConnections; nameForOutput = "lat"; - color = "meridiansColor"; - hideEverything = this._doHidePathsByMeridians; - } - let pointsName = nameForOutput + "PointsGroup"; - - let parallelsPathsCount = this["lngPathsCount"]; - let meridiansPathsCount = this["latPathsCount"]; - - let airportLatLng = this._airportMarker.getLatLng(); // We'll need to add it at both beginning and end - this[pathName].addLatLng(airportLatLng); - - // Merge selected polygons into one. We'll "mask" generated lines using it. - let unitedPolygons = undefined; - for (let name in this.selectedPolygons) { - if (!this.selectedPolygons.hasOwnProperty(name)) - continue; - unitedPolygons = this._addSelectedPolygonToGeoJSON(unitedPolygons, name); + color = this["color1"]; + widgetId = "hidePathsByMeridians"; } - - if (unitedPolygons === undefined) - return; - - // Iterate over each polygon in united multipolygon feature - let geometry = unitedPolygons.geometry; - let isMultiPolygon = (geometry.type === "MultiPolygon"); - for (let polygon of geometry.coordinates) { - let toConvert = isMultiPolygon ? polygon : [polygon]; // This function accepts array of arrays of coordinates. Simple polygons are just arrays of coordinates, so we gotta wrap it. - let turfPolygon = turfHelpers.polygon(toConvert); - let box = bbox(turfPolygon); // Create bounding box around current polygon - - // We'll draw paths using bounding box and then clip it by current polygon - let startLat = box[3]; // Northern lat - let endLat = box[1]; // Southern lat - let startLng = box[0]; // Western lng - let endLng = box[2] // Eastern lng - let swapPoints = false; // Should swap points on each new line - - // Calculate new distances between paths for current polygon - let lengthByLat = Math.abs(startLat - endLat); - let lengthByLng = Math.abs(endLng - startLng); - let newParallelsPathsCount = parallelsPathsCount * Math.ceil(lengthByLat / this.latDistance); - let newMeridiansPathsCount = meridiansPathsCount * Math.ceil(lengthByLng / this.lngDistance); - let parallelsDistance = lengthByLat / newParallelsPathsCount; - let meridiansDistance = lengthByLng / newMeridiansPathsCount; - - // Calculate correct capture basis in degrees. - let latDistance = Math.abs(endLat - startLat), lngDistance = Math.abs(endLng - startLng); - let latPointsCount = Math.round(latDistance / this.basis); - let lngPointsCount = Math.round(lngDistance / this.basis); - - let latBasis = latDistance / latPointsCount, lngBasis = lngDistance / lngPointsCount; - - let lat = startLat, lng = startLng; - let turfPolygonCoordinates = turfPolygon.geometry.coordinates[0] // MathTools accepts coordinates of the polygon, not polygon itself - let number = 1; - while (lat >= endLat && lng <= endLng) { + let pathGroup = this[pathName], + pointsName = nameForOutput + "PointsGroup", + parallelsPathsCount = this["lngPathsCount"], + meridiansPathsCount = this["latPathsCount"], + lineOptions = { + color, + weight: this.lineThicknessValue + }, + connLineOptions = { + color, + weight: this.lineThicknessValue, + dashArray: this.dashedLine, + }; + + let shouldHideNumbers = this.getWidgetById(widgetId).getValue() || this._doHidePathsNumbers; + + for (let polygon of this.mergedPolygons) { + let turfPolygon = turfHelpers.polygon([polygon]), // This function accepts array of arrays of coordinates. Simple polygons are just arrays of coordinates, so we gotta wrap it. + [startLng, endLat, endLng, startLat] = bbox(turfPolygon), // Create bounding box around current polygon. We'll draw paths using bounding box and then clip it by current polygon + swapPoints = false, // Should swap points on each new line + + // Calculate new distances between paths for current polygon + lengthByLat = Math.abs(startLat - endLat), + lengthByLng = Math.abs(endLng - startLng), + newParallelsPathsCount = parallelsPathsCount * Math.round(lengthByLat / this.latDistance), + newMeridiansPathsCount = meridiansPathsCount * Math.round(lengthByLng / this.lngDistance), + parallelsDistance = lengthByLat / newParallelsPathsCount, + meridiansDistance = lengthByLng / newMeridiansPathsCount, + + // Calculate correct capture basis in degrees. + latDistance = Math.abs(endLat - startLat), lngDistance = Math.abs(endLng - startLng), + latPointsCount = Math.round(latDistance / this.basis), + lngPointsCount = Math.round(lngDistance / this.basis), + + latBasis = latDistance / latPointsCount, lngBasis = lngDistance / lngPointsCount, + + lat = startLat, lng = startLng, + turfPolygonCoordinates = turfPolygon.geometry.coordinates[0], // MathTools accepts coordinates of the polygon, not polygon itself + number = 1, connectionLine = L.polyline([], connLineOptions); + connectionLine.actualPaths = []; + + while (MathTools.isGreaterThanOrEqualTo(lat, endLat) && MathTools.isLessThanOrEqualTo(lng, endLng)) { let lineCoordinates; if (isParallels) lineCoordinates = [ @@ -133,16 +119,6 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { let clippedLine = MathTools.clipLineByPolygon(lineCoordinates, turfPolygonCoordinates); - // This should not occur, but let's have a handler anyway - if (clippedLine === undefined) { - L.polyline([[lat, startLng], [lat, endLng]], {color: "black"}).addTo(this.map); - lat -= parallelsDistance; - //continue; - window.alert("An error occurred in Grid Layer. Please, report it to https://github.com/matafokka/SynthFlight and provide a screenshot of a selected area and all layer's settings."); - console.log(lineCoordinates, turfPolygonCoordinates); - break; - } - // Extend line by double capture basis to each side let index, captureBasis; if (isParallels) { @@ -180,42 +156,36 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { secondPoint = endPoint; } - // This line will be added to pathsWithoutConnectionsGroup - let line = L.polyline([], { - color: this[color], - weight: this.lineThicknessValue - }); + let line = L.polyline([], lineOptions); // Contains paths with turns, i.e. internal connections for (let point of [firstPoint, secondPoint]) { // Add points to the path let coord = [point[1], point[0]]; - this[pathName].addLatLng(coord); - - if (hideEverything) - continue; - line.addLatLng(coord); + connectionLine.addLatLng(coord); // Add numbers - if (this._doHidePathsNumbers) + if (shouldHideNumbers) continue; - let id = "pt" + pathName + number; - this.labelsGroup.addLabel(id, coord, number, L.LabelLayer.DefaultDisplayOptions[isParallels ? "Message" : "Error"]); + + let labelId = L.ALS.Helpers.generateID(); + this._pathsLabelsIDs.push(labelId); + this.labelsGroup.addLabel(labelId, coord, number, L.LabelLayer.DefaultDisplayOptions[isParallels ? "Message" : "Error"]); number++; } - this.pathsWithoutConnectionsGroup.addLayer(line); - // Add capture points - let ptLat = startPoint[1], ptLng = startPoint[0], ptEndLat = endPoint[1], ptEndLng = endPoint[0]; + pathGroup.addLayer(line); + connectionLine.actualPaths.push(line); - let ptColor = isParallels ? this.parallelsColor : this.meridiansColor; + // Add capture points + let [ptLng, ptLat] = startPoint, [ptEndLng, ptEndLat] = endPoint; while (MathTools.isGreaterThanOrEqualTo(ptLat, ptEndLat) && MathTools.isLessThanOrEqualTo(ptLng, ptEndLng)) { let circle = L.circleMarker([ptLat, ptLng], { radius: this.lineThicknessValue * 2, stroke: false, fillOpacity: 1, fill: true, - fillColor: ptColor, + fillColor: color, }); this[pointsName].addLayer(circle); if (isParallels) @@ -231,33 +201,6 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { lng += meridiansDistance; } - } - this[pathName].addLatLng(airportLatLng); - - // Calculate parameters based on paths length - let pathLength = Math.round(this.lineLengthUsingFlightHeight(this[pathName])); - let flightTime = parseFloat((pathLength / this.aircraftSpeedInMetersPerSecond / 3600).toFixed(2)); - - let params = [ - ["pathLength", "PathsLength", pathLength], - ["flightTime", "FlightTime", flightTime], - ["pathsCount", "PathsCount", this[nameForOutput + "PathsCount"]] - ]; - for (let param of params) { - let value = param[2]; - this[pathName][param[0]] = value; - this.getWidgetById(nameForOutput + param[1]).setValue(value); - } - - if (hideEverything) - return; - - // Display either polyline or paths without connections - if (this._doHidePathsConnections) { - this[pathName].remove(); - this.map.addLayer(this.pathsWithoutConnectionsGroup); - } else { - this.pathsWithoutConnectionsGroup.remove(); - this.map.addLayer(this[pathName]); + connectionsGroup.addLayer(connectionLine); } } \ No newline at end of file diff --git a/SynthGridLayer/misc.js b/SynthGridLayer/misc.js index 83c42014..d2590b2b 100644 --- a/SynthGridLayer/misc.js +++ b/SynthGridLayer/misc.js @@ -1,83 +1,67 @@ // Misc methods, event handlers, etc which most likely won't change in future const turfHelpers = require("@turf/helpers"); -const union = require("@turf/union").default; - -L.ALS.SynthGridLayer.prototype.onMarkerDrag = function () { - L.ALS.SynthBaseLayer.prototype.onMarkerDrag.call(this); - this._drawPaths(); -} +const polybool = require("polybooljs"); +const MathTools = require("../MathTools.js"); L.ALS.SynthGridLayer.prototype._setColor = function (widget) { this[widget.id] = widget.getValue(); this.updateGrid(); } -L.ALS.SynthGridLayer.prototype._setLineThickness = function (widget) { - this.lineThicknessValue = widget.getValue(); - this.updateGrid(); -} - -L.ALS.SynthGridLayer.prototype._hidePathsConnections = function (widget) { - this._doHidePathsConnections = widget.getValue(); - this._drawPaths(); -} - -/** - * Updates grid by redrawing all polygons, recalculating stuff, etc - */ -L.ALS.SynthGridLayer.prototype.updateGrid = function () { - this.labelsGroup.deleteAllLabels(); - this._onMapZoom(); - this.calculateParameters(); - this._calculatePolygonParameters(); - this._drawPaths(); -} - -L.ALS.SynthGridLayer.prototype._hidePolygonWidgets = function (widget) { - this._doHidePolygonWidgets = this._hideOrShowLayer(widget, this.widgetsGroup); -} +L.ALS.SynthGridLayer.prototype._updateLayersVisibility = function () { + let hidePathsByMeridians = this.getWidgetById("hidePathsByMeridians").getValue(), + hidePathsByParallels = this.getWidgetById("hidePathsByParallels").getValue(); -L.ALS.SynthGridLayer.prototype._hidePointsNumbers = function (widget) { - this._doHidePathsNumbers = widget.getValue(); - this.updateGrid(); -} + if (this.getWidgetById("hidePathsConnections").getValue()) { + this.parallelsInternalConnections.remove(); + this.parallelsExternalConnections.remove(); + this.meridiansInternalConnections.remove(); + this.meridiansExternalConnections.remove(); + } else { + this._hideOrShowLayer(hidePathsByParallels, this.parallelsInternalConnections); + this._hideOrShowLayer(hidePathsByParallels, this.parallelsExternalConnections); + this._hideOrShowLayer(hidePathsByMeridians, this.meridiansInternalConnections); + this._hideOrShowLayer(hidePathsByMeridians, this.meridiansExternalConnections); + } -L.ALS.SynthGridLayer.prototype._hideCapturePoints = function (widget) { - this._areCapturePointsHidden = this._hideOrShowLayer(widget, this.latPointsGroup); - this._hideOrShowLayer(widget, this.lngPointsGroup); - this._hidePathsByMeridians(this.getWidgetById("hidePathsByMeridians")); - this._hidePathsByParallels(this.getWidgetById("hidePathsByParallels")); -} + if (this.getWidgetById("hideCapturePoints").getValue()) { + this.latPointsGroup.remove(); + this.lngPointsGroup.remove(); + } else { + this._hideOrShowLayer(hidePathsByParallels, this.lngPointsGroup); + this._hideOrShowLayer(hidePathsByMeridians, this.latPointsGroup); + } -L.ALS.SynthGridLayer.prototype._hidePathsByMeridians = function (widget) { - this._doHidePathsByMeridians = this._hideOrShowLayer(widget, this["pathsByMeridians"]); - if (!this._areCapturePointsHidden) - this._hideOrShowLayer(widget, this.latPointsGroup); - this.updateGrid(); -} + this._hideOrShowLayer(hidePathsByParallels, this.pathsByParallels); + this._hideOrShowLayer(hidePathsByMeridians, this.pathsByMeridians); -L.ALS.SynthGridLayer.prototype._hidePathsByParallels = function (widget) { - this._doHidePathsByParallels = this._hideOrShowLayer(widget, this["pathsByParallels"]); - if (!this._areCapturePointsHidden) - this._hideOrShowLayer(widget, this.lngPointsGroup); - this.updateGrid(); + this._doHidePolygonWidgets = this._hideOrShowLayer(this.getWidgetById("hidePolygonWidgets").getValue(), this.widgetsGroup); + this._doHidePathsNumbers = this.getWidgetById("hideNumbers").getValue(); + this._drawPaths(); // We have to redraw paths both for hiding one of the paths and hiding numbers } /** * Hides or shows layer. - * @param checkbox {L.ALS.Widgets.Checkbox} Checkbox that indicates whether layer should be hidden or not + * @param hide {boolean} If true, hide layer * @param layer {Layer} Layer to show or hide * @return {boolean} If true, layer has been hidden. False otherwise. * @private */ -L.ALS.SynthGridLayer.prototype._hideOrShowLayer = function (checkbox, layer) { - let isChecked = checkbox.getValue(); - if (isChecked) +L.ALS.SynthGridLayer.prototype._hideOrShowLayer = function (hide, layer) { + if (hide) layer.remove(); else this.map.addLayer(layer); - return isChecked; + return hide; +} + +/** + * Updates grid by redrawing all polygons, recalculating stuff, etc + */ +L.ALS.SynthGridLayer.prototype.updateGrid = function () { + this._onMapZoom(); + this.calculateParameters(); } /** @@ -91,11 +75,6 @@ L.ALS.SynthGridLayer.prototype._generatePolygonName = function (polygon) { return "p_" + this.toFixed(firstPoint.lat) + "_" + this.toFixed(firstPoint.lng); } -L.ALS.SynthGridLayer.prototype.setAirportLatLng = function () { - L.ALS.SynthBaseLayer.prototype.setAirportLatLng.call(this); - this._drawPaths(); -} - /** * Loops over pathsByParallels and pathsByMeridians and calls callback * @param callback {function(Polyline)} Callback function that accepts polyline (path) @@ -147,7 +126,7 @@ L.ALS.SynthGridLayer.prototype.applyNewSettings = function (settings) { /** * Calculates grid hiding threshold - * @param settings {L.ALS.Settings} Settings to calculate threshold from + * @param settings {SettingsObject} Settings to calculate threshold from */ L.ALS.SynthGridLayer.prototype.calculateThreshold = function (settings) { let multiplier = (settings.gridHidingFactor - 5) / 5; // Factor is in range [1..10]. Let's make it [-1...1] @@ -158,39 +137,128 @@ L.ALS.SynthGridLayer.prototype.calculateThreshold = function (settings) { this.hidingThreshold = this._currentStandardScale === Infinity ? this.minThreshold : this.maxThreshold; } -/** - * Calculates line length using haversine formula with account of flight height - * @param line - * @return {number} - */ -L.ALS.SynthGridLayer.prototype.lineLengthUsingFlightHeight = function (line) { - let r = 6371000 + this["flightHeight"]; - let points = line.getLatLngs(); - let distance = 0; - for (let i = 0; i < points.length - 1; i++) { - let p1 = points[i], p2 = points[i + 1]; - let f1 = turfHelpers.degreesToRadians(p1.lat), f2 = turfHelpers.degreesToRadians(p2.lat); - let df = f2 - f1; - let dl = turfHelpers.degreesToRadians(p2.lng - p1.lng); - let a = Math.sin(df / 2) ** 2 + Math.cos(f1) * Math.cos(f2) * Math.sin(dl / 2) ** 2; - distance += r * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - } - return distance; -} - /** * Merges selected polygon into one GeoJSON feature. - * @param currentGeoJSON Current GeoJSON object - * @param name {string} Name of current polygon - * @return Merged feature + * @return number[][][] Merged feature * @private */ -L.ALS.SynthGridLayer.prototype._addSelectedPolygonToGeoJSON = function (currentGeoJSON, name) { - let polygonGeoJSON = this.selectedPolygons[name].toGeoJSON(); - if (currentGeoJSON === undefined) { - currentGeoJSON = polygonGeoJSON; - return currentGeoJSON; +L.ALS.SynthGridLayer.prototype._mergeSelectedPolygons = function () { + // Convert object with polygons to an array and push edges instead of points here + this.mergedPolygons = []; + for (let id in this.selectedPolygons) { + let latLngs = this.selectedPolygons[id].getLatLngs()[0], poly = []; + for (let p of latLngs) + poly.push([p.lng, p.lat]); + poly.push(poly[0]); // We need to close the polygons to use MathTools stuff + poly.zoneNumber = this.selectedPolygonsWidgets[id].getWidgetById("zoneNumber").getValue(); + poly.name = this.selectedPolygons[id].polygonName; // TODO: Remove after testing + this.mergedPolygons.push(poly); } - currentGeoJSON = union(currentGeoJSON, polygonGeoJSON); - return currentGeoJSON; + + // Until there's no adjacent polygons, compare each polygon to each and try to find adjacent one. Then merge it. + while (true) { + let toMerge; + for (let poly1 of this.mergedPolygons) { + for (let poly2 of this.mergedPolygons) { + if (poly1 === poly2 || poly1.zoneNumber !== poly2.zoneNumber) + continue; + + // Check if we have a small polygon completely inside of a big one, i.e., if it could form a hole. + // We need a special algorithm because a small polygon might have all common points, but not any common + // edges. In such case, polygons shouldn't be merged. + // If all points touch edges, but not all edges do the same, we're completely fine with it. + + // So we're gonna check if all points of a small polygon are inside a big one, but none of the points + // touches a point of a big polygon. + + let shouldMerge = true; + for (let p1 of poly1) { + shouldMerge = shouldMerge && MathTools.isPointInPolygon(p1, poly2); + + if (poly1.name === "M-30-24-D-b") + console.log(p1, MathTools.isPointInPolygon(p1, poly2)); + + if (!shouldMerge) + break; + + for (let p2 of poly2) { + shouldMerge = shouldMerge && !MathTools.arePointsEqual(p1, p2); + + if (!shouldMerge) + break; + } + + if (!shouldMerge) + break; + } + + if (shouldMerge) { + toMerge = {poly1, poly2}; + break; + } + + // Check if any two edges of the polygons overlap, in which case we should merge polygons + + for (let ii = 0; ii < poly1.length - 1; ii++) { + let edge1 = [poly1[ii], poly1[ii + 1]]; + + for (let jj = 0; jj < poly2.length - 1; jj++) { + let edge2 = [poly2[jj], poly2[jj + 1]], + intersection = MathTools.linesIntersection(edge1, edge2); + + if (!intersection || intersection.length === 1) + continue; + + let [p1, p2] = intersection, pairs = [[p1, p2], [p2, p1]]; + + // When edges are adjacent, i.e. when only one point of the first edge touches a point + // of the second edge + if (MathTools.arePointsEqual(p1, p2)) + continue; + + for (let pair of pairs) { + let [p1, p2] = pair; + if (MathTools.isPointOnLine(p1, edge1) && MathTools.isPointOnLine(p2, edge2)) { + toMerge = {poly1, poly2}; + break; + } + } + + if (toMerge) + break; + } + if (toMerge) + break; + } + if (toMerge) + break; + } + if (toMerge) + break; + } + if (!toMerge) + break; + + let merged = polybool.union( + {regions: [toMerge.poly1]}, + {regions: [toMerge.poly2]} + ).regions, + newPolygon = merged.length === 1 ? merged[0] : merged[1]; + newPolygon.zoneNumber = toMerge.poly1.zoneNumber; + + // Union returns polygon with four points, we need to close it + if (!MathTools.arePointsEqual(newPolygon[0], newPolygon[newPolygon.length - 1])) + newPolygon.push(newPolygon[0]); + + let newPolygons = [newPolygon]; // Array with merged polygons + + for (let poly of this.mergedPolygons) { + if (poly !== toMerge.poly1 && poly !== toMerge.poly2) + newPolygons.push(poly); + } + this.mergedPolygons = newPolygons; + } + + console.log(this.mergedPolygons) + return this.mergedPolygons; } \ No newline at end of file diff --git a/SynthGridLayer/polygons.js b/SynthGridLayer/polygons.js index d9f844fd..675bed69 100644 --- a/SynthGridLayer/polygons.js +++ b/SynthGridLayer/polygons.js @@ -40,11 +40,10 @@ L.ALS.SynthGridLayer.prototype._selectOrDeselectPolygon = function (event) { L.ALS.SynthGridLayer.prototype._calculatePolygonParameters = function () { let areaIncrement = Math.round(this["latCellSizeInMeters"] * this["lngCellSizeInMeters"]); this.selectedArea = 0; - let unitedPolygons = undefined; for (let name in this.selectedPolygons) { if (!this.selectedPolygons.hasOwnProperty(name)) continue; - unitedPolygons = this._addSelectedPolygonToGeoJSON(unitedPolygons, name); + this.selectedArea += areaIncrement; let layer = this.selectedPolygons[name]; @@ -80,19 +79,21 @@ L.ALS.SynthGridLayer.prototype._calculatePolygonParameters = function () { this.getWidgetById("selectedArea").setValue(this.selectedArea); // Draw thick borders around selected polygons + this._mergeSelectedPolygons(); this.bordersGroup.clearLayers(); - if (unitedPolygons === undefined) + if (this.mergedPolygons.length === 0) return; - let geometry = unitedPolygons.geometry; - let isMultiPolygon = (geometry.type === "MultiPolygon"); - for (let polygon of geometry.coordinates) { - let line = L.polyline([], { - color: this.gridBorderColor, - weight: this.lineThicknessValue * 2 - }); - let coordinates = isMultiPolygon ? polygon[0] : polygon; - for (let coordinate of coordinates) - line.addLatLng([coordinate[1], coordinate[0]]); - this.bordersGroup.addLayer(line); + + for (let polygon of this.mergedPolygons) { + let latLngs = []; + for (let p of polygon) + latLngs.push([p[1], p[0]]); + + this.bordersGroup.addLayer(L.polyline(latLngs, { + weight: this.lineThicknessValue * this.bordersGroup.thicknessMultiplier, + color: this.getWidgetById("gridBorderColor").getValue() + } + )); } + this._drawPaths(); } \ No newline at end of file diff --git a/SynthGridLayer/serialization.js b/SynthGridLayer/serialization.js index 74744a7c..9c3a3e21 100644 --- a/SynthGridLayer/serialization.js +++ b/SynthGridLayer/serialization.js @@ -13,7 +13,7 @@ L.ALS.SynthGridLayer.prototype.serialize = function (seenObjects) { return serialized; } -L.ALS.SynthGridLayer._toUpdateColors = ["gridBorderColor", "gridFillColor", "meridiansColor", "parallelsColor"]; +L.ALS.SynthGridLayer._toUpdateColors = ["gridBorderColor", "gridFillColor", "color0", "color1"]; L.ALS.SynthGridLayer.deserialize = function (serialized, layerSystem, settings, seenObjects) { let object = L.ALS.Layer.deserialize(serialized, layerSystem, settings, seenObjects); diff --git a/SynthGridLayer/toGeoJSON.js b/SynthGridLayer/toGeoJSON.js index 8ae84157..e39f78e9 100644 --- a/SynthGridLayer/toGeoJSON.js +++ b/SynthGridLayer/toGeoJSON.js @@ -2,12 +2,13 @@ const geojsonMerge = require("@mapbox/geojson-merge"); // Using this since turfH L.ALS.SynthGridLayer.prototype.toGeoJSON = function () { let jsons = []; + for (let name in this.selectedPolygons) { if (!this.selectedPolygons.hasOwnProperty(name)) continue; - let polygon = this.selectedPolygons[name]; - let polygonJson = polygon.toGeoJSON(); - let props = ["polygonName", "minHeight", "maxHeight", "meanHeight", "absoluteHeight", "reliefType", "elevationDifference"]; + let polygon = this.selectedPolygons[name], + polygonJson = polygon.toGeoJSON(), + props = ["polygonName", "minHeight", "maxHeight", "meanHeight", "absoluteHeight", "reliefType", "elevationDifference"]; for (let prop of props) polygonJson.properties[prop] = polygon[prop]; polygonJson.properties.name = "Selected cell"; @@ -18,33 +19,28 @@ L.ALS.SynthGridLayer.prototype.toGeoJSON = function () { airport.name = "Airport"; jsons.push(airport); - if (this["pathsByMeridians"].isEmpty() || this["pathsByParallels"].isEmpty()) { - window.alert(`No paths has been drawn in layer \"${this.getName()}\"! You'll get only selected gird cells and airport position.`); + if (this.pathsByMeridians.getLayers().length === 0 || this.pathsByParallels.getLayers().length === 0) { + window.alert(`No paths has been drawn in layer "${this.getName()}"! You'll get only selected gird cells and airport position.`); return geojsonMerge.merge(jsons); } - let meridianJson = this["pathsByMeridians"].toGeoJSON(); - meridianJson.properties.name = "Flight paths by meridians"; - let parallelsJson = this["pathsByParallels"].toGeoJSON(); - parallelsJson.properties.name = "Flight paths by parallels"; - // See _calculateParameters - let params = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "flightHeight", "overlayBetweenPaths", "overlayBetweenImages", "imageScale", "ly", "Ly", "By", "lx", "Lx", "Bx", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "latCellSizeInMeters", "lngCellSizeInMeters", "selectedArea"]; - for (let line of [meridianJson, parallelsJson]) { + let parallelsProps = { + pathsCount: this.latPathsCount, + name: "Flight paths by parallels" + }, + meridiansProps = { + pathsCount: this.lngPathsCount, + name: "Flight paths by meridians" + }, + params = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "flightHeight", "overlayBetweenPaths", "overlayBetweenImages", "imageScale", "ly", "Ly", "By", "lx", "Lx", "Bx", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "latCellSizeInMeters", "lngCellSizeInMeters", "selectedArea"]; + + for (let prop of [parallelsProps, meridiansProps]) { for (let param of params) - line.properties[param] = this[param]; - jsons.push(line) + prop[param] = this[param]; } - let lines = [ - ["pathsByParallels", parallelsJson], - ["pathsByMeridians", meridianJson] - ]; - let lineParams = ["pathLength", "flightTime", "pathsCount"]; - for (let line of lines) { - for (let param of lineParams) - line[1].properties[param] = this[line[0]][param]; - } + jsons.push(L.ALS.SynthBaseLayer.prototype.toGeoJSON.call(this, parallelsProps, meridiansProps)); let pointsParams = [["capturePointByMeridians", this.latPointsGroup.getLayers()], ["capturePointByParallels", this.lngPointsGroup.getLayers()]]; for (let param of pointsParams) { diff --git a/SynthLineLayer/SynthLineLayer.js b/SynthLineLayer/SynthLineLayer.js index 5850e3bb..7c0e377e 100644 --- a/SynthLineLayer/SynthLineLayer.js +++ b/SynthLineLayer/SynthLineLayer.js @@ -1,6 +1,5 @@ L.ALS.SynthLineLayer = L.ALS.SynthBaseDrawLayer.extend({ defaultName: "Line Layer", - hasYOverlay: false, hideCapturePoints: true, hidePathsConnections: false, @@ -9,24 +8,26 @@ L.ALS.SynthLineLayer = L.ALS.SynthBaseDrawLayer.extend({ this.drawControls = { polyline: { shapeOptions: { - color: "#ff0000" + color: "#ff0000", + weight: this.lineThicknessValue } } } + L.ALS.SynthBaseDrawLayer.prototype.init.call(this, settings, "lineLayerColor"); this.addWidgets( new L.ALS.Widgets.Checkbox("hideCapturePoints", "hideCapturePoints", this, "_hideCapturePoints").setValue(true), new L.ALS.Widgets.Checkbox("hidePathsConnections", "hidePathsConnections", this, "_hidePathsConnections"), - new L.ALS.Widgets.Color("color", "lineLayerColor", this, "setColorByWidget").setValue(settings.color) ); + this.addBaseParametersInputSection(); + this.addBaseParametersOutputSection(); + this.pointsGroup = L.featureGroup(); - this.connectionsGroup = L.featureGroup(); - L.ALS.SynthBaseDrawLayer.prototype.init.call(this, wizardResults, settings); - this.setColor(settings.color); this.addEventListenerTo(this.map, "draw:drawstart draw:editstart draw:deletestart", "onEditStart"); - this.addEventListenerTo(this.map, "draw:drawstop draw:editstop draw:deletestop", "updatePaths"); + this.addEventListenerTo(this.map, "draw:drawstop draw:editstop draw:deletestop", "onEditEnd"); + this.calculateParameters(); }, onEditStart: function () { @@ -34,292 +35,20 @@ L.ALS.SynthLineLayer = L.ALS.SynthBaseDrawLayer.extend({ this.map.removeLayer(this.pointsGroup); }, - updatePaths: function () { - this.connectPaths(); + onEditEnd: function () { + this.updatePathsMeta(); if (!this.getWidgetById("hidePathsConnections").getValue()) - this.map.addLayer(this.connectionsGroup) + this.map.addLayer(this.connectionsGroup); if (!this.getWidgetById("hideCapturePoints").getValue()) - this.map.addLayer(this.pointsGroup) - }, - - _hideCapturePoints: function (widget) { - this.hideCapturePoints = widget.getValue(); - this.updatePaths(); - }, - _hidePathsConnections: function (widget) { - this.hidePathsConnections = widget.getValue(); - this.updatePaths(); - }, - - onMarkerDrag: function () { - L.ALS.SynthBaseLayer.prototype.onMarkerDrag.call(this); - this.updatePaths(); - }, - - /** - * Connects paths - */ - connectPaths: function () { - // Actual paths are fixed and thus can't be optimized, so we should care only about their endpoints. - // We'll call them paths for the rest of this program. We have to connect line segments in shortest way. - - // This problem is similar to TSP and VRP. VRP is subset of TSP, much harder to solve and might not even work. - // Trust me, I tried Clarke-Wright algorithm and it didn't work (looks like it leaves points for other routes). - // So we'll go with TSP. Convex hull algorithm fits well to our problem and is easy to modify for our needs. - - // Hull will miss some of the endpoints, so for now the only modifications are connecting the missing point - // and adjusting connections between paths to get the shortest hull. I'll explain how we'll do it later. - - // Sources: - // TSP algorithms overview: http://160592857366.free.fr/joe/ebooks/ShareData/Heuristics%20for%20the%20Traveling%20Salesman%20Problem%20By%20Christian%20Nillson.pdf - // Monotone chain implementation: https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain#JavaScript - - this.connectionsGroup.clearLayers(); - - const points = [], layers = this.drawingGroup.getLayers(), airportPos = this._airportMarker.getLatLng(), - lineOptions = { - color: this.drawControls.polyline.shapeOptions.color, - dashArray: "4 8", - }; - - if (layers.length === 0) - return; - - if (layers.length === 1) { - const path = layers[0].getLatLngs(); - this.connectionsGroup.addLayer(L.polyline( - [path[0], airportPos, path[path.length - 1]], - lineOptions - )); - return; - } - - // Get points from each path and mark them as belonged to a current path - // TODO: Remove debug props before release - let debugProps = {color: "purple"}, paths = {}, pathsCount = layers.length; // Keep paths for merging - for (let path of layers) { - const layer = path.getLatLngs(), endPoints = [layer[0], layer[layer.length - 1]]; - //this.connectionsGroup.addLayer(L.polyline(endPoints, debugProps)); - - endPoints.connectionId = L.ALS.Helpers.generateID(); - paths[endPoints.connectionId] = endPoints; - - for (let p of endPoints) { - const newP = L.latLng(p); - newP.path = endPoints; - points.push(newP); - } - } - - // Find convex hull of the points using monotone chain. - points.sort((a, b) => { - return a.lng === b.lng ? a.lat - b.lat : a.lng - b.lng; - }); - - let lower = []; - for (let point of points) { - while (lower.length >= 2 && this.cross(lower[lower.length - 2], lower[lower.length - 1], point) <= 0) - lower.pop(); - lower.push(point); - } - - let upper = []; - for (let i = points.length - 1; i >= 0; i--) { - const point = points[i]; - while (upper.length >= 2 && this.cross(upper[upper.length - 2], upper[upper.length - 1], point) <= 0) - upper.pop(); - upper.push(point); - } - - lower.pop(); - upper.pop(); - - // Normalize path-connection order by appending other point of the first path, if it's not connected already. - let [p1, p2] = lower; - if (!p2) - p2 = upper[0]; - if (p1.path !== p2.path) { - for (let p of p1.path) { - if (p !== p1) { - p2 = p; - break; - } - } - lower = [p2, ...lower]; - } - - // Integrate missing end points of the paths into the hull and leave only connections. - // This points won't produce optimal path, but we'll fix it later. - let connections = [], passedPoints = {}, isFirstPoint = true; - for (let hull of [lower, upper]) { - for (let point of hull) { - if (isFirstPoint) { - isFirstPoint = false; - point.id = L.ALS.Helpers.generateID(); - connections.push(point); - passedPoints[point.id] = true; - continue; - } - - for (let p of point.path) { - if (!p.id) - p.id = L.ALS.Helpers.generateID(); - - if (passedPoints[p.id]) - continue; - - passedPoints[p.id] = true; - connections.push(p); - - // Remove point's path from the list because it'll be already connected - if (paths[p.path.connectionId]) { - delete paths[p.path.connectionId]; - pathsCount--; - } - } - } - } - - connections.push(connections[0]); // Connect endpoints of the hull - - // Optimize hull by rotating each path by 360 and checking if connections length has shortened - // Paths are always even, if counting from 0. - let optConnections = []; // We'll keep only optimal connections here and skip paths - for (let i = 2; i < connections.length - 1; i += 2) { - let pathP1 = connections[i], pathP2 = connections[i + 1], - prevPair = optConnections.pop(), // This won't exist for the first path - prevPoint = prevPair ? prevPair[0] : connections[i - 1], nextPoint = connections[i + 2]; - - optConnections.push(...this.getHullOptimalConnection(pathP1, pathP2, prevPoint, nextPoint)); - } - - // We haven't rotated the first path, so do it - let [toLast, toFirst] = this.getHullOptimalConnection(p1, p2, optConnections.pop()[0], optConnections[0][1]); - optConnections[0][0] = toFirst[0]; - optConnections.push(toLast); - - // Second path was built relying on unoptimized first line, so we have to rotate second line again. - let toSecond = optConnections[0], fromSecond = optConnections[1]; - let [optToSecond, optFromSecond] = this.getHullOptimalConnection(toSecond[1], fromSecond[0], toSecond[0], fromSecond[1]); - optConnections[0] = optToSecond; - optConnections[1] = optFromSecond; - - // Merge other paths with the hull by finding cheapest insertion and using it until no paths left - while (pathsCount > 0) { - let minLen = Infinity, insertion, insertAt, pathId; - for (let id in paths) { - if (!paths.hasOwnProperty(id)) - continue; - - let [p1, p2] = paths[id], pairs = [ - [p1, p2], - [p2, p1] - ]; - - for (let i = 0; i < optConnections.length; i++) { - let [conP1, conP2] = optConnections[i]; - - for (let pair of pairs) { - let [p1, p2] = pair, line1 = [p1, conP1], line2 = [p2, conP2], - len = this.getLineLength(line1) + this.getLineLength(line2); - - if (len < minLen) { - minLen = len; - insertion = [line1, line2]; - insertAt = i; - pathId = id; - } - } - } - } - optConnections = optConnections.slice(0, insertAt).concat(insertion, optConnections.slice(insertAt + 1, optConnections.length)); - delete paths[pathId]; - pathsCount--; - } - - // Add connections except one closest to the airport. We'll connect the airport to the paths using the shortest connection. - let minLen = Infinity, toRemove; - for (let pair of optConnections) { - let len = this.getLineLength([pair[0], airportPos]) + this.getLineLength([pair[1], airportPos]); - if (len > minLen) { - this.connectionsGroup.addLayer(L.polyline(pair, lineOptions)); - continue; - } - minLen = len; - if (toRemove) - this.connectionsGroup.addLayer(L.polyline(toRemove, lineOptions)); - toRemove = pair; - } - - this.connectionsGroup.addLayer(L.polyline([ - toRemove[0], airportPos, toRemove[1], // TODO: When implementing export, check if we need to add pairs instead of this - ], lineOptions)); - }, - - /** - * Rotates a path in hull and returns a pair of optimal connections - * @param pathP1 {LatLng|number[]} First point of path - * @param pathP2 {LatLng|number[]} Second point of path - * @param prevPoint {LatLng|number[]} Previous point - * @param nextPoint {LatLng|number[]} Next point - * @return A pair of optimal connections - */ - getHullOptimalConnection: function (pathP1, pathP2, prevPoint, nextPoint) { - let pairs = [ - [pathP1, pathP2], - [pathP2, pathP1], - ], - minLen = Infinity, optPair; - - for (let pair of pairs) { - let [p1, p2] = pair, - len = this.getLineLength([prevPoint, p1]) + this.getLineLength([nextPoint, p2]); - if (len < minLen) { - minLen = len; - optPair = pair; - } - } - return [[prevPoint, optPair[0]], [optPair[1], nextPoint]]; - }, - - cross: function (a, b, o) { - return (a.lng - o.lng) * (b.lat - o.lat) - (a.lat - o.lat) * (b.lng - o.lng); - }, - - /** - * Calculates length of a polyline - * @param line {L.Polyline|number[][]} Line to calculate length of. If array provided, lat-lng order doesn't matter as long as its consistent. - * @return {number} Line length - */ - getLineLength: function (line) { - const latLngs = line instanceof Array ? line : line.getLatLngs(); - let length = 0; - for (let i = 0; i < latLngs.length; i += 2) { - const pt1 = latLngs[i], pt2 = latLngs[i + 1]; - length += Math.sqrt( - ((pt1?.lat || pt1[0]) - (pt2.lat || pt2[0])) ** 2 + - ((pt1.lng || pt1[1]) - (pt2.lng || pt2[1])) ** 2 - ); - } - return length; - }, - - setColorByWidget: function (widget) { - this.setColor(widget.getValue()) - }, - - setColor: function (color) { - this.drawControls.polyline.shapeOptions.color = color; - for (let layer of this.drawingGroup.getLayers()) - layer.setStyle({color}); + this.map.addLayer(this.pointsGroup); }, onDraw: function (e) { L.ALS.SynthBaseDrawLayer.prototype.onDraw.call(this, e); e.layer.setStyle({ - color: this.getWidgetById("color").getValue(), + color: this.getWidgetById("color0").getValue(), opacity: 1 }); }, diff --git a/SynthLineLayer/SynthLineSettings.js b/SynthLineLayer/SynthLineSettings.js index ac1c063e..9d12dd44 100644 --- a/SynthLineLayer/SynthLineSettings.js +++ b/SynthLineLayer/SynthLineSettings.js @@ -1,9 +1,9 @@ -L.ALS.SynthLineSettings = L.ALS.Settings.extend({ +L.ALS.SynthLineSettings = L.ALS.SynthBaseSettings.extend({ initialize: function () { - L.ALS.Settings.prototype.initialize.call(this); + L.ALS.SynthBaseSettings.prototype.initialize.call(this); const color = "#ff0000"; - this.addWidget(new L.ALS.Widgets.Color("color", "settingsLineLayerColor").setValue(color), color); + this.addWidget(new L.ALS.Widgets.Color("color0", "settingsLineLayerColor").setValue(color), color); } -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/build.js b/build.js index 83e63b20..c4d0a614 100644 --- a/build.js +++ b/build.js @@ -96,6 +96,7 @@ let toCopy = ["index.html", "logo.ico", "node_modules/leaflet.coordinates/dist/Leaflet.Coordinates-0.1.5.css", "node_modules/leaflet.coordinates/dist/Leaflet.Coordinates-0.1.5.min.js", "node_modules/leaflet-draw/dist/leaflet.draw.css", + "node_modules/leaflet-draw/dist/images", ]; if (!onlyBrowser) diff --git a/locales/English.js b/locales/English.js index a333e302..7d8e67ed 100644 --- a/locales/English.js +++ b/locales/English.js @@ -1,5 +1,21 @@ L.ALS.Locales.addLocaleProperties("English", { + // SynthBaseLayer + connectionMethod: "Path connection method", + allIntoOne: "All into one", + oneFlightPerPath: "One flight per path", + pathSpoiler: "Path", + flashPath: "Flash path", + pathLength: "Path length", + flightTime: "Flight time", + + // SynthLineWizard + lineLayerName: "Line Layer", + lineLayerWizardLabel: "This layer is in development, doesn't do anything at the moment, might break the app when added", // TODO: Change to "This layer has no options" when it'll be finished + + // SynthLineLayer + lineLayerColor: "Line color:", + // SynthGridWizard gridWizardDisplayName: "Grid Layer", @@ -37,10 +53,6 @@ L.ALS.Locales.addLocaleProperties("English", { focalLength: "Focal length (mm):", lngPathsCount: "Paths count in one cell by parallels", latPathsCount: "Paths count in one cell by meridians", - lngPathsLength: "Length of paths by parallels", - latPathsLength: "Length of paths by meridians", - lngFlightTime: "Flight time by parallels", - latFlightTime: "Flight time by meridians", lngCellSizeInMeters: "Mean cell width", latCellSizeInMeters: "Mean cell height", selectedArea: "Selected area", diff --git a/locales/Russian.js b/locales/Russian.js index 8668d8ac..707451b4 100644 --- a/locales/Russian.js +++ b/locales/Russian.js @@ -1,5 +1,21 @@ L.ALS.Locales.addLocaleProperties("Русский", { + // SynthBaseLayer + connectionMethod: "Метод соединения маршрутов", + allIntoOne: "Все в один", + oneFlightPerPath: "Один полет на маршрут", + pathSpoiler: "Маршрут", + flashPath: "Помигать маршрутом", + pathLength: "Длина маршрута", + flightTime: "Время полета", + + // SynthLineWizard + lineLayerName: "Слой Линий", + lineLayerWizardLabel: "Этот слой находится в разработке, пока ничего не делает и может сломать приложение, если его добавить", + + // SynthLineLayer + lineLayerColor: "Цвет линий:", + // SynthGridWizard gridWizardDisplayName: "Слой Сетки", @@ -37,10 +53,6 @@ L.ALS.Locales.addLocaleProperties("Русский", { focalLength: "Фокусное расстояние (mm):", lngPathsCount: "Число маршрутов в одной ячейке по параллелям", latPathsCount: "Число маршрутов в одной ячейке по меридианам", - lngPathsLength: "Длина маршрутов по параллелям", - latPathsLength: "Длина маршрутов по меридианам", - lngFlightTime: "Время полета по параллеям", - latFlightTime: "Время полета по меридианам", lngCellSizeInMeters: "Средняя ширина ячейки", latCellSizeInMeters: "Средняя высота сетки", selectedArea: "Площадь выделенных полигонов", diff --git a/main.js b/main.js index 01f3d8e4..b051e463 100644 --- a/main.js +++ b/main.js @@ -13,6 +13,7 @@ L.ALS.Locales.AdditionalLocales.Russian(); require("./locales/English.js"); require("./locales/Russian.js"); require("./SynthShapefileLayer/SynthShapefileLayer.js"); +require("./SynthBase/SynthBaseSettings.js"); require("./SynthBase/SynthBaseLayer.js"); require("./SynthGridLayer/SynthGridLayer.js"); require("./node_modules/leaflet.coordinates/dist/Leaflet.Coordinates-0.1.5.min.js"); @@ -97,4 +98,17 @@ layerSystem.addBaseLayer(L.tileLayer(""), "Empty"); // Add layer types layerSystem.addLayerType(L.ALS.SynthLineLayer); layerSystem.addLayerType(L.ALS.SynthGridLayer); -layerSystem.addLayerType(L.ALS.SynthShapefileLayer); \ No newline at end of file +layerSystem.addLayerType(L.ALS.SynthShapefileLayer); + +const MathTools = require("./MathTools.js"); + +let poly = [ + [0.25, 51.583333333333336], [0.25, 51.33333333333333], + [-0.125, 51.33333333333333], [-0.125, 51.583333333333336], + [0.25, 51.583333333333336] +], point = [0, 51.416666666666664]; + +map.addLayer(L.marker(point)); +map.addLayer(L.polygon(poly)); + +console.log(MathTools.isPointInPolygon(point, poly)) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d16d73fb..c9a12700 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1236,17 +1236,6 @@ "@turf/helpers": "^6.4.0" } }, - "@turf/union": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.0.3.tgz", - "integrity": "sha512-SJPhEvsR96k4vFqymxPPC43jcqFydTafGjHWnYzlupxqUDzIYD8X5d9Ed8mONl2T9oM4ErbNuuZ9j/eHvoWtKw==", - "dev": true, - "requires": { - "@turf/helpers": "6.x", - "@turf/invariant": "6.x", - "martinez-polygon-clipping": "^0.4.3" - } - }, "@types/glob": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", @@ -3962,9 +3951,9 @@ } }, "geotiff-geokeys-to-proj4": { - "version": "2021.7.19-beta", - "resolved": "https://registry.npmjs.org/geotiff-geokeys-to-proj4/-/geotiff-geokeys-to-proj4-2021.7.19-beta.tgz", - "integrity": "sha512-HdmpN4eTBXuJ03nF12UtMxrGkioUrMgxRIoikAnabmnVJINN/vipJUtmaXCmh9+Q8T2mbdqtNuHEa004b1MjsQ==", + "version": "2021.10.31", + "resolved": "https://registry.npmjs.org/geotiff-geokeys-to-proj4/-/geotiff-geokeys-to-proj4-2021.10.31.tgz", + "integrity": "sha512-OCY1anvE9x60gA+ZLKzU9FvwoYySXknGQs1cwSDqcc1tsvah0Fa4Xv7EVQIO5Gsiz+kPK41H6HgSDd0BkyKyTA==", "dev": true }, "get-assigned-identifiers": { @@ -4743,85 +4732,5059 @@ "dependencies": { "universalify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + } + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jszip": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.6.1.tgz", + "integrity": "sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=", + "dev": true, + "requires": { + "pako": "~1.0.2" + } + }, + "junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true + }, + "keyboardevent-key-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keyboardevent-key-polyfill/-/keyboardevent-key-polyfill-1.1.0.tgz", + "integrity": "sha1-ijGdjkWhMXL8pWKGNy+QwdTHAUw=", + "dev": true + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, + "leaflet": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", + "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==", + "dev": true + }, + "leaflet-advanced-layer-system": { + "version": "file:../leaflet-advanced-layer-system", + "dev": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/compat-data": { + "version": "7.14.0" + }, + "@babel/core": { + "version": "7.14.3", + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.3", + "@babel/helper-compilation-targets": "^7.13.16", + "@babel/helper-module-transforms": "^7.14.2", + "@babel/helpers": "^7.14.0", + "@babel/parser": "^7.14.3", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.14.3", + "requires": { + "@babel/types": "^7.14.2", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.12.13", + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.12.13", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.13.16", + "requires": { + "@babel/compat-data": "^7.13.15", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.14.5", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.3", + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-replace-supers": "^7.14.3", + "@babel/helper-split-export-declaration": "^7.12.13" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.14.3", + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "regexpu-core": "^4.7.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.2.0", + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.13.0", + "requires": { + "@babel/types": "^7.13.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.2", + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.14.2" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.13.16", + "requires": { + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.16" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.13.12", + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-imports": { + "version": "7.13.12", + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.2", + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.14.0", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.13.0" + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.13.0", + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-wrap-function": "^7.13.0", + "@babel/types": "^7.13.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.14.3", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2" + } + }, + "@babel/helper-simple-access": { + "version": "7.13.12", + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0" + }, + "@babel/helper-validator-option": { + "version": "7.12.17" + }, + "@babel/helper-wrap-function": { + "version": "7.13.0", + "requires": { + "@babel/helper-function-name": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + } + }, + "@babel/helpers": { + "version": "7.14.0", + "requires": { + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.14.0" + } + }, + "@babel/highlight": { + "version": "7.14.0", + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.3" + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.13.12", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.13.12" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.13.0", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.14.3", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.14.3", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-class-static-block": "^7.12.13" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.14.2", + "requires": { + "@babel/compat-data": "^7.14.0", + "@babel/helper-compilation-targets": "^7.13.16", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.14.2" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.13.0", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.14.0", + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-create-class-features-plugin": "^7.14.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-private-property-in-object": "^7.14.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.12.13", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.0", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.13.0", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.13.0", + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.14.2", + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.13.0", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.13.17", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.12.13", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.12.13", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.13.0", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.12.13", + "requires": { + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.14.2", + "requires": { + "@babel/helper-module-transforms": "^7.14.2", + "@babel/helper-plugin-utils": "^7.13.0", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.14.0", + "requires": { + "@babel/helper-module-transforms": "^7.14.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-simple-access": "^7.13.12", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.13.8", + "requires": { + "@babel/helper-hoist-variables": "^7.13.0", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-identifier": "^7.12.11", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.14.0", + "requires": { + "@babel/helper-module-transforms": "^7.14.0", + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.12.13", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.14.2", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.13.15", + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.14.3", + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-plugin-utils": "^7.13.0", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", + "semver": "^6.3.0" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.13.0", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.13.0", + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.13", + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.12.13", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/preset-env": { + "version": "7.14.2", + "requires": { + "@babel/compat-data": "^7.14.0", + "@babel/helper-compilation-targets": "^7.13.16", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", + "@babel/plugin-proposal-async-generator-functions": "^7.14.2", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-class-static-block": "^7.13.11", + "@babel/plugin-proposal-dynamic-import": "^7.14.2", + "@babel/plugin-proposal-export-namespace-from": "^7.14.2", + "@babel/plugin-proposal-json-strings": "^7.14.2", + "@babel/plugin-proposal-logical-assignment-operators": "^7.14.2", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.2", + "@babel/plugin-proposal-numeric-separator": "^7.14.2", + "@babel/plugin-proposal-object-rest-spread": "^7.14.2", + "@babel/plugin-proposal-optional-catch-binding": "^7.14.2", + "@babel/plugin-proposal-optional-chaining": "^7.14.2", + "@babel/plugin-proposal-private-methods": "^7.13.0", + "@babel/plugin-proposal-private-property-in-object": "^7.14.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.12.13", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.0", + "@babel/plugin-syntax-top-level-await": "^7.12.13", + "@babel/plugin-transform-arrow-functions": "^7.13.0", + "@babel/plugin-transform-async-to-generator": "^7.13.0", + "@babel/plugin-transform-block-scoped-functions": "^7.12.13", + "@babel/plugin-transform-block-scoping": "^7.14.2", + "@babel/plugin-transform-classes": "^7.14.2", + "@babel/plugin-transform-computed-properties": "^7.13.0", + "@babel/plugin-transform-destructuring": "^7.13.17", + "@babel/plugin-transform-dotall-regex": "^7.12.13", + "@babel/plugin-transform-duplicate-keys": "^7.12.13", + "@babel/plugin-transform-exponentiation-operator": "^7.12.13", + "@babel/plugin-transform-for-of": "^7.13.0", + "@babel/plugin-transform-function-name": "^7.12.13", + "@babel/plugin-transform-literals": "^7.12.13", + "@babel/plugin-transform-member-expression-literals": "^7.12.13", + "@babel/plugin-transform-modules-amd": "^7.14.2", + "@babel/plugin-transform-modules-commonjs": "^7.14.0", + "@babel/plugin-transform-modules-systemjs": "^7.13.8", + "@babel/plugin-transform-modules-umd": "^7.14.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", + "@babel/plugin-transform-new-target": "^7.12.13", + "@babel/plugin-transform-object-super": "^7.12.13", + "@babel/plugin-transform-parameters": "^7.14.2", + "@babel/plugin-transform-property-literals": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.13.15", + "@babel/plugin-transform-reserved-words": "^7.12.13", + "@babel/plugin-transform-shorthand-properties": "^7.12.13", + "@babel/plugin-transform-spread": "^7.13.0", + "@babel/plugin-transform-sticky-regex": "^7.12.13", + "@babel/plugin-transform-template-literals": "^7.13.0", + "@babel/plugin-transform-typeof-symbol": "^7.12.13", + "@babel/plugin-transform-unicode-escapes": "^7.12.13", + "@babel/plugin-transform-unicode-regex": "^7.12.13", + "@babel/preset-modules": "^0.1.4", + "@babel/types": "^7.14.2", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", + "core-js-compat": "^3.9.0", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.14.0", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.14.0", + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.12.13", + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.14.2", + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.2", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.14.2", + "@babel/types": "^7.14.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.2", + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "to-fast-properties": "^2.0.0" + } + }, + "@csstools/convert-colors": { + "version": "1.4.0" + }, + "@goto-bus-stop/common-shake": { + "version": "2.4.0", + "requires": { + "acorn-walk": "^7.0.0", + "debug": "^3.2.6", + "escope": "^3.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "JSONStream": { + "version": "1.3.5", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abab": { + "version": "2.0.5" + }, + "acorn": { + "version": "7.4.1" + }, + "acorn-globals": { + "version": "6.0.0", + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "acorn-node": { + "version": "1.8.2", + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0" + }, + "ajv": { + "version": "6.12.6", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "2.1.1" + }, + "ansi-styles": { + "version": "3.2.1", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "argparse": { + "version": "1.0.10", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0" + }, + "arr-flatten": { + "version": "1.1.0" + }, + "arr-union": { + "version": "3.1.0" + }, + "array-unique": { + "version": "0.3.2" + }, + "asn1": { + "version": "0.2.4", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0" + } + } + }, + "assert": { + "version": "1.5.0", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1" + }, + "util": { + "version": "0.10.3", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0" + }, + "assign-symbols": { + "version": "1.0.0" + }, + "async-each": { + "version": "1.0.3" + }, + "asynckit": { + "version": "0.4.0" + }, + "at-least-node": { + "version": "1.0.0" + }, + "atob": { + "version": "2.1.2" + }, + "autoprefixer": { + "version": "9.8.6", + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "aws-sign2": { + "version": "0.7.0" + }, + "aws4": { + "version": "1.11.0" + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.0", + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.0", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.0", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.0", + "core-js-compat": "^3.9.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.2.0", + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.0" + } + }, + "babelify": { + "version": "10.0.0" + }, + "balanced-match": { + "version": "1.0.2" + }, + "base": { + "version": "0.11.2", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "base64-js": { + "version": "1.5.1" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "1.13.1" + }, + "bluebird": { + "version": "3.7.2" + }, + "bn.js": { + "version": "5.2.0" + }, + "brace-expansion": { + "version": "1.1.11", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + } + }, + "brorand": { + "version": "1.1.0" + }, + "browser-fs-access": { + "version": "0.20.1" + }, + "browser-pack": { + "version": "6.1.0", + "requires": { + "JSONStream": "^1.0.3", + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + } + }, + "browser-process-hrtime": { + "version": "0.1.3" + }, + "browser-resolve": { + "version": "2.0.0", + "requires": { + "resolve": "^1.17.0" + } + }, + "browserify": { + "version": "16.5.2", + "requires": { + "JSONStream": "^1.0.3", + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^2.0.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.2.3", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "5.2.1", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.0", + "requires": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "browserify-sign": { + "version": "4.2.1", + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.16.6", + "requires": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, + "buffer": { + "version": "4.9.2", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1" + }, + "buffer-xor": { + "version": "1.0.3" + }, + "builtin-status-codes": { + "version": "3.0.0" + }, + "cache-base": { + "version": "1.0.1", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cached-path-relative": { + "version": "1.0.2" + }, + "call-bind": { + "version": "1.0.2", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caniuse-lite": { + "version": "1.0.30001271" + }, + "caseless": { + "version": "0.12.0" + }, + "catharsis": { + "version": "0.9.0", + "requires": { + "lodash": "^4.17.15" + } + }, + "chalk": { + "version": "2.4.2", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "2.1.8", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0" + } + } + }, + "cipher-base": { + "version": "1.0.4", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "circular-json": { + "version": "0.3.3" + }, + "class-utils": { + "version": "0.3.6", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0" + } + } + } + } + }, + "classlist": { + "version": "1.2.20180112" + }, + "collection-visit": { + "version": "1.0.0", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3" + }, + "colorette": { + "version": "1.2.2" + }, + "combine-source-map": { + "version": "0.8.0", + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3" + } + } + }, + "combined-stream": { + "version": "1.0.8", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.3" + }, + "common-shakeify": { + "version": "0.6.2", + "requires": { + "@goto-bus-stop/common-shake": "^2.2.0", + "convert-source-map": "^1.5.1", + "through2": "^2.0.3", + "transform-ast": "^2.4.3", + "wrap-comment": "^1.0.1" + } + }, + "component-emitter": { + "version": "1.3.0" + }, + "concat-map": { + "version": "0.0.1" + }, + "concat-stream": { + "version": "1.6.2", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "console-browserify": { + "version": "1.2.0" + }, + "constants-browserify": { + "version": "1.0.0" + }, + "convert-source-map": { + "version": "1.7.0", + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2" + } + } + }, + "copy-descriptor": { + "version": "0.1.1" + }, + "core-js": { + "version": "3.12.1" + }, + "core-js-compat": { + "version": "3.12.1", + "requires": { + "browserslist": "^4.16.6", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0" + } + } + }, + "core-js-pure": { + "version": "3.12.1" + }, + "core-util-is": { + "version": "1.0.2" + }, + "create-ecdh": { + "version": "4.0.4", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0" + } + } + }, + "create-hash": { + "version": "1.2.0", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0" + }, + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-tree": { + "version": "1.1.3", + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1" + } + } + }, + "cssdb": { + "version": "4.4.0" + }, + "cssesc": { + "version": "3.0.0" + }, + "csso": { + "version": "4.2.0", + "requires": { + "css-tree": "^1.1.2" + } + }, + "cssom": { + "version": "0.4.4" + }, + "cssstyle": { + "version": "2.3.0", + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8" + } + } + }, + "d": { + "version": "1.0.1", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "dash-ast": { + "version": "1.0.0" + }, + "dashdash": { + "version": "1.14.1", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "2.0.0", + "requires": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + } + }, + "debounce": { + "version": "1.2.1" + }, + "debug": { + "version": "4.3.1", + "requires": { + "ms": "2.1.2" + } + }, + "decimal.js": { + "version": "10.2.1" + }, + "decode-uri-component": { + "version": "0.2.0" + }, + "deep-is": { + "version": "0.1.3" + }, + "define-properties": { + "version": "1.1.3", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "defined": { + "version": "1.0.0" + }, + "delayed-stream": { + "version": "1.0.0" + }, + "deps-sort": { + "version": "2.0.1", + "requires": { + "JSONStream": "^1.0.3", + "shasum-object": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + } + }, + "des.js": { + "version": "1.0.1", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detective": { + "version": "5.2.0", + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "diffie-hellman": { + "version": "5.0.3", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0" + } + } + }, + "dom-serializer": { + "version": "1.3.2", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "dependencies": { + "domhandler": { + "version": "4.2.0", + "requires": { + "domelementtype": "^2.2.0" + } + } + } + }, + "domain-browser": { + "version": "1.2.0" + }, + "domelementtype": { + "version": "2.2.0" + }, + "domexception": { + "version": "2.0.1", + "requires": { + "webidl-conversions": "^5.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "5.0.0" + } + } + }, + "domhandler": { + "version": "3.3.0", + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.6.0", + "requires": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "dependencies": { + "domhandler": { + "version": "4.2.0", + "requires": { + "domelementtype": "^2.2.0" + } + } + } + }, + "duplexer2": { + "version": "0.1.4", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "electron-to-chromium": { + "version": "1.3.730" + }, + "elliptic": { + "version": "6.5.4", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0" + } + } + }, + "entities": { + "version": "2.0.3" + }, + "es5-ext": { + "version": "0.10.53", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escalade": { + "version": "3.1.1" + }, + "escape-string-regexp": { + "version": "1.0.5" + }, + "escodegen": { + "version": "2.0.0", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0" + }, + "source-map": { + "version": "0.6.1", + "optional": true + } + } + }, + "escope": { + "version": "3.6.0", + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1" + }, + "esrecurse": { + "version": "4.3.0", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0" + } + } + }, + "estraverse": { + "version": "4.3.0" + }, + "esutils": { + "version": "2.0.3" + }, + "event-emitter": { + "version": "0.3.5", + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "events": { + "version": "2.1.0" + }, + "evp_bytestokey": { + "version": "1.0.3", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0" + } + } + }, + "ms": { + "version": "2.0.0" + } + } + }, + "ext": { + "version": "1.4.0", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.5.0" + } + } + }, + "extend": { + "version": "3.0.2" + }, + "extend-shallow": { + "version": "2.0.1", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "2.0.4", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "extsprintf": { + "version": "1.3.0" + }, + "fast-deep-equal": { + "version": "3.1.3" + }, + "fast-json-stable-stringify": { + "version": "2.1.0" + }, + "fast-levenshtein": { + "version": "2.0.6" + }, + "fast-safe-stringify": { + "version": "2.0.7" + }, + "file-entry-cache": { + "version": "1.3.1", + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "file-saver": { + "version": "2.0.5" + }, + "fill-range": { + "version": "4.0.0", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + } + }, + "flat-cache": { + "version": "1.3.4", + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "flatten": { + "version": "1.0.3" + }, + "for-in": { + "version": "1.0.2" + }, + "forever-agent": { + "version": "0.6.1" + }, + "form-data": { + "version": "2.3.3", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs-extra": { + "version": "9.1.0", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0" + }, + "function-bind": { + "version": "1.1.1" + }, + "gensync": { + "version": "1.0.0-beta.2" + }, + "get-assigned-identifiers": { + "version": "1.2.0" + }, + "get-intrinsic": { + "version": "1.1.1", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-value": { + "version": "2.0.6" + }, + "getpass": { + "version": "0.1.7", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.7", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "globals": { + "version": "11.12.0" + }, + "graceful-fs": { + "version": "4.2.6" + }, + "har-schema": { + "version": "2.0.0" + }, + "har-validator": { + "version": "5.1.5", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0" + }, + "has-symbols": { + "version": "1.0.2" + }, + "has-value": { + "version": "1.0.0", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "hash-string": { + "version": "1.0.0" + }, + "hash.js": { + "version": "1.1.7", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "html-encoding-sniffer": { + "version": "2.0.1", + "requires": { + "whatwg-encoding": "^1.0.5" + } + }, + "htmlescape": { + "version": "1.1.1" + }, + "htmlparser2": { + "version": "4.1.0", + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0" + }, + "iconv-lite": { + "version": "0.4.24", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1" + }, + "immediate": { + "version": "3.0.6" + }, + "indexes-of": { + "version": "1.0.1" + }, + "inflight": { + "version": "1.0.6", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4" + }, + "ink-docstrap": { + "version": "1.3.2", + "requires": { + "moment": "^2.14.1", + "sanitize-html": "^1.13.0" + } + }, + "inline-source-map": { + "version": "0.6.2", + "requires": { + "source-map": "~0.5.3" + } + }, + "insert-module-globals": { + "version": "7.2.1", + "requires": { + "JSONStream": "^1.0.3", + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3" + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6" + }, + "is-core-module": { + "version": "2.4.0", + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "requires": { + "kind-of": "^6.0.0" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3" + } + } + }, + "is-descriptor": { + "version": "1.0.2", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.3" + } + } + }, + "is-extendable": { + "version": "0.1.1" + }, + "is-extglob": { + "version": "2.1.1" + }, + "is-glob": { + "version": "4.0.1", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-potential-custom-element-name": { + "version": "1.0.1" + }, + "is-typedarray": { + "version": "1.0.0" + }, + "is-windows": { + "version": "1.0.2" + }, + "isarray": { + "version": "1.0.0" + }, + "isobject": { + "version": "3.0.1" + }, + "isstream": { + "version": "0.1.2" + }, + "jq-trim": { + "version": "0.1.2" + }, + "js-tokens": { + "version": "4.0.0" + }, + "js2xmlparser": { + "version": "4.0.1", + "requires": { + "xmlcreate": "^2.0.3" + } + }, + "jsbn": { + "version": "0.1.1" + }, + "jscolor": { + "version": "2.1.1" + }, + "jsdoc": { + "version": "3.6.7", + "requires": { + "@babel/parser": "^7.9.4", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.1", + "klaw": "^3.0.0", + "markdown-it": "^10.0.0", + "markdown-it-anchor": "^5.2.7", + "marked": "^2.0.3", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "taffydb": "2.6.2", + "underscore": "~1.13.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0" + } + } + }, + "jsdom": { + "version": "16.5.3", + "requires": { + "abab": "^2.0.5", + "acorn": "^8.1.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "html-encoding-sniffer": "^2.0.1", + "is-potential-custom-element-name": "^1.0.0", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "request": "^2.88.2", + "request-promise-native": "^1.0.9", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.4", + "xml-name-validator": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "8.2.4" + } + } + }, + "jsdom-global": { + "version": "3.0.2" + }, + "jsesc": { + "version": "2.5.2" + }, + "json-schema": { + "version": "0.2.3" + }, + "json-schema-traverse": { + "version": "0.4.1" + }, + "json-stable-stringify": { + "version": "0.0.1", + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1" + }, + "json5": { + "version": "2.2.0", + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonfile": { + "version": "6.1.0", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonify": { + "version": "0.0.0" + }, + "jsonparse": { + "version": "1.3.1" + }, + "jsprim": { + "version": "1.4.1", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jszip": { + "version": "3.6.0", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "keyboardevent-key-polyfill": { + "version": "1.1.0" + }, + "kind-of": { + "version": "3.2.2", + "requires": { + "is-buffer": "^1.1.5" + } + }, + "klaw": { + "version": "3.0.0", + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "labeled-stream-splicer": { + "version": "2.0.2", + "requires": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, + "leaflet": { + "version": "1.7.1" + }, + "levn": { + "version": "0.3.0", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lie": { + "version": "3.3.0", + "requires": { + "immediate": "~3.0.5" + } + }, + "linkify-it": { + "version": "2.2.0", + "requires": { + "uc.micro": "^1.0.1" + } + }, + "lodash": { + "version": "4.17.21" + }, + "lodash.debounce": { + "version": "4.0.8" + }, + "lodash.memoize": { + "version": "3.0.4" + }, + "magic-string": { + "version": "0.23.2", + "requires": { + "sourcemap-codec": "^1.4.1" + } + }, + "map-cache": { + "version": "0.2.2" + }, + "map-visit": { + "version": "1.0.0", + "requires": { + "object-visit": "^1.0.0" + } + }, + "markdown-it": { + "version": "10.0.0", + "requires": { + "argparse": "^1.0.7", + "entities": "~2.0.0", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.3.0" + }, + "marked": { + "version": "2.0.3" + }, + "md5.js": { + "version": "1.3.5", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.14" + }, + "mdurl": { + "version": "1.0.1" + }, + "merge-source-map": { + "version": "1.0.4", + "requires": { + "source-map": "^0.5.6" + } + }, + "micromatch": { + "version": "3.1.10", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3" + } + } + }, + "miller-rabin": { + "version": "4.0.1", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0" + } + } + }, + "mime-db": { + "version": "1.47.0" + }, + "mime-types": { + "version": "2.1.30", + "requires": { + "mime-db": "1.47.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1" + }, + "minimatch": { + "version": "3.0.4", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5" + }, + "mixin-deep": { + "version": "1.3.2", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "1.0.4" + }, + "mkdirp-classic": { + "version": "0.5.3" + }, + "module-deps": { + "version": "6.2.3", + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^2.0.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "moment": { + "version": "2.29.1" + }, + "ms": { + "version": "2.1.2" + }, + "mutexify": { + "version": "1.3.1" + }, + "nanobench": { + "version": "2.1.1", + "requires": { + "browser-process-hrtime": "^0.1.2", + "chalk": "^1.1.3", + "mutexify": "^1.1.0", + "pretty-hrtime": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1" + }, + "chalk": { + "version": "1.1.3", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0" + } + } + }, + "nanoid": { + "version": "3.1.23" + }, + "nanomatch": { + "version": "1.2.13", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "kind-of": { + "version": "6.0.3" + } + } + }, + "next-tick": { + "version": "1.0.0" + }, + "node-releases": { + "version": "1.1.72" + }, + "normalize-path": { + "version": "2.1.1", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-range": { + "version": "0.1.2" + }, + "num2fraction": { + "version": "1.2.2" + }, + "nwsapi": { + "version": "2.2.0" + }, + "oauth-sign": { + "version": "0.9.0" + }, + "object-assign": { + "version": "4.1.1" + }, + "object-copy": { + "version": "0.1.0", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0" + } + } + } + } + }, + "object-defineproperty-ie": { + "version": "3.1.0" + }, + "object-keys": { + "version": "1.1.1" + }, + "object-visit": { + "version": "1.0.1", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.8.3", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-browserify": { + "version": "0.3.0" + }, + "outpipe": { + "version": "1.1.1", + "requires": { + "shell-quote": "^1.4.2" + } + }, + "pako": { + "version": "1.0.11" + }, + "parents": { + "version": "1.0.1", + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.6", + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-srcset": { + "version": "1.0.2" + }, + "parse5": { + "version": "6.0.1" + }, + "pascalcase": { + "version": "0.1.1" + }, + "path-browserify": { + "version": "0.0.1" + }, + "path-dirname": { + "version": "1.0.2" + }, + "path-is-absolute": { + "version": "1.0.1" + }, + "path-parse": { + "version": "1.0.6" + }, + "path-platform": { + "version": "0.11.15" + }, + "pbkdf2": { + "version": "3.1.2", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "performance-now": { + "version": "2.1.0" + }, + "persistify": { + "version": "2.0.1", + "requires": { + "file-entry-cache": "^1.2.0", + "flat-cache": "^1.2.1", + "hash-string": "^1.0.0", + "jq-trim": "^0.1.1", + "outpipe": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1" + }, + "postcss": { + "version": "8.3.6", + "requires": { + "colorette": "^1.2.2", + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" + } + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.2", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^6.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-css-variables": { + "version": "0.17.0", + "requires": { + "balanced-match": "^1.0.0", + "escape-string-regexp": "^1.0.3", + "extend": "^3.0.1", + "postcss": "^6.0.8" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1" + } + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "requires": { + "postcss": "^7.0.14" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-properties": { + "version": "8.0.11", + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0" + }, + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0" + }, + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-env-function": { + "version": "2.0.2", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-font-variant": { + "version": "4.0.1", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-initial": { + "version": "3.0.4", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-logical": { + "version": "3.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-nesting": { + "version": "7.0.1", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-page-break": { + "version": "2.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-place": { + "version": "4.0.1", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-prefix-selector": { + "version": "1.13.0", + "requires": { + "postcss": "^8.3.6" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0" + }, + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-selector-parser": { + "version": "5.0.0", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "requires": { + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-not": { + "version": "4.0.1", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "6.0.6", + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0" + }, + "postcss-values-parser": { + "version": "2.0.1", + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.1.2" + }, + "pretty-hrtime": { + "version": "1.0.3" + }, + "process": { + "version": "0.11.10" + }, + "process-nextick-args": { + "version": "2.0.1" + }, + "psl": { + "version": "1.8.0" + }, + "public-encrypt": { + "version": "4.0.3", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0" + } + } + }, + "punycode": { + "version": "1.4.1" + }, + "qs": { + "version": "6.5.2" + }, + "querystring": { + "version": "0.2.0" + }, + "querystring-es3": { + "version": "0.2.1" + }, + "randombytes": { + "version": "2.1.0", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "read-only-stream": { + "version": "2.0.0", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2" + }, + "string_decoder": { + "version": "1.1.1", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "readdirp": { + "version": "2.2.1", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "regenerate": { + "version": "1.4.2" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7" + }, + "regenerator-transform": { + "version": "0.14.5", + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "regexpu-core": { + "version": "4.7.1", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2" + }, + "regjsparser": { + "version": "0.6.9", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0" + } + } + }, + "remixicon": { + "version": "2.5.0" + }, + "remove-trailing-separator": { + "version": "1.1.0" + }, + "repeat-element": { + "version": "1.1.4" + }, + "repeat-string": { + "version": "1.6.1" + }, + "request": { + "version": "2.88.2", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "2.1.1" + }, + "tough-cookie": { + "version": "2.5.0", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.4", + "requires": { + "lodash": "^4.17.19" + } + }, + "request-promise-native": { + "version": "1.0.9", + "requires": { + "request-promise-core": "1.1.4", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "punycode": { + "version": "2.1.1" + }, + "tough-cookie": { + "version": "2.5.0", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "requizzle": { + "version": "0.2.3", + "requires": { + "lodash": "^4.17.14" + } + }, + "resolve": { + "version": "1.20.0", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-url": { + "version": "0.2.1" + }, + "ret": { + "version": "0.1.15" + }, + "rimraf": { + "version": "2.6.3", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "safe-buffer": { + "version": "5.2.1" + }, + "safe-regex": { + "version": "1.1.0", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2" + }, + "sanitize-html": { + "version": "1.27.5", + "requires": { + "htmlparser2": "^4.1.0", + "lodash": "^4.17.15", + "parse-srcset": "^1.0.2", + "postcss": "^7.0.27" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1" + }, + "supports-color": { + "version": "6.1.0", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "saxes": { + "version": "5.0.1", + "requires": { + "xmlchars": "^2.2.0" + } + }, + "semver": { + "version": "6.3.0" + }, + "set-immediate-shim": { + "version": "1.0.1" + }, + "set-value": { + "version": "2.0.1", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + } + }, + "sha.js": { + "version": "2.4.11", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shasum": { + "version": "1.0.2", + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shasum-object": { + "version": "1.0.0", + "requires": { + "fast-safe-stringify": "^2.0.7" + } + }, + "shell-quote": { + "version": "1.7.2" + }, + "simple-concat": { + "version": "1.0.1" + }, + "snapdragon": { + "version": "0.8.2", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0" + } + } + }, + "ms": { + "version": "2.0.0" + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "requires": { + "kind-of": "^3.2.0" + } + }, + "sortablejs": { + "version": "1.10.2" + }, + "source-map": { + "version": "0.5.7" + }, + "source-map-js": { + "version": "0.6.2" + }, + "source-map-resolve": { + "version": "0.5.3", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1" + } + } + }, + "source-map-url": { + "version": "0.4.1" + }, + "sourcemap-codec": { + "version": "1.4.8" + }, + "split-string": { + "version": "3.1.0", + "requires": { + "extend-shallow": "^3.0.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3" + }, + "sshpk": { + "version": "1.16.1", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "static-extend": { + "version": "0.1.2", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0" + } + } + } + } + }, + "stealthy-require": { + "version": "1.1.1" + }, + "stream-browserify": { + "version": "2.0.2", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "3.2.0", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "stream-splicer": { + "version": "2.0.1", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.3.0", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1" + }, + "subarg": { + "version": "1.0.0", + "requires": { + "minimist": "^1.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4" + }, + "syntax-error": { + "version": "1.4.0", + "requires": { + "acorn-node": "^1.2.0" + } + }, + "taffydb": { + "version": "2.6.2" + }, + "terser": { + "version": "3.17.0", + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + }, + "dependencies": { + "source-map": { + "version": "0.6.1" + } + } + }, + "through": { + "version": "2.3.8" + }, + "through2": { + "version": "2.0.5", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "time-input-polyfill": { + "version": "1.0.10" + }, + "timers-browserify": { + "version": "1.4.2", + "requires": { + "process": "~0.11.0" + } + }, + "to-fast-properties": { + "version": "2.0.0" + }, + "to-object-path": { + "version": "0.3.0", + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "4.0.0", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + }, + "dependencies": { + "punycode": { + "version": "2.1.1" + }, + "universalify": { + "version": "0.1.2" + } + } + }, + "tr46": { + "version": "2.0.2", + "requires": { + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1" + } + } + }, + "transform-ast": { + "version": "2.4.4", + "requires": { + "acorn-node": "^1.3.0", + "convert-source-map": "^1.5.1", + "dash-ast": "^1.0.0", + "is-buffer": "^2.0.0", + "magic-string": "^0.23.2", + "merge-source-map": "1.0.4", + "nanobench": "^2.1.1" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.5" + } + } + }, + "tty-browserify": { + "version": "0.0.1" + }, + "tunnel-agent": { + "version": "0.6.0", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5" + }, + "type": { + "version": "1.2.0" + }, + "type-check": { + "version": "0.3.2", + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6" + }, + "uc.micro": { + "version": "1.0.6" + }, + "uglifyify": { + "version": "5.0.2", + "requires": { + "convert-source-map": "~1.1.0", + "minimatch": "^3.0.2", + "terser": "^3.7.5", + "through": "~2.3.4", + "xtend": "^4.0.1" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3" + } + } + }, + "umd": { + "version": "3.0.3" + }, + "undeclared-identifiers": { + "version": "1.1.3", + "requires": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "underscore": { + "version": "1.13.1" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0" + }, + "union-value": { + "version": "1.0.1", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1" + }, + "universalify": { + "version": "2.0.0" + }, + "unset-value": { + "version": "1.0.0", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4" + } + } + }, + "upath": { + "version": "1.2.0" + }, + "uri-js": { + "version": "4.4.1", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1" + } + } + }, + "urix": { + "version": "0.1.0" + }, + "url": { + "version": "0.11.0", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2" + } + } + }, + "use": { + "version": "3.1.1" + }, + "util": { + "version": "0.10.4", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3" + } + } + }, + "util-deprecate": { + "version": "1.0.2" + }, + "uuid": { + "version": "3.4.0" + }, + "verror": { + "version": "1.10.0", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vm-browserify": { + "version": "1.1.2" + }, + "w3c-hr-time": { + "version": "1.0.2", + "requires": { + "browser-process-hrtime": "^1.0.0" + }, + "dependencies": { + "browser-process-hrtime": { + "version": "1.0.0" + } + } + }, + "w3c-xmlserializer": { + "version": "2.0.0", + "requires": { + "xml-name-validator": "^3.0.0" + } + }, + "watchify": { + "version": "3.11.1", + "requires": { + "anymatch": "^2.0.0", + "browserify": "^16.1.0", + "chokidar": "^2.1.1", + "defined": "^1.0.0", + "outpipe": "^1.1.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "webidl-conversions": { + "version": "6.1.0" + }, + "whatwg-encoding": { + "version": "1.0.5", + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0" + }, + "whatwg-url": { + "version": "8.5.0", + "requires": { + "lodash": "^4.7.0", + "tr46": "^2.0.2", + "webidl-conversions": "^6.1.0" + } + }, + "word-wrap": { + "version": "1.2.3" + }, + "wrap-comment": { + "version": "1.0.1" + }, + "wrappy": { + "version": "1.0.2" + }, + "write": { + "version": "0.2.1", + "requires": { + "mkdirp": "^0.5.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "ws": { + "version": "7.4.5" + }, + "xml-name-validator": { + "version": "3.0.0" + }, + "xmlchars": { + "version": "2.2.0" + }, + "xmlcreate": { + "version": "2.0.3" + }, + "xtend": { + "version": "4.0.2" } } }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, - "jszip": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.6.1.tgz", - "integrity": "sha1-uI86ey5noqBIFSmCx6N1bZxIKPA=", - "dev": true, - "requires": { - "pako": "~1.0.2" - } - }, - "junk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", - "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", - "dev": true - }, - "keyboardevent-key-polyfill": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/keyboardevent-key-polyfill/-/keyboardevent-key-polyfill-1.1.0.tgz", - "integrity": "sha1-ijGdjkWhMXL8pWKGNy+QwdTHAUw=", - "dev": true - }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "leaflet": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.7.1.tgz", - "integrity": "sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw==", - "dev": true - }, - "leaflet-advanced-layer-system": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/leaflet-advanced-layer-system/-/leaflet-advanced-layer-system-2.0.0.tgz", - "integrity": "sha512-IaZ5kpCNH6aEtN5Cmf5F6QQF/XBBcj+rL3bGYKxUgyoQd9ZSErANL55SIAZzzvQ7jwq5Qs+uw8KNx9Vps5LwTw==", - "dev": true - }, "leaflet-draw": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/leaflet-draw/-/leaflet-draw-1.0.4.tgz", @@ -4964,16 +9927,6 @@ "object-visit": "^1.0.0" } }, - "martinez-polygon-clipping": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/martinez-polygon-clipping/-/martinez-polygon-clipping-0.4.3.tgz", - "integrity": "sha512-3ZNS0ksKhWTLsmCUkNf+/UimndZ5U2cVOS0I+IjiwF+M23E77TmeOZSmbRJbfCoQUog/vcQ42s3DXrhgOhgPqw==", - "dev": true, - "requires": { - "splaytree": "^0.1.4", - "tinyqueue": "^1.2.0" - } - }, "matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -5805,6 +10758,12 @@ "xmldom": "0.1.x" } }, + "polybooljs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/polybooljs/-/polybooljs-1.2.0.tgz", + "integrity": "sha1-tDkMLgedTCYtOyUExiiNlbp6R1g=", + "dev": true + }, "polygon-clipping": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz", @@ -9046,12 +14005,6 @@ "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", "dev": true }, - "splaytree": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-0.1.4.tgz", - "integrity": "sha512-D50hKrjZgBzqD3FT2Ek53f2dcDLAQT8SSGrzj3vidNH5ISRgceeGVJ2dQIthKOuayqFXfFjXheHNo4bbt9LhRQ==", - "dev": true - }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -9601,12 +14554,6 @@ "esm": "^3.2.25" } }, - "tinyqueue": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-1.2.3.tgz", - "integrity": "sha512-Qz9RgWuO9l8lT+Y9xvbzhPT2efIUIFd69N7eF7tJ9lnQl0iLj1M7peK7IoUGZL9DJHw9XftqLreccfxcQgYLxA==", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index 138ccfb2..25e42afb 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "@turf/bbox-polygon": "^6.4.0", "@turf/helpers": "^6.1.4", "@turf/intersect": "^6.4.0", - "@turf/union": "^6.0.3", "babelify": "^10.0.0", "browserify": "^16.1.1", "buffer": "^4.9.2", @@ -76,14 +75,15 @@ "fastestsmallesttextencoderdecoder": "^1.0.22", "fs-extra": "^9.0.1", "geotiff": "^1.0.4", - "geotiff-geokeys-to-proj4": "^2021.7.19-beta", + "geotiff-geokeys-to-proj4": "^2021.10.31", "http-server": "^0.12.3", "keyboardevent-key-polyfill": "^1.1.0", "leaflet": "^1.7.1", - "leaflet-advanced-layer-system": "^2.0.0", + "leaflet-advanced-layer-system": "^2.1.0", "leaflet-draw": "^1.0.4", "leaflet.coordinates": "~0.1.5", "persistify": "^2.0.1", + "polybooljs": "^1.2.0", "postcss": "^8.2.4", "postcss-preset-env": "^6.7.0", "proj4": "^2.7.4", From e4790713cab91e5411df480cf2ef32602c42e47f Mon Sep 17 00:00:00 2001 From: matafokka Date: Thu, 18 Nov 2021 20:10:43 +0300 Subject: [PATCH 3/7] Hopefully, finished exports Looks like exports are working fine Removed debugging data --- MathTools.js | 5 -- SynthBase/Hull.js | 4 +- SynthBase/SynthBaseLayer.js | 27 +++++++- SynthBase/toGeoJSON.js | 127 ++++++------------------------------ SynthGridLayer/misc.js | 4 -- main.js | 13 ---- 6 files changed, 46 insertions(+), 134 deletions(-) diff --git a/MathTools.js b/MathTools.js index 6858aaf6..d453d725 100644 --- a/MathTools.js +++ b/MathTools.js @@ -133,11 +133,6 @@ class MathTools { if (notOnVertex) intersections++; - - if (MathTools.arePointsEqual(point, [-0.25, 51.5])) { - console.log(intersection) - map.addLayer(L.marker([intersection[0][1], intersection[0][0]])) - } } //console.log(point, intersections); return (intersections % 2 !== 0); diff --git a/SynthBase/Hull.js b/SynthBase/Hull.js index 88880868..6ef70d67 100644 --- a/SynthBase/Hull.js +++ b/SynthBase/Hull.js @@ -290,7 +290,7 @@ L.ALS.SynthBaseLayer.prototype.connectHull = function () { /** * Creates a cycle out of hull connection of given path * @param path {Object} Path to create cycle of - * @return {LatLng[]} Cycle + * @return {LatLng[][]} Cycle */ L.ALS.SynthBaseLayer.prototype.hullToCycles = function (path) { // The idea is to start with the first connection, find the starting point in it and for each connection @@ -358,7 +358,7 @@ L.ALS.SynthBaseLayer.prototype.hullToCycles = function (path) { cycle = [hullP2, ...afterAirport, ...beforeAirport, hullP2]; this.map.addLayer(L.polyline(cycle, {weight: 10, opacity: 0.5})); - return cycle; + return [cycle]; } L.ALS.SynthBaseLayer.prototype.getOrderedPathFromHull = function (prevPoint, connection, copyTo, i = 0) { diff --git a/SynthBase/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js index 3cff3676..02614b15 100644 --- a/SynthBase/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -255,7 +255,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot .addWidgets( button, new L.ALS.Widgets.ValueLabel("pathLength", "pathLength", "m").setFormatNumbers(true).setNumberOfDigitsAfterPoint(0).setValue(length), - new L.ALS.Widgets.ValueLabel("flightTime", "flightTime", "h").setFormatNumbers(true).setValue(this.getFlightTime(length)), + new L.ALS.Widgets.ValueLabel("flightTime", "flightTime", "h:mm").setValue(this.getFlightTime(length)), ); this.addWidgets(widget); @@ -264,8 +264,29 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot return widget; }, - getFlightTime: function (length) { - return parseFloat((length / this.aircraftSpeedInMetersPerSecond / 3600).toFixed(2)); + /** + * Calculates flight time for given path length + * @param length {number} Path length + * @param formatAsTimeSpan {boolean} If true, will format time as time span + * @return {string|number} Flight time in hours + */ + getFlightTime: function (length, formatAsTimeSpan = true) { + let time = length / this.aircraftSpeedInMetersPerSecond / 3600; + + if (!formatAsTimeSpan) + return time; + + let hours = Math.floor(time), minutes = Math.round((time % 1) * 60).toString(); + + if (minutes === "60") { + hours++; + minutes = "00"; + } + + if (minutes.length === 1) + minutes = "0" + minutes; + + return hours + ":" + minutes; }, /** diff --git a/SynthBase/toGeoJSON.js b/SynthBase/toGeoJSON.js index e6751113..5d024cd3 100644 --- a/SynthBase/toGeoJSON.js +++ b/SynthBase/toGeoJSON.js @@ -2,123 +2,36 @@ const MathTools = require("../MathTools.js"); const turfHelpers = require("@turf/helpers"); const geojsonMerge = require("@mapbox/geojson-merge"); // Using this since turfHelpers.featureCollection() discards previously defined properties. -L.ALS.SynthBaseLayer.prototype.toGeoJSON = function (path1Metadata, path2Metadata) { - // We need to merge paths and connections into one line. The task is to find cycles. +/** + * Exports paths to GeoJSON. Should be overridden by child classes. + * @param path1Metadata {Object} Metadata for path 1. Will be copied to route in this path. + * @param path2Metadata {Object} Metadata for path 2. Will be copied to route in this path. Leave it as undefined if your layer has only one path. + * @return {Object} GeoJSON with both paths. + */ +L.ALS.SynthBaseLayer.prototype.toGeoJSON = function (path1Metadata, path2Metadata = undefined) { + let fn = this.getWidgetById("connectionMethod").getValue() === "allIntoOne" ? "hullToCycles" : "onePerFlightToCycles", + pathNumber = 1, toMerge = []; - /*let pushLatLngs = (latLngs, pushTo, isPath = false) => { - let toPush = []; - for (let p of latLngs) - toPush.push([p.lng, p.lat]); - toPush.isPath = isPath; - pushTo.push(toPush); - } - - let airportPos = this._airportMarker.getLatLng(), airportPosArr = [airportPos.lng, airportPos.lat], cycles = [], pathNumber = 1; for (let path of this.paths) { - let groups = [], connectionLayers = path.connectionsGroup.getLayers(), pathLayers = path.pathGroup.getLayers(); - - // Copy connections. Connection to the airport contains 3 points which needs workaround. Also convert LatLng to array of lng-lat. - for (let layer of connectionLayers) { - if (layer === path.previouslyRemovedConnection) - continue; - - let latLngs = layer.getLatLngs(); - if (latLngs.length === 2) { - pushLatLngs(latLngs, groups); - continue; - } - pushLatLngs([latLngs[0], latLngs[1]], groups); - pushLatLngs([latLngs[1], latLngs[2]], groups); - } - - // Do the same for the paths - for (let layer of pathLayers) - pushLatLngs(layer.getLatLngs(), groups, true); - - let groupsLength = groups.length, layers = {}; - - for (let i = 0; i < groups.length; i++) - layers[i] = groups[i]; - - while (groupsLength > 0) { - let cycle = [], startPoint, prevPoint; - - // Find starting line - for (let i in layers) { - let [p1, p2] = layers[i]; // It'll always be a connection, so we can pick just two points - - if (MathTools.arePointsEqual(p1, airportPosArr)) { - startPoint = p1; - prevPoint = p2; - } - else if (MathTools.arePointsEqual(p2, airportPosArr)) { - startPoint = p2; - prevPoint = p1; - } else - continue; + let cycles = this[fn](path), metadata = pathNumber === 1 ? path1Metadata : path2Metadata; - delete layers[i]; - groupsLength--; - cycle.push(startPoint, prevPoint); - break; - } + for (let cycle of cycles) { - // Keep finding and adding points to the cycle until we reach airport pos, i.e. until cycle closes - let isFinal = false, needPath = true; - while (!isFinal) { - for (let i in layers) { - let layer = layers[i], p1 = layer[0], p2 = layer[layer.length - 1], toAdd, - start = -1, end = layer.length, addition = 1; // To add points later + let lngLats = []; + for (let p of cycle) + lngLats.push([p.lng, p.lat]); - if ((needPath && !layer.isPath) || (!needPath && layer.isPath)) - continue; - - if (MathTools.arePointsEqual(p1, prevPoint)) - toAdd = p2; - else if (MathTools.arePointsEqual(p2, prevPoint)) { - toAdd = p1; - start = end; - end = -1; - addition = -1; - } - else - continue; - - needPath = !needPath; - delete layers[i]; - cycle.pop(); - groupsLength--; - prevPoint = toAdd; - isFinal = MathTools.arePointsEqual(toAdd, airportPosArr); - - // Add other points from paths - for (let j = start + addition; j !== end; j += addition) - cycle.push(layer[j]); - - break; - } - } - let cycleJson = turfHelpers.lineString(cycle), metadata = pathNumber === 1 ? path1Metadata : path2Metadata; + let cycleJson = turfHelpers.lineString(lngLats); for (let name in metadata) cycleJson.properties[name] = metadata[name]; cycleJson.properties.length = this.lineLengthUsingFlightHeight(cycle); - cycleJson.properties.flightTime = this.getFlightTime(cycleJson.properties.length); - cycles.push(cycleJson); - } - pathNumber++; - }*/ - - this.hullToCycles(this.paths[1]); - - /*let isHull = this.getWidgetById("connectionMethod").getValue() === "allIntoOne"; - for (let path of this.paths) { - if (isHull) { - console.log(this.hullToCycles(path)); - } else { + cycleJson.properties.flightTime = this.getFlightTime(cycleJson.properties.length, false); + toMerge.push(cycleJson); } - }*/ + pathNumber++; + } - return geojsonMerge.merge(cycles); + return geojsonMerge.merge(toMerge); } diff --git a/SynthGridLayer/misc.js b/SynthGridLayer/misc.js index d2590b2b..da38d1dd 100644 --- a/SynthGridLayer/misc.js +++ b/SynthGridLayer/misc.js @@ -175,9 +175,6 @@ L.ALS.SynthGridLayer.prototype._mergeSelectedPolygons = function () { for (let p1 of poly1) { shouldMerge = shouldMerge && MathTools.isPointInPolygon(p1, poly2); - if (poly1.name === "M-30-24-D-b") - console.log(p1, MathTools.isPointInPolygon(p1, poly2)); - if (!shouldMerge) break; @@ -259,6 +256,5 @@ L.ALS.SynthGridLayer.prototype._mergeSelectedPolygons = function () { this.mergedPolygons = newPolygons; } - console.log(this.mergedPolygons) return this.mergedPolygons; } \ No newline at end of file diff --git a/main.js b/main.js index b051e463..78a5f9a3 100644 --- a/main.js +++ b/main.js @@ -99,16 +99,3 @@ layerSystem.addBaseLayer(L.tileLayer(""), "Empty"); layerSystem.addLayerType(L.ALS.SynthLineLayer); layerSystem.addLayerType(L.ALS.SynthGridLayer); layerSystem.addLayerType(L.ALS.SynthShapefileLayer); - -const MathTools = require("./MathTools.js"); - -let poly = [ - [0.25, 51.583333333333336], [0.25, 51.33333333333333], - [-0.125, 51.33333333333333], [-0.125, 51.583333333333336], - [0.25, 51.583333333333336] -], point = [0, 51.416666666666664]; - -map.addLayer(L.marker(point)); -map.addLayer(L.polygon(poly)); - -console.log(MathTools.isPointInPolygon(point, poly)) \ No newline at end of file From 3368014b1240c05ee8f158be15afedd36e4d97e7 Mon Sep 17 00:00:00 2001 From: matafokka Date: Tue, 23 Nov 2021 20:43:07 +0300 Subject: [PATCH 4/7] Methodology changes 1. Moved calculations from converting meters to degrees using turf helpers to actually using meters. 1. Removed "mean" calculations for polygons. Now for each polygon all related parameters are calculated. 1. All related changes are made, for example, changed labels. --- SynthBase/Hull.js | 17 ++++--- SynthBase/SynthBaseLayer.js | 9 ++-- SynthBase/calculateParameters.js | 9 ++-- SynthBase/toGeoJSON.js | 2 +- SynthGridLayer/SynthGridLayer.js | 15 +------ SynthGridLayer/calculateParameters.js | 20 --------- SynthGridLayer/drawPaths.js | 65 +++++++++++++++------------ SynthGridLayer/misc.js | 14 ++++++ SynthGridLayer/polygons.js | 26 ++++++++--- SynthGridLayer/toGeoJSON.js | 14 ++---- locales/English.js | 12 ++--- locales/Russian.js | 16 +++---- 12 files changed, 110 insertions(+), 109 deletions(-) delete mode 100644 SynthGridLayer/calculateParameters.js diff --git a/SynthBase/Hull.js b/SynthBase/Hull.js index 6ef70d67..71578b8a 100644 --- a/SynthBase/Hull.js +++ b/SynthBase/Hull.js @@ -237,7 +237,7 @@ L.ALS.SynthBaseLayer.prototype.connectHullToAirport = function () { if (layer === hullConnection) continue; if (layer.pathLength === undefined) - layer.pathLength = this.lineLengthUsingFlightHeight(layer); + layer.pathLength = this.getLineLengthMeters(layer); totalLength += layer.pathLength; } @@ -246,7 +246,7 @@ L.ALS.SynthBaseLayer.prototype.connectHullToAirport = function () { continue; let [p1, p2] = layer.getLatLngs(), - len = totalLength - layer.pathLength + this.lineLengthUsingFlightHeight([p1, airportPos]) + this.lineLengthUsingFlightHeight([p2, airportPos]); + len = totalLength - layer.pathLength + this.getLineLengthMeters([p1, airportPos]) + this.getLineLengthMeters([p2, airportPos]); if (len < minLen) { minLen = len; @@ -383,15 +383,20 @@ L.ALS.SynthBaseLayer.prototype.getOrderedPathFromHull = function (prevPoint, con /** * Calculates length of a polyline - * @param line {L.Polyline || L.LatLng[]} Line to calculate length of + * @param line {L.Polyline | L.LatLng[] | number[][]} Line to calculate length of * @return {number} Line length */ L.ALS.SynthBaseLayer.prototype.getLineLength = function (line) { - const latLngs = line instanceof Array ? line : line.getLatLngs(); - let length = 0; + let latLngs = line instanceof Array ? line : line.getLatLngs(), length = 0, x = 0, y = 1; + + if (latLngs[0].lat !== undefined) { + x = "lng"; + y = "lat"; + } + for (let i = 0; i < latLngs.length - 1; i++) { const p1 = latLngs[i], p2 = latLngs[i + 1]; - length += Math.sqrt((p1.lng - p2.lng) ** 2 + (p1.lat - p2.lat) ** 2); + length += Math.sqrt((p1[x] - p2[x]) ** 2 + (p1[y] - p2[y]) ** 2); } return length; } diff --git a/SynthBase/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js index 02614b15..594b739a 100644 --- a/SynthBase/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -292,10 +292,11 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot /** * Calculates line length using haversine formula with account of flight height * @param line {L.Polyline|number[][]|LatLng[]} Line + * @param useFlightHeight {boolean} If true, will account flight height, i.e. line will float above the Earth * @return {number} Line length */ - lineLengthUsingFlightHeight: function (line) { - let r = 6371000 + this.flightHeight, points = line instanceof Array ? line : line.getLatLngs(), distance = 0, x, y; + getLineLengthMeters: function (line, useFlightHeight = true) { + let r = 6371000 + (useFlightHeight ? this.flightHeight : 0), points = line instanceof Array ? line : line.getLatLngs(), distance = 0, x, y; if (points.length === 0) return 0; @@ -330,7 +331,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot layer.getLatLngs()[1] = airportPos; layer.redraw(); - let length = layer.pathLength + this.lineLengthUsingFlightHeight(layer); + let length = layer.pathLength + this.getLineLengthMeters(layer); layer.pathWidget.getWidgetById("pathLength").setValue(length); layer.pathWidget.getWidgetById("flightTime").setValue(this.getFlightTime(length)); } @@ -352,7 +353,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot connectionsGroup.clearLayers(); for (let layer of layers) { - layer.pathLength = this.lineLengthUsingFlightHeight(layer); + layer.pathLength = this.getLineLengthMeters(layer); let latLngs = layer.getLatLngs(), connectionLine = L.polyline([latLngs[0], [0, 0], latLngs[latLngs.length - 1]], lineOptions); diff --git a/SynthBase/calculateParameters.js b/SynthBase/calculateParameters.js index c441d2f1..c28d0504 100644 --- a/SynthBase/calculateParameters.js +++ b/SynthBase/calculateParameters.js @@ -16,16 +16,15 @@ L.ALS.SynthBaseLayer.prototype.calculateParameters = function () { let pixelWidth = this["pixelWidth"] * 1e-6; let focalLength = this["focalLength"] * 0.001; - if (this.hasYOverlay) { - this.ly = this["cameraWidth"] * pixelWidth; // Image size in meters - this.Ly = this.ly * this["imageScale"] // Image width on the ground + this.ly = this["cameraWidth"] * pixelWidth; // Image size in meters + this.Ly = this.ly * this["imageScale"] // Image width on the ground + + if (this.hasYOverlay) this.By = this.Ly * (100 - this["overlayBetweenPaths"]) / 100; // Distance between paths - } this.lx = this["cameraHeight"] * pixelWidth; // Image height this.Lx = this.lx * this["imageScale"]; // Image height on the ground this.Bx = this.Lx * (100 - this["overlayBetweenImages"]) / 100; // Capture basis, distance between images' centers - this.basis = turfHelpers.lengthToDegrees(this.Bx, "meters"); this.GSI = pixelWidth * this["imageScale"]; this.IFOV = pixelWidth / focalLength * 1e6; diff --git a/SynthBase/toGeoJSON.js b/SynthBase/toGeoJSON.js index 5d024cd3..7880ff44 100644 --- a/SynthBase/toGeoJSON.js +++ b/SynthBase/toGeoJSON.js @@ -25,7 +25,7 @@ L.ALS.SynthBaseLayer.prototype.toGeoJSON = function (path1Metadata, path2Metadat for (let name in metadata) cycleJson.properties[name] = metadata[name]; - cycleJson.properties.length = this.lineLengthUsingFlightHeight(cycle); + cycleJson.properties.length = this.getLineLengthMeters(cycle); cycleJson.properties.flightTime = this.getFlightTime(cycleJson.properties.length, false); toMerge.push(cycleJson); diff --git a/SynthGridLayer/SynthGridLayer.js b/SynthGridLayer/SynthGridLayer.js index 71e50c68..195a85c3 100644 --- a/SynthGridLayer/SynthGridLayer.js +++ b/SynthGridLayer/SynthGridLayer.js @@ -108,21 +108,9 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa this.addWidgets( new L.ALS.Widgets.File("DEMFiles", DEMFilesLabel, this, "onDEMLoad").setMultiple(true), new L.ALS.Widgets.Divider("div3"), + new L.ALS.Widgets.ValueLabel("selectedArea", "selectedArea", "sq.m.").setNumberOfDigitsAfterPoint(0).setFormatNumbers(true), ); - let valueLabels = [ - new L.ALS.Widgets.ValueLabel("lngPathsCount", "lngPathsCount"), - new L.ALS.Widgets.ValueLabel("latPathsCount", "latPathsCount"), - new L.ALS.Widgets.ValueLabel("lngCellSizeInMeters", "lngCellSizeInMeters", "m"), - new L.ALS.Widgets.ValueLabel("latCellSizeInMeters", "latCellSizeInMeters", "m"), - new L.ALS.Widgets.ValueLabel("selectedArea", "selectedArea", "sq.m."), - ]; - - for (let widget of valueLabels) { - widget.setFormatNumbers(true); - this.addWidget(widget); - } - this.addBaseParametersOutputSection(); this.lngDistance = parseFloat(wizardResults["gridLngDistance"]); @@ -158,7 +146,6 @@ L.ALS.SynthGridLayer = L.ALS.SynthBaseLayer.extend( /** @lends L.ALS.SynthGridLa }); -require("./calculateParameters.js"); require("./DEM.js"); require("./drawPaths.js"); require("./misc.js"); diff --git a/SynthGridLayer/calculateParameters.js b/SynthGridLayer/calculateParameters.js deleted file mode 100644 index a7095882..00000000 --- a/SynthGridLayer/calculateParameters.js +++ /dev/null @@ -1,20 +0,0 @@ -const turfHelpers = require("@turf/helpers"); - -L.ALS.SynthGridLayer.prototype.calculateParameters = function () { - L.ALS.SynthBaseLayer.prototype.calculateParameters.call(this); - - let latLngs = ["lat", "lng"]; - - for (let name of latLngs) { - let sizeName = name + "CellSizeInMeters", countName = name + "PathsCount"; - - let cellSize = Math.round(turfHelpers.radiansToLength(turfHelpers.degreesToRadians(this[name + "Distance"]), "meters")); - let pathsCount = Math.ceil(cellSize / this.By); - this[sizeName] = cellSize; - this[countName] = pathsCount; - - this.getWidgetById(sizeName).setValue(this.toFixed(cellSize)); - this.getWidgetById(countName).setValue(pathsCount); - } - this._calculatePolygonParameters(); -} \ No newline at end of file diff --git a/SynthGridLayer/drawPaths.js b/SynthGridLayer/drawPaths.js index cabf6e1c..e95ff02f 100644 --- a/SynthGridLayer/drawPaths.js +++ b/SynthGridLayer/drawPaths.js @@ -15,8 +15,8 @@ L.ALS.SynthGridLayer.prototype._drawPaths = function () { // Validate parameters let errorLabel = this.getWidgetById("calculateParametersError"); - let parallelsPathsCount = this["lngPathsCount"]; - let meridiansPathsCount = this["latPathsCount"]; + let parallelsPathsCount = this["lngFakePathsCount"]; + let meridiansPathsCount = this["latFakePathsCount"]; if (parallelsPathsCount === undefined) { errorLabel.setValue("errorDistanceHasNotBeenCalculated"); @@ -49,24 +49,24 @@ L.ALS.SynthGridLayer.prototype._drawPaths = function () { */ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { - let pathName, nameForOutput, color, connectionsGroup, widgetId; + let pathName, nameForOutput, color, connectionsGroup, widgetId, extensionIndex; if (isParallels) { pathName = "pathsByParallels"; connectionsGroup = this.parallelsInternalConnections; nameForOutput = "lng"; color = this["color0"]; widgetId = "hidePathsByParallels"; + extensionIndex = 0; } else { pathName = "pathsByMeridians"; connectionsGroup = this.meridiansInternalConnections; nameForOutput = "lat"; color = this["color1"]; widgetId = "hidePathsByMeridians"; + extensionIndex = 1; } let pathGroup = this[pathName], pointsName = nameForOutput + "PointsGroup", - parallelsPathsCount = this["lngPathsCount"], - meridiansPathsCount = this["latPathsCount"], lineOptions = { color, weight: this.lineThicknessValue @@ -80,28 +80,30 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { let shouldHideNumbers = this.getWidgetById(widgetId).getValue() || this._doHidePathsNumbers; for (let polygon of this.mergedPolygons) { - let turfPolygon = turfHelpers.polygon([polygon]), // This function accepts array of arrays of coordinates. Simple polygons are just arrays of coordinates, so we gotta wrap it. + let turfPolygon = turfHelpers.polygon([polygon]), // This function accepts array of arrays of coordinates. Our polygons are just arrays of coordinates, so we gotta wrap it. [startLng, endLat, endLng, startLat] = bbox(turfPolygon), // Create bounding box around current polygon. We'll draw paths using bounding box and then clip it by current polygon swapPoints = false, // Should swap points on each new line // Calculate new distances between paths for current polygon lengthByLat = Math.abs(startLat - endLat), lengthByLng = Math.abs(endLng - startLng), - newParallelsPathsCount = parallelsPathsCount * Math.round(lengthByLat / this.latDistance), - newMeridiansPathsCount = meridiansPathsCount * Math.round(lengthByLng / this.lngDistance), - parallelsDistance = lengthByLat / newParallelsPathsCount, - meridiansDistance = lengthByLng / newMeridiansPathsCount, + parallelsDistance = lengthByLat * this.By / this.getLineLengthMeters([[startLng, startLat], [startLng, endLat]], false), + meridiansDistance = lengthByLng * this.By / this.getLineLengthMeters([[startLng, startLat], [endLng, startLat]], false), // Calculate correct capture basis in degrees. - latDistance = Math.abs(endLat - startLat), lngDistance = Math.abs(endLng - startLng), - latPointsCount = Math.round(latDistance / this.basis), - lngPointsCount = Math.round(lngDistance / this.basis), + bottomSide = [[startLng, endLat], [startLng + this.lngDistance, endLat]], + rightSide = [[endLng, startLat], [endLng, startLat - this.latDistance]], + parallelsPointsCount = Math.ceil(this.getLineLengthMeters(bottomSide, false) / this.Bx), + meridiansPointsCount = Math.ceil(this.getLineLengthMeters(rightSide, false) / this.Bx), - latBasis = latDistance / latPointsCount, lngBasis = lngDistance / lngPointsCount, + parallelsBasis = this.getLineLength(bottomSide) / parallelsPointsCount, meridiansBasis = this.getLineLength(rightSide) / meridiansPointsCount, + extendBy = isParallels ? parallelsBasis * 2 : -meridiansBasis * 2, lat = startLat, lng = startLng, turfPolygonCoordinates = turfPolygon.geometry.coordinates[0], // MathTools accepts coordinates of the polygon, not polygon itself - number = 1, connectionLine = L.polyline([], connLineOptions); + number = 1, connectionLine = L.polyline([], connLineOptions), + prevLine; + connectionLine.actualPaths = []; while (MathTools.isGreaterThanOrEqualTo(lat, endLat) && MathTools.isLessThanOrEqualTo(lng, endLng)) { @@ -119,15 +121,18 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { let clippedLine = MathTools.clipLineByPolygon(lineCoordinates, turfPolygonCoordinates); - // Extend line by double capture basis to each side - let index, captureBasis; - if (isParallels) { - index = 0; - captureBasis = lngBasis * 2; - } else { - index = 1; - captureBasis = -latBasis * 2; + // Line can be outside of polygon, so we have to get use previous line as clipped line + // TODO: Remove? + if (!clippedLine) { + clippedLine = []; + for (let lngLat of prevLine) { + if (isParallels) + clippedLine.push([lngLat[0], lat]); + else + clippedLine.push([lng, lngLat[1]]); + } } + prevLine = clippedLine; // WARNING: It somehow modifies polygons when generating paths by parallels! Imagine following selected polygons: // [] @@ -136,15 +141,17 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { // \] // [][] // I don't know why it happens, I traced everything. I'll just leave this comment as an explanation and a warning. - /*clippedLine[0][index] -= captureBasis; - clippedLine[1][index] += captureBasis;*/ + /*clippedLine[0][extensionIndex] -= extendBy; + clippedLine[1][extensionIndex] += extendBy;*/ // Instead, let's just copy our points to the new array. Array.slice() and newClippedLine.push(point) doesn't work either. let newClippedLine = []; for (let point of clippedLine) newClippedLine.push([point[0], point[1]]); - newClippedLine[0][index] -= captureBasis; - newClippedLine[1][index] += captureBasis; + + // Extend line by double capture basis to each side + newClippedLine[0][extensionIndex] -= extendBy; + newClippedLine[1][extensionIndex] += extendBy; let startPoint = newClippedLine[0], endPoint = newClippedLine[1]; // Points for generating capturing points let firstPoint, secondPoint; // Points for generating lines @@ -189,9 +196,9 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { }); this[pointsName].addLayer(circle); if (isParallels) - ptLng += lngBasis; + ptLng += parallelsBasis; else - ptLat -= latBasis; + ptLat -= meridiansBasis; } swapPoints = !swapPoints; diff --git a/SynthGridLayer/misc.js b/SynthGridLayer/misc.js index da38d1dd..5edaf4d6 100644 --- a/SynthGridLayer/misc.js +++ b/SynthGridLayer/misc.js @@ -9,6 +9,20 @@ L.ALS.SynthGridLayer.prototype._setColor = function (widget) { this.updateGrid(); } +L.ALS.SynthGridLayer.prototype.calculateParameters = function () { + L.ALS.SynthBaseLayer.prototype.calculateParameters.call(this); + + // Calculate estimated paths count for a polygon. Values are somewhat true for equatorial regions. + // We'll check if it's too small (in near-polar regions, there'll be only one path when value is 2) or too big. + let latLngs = ["lat", "lng"]; + for (let name of latLngs) { + let cellSize = Math.round(turfHelpers.radiansToLength(turfHelpers.degreesToRadians(this[name + "Distance"]), "meters")); + this[name + "FakePathsCount"] = Math.ceil(cellSize / this.By); + } + + this._calculatePolygonParameters(); +} + L.ALS.SynthGridLayer.prototype._updateLayersVisibility = function () { let hidePathsByMeridians = this.getWidgetById("hidePathsByMeridians").getValue(), hidePathsByParallels = this.getWidgetById("hidePathsByParallels").getValue(); diff --git a/SynthGridLayer/polygons.js b/SynthGridLayer/polygons.js index 675bed69..87642003 100644 --- a/SynthGridLayer/polygons.js +++ b/SynthGridLayer/polygons.js @@ -17,10 +17,14 @@ L.ALS.SynthGridLayer.prototype._selectOrDeselectPolygon = function (event) { new L.ALS.Widgets.ValueLabel("absoluteHeight", "absoluteHeight", "m"), new L.ALS.Widgets.ValueLabel("elevationDifference", "elevationDifference"), new L.ALS.Widgets.ValueLabel("reliefType", "reliefType"), - new L.ALS.Widgets.SimpleLabel("error").setStyle("error") + new L.ALS.Widgets.SimpleLabel("error").setStyle("error"), + new L.ALS.Widgets.ValueLabel("lngPathsCount", "lngPathsCount", "", "value"), + new L.ALS.Widgets.ValueLabel("latPathsCount", "latPathsCount", "", "value"), + new L.ALS.Widgets.ValueLabel("lngCellSizeInMeters", "lngCellSizeInMeters", "m").setNumberOfDigitsAfterPoint(0), + new L.ALS.Widgets.ValueLabel("latCellSizeInMeters", "latCellSizeInMeters", "m").setNumberOfDigitsAfterPoint(0), ); - let toFormatNumbers = ["meanHeight", "absoluteHeight", "elevationDifference"]; + let toFormatNumbers = ["meanHeight", "absoluteHeight", "elevationDifference", "lngPathsCount", "latPathsCount", "lngCellSizeInMeters", "latCellSizeInMeters"]; for (let id of toFormatNumbers) controlsContainer.getWidgetById(id).setFormatNumbers(true); @@ -38,17 +42,27 @@ L.ALS.SynthGridLayer.prototype._selectOrDeselectPolygon = function (event) { } L.ALS.SynthGridLayer.prototype._calculatePolygonParameters = function () { - let areaIncrement = Math.round(this["latCellSizeInMeters"] * this["lngCellSizeInMeters"]); this.selectedArea = 0; for (let name in this.selectedPolygons) { if (!this.selectedPolygons.hasOwnProperty(name)) continue; - this.selectedArea += areaIncrement; - - let layer = this.selectedPolygons[name]; + let layer = this.selectedPolygons[name], latLngs = layer.getLatLngs()[0]; let widgetContainer = this.selectedPolygonsWidgets[name]; + layer.lngCellSizeInMeters = this.getLineLengthMeters([latLngs[0], latLngs[1]], false); + layer.latCellSizeInMeters = this.getLineLengthMeters([latLngs[1], latLngs[2]], false); + + layer.lngPathsCount = Math.ceil(layer.lngCellSizeInMeters / this.By); + layer.latPathsCount = Math.ceil(layer.latCellSizeInMeters / this.By); + + this.selectedArea += layer.lngCellSizeInMeters * layer.latCellSizeInMeters; + + widgetContainer.getWidgetById("lngCellSizeInMeters").setValue(layer.lngCellSizeInMeters); + widgetContainer.getWidgetById("latCellSizeInMeters").setValue(layer.latCellSizeInMeters); + widgetContainer.getWidgetById("lngPathsCount").setValue(layer.lngPathsCount); + widgetContainer.getWidgetById("latPathsCount").setValue(layer.latPathsCount); + layer.minHeight = widgetContainer.getWidgetById("minHeight").getValue(); layer.maxHeight = widgetContainer.getWidgetById("maxHeight").getValue(); diff --git a/SynthGridLayer/toGeoJSON.js b/SynthGridLayer/toGeoJSON.js index e39f78e9..fd25bc4f 100644 --- a/SynthGridLayer/toGeoJSON.js +++ b/SynthGridLayer/toGeoJSON.js @@ -8,7 +8,7 @@ L.ALS.SynthGridLayer.prototype.toGeoJSON = function () { continue; let polygon = this.selectedPolygons[name], polygonJson = polygon.toGeoJSON(), - props = ["polygonName", "minHeight", "maxHeight", "meanHeight", "absoluteHeight", "reliefType", "elevationDifference"]; + props = ["polygonName", "minHeight", "maxHeight", "meanHeight", "absoluteHeight", "reliefType", "elevationDifference", "latCellSizeInMeters", "lngCellSizeInMeters", "lngPathsCount", "latPathsCount"]; for (let prop of props) polygonJson.properties[prop] = polygon[prop]; polygonJson.properties.name = "Selected cell"; @@ -25,15 +25,9 @@ L.ALS.SynthGridLayer.prototype.toGeoJSON = function () { } // See _calculateParameters - let parallelsProps = { - pathsCount: this.latPathsCount, - name: "Flight paths by parallels" - }, - meridiansProps = { - pathsCount: this.lngPathsCount, - name: "Flight paths by meridians" - }, - params = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "flightHeight", "overlayBetweenPaths", "overlayBetweenImages", "imageScale", "ly", "Ly", "By", "lx", "Lx", "Bx", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "latCellSizeInMeters", "lngCellSizeInMeters", "selectedArea"]; + let parallelsProps = {name: "Flight paths by parallels"}, + meridiansProps = {name: "Flight paths by meridians"}, + params = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "flightHeight", "overlayBetweenPaths", "overlayBetweenImages", "imageScale", "ly", "Ly", "By", "lx", "Lx", "Bx", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "selectedArea"]; for (let prop of [parallelsProps, meridiansProps]) { for (let param of params) diff --git a/locales/English.js b/locales/English.js index 7d8e67ed..8a6d8507 100644 --- a/locales/English.js +++ b/locales/English.js @@ -51,10 +51,6 @@ L.ALS.Locales.addLocaleProperties("English", { overlayBetweenPaths: "Overlay between images from adjacent paths (%):", overlayBetweenImages: "Overlay between images from the same path (%):", focalLength: "Focal length (mm):", - lngPathsCount: "Paths count in one cell by parallels", - latPathsCount: "Paths count in one cell by meridians", - lngCellSizeInMeters: "Mean cell width", - latCellSizeInMeters: "Mean cell height", selectedArea: "Selected area", flightHeight: "Flight height (m)", lx: "Image height, lx", @@ -78,11 +74,15 @@ L.ALS.Locales.addLocaleProperties("English", { absoluteHeight: "Absolute height", elevationDifference: "(Max. height - Min. height) / Flight height", reliefType: "Relief type", + lngPathsCount: "Paths count by parallels", + latPathsCount: "Paths count by meridians", + lngCellSizeInMeters: "Cell width", + latCellSizeInMeters: "Cell height", errorDistanceHasNotBeenCalculated: "Distance between paths hasn't been calculated!", - errorPathsCountTooBig: "Calculated paths count is too big, it should be less than 20. Please, check your values.", + errorPathsCountTooBig: "Calculated paths count is too big. Please, check your values.", errorCamHeight: "Camera height is greater than camera width!", - errorPathsCountTooSmall: "Calculated paths count is too small, it should be greater than 2. Please, check your values.", + errorPathsCountTooSmall: "Calculated paths count is too small. Please, check your values.", errorMinHeightBiggerThanMaxHeight: "Min. height should be less than or equal to max. height!", DEMFiles: "Load DEM files to calculate statistics. Select GeoTIFF or ASCII Grid files. Select .prj or .aux.xml files with the same name to override CRS. For ASCII Grid, if no of these file selected, WGS84 is assumed.", diff --git a/locales/Russian.js b/locales/Russian.js index 707451b4..37f36c7f 100644 --- a/locales/Russian.js +++ b/locales/Russian.js @@ -21,7 +21,7 @@ L.ALS.Locales.addLocaleProperties("Русский", { gridWizardDisplayName: "Слой Сетки", gridWizardNotification: `Если масштаб карты слишком мелкий, сетка будет скрыта. Пожалуйста, увеличьте масштаб карты, чтобы ее увидеть. - Чтобы выделить полигон, либо нажмите на него правой кнопкой мыши (или тапните и задержите палец) или два раза кликните (тапните) на него.`, + Чтобы выделить трапецию, либо нажмите на него правой кнопкой мыши (или тапните и задержите палец) или два раза кликните (тапните) на него.`, gridStandardScales: "Масштаб сетки:", gridLngDistance: "Расстояние между параллелями:", @@ -51,11 +51,7 @@ L.ALS.Locales.addLocaleProperties("Русский", { overlayBetweenPaths: "Перекрытие между изображениями с соседних маршрутов (%):", overlayBetweenImages: "Перекрытие между изображениями с одного маршрута (%):", focalLength: "Фокусное расстояние (mm):", - lngPathsCount: "Число маршрутов в одной ячейке по параллелям", - latPathsCount: "Число маршрутов в одной ячейке по меридианам", - lngCellSizeInMeters: "Средняя ширина ячейки", - latCellSizeInMeters: "Средняя высота сетки", - selectedArea: "Площадь выделенных полигонов", + selectedArea: "Площадь выделенных трапеций", flightHeight: "Высота полета (m)", lx: "Высота изображения, lx", Lx: "Высота изображения на местности, Lx", @@ -73,11 +69,15 @@ L.ALS.Locales.addLocaleProperties("Русский", { absoluteHeight: "Абс. высота", elevationDifference: "(Макс. высота - Мин. высота) / Высота полета", reliefType: "Тип рельефа", + lngPathsCount: "Число маршрутов по параллелям", + latPathsCount: "Число маршрутов по меридианам", + lngCellSizeInMeters: "Ширина трапеции", + latCellSizeInMeters: "Высота трапеции", errorDistanceHasNotBeenCalculated: "Расстояние между маршрутами не было вычислено!", - errorPathsCountTooBig: "Вычисленное количество маршрутов слишком велико, оно должно быть меньше 20. Пожалуйста, проверьте ваши значения.", + errorPathsCountTooBig: "Вычисленное количество маршрутов слишком велико. Пожалуйста, проверьте ваши значения.", errorCamHeight: "Высота камеры больше ширины камеры!", - errorPathsCountTooSmall: "Вычисленное количество маршрутов слишком мало, оно должно быть больше 2. Пожалуйста, проверьте ваши значения", + errorPathsCountTooSmall: "Вычисленное количество маршрутов слишком мало. Пожалуйста, проверьте ваши значения", errorMinHeightBiggerThanMaxHeight: "Мин. высота должна быть меньше или равна макс. высоте!", DEMFiles: "Загрузить файлы ЦМР для расчета статистики. Выберете файлы GeoTIFF или ASCII Grid. Выберете файлы .prj или .aux.xml с такими же именами для перезаписи CRS. Если для ASCII Grid не выбран ни один из этих файлов, предполагается, что используется WGS84.", From 604f6469b999a5677d81f5f858dc357fb207e020 Mon Sep 17 00:00:00 2001 From: matafokka Date: Wed, 24 Nov 2021 20:51:14 +0300 Subject: [PATCH 5/7] Found out that current methods yield considerable errors. Changed stuff to calculating other point of an arc by required length. --- SynthBase/SynthBaseLayer.js | 34 ++++++++++++++++++++++++++++++- SynthGridLayer/drawPaths.js | 40 +++++++++++++++++-------------------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/SynthBase/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js index 594b739a..41956e8a 100644 --- a/SynthBase/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -296,7 +296,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot * @return {number} Line length */ getLineLengthMeters: function (line, useFlightHeight = true) { - let r = 6371000 + (useFlightHeight ? this.flightHeight : 0), points = line instanceof Array ? line : line.getLatLngs(), distance = 0, x, y; + let r = this._getEarthRadius(useFlightHeight), points = line instanceof Array ? line : line.getLatLngs(), distance = 0, x, y; if (points.length === 0) return 0; @@ -319,6 +319,38 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot return distance; }, + /** + * Using given point and length calculates by how much you should modify (add or remove to) lng or lat to get a line of given length. + * + * In other words, can be used to draw straight vertical or straight horizontal lines + * + * @param startingPoint {number[]} Point in format [lng, lat] + * @param length {number} Line length + * @param isVertical {boolean} Whether line should vertical or horizontal + * @param useFlightHeight {boolean} If true, will account flight height + * @return {number} By how much you should modify (add or remove to) lng or lat to get a line of given length + */ + getArcAngleByLength: function (startingPoint, length, isVertical, useFlightHeight = false) { + let r = this._getEarthRadius(useFlightHeight); + + // For vertical lines, we can simply use arc length since any two equal angles will form two equal arcs along any meridian. + if (isVertical) + return turfHelpers.radiansToDegrees(length / r); + + // For horizontal lines, first, we need to find a circle formed by cutting sphere by a horizontal plane + // containing given point. To do so, we'll find a distance from a point to the line from center to + // the North pole. This will be a radius of the said circle. Then we'll use formula above on a formed circle. + + // Angle between line from point to center and line from center to the North pole. + let angle = turfHelpers.degreesToRadians(90 - Math.abs(startingPoint[1])), + newR = Math.sin(angle) * r; + return turfHelpers.radiansToDegrees(length / newR); + }, + + _getEarthRadius: function (useFlightHeight = false) { + return 6378137 + (useFlightHeight ? this.flightHeight : 0); + }, + /** * Called when there's one flight per each path. You should call {@link L.ALS.SynthBaseLayer#connectOnePerFlight} here. */ diff --git a/SynthGridLayer/drawPaths.js b/SynthGridLayer/drawPaths.js index e95ff02f..8d538762 100644 --- a/SynthGridLayer/drawPaths.js +++ b/SynthGridLayer/drawPaths.js @@ -84,21 +84,6 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { [startLng, endLat, endLng, startLat] = bbox(turfPolygon), // Create bounding box around current polygon. We'll draw paths using bounding box and then clip it by current polygon swapPoints = false, // Should swap points on each new line - // Calculate new distances between paths for current polygon - lengthByLat = Math.abs(startLat - endLat), - lengthByLng = Math.abs(endLng - startLng), - parallelsDistance = lengthByLat * this.By / this.getLineLengthMeters([[startLng, startLat], [startLng, endLat]], false), - meridiansDistance = lengthByLng * this.By / this.getLineLengthMeters([[startLng, startLat], [endLng, startLat]], false), - - // Calculate correct capture basis in degrees. - bottomSide = [[startLng, endLat], [startLng + this.lngDistance, endLat]], - rightSide = [[endLng, startLat], [endLng, startLat - this.latDistance]], - parallelsPointsCount = Math.ceil(this.getLineLengthMeters(bottomSide, false) / this.Bx), - meridiansPointsCount = Math.ceil(this.getLineLengthMeters(rightSide, false) / this.Bx), - - parallelsBasis = this.getLineLength(bottomSide) / parallelsPointsCount, meridiansBasis = this.getLineLength(rightSide) / meridiansPointsCount, - extendBy = isParallels ? parallelsBasis * 2 : -meridiansBasis * 2, - lat = startLat, lng = startLng, turfPolygonCoordinates = turfPolygon.geometry.coordinates[0], // MathTools accepts coordinates of the polygon, not polygon itself number = 1, connectionLine = L.polyline([], connLineOptions), @@ -149,9 +134,16 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { for (let point of clippedLine) newClippedLine.push([point[0], point[1]]); - // Extend line by double capture basis to each side - newClippedLine[0][extensionIndex] -= extendBy; - newClippedLine[1][extensionIndex] += extendBy; + // Extend the line, so it'll hold whole number of images + double basis, i.e. two images from each side + let length = this.getLineLengthMeters(newClippedLine, false), + numberOfImages = Math.ceil(length / this.Bx) + 4, + extendBy = (this.Bx * numberOfImages - length) / 2, + multiplier = isParallels ? -1 : 1; // We'll start from the leftmost or topmost point + + for (let point of newClippedLine) { + point[extensionIndex] += multiplier * this.getArcAngleByLength(newClippedLine[1], extendBy, !isParallels); + multiplier *= -1; + } let startPoint = newClippedLine[0], endPoint = newClippedLine[1]; // Points for generating capturing points let firstPoint, secondPoint; // Points for generating lines @@ -195,17 +187,21 @@ L.ALS.SynthGridLayer.prototype._drawPathsWorker = function (isParallels) { fillColor: color, }); this[pointsName].addLayer(circle); + + let moveBy = this.getArcAngleByLength([ptLng, ptLat], this.Bx, !isParallels); if (isParallels) - ptLng += parallelsBasis; + ptLng += moveBy; else - ptLat -= meridiansBasis; + ptLat -= moveBy; } swapPoints = !swapPoints; + + let moveBy = this.getArcAngleByLength([ptLng, ptLat], this.By, isParallels); if (isParallels) - lat -= parallelsDistance; + lat -= moveBy; else - lng += meridiansDistance; + lng += moveBy; } connectionsGroup.addLayer(connectionLine); From 2d1a5b781531df228f8939e75a5f1adeec4cbe4f Mon Sep 17 00:00:00 2001 From: matafokka Date: Fri, 26 Nov 2021 18:06:25 +0300 Subject: [PATCH 6/7] Fixes and new small features 1. Did a bit of refactoring. 1. Fixed polygon widgets displaying when changing any of "hiding" checkboxes. 1. Fixed paths not clearing when last polygon has been unselected. 1. Fixed FOV calculation. 1. Hull now uses meters instead of degrees. 1. Added time between captures. 1. Added warning when flight time exceeds 4 hours. 1. Removed paths count since it was misleading. Imagine three polygons connected horizontally. In the second polygon, first path by meridians won't lie straight on the left side, it'll be moved to left a bit. Thus, last path will be moved outside of the polygon as well and added by third polygon. So we get one path less than calculated. All of that means that the formula itself is misleading. --- MathTools.js | 12 +++--- SynthBase/Hull.js | 19 +++------- SynthBase/SynthBaseLayer.js | 63 ++++++++++++++------------------ SynthBase/calculateParameters.js | 6 ++- SynthBase/toGeoJSON.js | 3 +- SynthGridLayer/drawPaths.js | 15 +++++--- SynthGridLayer/misc.js | 3 +- SynthGridLayer/onMapZoom.js | 17 ++++++--- SynthGridLayer/polygons.js | 13 ++----- SynthGridLayer/toGeoJSON.js | 4 +- locales/English.js | 4 +- locales/Russian.js | 4 +- main.js | 2 +- package.json | 2 +- 14 files changed, 83 insertions(+), 84 deletions(-) diff --git a/MathTools.js b/MathTools.js index d453d725..afc2003c 100644 --- a/MathTools.js +++ b/MathTools.js @@ -313,14 +313,16 @@ class MathTools { } static arePointsEqual(p1, p2) { - let x = 0, y = 1; - if (p1.lat !== undefined) { - x = "lng"; - y = "lat"; - } + let {x, y} = this.getXYPropertiesForPoint(p1); return this.isEqual(p1[x], p2[x]) && this.isEqual(p1[y], p2[y]); } + static getXYPropertiesForPoint(p) { + if (p.lat === undefined) + return {x: 0, y: 1} + return {x: "lng", y: "lat"} + } + } module.exports = MathTools; \ No newline at end of file diff --git a/SynthBase/Hull.js b/SynthBase/Hull.js index 71578b8a..76aeb458 100644 --- a/SynthBase/Hull.js +++ b/SynthBase/Hull.js @@ -143,7 +143,7 @@ L.ALS.SynthBaseLayer.prototype.buildHull = function (path, color) { for (let pair of pairs) { let [p1, p2] = pair, line1 = [p1, conP1], line2 = [p2, conP2], - len = this.getLineLength(line1) + this.getLineLength(line2); + len = this.getLineLengthMeters(line1) + this.getLineLengthMeters(line2); if (len < minLen) { minLen = len; @@ -213,7 +213,7 @@ L.ALS.SynthBaseLayer.prototype.getHullOptimalConnection = function (pathP1, path for (let pair of pairs) { let [p1, p2] = pair, - len = this.getLineLength([prevPoint, p1]) + this.getLineLength([nextPoint, p2]); + len = this.getLineLengthMeters([prevPoint, p1]) + this.getLineLengthMeters([nextPoint, p2]); if (len < minLen) { minLen = len; @@ -268,10 +268,7 @@ L.ALS.SynthBaseLayer.prototype.connectHullToAirport = function () { let [p1, p2] = toRemove.getLatLngs(); hullConnection.setLatLngs([p1, airportPos, p2]); - - // Update widgets - path.pathWidget.getWidgetById("pathLength").setValue(minLen); - path.pathWidget.getWidgetById("flightTime").setValue(this.getFlightTime(minLen)); + path.updateWidgets(minLen); } } @@ -281,7 +278,7 @@ L.ALS.SynthBaseLayer.prototype.connectHullToAirport = function () { L.ALS.SynthBaseLayer.prototype.connectHull = function () { for (let i = 0; i < this.paths.length; i++) { let path = this.paths[i]; - path.pathWidget = this._createPathWidget(1, path.toUpdateColors); + this._createPathWidget(path, 1, path.toUpdateColors); this.buildHull(path, this.getWidgetById(`color${i}`).getValue()); } this.connectHullToAirport(); @@ -387,12 +384,8 @@ L.ALS.SynthBaseLayer.prototype.getOrderedPathFromHull = function (prevPoint, con * @return {number} Line length */ L.ALS.SynthBaseLayer.prototype.getLineLength = function (line) { - let latLngs = line instanceof Array ? line : line.getLatLngs(), length = 0, x = 0, y = 1; - - if (latLngs[0].lat !== undefined) { - x = "lng"; - y = "lat"; - } + let latLngs = line instanceof Array ? line : line.getLatLngs(), length = 0, + {x, y} = MathTools.getXYPropertiesForPoint(latLngs[0]); for (let i = 0; i < latLngs.length - 1; i++) { const p1 = latLngs[i], p2 = latLngs[i + 1]; diff --git a/SynthBase/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js index 41956e8a..e6aed009 100644 --- a/SynthBase/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -1,4 +1,5 @@ const turfHelpers = require("@turf/helpers"); +const MathTools = require("../MathTools.js"); /** * Base layer. Provides airport markers, basic calculations and menu entries for them. @@ -156,11 +157,12 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot } let valueLabels = [ - new L.ALS.Widgets.ValueLabel("flightHeight", "flightHeight"), + new L.ALS.Widgets.ValueLabel("flightHeight", "flightHeight", "m"), new L.ALS.Widgets.ValueLabel("lx", "lx", "m"), new L.ALS.Widgets.ValueLabel("Lx", "Lx", "m"), new L.ALS.Widgets.ValueLabel("Bx", "Bx", "m"), ...yWidgets, + new L.ALS.Widgets.ValueLabel("timeBetweenCaptures", "timeBetweenCaptures", "s"), new L.ALS.Widgets.ValueLabel("GSI", "GSI", "m"), new L.ALS.Widgets.ValueLabel("IFOV", "IFOV", "μrad"), new L.ALS.Widgets.ValueLabel("GIFOV", "GIFOV", "m"), @@ -247,36 +249,36 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot this.connectHull(); }, - _createPathWidget: function (length, toFlash) { - let button = new L.ALS.Widgets.Button("flashPath", "flashPath", this, "flashPath"); - button.toFlash = toFlash; - - let widget = new L.ALS.Widgets.Spoiler(`pathWidget${this._pathsWidgetsNumber}`, `${L.ALS.locale.pathSpoiler} ${this._pathsWidgetsNumber}`) - .addWidgets( - button, - new L.ALS.Widgets.ValueLabel("pathLength", "pathLength", "m").setFormatNumbers(true).setNumberOfDigitsAfterPoint(0).setValue(length), - new L.ALS.Widgets.ValueLabel("flightTime", "flightTime", "h:mm").setValue(this.getFlightTime(length)), - ); + _createPathWidget: function (layer, length, toFlash) { + let button = new L.ALS.Widgets.Button("flashPath", "flashPath", this, "flashPath"), + lengthWidget = new L.ALS.Widgets.ValueLabel("pathLength", "pathLength", "m").setFormatNumbers(true).setNumberOfDigitsAfterPoint(0), + timeWidget = new L.ALS.Widgets.ValueLabel("flightTime", "flightTime", "h:mm").setValue(), + warning = new L.ALS.Widgets.SimpleLabel("warning", "", "center", "warning"), + widget = new L.ALS.Widgets.Spoiler(`pathWidget${this._pathsWidgetsNumber}`, `${L.ALS.locale.pathSpoiler} ${this._pathsWidgetsNumber}`) + .addWidgets(button, lengthWidget, timeWidget, warning); + + layer.updateWidgets = (length) => { + lengthWidget.setValue(length); + let time = this.getFlightTime(length); + timeWidget.setValue(time.formatted); + warning.setValue(time.number > 4 ? "flightTimeWarning" : ""); + } + button.toFlash = toFlash; this.addWidgets(widget); this._pathsWidgetsNumber++; this._pathsWidgets.push(widget); - return widget; + layer.updateWidgets(length); }, /** * Calculates flight time for given path length * @param length {number} Path length - * @param formatAsTimeSpan {boolean} If true, will format time as time span - * @return {string|number} Flight time in hours + * @return {{number: number, formatted: string}} Flight time in hours, both as number and formatted string */ - getFlightTime: function (length, formatAsTimeSpan = true) { - let time = length / this.aircraftSpeedInMetersPerSecond / 3600; - - if (!formatAsTimeSpan) - return time; - - let hours = Math.floor(time), minutes = Math.round((time % 1) * 60).toString(); + getFlightTime: function (length) { + let time = length / this.aircraftSpeedInMetersPerSecond / 3600, + hours = Math.floor(time), minutes = Math.round((time % 1) * 60).toString(); if (minutes === "60") { hours++; @@ -286,7 +288,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot if (minutes.length === 1) minutes = "0" + minutes; - return hours + ":" + minutes; + return {number: time, formatted: hours +":" + minutes}; }, /** @@ -296,17 +298,11 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot * @return {number} Line length */ getLineLengthMeters: function (line, useFlightHeight = true) { - let r = this._getEarthRadius(useFlightHeight), points = line instanceof Array ? line : line.getLatLngs(), distance = 0, x, y; + let r = this._getEarthRadius(useFlightHeight), points = line instanceof Array ? line : line.getLatLngs(), distance = 0; if (points.length === 0) return 0; - if (points[0].lat === undefined) { - x = "0"; - y = "1"; - } else { - x = "lng"; - y = "lat"; - } + let {x, y} = MathTools.getXYPropertiesForPoint(points[0]); for (let i = 0; i < points.length - 1; i++) { let p1 = points[i], p2 = points[i + 1], @@ -362,10 +358,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot for (let layer of layers) { layer.getLatLngs()[1] = airportPos; layer.redraw(); - - let length = layer.pathLength + this.getLineLengthMeters(layer); - layer.pathWidget.getWidgetById("pathLength").setValue(length); - layer.pathWidget.getWidgetById("flightTime").setValue(this.getFlightTime(length)); + layer.updateWidgets(layer.pathLength + this.getLineLengthMeters(layer)); } } @@ -394,7 +387,7 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot if (layer.actualPaths) toFlash.push(...layer.actualPaths) - connectionLine.pathWidget = this._createPathWidget(1, toFlash); + this._createPathWidget(connectionLine, 1, toFlash); connectionsGroup.addLayer(connectionLine); } } diff --git a/SynthBase/calculateParameters.js b/SynthBase/calculateParameters.js index c28d0504..c10b021a 100644 --- a/SynthBase/calculateParameters.js +++ b/SynthBase/calculateParameters.js @@ -29,12 +29,14 @@ L.ALS.SynthBaseLayer.prototype.calculateParameters = function () { this.GSI = pixelWidth * this["imageScale"]; this.IFOV = pixelWidth / focalLength * 1e6; this.GIFOV = this.GSI; - this.FOV = this["cameraWidth"] * this.IFOV; + this.FOV = turfHelpers.radiansToDegrees(this["cameraWidth"] * this.IFOV / 1e6); this.GFOV = this["cameraWidth"] * this.GSI; this.aircraftSpeedInMetersPerSecond = this["aircraftSpeed"] * 1 / 36; - let names = ["flightHeight", "lx", "Lx", "Bx", "ly", "Ly", "By", "GSI", "IFOV", "GIFOV", "FOV", "GFOV",]; + this.timeBetweenCaptures = this.Bx / this.aircraftSpeedInMetersPerSecond; + + let names = ["flightHeight", "lx", "Lx", "Bx", "ly", "Ly", "By", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "timeBetweenCaptures"]; for (let name of names) { const field = this[name]; if (field === undefined) diff --git a/SynthBase/toGeoJSON.js b/SynthBase/toGeoJSON.js index 7880ff44..2cd1caa1 100644 --- a/SynthBase/toGeoJSON.js +++ b/SynthBase/toGeoJSON.js @@ -1,4 +1,3 @@ -const MathTools = require("../MathTools.js"); const turfHelpers = require("@turf/helpers"); const geojsonMerge = require("@mapbox/geojson-merge"); // Using this since turfHelpers.featureCollection() discards previously defined properties. @@ -26,7 +25,7 @@ L.ALS.SynthBaseLayer.prototype.toGeoJSON = function (path1Metadata, path2Metadat cycleJson.properties[name] = metadata[name]; cycleJson.properties.length = this.getLineLengthMeters(cycle); - cycleJson.properties.flightTime = this.getFlightTime(cycleJson.properties.length, false); + cycleJson.properties.flightTime = this.getFlightTime(cycleJson.properties.length).number; toMerge.push(cycleJson); } diff --git a/SynthGridLayer/drawPaths.js b/SynthGridLayer/drawPaths.js index 8d538762..ee464f87 100644 --- a/SynthGridLayer/drawPaths.js +++ b/SynthGridLayer/drawPaths.js @@ -2,8 +2,7 @@ const bbox = require("@turf/bbox").default; const MathTools = require("../MathTools.js"); const turfHelpers = require("@turf/helpers"); -L.ALS.SynthGridLayer.prototype._drawPaths = function () { - // Remove previously added paths +L.ALS.SynthGridLayer.prototype._clearPaths = function () { let groupsToClear = [this.pathsByParallels, this.pathsByMeridians, this.meridiansExternalConnections, this.meridiansInternalConnections, this.parallelsExternalConnections, this.parallelsInternalConnections, this.latPointsGroup, this.lngPointsGroup]; for (let group of groupsToClear) group.clearLayers(); @@ -11,12 +10,16 @@ L.ALS.SynthGridLayer.prototype._drawPaths = function () { for (let id of this._pathsLabelsIDs) this.labelsGroup.deleteLabel(id); this._pathsLabelsIDs = []; +} + +L.ALS.SynthGridLayer.prototype._drawPaths = function () { + this._clearPaths(); - // Validate parameters + // Validate estimated paths count - let errorLabel = this.getWidgetById("calculateParametersError"); - let parallelsPathsCount = this["lngFakePathsCount"]; - let meridiansPathsCount = this["latFakePathsCount"]; + let errorLabel = this.getWidgetById("calculateParametersError"), + parallelsPathsCount = this["lngFakePathsCount"], + meridiansPathsCount = this["latFakePathsCount"]; if (parallelsPathsCount === undefined) { errorLabel.setValue("errorDistanceHasNotBeenCalculated"); diff --git a/SynthGridLayer/misc.js b/SynthGridLayer/misc.js index 5edaf4d6..4f758052 100644 --- a/SynthGridLayer/misc.js +++ b/SynthGridLayer/misc.js @@ -50,7 +50,8 @@ L.ALS.SynthGridLayer.prototype._updateLayersVisibility = function () { this._hideOrShowLayer(hidePathsByParallels, this.pathsByParallels); this._hideOrShowLayer(hidePathsByMeridians, this.pathsByMeridians); - this._doHidePolygonWidgets = this._hideOrShowLayer(this.getWidgetById("hidePolygonWidgets").getValue(), this.widgetsGroup); + this._doHidePolygonWidgets = this.getWidgetById("hidePolygonWidgets").getValue(); + this._hideOrShowLayer(this._doHidePolygonWidgets || this._shouldHideEverything, this.widgetsGroup); this._doHidePathsNumbers = this.getWidgetById("hideNumbers").getValue(); this._drawPaths(); // We have to redraw paths both for hiding one of the paths and hiding numbers } diff --git a/SynthGridLayer/onMapZoom.js b/SynthGridLayer/onMapZoom.js index 7011edfe..44d894e7 100644 --- a/SynthGridLayer/onMapZoom.js +++ b/SynthGridLayer/onMapZoom.js @@ -13,22 +13,29 @@ L.ALS.SynthGridLayer.prototype._onMapZoom = function () { let distancePx = this.map.latLngToContainerPoint(distanceLatLng).x; // Grid becomes messy when distance is around 15 pixels - let shouldHide = distancePx < this.hidingThreshold; - if (shouldHide) { + + /** + * Whether should hide stuff, if map is zoomed out + * @type {boolean} + * @private + */ + this._shouldHideEverything = distancePx < this.hidingThreshold; + + if (this._shouldHideEverything) { let groups = ["polygonGroup", "bordersGroup", "labelsGroup", "widgetsGroup"]; for (let group of groups) this[group].remove(); this.isDisplayed = false; - } else if (!shouldHide && !this.isDisplayed) { + } else if (!this._shouldHideEverything && !this.isDisplayed) { this.isDisplayed = true; this.polygonGroup.addTo(this.map); // Add removed stuff this.bordersGroup.addTo(this.map); this.labelsGroup.addTo(this.map); } - shouldHide = distancePx < 200; + this._shouldHideEverything = distancePx < 200; if (this.isDisplayed && !this._doHidePolygonWidgets) { - if (shouldHide) + if (this._shouldHideEverything) this.widgetsGroup.remove(); else { this.widgetsGroup.addTo(this.map); diff --git a/SynthGridLayer/polygons.js b/SynthGridLayer/polygons.js index 87642003..69105be9 100644 --- a/SynthGridLayer/polygons.js +++ b/SynthGridLayer/polygons.js @@ -18,13 +18,11 @@ L.ALS.SynthGridLayer.prototype._selectOrDeselectPolygon = function (event) { new L.ALS.Widgets.ValueLabel("elevationDifference", "elevationDifference"), new L.ALS.Widgets.ValueLabel("reliefType", "reliefType"), new L.ALS.Widgets.SimpleLabel("error").setStyle("error"), - new L.ALS.Widgets.ValueLabel("lngPathsCount", "lngPathsCount", "", "value"), - new L.ALS.Widgets.ValueLabel("latPathsCount", "latPathsCount", "", "value"), new L.ALS.Widgets.ValueLabel("lngCellSizeInMeters", "lngCellSizeInMeters", "m").setNumberOfDigitsAfterPoint(0), new L.ALS.Widgets.ValueLabel("latCellSizeInMeters", "latCellSizeInMeters", "m").setNumberOfDigitsAfterPoint(0), ); - let toFormatNumbers = ["meanHeight", "absoluteHeight", "elevationDifference", "lngPathsCount", "latPathsCount", "lngCellSizeInMeters", "latCellSizeInMeters"]; + let toFormatNumbers = ["meanHeight", "absoluteHeight", "elevationDifference", "lngCellSizeInMeters", "latCellSizeInMeters"]; for (let id of toFormatNumbers) controlsContainer.getWidgetById(id).setFormatNumbers(true); @@ -53,15 +51,10 @@ L.ALS.SynthGridLayer.prototype._calculatePolygonParameters = function () { layer.lngCellSizeInMeters = this.getLineLengthMeters([latLngs[0], latLngs[1]], false); layer.latCellSizeInMeters = this.getLineLengthMeters([latLngs[1], latLngs[2]], false); - layer.lngPathsCount = Math.ceil(layer.lngCellSizeInMeters / this.By); - layer.latPathsCount = Math.ceil(layer.latCellSizeInMeters / this.By); - this.selectedArea += layer.lngCellSizeInMeters * layer.latCellSizeInMeters; widgetContainer.getWidgetById("lngCellSizeInMeters").setValue(layer.lngCellSizeInMeters); widgetContainer.getWidgetById("latCellSizeInMeters").setValue(layer.latCellSizeInMeters); - widgetContainer.getWidgetById("lngPathsCount").setValue(layer.lngPathsCount); - widgetContainer.getWidgetById("latPathsCount").setValue(layer.latPathsCount); layer.minHeight = widgetContainer.getWidgetById("minHeight").getValue(); layer.maxHeight = widgetContainer.getWidgetById("maxHeight").getValue(); @@ -95,8 +88,10 @@ L.ALS.SynthGridLayer.prototype._calculatePolygonParameters = function () { // Draw thick borders around selected polygons this._mergeSelectedPolygons(); this.bordersGroup.clearLayers(); - if (this.mergedPolygons.length === 0) + if (this.mergedPolygons.length === 0) { + this._clearPaths(); return; + } for (let polygon of this.mergedPolygons) { let latLngs = []; diff --git a/SynthGridLayer/toGeoJSON.js b/SynthGridLayer/toGeoJSON.js index fd25bc4f..5f4e8175 100644 --- a/SynthGridLayer/toGeoJSON.js +++ b/SynthGridLayer/toGeoJSON.js @@ -8,7 +8,7 @@ L.ALS.SynthGridLayer.prototype.toGeoJSON = function () { continue; let polygon = this.selectedPolygons[name], polygonJson = polygon.toGeoJSON(), - props = ["polygonName", "minHeight", "maxHeight", "meanHeight", "absoluteHeight", "reliefType", "elevationDifference", "latCellSizeInMeters", "lngCellSizeInMeters", "lngPathsCount", "latPathsCount"]; + props = ["polygonName", "minHeight", "maxHeight", "meanHeight", "absoluteHeight", "reliefType", "elevationDifference", "latCellSizeInMeters", "lngCellSizeInMeters"]; for (let prop of props) polygonJson.properties[prop] = polygon[prop]; polygonJson.properties.name = "Selected cell"; @@ -27,7 +27,7 @@ L.ALS.SynthGridLayer.prototype.toGeoJSON = function () { // See _calculateParameters let parallelsProps = {name: "Flight paths by parallels"}, meridiansProps = {name: "Flight paths by meridians"}, - params = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "flightHeight", "overlayBetweenPaths", "overlayBetweenImages", "imageScale", "ly", "Ly", "By", "lx", "Lx", "Bx", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "selectedArea"]; + params = ["cameraWidth", "cameraHeight", "pixelWidth", "focalLength", "flightHeight", "overlayBetweenPaths", "overlayBetweenImages", "imageScale", "ly", "Ly", "By", "lx", "Lx", "Bx", "GSI", "IFOV", "GIFOV", "FOV", "GFOV", "selectedArea", "timeBetweenCaptures"]; for (let prop of [parallelsProps, meridiansProps]) { for (let param of params) diff --git a/locales/English.js b/locales/English.js index 8a6d8507..f09f71b8 100644 --- a/locales/English.js +++ b/locales/English.js @@ -8,6 +8,7 @@ L.ALS.Locales.addLocaleProperties("English", { flashPath: "Flash path", pathLength: "Path length", flightTime: "Flight time", + flightTimeWarning: "Flight time exceeds 4 hours. Consider splitting capture area that contains this path into chunks.", // SynthLineWizard lineLayerName: "Line Layer", @@ -52,13 +53,14 @@ L.ALS.Locales.addLocaleProperties("English", { overlayBetweenImages: "Overlay between images from the same path (%):", focalLength: "Focal length (mm):", selectedArea: "Selected area", - flightHeight: "Flight height (m)", + flightHeight: "Flight height", lx: "Image height, lx", Lx: "Image height on the ground, Lx", Bx: "Distance between photographing positions, Bx", ly: "Image width, ly", Ly: "Image width on the ground, Ly", By: "Distance between adjacent paths, By", + timeBetweenCaptures: "Time between captures", GSI: "GSI", IFOV: "IFOV", GIFOV: "GIFOV", diff --git a/locales/Russian.js b/locales/Russian.js index 37f36c7f..eb06289c 100644 --- a/locales/Russian.js +++ b/locales/Russian.js @@ -8,6 +8,7 @@ L.ALS.Locales.addLocaleProperties("Русский", { flashPath: "Помигать маршрутом", pathLength: "Длина маршрута", flightTime: "Время полета", + flightTimeWarning: "Время полета превышает 4 часа. Подумайте, не стоит ли разбить съемочный участок, содержащий данный маршрут, на несколько участков.", // SynthLineWizard lineLayerName: "Слой Линий", @@ -52,13 +53,14 @@ L.ALS.Locales.addLocaleProperties("Русский", { overlayBetweenImages: "Перекрытие между изображениями с одного маршрута (%):", focalLength: "Фокусное расстояние (mm):", selectedArea: "Площадь выделенных трапеций", - flightHeight: "Высота полета (m)", + flightHeight: "Высота полета", lx: "Высота изображения, lx", Lx: "Высота изображения на местности, Lx", Bx: "Расстояние между точками фотографирования, Bx", ly: "Ширина изображения, ly", Ly: "Ширина изображения на местности, Ly", By: "Расстояние между соседними маршрутами, By", + timeBetweenCaptures: "Временной промежуток между снимками", airportForLayer: "Аэропорт для слоя", diff --git a/main.js b/main.js index 78a5f9a3..2127e49a 100644 --- a/main.js +++ b/main.js @@ -96,6 +96,6 @@ for (let country of countries) { layerSystem.addBaseLayer(L.tileLayer(""), "Empty"); // Add layer types -layerSystem.addLayerType(L.ALS.SynthLineLayer); layerSystem.addLayerType(L.ALS.SynthGridLayer); +layerSystem.addLayerType(L.ALS.SynthLineLayer); layerSystem.addLayerType(L.ALS.SynthShapefileLayer); diff --git a/package.json b/package.json index 25e42afb..a600a79c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "synthflight", "productName": "SynthFlight", - "version": "0.0.13-alpha", + "version": "0.0.14-alpha", "description": "A fully client-side software for planning aerial photography", "main": "electronApp.js", "browser": "index.html", From f3b546bf30f789e41906c5bb1fa0069a3ab0e49f Mon Sep 17 00:00:00 2001 From: matafokka Date: Fri, 26 Nov 2021 18:52:24 +0300 Subject: [PATCH 7/7] Fixed export not working when there's 0 or 1 layer --- SynthBase/Hull.js | 26 +++++++++++++++++--------- SynthBase/SynthBaseLayer.js | 17 ++++++++++------- SynthBase/toGeoJSON.js | 7 ++++++- SynthGridLayer/misc.js | 1 - 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/SynthBase/Hull.js b/SynthBase/Hull.js index 76aeb458..2a5cb294 100644 --- a/SynthBase/Hull.js +++ b/SynthBase/Hull.js @@ -30,12 +30,10 @@ L.ALS.SynthBaseLayer.prototype.buildHull = function (path, color) { return; if (layers.length === 1) { - const path = layers[0].getLatLngs(); - connectionsGroup.addLayer(L.polyline( - [path[0], path[path.length - 1]], - lineOptions - )); + const latLngs = layers[0].getLatLngs(); + connectionsGroup.addLayer(L.polyline([latLngs[0], latLngs[latLngs.length - 1]], lineOptions)); connectionsGroup.addLayer(hullConnection); + path.hullConnections = []; return; } @@ -290,6 +288,19 @@ L.ALS.SynthBaseLayer.prototype.connectHull = function () { * @return {LatLng[][]} Cycle */ L.ALS.SynthBaseLayer.prototype.hullToCycles = function (path) { + + if (!path.hullConnections) + return undefined; + + if (path.hullConnections.length === 0) { + let airportPos = this._airportMarker.getLatLng(); + return [[ + airportPos, + ...path.pathGroup.getLayers()[0].getLatLngs(), + airportPos + ]]; + } + // The idea is to start with the first connection, find the starting point in it and for each connection // find the next point of a current connection and add points of its paths. We can do this because connections // are ordered. We'll also compare instances of the points because they're copied from the path, and it'll @@ -352,10 +363,7 @@ L.ALS.SynthBaseLayer.prototype.hullToCycles = function (path) { } } - cycle = [hullP2, ...afterAirport, ...beforeAirport, hullP2]; - - this.map.addLayer(L.polyline(cycle, {weight: 10, opacity: 0.5})); - return [cycle]; + return [[hullP2, ...afterAirport, ...beforeAirport, hullP2]]; } L.ALS.SynthBaseLayer.prototype.getOrderedPathFromHull = function (prevPoint, connection, copyTo, i = 0) { diff --git a/SynthBase/SynthBaseLayer.js b/SynthBase/SynthBaseLayer.js index e6aed009..22439579 100644 --- a/SynthBase/SynthBaseLayer.js +++ b/SynthBase/SynthBaseLayer.js @@ -110,11 +110,8 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot new L.ALS.Widgets.Number("lineThickness", "lineThickness", this, "setLineThickness").setMin(1).setMax(20).setValue(this._settings.lineThicknessValue), ); - for (let i = 0; i < this.paths.length; i++) { - this.addWidget( - new L.ALS.Widgets.Color(`color${i}`, this.paths[i].colorLabel, this, "_setPathsColor").setValue(this._settings[`color${i}`]), - ); - } + for (let i = 0; i < this.paths.length; i++) + this.addWidget(new L.ALS.Widgets.Color(`color${i}`, this.paths[i].colorLabel, this, "_setPathsColor").setValue(this._settings[`color${i}`])); this.addWidgets( new L.ALS.Widgets.Divider("div1"), @@ -400,9 +397,15 @@ L.ALS.SynthBaseLayer = L.ALS.Layer.extend(/** @lends L.ALS.SynthBaseLayer.protot * @return {LatLng[][]} Cycles */ onePerFlightToCycles: function (path) { - let layers = path.pathGroup.getLayers(), cycles = [], airportPos = this._airportMarker.getLatLng(); + let layers = path.pathGroup.getLayers(); + + if (layers.length === 0) + return undefined; + + let cycles = [], airportPos = this._airportMarker.getLatLng(); for (let layer of layers) - cycles.push([airportPos, ...layer.getLatLngs()], airportPos); + cycles.push([airportPos, ...layer.getLatLngs(), airportPos]); + return cycles; }, diff --git a/SynthBase/toGeoJSON.js b/SynthBase/toGeoJSON.js index 2cd1caa1..7cd74026 100644 --- a/SynthBase/toGeoJSON.js +++ b/SynthBase/toGeoJSON.js @@ -12,7 +12,12 @@ L.ALS.SynthBaseLayer.prototype.toGeoJSON = function (path1Metadata, path2Metadat pathNumber = 1, toMerge = []; for (let path of this.paths) { - let cycles = this[fn](path), metadata = pathNumber === 1 ? path1Metadata : path2Metadata; + let cycles = this[fn](path); + + if (!cycles) + return {}; + + let metadata = pathNumber === 1 ? path1Metadata : path2Metadata; for (let cycle of cycles) { diff --git a/SynthGridLayer/misc.js b/SynthGridLayer/misc.js index 4f758052..5d6a738e 100644 --- a/SynthGridLayer/misc.js +++ b/SynthGridLayer/misc.js @@ -166,7 +166,6 @@ L.ALS.SynthGridLayer.prototype._mergeSelectedPolygons = function () { poly.push([p.lng, p.lat]); poly.push(poly[0]); // We need to close the polygons to use MathTools stuff poly.zoneNumber = this.selectedPolygonsWidgets[id].getWidgetById("zoneNumber").getValue(); - poly.name = this.selectedPolygons[id].polygonName; // TODO: Remove after testing this.mergedPolygons.push(poly); }