first pass splitting js into commonjs modules
This commit is contained in:
parent
843cf26140
commit
17f9464208
2
Makefile
2
Makefile
@ -36,7 +36,7 @@ build:
|
|||||||
# This is to build all the stylesheets, etc
|
# This is to build all the stylesheets, etc
|
||||||
.PHONY: build-assets
|
.PHONY: build-assets
|
||||||
build-assets:
|
build-assets:
|
||||||
npm update
|
#npm update
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
.PHONY: install
|
.PHONY: install
|
||||||
|
3
package-lock.json
generated
3
package-lock.json
generated
@ -2,6 +2,9 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"Leaflet.Geodesic": {
|
||||||
|
"version": "git+https://git@github.com/henrythasler/Leaflet.Geodesic.git#7d710dd13020efb7c9901f5b3448a4541f507c56"
|
||||||
|
},
|
||||||
"abbrev": {
|
"abbrev": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
* @type {{render_airspace_map, render_live_map, render_route_map}}
|
* @type {{render_airspace_map, render_live_map, render_route_map}}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const phpvms = (function() {
|
let phpvms = {};
|
||||||
|
phpvms.map = (function() {
|
||||||
|
|
||||||
const PLAN_ROUTE_COLOR = '#36b123';
|
const PLAN_ROUTE_COLOR = '#36b123';
|
||||||
const ACTUAL_ROUTE_COLOR = '#172aea';
|
const ACTUAL_ROUTE_COLOR = '#172aea';
|
||||||
|
@ -41883,3 +41883,499 @@ module.exports = function() {}
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// This file is part of Leaflet.Geodesic.
|
||||||
|
// Copyright (C) 2017 Henry Thasler
|
||||||
|
// based on code by Chris Veness Copyright (C) 2014 https://github.com/chrisveness/geodesy
|
||||||
|
//
|
||||||
|
// Leaflet.Geodesic is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Leaflet.Geodesic is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Leaflet.Geodesic. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
/** Extend Number object with method to convert numeric degrees to radians */
|
||||||
|
if (typeof Number.prototype.toRadians === "undefined") {
|
||||||
|
Number.prototype.toRadians = function() {
|
||||||
|
return this * Math.PI / 180;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extend Number object with method to convert radians to numeric (signed) degrees */
|
||||||
|
if (typeof Number.prototype.toDegrees === "undefined") {
|
||||||
|
Number.prototype.toDegrees = function() {
|
||||||
|
return this * 180 / Math.PI;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var INTERSECT_LNG = 179.999; // Lng used for intersection and wrap around on map edges
|
||||||
|
|
||||||
|
L.Geodesic = L.Polyline.extend({
|
||||||
|
options: {
|
||||||
|
color: "blue",
|
||||||
|
steps: 10,
|
||||||
|
dash: 1,
|
||||||
|
wrap: true
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function(latlngs, options) {
|
||||||
|
this.options = this._merge_options(this.options, options);
|
||||||
|
this.options.dash = Math.max(1e-3, Math.min(1, parseFloat(this.options.dash) || 1));
|
||||||
|
this.datum = {};
|
||||||
|
this.datum.ellipsoid = {
|
||||||
|
a: 6378137,
|
||||||
|
b: 6356752.3142,
|
||||||
|
f: 1 / 298.257223563
|
||||||
|
}; // WGS-84
|
||||||
|
this._latlngs = this._generate_Geodesic(latlngs);
|
||||||
|
L.Polyline.prototype.initialize.call(this, this._latlngs, this.options);
|
||||||
|
},
|
||||||
|
|
||||||
|
setLatLngs: function(latlngs) {
|
||||||
|
this._latlngs = this._generate_Geodesic(latlngs);
|
||||||
|
L.Polyline.prototype.setLatLngs.call(this, this._latlngs);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates some statistic values of current geodesic multipolyline
|
||||||
|
* @returns (Object} Object with several properties (e.g. overall distance)
|
||||||
|
*/
|
||||||
|
getStats: function() {
|
||||||
|
let obj = {
|
||||||
|
distance: 0,
|
||||||
|
points: 0,
|
||||||
|
polygons: this._latlngs.length
|
||||||
|
}, poly, points;
|
||||||
|
|
||||||
|
for (poly = 0; poly < this._latlngs.length; poly++) {
|
||||||
|
obj.points += this._latlngs[poly].length;
|
||||||
|
for (points = 0; points < (this._latlngs[poly].length - 1); points++) {
|
||||||
|
obj.distance += this._vincenty_inverse(this._latlngs[poly][points],
|
||||||
|
this._latlngs[poly][points + 1]).distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates geodesic lines from geoJson. Replaces all current features of this instance.
|
||||||
|
* Supports LineString, MultiLineString and Polygon
|
||||||
|
* @param {Object} geojson - geosjon as object.
|
||||||
|
*/
|
||||||
|
geoJson: function(geojson) {
|
||||||
|
|
||||||
|
let normalized = L.GeoJSON.asFeature(geojson);
|
||||||
|
let features = normalized.type === "FeatureCollection" ? normalized.features : [
|
||||||
|
normalized
|
||||||
|
];
|
||||||
|
this._latlngs = [];
|
||||||
|
for (let feature of features) {
|
||||||
|
let geometry = feature.type === "Feature" ? feature.geometry :
|
||||||
|
feature,
|
||||||
|
coords = geometry.coordinates;
|
||||||
|
|
||||||
|
switch (geometry.type) {
|
||||||
|
case "LineString":
|
||||||
|
this._latlngs.push(this._generate_Geodesic([L.GeoJSON.coordsToLatLngs(
|
||||||
|
coords, 0)]));
|
||||||
|
break;
|
||||||
|
case "MultiLineString":
|
||||||
|
case "Polygon":
|
||||||
|
this._latlngs.push(this._generate_Geodesic(L.GeoJSON.coordsToLatLngs(
|
||||||
|
coords, 1)));
|
||||||
|
break;
|
||||||
|
case "Point":
|
||||||
|
case "MultiPoint":
|
||||||
|
console.log("Dude, points can't be drawn as geodesic lines...");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Drawing " + geometry.type +
|
||||||
|
" as a geodesic is not supported. Skipping...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
L.Polyline.prototype.setLatLngs.call(this, this._latlngs);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a great circle. Replaces all current lines.
|
||||||
|
* @param {Object} center - geographic position
|
||||||
|
* @param {number} radius - radius of the circle in metres
|
||||||
|
*/
|
||||||
|
createCircle: function(center, radius) {
|
||||||
|
let polylineIndex = 0;
|
||||||
|
let prev = {
|
||||||
|
lat: 0,
|
||||||
|
lng: 0,
|
||||||
|
brg: 0
|
||||||
|
};
|
||||||
|
let step;
|
||||||
|
|
||||||
|
this._latlngs = [];
|
||||||
|
this._latlngs[polylineIndex] = [];
|
||||||
|
|
||||||
|
let direct = this._vincenty_direct(L.latLng(center), 0, radius, this.options
|
||||||
|
.wrap);
|
||||||
|
prev = L.latLng(direct.lat, direct.lng);
|
||||||
|
this._latlngs[polylineIndex].push(prev);
|
||||||
|
for (step = 1; step <= this.options.steps;) {
|
||||||
|
direct = this._vincenty_direct(L.latLng(center), 360 / this.options
|
||||||
|
.steps * step, radius, this.options.wrap);
|
||||||
|
let gp = L.latLng(direct.lat, direct.lng);
|
||||||
|
if (Math.abs(gp.lng - prev.lng) > 180) {
|
||||||
|
let inverse = this._vincenty_inverse(prev, gp);
|
||||||
|
let sec = this._intersection(prev, inverse.initialBearing, {
|
||||||
|
lat: -89,
|
||||||
|
lng: ((gp.lng - prev.lng) > 0) ? -INTERSECT_LNG : INTERSECT_LNG
|
||||||
|
}, 0);
|
||||||
|
if (sec) {
|
||||||
|
this._latlngs[polylineIndex].push(L.latLng(sec.lat, sec.lng));
|
||||||
|
polylineIndex++;
|
||||||
|
this._latlngs[polylineIndex] = [];
|
||||||
|
prev = L.latLng(sec.lat, -sec.lng);
|
||||||
|
this._latlngs[polylineIndex].push(prev);
|
||||||
|
} else {
|
||||||
|
polylineIndex++;
|
||||||
|
this._latlngs[polylineIndex] = [];
|
||||||
|
this._latlngs[polylineIndex].push(gp);
|
||||||
|
prev = gp;
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._latlngs[polylineIndex].push(gp);
|
||||||
|
prev = gp;
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
L.Polyline.prototype.setLatLngs.call(this, this._latlngs);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a geodesic Polyline from given coordinates
|
||||||
|
* Note: dashed lines are under work
|
||||||
|
* @param {Object} latlngs - One or more polylines as an array. See Leaflet doc about Polyline
|
||||||
|
* @returns (Object} An array of arrays of geographical points.
|
||||||
|
*/
|
||||||
|
_generate_Geodesic: function(latlngs) {
|
||||||
|
let _geo = [], _geocnt = 0;
|
||||||
|
|
||||||
|
for (let poly = 0; poly < latlngs.length; poly++) {
|
||||||
|
_geo[_geocnt] = [];
|
||||||
|
let prev = L.latLng(latlngs[poly][0]);
|
||||||
|
for (let points = 0; points < (latlngs[poly].length - 1); points++) {
|
||||||
|
// use prev, so that wrapping behaves correctly
|
||||||
|
let pointA = prev;
|
||||||
|
let pointB = L.latLng(latlngs[poly][points + 1]);
|
||||||
|
if (pointA.equals(pointB)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let inverse = this._vincenty_inverse(pointA, pointB);
|
||||||
|
_geo[_geocnt].push(prev);
|
||||||
|
for (let s = 1; s <= this.options.steps;) {
|
||||||
|
let distance = inverse.distance / this.options.steps;
|
||||||
|
// dashed lines don't go the full distance between the points
|
||||||
|
let dist_mult = s - 1 + this.options.dash;
|
||||||
|
let direct = this._vincenty_direct(pointA, inverse.initialBearing, distance*dist_mult, this.options.wrap);
|
||||||
|
let gp = L.latLng(direct.lat, direct.lng);
|
||||||
|
if (Math.abs(gp.lng - prev.lng) > 180) {
|
||||||
|
let sec = this._intersection(pointA, inverse.initialBearing, {
|
||||||
|
lat: -89,
|
||||||
|
lng: ((gp.lng - prev.lng) > 0) ? -INTERSECT_LNG : INTERSECT_LNG
|
||||||
|
}, 0);
|
||||||
|
if (sec) {
|
||||||
|
_geo[_geocnt].push(L.latLng(sec.lat, sec.lng));
|
||||||
|
_geocnt++;
|
||||||
|
_geo[_geocnt] = [];
|
||||||
|
prev = L.latLng(sec.lat, -sec.lng);
|
||||||
|
_geo[_geocnt].push(prev);
|
||||||
|
} else {
|
||||||
|
_geocnt++;
|
||||||
|
_geo[_geocnt] = [];
|
||||||
|
_geo[_geocnt].push(gp);
|
||||||
|
prev = gp;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_geo[_geocnt].push(gp);
|
||||||
|
// Dashed lines start a new line
|
||||||
|
if (this.options.dash < 1){
|
||||||
|
_geocnt++;
|
||||||
|
// go full distance this time, to get starting point for next line
|
||||||
|
let direct_full = this._vincenty_direct(pointA, inverse.initialBearing, distance*s, this.options.wrap);
|
||||||
|
_geo[_geocnt] = [];
|
||||||
|
prev = L.latLng(direct_full.lat, direct_full.lng);
|
||||||
|
_geo[_geocnt].push(prev);
|
||||||
|
}
|
||||||
|
else prev = gp;
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_geocnt++;
|
||||||
|
}
|
||||||
|
return _geo;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vincenty direct calculation.
|
||||||
|
* based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {number} initialBearing - Initial bearing in degrees from north.
|
||||||
|
* @param {number} distance - Distance along bearing in metres.
|
||||||
|
* @returns (Object} Object including point (destination point), finalBearing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_vincenty_direct: function(p1, initialBearing, distance, wrap) {
|
||||||
|
var φ1 = p1.lat.toRadians(),
|
||||||
|
λ1 = p1.lng.toRadians();
|
||||||
|
var α1 = initialBearing.toRadians();
|
||||||
|
var s = distance;
|
||||||
|
|
||||||
|
var a = this.datum.ellipsoid.a,
|
||||||
|
b = this.datum.ellipsoid.b,
|
||||||
|
f = this.datum.ellipsoid.f;
|
||||||
|
|
||||||
|
var sinα1 = Math.sin(α1);
|
||||||
|
var cosα1 = Math.cos(α1);
|
||||||
|
|
||||||
|
var tanU1 = (1 - f) * Math.tan(φ1),
|
||||||
|
cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)),
|
||||||
|
sinU1 = tanU1 * cosU1;
|
||||||
|
var σ1 = Math.atan2(tanU1, cosα1);
|
||||||
|
var sinα = cosU1 * sinα1;
|
||||||
|
var cosSqα = 1 - sinα * sinα;
|
||||||
|
var uSq = cosSqα * (a * a - b * b) / (b * b);
|
||||||
|
var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 *
|
||||||
|
uSq)));
|
||||||
|
var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
|
||||||
|
|
||||||
|
var σ = s / (b * A),
|
||||||
|
σʹ, iterations = 0;
|
||||||
|
var sinσ, cosσ;
|
||||||
|
var cos2σM;
|
||||||
|
do {
|
||||||
|
cos2σM = Math.cos(2 * σ1 + σ);
|
||||||
|
sinσ = Math.sin(σ);
|
||||||
|
cosσ = Math.cos(σ);
|
||||||
|
var Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM *
|
||||||
|
cos2σM) -
|
||||||
|
B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM *
|
||||||
|
cos2σM)));
|
||||||
|
σʹ = σ;
|
||||||
|
σ = s / (b * A) + Δσ;
|
||||||
|
} while (Math.abs(σ - σʹ) > 1e-12 && ++iterations);
|
||||||
|
|
||||||
|
var x = sinU1 * sinσ - cosU1 * cosσ * cosα1;
|
||||||
|
var φ2 = Math.atan2(sinU1 * cosσ + cosU1 * sinσ * cosα1, (1 - f) *
|
||||||
|
Math.sqrt(sinα * sinα + x * x));
|
||||||
|
var λ = Math.atan2(sinσ * sinα1, cosU1 * cosσ - sinU1 * sinσ * cosα1);
|
||||||
|
var C = f / 16 * cosSqα * (4 + f * (4 - 3 * cosSqα));
|
||||||
|
var L = λ - (1 - C) * f * sinα *
|
||||||
|
(σ + C * sinσ * (cos2σM + C * cosσ * (-1 + 2 * cos2σM * cos2σM)));
|
||||||
|
|
||||||
|
var λ2;
|
||||||
|
if (wrap) {
|
||||||
|
λ2 = (λ1 + L + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180...+180
|
||||||
|
} else {
|
||||||
|
λ2 = (λ1 + L); // do not normalize
|
||||||
|
}
|
||||||
|
|
||||||
|
var revAz = Math.atan2(sinα, -x);
|
||||||
|
|
||||||
|
return {
|
||||||
|
lat: φ2.toDegrees(),
|
||||||
|
lng: λ2.toDegrees(),
|
||||||
|
finalBearing: revAz.toDegrees()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vincenty inverse calculation.
|
||||||
|
* based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {LatLng} p1 - Latitude/longitude of start point.
|
||||||
|
* @param {LatLng} p2 - Latitude/longitude of destination point.
|
||||||
|
* @returns {Object} Object including distance, initialBearing, finalBearing.
|
||||||
|
* @throws {Error} If formula failed to converge.
|
||||||
|
*/
|
||||||
|
_vincenty_inverse: function(p1, p2) {
|
||||||
|
var φ1 = p1.lat.toRadians(),
|
||||||
|
λ1 = p1.lng.toRadians();
|
||||||
|
var φ2 = p2.lat.toRadians(),
|
||||||
|
λ2 = p2.lng.toRadians();
|
||||||
|
|
||||||
|
var a = this.datum.ellipsoid.a,
|
||||||
|
b = this.datum.ellipsoid.b,
|
||||||
|
f = this.datum.ellipsoid.f;
|
||||||
|
|
||||||
|
var L = λ2 - λ1;
|
||||||
|
var tanU1 = (1 - f) * Math.tan(φ1),
|
||||||
|
cosU1 = 1 / Math.sqrt((1 + tanU1 * tanU1)),
|
||||||
|
sinU1 = tanU1 * cosU1;
|
||||||
|
var tanU2 = (1 - f) * Math.tan(φ2),
|
||||||
|
cosU2 = 1 / Math.sqrt((1 + tanU2 * tanU2)),
|
||||||
|
sinU2 = tanU2 * cosU2;
|
||||||
|
|
||||||
|
var λ = L,
|
||||||
|
λʹ, iterations = 0;
|
||||||
|
var cosSqα, sinσ, cos2σM, cosσ, σ, sinλ, cosλ;
|
||||||
|
do {
|
||||||
|
sinλ = Math.sin(λ);
|
||||||
|
cosλ = Math.cos(λ);
|
||||||
|
var sinSqσ = (cosU2 * sinλ) * (cosU2 * sinλ) + (cosU1 * sinU2 -
|
||||||
|
sinU1 * cosU2 * cosλ) * (cosU1 * sinU2 - sinU1 * cosU2 * cosλ);
|
||||||
|
sinσ = Math.sqrt(sinSqσ);
|
||||||
|
if (sinσ == 0) return 0; // co-incident points
|
||||||
|
cosσ = sinU1 * sinU2 + cosU1 * cosU2 * cosλ;
|
||||||
|
σ = Math.atan2(sinσ, cosσ);
|
||||||
|
var sinα = cosU1 * cosU2 * sinλ / sinσ;
|
||||||
|
cosSqα = 1 - sinα * sinα;
|
||||||
|
cos2σM = cosσ - 2 * sinU1 * sinU2 / cosSqα;
|
||||||
|
if (isNaN(cos2σM)) cos2σM = 0; // equatorial line: cosSqα=0 (§6)
|
||||||
|
var C = f / 16 * cosSqα * (4 + f * (4 - 3 * cosSqα));
|
||||||
|
λʹ = λ;
|
||||||
|
λ = L + (1 - C) * f * sinα * (σ + C * sinσ * (cos2σM + C * cosσ * (-
|
||||||
|
1 + 2 * cos2σM * cos2σM)));
|
||||||
|
} while (Math.abs(λ - λʹ) > 1e-12 && ++iterations < 100);
|
||||||
|
if (iterations >= 100) {
|
||||||
|
console.log("Formula failed to converge. Altering target position.");
|
||||||
|
return this._vincenty_inverse(p1, {
|
||||||
|
lat: p2.lat,
|
||||||
|
lng: p2.lng - 0.01
|
||||||
|
});
|
||||||
|
// throw new Error('Formula failed to converge');
|
||||||
|
}
|
||||||
|
|
||||||
|
var uSq = cosSqα * (a * a - b * b) / (b * b);
|
||||||
|
var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 *
|
||||||
|
uSq)));
|
||||||
|
var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
|
||||||
|
var Δσ = B * sinσ * (cos2σM + B / 4 * (cosσ * (-1 + 2 * cos2σM *
|
||||||
|
cos2σM) -
|
||||||
|
B / 6 * cos2σM * (-3 + 4 * sinσ * sinσ) * (-3 + 4 * cos2σM *
|
||||||
|
cos2σM)));
|
||||||
|
|
||||||
|
var s = b * A * (σ - Δσ);
|
||||||
|
|
||||||
|
var fwdAz = Math.atan2(cosU2 * sinλ, cosU1 * sinU2 - sinU1 * cosU2 *
|
||||||
|
cosλ);
|
||||||
|
var revAz = Math.atan2(cosU1 * sinλ, -sinU1 * cosU2 + cosU1 * sinU2 *
|
||||||
|
cosλ);
|
||||||
|
|
||||||
|
s = Number(s.toFixed(3)); // round to 1mm precision
|
||||||
|
return {
|
||||||
|
distance: s,
|
||||||
|
initialBearing: fwdAz.toDegrees(),
|
||||||
|
finalBearing: revAz.toDegrees()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the point of intersection of two paths defined by point and bearing.
|
||||||
|
* based on the work of Chris Veness (https://github.com/chrisveness/geodesy)
|
||||||
|
*
|
||||||
|
* @param {LatLon} p1 - First point.
|
||||||
|
* @param {number} brng1 - Initial bearing from first point.
|
||||||
|
* @param {LatLon} p2 - Second point.
|
||||||
|
* @param {number} brng2 - Initial bearing from second point.
|
||||||
|
* @returns {Object} containing lat/lng information of intersection.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* var p1 = LatLon(51.8853, 0.2545), brng1 = 108.55;
|
||||||
|
* var p2 = LatLon(49.0034, 2.5735), brng2 = 32.44;
|
||||||
|
* var pInt = LatLon.intersection(p1, brng1, p2, brng2); // pInt.toString(): 50.9078°N, 4.5084°E
|
||||||
|
*/
|
||||||
|
_intersection: function(p1, brng1, p2, brng2) {
|
||||||
|
// see http://williams.best.vwh.net/avform.htm#Intersection
|
||||||
|
|
||||||
|
var φ1 = p1.lat.toRadians(),
|
||||||
|
λ1 = p1.lng.toRadians();
|
||||||
|
var φ2 = p2.lat.toRadians(),
|
||||||
|
λ2 = p2.lng.toRadians();
|
||||||
|
var θ13 = Number(brng1).toRadians(),
|
||||||
|
θ23 = Number(brng2).toRadians();
|
||||||
|
var Δφ = φ2 - φ1,
|
||||||
|
Δλ = λ2 - λ1;
|
||||||
|
|
||||||
|
var δ12 = 2 * Math.asin(Math.sqrt(Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
|
||||||
|
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ /
|
||||||
|
2)));
|
||||||
|
if (δ12 == 0) return null;
|
||||||
|
|
||||||
|
// initial/final bearings between points
|
||||||
|
var θ1 = Math.acos((Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) /
|
||||||
|
(Math.sin(δ12) * Math.cos(φ1)));
|
||||||
|
if (isNaN(θ1)) θ1 = 0; // protect against rounding
|
||||||
|
var θ2 = Math.acos((Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) /
|
||||||
|
(Math.sin(δ12) * Math.cos(φ2)));
|
||||||
|
var θ12, θ21;
|
||||||
|
if (Math.sin(λ2 - λ1) > 0) {
|
||||||
|
θ12 = θ1;
|
||||||
|
θ21 = 2 * Math.PI - θ2;
|
||||||
|
} else {
|
||||||
|
θ12 = 2 * Math.PI - θ1;
|
||||||
|
θ21 = θ2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var α1 = (θ13 - θ12 + Math.PI) % (2 * Math.PI) - Math.PI; // angle 2-1-3
|
||||||
|
var α2 = (θ21 - θ23 + Math.PI) % (2 * Math.PI) - Math.PI; // angle 1-2-3
|
||||||
|
|
||||||
|
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return null; // infinite intersections
|
||||||
|
if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection
|
||||||
|
|
||||||
|
//α1 = Math.abs(α1);
|
||||||
|
//α2 = Math.abs(α2);
|
||||||
|
// ... Ed Williams takes abs of α1/α2, but seems to break calculation?
|
||||||
|
|
||||||
|
var α3 = Math.acos(-Math.cos(α1) * Math.cos(α2) +
|
||||||
|
Math.sin(α1) * Math.sin(α2) * Math.cos(δ12));
|
||||||
|
var δ13 = Math.atan2(Math.sin(δ12) * Math.sin(α1) * Math.sin(α2),
|
||||||
|
Math.cos(α2) + Math.cos(α1) * Math.cos(α3));
|
||||||
|
var φ3 = Math.asin(Math.sin(φ1) * Math.cos(δ13) +
|
||||||
|
Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13));
|
||||||
|
var Δλ13 = Math.atan2(Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1),
|
||||||
|
Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3));
|
||||||
|
var λ3 = λ1 + Δλ13;
|
||||||
|
λ3 = (λ3 + 3 * Math.PI) % (2 * Math.PI) - Math.PI; // normalise to -180..+180º
|
||||||
|
|
||||||
|
return {
|
||||||
|
lat: φ3.toDegrees(),
|
||||||
|
lng: λ3.toDegrees()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
|
||||||
|
* @param obj1
|
||||||
|
* @param obj2
|
||||||
|
* @returns obj3 a new object based on obj1 and obj2
|
||||||
|
*/
|
||||||
|
_merge_options: function(obj1, obj2) {
|
||||||
|
let obj3 = {};
|
||||||
|
for (let attrname in obj1) {
|
||||||
|
obj3[attrname] = obj1[attrname];
|
||||||
|
}
|
||||||
|
for (let attrname in obj2) {
|
||||||
|
obj3[attrname] = obj2[attrname];
|
||||||
|
}
|
||||||
|
return obj3;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
L.geodesic = function(latlngs, options) {
|
||||||
|
return new L.Geodesic(latlngs, options);
|
||||||
|
};
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
{
|
{
|
||||||
|
"/js/admin/app.js": "/js/admin/app.js?id=1cc53441cb171210435d",
|
||||||
|
"/js/frontend/app.js": "/js/frontend/app.js?id=650885634c97f0fe2deb",
|
||||||
"/assets/admin/vendor/paper-dashboard.css": "/assets/admin/vendor/paper-dashboard.css?id=3bbf7dd2a80739ab63b9",
|
"/assets/admin/vendor/paper-dashboard.css": "/assets/admin/vendor/paper-dashboard.css?id=3bbf7dd2a80739ab63b9",
|
||||||
"/assets/frontend/css/now-ui-kit.css": "/assets/frontend/css/now-ui-kit.css?id=9923ce002ceafb1d740a",
|
"/assets/frontend/css/now-ui-kit.css": "/assets/frontend/css/now-ui-kit.css?id=9923ce002ceafb1d740a",
|
||||||
|
"/js/admin/vendor.js": "/js/admin/vendor.js?id=1c5ddb087f24b16da40f",
|
||||||
|
"/js/admin/manifest.js": "/js/admin/manifest.js?id=ce6566a24afe6e358977",
|
||||||
"/assets/fonts/glyphicons-halflings-regular.woff2": "/assets/fonts/glyphicons-halflings-regular.woff2?id=b5b5055c6d812c0f9f0d",
|
"/assets/fonts/glyphicons-halflings-regular.woff2": "/assets/fonts/glyphicons-halflings-regular.woff2?id=b5b5055c6d812c0f9f0d",
|
||||||
"/assets/admin/fonts/glyphicons-halflings-regular.woff2": "/assets/admin/fonts/glyphicons-halflings-regular.woff2?id=b5b5055c6d812c0f9f0d",
|
"/assets/admin/fonts/glyphicons-halflings-regular.woff2": "/assets/admin/fonts/glyphicons-halflings-regular.woff2?id=b5b5055c6d812c0f9f0d",
|
||||||
|
"/assets/admin/js/icheck.js": "/assets/admin/js/icheck.js?id=14d0d9d9cae155580c5e",
|
||||||
|
"/assets/admin/js/bootstrap-editable.js": "/assets/admin/js/bootstrap-editable.js?id=48c6c28ec6f8b768f0d3",
|
||||||
|
"/assets/admin/js/bootstrap-editable.min.js": "/assets/admin/js/bootstrap-editable.min.js?id=3a6eeef3fbdb0d4cf5ba",
|
||||||
"/assets/admin/img/clear.png": "/assets/admin/img/clear.png?id=0e92f4c3efc6988a3c96",
|
"/assets/admin/img/clear.png": "/assets/admin/img/clear.png?id=0e92f4c3efc6988a3c96",
|
||||||
"/assets/admin/img/loading.gif": "/assets/admin/img/loading.gif?id=90a4b76b4f11558691f6",
|
"/assets/admin/img/loading.gif": "/assets/admin/img/loading.gif?id=90a4b76b4f11558691f6",
|
||||||
"/assets/admin/css/vendor.min.css": "/assets/admin/css/vendor.min.css?id=cae3dbc399c60b06b967",
|
"/assets/admin/css/vendor.min.css": "/assets/admin/css/vendor.min.css?id=cae3dbc399c60b06b967",
|
||||||
@ -10,7 +17,7 @@
|
|||||||
"/assets/admin/css/blue.png": "/assets/admin/css/blue.png?id=753a3c0dec86d3a38d9c",
|
"/assets/admin/css/blue.png": "/assets/admin/css/blue.png?id=753a3c0dec86d3a38d9c",
|
||||||
"/assets/admin/css/blue@2x.png": "/assets/admin/css/blue@2x.png?id=97da23d47b838cbd4bef",
|
"/assets/admin/css/blue@2x.png": "/assets/admin/css/blue@2x.png?id=97da23d47b838cbd4bef",
|
||||||
"/assets/system/js/jquery.js": "/assets/system/js/jquery.js?id=6a07da9fae934baf3f74",
|
"/assets/system/js/jquery.js": "/assets/system/js/jquery.js?id=6a07da9fae934baf3f74",
|
||||||
"/assets/system/js/vendor.js": "/assets/system/js/vendor.js?id=79d997fd74bdb7f37b0b",
|
"/assets/system/js/vendor.js": "/assets/system/js/vendor.js?id=0e6df5b264518fb37f48",
|
||||||
"/assets/system/css/vendor.min.css": "/assets/system/css/vendor.min.css?id=23451051c1ac99cae5fb",
|
"/assets/system/css/vendor.min.css": "/assets/system/css/vendor.min.css?id=23451051c1ac99cae5fb",
|
||||||
"/assets/system/css/installer.css": "/assets/system/css/installer.css?id=1101b178b0383008aaa1",
|
"/assets/system/css/installer.css": "/assets/system/css/installer.css?id=1101b178b0383008aaa1",
|
||||||
"/assets/system/js/installer-vendor.js": "/assets/system/js/installer-vendor.js?id=efb330ef732d2f856623"
|
"/assets/system/js/installer-vendor.js": "/assets/system/js/installer-vendor.js?id=efb330ef732d2f856623"
|
||||||
|
11
resources/js/admin/app.js
Normal file
11
resources/js/admin/app.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Any functionality required for the admin app
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('./../bootstrap');
|
||||||
|
require('eonasdan-bootstrap-datetimepicker');
|
||||||
|
|
||||||
|
require('./sidebar');
|
||||||
|
|
||||||
|
// Import the mapping function
|
||||||
|
window.phpvms.map = require('../maps/index');
|
90
resources/js/admin/sidebar.js
Normal file
90
resources/js/admin/sidebar.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
|
||||||
|
const jquery = require('jquery');
|
||||||
|
|
||||||
|
const getStorage = function (key) {
|
||||||
|
const st = window.localStorage.getItem(key);
|
||||||
|
|
||||||
|
console.log('storage: ', key, st);
|
||||||
|
if (_.isNil(st)) {
|
||||||
|
return {
|
||||||
|
"menu": [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(st);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveStorage = function (key, obj) {
|
||||||
|
console.log('save: ', key, obj);
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
const addItem = function (obj, item) {
|
||||||
|
|
||||||
|
if (_.isNil(obj)) {
|
||||||
|
obj = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = _.indexOf(obj, item);
|
||||||
|
if (index === -1) {
|
||||||
|
obj.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeItem = function (obj, item) {
|
||||||
|
|
||||||
|
if (_.isNil(obj)) {
|
||||||
|
obj = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = _.indexOf(obj, item);
|
||||||
|
if (index !== -1) {
|
||||||
|
console.log("removing", item);
|
||||||
|
obj.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
jquery(document).ready(function () {
|
||||||
|
|
||||||
|
$(".select2").select2();
|
||||||
|
|
||||||
|
let storage = getStorage("phpvms.admin");
|
||||||
|
|
||||||
|
// see what menu items should be open
|
||||||
|
for (let idx = 0; idx < storage.menu.length; idx++) {
|
||||||
|
const id = storage.menu[idx];
|
||||||
|
const elem = jquery(".collapse#" + id);
|
||||||
|
elem.addClass("in").trigger("show.bs.collapse");
|
||||||
|
|
||||||
|
const caret = jquery("a." + id + " b");
|
||||||
|
caret.addClass("pe-7s-angle-down");
|
||||||
|
caret.removeClass("pe-7s-angle-right");
|
||||||
|
}
|
||||||
|
|
||||||
|
jquery(".collapse").on("hide.bs.collapse", function () {
|
||||||
|
console.log('hiding');
|
||||||
|
const id = jquery(this).attr('id');
|
||||||
|
const elem = jquery("a." + id + " b");
|
||||||
|
elem.removeClass("pe-7s-angle-down");
|
||||||
|
elem.addClass("pe-7s-angle-right");
|
||||||
|
|
||||||
|
removeItem(storage.menu, id);
|
||||||
|
saveStorage("phpvms.admin", storage);
|
||||||
|
});
|
||||||
|
|
||||||
|
jquery(".collapse").on("show.bs.collapse", function () {
|
||||||
|
console.log('showing');
|
||||||
|
const id = jquery(this).attr('id');
|
||||||
|
const caret = jquery("a." + id + " b");
|
||||||
|
caret.addClass("pe-7s-angle-down");
|
||||||
|
caret.removeClass("pe-7s-angle-right");
|
||||||
|
|
||||||
|
addItem(storage.menu, id);
|
||||||
|
saveStorage("phpvms.admin", storage);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
27
resources/js/bootstrap.js
vendored
Normal file
27
resources/js/bootstrap.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Bootstrap any Javascript libraries required
|
||||||
|
*/
|
||||||
|
|
||||||
|
window._ = require('lodash');
|
||||||
|
window.Popper = require('popper.js').default;
|
||||||
|
window.$ = window.jquery = require('jquery');
|
||||||
|
window.select2 = require('select2');
|
||||||
|
window.pjax = require('pjax');
|
||||||
|
|
||||||
|
// Container for phpVMS specific functions
|
||||||
|
window.phpvms = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure Axios
|
||||||
|
*/
|
||||||
|
window.axios = require('axios');
|
||||||
|
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
let token = document.head.querySelector('meta[name="csrf-token"]');
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
|
||||||
|
} else {
|
||||||
|
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
|
||||||
|
}
|
5
resources/js/frontend/app.js
Normal file
5
resources/js/frontend/app.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
require('./../bootstrap');
|
||||||
|
|
||||||
|
// Import the mapping function
|
||||||
|
window.phpvms.map = require('../maps/index');
|
33
resources/js/maps/airspace_map.js
Normal file
33
resources/js/maps/airspace_map.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
|
const leaflet = require('leaflet');
|
||||||
|
|
||||||
|
import draw_base_map from './base_map';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a map with the airspace, etc around a given set of coords
|
||||||
|
* e.g, the airport map
|
||||||
|
* @param opts
|
||||||
|
*/
|
||||||
|
export default (opts) => {
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
render_elem: 'map',
|
||||||
|
overlay_elem: '',
|
||||||
|
lat: 0,
|
||||||
|
lon: 0,
|
||||||
|
zoom: 12,
|
||||||
|
layers: [],
|
||||||
|
set_marker: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let map = draw_base_map(opts);
|
||||||
|
const coords = [opts.lat, opts.lon];
|
||||||
|
console.log('Applying coords', coords);
|
||||||
|
|
||||||
|
map.setView(coords, opts.zoom);
|
||||||
|
if (opts.set_marker === true) {
|
||||||
|
leaflet.marker(coords).addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
71
resources/js/maps/base_map.js
Normal file
71
resources/js/maps/base_map.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const leaflet = require('leaflet');
|
||||||
|
|
||||||
|
export default (opts) => {
|
||||||
|
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
render_elem: 'map',
|
||||||
|
center: [29.98139, -95.33374],
|
||||||
|
zoom: 5,
|
||||||
|
maxZoom: 10,
|
||||||
|
layers: [],
|
||||||
|
set_marker: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let feature_groups = [];
|
||||||
|
/*var openaip_airspace_labels = new leaflet.TileLayer.WMS(
|
||||||
|
"http://{s}.tile.maps.openaip.net/geowebcache/service/wms", {
|
||||||
|
maxZoom: 14,
|
||||||
|
minZoom: 12,
|
||||||
|
layers: 'openaip_approved_airspaces_labels',
|
||||||
|
tileSize: 1024,
|
||||||
|
detectRetina: true,
|
||||||
|
subdomains: '12',
|
||||||
|
format: 'image/png',
|
||||||
|
transparent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
openaip_airspace_labels.addTo(map);*/
|
||||||
|
|
||||||
|
const opencyclemap_phys_osm = new leaflet.TileLayer(
|
||||||
|
'http://{s}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png?apikey=f09a38fa87514de4890fc96e7fe8ecb1', {
|
||||||
|
maxZoom: 14,
|
||||||
|
minZoom: 4,
|
||||||
|
format: 'image/png',
|
||||||
|
transparent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
feature_groups.push(opencyclemap_phys_osm);
|
||||||
|
|
||||||
|
/*const openaip_cached_basemap = new leaflet.TileLayer("http://{s}.tile.maps.openaip.net/geowebcache/service/tms/1.0.0/openaip_basemap@EPSG%3A900913@png/{z}/{x}/{y}.png", {
|
||||||
|
maxZoom: 14,
|
||||||
|
minZoom: 4,
|
||||||
|
tms: true,
|
||||||
|
detectRetina: true,
|
||||||
|
subdomains: '12',
|
||||||
|
format: 'image/png',
|
||||||
|
transparent: true
|
||||||
|
});
|
||||||
|
|
||||||
|
feature_groups.push(openaip_cached_basemap);
|
||||||
|
*/
|
||||||
|
|
||||||
|
const openaip_basemap_phys_osm = leaflet.featureGroup(feature_groups);
|
||||||
|
|
||||||
|
let map = leaflet.map('map', {
|
||||||
|
layers: [openaip_basemap_phys_osm],
|
||||||
|
center: opts.center,
|
||||||
|
zoom: opts.zoom,
|
||||||
|
scrollWheelZoom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const attrib = leaflet.control.attribution({position: 'bottomleft'});
|
||||||
|
attrib.addAttribution("<a href=\"https://www.thunderforest.com\" target=\"_blank\" style=\"\">Thunderforest</a>");
|
||||||
|
attrib.addAttribution("<a href=\"https://www.openaip.net\" target=\"_blank\" style=\"\">openAIP</a>");
|
||||||
|
attrib.addAttribution("<a href=\"https://www.openstreetmap.org/copyright\" target=\"_blank\" style=\"\">OpenStreetMap</a> contributors");
|
||||||
|
attrib.addAttribution("<a href=\"https://www.openweathermap.org\" target=\"_blank\" style=\"\">OpenWeatherMap</a>");
|
||||||
|
|
||||||
|
attrib.addTo(map);
|
||||||
|
|
||||||
|
return map;
|
||||||
|
};
|
4
resources/js/maps/config.js
Normal file
4
resources/js/maps/config.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export let
|
||||||
|
PLAN_ROUTE_COLOR = '#36b123',
|
||||||
|
ACTUAL_ROUTE_COLOR = '#172aea';
|
17
resources/js/maps/index.js
Normal file
17
resources/js/maps/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* All of the functionality required for maps
|
||||||
|
*/
|
||||||
|
|
||||||
|
window.L = require('leaflet');
|
||||||
|
require('Leaflet.Geodesic');
|
||||||
|
require('leaflet-rotatedmarker');
|
||||||
|
|
||||||
|
import render_airspace_map from './airspace_map';
|
||||||
|
import render_live_map from './live_map';
|
||||||
|
import render_route_map from './route_map';
|
||||||
|
|
||||||
|
export {
|
||||||
|
render_airspace_map,
|
||||||
|
render_live_map,
|
||||||
|
render_route_map,
|
||||||
|
};
|
124
resources/js/maps/live_map.js
Normal file
124
resources/js/maps/live_map.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
|
||||||
|
const _ = require('lodash');
|
||||||
|
const leaflet = require('leaflet');
|
||||||
|
|
||||||
|
import draw_base_map from './base_map';
|
||||||
|
import {ACTUAL_ROUTE_COLOR} from './config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the live map
|
||||||
|
* @param opts
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export default (opts) => {
|
||||||
|
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
update_uri: '/api/acars',
|
||||||
|
pirep_uri: '/api/pireps/{id}/acars',
|
||||||
|
positions: null,
|
||||||
|
render_elem: 'map',
|
||||||
|
aircraft_icon: '/assets/img/acars/aircraft.png',
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = draw_base_map(opts);
|
||||||
|
const aircraftIcon = leaflet.icon({
|
||||||
|
iconUrl: opts.aircraft_icon,
|
||||||
|
iconSize: [42, 42],
|
||||||
|
iconAnchor: [21, 21],
|
||||||
|
});
|
||||||
|
|
||||||
|
let layerFlights = null;
|
||||||
|
let layerSelFlight = null;
|
||||||
|
let layerSelFlightFeature = null;
|
||||||
|
let layerSelFlightLayer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a flight is clicked on, show the path, etc for that flight
|
||||||
|
* @param feature
|
||||||
|
* @param layer
|
||||||
|
*/
|
||||||
|
const onFlightClick = (feature, layer) => {
|
||||||
|
|
||||||
|
const uri = opts.pirep_uri.replace('{id}', feature.properties.pirep_id);
|
||||||
|
|
||||||
|
const flight_route = $.ajax({
|
||||||
|
url: uri,
|
||||||
|
dataType: "json",
|
||||||
|
error: console.log
|
||||||
|
});
|
||||||
|
|
||||||
|
$.when(flight_route).done((routeJson) => {
|
||||||
|
if (layerSelFlight !== null) {
|
||||||
|
map.removeLayer(layerSelFlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
layerSelFlight = leaflet.geodesic([], {
|
||||||
|
weight: 7,
|
||||||
|
opacity: 0.9,
|
||||||
|
color: ACTUAL_ROUTE_COLOR,
|
||||||
|
wrap: false,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
layerSelFlight.geoJson(routeJson.line);
|
||||||
|
|
||||||
|
layerSelFlightFeature = feature;
|
||||||
|
layerSelFlightLayer = layer;
|
||||||
|
//map.fitBounds(layerSelFlight.getBounds());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateMap = () => {
|
||||||
|
|
||||||
|
console.log('reloading flights from acars...');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX UPDATE
|
||||||
|
*/
|
||||||
|
|
||||||
|
let flights = $.ajax({
|
||||||
|
url: opts.update_uri,
|
||||||
|
dataType: "json",
|
||||||
|
error: console.log
|
||||||
|
});
|
||||||
|
|
||||||
|
$.when(flights).done(function (flightGeoJson) {
|
||||||
|
|
||||||
|
if (layerFlights !== null) {
|
||||||
|
layerFlights.clearLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
layerFlights = leaflet.geoJSON(flightGeoJson, {
|
||||||
|
onEachFeature: (feature, layer) => {
|
||||||
|
|
||||||
|
layer.on({
|
||||||
|
click: (e) => {
|
||||||
|
onFlightClick(feature, layer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let popup_html = "";
|
||||||
|
if (feature.properties && feature.properties.popup) {
|
||||||
|
popup_html += feature.properties.popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.bindPopup(popup_html);
|
||||||
|
},
|
||||||
|
pointToLayer: function (feature, latlon) {
|
||||||
|
return leaflet.marker(latlon, {
|
||||||
|
icon: aircraftIcon,
|
||||||
|
rotationAngle: feature.properties.heading
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
layerFlights.addTo(map);
|
||||||
|
|
||||||
|
if (layerSelFlight !== null) {
|
||||||
|
onFlightClick(layerSelFlightFeature, layerSelFlightLayer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateMap();
|
||||||
|
setInterval(updateMap, 10000);
|
||||||
|
};
|
123
resources/js/maps/route_map.js
Normal file
123
resources/js/maps/route_map.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const leaflet = require('leaflet');
|
||||||
|
|
||||||
|
import draw_base_map from './base_map';
|
||||||
|
import {ACTUAL_ROUTE_COLOR, PLAN_ROUTE_COLOR} from './config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show some popup text when a feature is clicked on
|
||||||
|
* @param feature
|
||||||
|
* @param layer
|
||||||
|
*/
|
||||||
|
export const onFeaturePointClick = (feature, layer) => {
|
||||||
|
let popup_html = "";
|
||||||
|
if (feature.properties && feature.properties.popup) {
|
||||||
|
popup_html += feature.properties.popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
layer.bindPopup(popup_html);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show each point as a marker
|
||||||
|
* @param feature
|
||||||
|
* @param latlng
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
export const pointToLayer = (feature, latlng) => {
|
||||||
|
return leaflet.circleMarker(latlng, {
|
||||||
|
radius: 12,
|
||||||
|
fillColor: "#ff7800",
|
||||||
|
color: "#000",
|
||||||
|
weight: 1,
|
||||||
|
opacity: 1,
|
||||||
|
fillOpacity: 0.8
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param opts
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export default (opts) => {
|
||||||
|
|
||||||
|
opts = _.defaults(opts, {
|
||||||
|
route_points: null,
|
||||||
|
planned_route_line: null,
|
||||||
|
actual_route_points: null,
|
||||||
|
actual_route_line: null,
|
||||||
|
render_elem: 'map',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(opts);
|
||||||
|
|
||||||
|
let map = draw_base_map(opts);
|
||||||
|
|
||||||
|
let geodesicLayer = leaflet.geodesic([], {
|
||||||
|
weight: 7,
|
||||||
|
opacity: 0.9,
|
||||||
|
color: PLAN_ROUTE_COLOR,
|
||||||
|
steps: 50,
|
||||||
|
wrap: false,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
geodesicLayer.geoJson(opts.planned_route_line);
|
||||||
|
|
||||||
|
try {
|
||||||
|
map.fitBounds(geodesicLayer.getBounds());
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the route points after
|
||||||
|
if (opts.route_points !== null) {
|
||||||
|
let route_points = leaflet.geoJSON(opts.route_points, {
|
||||||
|
onEachFeature: onFeaturePointClick,
|
||||||
|
pointToLayer: pointToLayer,
|
||||||
|
style: {
|
||||||
|
"color": PLAN_ROUTE_COLOR,
|
||||||
|
"weight": 5,
|
||||||
|
"opacity": 0.65,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
route_points.addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* draw the actual route
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (opts.actual_route_line !== null && opts.actual_route_line.features.length > 0) {
|
||||||
|
let geodesicLayer = leaflet.geodesic([], {
|
||||||
|
weight: 7,
|
||||||
|
opacity: 0.9,
|
||||||
|
color: ACTUAL_ROUTE_COLOR,
|
||||||
|
steps: 50,
|
||||||
|
wrap: false,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
geodesicLayer.geoJson(opts.actual_route_line);
|
||||||
|
|
||||||
|
try {
|
||||||
|
map.fitBounds(geodesicLayer.getBounds());
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.actual_route_points !== null && opts.actual_route_points.features.length > 0) {
|
||||||
|
let route_points = leaflet.geoJSON(opts.actual_route_points, {
|
||||||
|
onEachFeature: onFeaturePointClick,
|
||||||
|
pointToLayer: pointToLayer,
|
||||||
|
style: {
|
||||||
|
"color": ACTUAL_ROUTE_COLOR,
|
||||||
|
"weight": 5,
|
||||||
|
"opacity": 0.65,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
route_points.addTo(map);
|
||||||
|
}
|
||||||
|
};
|
@ -189,5 +189,6 @@ $(document).ready(function () {
|
|||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@yield('scripts')
|
@yield('scripts')
|
||||||
</html>
|
</html>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
phpvms.render_route_map({
|
phpvms.map.render_route_map({
|
||||||
route_points: {!! json_encode($map_features['route_points']) !!},
|
route_points: {!! json_encode($map_features['route_points']) !!},
|
||||||
planned_route_line: {!! json_encode($map_features['planned_route_line']); !!},
|
planned_route_line: {!! json_encode($map_features['planned_route_line']); !!},
|
||||||
});
|
});
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
phpvms.render_route_map({
|
phpvms.map.render_route_map({
|
||||||
route_points: {!! json_encode($map_features['planned_rte_points']) !!},
|
route_points: {!! json_encode($map_features['planned_rte_points']) !!},
|
||||||
planned_route_line: {!! json_encode($map_features['planned_rte_line']); !!},
|
planned_route_line: {!! json_encode($map_features['planned_rte_line']); !!},
|
||||||
actual_route_line: {!! json_encode($map_features['actual_route_line']); !!},
|
actual_route_line: {!! json_encode($map_features['actual_route_line']); !!},
|
||||||
|
@ -22,6 +22,8 @@ mix.webpackConfig({
|
|||||||
|
|
||||||
mix.copy('node_modules/bootstrap3/fonts/*.woff2', 'public/assets/fonts/');
|
mix.copy('node_modules/bootstrap3/fonts/*.woff2', 'public/assets/fonts/');
|
||||||
mix.copy('node_modules/bootstrap3/fonts/*.woff2', 'public/assets/admin/fonts/');
|
mix.copy('node_modules/bootstrap3/fonts/*.woff2', 'public/assets/admin/fonts/');
|
||||||
|
mix.copy('node_modules/icheck/icheck.js', 'public/assets/admin/js/');
|
||||||
|
mix.copy('node_modules/x-editable/dist/bootstrap3-editable/js/*', 'public/assets/admin/js/');
|
||||||
mix.copy('node_modules/x-editable/dist/bootstrap3-editable/img/*', 'public/assets/admin/img/');
|
mix.copy('node_modules/x-editable/dist/bootstrap3-editable/img/*', 'public/assets/admin/img/');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,3 +134,22 @@ mix.sass('public/assets/frontend/sass/now-ui-kit.scss',
|
|||||||
compressed: true
|
compressed: true
|
||||||
})
|
})
|
||||||
.sourceMaps();
|
.sourceMaps();
|
||||||
|
|
||||||
|
|
||||||
|
// These should go into the separate vendor.js file
|
||||||
|
const extract = [
|
||||||
|
'lodash',
|
||||||
|
'popper.js',
|
||||||
|
'jquery',
|
||||||
|
'select2',
|
||||||
|
'pjax',
|
||||||
|
'leaflet',
|
||||||
|
'Leaflet.Geodesic',
|
||||||
|
'leaflet-rotatedmarker'
|
||||||
|
];
|
||||||
|
|
||||||
|
mix.js('resources/js/frontend/app.js', 'public/js/frontend')
|
||||||
|
.extract(extract);
|
||||||
|
|
||||||
|
|
||||||
|
mix.js('resources/js/admin/app.js', 'public/js/admin');
|
||||||
|
Loading…
Reference in New Issue
Block a user