diff --git a/js/geozone.js b/js/geozone.js index ee6e90d04..1f9d006f8 100644 --- a/js/geozone.js +++ b/js/geozone.js @@ -1,5 +1,7 @@ 'use strict' +const { Shape } = require("three"); + const GeozoneType = Object.freeze({ EXCULSIVE: 0, INCLUSIVE: 1, @@ -176,6 +178,71 @@ let Geozone = function (type, shape, minAltitude, maxAltitude, sealevelRef, radi vertices = []; } + self.isCounterClockwise = () => { + + if (shape == GeozoneShapes.CIRCULAR) { + return true; + } + + let area = 0; + for (let i = 0; i < vertices.length; i++) { + const x1 = vertices[i].getLat(); + const y1 = vertices[i].getLon(); + const next = vertices[(i + 1) % vertices.length]; + const x2 = next.getLat(); + const y2 = next.getLon(); + area += x1 * y2 - y1 * x2; + } + return area < 0; + } + + self.isComplex = () => { + if (shape == GeozoneShapes.CIRCULAR) { + return false; + } + + // Intersection of two lines https://en.wikipedia.org/wiki/Line-line_intersection + function doLinesIntersect(line1Start, line1End, line2Start, line2End) + { + const s1 = line1End.x - line1Start.x; + const t1 = -(line2End.x - line2Start.x); + const r1 = line2Start.x - line1Start.x; + + const s2 = line1End.y - line1Start.y; + const t2 = -(line2End.y - line2Start.y); + const r2 = line2Start.y - line1Start.y; + + // Use Cramer's rule for the solution of the system of linear equations + const determ = s1 * t2 - t1 * s2; + if (determ == 0) { // No solution + return false; + } + + const s0 = (r1 * t2 - t1 * r2) / determ; + const t0 = (s1 * r2 - r1 * s2) / determ; + + if (s0 == 0 && t0 == 0) { + return false; + } + return !(s0 <= 0 || s0 >= 1 || t0 <= 0 || t0 >= 1) + } + + for (var i = 0; i < vertices.length; i++) { + const a = {x: vertices[i].getLat(), y: vertices[i].getLon()}; + const next = vertices[(i + 1) % vertices.length]; + const b = {x: next.getLat(), y: next.getLon()}; + for (var j = i + 2; j < vertices.length; j++) { + const c = {x: vertices[j].getLat(), y: vertices[j].getLon()};; + const next2 = vertices[(j + 1) % vertices.length]; + const d = {x: next2.getLat(), y: next2.getLon()}; + if (doLinesIntersect(a, b, c, d)) { + return true; + } + } + } + return false; + } + self.getElevationFromServer = async function (lon, lat, globalSettings) { let elevation = "N/A"; if (globalSettings.mapProviderType == 'bing') { diff --git a/js/geozoneCollection.js b/js/geozoneCollection.js index 66b5e666a..1754b966b 100644 --- a/js/geozoneCollection.js +++ b/js/geozoneCollection.js @@ -140,7 +140,7 @@ let GeozoneCollection = function() { buffer.push(zone.getVerticesCount()); } } else { - buffer = [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + buffer = [id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } return buffer; diff --git a/locale/en/messages.json b/locale/en/messages.json index bdddc9834..f5841778e 100644 --- a/locale/en/messages.json +++ b/locale/en/messages.json @@ -4772,6 +4772,9 @@ "featureGEOZONE": { "message": "Geozone" }, + "geozone": { + "message": "Geozone" + }, "featureGEOZONETip": { "message": "Virtual perimeters for geographical areas (also called geofence) with automatically triggered actions when the perimeters are violated." }, @@ -4829,6 +4832,21 @@ "missionGeozoneAvailableVertices": { "message": "Available Vertices:" }, + "geozoneInvalidzone": { + "message": "Invalid Geozone(s) detected:" + }, + "gezoneInvalidReasonNotCC": { + "message": "Not counter clockwise" + }, + "gezoneInvalidReasonComplex": { + "message": "Complex" + }, + "gezoneInvalidReasonMinMaxAlt": { + "message": "Max. Alt <= Min. Alt" + }, + "geozoneUnableToSave": { + "message": "Unable to save geozones: Invalid zones" + }, "missionMultiMissionHead": { "message": "Multi Missions" }, diff --git a/tabs/mission_control.html b/tabs/mission_control.html index 41341702b..e6f8643d0 100644 --- a/tabs/mission_control.html +++ b/tabs/mission_control.html @@ -66,6 +66,11 @@
+
+ +
+ +
diff --git a/tabs/mission_control.js b/tabs/mission_control.js index 0aa4d62f6..6aa3eb879 100644 --- a/tabs/mission_control.js +++ b/tabs/mission_control.js @@ -76,6 +76,7 @@ TABS.mission_control.initialize = function (callback) { let $waypointOptionsTableBody; let selectedGeozone; let $geozoneContent; + let invalidGeoZones = false; let isGeozoneEnabeld = false; let settings = {speed: 0, alt: 5000, safeRadiusSH: 50, fwApproachAlt: 60, fwLandAlt: 5, maxDistSH: 0, fwApproachLength: 0, fwLoiterRadius: 0, bingDemModel: false}; @@ -152,6 +153,7 @@ TABS.mission_control.initialize = function (callback) { } $('#infoGeozoneMissionWarning').hide(); + $('#infoGeozoneInvalid').hide(); $safehomeContentBox = $('#SafehomeContentBox'); $waypointOptionsTableBody = $('#waypointOptionsTableBody'); $geozoneContent = $('#geozoneContent'); @@ -919,7 +921,7 @@ TABS.mission_control.initialize = function (callback) { cleanGeozoneLayers(); if (!selectedGeozone) { cleanGeozoneLines(); - geozoneMissionWarning(); + geozoneWarning(); return; } @@ -931,7 +933,7 @@ TABS.mission_control.initialize = function (callback) { }); } }); - geozoneMissionWarning(); + geozoneWarning(); } function cleanGeozoneLines() { @@ -949,13 +951,45 @@ TABS.mission_control.initialize = function (callback) { geozoneMarkers = []; } - function geozoneMissionWarning() { + function geozoneWarning() { if (markers.length >= 1 && geozoneMarkers.length >= 1) { $('#infoGeozoneMissionWarning').show(); } else { $('#infoGeozoneMissionWarning').hide(); } + + $('#geozoneInvalidContent').empty(); + invalidGeoZones = false; + for (var i = 0; i < FC.GEOZONES.geozoneCount(); i++) { + const zone = FC.GEOZONES.at(i); + + var reasons = [] + if (!zone.isCounterClockwise()) { + reasons.push(i18n.getMessage("gezoneInvalidReasonNotCC")); + } + + if (zone.isComplex()) { + reasons.push(i18n.getMessage("gezoneInvalidReasonComplex")); + } + + if (zone.getMaxAltitude() <= zone.getMinAltitude()) { + reasons.push(i18n.getMessage("gezoneInvalidReasonMinMaxAlt")); + } + + + + if (reasons.length > 0) { + $('#geozoneInvalidContent').append(`
${i18n.getMessage("geozone")} ${zone.getNumber() + 1}: ${reasons.join(", ")}

`); + invalidGeoZones = true; + } + } + + if (invalidGeoZones) { + $('#infoGeozoneInvalid').show(); + } else { + $('#infoGeozoneInvalid').hide(); + } } function updateGeozoneInfo() { @@ -1621,7 +1655,7 @@ TABS.mission_control.initialize = function (callback) { }); } - geozoneMissionWarning(); + geozoneWarning(); } function redrawLayer() { @@ -1734,7 +1768,7 @@ TABS.mission_control.initialize = function (callback) { } }); }); - + geozoneWarning(); } else { $('#geozoneContentBox').hide(); } @@ -1992,6 +2026,9 @@ TABS.mission_control.initialize = function (callback) { var handleShowGeozoneSettings = function () { $('#missionPlannerGeozones').fadeIn(300); + if (!selectedGeozone) { + selectedGeozone = FC.GEOZONES.first(); + } renderGeozoneOptions(); renderGeozonesOnMap(); }; @@ -3363,6 +3400,12 @@ TABS.mission_control.initialize = function (callback) { }); $('#saveEepromGeozoneButton').on('click', event => { + + if (invalidGeoZones) { + GUI.alert(i18n.getMessage("geozoneUnableToSave")); + return; + } + if (confirm(i18n.getMessage("missionGeozoneReboot"))) { $(event.currentTarget).addClass('disabled'); GUI.log('Start of sending Geozones');