diff --git a/client/copy.bat b/client/copy.bat index f163f70e..cacd14c4 100644 --- a/client/copy.bat +++ b/client/copy.bat @@ -1,2 +1,3 @@ copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js +copy .\\node_modules\\leaflet-path-drag\\dist\\L.Path.Drag.js .\\public\\javascripts\\L.Path.Drag.js \ No newline at end of file diff --git a/client/demo.js b/client/demo.js index 5a76f8d1..be34fce8 100644 --- a/client/demo.js +++ b/client/demo.js @@ -1,641 +1,250 @@ +var enc = new TextEncoder(); const DEMO_UNIT_DATA = { - ["1"]:{ - baseData: { - AI: false, - name: "KC-135", - unitName: "Olympus 1-1 aka Mr. Very long name", - groupName: "Group 2", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.20, - longitude: -115.80, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 50, - flags: {Human: false}, - ammo: [ - { - count: 4, - desc: { - displayName: "AIM-120" - } - }, - { - count: 2, - desc: { - displayName: "AIM-7" - } - } - ], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Holding", - currentState: "Idle", - activePath: undefined, - desiredSpeed: 400, - desiredSpeedType: "CAS", - desiredAltitude: 3000, - desiredAltitudeType: "ASL", - isTanker: false, - - }, - optionsData: { - ROE: "Designated", - reactionToThreat: "Abort", - } + ["1"]:{ alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "KC-135", unitName: "Cool guy 1-1", groupName: "Cool group 1", state: 3, task: "Being cool!", + hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, heading: 45, isTanker: true, isAWACS: false, onOff: true, followRoads: false, fuel: 50, + desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, + formationOffset: { x: 0, y: 0, z: 0 }, + targetID: 2, + targetPosition: { lat: 0, lng: 0, alt: 0 }, + ROE: 2, + reactionToThreat: 1, + emissionsCountermeasures: 1, + TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 }, + radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, + generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, + ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], + contacts: [], + activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ] }, - ["2"]:{ - baseData: { - AI: true, - name: "KC-135", - unitName: "Olympus 1-2", - groupName: "Group 3", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.2, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 300, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "Designated", - reactionToThreat: "Abort", - } - }, - ["3"]:{ - baseData: { - AI: true, - name: "M-60", - unitName: "Olympus 1-3", - groupName: "Group 4", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.175, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000, - onOff: false - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["4"]:{ - baseData: { - AI: true, - name: "2S6 Tunguska", - unitName: "Olympus 1-4", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.175, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["5"]:{ - baseData: { - AI: true, - name: "M-60", - unitName: "Olympus 1-3", - groupName: "Group 1", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.15, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["6"]:{ - baseData: { - AI: true, - name: "M-60", - unitName: "Olympus 1-4", - groupName: "Group 1", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.15, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["7"]:{ - baseData: { - AI: true, - name: "CVN-75 Very long name", - unitName: "Olympus 1-7", - groupName: "Group 1", - alive: true, - category: "NavyUnit", - }, - flightData: { - latitude: 37.125, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["8"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-8", - groupName: "Group 1", - alive: true, - category: "NavyUnit", - }, - flightData: { - latitude: 37.125, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["9"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-9", - groupName: "Group 1", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.10, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["10"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-10", - groupName: "Group 1", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.10, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["11"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-11", - groupName: "Group 1", - alive: true, - category: "Missile", - }, - flightData: { - latitude: 37.075, - longitude: -115.80, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["12"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-12", - groupName: "Group 1", - alive: true, - category: "Missile", - }, - flightData: { - latitude: 37.075, - longitude: -115.75, - altitude: 2000, - heading: 0.6, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["13"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-11", - groupName: "Group 1", - alive: true, - category: "Bomb", - }, - flightData: { - latitude: 37.05, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["14"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-12", - groupName: "Group 1", - alive: true, - category: "Bomb", - }, - flightData: { - latitude: 37.05, - longitude: -115.75, - altitude: 2000, - heading: 0.6, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } + ["2"]:{ alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "KC-135", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool", + hasTask: false, position: { lat: 36.9, lng: -116, alt: 1000 }, speed: 200, heading: 0, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, + desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, + formationOffset: { x: 0, y: 0, z: 0 }, + targetID: 0, + targetPosition: { lat: 38, lng: -117, alt: 1000 }, + ROE: 2, + reactionToThreat: 1, + emissionsCountermeasures: 1, + TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 }, + radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, + generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, + ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], + contacts: [{ID: 1, detectionMethod: 4}], + activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ] } } class DemoDataGenerator { - constructor(unitsNumber) + constructor() { - this.demoUnits = this.generateRandomUnitsDemoData(unitsNumber); + } units(req, res){ - var ret = this.demoUnits; - for (let ID in this.demoUnits["units"]){ - this.demoUnits["units"][ID].flightData.latitude += 0.00001; + var array = new Uint8Array(); + var time = Date.now(); + array = this.concat(array, this.uint64ToByteArray(BigInt(time))); + for (let idx in DEMO_UNIT_DATA) { + const unit = DEMO_UNIT_DATA[idx]; + array = this.concat(array, this.uint32ToByteArray(idx)); + array = this.appendString(array, "Aircraft", 1); + array = this.appendUint8(array, unit.alive, 2); + array = this.appendUint8(array, unit.human, 3); + array = this.appendUint8(array, unit.controlled, 4); + array = this.appendUint16(array, unit.coalition, 5); + array = this.appendUint8(array, unit.country, 6); + array = this.appendString(array, unit.name, 7); + array = this.appendString(array, unit.unitName, 8); + array = this.appendString(array, unit.groupName, 9); + array = this.appendUint8(array, unit.state, 10); + array = this.appendString(array, unit.task, 11); + array = this.appendUint8(array, unit.hasTask, 12); + array = this.appendCoordinates(array, unit.position, 13); + array = this.appendDouble(array, unit.speed, 14); + array = this.appendDouble(array, unit.heading, 15); + array = this.appendUint8(array, unit.isTanker, 16); + array = this.appendUint8(array, unit.isAWACS, 17); + array = this.appendUint8(array, unit.onOff, 18); + array = this.appendUint8(array, unit.followRoads, 19); + array = this.appendUint16(array, unit.fuel, 20); + array = this.appendDouble(array, unit.desiredSpeed, 21); + array = this.appendUint8(array, unit.desiredSpeedType, 22); + array = this.appendDouble(array, unit.desiredAltitude, 23); + array = this.appendUint8(array, unit.desiredAltitudeType, 24); + array = this.appendUint32(array, unit.leaderID, 25); + array = this.appendOffset(array, unit.formationOffset, 26); + array = this.appendUint32(array, unit.targetID, 27); + array = this.appendCoordinates(array, unit.targetPosition, 28); + array = this.appendUint8(array, unit.ROE, 29); + array = this.appendUint8(array, unit.reactionToThreat, 30); + array = this.appendUint8(array, unit.emissionsCountermeasures, 31); + array = this.appendTACAN(array, unit.TACAN, 32); + array = this.appendRadio(array, unit.radio, 33); + array = this.appendRadio(array, unit.generalSettings, 34); + array = this.appendAmmo(array, unit.ammo, 35); + array = this.appendContacts(array, unit.contacts, 36); + array = this.appendActivePath(array, unit.activePath, 37); + array = this.concat(array, this.uint8ToByteArray(255)); } - ret.time = Date.now(); - res.send(JSON.stringify(ret)); + res.end(Buffer.from(array, 'binary')); }; + + concat(array1, array2) { + var mergedArray = new Uint8Array(array1.length + array2.length); + mergedArray.set(array1); + mergedArray.set(array2, array1.length); + return mergedArray; + } + + uint8ToByteArray(number) { + var buffer = new ArrayBuffer(1); + var longNum = new Uint8Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + uint16ToByteArray(number) { + var buffer = new ArrayBuffer(2); + var longNum = new Uint16Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + uint32ToByteArray(number) { + var buffer = new ArrayBuffer(4); + var longNum = new Uint32Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + uint64ToByteArray(number) { + var buffer = new ArrayBuffer(8); + var longNum = new BigUint64Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + doubleToByteArray(number) { + var buffer = new ArrayBuffer(8); + var longNum = new Float64Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + appendUint8(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint8ToByteArray(number)); + return array; + } + + appendUint16(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(number)); + return array; + } + + appendUint32(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint32ToByteArray(number)); + return array; + } + + appendDouble(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.doubleToByteArray(number)); + return array; + } + + appendCoordinates(array, coordinates, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.doubleToByteArray(coordinates.lat)); + array = this.concat(array, this.doubleToByteArray(coordinates.lng)); + array = this.concat(array, this.doubleToByteArray(coordinates.alt)); + return array; + } + + appendOffset(array, offset, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.doubleToByteArray(offset.x)); + array = this.concat(array, this.doubleToByteArray(offset.y)); + array = this.concat(array, this.doubleToByteArray(offset.z)); + return array; + } + + appendString(array, string, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(string.length)); + array = this.concat(array, enc.encode(string)); + return array; + } + + padString(string, length) { + while (string.length < length) + string += " "; + return string.substring(0, length); + } + + appendTACAN(array, TACAN, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint8ToByteArray(TACAN.isOn)); + array = this.concat(array, this.uint8ToByteArray(TACAN.channel)); + array = this.concat(array, enc.encode(TACAN.XY)); + array = this.concat(array, enc.encode(this.padString(TACAN.callsign, 4))); + return array; + } + + appendRadio(array, radio, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint32ToByteArray(radio.frequency)); + array = this.concat(array, this.uint8ToByteArray(radio.callsign)); + array = this.concat(array, this.uint8ToByteArray(radio.callsignNumber)); + return array; + } + + appendGeneralSettings(array, generalSettings, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAA)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAfterburner)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAG)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAirWpn)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitJettison)); + return array; + } + + appendAmmo(array, ammo, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(ammo.length)); + ammo.forEach((element) => { + array = this.concat(array, this.uint16ToByteArray(element.quantity)); + array = this.concat(array, enc.encode(this.padString(element.name, 33))); + array = this.concat(array, this.uint8ToByteArray(element.guidance)); + array = this.concat(array, this.uint8ToByteArray(element.category)); + array = this.concat(array, this.uint8ToByteArray(element.missileCategory)); + }) + return array; + } + + appendContacts(array, contacts, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(contacts.length)); + contacts.forEach((element) => { + array = this.concat(array, this.uint32ToByteArray(element.ID)); + array = this.concat(array, this.uint8ToByteArray(element.detectionMethod)); + }) + return array; + } + + appendActivePath(array, activePath, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(activePath.length)); + activePath.forEach((element) => { + array = this.concat(array, this.doubleToByteArray(element.lat)); + array = this.concat(array, this.doubleToByteArray(element.lng)); + array = this.concat(array, this.doubleToByteArray(element.alt)); + }) + return array; + } logs(req, res){ var ret = {logs: {}}; @@ -696,10 +305,6 @@ class DemoDataGenerator { res.send(JSON.stringify(ret)); } - generateRandomUnitsDemoData(unitsNumber) - { - return {"units": DEMO_UNIT_DATA}; - } } module.exports = DemoDataGenerator; \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 2fc981a0..f773cef7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "DCSOlympus", "version": "v0.3.0-alpha", "dependencies": { + "@turf/turf": "^6.5.0", "@types/formatcoords": "^1.1.0", "@types/geojson": "^7946.0.10", "@types/leaflet": "^1.9.0", @@ -19,6 +20,7 @@ "formatcoords": "^1.1.3", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", + "leaflet-path-drag": "*", "leaflet.nauticscale": "^1.1.0", "morgan": "~1.9.1", "save": "^2.9.0" @@ -1837,6 +1839,1570 @@ "tslib": "^2.5.0" } }, + "node_modules/@turf/along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", + "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/angle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-6.5.0.tgz", + "integrity": "sha512-4pXMbWhFofJJAOvTMCns6N4C8CMd5Ih4O2jSAG9b3dDHakj3O4yN1+Zbm+NUei+eVEZ9gFeVp9svE3aMDenIkw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/area": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz", + "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-clip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-6.5.0.tgz", + "integrity": "sha512-F6PaIRF8WMp8EmgU/Ke5B1Y6/pia14UAYB5TiBC668w5rVVjy5L8rTm/m2lEkkDMHlzoP9vNY4pxpNthE7rLcQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz", + "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", + "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bezier-spline": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-6.5.0.tgz", + "integrity": "sha512-vokPaurTd4PF96rRgGVm6zYYC5r1u98ZsG+wZEv9y3kJTuJRX/O3xIY2QnTGTdbVmAJN1ouOsD0RoZYaVoXORQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-clockwise": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz", + "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-contains": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz", + "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-crosses": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-6.5.0.tgz", + "integrity": "sha512-gvshbTPhAHporTlQwBJqyfW+2yV8q/mOTxG6PzRVl6ARsqNoqYQWkd4MLug7OmAqVyBzLK3201uAeBjxbGw0Ng==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-disjoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-6.5.0.tgz", + "integrity": "sha512-rZ2ozlrRLIAGo2bjQ/ZUu4oZ/+ZjGvLkN5CKXSKBcu6xFO6k2bgqeM8a1836tAW+Pqp/ZFsTA5fZHsJZvP2D5g==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-equal": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-6.5.0.tgz", + "integrity": "sha512-cY0M3yoLC26mhAnjv1gyYNQjn7wxIXmL2hBmI/qs8g5uKuC2hRWi13ydufE3k4x0aNRjFGlg41fjoYLwaVF+9Q==", + "dependencies": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "geojson-equality": "0.1.6" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-intersects": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-6.5.0.tgz", + "integrity": "sha512-nIxkizjRdjKCYFQMnml6cjPsDOBCThrt+nkqtSEcxkKMhAQj5OO7o2CecioNTaX8EayqwMGVKcsz27oP4mKPTw==", + "dependencies": { + "@turf/boolean-disjoint": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-6.5.0.tgz", + "integrity": "sha512-8btMIdnbXVWUa1M7D4shyaSGxLRw6NjMcqKBcsTXcZdnaixl22k7ar7BvIzkaRYN3SFECk9VGXfLncNS3ckQUw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-equality": "0.1.6" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-parallel": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-6.5.0.tgz", + "integrity": "sha512-aSHJsr1nq9e5TthZGZ9CZYeXklJyRgR5kCLm5X4urz7+MotMOp/LsGOsvKvK9NeUl9+8OUmfMn8EFTT8LkcvIQ==", + "dependencies": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz", + "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-within": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-6.5.0.tgz", + "integrity": "sha512-YQB3oU18Inx35C/LU930D36RAVe7LDXk1kWsQ8mLmuqYn9YdPsDQTMTkLJMhoQ8EbN7QTdy333xRQ4MYgToteQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/buffer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz", + "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "d3-geo": "1.7.1", + "turf-jsts": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz", + "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-mean": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-6.5.0.tgz", + "integrity": "sha512-AAX6f4bVn12pTVrMUiB9KrnV94BgeBKpyg3YpfnEbBpkN/znfVhL8dG8IxMAxAoSZ61Zt9WLY34HfENveuOZ7Q==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-median": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-6.5.0.tgz", + "integrity": "sha512-dT8Ndu5CiZkPrj15PBvslpuf01ky41DEYEPxS01LOxp5HOUHXp1oJxsPxvc+i/wK4BwccPNzU1vzJ0S4emd1KQ==", + "dependencies": { + "@turf/center-mean": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-of-mass": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-6.5.0.tgz", + "integrity": "sha512-EWrriU6LraOfPN7m1jZi+1NLTKNkuIsGLZc2+Y8zbGruvUW+QV7K0nhf7iZWutlxHXTBqEXHbKue/o79IumAsQ==", + "dependencies": { + "@turf/centroid": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz", + "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", + "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "dependencies": { + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clean-coords": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-6.5.0.tgz", + "integrity": "sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz", + "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-6.5.0.tgz", + "integrity": "sha512-Y6gfnTJzQ1hdLfCsyd5zApNbfLIxYEpmDibHUqR5z03Lpe02pa78JtgrgUNt1seeO/aJ4TG1NLN8V5gOrHk04g==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-dbscan": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz", + "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "density-clustering": "1.3.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-kmeans": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz", + "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "skmeans": "0.9.7" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/collect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-6.5.0.tgz", + "integrity": "sha512-4dN/T6LNnRg099m97BJeOcTA5fSI8cu87Ydgfibewd2KQwBexO69AnjEFqfPX3Wj+Zvisj1uAVIZbPmSSrZkjg==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "rbush": "2.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/combine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-6.5.0.tgz", + "integrity": "sha512-Q8EIC4OtAcHiJB3C4R+FpB4LANiT90t17uOd851qkM2/o6m39bfN5Mv0PWqMZIHWrrosZqRqoY9dJnzz/rJxYQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/concave": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-6.5.0.tgz", + "integrity": "sha512-I/sUmUC8TC5h/E2vPwxVht+nRt+TnXIPRoztDFvS8/Y0+cBDple9inLSo9nnPXMXidrBlGXZ9vQx/BjZUJgsRQ==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/tin": "^6.5.0", + "topojson-client": "3.x", + "topojson-server": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/convex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-6.5.0.tgz", + "integrity": "sha512-x7ZwC5z7PJB0SBwNh7JCeCNx7Iu+QSrH7fYgK0RhhNop13TqUlvHMirMLRgf2db1DqUetrAO2qHJeIuasquUWg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "concaveman": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/difference": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz", + "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/dissolve": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-6.5.0.tgz", + "integrity": "sha512-WBVbpm9zLTp0Bl9CE35NomTaOL1c4TQCtEoO43YaAhNEWJOOIhZMFJyr8mbvYruKl817KinT3x7aYjjCMjTAsQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", + "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance-weight": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-6.5.0.tgz", + "integrity": "sha512-a8qBKkgVNvPKBfZfEJZnC3DV7dfIsC3UIdpRci/iap/wZLH41EmS90nM+BokAJflUHYy8PqE44wySGWHN1FXrQ==", + "dependencies": { + "@turf/centroid": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz", + "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/transform-rotate": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/envelope": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-6.5.0.tgz", + "integrity": "sha512-9Z+FnBWvOGOU4X+fMZxYFs1HjFlkKqsddLuMknRaqcJd6t+NIv5DWvPtDL8ATD2GEExYDiFLwMdckfr1yqJgHA==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/explode": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-6.5.0.tgz", + "integrity": "sha512-6cSvMrnHm2qAsace6pw9cDmK2buAlw8+tjeJVXMfMyY+w7ZUi1rprWMsY92J7s2Dar63Bv09n56/1V7+tcj52Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flatten": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz", + "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-6.5.0.tgz", + "integrity": "sha512-oyikJFNjt2LmIXQqgOGLvt70RgE2lyzPMloYWM7OR5oIFGRiBvqVD2hA6MNw6JewIm30fWZ8DQJw1NHXJTJPbg==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/great-circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-6.5.0.tgz", + "integrity": "sha512-7ovyi3HaKOXdFyN7yy1yOMa8IyOvV46RC1QOQTT+RYUN8ke10eyqExwBpL9RFUPvlpoTzoYbM/+lWPogQlFncg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/hex-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-6.5.0.tgz", + "integrity": "sha512-Ln3tc2tgZT8etDOldgc6e741Smg1CsMKAz1/Mlel+MEL5Ynv2mhx3m0q4J9IB1F3a4MNjDeVvm8drAaf9SF33g==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/interpolate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-6.5.0.tgz", + "integrity": "sha512-LSH5fMeiGyuDZ4WrDJNgh81d2DnNDUVJtuFryJFup8PV8jbs46lQGfI3r1DJ2p1IlEJIz3pmAZYeTfMMoeeohw==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/triangle-grid": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz", + "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isobands": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-6.5.0.tgz", + "integrity": "sha512-4h6sjBPhRwMVuFaVBv70YB7eGz+iw0bhPRnp+8JBdX1UPJSXhoi/ZF2rACemRUr0HkdVB/a1r9gC32vn5IAEkw==", + "dependencies": { + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isolines": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-6.5.0.tgz", + "integrity": "sha512-6ElhiLCopxWlv4tPoxiCzASWt/jMRvmp6mRYrpzOm3EUl75OhHKa/Pu6Y9nWtCMmVC/RcWtiiweUocbPLZLm0A==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/kinks": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-6.5.0.tgz", + "integrity": "sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/length": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz", + "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-arc": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-6.5.0.tgz", + "integrity": "sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==", + "dependencies": { + "@turf/circle": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-chunk": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-6.5.0.tgz", + "integrity": "sha512-i1FGE6YJaaYa+IJesTfyRRQZP31QouS+wh/pa6O3CC0q4T7LtHigyBSYjrbjSLfn2EVPYGlPCMFEqNWCOkC6zg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", + "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-offset": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-6.5.0.tgz", + "integrity": "sha512-CEXZbKgyz8r72qRvPchK0dxqsq8IQBdH275FE6o4MrBkzMcoZsfSjghtXzKaz9vvro+HfIXal0sTk2mqV1lQTw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-6.5.0.tgz", + "integrity": "sha512-xHOaWLd0hkaC/1OLcStCpfq55lPHpPNadZySDXYiYjEz5HXr1oKmtMYpn0wGizsLwrOixRdEp+j7bL8dPt4ojQ==", + "dependencies": { + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "deep-equal": "1.x", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-segment": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", + "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-6.5.0.tgz", + "integrity": "sha512-vDqJxve9tBHhOaVVFXqVjF5qDzGtKWviyjbyi2QnSnxyFAmLlLnBfMX8TLQCAf2GxHibB95RO5FBE6I2KVPRuw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice-along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-6.5.0.tgz", + "integrity": "sha512-KHJRU6KpHrAj+BTgTNqby6VCTnDzG6a1sJx/I3hNvqMBLvWVA2IrkR9L9DtsQsVY63IBwVdQDqiwCuZLDQh4Ng==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-split": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-6.5.0.tgz", + "integrity": "sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/truncate": "^6.5.0", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-to-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-6.5.0.tgz", + "integrity": "sha512-qYBuRCJJL8Gx27OwCD1TMijM/9XjRgXH/m/TyuND4OXedBpIWlK5VbTIO2gJ8OCfznBBddpjiObLBrkuxTpN4Q==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/mask": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-6.5.0.tgz", + "integrity": "sha512-RQha4aU8LpBrmrkH8CPaaoAfk0Egj5OuXtv6HuCQnHeGNOQt3TQVibTA3Sh4iduq4EPxnZfDjgsOeKtrCA19lg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/midpoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz", + "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/moran-index": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-6.5.0.tgz", + "integrity": "sha512-ItsnhrU2XYtTtTudrM8so4afBCYWNaB0Mfy28NZwLjB5jWuAsvyV+YW+J88+neK/ougKMTawkmjQqodNJaBeLQ==", + "dependencies": { + "@turf/distance-weight": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-6.5.0.tgz", + "integrity": "sha512-fguV09QxilZv/p94s8SMsXILIAMiaXI5PATq9d7YWijLxWUj6Q/r43kxyoi78Zmwwh1Zfqz9w+bCYUAxZ5+euA==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", + "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-6.5.0.tgz", + "integrity": "sha512-PXV7cN0BVzUZdjj6oeb/ESnzXSfWmEMrsfZSDRgqyZ9ytdiIj/eRsnOXLR13LkTdXVOJYDBuf7xt1mLhM4p6+Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "object-assign": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/planepoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-6.5.0.tgz", + "integrity": "sha512-R3AahA6DUvtFbka1kcJHqZ7DMHmPXDEQpbU5WaglNn7NaCQg9HB0XM0ZfqWcd5u92YXV+Gg8QhC8x5XojfcM4Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-6.5.0.tgz", + "integrity": "sha512-Iq38lFokNNtQJnOj/RBKmyt6dlof0yhaHEDELaWHuECm1lIZLY3ZbVMwbs+nXkwTAHjKfS/OtMheUBkw+ee49w==", + "dependencies": { + "@turf/boolean-within": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-on-feature": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-6.5.0.tgz", + "integrity": "sha512-bDpuIlvugJhfcF/0awAQ+QI6Om1Y1FFYE8Y/YdxGRongivix850dTeXCo0mDylFdWFPGDo7Mmh9Vo4VxNwW/TA==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-line-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz", + "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/points-within-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz", + "integrity": "sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-smooth": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-6.5.0.tgz", + "integrity": "sha512-LO/X/5hfh/Rk4EfkDBpLlVwt3i6IXdtQccDT9rMjXEP32tRgy0VMFmdkNaXoGlSSKf/1mGqLl4y4wHd86DqKbg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-tangents": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-6.5.0.tgz", + "integrity": "sha512-sB4/IUqJMYRQH9jVBwqS/XDitkEfbyqRy+EH/cMRJURTg78eHunvJ708x5r6umXsbiUyQU4eqgPzEylWEQiunw==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz", + "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygonize": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-6.5.0.tgz", + "integrity": "sha512-a/3GzHRaCyzg7tVYHo43QUChCspa99oK4yPqooVIwTC61npFzdrmnywMv0S+WZjHZwK37BrFJGFrZGf6ocmY5w==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz", + "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/random": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-6.5.0.tgz", + "integrity": "sha512-8Q25gQ/XbA7HJAe+eXp4UhcXM9aOOJFaxZ02+XSNwMvY8gtWSCBLVqRcW4OhqilgZ8PeuQDWgBxeo+BIqqFWFQ==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rectangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-6.5.0.tgz", + "integrity": "sha512-yQZ/1vbW68O2KsSB3OZYK+72aWz/Adnf7m2CMKcC+aq6TwjxZjAvlbCOsNUnMAuldRUVN1ph6RXMG4e9KEvKvg==", + "dependencies": { + "@turf/boolean-intersects": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rewind": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz", + "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==", + "dependencies": { + "@turf/boolean-clockwise": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz", + "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz", + "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz", + "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sample": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-6.5.0.tgz", + "integrity": "sha512-kSdCwY7el15xQjnXYW520heKUrHwRvnzx8ka4eYxX9NFeOxaFITLW2G7UtXb6LJK8mmPXI8Aexv23F2ERqzGFg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sector": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-6.5.0.tgz", + "integrity": "sha512-cYUOkgCTWqa23SOJBqxoFAc/yGCUsPRdn/ovbRTn1zNTm/Spmk6hVB84LCKOgHqvSF25i0d2kWqpZDzLDdAPbw==", + "dependencies": { + "@turf/circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/shortest-path": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-6.5.0.tgz", + "integrity": "sha512-4de5+G7+P4hgSoPwn+SO9QSi9HY5NEV/xRJ+cmoFVRwv2CDsuOPDheHKeuIAhKyeKDvPvPt04XYWbac4insJMg==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/transform-scale": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/simplify": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-6.5.0.tgz", + "integrity": "sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==", + "dependencies": { + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-6.5.0.tgz", + "integrity": "sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-6.5.0.tgz", + "integrity": "sha512-mlR0ayUdA+L4c9h7p4k3pX6gPWHNGuZkt2c5II1TJRmhLkW2557d6b/Vjfd1z9OVaajb1HinIs1FMSAPXuuUrA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/rectangle-grid": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/standard-deviational-ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-6.5.0.tgz", + "integrity": "sha512-02CAlz8POvGPFK2BKK8uHGUk/LXb0MK459JVjKxLC2yJYieOBTqEbjP0qaWhiBhGzIxSMaqe8WxZ0KvqdnstHA==", + "dependencies": { + "@turf/center-mean": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tag": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-6.5.0.tgz", + "integrity": "sha512-XwlBvrOV38CQsrNfrxvBaAPBQgXMljeU0DV8ExOyGM7/hvuGHJw3y8kKnQ4lmEQcmcrycjDQhP7JqoRv8vFssg==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tesselate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-6.5.0.tgz", + "integrity": "sha512-M1HXuyZFCfEIIKkglh/r5L9H3c5QTEsnMBoZOFQiRnGPGmJWcaBissGb7mTFX2+DKE7FNWXh4TDnZlaLABB0dQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "earcut": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-6.5.0.tgz", + "integrity": "sha512-YLYikRzKisfwj7+F+Tmyy/LE3d2H7D4kajajIfc9mlik2+esG7IolsX/+oUz1biguDYsG0DUA8kVYXDkobukfg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-rotate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz", + "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==", + "dependencies": { + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-scale": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz", + "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-translate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz", + "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/triangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-6.5.0.tgz", + "integrity": "sha512-2jToUSAS1R1htq4TyLQYPTIsoy6wg3e3BQXjm2rANzw4wPQCXGOxrur1Fy9RtzwqwljlC7DF4tg0OnWr8RjmfA==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/truncate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-6.5.0.tgz", + "integrity": "sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/turf": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-6.5.0.tgz", + "integrity": "sha512-ipMCPnhu59bh92MNt8+pr1VZQhHVuTMHklciQURo54heoxRzt1neNYZOBR6jdL+hNsbDGAECMuIpAutX+a3Y+w==", + "dependencies": { + "@turf/along": "^6.5.0", + "@turf/angle": "^6.5.0", + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/bbox-clip": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/bearing": "^6.5.0", + "@turf/bezier-spline": "^6.5.0", + "@turf/boolean-clockwise": "^6.5.0", + "@turf/boolean-contains": "^6.5.0", + "@turf/boolean-crosses": "^6.5.0", + "@turf/boolean-disjoint": "^6.5.0", + "@turf/boolean-equal": "^6.5.0", + "@turf/boolean-intersects": "^6.5.0", + "@turf/boolean-overlap": "^6.5.0", + "@turf/boolean-parallel": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/buffer": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/center-mean": "^6.5.0", + "@turf/center-median": "^6.5.0", + "@turf/center-of-mass": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/circle": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/clusters": "^6.5.0", + "@turf/clusters-dbscan": "^6.5.0", + "@turf/clusters-kmeans": "^6.5.0", + "@turf/collect": "^6.5.0", + "@turf/combine": "^6.5.0", + "@turf/concave": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/difference": "^6.5.0", + "@turf/dissolve": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/distance-weight": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/flatten": "^6.5.0", + "@turf/flip": "^6.5.0", + "@turf/great-circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/interpolate": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/isobands": "^6.5.0", + "@turf/isolines": "^6.5.0", + "@turf/kinks": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/line-chunk": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-offset": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/line-slice": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/line-split": "^6.5.0", + "@turf/line-to-polygon": "^6.5.0", + "@turf/mask": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/midpoint": "^6.5.0", + "@turf/moran-index": "^6.5.0", + "@turf/nearest-point": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/nearest-point-to-line": "^6.5.0", + "@turf/planepoint": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/point-on-feature": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0", + "@turf/polygon-smooth": "^6.5.0", + "@turf/polygon-tangents": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0", + "@turf/polygonize": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/random": "^6.5.0", + "@turf/rewind": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0", + "@turf/sample": "^6.5.0", + "@turf/sector": "^6.5.0", + "@turf/shortest-path": "^6.5.0", + "@turf/simplify": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/standard-deviational-ellipse": "^6.5.0", + "@turf/tag": "^6.5.0", + "@turf/tesselate": "^6.5.0", + "@turf/tin": "^6.5.0", + "@turf/transform-rotate": "^6.5.0", + "@turf/transform-scale": "^6.5.0", + "@turf/transform-translate": "^6.5.0", + "@turf/triangle-grid": "^6.5.0", + "@turf/truncate": "^6.5.0", + "@turf/union": "^6.5.0", + "@turf/unkink-polygon": "^6.5.0", + "@turf/voronoi": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/union": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz", + "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/unkink-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-6.5.0.tgz", + "integrity": "sha512-8QswkzC0UqKmN1DT6HpA9upfa1HdAA5n6bbuzHy8NJOX8oVizVAqfEPY0wqqTgboDjmBR4yyImsdPGUl3gZ8JQ==", + "dependencies": { + "@turf/area": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "rbush": "^2.0.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/voronoi": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-6.5.0.tgz", + "integrity": "sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "d3-voronoi": "1.1.2" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/formatcoords": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/formatcoords/-/formatcoords-1.1.0.tgz", @@ -2634,7 +4200,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -2757,6 +4322,11 @@ "source-map": "~0.5.3" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2777,6 +4347,30 @@ "typedarray": "^0.0.6" } }, + "node_modules/concaveman": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz", + "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==", + "dependencies": { + "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", + "robust-predicates": "^2.0.4", + "tinyqueue": "^2.0.3" + } + }, + "node_modules/concaveman/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/concaveman/node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/concurrently": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", @@ -2976,6 +4570,24 @@ "node": "*" } }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==" + }, "node_modules/dash-ast": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", @@ -3003,6 +4615,37 @@ "ms": "2.0.0" } }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dependencies": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/defined": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", @@ -3012,6 +4655,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/density-clustering": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz", + "integrity": "sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ==" + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3108,6 +4756,11 @@ "readable-stream": "^2.0.2" } }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3451,8 +5104,15 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -3463,6 +5123,44 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-equality": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz", + "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==", + "dependencies": { + "deep-equal": "^1.0.0" + } + }, + "node_modules/geojson-rbush": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", + "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "dependencies": { + "@turf/bbox": "*", + "@turf/helpers": "6.x", + "@turf/meta": "6.x", + "@types/geojson": "7946.0.8", + "rbush": "^3.0.1" + } + }, + "node_modules/geojson-rbush/node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "node_modules/geojson-rbush/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/geojson-rbush/node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -3482,7 +5180,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -3549,7 +5246,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -3586,11 +5282,21 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3602,7 +5308,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -3820,7 +5525,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3880,6 +5584,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3934,6 +5652,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", @@ -4060,6 +5793,11 @@ "leaflet": ">=1.7.0" } }, + "node_modules/leaflet-path-drag": { + "version": "1.8.0-beta.3", + "resolved": "https://registry.npmjs.org/leaflet-path-drag/-/leaflet-path-drag-1.8.0-beta.3.tgz", + "integrity": "sha512-kpZ6sPOKlR+m+VChIzZZ7XFH4C+VGTrAxgnM4UN5iYl7lJ00iDOxS+r717bDf/xFFHB6n2jpUp9vvzjjteMpeQ==" + }, "node_modules/leaflet.nauticscale": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/leaflet.nauticscale/-/leaflet.nauticscale-1.1.0.tgz", @@ -4399,11 +6137,33 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -4572,6 +6332,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, + "node_modules/polygon-clipping": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz", + "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==", + "dependencies": { + "splaytree": "^3.1.0" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -4658,6 +6431,11 @@ "node": ">=0.4.x" } }, + "node_modules/quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4699,6 +6477,14 @@ "node": ">= 0.8" } }, + "node_modules/rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "dependencies": { + "quickselect": "^1.0.1" + } + }, "node_modules/read-only-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", @@ -4777,6 +6563,22 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -4851,6 +6653,11 @@ "inherits": "^2.0.1" } }, + "node_modules/robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==" + }, "node_modules/rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -5004,6 +6811,11 @@ "semver": "bin/semver.js" } }, + "node_modules/skmeans": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", + "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==" + }, "node_modules/sortablejs": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", @@ -5025,6 +6837,11 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "node_modules/splaytree": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", + "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==" + }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -5279,6 +7096,11 @@ "node": ">=0.6.0" } }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5300,6 +7122,30 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "geo2topo": "bin/geo2topo" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -5366,6 +7212,11 @@ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true }, + "node_modules/turf-jsts": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", + "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA==" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6976,6 +8827,1249 @@ "tslib": "^2.5.0" } }, + "@turf/along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", + "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/angle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-6.5.0.tgz", + "integrity": "sha512-4pXMbWhFofJJAOvTMCns6N4C8CMd5Ih4O2jSAG9b3dDHakj3O4yN1+Zbm+NUei+eVEZ9gFeVp9svE3aMDenIkw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + } + }, + "@turf/area": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz", + "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/bbox-clip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-6.5.0.tgz", + "integrity": "sha512-F6PaIRF8WMp8EmgU/Ke5B1Y6/pia14UAYB5TiBC668w5rVVjy5L8rTm/m2lEkkDMHlzoP9vNY4pxpNthE7rLcQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/bbox-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz", + "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", + "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/bezier-spline": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-6.5.0.tgz", + "integrity": "sha512-vokPaurTd4PF96rRgGVm6zYYC5r1u98ZsG+wZEv9y3kJTuJRX/O3xIY2QnTGTdbVmAJN1ouOsD0RoZYaVoXORQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-clockwise": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz", + "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-contains": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz", + "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-crosses": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-6.5.0.tgz", + "integrity": "sha512-gvshbTPhAHporTlQwBJqyfW+2yV8q/mOTxG6PzRVl6ARsqNoqYQWkd4MLug7OmAqVyBzLK3201uAeBjxbGw0Ng==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + } + }, + "@turf/boolean-disjoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-6.5.0.tgz", + "integrity": "sha512-rZ2ozlrRLIAGo2bjQ/ZUu4oZ/+ZjGvLkN5CKXSKBcu6xFO6k2bgqeM8a1836tAW+Pqp/ZFsTA5fZHsJZvP2D5g==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + } + }, + "@turf/boolean-equal": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-6.5.0.tgz", + "integrity": "sha512-cY0M3yoLC26mhAnjv1gyYNQjn7wxIXmL2hBmI/qs8g5uKuC2hRWi13ydufE3k4x0aNRjFGlg41fjoYLwaVF+9Q==", + "requires": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "geojson-equality": "0.1.6" + } + }, + "@turf/boolean-intersects": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-6.5.0.tgz", + "integrity": "sha512-nIxkizjRdjKCYFQMnml6cjPsDOBCThrt+nkqtSEcxkKMhAQj5OO7o2CecioNTaX8EayqwMGVKcsz27oP4mKPTw==", + "requires": { + "@turf/boolean-disjoint": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/boolean-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-6.5.0.tgz", + "integrity": "sha512-8btMIdnbXVWUa1M7D4shyaSGxLRw6NjMcqKBcsTXcZdnaixl22k7ar7BvIzkaRYN3SFECk9VGXfLncNS3ckQUw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-equality": "0.1.6" + } + }, + "@turf/boolean-parallel": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-6.5.0.tgz", + "integrity": "sha512-aSHJsr1nq9e5TthZGZ9CZYeXklJyRgR5kCLm5X4urz7+MotMOp/LsGOsvKvK9NeUl9+8OUmfMn8EFTT8LkcvIQ==", + "requires": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + } + }, + "@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz", + "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-within": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-6.5.0.tgz", + "integrity": "sha512-YQB3oU18Inx35C/LU930D36RAVe7LDXk1kWsQ8mLmuqYn9YdPsDQTMTkLJMhoQ8EbN7QTdy333xRQ4MYgToteQ==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/buffer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz", + "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "d3-geo": "1.7.1", + "turf-jsts": "*" + } + }, + "@turf/center": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz", + "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/center-mean": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-6.5.0.tgz", + "integrity": "sha512-AAX6f4bVn12pTVrMUiB9KrnV94BgeBKpyg3YpfnEbBpkN/znfVhL8dG8IxMAxAoSZ61Zt9WLY34HfENveuOZ7Q==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/center-median": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-6.5.0.tgz", + "integrity": "sha512-dT8Ndu5CiZkPrj15PBvslpuf01ky41DEYEPxS01LOxp5HOUHXp1oJxsPxvc+i/wK4BwccPNzU1vzJ0S4emd1KQ==", + "requires": { + "@turf/center-mean": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/center-of-mass": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-6.5.0.tgz", + "integrity": "sha512-EWrriU6LraOfPN7m1jZi+1NLTKNkuIsGLZc2+Y8zbGruvUW+QV7K0nhf7iZWutlxHXTBqEXHbKue/o79IumAsQ==", + "requires": { + "@turf/centroid": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/centroid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz", + "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", + "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "requires": { + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/clean-coords": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-6.5.0.tgz", + "integrity": "sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/clone": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz", + "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/clusters": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-6.5.0.tgz", + "integrity": "sha512-Y6gfnTJzQ1hdLfCsyd5zApNbfLIxYEpmDibHUqR5z03Lpe02pa78JtgrgUNt1seeO/aJ4TG1NLN8V5gOrHk04g==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/clusters-dbscan": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz", + "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "density-clustering": "1.3.0" + } + }, + "@turf/clusters-kmeans": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz", + "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "skmeans": "0.9.7" + } + }, + "@turf/collect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-6.5.0.tgz", + "integrity": "sha512-4dN/T6LNnRg099m97BJeOcTA5fSI8cu87Ydgfibewd2KQwBexO69AnjEFqfPX3Wj+Zvisj1uAVIZbPmSSrZkjg==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "rbush": "2.x" + } + }, + "@turf/combine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-6.5.0.tgz", + "integrity": "sha512-Q8EIC4OtAcHiJB3C4R+FpB4LANiT90t17uOd851qkM2/o6m39bfN5Mv0PWqMZIHWrrosZqRqoY9dJnzz/rJxYQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/concave": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-6.5.0.tgz", + "integrity": "sha512-I/sUmUC8TC5h/E2vPwxVht+nRt+TnXIPRoztDFvS8/Y0+cBDple9inLSo9nnPXMXidrBlGXZ9vQx/BjZUJgsRQ==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/tin": "^6.5.0", + "topojson-client": "3.x", + "topojson-server": "3.x" + } + }, + "@turf/convex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-6.5.0.tgz", + "integrity": "sha512-x7ZwC5z7PJB0SBwNh7JCeCNx7Iu+QSrH7fYgK0RhhNop13TqUlvHMirMLRgf2db1DqUetrAO2qHJeIuasquUWg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "concaveman": "*" + } + }, + "@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/difference": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz", + "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/dissolve": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-6.5.0.tgz", + "integrity": "sha512-WBVbpm9zLTp0Bl9CE35NomTaOL1c4TQCtEoO43YaAhNEWJOOIhZMFJyr8mbvYruKl817KinT3x7aYjjCMjTAsQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", + "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/distance-weight": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-6.5.0.tgz", + "integrity": "sha512-a8qBKkgVNvPKBfZfEJZnC3DV7dfIsC3UIdpRci/iap/wZLH41EmS90nM+BokAJflUHYy8PqE44wySGWHN1FXrQ==", + "requires": { + "@turf/centroid": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz", + "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/transform-rotate": "^6.5.0" + } + }, + "@turf/envelope": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-6.5.0.tgz", + "integrity": "sha512-9Z+FnBWvOGOU4X+fMZxYFs1HjFlkKqsddLuMknRaqcJd6t+NIv5DWvPtDL8ATD2GEExYDiFLwMdckfr1yqJgHA==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/explode": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-6.5.0.tgz", + "integrity": "sha512-6cSvMrnHm2qAsace6pw9cDmK2buAlw8+tjeJVXMfMyY+w7ZUi1rprWMsY92J7s2Dar63Bv09n56/1V7+tcj52Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/flatten": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz", + "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/flip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-6.5.0.tgz", + "integrity": "sha512-oyikJFNjt2LmIXQqgOGLvt70RgE2lyzPMloYWM7OR5oIFGRiBvqVD2hA6MNw6JewIm30fWZ8DQJw1NHXJTJPbg==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/great-circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-6.5.0.tgz", + "integrity": "sha512-7ovyi3HaKOXdFyN7yy1yOMa8IyOvV46RC1QOQTT+RYUN8ke10eyqExwBpL9RFUPvlpoTzoYbM/+lWPogQlFncg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==" + }, + "@turf/hex-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-6.5.0.tgz", + "integrity": "sha512-Ln3tc2tgZT8etDOldgc6e741Smg1CsMKAz1/Mlel+MEL5Ynv2mhx3m0q4J9IB1F3a4MNjDeVvm8drAaf9SF33g==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/interpolate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-6.5.0.tgz", + "integrity": "sha512-LSH5fMeiGyuDZ4WrDJNgh81d2DnNDUVJtuFryJFup8PV8jbs46lQGfI3r1DJ2p1IlEJIz3pmAZYeTfMMoeeohw==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/triangle-grid": "^6.5.0" + } + }, + "@turf/intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz", + "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/isobands": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-6.5.0.tgz", + "integrity": "sha512-4h6sjBPhRwMVuFaVBv70YB7eGz+iw0bhPRnp+8JBdX1UPJSXhoi/ZF2rACemRUr0HkdVB/a1r9gC32vn5IAEkw==", + "requires": { + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + } + }, + "@turf/isolines": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-6.5.0.tgz", + "integrity": "sha512-6ElhiLCopxWlv4tPoxiCzASWt/jMRvmp6mRYrpzOm3EUl75OhHKa/Pu6Y9nWtCMmVC/RcWtiiweUocbPLZLm0A==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + } + }, + "@turf/kinks": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-6.5.0.tgz", + "integrity": "sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/length": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz", + "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-arc": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-6.5.0.tgz", + "integrity": "sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==", + "requires": { + "@turf/circle": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/line-chunk": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-6.5.0.tgz", + "integrity": "sha512-i1FGE6YJaaYa+IJesTfyRRQZP31QouS+wh/pa6O3CC0q4T7LtHigyBSYjrbjSLfn2EVPYGlPCMFEqNWCOkC6zg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", + "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-rbush": "3.x" + } + }, + "@turf/line-offset": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-6.5.0.tgz", + "integrity": "sha512-CEXZbKgyz8r72qRvPchK0dxqsq8IQBdH275FE6o4MrBkzMcoZsfSjghtXzKaz9vvro+HfIXal0sTk2mqV1lQTw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-6.5.0.tgz", + "integrity": "sha512-xHOaWLd0hkaC/1OLcStCpfq55lPHpPNadZySDXYiYjEz5HXr1oKmtMYpn0wGizsLwrOixRdEp+j7bL8dPt4ojQ==", + "requires": { + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "deep-equal": "1.x", + "geojson-rbush": "3.x" + } + }, + "@turf/line-segment": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", + "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-slice": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-6.5.0.tgz", + "integrity": "sha512-vDqJxve9tBHhOaVVFXqVjF5qDzGtKWviyjbyi2QnSnxyFAmLlLnBfMX8TLQCAf2GxHibB95RO5FBE6I2KVPRuw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0" + } + }, + "@turf/line-slice-along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-6.5.0.tgz", + "integrity": "sha512-KHJRU6KpHrAj+BTgTNqby6VCTnDzG6a1sJx/I3hNvqMBLvWVA2IrkR9L9DtsQsVY63IBwVdQDqiwCuZLDQh4Ng==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/line-split": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-6.5.0.tgz", + "integrity": "sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/truncate": "^6.5.0", + "geojson-rbush": "3.x" + } + }, + "@turf/line-to-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-6.5.0.tgz", + "integrity": "sha512-qYBuRCJJL8Gx27OwCD1TMijM/9XjRgXH/m/TyuND4OXedBpIWlK5VbTIO2gJ8OCfznBBddpjiObLBrkuxTpN4Q==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/mask": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-6.5.0.tgz", + "integrity": "sha512-RQha4aU8LpBrmrkH8CPaaoAfk0Egj5OuXtv6HuCQnHeGNOQt3TQVibTA3Sh4iduq4EPxnZfDjgsOeKtrCA19lg==", + "requires": { + "@turf/helpers": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/midpoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz", + "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/moran-index": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-6.5.0.tgz", + "integrity": "sha512-ItsnhrU2XYtTtTudrM8so4afBCYWNaB0Mfy28NZwLjB5jWuAsvyV+YW+J88+neK/ougKMTawkmjQqodNJaBeLQ==", + "requires": { + "@turf/distance-weight": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/nearest-point": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-6.5.0.tgz", + "integrity": "sha512-fguV09QxilZv/p94s8SMsXILIAMiaXI5PATq9d7YWijLxWUj6Q/r43kxyoi78Zmwwh1Zfqz9w+bCYUAxZ5+euA==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/nearest-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", + "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/nearest-point-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-6.5.0.tgz", + "integrity": "sha512-PXV7cN0BVzUZdjj6oeb/ESnzXSfWmEMrsfZSDRgqyZ9ytdiIj/eRsnOXLR13LkTdXVOJYDBuf7xt1mLhM4p6+Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "object-assign": "*" + } + }, + "@turf/planepoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-6.5.0.tgz", + "integrity": "sha512-R3AahA6DUvtFbka1kcJHqZ7DMHmPXDEQpbU5WaglNn7NaCQg9HB0XM0ZfqWcd5u92YXV+Gg8QhC8x5XojfcM4Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/point-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-6.5.0.tgz", + "integrity": "sha512-Iq38lFokNNtQJnOj/RBKmyt6dlof0yhaHEDELaWHuECm1lIZLY3ZbVMwbs+nXkwTAHjKfS/OtMheUBkw+ee49w==", + "requires": { + "@turf/boolean-within": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/point-on-feature": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-6.5.0.tgz", + "integrity": "sha512-bDpuIlvugJhfcF/0awAQ+QI6Om1Y1FFYE8Y/YdxGRongivix850dTeXCo0mDylFdWFPGDo7Mmh9Vo4VxNwW/TA==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + } + }, + "@turf/point-to-line-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz", + "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + } + }, + "@turf/points-within-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz", + "integrity": "sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/polygon-smooth": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-6.5.0.tgz", + "integrity": "sha512-LO/X/5hfh/Rk4EfkDBpLlVwt3i6IXdtQccDT9rMjXEP32tRgy0VMFmdkNaXoGlSSKf/1mGqLl4y4wHd86DqKbg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/polygon-tangents": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-6.5.0.tgz", + "integrity": "sha512-sB4/IUqJMYRQH9jVBwqS/XDitkEfbyqRy+EH/cMRJURTg78eHunvJ708x5r6umXsbiUyQU4eqgPzEylWEQiunw==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + } + }, + "@turf/polygon-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz", + "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/polygonize": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-6.5.0.tgz", + "integrity": "sha512-a/3GzHRaCyzg7tVYHo43QUChCspa99oK4yPqooVIwTC61npFzdrmnywMv0S+WZjHZwK37BrFJGFrZGf6ocmY5w==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/projection": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz", + "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/random": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-6.5.0.tgz", + "integrity": "sha512-8Q25gQ/XbA7HJAe+eXp4UhcXM9aOOJFaxZ02+XSNwMvY8gtWSCBLVqRcW4OhqilgZ8PeuQDWgBxeo+BIqqFWFQ==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/rectangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-6.5.0.tgz", + "integrity": "sha512-yQZ/1vbW68O2KsSB3OZYK+72aWz/Adnf7m2CMKcC+aq6TwjxZjAvlbCOsNUnMAuldRUVN1ph6RXMG4e9KEvKvg==", + "requires": { + "@turf/boolean-intersects": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/rewind": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz", + "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==", + "requires": { + "@turf/boolean-clockwise": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/rhumb-bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz", + "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/rhumb-destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz", + "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/rhumb-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz", + "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/sample": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-6.5.0.tgz", + "integrity": "sha512-kSdCwY7el15xQjnXYW520heKUrHwRvnzx8ka4eYxX9NFeOxaFITLW2G7UtXb6LJK8mmPXI8Aexv23F2ERqzGFg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/sector": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-6.5.0.tgz", + "integrity": "sha512-cYUOkgCTWqa23SOJBqxoFAc/yGCUsPRdn/ovbRTn1zNTm/Spmk6hVB84LCKOgHqvSF25i0d2kWqpZDzLDdAPbw==", + "requires": { + "@turf/circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/shortest-path": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-6.5.0.tgz", + "integrity": "sha512-4de5+G7+P4hgSoPwn+SO9QSi9HY5NEV/xRJ+cmoFVRwv2CDsuOPDheHKeuIAhKyeKDvPvPt04XYWbac4insJMg==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/transform-scale": "^6.5.0" + } + }, + "@turf/simplify": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-6.5.0.tgz", + "integrity": "sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==", + "requires": { + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/square": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-6.5.0.tgz", + "integrity": "sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/square-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-6.5.0.tgz", + "integrity": "sha512-mlR0ayUdA+L4c9h7p4k3pX6gPWHNGuZkt2c5II1TJRmhLkW2557d6b/Vjfd1z9OVaajb1HinIs1FMSAPXuuUrA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/rectangle-grid": "^6.5.0" + } + }, + "@turf/standard-deviational-ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-6.5.0.tgz", + "integrity": "sha512-02CAlz8POvGPFK2BKK8uHGUk/LXb0MK459JVjKxLC2yJYieOBTqEbjP0qaWhiBhGzIxSMaqe8WxZ0KvqdnstHA==", + "requires": { + "@turf/center-mean": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0" + } + }, + "@turf/tag": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-6.5.0.tgz", + "integrity": "sha512-XwlBvrOV38CQsrNfrxvBaAPBQgXMljeU0DV8ExOyGM7/hvuGHJw3y8kKnQ4lmEQcmcrycjDQhP7JqoRv8vFssg==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/tesselate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-6.5.0.tgz", + "integrity": "sha512-M1HXuyZFCfEIIKkglh/r5L9H3c5QTEsnMBoZOFQiRnGPGmJWcaBissGb7mTFX2+DKE7FNWXh4TDnZlaLABB0dQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "earcut": "^2.0.0" + } + }, + "@turf/tin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-6.5.0.tgz", + "integrity": "sha512-YLYikRzKisfwj7+F+Tmyy/LE3d2H7D4kajajIfc9mlik2+esG7IolsX/+oUz1biguDYsG0DUA8kVYXDkobukfg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/transform-rotate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz", + "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==", + "requires": { + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + } + }, + "@turf/transform-scale": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz", + "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + } + }, + "@turf/transform-translate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz", + "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0" + } + }, + "@turf/triangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-6.5.0.tgz", + "integrity": "sha512-2jToUSAS1R1htq4TyLQYPTIsoy6wg3e3BQXjm2rANzw4wPQCXGOxrur1Fy9RtzwqwljlC7DF4tg0OnWr8RjmfA==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0" + } + }, + "@turf/truncate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-6.5.0.tgz", + "integrity": "sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/turf": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-6.5.0.tgz", + "integrity": "sha512-ipMCPnhu59bh92MNt8+pr1VZQhHVuTMHklciQURo54heoxRzt1neNYZOBR6jdL+hNsbDGAECMuIpAutX+a3Y+w==", + "requires": { + "@turf/along": "^6.5.0", + "@turf/angle": "^6.5.0", + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/bbox-clip": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/bearing": "^6.5.0", + "@turf/bezier-spline": "^6.5.0", + "@turf/boolean-clockwise": "^6.5.0", + "@turf/boolean-contains": "^6.5.0", + "@turf/boolean-crosses": "^6.5.0", + "@turf/boolean-disjoint": "^6.5.0", + "@turf/boolean-equal": "^6.5.0", + "@turf/boolean-intersects": "^6.5.0", + "@turf/boolean-overlap": "^6.5.0", + "@turf/boolean-parallel": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/buffer": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/center-mean": "^6.5.0", + "@turf/center-median": "^6.5.0", + "@turf/center-of-mass": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/circle": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/clusters": "^6.5.0", + "@turf/clusters-dbscan": "^6.5.0", + "@turf/clusters-kmeans": "^6.5.0", + "@turf/collect": "^6.5.0", + "@turf/combine": "^6.5.0", + "@turf/concave": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/difference": "^6.5.0", + "@turf/dissolve": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/distance-weight": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/flatten": "^6.5.0", + "@turf/flip": "^6.5.0", + "@turf/great-circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/interpolate": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/isobands": "^6.5.0", + "@turf/isolines": "^6.5.0", + "@turf/kinks": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/line-chunk": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-offset": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/line-slice": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/line-split": "^6.5.0", + "@turf/line-to-polygon": "^6.5.0", + "@turf/mask": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/midpoint": "^6.5.0", + "@turf/moran-index": "^6.5.0", + "@turf/nearest-point": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/nearest-point-to-line": "^6.5.0", + "@turf/planepoint": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/point-on-feature": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0", + "@turf/polygon-smooth": "^6.5.0", + "@turf/polygon-tangents": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0", + "@turf/polygonize": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/random": "^6.5.0", + "@turf/rewind": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0", + "@turf/sample": "^6.5.0", + "@turf/sector": "^6.5.0", + "@turf/shortest-path": "^6.5.0", + "@turf/simplify": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/standard-deviational-ellipse": "^6.5.0", + "@turf/tag": "^6.5.0", + "@turf/tesselate": "^6.5.0", + "@turf/tin": "^6.5.0", + "@turf/transform-rotate": "^6.5.0", + "@turf/transform-scale": "^6.5.0", + "@turf/transform-translate": "^6.5.0", + "@turf/triangle-grid": "^6.5.0", + "@turf/truncate": "^6.5.0", + "@turf/union": "^6.5.0", + "@turf/unkink-polygon": "^6.5.0", + "@turf/voronoi": "^6.5.0" + } + }, + "@turf/union": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz", + "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/unkink-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-6.5.0.tgz", + "integrity": "sha512-8QswkzC0UqKmN1DT6HpA9upfa1HdAA5n6bbuzHy8NJOX8oVizVAqfEPY0wqqTgboDjmBR4yyImsdPGUl3gZ8JQ==", + "requires": { + "@turf/area": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "rbush": "^2.0.1" + } + }, + "@turf/voronoi": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-6.5.0.tgz", + "integrity": "sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "d3-voronoi": "1.1.2" + } + }, "@types/formatcoords": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/formatcoords/-/formatcoords-1.1.0.tgz", @@ -7648,7 +10742,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -7731,6 +10824,11 @@ "source-map": "~0.5.3" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7748,6 +10846,32 @@ "typedarray": "^0.0.6" } }, + "concaveman": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz", + "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==", + "requires": { + "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", + "robust-predicates": "^2.0.4", + "tinyqueue": "^2.0.3" + }, + "dependencies": { + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "requires": { + "quickselect": "^2.0.0" + } + } + } + }, "concurrently": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", @@ -7914,6 +11038,24 @@ "randomfill": "^1.0.3" } }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "requires": { + "d3-array": "1" + } + }, + "d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==" + }, "dash-ast": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", @@ -7934,12 +11076,39 @@ "ms": "2.0.0" } }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "defined": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", "dev": true }, + "density-clustering": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz", + "integrity": "sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -8022,6 +11191,11 @@ "readable-stream": "^2.0.2" } }, + "earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8319,8 +11493,12 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, "gensync": { "version": "1.0.0-beta.2", @@ -8328,6 +11506,46 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "geojson-equality": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz", + "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==", + "requires": { + "deep-equal": "^1.0.0" + } + }, + "geojson-rbush": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", + "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "requires": { + "@turf/bbox": "*", + "@turf/helpers": "6.x", + "@turf/meta": "6.x", + "@types/geojson": "7946.0.8", + "rbush": "^3.0.1" + }, + "dependencies": { + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "requires": { + "quickselect": "^2.0.0" + } + } + } + }, "get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -8344,7 +11562,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -8393,7 +11610,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -8420,17 +11636,23 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -8595,7 +11817,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8637,6 +11858,14 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8673,6 +11902,15 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", @@ -8764,6 +12002,11 @@ "integrity": "sha512-qnetYIYFqlrAbX7MaGsAkBsuA2fg3Z4xDRlEXJN1wSrnhNsIhQUzXt7Z3vapdEzx4r7VUqWTaqHYd7eY5C6Gfw==", "requires": {} }, + "leaflet-path-drag": { + "version": "1.8.0-beta.3", + "resolved": "https://registry.npmjs.org/leaflet-path-drag/-/leaflet-path-drag-1.8.0-beta.3.tgz", + "integrity": "sha512-kpZ6sPOKlR+m+VChIzZZ7XFH4C+VGTrAxgnM4UN5iYl7lJ00iDOxS+r717bDf/xFFHB6n2jpUp9vvzjjteMpeQ==" + }, "leaflet.nauticscale": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/leaflet.nauticscale/-/leaflet.nauticscale-1.1.0.tgz", @@ -9039,8 +12282,21 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "on-finished": { "version": "2.3.0", @@ -9183,6 +12439,19 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, + "polygon-clipping": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz", + "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==", + "requires": { + "splaytree": "^3.1.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9255,6 +12524,11 @@ "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", "dev": true }, + "quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9290,6 +12564,14 @@ "unpipe": "1.0.0" } }, + "rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "requires": { + "quickselect": "^1.0.1" + } + }, "read-only-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", @@ -9364,6 +12646,16 @@ "@babel/runtime": "^7.8.4" } }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, "regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -9422,6 +12714,11 @@ "inherits": "^2.0.1" } }, + "robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==" + }, "rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -9542,6 +12839,11 @@ } } }, + "skmeans": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", + "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==" + }, "sortablejs": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", @@ -9560,6 +12862,11 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "splaytree": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", + "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==" + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -9770,6 +13077,11 @@ "process": "~0.11.0" } }, + "tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -9785,6 +13097,22 @@ "is-number": "^7.0.0" } }, + "topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "requires": { + "commander": "2" + } + }, + "topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "requires": { + "commander": "2" + } + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -9838,6 +13166,11 @@ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true }, + "turf-jsts": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", + "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/client/package.json b/client/package.json index 5030d648..9c451789 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" }, "dependencies": { + "@turf/turf": "^6.5.0", "@types/formatcoords": "^1.1.0", "@types/geojson": "^7946.0.10", "@types/leaflet": "^1.9.0", @@ -21,6 +22,7 @@ "formatcoords": "^1.1.3", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", + "leaflet-path-drag": "*", "leaflet.nauticscale": "^1.1.0", "morgan": "~1.9.1", "save": "^2.9.0" diff --git a/client/public/javascripts/L.Path.Drag.js b/client/public/javascripts/L.Path.Drag.js new file mode 100644 index 00000000..b6dec124 --- /dev/null +++ b/client/public/javascripts/L.Path.Drag.js @@ -0,0 +1,6 @@ +/** + * Leaflet vector features drag functionality + * @author Alexander Milevski + * @preserve + */ +L.Path.include({_transform:function(t){if(this._renderer){if(t){this._renderer.transformPath(this,t)}else{this._renderer._resetTransformPath(this);this._update()}}return this},_onMouseClick:function(t){if(this.dragging&&this.dragging.moved()||this._map.dragging&&this._map.dragging.moved()){return}this._fireMouseEvent(t)}});var END={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"};var MOVE={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"};function distance(t,a){var i=t.x-a.x,n=t.y-a.y;return Math.sqrt(i*i+n*n)}L.Handler.PathDrag=L.Handler.extend({statics:{DRAGGING_CLS:"leaflet-path-draggable"},initialize:function(t){this._path=t;this._matrix=null;this._startPoint=null;this._dragStartPoint=null;this._mapDraggingWasEnabled=false;this._path._dragMoved=false},addHooks:function(){this._path.on("mousedown",this._onDragStart,this);this._path.options.className=this._path.options.className?this._path.options.className+" "+L.Handler.PathDrag.DRAGGING_CLS:L.Handler.PathDrag.DRAGGING_CLS;if(this._path._path){L.DomUtil.addClass(this._path._path,L.Handler.PathDrag.DRAGGING_CLS)}},removeHooks:function(){this._path.off("mousedown",this._onDragStart,this);this._path.options.className=this._path.options.className.replace(new RegExp("\\s+"+L.Handler.PathDrag.DRAGGING_CLS),"");if(this._path._path){L.DomUtil.removeClass(this._path._path,L.Handler.PathDrag.DRAGGING_CLS)}},moved:function(){return this._path._dragMoved},_onDragStart:function(t){var a=t.originalEvent._simulated?"touchstart":t.originalEvent.type;this._mapDraggingWasEnabled=false;this._startPoint=t.containerPoint.clone();this._dragStartPoint=t.containerPoint.clone();this._matrix=[1,0,0,1,0,0];L.DomEvent.stop(t.originalEvent);L.DomUtil.addClass(this._path._renderer._container,"leaflet-interactive");L.DomEvent.on(document,MOVE[a],this._onDrag,this).on(document,END[a],this._onDragEnd,this);if(this._path._map.dragging.enabled()){this._path._map.dragging.disable();this._mapDraggingWasEnabled=true}this._path._dragMoved=false;if(this._path._popup){this._path._popup.close()}this._replaceCoordGetters(t)},_onDrag:function(t){L.DomEvent.stop(t);var a=t.touches&&t.touches.length>=1?t.touches[0]:t;var i=this._path._map.mouseEventToContainerPoint(a);if(t.type==="touchmove"&&!this._path._dragMoved){var n=this._dragStartPoint.distanceTo(i);if(n<=this._path._map.options.tapTolerance){return}}var e=i.x;var r=i.y;var s=e-this._startPoint.x;var o=r-this._startPoint.y;if(s||o){if(!this._path._dragMoved){this._path._dragMoved=true;this._path.options.interactive=false;this._path._map.dragging._draggable._moved=true;this._path.fire("dragstart",t);this._path.bringToFront()}this._matrix[4]+=s;this._matrix[5]+=o;this._startPoint.x=e;this._startPoint.y=r;this._path.fire("predrag",t);this._path._transform(this._matrix);this._path.fire("drag",t)}},_onDragEnd:function(t){var a=this._path._map.mouseEventToContainerPoint(t);var i=this.moved();if(i){this._transformPoints(this._matrix);this._path._updatePath();this._path._project();this._path._transform(null);L.DomEvent.stop(t)}L.DomEvent.off(document,"mousemove touchmove",this._onDrag,this);L.DomEvent.off(document,"mouseup touchend",this._onDragEnd,this);this._restoreCoordGetters();if(i){this._path.fire("dragend",{distance:distance(this._dragStartPoint,a)});var n=this._path._containsPoint;this._path._containsPoint=L.Util.falseFn;L.Util.requestAnimFrame(function(){this._path._dragMoved=false;this._path.options.interactive=true;this._path._containsPoint=n},this)}if(this._mapDraggingWasEnabled){this._path._map.dragging.enable()}},_transformPoints:function(t,a){var i=this._path;var n,e,r;var s=L.point(t[4],t[5]);var o=i._map.options.crs;var h=o.transformation;var _=o.scale(i._map.getZoom());var g=o.projection;var d=h.untransform(s,_).subtract(h.untransform(L.point(0,0),_));var p=!a;i._bounds=new L.LatLngBounds;if(i._point){a=g.unproject(g.project(i._latlng)._add(d));if(p){i._latlng=a;i._point._add(s)}}else if(i._rings||i._parts){var l=i._rings||i._parts;var f=i._latlngs;a=a||f;if(!L.Util.isArray(f[0])){f=[f];a=[a]}for(n=0,e=l.length;n :last-child { .ol-panel .ol-group-button-toggle { align-items: center; - column-gap: 15px; display: flex; flex-wrap: nowrap; white-space: nowrap; @@ -421,7 +425,7 @@ nav.ol-panel> :last-child { border: 0; display: flex; justify-items: left; - text-indent: 5px; + text-indent: 2px; } .ol-panel .ol-group-button-toggle button::before { @@ -624,39 +628,39 @@ nav.ol-panel> :last-child { width: 28px; } -#unit-visibility-control { +.ol-navbar-buttons-group { align-items: center; } -#unit-visibility-control button { +.ol-navbar-buttons-group button { border: none; height: 32px; padding: 0px; width: 32px; } -#unit-visibility-control button svg { +.ol-navbar-buttons-group button svg { height: 16px; pointer-events: none; width: 16px; } -#unit-visibility-control button { +.ol-navbar-buttons-group button { background-color: white; border: 1px solid transparent; } -#unit-visibility-control button.off { +.ol-navbar-buttons-group button.off { background-color: transparent; border: 1px solid white; } -#unit-visibility-control button.off svg * { +.ol-navbar-buttons-group button.off svg * { fill: white !important; stroke: white !important; } -#unit-visibility-control button svg * { +.ol-navbar-buttons-group button svg * { fill: var(--background-steel) !important; stroke: var(--background-steel) !important; } @@ -667,10 +671,9 @@ nav.ol-panel> :last-child { flex-direction: column; } -#atc-navbar-control button { - background: #ffffff20; - border-radius: var(--border-radius-sm); - padding: 4px; +#atc-navbar-control button svg { + height: 24px; + width: 24px; } #roe-buttons-container button, @@ -877,6 +880,33 @@ nav.ol-panel> :last-child { z-index: 9999; } +.ol-draw-icon { + background-image: url("/resources/theme/images/markers/draw.svg"); + height: 24px; + pointer-events: none; + width: 24px; + z-index: 9999; +} + +.ol-coalitionarea-handle-icon, +.ol-coalitionarea-middle-handle-icon { + pointer-events: none; + z-index: 9999; + border-radius: 999px; +} + +.ol-coalitionarea-handle-icon { + background-color: #FFFFFFEE; + width: 24px; + height: 24px; +} + +.ol-coalitionarea-middle-handle-icon { + background-color: #FFFFFFAA; + width: 16px; + height: 16px; +} + dl.ol-data-grid { align-items: center; display: flex; @@ -1134,4 +1164,87 @@ input[type=number]::-webkit-outer-spin-button { .ol-switch[data-value="undefined"]>.ol-switch-fill::after { transform: translateX(calc((var(--width) - var(--height)) * 0.5)); +} + +.ol-contexmenu-panel { + padding: 20px; +} + +.ol-coalition-switch[data-value="false"]>.ol-switch-fill { + background-color: var(--primary-blue); +} + +.ol-coalition-switch[data-value="true"]>.ol-switch-fill { + background-color: var(--primary-red); +} + +.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill { + background-color: var(--primary-neutral); +} + +.ol-context-menu>div:nth-child(2) { + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + padding-right: 0px; +} + +.ol-context-menu>ul { + max-height: 200px; + overflow-x: hidden; + overflow-y: auto; +} + +.ol-context-menu .ol-panel { + border-radius: var(--border-radius-sm); + width: 100%; +} + +.ol-context-menu ul { + margin: 0px; +} + +.ol-context-menu>div:nth-child(n+3) { + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; + row-gap: 5px; +} + +.ol-context-menu .ol-select-container { + align-self: stretch; + flex: 0 0 auto; + width: 100%; +} + + +.ol-contexmenu-button { + border: none; + border-radius: 0px; + height: 48px; + margin-bottom: -10px; + margin-top: -10px; + width: 48px; +} + +.ol-contexmenu-button:last-of-type { + border-bottom-right-radius: var(--border-radius-sm); + border-top-right-radius: var(--border-radius-sm); +} + +[data-coalition="blue"].ol-contexmenu-button:hover, +[data-coalition="blue"].ol-contexmenu-button.is-open { + background-color: var(--primary-blue) +} + +[data-coalition="red"].ol-contexmenu-button:hover, +[data-coalition="red"].ol-contexmenu-button.is-open { + background-color: var(--primary-red) +} + +[data-coalition="neutral"].ol-contexmenu-button:hover, +[data-coalition="neutral"].ol-contexmenu-button.is-open { + background-color: var(--primary-neutral) } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 9f50a4d4..99e8b35c 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -35,61 +35,13 @@ width: 50px; } -#coalition-switch[data-value="false"]>.ol-switch-fill { - background-color: var(--primary-blue); -} - -#coalition-switch[data-value="true"]>.ol-switch-fill { - background-color: var(--primary-red); -} - -#coalition-switch[data-value="undefined"]>.ol-switch-fill { - background-color: var(--primary-neutral); -} - -#map-contextmenu>div:nth-child(2) { - align-items: center; - display: flex; - flex-direction: row; - justify-content: space-between; - padding-right: 0px; -} - -#map-contextmenu>ul { - max-height: 200px; - overflow-x: hidden; - overflow-y: auto; -} - -#map-contextmenu .ol-panel { - border-radius: var(--border-radius-sm); - width: 100%; -} - -#map-contextmenu ul { - margin: 0px; -} - -#map-contextmenu>div:nth-child(n+3) { - align-items: center; - display: flex; - flex-direction: column; - justify-content: space-between; - row-gap: 5px; -} - -#map-contextmenu .ol-select-container { - align-self: stretch; - flex: 0 0 auto; - width: 100%; -} - #aircraft-spawn-menu .ol-select.is-open .ol-select-options { max-height: 300px; } #aircraft-spawn-menu>button, -#ground-unit-spawn-menu>button { +#ground-unit-spawn-menu>button, +#iads-menu>button { text-align: center; width: 100%; } @@ -99,7 +51,7 @@ background-size: 48px; } -#ground-unit-spawn-button { +#ground-ol-contexmenu-button { background-image: url("/resources/theme/images/buttons/spawn/ground.svg"); background-size: 48px; } @@ -114,71 +66,54 @@ background-size: 48px; } -.unit-spawn-button { - border: none; - border-radius: 0px; - height: 48px; - margin-bottom: -10px; - margin-top: -10px; - width: 48px; -} - -.unit-spawn-button:last-of-type { - border-bottom-right-radius: var(--border-radius-sm); - border-top-right-radius: var(--border-radius-sm); -} - -[data-active-coalition="blue"].unit-spawn-button:hover, -[data-active-coalition="blue"].unit-spawn-button.is-open, -[data-active-coalition="blue"]#active-coalition-label, -[data-active-coalition="blue"].deploy-unit-button, -[data-active-coalition="blue"]#spawn-airbase-aircraft-button { +[data-coalition="blue"]#active-coalition-label, +[data-coalition="blue"].deploy-unit-button, +[data-coalition="blue"]#spawn-airbase-aircraft-button, +[data-coalition="blue"].create-iads-button { background-color: var(--primary-blue) } -[data-active-coalition="red"].unit-spawn-button:hover, -[data-active-coalition="red"].unit-spawn-button.is-open, -[data-active-coalition="red"]#active-coalition-label, -[data-active-coalition="red"].deploy-unit-button, -[data-active-coalition="red"]#spawn-airbase-aircraft-button { +[data-coalition="red"]#active-coalition-label, +[data-coalition="red"].deploy-unit-button, +[data-coalition="red"]#spawn-airbase-aircraft-button, +[data-coalition="red"].create-iads-button { background-color: var(--primary-red) } -[data-active-coalition="neutral"].unit-spawn-button:hover, -[data-active-coalition="neutral"].unit-spawn-button.is-open, -[data-active-coalition="neutral"]#active-coalition-label, -[data-active-coalition="neutral"].deploy-unit-button, -[data-active-coalition="neutral"]#spawn-airbase-aircraft-button { +[data-coalition="neutral"]#active-coalition-label, +[data-coalition="neutral"].deploy-unit-button, +[data-coalition="neutral"]#spawn-airbase-aircraft-button, +[data-coalition="neutral"].create-iads-button { background-color: var(--primary-neutral) } -[data-active-coalition="blue"].deploy-unit-button:disabled { +[data-coalition="blue"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-blue); cursor: default; } -[data-active-coalition="red"].deploy-unit-button:disabled { +[data-coalition="red"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-red); cursor: default; } -[data-active-coalition="neutral"].deploy-unit-button:disabled { +[data-coalition="neutral"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-neutral); cursor: default; } -[data-active-coalition="blue"]#active-coalition-label::after { +[data-coalition="blue"]#active-coalition-label::after { content: "Create blue unit"; } -[data-active-coalition="red"]#active-coalition-label::after { +[data-coalition="red"]#active-coalition-label::after { content: "Create red unit"; } -[data-active-coalition="neutral"]#active-coalition-label::after { +[data-coalition="neutral"]#active-coalition-label::after { content: "Create neutral unit"; } @@ -407,3 +342,65 @@ width: 180px; z-index: 9999; } + +/* Coalition area context menu */ +#coalition-area-contextmenu { + display: flex; + flex-direction: column; + height: fit-content; + position: absolute; + row-gap: 5px; + width: 250px; + z-index: 9999; +} + +#coalition-area-switch { + margin-right: 10px; + height: 25px; + width: 50px; +} + +#iads-button { + background-image: url("/resources/theme/images/buttons/spawn/sam.svg"); + background-size: 48px; +} + +#cap-button { + background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg"); + background-size: 48px; +} + +#coalitionarea-back-button { + background-image: url("/resources/theme/images/buttons/other/back.svg"); + background-size: 48px; +} + +#coalitionarea-delete-button { + background-image: url("/resources/theme/images/buttons/other/delete.svg"); + background-size: 48px; +} + +#coalition-area-contextmenu .ol-checkbox { + align-self: flex-start; +} + +#iads-menu .ol-select-options>* { + padding-top: 8px; + padding-bottom: 8px; +} + +#iads-menu .ol-select-options>*:first-child { + padding-top: 15px; +} + +#iads-menu .ol-select-options>*:last-child { + padding-bottom: 15px; +} + +#iads-menu .ol-select { + width: 100%; +} + +#iads-menu { + row-gap: 10px; +} diff --git a/client/public/themes/olympus/images/buttons/other/back.svg b/client/public/themes/olympus/images/buttons/other/back.svg new file mode 100644 index 00000000..52c98f94 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/other/back.svg @@ -0,0 +1,41 @@ + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/other/delete.svg b/client/public/themes/olympus/images/buttons/other/delete.svg new file mode 100644 index 00000000..c290353d --- /dev/null +++ b/client/public/themes/olympus/images/buttons/other/delete.svg @@ -0,0 +1,40 @@ + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/spawn/sam.svg b/client/public/themes/olympus/images/buttons/spawn/sam.svg new file mode 100644 index 00000000..0109de6b --- /dev/null +++ b/client/public/themes/olympus/images/buttons/spawn/sam.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg b/client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg new file mode 100644 index 00000000..f755fed5 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/buttons/tools/ground.svg b/client/public/themes/olympus/images/buttons/tools/ground.svg new file mode 100644 index 00000000..34ab621c --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/ground.svg @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/tools/pen-solid.svg b/client/public/themes/olympus/images/buttons/tools/pen-solid.svg new file mode 100644 index 00000000..a690992f --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/pen-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/buttons/tools/tower.svg b/client/public/themes/olympus/images/buttons/tools/tower.svg new file mode 100644 index 00000000..a5134f93 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/tower.svg @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/markers/draw.svg b/client/public/themes/olympus/images/markers/draw.svg new file mode 100644 index 00000000..7e1d8f98 --- /dev/null +++ b/client/public/themes/olympus/images/markers/draw.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/client/src/@types/server.d.ts b/client/src/@types/server.d.ts index e1a9c3e9..23920339 100644 --- a/client/src/@types/server.d.ts +++ b/client/src/@types/server.d.ts @@ -1,5 +1,5 @@ interface UnitsData { - units: {[key: string]: UnitData}, + units: string, sessionHash: string } diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 0a7847b9..ec6aaa39 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -1,60 +1,23 @@ -interface UpdateData { - [key: string]: any +import { LatLng } from "leaflet" + +interface UnitIconOptions { + showState: boolean, + showVvi: boolean, + showHotgroup: boolean, + showUnitIcon: boolean, + showShortLabel: boolean, + showFuel: boolean, + showAmmo: boolean, + showSummary: boolean, + rotateToHeading: boolean } -interface BaseData { - controlled: boolean; - name: string; - unitName: string; - groupName: string; - alive: boolean; - category: string; -} - -interface FlightData { - latitude: number; - longitude: number; - altitude: number; - heading: number; - speed: number; -} - -interface MissionData { - fuel: number; - flags: any; - ammo: any; - contacts: any; - hasTask: boolean; - coalition: string; -} - -interface FormationData { - leaderID: number; -} - -interface TaskData { - currentState: string; - currentTask: string; - activePath: any; - desiredSpeed: number; - desiredSpeedType: string; - desiredAltitude: number; - desiredAltitudeType: string; - targetLocation: any; - isTanker: boolean; - isAWACS: boolean; - onOff: boolean; - followRoads: boolean; - targetID: number; -} - -interface OptionsData { - ROE: string; - reactionToThreat: string; - emissionsCountermeasures: string; - TACAN: TACAN; - radio: Radio; - generalSettings: GeneralSettings; +interface GeneralSettings { + prohibitJettison: boolean; + prohibitAA: boolean; + prohibitAG: boolean; + prohibitAfterburner: boolean; + prohibitAirWpn: boolean; } interface TACAN { @@ -70,31 +33,21 @@ interface Radio { callsignNumber: number; } -interface GeneralSettings { - prohibitJettison: boolean; - prohibitAA: boolean; - prohibitAG: boolean; - prohibitAfterburner: boolean; - prohibitAirWpn: boolean; +interface Ammo { + quantity: number, + name: string, + guidance: number, + category: number, + missileCategory: number } -interface UnitIconOptions { - showState: boolean, - showVvi: boolean, - showHotgroup: boolean, - showUnitIcon: boolean, - showShortLabel: boolean, - showFuel: boolean, - showAmmo: boolean, - showSummary: boolean, - rotateToHeading: boolean +interface Contact { + ID: number, + detectionMethod: number } -interface UnitData { - baseData: BaseData; - flightData: FlightData; - missionData: MissionData; - formationData: FormationData; - taskData: TaskData; - optionsData: OptionsData; +interface Offset { + x: number, + y: number, + z: number } \ No newline at end of file diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index 2edcbc9e..832cd691 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -115,11 +115,11 @@ export abstract class ATCBoard { addFlight( unit:Unit ) { - const baseData = unit.getBaseData(); + const baseData = unit.getData(); const unitCanBeAdded = () => { - if ( baseData.category !== "Aircraft" ) { + if ( unit.getCategory() !== "Aircraft" ) { return false; } @@ -345,7 +345,7 @@ export abstract class ATCBoard { const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => { const unit = units[ unitId ]; - const baseData = unit.getBaseData(); + const baseData = unit.getData(); if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) { acc.push( unit ); @@ -359,7 +359,7 @@ export abstract class ATCBoard { results.forEach( unit => { - const baseData = unit.getBaseData(); + const baseData = unit.getData(); const a = document.createElement( "a" ); a.innerText = baseData.unitName; diff --git a/client/src/atc/board/tower.ts b/client/src/atc/board/tower.ts index c1698966..8800dfc2 100644 --- a/client/src/atc/board/tower.ts +++ b/client/src/atc/board/tower.ts @@ -34,13 +34,13 @@ export class ATCBoardTower extends ATCBoard { return; } - const flightData:FlightData = { + const flightData = { latitude: -1, longitude: -1, altitude: -1, heading: -1, speed: -1, - ...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getFlightData() : {} ) + ...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} ) }; if ( !strip ) { diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index 1ef21f2c..54b0674e 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -12,8 +12,8 @@ export class UnitDataTable extends Panel { var units = getUnitsManager().getUnits(); const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => { - const aVal = a.getBaseData().unitName?.toLowerCase(); - const bVal = b.getBaseData().unitName?.toLowerCase(); + const aVal = a.getUnitName()?.toLowerCase(); + const bVal = b.getUnitName()?.toLowerCase(); if (aVal > bVal) { return 1; @@ -48,7 +48,7 @@ export class UnitDataTable extends Panel { for (const unit of unitsArray) { - const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"]; + const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"]; addRow(el, dataset); } diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 0f91ed25..aa9c1189 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -1,12 +1,31 @@ import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet"; -export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"]; -export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"]; -export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"]; +export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area"]; +export const ROEs: string[] = ["free", "designated", "return", "hold"]; +export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"]; +export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"]; -export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"]; -export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"]; -export const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"]; +export const ROEDescriptions: string[] = [ + "Free (Attack anyone)", + "Designated (Attack the designated target only)", + "", + "Return (Only fire if fired upon)", + "Hold (Never fire)" +]; + +export const reactionsToThreatDescriptions: string[] = [ + "None (No reaction)", + "Manoeuvre (no countermeasures)", + "Passive (Countermeasures only, no manoeuvre)", + "Evade (Countermeasures and manoeuvers)" +]; + +export const emissionsCountermeasuresDescriptions: string[] = [ + "Silent (Radar OFF, no ECM)", + "Attack (Radar only for targeting, ECM only if locked)", + "Defend (Radar for searching, ECM if locked)", + "Always on (Radar and ECM always on)" +]; export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 }; export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 }; @@ -99,4 +118,59 @@ export const layers = { maxZoom: 20, attribution: 'CyclOSM | Map data: © OpenStreetMap contributors' } -} \ No newline at end of file +} + +/* Map constants */ +export const IDLE = "Idle"; +export const MOVE_UNIT = "Move unit"; +export const BOMBING = "Bombing"; +export const CARPET_BOMBING = "Carpet bombing"; +export const FIRE_AT_AREA = "Fire at area"; +export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; +export const COALITIONAREA_INTERACT = "Interact with Coalition Areas" +export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; +export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; + +export const IADSRoles: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05}; + +export enum DataIndexes { + startOfData = 0, + category, + alive, + human, + controlled, + coalition, + country, + name, + unitName, + groupName, + state, + task, + hasTask, + position, + speed, + heading, + isTanker, + isAWACS, + onOff, + followRoads, + fuel, + desiredSpeed, + desiredSpeedType, + desiredAltitude, + desiredAltitudeType, + leaderID, + formationOffset, + targetID, + targetPosition, + ROE, + reactionToThreat, + emissionsCountermeasures, + TACAN, + radio, + generalSettings, + ammo, + contacts, + activePath, + endOfData = 255 +}; \ No newline at end of file diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts new file mode 100644 index 00000000..dcb9bc23 --- /dev/null +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -0,0 +1,112 @@ +import { getMap, getUnitsManager } from ".."; +import { IADSRoles } from "../constants/constants"; +import { CoalitionArea } from "../map/coalitionarea"; +import { ContextMenu } from "./contextmenu"; +import { Dropdown } from "./dropdown"; +import { Slider } from "./slider"; +import { Switch } from "./switch"; + +export class CoalitionAreaContextMenu extends ContextMenu { + #coalitionSwitch: Switch; + #coalitionArea: CoalitionArea | null = null; + #iadsDensitySlider: Slider; + #iadsRoleDropdown: Dropdown; + + //#iadsPeriodDropdown: Dropdown; + + constructor(id: string) { + super(id); + + this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value)); + this.#coalitionSwitch.setValue(false); + this.#iadsRoleDropdown = new Dropdown("iads-units-role-options", () => { }); + //this.#iadsPeriodDropdown = new Dropdown("iads-period-options", () => {}); + this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { }); + this.#iadsDensitySlider.setIncrement(5); + this.#iadsDensitySlider.setValue(50); + this.#iadsDensitySlider.setActive(true); + + document.addEventListener("coalitionAreaContextMenuShow", (e: any) => { + if (this.getVisibleSubMenu() !== e.detail.type) + this.showSubMenu(e.detail.type); + else + this.hideSubMenus(); + }); + + document.addEventListener("coalitionAreaDelete", (e: any) => { + if (this.#coalitionArea) + getMap().deleteCoalitionArea(this.#coalitionArea); + getMap().hideCoalitionAreaContextMenu(); + }); + + document.addEventListener("contextMenuCreateIads", (e: any) => { + const values: { [key: string]: boolean } = {}; + const element = this.#iadsRoleDropdown.getOptionElements(); + for (let idx = 0; idx < element.length; idx++) { + const option = element.item(idx) as HTMLElement; + const key = option.querySelector("span")?.innerText; + const value = option.querySelector("input")?.checked; + if (key !== undefined && value !== undefined) + values[key] = value; + } + + const area = this.getCoalitionArea(); + if (area) + getUnitsManager().createIADS(area, values, this.#iadsDensitySlider.getValue()); + }) + + /* Create the checkboxes to select the unit roles */ + this.#iadsRoleDropdown.setOptionsElements(Object.keys(IADSRoles).map((unitRole: string) => { + var div = document.createElement("div"); + div.classList.add("ol-checkbox"); + var label = document.createElement("label"); + label.title = `Add ${unitRole}s to the IADS`; + var input = document.createElement("input"); + input.type = "checkbox"; + input.checked = true; + var span = document.createElement("span"); + span.innerText = unitRole; + label.appendChild(input); + label.appendChild(span); + div.appendChild(label); + return div as HTMLElement; + })); + + this.hide(); + } + + showSubMenu(type: string) { + this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads"); + this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads"); + this.clip(); + + this.setVisibleSubMenu(type); + } + + hideSubMenus() { + this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true); + this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false); + this.clip(); + + this.setVisibleSubMenu(null); + } + + getCoalitionArea() { + return this.#coalitionArea; + } + + setCoalitionArea(coalitionArea: CoalitionArea) { + this.#coalitionArea = coalitionArea; + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { + element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) + }); + this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red"); + } + + #onSwitchClick(value: boolean) { + this.getCoalitionArea()?.setCoalition(value ? "red" : "blue"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { + element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) + }); + } +} \ No newline at end of file diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts index 58cf371f..8e3261bb 100644 --- a/client/src/controls/contextmenu.ts +++ b/client/src/controls/contextmenu.ts @@ -5,6 +5,7 @@ export class ContextMenu { #latlng: LatLng = new LatLng(0, 0); #x: number = 0; #y: number = 0; + #visibleSubMenu: string | null = null; constructor(id: string) { this.#container = document.getElementById(id); @@ -52,4 +53,12 @@ export class ContextMenu { this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px"; } } + + setVisibleSubMenu(menu: string | null) { + this.#visibleSubMenu = menu; + } + + getVisibleSubMenu() { + return this.#visibleSubMenu; + } } \ No newline at end of file diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index 2877d80d..b8f490ea 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -50,6 +50,15 @@ export class Dropdown { })); } + setOptionsElements(optionsElements: HTMLElement[]) { + this.#optionsList = []; + this.#options.replaceChildren(...optionsElements); + } + + getOptionElements() { + return this.#options.children; + } + selectText(text: string) { const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text); if (index > -1) { diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 51027f01..c0cdcdf9 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -11,12 +11,13 @@ import { ftToM } from "../other/utils"; export interface SpawnOptions { role: string; - type: string; + name: string; latlng: LatLng; coalition: string; - loadout: string | null; - airbaseName: string | null; - altitude: number | null; + loadout?: string | null; + airbaseName?: string | null; + altitude?: number | null; + immediate?: boolean; } export class MapContextMenu extends ContextMenu { @@ -27,12 +28,12 @@ export class MapContextMenu extends ContextMenu { #aircrafSpawnAltitudeSlider: Slider; #groundUnitRoleDropdown: Dropdown; #groundUnitTypeDropdown: Dropdown; - #spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) }; - + #spawnOptions: SpawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) }; + constructor(id: string) { super(id); - this.#coalitionSwitch = new Switch("coalition-switch", this.#onSwitchClick); + this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value)); this.#coalitionSwitch.setValue(false); this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e)); this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role)); @@ -45,15 +46,18 @@ export class MapContextMenu extends ContextMenu { this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role)); this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type)); - document.addEventListener("contextMenuShow", (e: any) => { - this.showSubMenu(e.detail.type); + document.addEventListener("mapContextMenuShow", (e: any) => { + if (this.getVisibleSubMenu() !== e.detail.type) + this.showSubMenu(e.detail.type); + else + this.hideSubMenus(); }); document.addEventListener("contextMenuDeployAircraft", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { - getMap().addTemporaryMarker(this.#spawnOptions.latlng); + getMap().addTemporaryMarker(this.#spawnOptions); spawnAircraft(this.#spawnOptions); } }); @@ -62,7 +66,7 @@ export class MapContextMenu extends ContextMenu { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { - getMap().addTemporaryMarker(this.#spawnOptions.latlng); + getMap().addTemporaryMarker(this.#spawnOptions); spawnGroundUnit(this.#spawnOptions); } }); @@ -92,7 +96,7 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft"); this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft"); this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", type !== "ground-unit"); - this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", type === "ground-unit"); + this.getContainer()?.querySelector("#ground-ol-contexmenu-button")?.classList.toggle("is-open", type === "ground-unit"); this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke"); this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke"); this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion"); @@ -103,8 +107,30 @@ export class MapContextMenu extends ContextMenu { this.#resetGroundUnitRole(); this.#resetGroundUnitType(); this.clip(); + + this.setVisibleSubMenu(type); } + hideSubMenus() { + this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true); + this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false); + this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", true); + this.getContainer()?.querySelector("#ground-ol-contexmenu-button")?.classList.toggle("is-open", false); + this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true); + this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false); + this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true); + this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false); + + this.#resetAircraftRole(); + this.#resetAircraftType(); + this.#resetGroundUnitRole(); + this.#resetGroundUnitType(); + this.clip(); + + this.setVisibleSubMenu(null); + } + + showUpperBar() { this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", false); } @@ -123,6 +149,7 @@ export class MapContextMenu extends ContextMenu { #onSwitchClick(value: boolean) { value? setActiveCoalition("red"): setActiveCoalition("blue"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) }); } #onSwitchRightClick(e: any) { @@ -152,7 +179,7 @@ export class MapContextMenu extends ContextMenu { this.#resetAircraftType(); var type = aircraftDatabase.getByLabel(label)?.name || null; if (type != null) { - this.#spawnOptions.type = type; + this.#spawnOptions.name = type; this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role)); this.#aircraftLoadoutDropdown.selectValue(0); var image = (this.getContainer()?.querySelector("#unit-image")); @@ -171,7 +198,7 @@ export class MapContextMenu extends ContextMenu { } #setAircraftLoadout(loadoutName: string) { - var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.type, loadoutName); + var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName); if (loadout) { this.#spawnOptions.loadout = loadout.code; (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; @@ -214,7 +241,7 @@ export class MapContextMenu extends ContextMenu { this.#resetGroundUnitType(); var type = groundUnitsDatabase.getByLabel(label)?.name || null; if (type != null) { - this.#spawnOptions.type = type; + this.#spawnOptions.name = type; (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; } this.clip(); diff --git a/client/src/index.ts b/client/src/index.ts index e4760d01..933551cb 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -223,6 +223,10 @@ export function getUnitsManager() { return unitsManager; } +export function getMissionHandler() { + return missionHandler; +} + export function getUnitInfoPanel() { return unitInfoPanel; } @@ -249,7 +253,6 @@ export function getHotgroupPanel() { export function setActiveCoalition(newActiveCoalition: string) { activeCoalition = newActiveCoalition; - document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) }); } export function getActiveCoalition() { diff --git a/client/src/map/boxselect.ts b/client/src/map/boxselect.ts index 2ea92fd8..a37f4ace 100644 --- a/client/src/map/boxselect.ts +++ b/client/src/map/boxselect.ts @@ -46,7 +46,7 @@ export var BoxSelect = Handler.extend({ _onMouseDown: function (e: any) { if ((e.which == 1 && e.button == 0 && e.shiftKey)) { - + this._map.fire('selectionstart'); // Clear the deferred resetState if it hasn't executed yet, otherwise it // will interrupt the interaction and orphan a box element in the container. this._clearDeferredResetState(); diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts new file mode 100644 index 00000000..2e2ab0fe --- /dev/null +++ b/client/src/map/coalitionarea.ts @@ -0,0 +1,171 @@ +import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; +import { getMap } from ".."; +import { CoalitionAreaHandle } from "./coalitionareahandle"; +import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle"; + +export class CoalitionArea extends Polygon { + #coalition: string = "blue"; + #selected: boolean = true; + #editing: boolean = true; + #handles: CoalitionAreaHandle[] = []; + #middleHandles: CoalitionAreaMiddleHandle[] = []; + #activeIndex: number = 0; + + constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) { + if (options === undefined) + options = {}; + + options.bubblingMouseEvents = false; + super(latlngs, options); + this.#setColors(); + this.#registerCallbacks(); + + } + + setCoalition(coalition: string) { + this.#coalition = coalition; + this.#setColors(); + } + + getCoalition() { + return this.#coalition; + } + + setSelected(selected: boolean) { + this.#selected = selected; + this.#setColors(); + this.#setHandles(); + if (!this.getSelected() && this.getEditing()) { + /* Remove the vertex we were working on */ + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs.splice(this.#activeIndex, 1); + this.setLatLngs(latlngs); + this.setEditing(false); + } + } + + getSelected() { + return this.#selected; + } + + setEditing(editing: boolean) { + this.#editing = editing; + this.#setHandles(); + var latlngs = this.getLatLngs()[0] as LatLng[]; + + /* Remove areas with less than 2 vertexes */ + if (latlngs.length <= 2) + getMap().deleteCoalitionArea(this); + } + + getEditing() { + return this.#editing; + } + + setInteractive(interactive: boolean) { + this.setOpacity(interactive? 1: 0.5); + this.options.interactive = interactive; + + if (interactive) + DomUtil.addClass(this.getElement() as HTMLElement, 'leaflet-interactive'); + else + DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive'); + } + + addTemporaryLatLng(latlng: LatLng) { + this.#activeIndex++; + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs.splice(this.#activeIndex, 0, latlng); + this.setLatLngs(latlngs); + this.#setHandles(); + } + + moveActiveVertex(latlng: LatLng) { + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs[this.#activeIndex] = latlng; + this.setLatLngs(latlngs); + this.#setHandles(); + } + + setOpacity(opacity: number) { + this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25}); + } + + #setColors() { + const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858"; + this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor }); + } + + #setHandles() { + this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap())); + this.#handles = []; + if (this.getSelected()) { + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs.forEach((latlng: LatLng, idx: number) => { + /* Add the polygon vertex handle (for moving the vertex) */ + const handle = new CoalitionAreaHandle(latlng); + handle.addTo(getMap()); + handle.on("drag", (e: any) => { + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs[idx] = e.target.getLatLng(); + this.setLatLngs(latlngs); + this.#setMiddleHandles(); + }); + this.#handles.push(handle); + }); + } + this.#setMiddleHandles(); + } + + #setMiddleHandles() { + this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getMap())); + this.#middleHandles = []; + var latlngs = this.getLatLngs()[0] as LatLng[]; + if (this.getSelected() && latlngs.length >= 2) { + var lastLatLng: LatLng | null = null; + latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => { + /* Add the polygon middle point handle (for adding new vertexes) */ + if (lastLatLng != null) { + const handle1Point = getMap().latLngToLayerPoint(latlng); + const handle2Point = getMap().latLngToLayerPoint(lastLatLng); + const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2); + const middleLatLng = getMap().layerPointToLatLng(middlePoint); + + const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng); + middleHandle.addTo(getMap()); + middleHandle.on("click", (e: any) => { + this.#activeIndex = idx - 1; + this.addTemporaryLatLng(middleLatLng); + }); + this.#middleHandles.push(middleHandle); + } + lastLatLng = latlng; + }); + } + } + + #registerCallbacks() { + this.on("click", (e: any) => { + getMap().deselectAllCoalitionAreas(); + if (!this.getSelected()) { + this.setSelected(true); + } + }); + + this.on("contextmenu", (e: any) => { + if (!this.getEditing()) { + getMap().deselectAllCoalitionAreas(); + this.setSelected(true); + getMap().showCoalitionAreaContextMenu(e, this); + } + else + this.setEditing(false); + }); + } + + onRemove(map: Map): this { + super.onRemove(map); + this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getMap())); + return this; + } +} \ No newline at end of file diff --git a/client/src/map/coalitionareahandle.ts b/client/src/map/coalitionareahandle.ts new file mode 100644 index 00000000..6dceaaa8 --- /dev/null +++ b/client/src/map/coalitionareahandle.ts @@ -0,0 +1,19 @@ +import { DivIcon, LatLng } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class CoalitionAreaHandle extends CustomMarker { + constructor(latlng: LatLng) { + super(latlng, {interactive: true, draggable: true}); + } + + createIcon() { + this.setIcon(new DivIcon({ + iconSize: [24, 24], + iconAnchor: [12, 12], + className: "leaflet-coalitionarea-handle-marker", + })); + var el = document.createElement("div"); + el.classList.add("ol-coalitionarea-handle-icon"); + this.getElement()?.appendChild(el); + } +} \ No newline at end of file diff --git a/client/src/map/coalitionareamiddlehandle.ts b/client/src/map/coalitionareamiddlehandle.ts new file mode 100644 index 00000000..b268ec9b --- /dev/null +++ b/client/src/map/coalitionareamiddlehandle.ts @@ -0,0 +1,19 @@ +import { DivIcon, LatLng } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class CoalitionAreaMiddleHandle extends CustomMarker { + constructor(latlng: LatLng) { + super(latlng, {interactive: true, draggable: false}); + } + + createIcon() { + this.setIcon(new DivIcon({ + iconSize: [16, 16], + iconAnchor: [8, 8], + className: "leaflet-coalitionarea-middle-handle-marker", + })); + var el = document.createElement("div"); + el.classList.add("ol-coalitionarea-middle-handle-icon"); + this.getElement()?.appendChild(el); + } +} \ No newline at end of file diff --git a/client/src/map/destinationpreviewmarker.ts b/client/src/map/destinationpreviewmarker.ts index aa76f395..28f21650 100644 --- a/client/src/map/destinationpreviewmarker.ts +++ b/client/src/map/destinationpreviewmarker.ts @@ -1,7 +1,12 @@ -import { DivIcon } from "leaflet"; +import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; import { CustomMarker } from "./custommarker"; export class DestinationPreviewMarker extends CustomMarker { + constructor(latlng: LatLngExpression, options?: MarkerOptions) { + super(latlng, options); + this.setZIndexOffset(9999); + } + createIcon() { this.setIcon(new DivIcon({ iconSize: [52, 52], diff --git a/client/src/map/drawingcursor.ts b/client/src/map/drawingcursor.ts new file mode 100644 index 00000000..2758dc1a --- /dev/null +++ b/client/src/map/drawingcursor.ts @@ -0,0 +1,20 @@ +import { DivIcon, LatLng } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class DrawingCursor extends CustomMarker { + constructor() { + super(new LatLng(0, 0), {interactive: false}) + this.setZIndexOffset(9999); + } + + createIcon() { + this.setIcon(new DivIcon({ + iconSize: [24, 24], + iconAnchor: [0, 24], + className: "leaflet-draw-marker", + })); + var el = document.createElement("div"); + el.classList.add("ol-draw-icon"); + this.getElement()?.appendChild(el); + } +} \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 04b85d28..18c74d99 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,22 +12,17 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants"; +import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, COALITIONAREA_INTERACT } from "../constants/constants"; import { TargetMarker } from "./targetmarker"; +import { CoalitionArea } from "./coalitionarea"; +import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; +import { DrawingCursor } from "./drawingcursor"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); // TODO would be nice to convert to ts require("../../public/javascripts/leaflet.nauticscale.js") - -/* Map constants */ -export const IDLE = "Idle"; -export const MOVE_UNIT = "Move unit"; -export const BOMBING = "Bombing"; -export const CARPET_BOMBING = "Carpet bombing"; -export const FIRE_AT_AREA = "Fire at area"; -export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; -export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; +require("../../public/javascripts/L.Path.Drag.js") export class Map extends L.Map { #ID: string; @@ -42,27 +37,34 @@ export class Map extends L.Map { #panUp: boolean = false; #panDown: boolean = false; #lastMousePosition: L.Point = new L.Point(0, 0); + #shiftKey: boolean = false; + #ctrlKey: boolean = false; #centerUnit: Unit | null = null; #miniMap: ClickableMiniMap | null = null; #miniMapLayerGroup: L.LayerGroup; #temporaryMarkers: TemporaryUnitMarker[] = []; - #destinationPreviewMarkers: DestinationPreviewMarker[] = []; - #targetMarker: TargetMarker; + #selecting: boolean = false; + #destinationGroupRotation: number = 0; #computeDestinationRotation: boolean = false; #destinationRotationCenter: L.LatLng | null = null; + #coalitionAreas: CoalitionArea[] = []; + + #targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false }); + #destinationPreviewCursors: DestinationPreviewMarker[] = []; + #drawingCursor: DrawingCursor = new DrawingCursor(); #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); + #coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu"); #mapSourceDropdown: Dropdown; #optionButtons: { [key: string]: HTMLButtonElement[] } = {} constructor(ID: string) { /* Init the leaflet map */ - - //@ts-ignore + //@ts-ignore Needed because the boxSelect option is non-standard super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 }); this.setView([37.23, -115.8], 10); @@ -92,18 +94,19 @@ export class Map extends L.Map { this.on("zoomstart", (e: any) => this.#onZoom(e)); this.on("drag", (e: any) => this.centerOnUnit(null)); this.on("contextmenu", (e: any) => this.#onContextMenu(e)); + this.on('selectionstart', (e: any) => this.#onSelectionStart(e)); this.on('selectionend', (e: any) => this.#onSelectionEnd(e)); this.on('mousedown', (e: any) => this.#onMouseDown(e)); this.on('mouseup', (e: any) => this.#onMouseUp(e)); this.on('mousemove', (e: any) => this.#onMouseMove(e)); - this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); - this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); + this.on('keydown', (e: any) => this.#onKeyDown(e)); + this.on('keyup', (e: any) => this.#onKeyUp(e)); /* Event listeners */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { const el = ev.detail._element; el?.classList.toggle("off"); - getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off")); + getUnitsManager().setHiddenType(ev.detail.coalition, !el?.classList.contains("off")); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); @@ -114,6 +117,29 @@ export class Map extends L.Map { Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); + document.addEventListener("toggleCoalitionAreaInteraction", (ev: CustomEventInit) => { + const el = ev.detail._element; + /* Add listener to set the button to off if the state changes */ + document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_INTERACT))); + if (this.getState() !== COALITIONAREA_INTERACT) + this.setState(COALITIONAREA_INTERACT); + else + this.setState(IDLE); + }); + + document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => { + const el = ev.detail._element; + /* Add listener to set the button to off if the state changes */ + document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_DRAW_POLYGON))); + this.deselectAllCoalitionAreas(); + if (ev.detail?.type == "polygon") { + if (this.getState() !== COALITIONAREA_DRAW_POLYGON) + this.setState(COALITIONAREA_DRAW_POLYGON); + else + this.setState(IDLE); + } + }); + document.addEventListener("unitUpdated", (ev: CustomEvent) => { if (this.#centerUnit != null && ev.detail == this.#centerUnit) this.#panToUnit(this.#centerUnit); @@ -122,7 +148,7 @@ export class Map extends L.Map { /* Pan interval */ this.#panInterval = window.setInterval(() => { if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft) - this.panBy(new L.Point(((this.#panLeft? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta, + this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta, ((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta)); }, 20); @@ -131,16 +157,13 @@ export class Map extends L.Map { return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`); }); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); - - /* Markers */ - this.#targetMarker = new TargetMarker(new L.LatLng(0, 0), {interactive: false}); } setLayer(layerName: string) { - if (this.#layer != null) + if (this.#layer != null) this.removeLayer(this.#layer) - - if (layerName in mapLayers){ + + if (layerName in mapLayers) { const layerData = mapLayers[layerName as keyof typeof mapLayers]; var options: L.TileLayerOptions = { attribution: layerData.attribution, @@ -160,21 +183,27 @@ export class Map extends L.Map { /* State machine */ setState(state: string) { this.#state = state; - if (this.#state === IDLE) { - this.#resetDestinationMarkers(); - this.#resetTargetMarker(); - this.#showCursor(); + this.#updateCursor(); + + /* Operations to perform if you are NOT in a state */ + if (this.#state !== COALITIONAREA_INTERACT) { + this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => { + coalitionArea.setInteractive(false); + }); } - else if (this.#state === MOVE_UNIT) { - this.#resetTargetMarker(); - this.#createDestinationMarkers(); - if (this.#destinationPreviewMarkers.length > 0) - this.#hideCursor(); + if (this.#state !== COALITIONAREA_DRAW_POLYGON) { + this.#deselectCoalitionAreas(); } - else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { - this.#resetDestinationMarkers(); - this.#createTargetMarker(); - this.#hideCursor(); + + /* Operations to perform if you ARE in a state */ + if (this.#state === COALITIONAREA_INTERACT) { + this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => { + coalitionArea.setInteractive(true); + }); + } + else if (this.#state === COALITIONAREA_DRAW_POLYGON) { + this.#coalitionAreas.push(new CoalitionArea([])); + this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); } document.dispatchEvent(new CustomEvent("mapStateChanged")); } @@ -183,11 +212,23 @@ export class Map extends L.Map { return this.#state; } + deselectAllCoalitionAreas() { + this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => coalitionArea.setSelected(false)); + } + + deleteCoalitionArea(coalitionArea: CoalitionArea) { + if (this.#coalitionAreas.includes(coalitionArea)) + this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1); + if (this.hasLayer(coalitionArea)) + this.removeLayer(coalitionArea); + } + /* Context Menus */ hideAllContextMenus() { this.hideMapContextMenu(); this.hideUnitContextMenu(); this.hideAirbaseContextMenu(); + this.hideCoalitionAreaContextMenu(); } showMapContextMenu(e: any) { @@ -238,6 +279,22 @@ export class Map extends L.Map { this.#airbaseContextMenu.hide(); } + showCoalitionAreaContextMenu(e: any, coalitionArea: CoalitionArea) { + this.hideAllContextMenus(); + var x = e.originalEvent.x; + var y = e.originalEvent.y; + this.#coalitionAreaContextMenu.show(x, y, e.latlng); + this.#coalitionAreaContextMenu.setCoalitionArea(coalitionArea); + } + + getCoalitionAreaContextMenu() { + return this.#coalitionAreaContextMenu; + } + + hideCoalitionAreaContextMenu() { + this.#coalitionAreaContextMenu.hide(); + } + /* Mouse coordinates */ getMousePosition() { return this.#lastMousePosition; @@ -333,36 +390,49 @@ export class Map extends L.Map { } } - addTemporaryMarker(latlng: L.LatLng) { - var marker = new TemporaryUnitMarker(latlng); + addTemporaryMarker(spawnOptions: SpawnOptions) { + var marker = new TemporaryUnitMarker(spawnOptions); marker.addTo(this); this.#temporaryMarkers.push(marker); } removeTemporaryMarker(latlng: L.LatLng) { - var d: number | null = null; + // TODO something more refined than this + var dist: number | null = null; var closest: L.Marker | null = null; var i: number = 0; this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => { var t = latlng.distanceTo(marker.getLatLng()); - if (d == null || t < d) { - d = t; + if (dist == null || t < dist) { + dist = t; closest = marker; i = idx; } }); - if (closest) { + if (closest && dist != null && dist < 100) { this.removeLayer(closest); - delete this.#temporaryMarkers[i]; + this.#temporaryMarkers.splice(i, 1); } } + getSelectedCoalitionArea() { + return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() }); + } + /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { this.hideAllContextMenus(); if (this.#state === IDLE) { - + this.deselectAllCoalitionAreas(); + } + else if (this.#state === COALITIONAREA_DRAW_POLYGON) { + if (this.getSelectedCoalitionArea()?.getEditing()) { + this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng); + } + else { + this.deselectAllCoalitionAreas(); + } } else { this.setState(IDLE); @@ -372,7 +442,7 @@ export class Map extends L.Map { } #onDoubleClick(e: any) { - + this.deselectAllCoalitionAreas(); } #onContextMenu(e: any) { @@ -386,50 +456,49 @@ export class Map extends L.Map { if (!e.originalEvent.ctrlKey) { getUnitsManager().selectedUnitsClearDestinations(); } - getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation) - this.#destinationGroupRotation = 0; + getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation) + this.#destinationGroupRotation = 0; this.#destinationRotationCenter = null; this.#computeDestinationRotation = false; } else if (this.#state === BOMBING) { - getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); - getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); + getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); + getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); } else if (this.#state === CARPET_BOMBING) { - getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); - getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates()); + getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); + getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates()); } else if (this.#state === FIRE_AT_AREA) { - getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); - getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); + getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE); + getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); + } + else { + this.setState(IDLE); } } - #executeAction(e: any, action: string) { - if (action === "bomb") - getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates()); - else if (action === "carpet-bomb") - getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates()); - else if (action === "building-bomb") - getUnitsManager().selectedUnitsBombBuilding(this.getMouseCoordinates()); - else if (action === "fire-at-area") - getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); + #onSelectionStart(e: any) { + this.#selecting = true; + this.#updateCursor(); } #onSelectionEnd(e: any) { + this.#selecting = false; clearTimeout(this.#leftClickTimer); this.#preventLeftClick = true; this.#leftClickTimer = window.setTimeout(() => { this.#preventLeftClick = false; }, 200); getUnitsManager().selectFromBounds(e.selectionBounds); + this.#updateCursor(); } #onMouseDown(e: any) { this.hideAllContextMenus(); if (this.#state == MOVE_UNIT) { - this.#destinationGroupRotation = 0; + this.#destinationGroupRotation = 0; this.#destinationRotationCenter = null; this.#computeDestinationRotation = false; if (e.originalEvent.button == 2) { @@ -446,14 +515,36 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; - if (this.#state === MOVE_UNIT){ + this.#updateCursor(); + + if (this.#state === MOVE_UNIT) { + /* Update the position of the destination cursors depeding on mouse rotation */ if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); - this.#updateDestinationPreview(e); + this.#updateDestinationCursors(); } else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { - this.#targetMarker.setLatLng(this.getMouseCoordinates()); + this.#targetCursor.setLatLng(this.getMouseCoordinates()); } + else if (this.#state === COALITIONAREA_DRAW_POLYGON) { + this.#drawingCursor.setLatLng(e.latlng); + /* Update the polygon being drawn with the current position of the mouse cursor */ + this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng); + } + } + + #onKeyDown(e: any) { + this.#shiftKey = e.originalEvent.shiftKey; + this.#ctrlKey = e.originalEvent.ctrlKey; + this.#updateCursor(); + this.#updateDestinationCursors(); + } + + #onKeyUp(e: any) { + this.#shiftKey = e.originalEvent.shiftKey; + this.#ctrlKey = e.originalEvent.ctrlKey; + this.#updateCursor(); + this.#updateDestinationCursors(); } #onZoom(e: any) { @@ -462,7 +553,7 @@ export class Map extends L.Map { } #panToUnit(unit: Unit) { - var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude); + var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng); this.setView(unitPosition, this.getZoom(), { animate: false }); } @@ -471,13 +562,6 @@ export class Map extends L.Map { return minimapBoundaries; } - #updateDestinationPreview(e: any) { - Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { - if (idx < this.#destinationPreviewMarkers.length) - this.#destinationPreviewMarkers[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); - }) - } - #createOptionButton(value: string, url: string, title: string, callback: string, argument: string) { var button = document.createElement("button"); const img = document.createElement("img"); @@ -491,47 +575,115 @@ export class Map extends L.Map { return button; } - #createDestinationMarkers() { - this.#resetDestinationMarkers(); + #deselectCoalitionAreas() { + this.getSelectedCoalitionArea()?.setSelected(false); + } - if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) { - /* Create the unit destination preview markers */ - this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { - var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false}); + /* Cursors */ + #showDefaultCursor() { + document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); + } + + #hideDefaultCursor() { + document.getElementById(this.#ID)?.classList.add("hidden-cursor"); + } + + #showDestinationCursors() { + const singleCursor = !this.#shiftKey; + const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length; + if (selectedUnitsCount > 0) { + if (singleCursor && this.#destinationPreviewCursors.length != 1) { + this.#hideDestinationCursors(); + var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); marker.addTo(this); - return marker; - }) + this.#destinationPreviewCursors = [marker]; + } + else if (!singleCursor) { + while (this.#destinationPreviewCursors.length > selectedUnitsCount) { + this.removeLayer(this.#destinationPreviewCursors[0]); + this.#destinationPreviewCursors.splice(0, 1); + } + + while (this.#destinationPreviewCursors.length < selectedUnitsCount) { + var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); + cursor.addTo(this); + this.#destinationPreviewCursors.push(cursor); + } + + this.#updateDestinationCursors(); + } } } - #resetDestinationMarkers() { - /* Remove all the destination preview markers */ - this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { + #updateDestinationCursors() { + const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(); + if (this.#destinationPreviewCursors.length == 1) + this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates()); + else { + Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + if (idx < this.#destinationPreviewCursors.length) + this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates()); + }) + }; + } + + #hideDestinationCursors() { + /* Remove all the destination cursors */ + this.#destinationPreviewCursors.forEach((marker: L.Marker) => { this.removeLayer(marker); }) - this.#destinationPreviewMarkers = []; + this.#destinationPreviewCursors = []; + /* Reset the variables used to compute the rotation of the group cursors */ this.#destinationGroupRotation = 0; this.#computeDestinationRotation = false; this.#destinationRotationCenter = null; } - #createTargetMarker(){ - this.#resetTargetMarker(); - this.#targetMarker.addTo(this); + #showTargetCursor() { + this.#hideTargetCursor(); + this.#targetCursor.addTo(this); } - #resetTargetMarker() { - this.#targetMarker.setLatLng(new L.LatLng(0, 0)); - this.removeLayer(this.#targetMarker); + #hideTargetCursor() { + this.#targetCursor.setLatLng(new L.LatLng(0, 0)); + this.removeLayer(this.#targetCursor); } - #showCursor() { - document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); + #showDrawingCursor() { + this.#hideDefaultCursor(); + if (!this.hasLayer(this.#drawingCursor)) + this.#drawingCursor.addTo(this); } - #hideCursor() { - document.getElementById(this.#ID)?.classList.add("hidden-cursor"); + #hideDrawingCursor() { + this.#drawingCursor.setLatLng(new L.LatLng(0, 0)); + if (this.hasLayer(this.#drawingCursor)) + this.#drawingCursor.removeFrom(this); } -} + + #updateCursor() { + /* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */ + if (this.#ctrlKey || this.#selecting) { + /* Hide all non default cursors */ + this.#hideDestinationCursors(); + this.#hideTargetCursor(); + this.#hideDrawingCursor(); + + this.#showDefaultCursor(); + } else { + /* Hide all the unnecessary cursors depending on the active state */ + if (this.#state !== IDLE && this.#state !== COALITIONAREA_INTERACT) this.#hideDefaultCursor(); + if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors(); + if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor(); + if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor(); + + /* Show the active cursor depending on the active state */ + if (this.#state === IDLE || this.#state === COALITIONAREA_INTERACT) this.#showDefaultCursor(); + else if (this.#state === MOVE_UNIT) this.#showDestinationCursors(); + else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor(); + else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor(); + } + } +} diff --git a/client/src/map/targetmarker.ts b/client/src/map/targetmarker.ts index 30232dc1..9b781f1c 100644 --- a/client/src/map/targetmarker.ts +++ b/client/src/map/targetmarker.ts @@ -1,8 +1,11 @@ -import { DivIcon } from "leaflet"; +import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; import { CustomMarker } from "./custommarker"; export class TargetMarker extends CustomMarker { - #interactive: boolean = false; + constructor(latlng: LatLngExpression, options?: MarkerOptions) { + super(latlng, options); + this.setZIndexOffset(9999); + } createIcon() { this.setIcon(new DivIcon({ @@ -12,7 +15,6 @@ export class TargetMarker extends CustomMarker { })); var el = document.createElement("div"); el.classList.add("ol-target-icon"); - el.classList.toggle("ol-target-icon-interactive", this.#interactive) this.getElement()?.appendChild(el); } } diff --git a/client/src/map/temporaryunitmarker.ts b/client/src/map/temporaryunitmarker.ts index 904ee83d..7c266e0e 100644 --- a/client/src/map/temporaryunitmarker.ts +++ b/client/src/map/temporaryunitmarker.ts @@ -1,13 +1,52 @@ -import { Icon } from "leaflet"; import { CustomMarker } from "./custommarker"; +import { SpawnOptions } from "../controls/mapcontextmenu"; +import { DivIcon } from "leaflet"; +import { SVGInjector } from "@tanem/svg-injector"; +import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils"; export class TemporaryUnitMarker extends CustomMarker { + #spawnOptions: SpawnOptions; + + constructor(spawnOptions: SpawnOptions) { + super(spawnOptions.latlng, {interactive: false}); + this.#spawnOptions = spawnOptions; + } + createIcon() { - var icon = new Icon({ - iconUrl: '/resources/theme/images/markers/temporary-icon.png', - iconSize: [52, 52], - iconAnchor: [26, 26] + const category = getMarkerCategoryByName(this.#spawnOptions.name); + + /* Set the icon */ + var icon = new DivIcon({ + className: 'leaflet-unit-icon', + iconAnchor: [25, 25], + iconSize: [50, 50], }); this.setIcon(icon); + + var el = document.createElement("div"); + el.classList.add("unit"); + el.setAttribute("data-object", `unit-${category}`); + el.setAttribute("data-coalition", this.#spawnOptions.coalition); + + // Main icon + var unitIcon = document.createElement("div"); + unitIcon.classList.add("unit-icon"); + var img = document.createElement("img"); + img.src = `/resources/theme/images/units/${category}.svg`; + img.onload = () => SVGInjector(img); + unitIcon.appendChild(img); + unitIcon.toggleAttribute("data-rotate-to-heading", false); + el.append(unitIcon); + + // Short label + if (category == "aircraft" || category == "helicopter") { + var shortLabel = document.createElement("div"); + shortLabel.classList.add("unit-short-label"); + shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#spawnOptions.name)?.shortLabel || ""; + el.append(shortLabel); + } + + this.getElement()?.appendChild(el); + this.getElement()?.classList.add("ol-temporary-marker"); } } \ No newline at end of file diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 867e08bf..0c81884b 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -3,50 +3,41 @@ import { getInfoPopup, getMap } from ".."; import { Airbase } from "./airbase"; import { Bullseye } from "./bullseye"; -export class MissionHandler -{ - #bullseyes : {[name: string]: Bullseye} = {}; - #airbases : {[name: string]: Airbase} = {}; - #theatre : string = ""; +export class MissionHandler { + #bullseyes: { [name: string]: Bullseye } = {}; + #airbases: { [name: string]: Airbase } = {}; + #theatre: string = ""; - #airbaseData : { [name: string]: object } = {}; + #airbaseData: { [name: string]: object } = {}; // Time - #date : any; - #elapsedTime : any; - #startTime : any; - #time : any; + #date: any; + #elapsedTime: any; + #startTime: any; + #time: any; - #updateTime : any; + #updateTime: any; - constructor() - { + constructor() { } - update(data: BullseyesData | AirbasesData | any) - { - if ("bullseyes" in data) - { - for (let idx in data.bullseyes) - { + update(data: BullseyesData | AirbasesData | any) { + if ("bullseyes" in data) { + for (let idx in data.bullseyes) { const bullseye = data.bullseyes[idx]; if (!(idx in this.#bullseyes)) this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap()); - - if (bullseye.latitude && bullseye.longitude && bullseye.coalition) - { - this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); + + if (bullseye.latitude && bullseye.longitude && bullseye.coalition) { + this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); this.#bullseyes[idx].setCoalition(bullseye.coalition); } } } - - if ("mission" in data) - { - if (data.mission != null && data.mission.theatre != this.#theatre) - { + if ("mission" in data) { + if (data.mission != null && data.mission.theatre != this.#theatre) { this.#theatre = data.mission.theatre; getMap().setTheatre(this.#theatre); @@ -54,31 +45,18 @@ export class MissionHandler } } - - if ("airbases" in data) - { -/* - console.log( Object.values( data.airbases ).sort( ( a:any, b:any ) => { - const aVal = a.callsign.toLowerCase(); - const bVal = b.callsign.toLowerCase(); - - return aVal > bVal ? 1 : -1; - }) ); -//*/ - for (let idx in data.airbases) - { + if ("airbases" in data) { + for (let idx in data.airbases) { var airbase = data.airbases[idx] - if (this.#airbases[idx] === undefined && airbase.callsign != '') - { + if (this.#airbases[idx] === undefined && airbase.callsign != '') { this.#airbases[idx] = new Airbase({ - position: new LatLng(airbase.latitude, airbase.longitude), + position: new LatLng(airbase.latitude, airbase.longitude), name: airbase.callsign }).addTo(getMap()); this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e)); } - if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) - { + if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) { this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); this.#airbases[idx].setCoalition(airbase.coalition); } @@ -87,83 +65,68 @@ export class MissionHandler } } - if ("mission" in data && data.mission != null) - { - if (data.mission != null && data.mission.theatre != this.#theatre) - { + if ("mission" in data && data.mission != null) { + if (data.mission != null && data.mission.theatre != this.#theatre) { this.#theatre = data.mission.theatre; getMap().setTheatre(this.#theatre); - getInfoPopup().setText("Map set to " + this.#theatre); } - if ( "date" in data.mission ) { + if ("date" in data.mission) this.#date = data.mission.date; - } - - if ( "elapsedTime" in data.mission ) { + if ("elapsedTime" in data.mission) this.#elapsedTime = data.mission.elapsedTime; - } - - if ( "startTime" in data.mission ) { + if ("startTime" in data.mission) this.#startTime = data.mission.startTime; - } - - if ( "time" in data.mission ) { + if ("time" in data.mission) this.#time = data.mission.time; - } - } - - if ( "time" in data ) { + if ("time" in data) this.#updateTime = data.time; - } - } - getBullseyes() - { + getBullseyes() { return this.#bullseyes; } + getAirbases() { + return this.#airbases; + } + getDate() { return this.#date; } - getNowDate() { const date = this.getDate(); const time = this.getTime(); - if ( !date ) { + if (!date) { return new Date(); } - - let year = date.Year; + + let year = date.Year; let month = date.Month - 1; - if ( month < 0 ) { + if (month < 0) { month = 11; year--; } - return new Date( year, month, date.Day, time.h, time.m, time.s ); + return new Date(year, month, date.Day, time.h, time.m, time.s); } - getTime() { return this.#time; } - getUpdateTime() { return this.#updateTime; } - #onAirbaseClick(e: any) - { + #onAirbaseClick(e: any) { getMap().showAirbaseContextMenu(e, e.sourceTarget); } } \ No newline at end of file diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index d6ef4fd9..39f95722 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -1,3 +1,12 @@ +import { LatLng, Point, Polygon } from "leaflet"; +import * as turf from "@turf/turf"; +import { UnitDatabase } from "../units/unitdatabase"; +import { aircraftDatabase } from "../units/aircraftdatabase"; +import { helicopterDatabase } from "../units/helicopterdatabase"; +import { groundUnitsDatabase } from "../units/groundunitsdatabase"; +import { Buffer } from "buffer"; +import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; + export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { const φ1 = deg2rad(lat1); // φ, λ in radians const φ2 = deg2rad(lat2); @@ -184,4 +193,101 @@ export function mToNm(m: number) { export function nmToFt(nm: number) { return nm * 6076.12; +} + +export function randomPointInPoly(polygon: Polygon): LatLng { + var bounds = polygon.getBounds(); + var x_min = bounds.getEast(); + var x_max = bounds.getWest(); + var y_min = bounds.getSouth(); + var y_max = bounds.getNorth(); + + var lat = y_min + (Math.random() * (y_max - y_min)); + var lng = x_min + (Math.random() * (x_max - x_min)); + + var poly = polygon.toGeoJSON(); + var inside = turf.inside(turf.point([lng, lat]), poly); + + if (inside) { + return new LatLng(lat, lng); + } else { + return randomPointInPoly(polygon); + } +} + +export function polygonArea(polygon: Polygon) { + var poly = polygon.toGeoJSON(); + return turf.area(poly); +} + +export function randomUnitBlueprintByRole(unitDatabse: UnitDatabase, role: string) { + const unitBlueprints = unitDatabse.getByRole(role); + var index = Math.floor(Math.random() * unitBlueprints.length); + return unitBlueprints[index]; +} + +export function getMarkerCategoryByName(name: string) { + if (aircraftDatabase.getByName(name) != null) + return "aircraft"; + else if (helicopterDatabase.getByName(name) != null) + return "helicopter"; + else if (groundUnitsDatabase.getByName(name) != null){ + // TODO this is very messy + var role = groundUnitsDatabase.getByName(name)?.loadouts[0].roles[0]; + return (role?.includes("SAM")) ? "groundunit-sam" : "groundunit-other"; + } + else + return ""; // TODO add other unit types +} + +export function getUnitDatabaseByCategory(category: string) { + if (category == "aircraft") + return aircraftDatabase; + else if (category == "helicopter") + return helicopterDatabase; + else if (category.includes("groundunit")) + return groundUnitsDatabase; + else + return null; +} + +export function base64ToBytes(base64: string) { + return Buffer.from(base64, 'base64').buffer; +} + +export function enumToState(state: number) { + if (state < states.length) + return states[state]; + else + return states[0]; +} + +export function enumToROE(ROE: number) { + if (ROE < ROEs.length) + return ROEs[ROE]; + else + return ROEs[0]; +} + +export function enumToReactionToThreat(reactionToThreat: number) { + if (reactionToThreat < reactionsToThreat.length) + return reactionsToThreat[reactionToThreat]; + else + return reactionsToThreat[0]; +} + +export function enumToEmissioNCountermeasure(emissionCountermeasure: number) { + if (emissionCountermeasure < emissionsCountermeasures.length) + return emissionsCountermeasures[emissionCountermeasure]; + else + return emissionsCountermeasures[0]; +} + +export function enumToCoalition(coalitionID: number) { + switch (coalitionID){ + case 0: return "neutral"; + case 1: return "red"; + case 2: return "blue"; + } + return ""; } \ No newline at end of file diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index b5e7e8eb..3a09436f 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -36,7 +36,7 @@ export class MouseInfoPanel extends Panel { var selectedUnitPosition = null; var selectedUnits = getUnitsManager().getSelectedUnits(); if (selectedUnits && selectedUnits.length == 1) - selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude); + selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng); /* Draw measures from selected unit, from pin location, and from bullseyes */ this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index c5d6f489..1abf45ef 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -8,6 +8,7 @@ import { Panel } from "./panel"; import { Switch } from "../controls/switch"; import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants"; import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils"; +import { GeneralSettings, Radio, TACAN } from "../@types/unit"; export class UnitControlPanel extends Panel { #altitudeSlider: Slider; @@ -33,7 +34,8 @@ export class UnitControlPanel extends Panel { this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); }); /* Option buttons */ - this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => { + // Reversing the ROEs so that the least "aggressive" option is always on the left + this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => { return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); }); }); @@ -98,13 +100,13 @@ export class UnitControlPanel extends Panel { if (units.length < 20) { this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => { var button = document.createElement("button"); - var callsign = unit.getBaseData().unitName || ""; - var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name; + var callsign = unit.getUnitName() || ""; + var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName(); button.setAttribute("data-label", label); button.setAttribute("data-callsign", callsign); - button.setAttribute("data-coalition", unit.getMissionData().coalition); + button.setAttribute("data-coalition", unit.getCoalition()); button.classList.add("pill", "highlight-coalition") button.addEventListener("click", () => { @@ -139,12 +141,12 @@ export class UnitControlPanel extends Panel { element.toggleAttribute("data-show-advanced-settings-button", units.length == 1); /* Flight controls */ - var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitude}); - var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitudeType}); - var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeed}); - var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeedType}); - var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff}); - var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads}); + var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()}); + var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()}); + var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()}); + var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()}); + var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()}); + var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()}); if (selectedUnitsTypes.length == 1) { this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false); @@ -170,15 +172,15 @@ export class UnitControlPanel extends Panel { /* Option buttons */ this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value)) + button.classList.toggle("selected", units.every((unit: Unit) => unit.getROE() === button.value)) }); this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value)) + button.classList.toggle("selected", units.every((unit: Unit) => unit.getReactionToThreat() === button.value)) }); this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value)) + button.classList.toggle("selected", units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value)) }); this.#onOffSwitch.setValue(onOff, false); @@ -207,11 +209,11 @@ export class UnitControlPanel extends Panel { const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement; const unit = units[0]; - const roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles}) + const roles = aircraftDatabase.getByName(unit.getName())?.loadouts.map((loadout) => {return loadout.roles}) const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker"); const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS"); - const radioMHz = Math.floor(unit.getOptionsData().radio.frequency / 1000000); - const radioDecimals = (unit.getOptionsData().radio.frequency / 1000000 - radioMHz) * 1000; + const radioMHz = Math.floor(unit.getRadio().frequency / 1000000); + const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000; /* Activate the correct options depending on unit type */ this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS); @@ -223,28 +225,28 @@ export class UnitControlPanel extends Panel { /* Set common properties */ // Name - unitNameEl.innerText = unit.getBaseData().unitName; + unitNameEl.innerText = unit.getUnitName(); // General settings - prohibitJettisonCheckbox.checked = unit.getOptionsData().generalSettings.prohibitJettison; - prohibitAfterburnerCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAfterburner; - prohibitAACheckbox.checked = unit.getOptionsData().generalSettings.prohibitAA; - prohibitAGCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAG; - prohibitAirWpnCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAirWpn; + prohibitJettisonCheckbox.checked = unit.getGeneralSettings().prohibitJettison; + prohibitAfterburnerCheckbox.checked = unit.getGeneralSettings().prohibitAfterburner; + prohibitAACheckbox.checked = unit.getGeneralSettings().prohibitAA; + prohibitAGCheckbox.checked = unit.getGeneralSettings().prohibitAG; + prohibitAirWpnCheckbox.checked = unit.getGeneralSettings().prohibitAirWpn; // Tasking - tankerCheckbox.checked = unit.getTaskData().isTanker; - AWACSCheckbox.checked = unit.getTaskData().isAWACS; + tankerCheckbox.checked = unit.getIsTanker(); + AWACSCheckbox.checked = unit.getIsAWACS(); // TACAN - TACANCheckbox.checked = unit.getOptionsData().TACAN.isOn; - TACANChannelInput.value = String(unit.getOptionsData().TACAN.channel); - TACANCallsignInput.value = String(unit.getOptionsData().TACAN.callsign); - this.#TACANXYDropdown.setValue(unit.getOptionsData().TACAN.XY); + TACANCheckbox.checked = unit.getTACAN().isOn; + TACANChannelInput.value = String(unit.getTACAN().channel); + TACANCallsignInput.value = String(unit.getTACAN().callsign); + this.#TACANXYDropdown.setValue(unit.getTACAN().XY); // Radio radioMhzInput.value = String(radioMHz); - radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber); + radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber); this.#radioDecimalsDropdown.setValue("." + radioDecimals); if (tanker) /* Set tanker specific options */ @@ -255,7 +257,7 @@ export class UnitControlPanel extends Panel { this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]); // This must be done after setting the options - if (!this.#radioCallsignDropdown.selectValue(unit.getOptionsData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range + if (!this.#radioCallsignDropdown.selectValue(unit.getRadio().callsign - 1)) // Ensure the selected value is in the acceptable range this.#radioCallsignDropdown.selectValue(0); } } diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index 91e18920..cedb32bc 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -1,4 +1,5 @@ import { getUnitsManager } from ".."; +import { Ammo } from "../@types/unit"; import { ConvertDDToDMS, rad2deg } from "../other/utils"; import { aircraftDatabase } from "../units/aircraftdatabase"; import { Unit } from "../units/unit"; @@ -51,21 +52,21 @@ export class UnitInfoPanel extends Panel { #onUnitUpdate(unit: Unit) { if (this.getElement() != null && this.getVisible() && unit.getSelected()) { - const baseData = unit.getBaseData(); + const baseData = unit.getData(); /* Set the unit info */ this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name; this.#unitName.innerText = baseData.unitName; - if (unit.getMissionData().flags.Human) + if (unit.getHuman()) this.#unitControl.innerText = "Human"; else if (baseData.controlled) this.#unitControl.innerText = "Olympus controlled"; else this.#unitControl.innerText = "DCS Controlled"; - this.#fuelBar.style.width = String(unit.getMissionData().fuel + "%"); - this.#fuelPercentage.dataset.percentage = "" + unit.getMissionData().fuel; - this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task"; - this.#currentTask.dataset.coalition = unit.getMissionData().coalition; + this.#fuelBar.style.width = String(unit.getFuel() + "%"); + this.#fuelPercentage.dataset.percentage = "" + unit.getFuel(); + this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task"; + this.#currentTask.dataset.coalition = unit.getCoalition(); this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`; this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == ''); @@ -74,13 +75,13 @@ export class UnitInfoPanel extends Panel { const items = this.#loadoutContainer.querySelector("#loadout-items"); if (items) { - const ammo = Object.values(unit.getMissionData().ammo); + const ammo = Object.values(unit.getAmmo()); if (ammo.length > 0) { - items.replaceChildren(...Object.values(unit.getMissionData().ammo).map( - (ammo: any) => { + items.replaceChildren(...Object.values(unit.getAmmo()).map( + (ammo: Ammo) => { var el = document.createElement("div"); - el.dataset.qty = ammo.count; - el.dataset.item = ammo.desc.displayName; + el.dataset.qty = `${ammo.quantity}`; + el.dataset.item = ammo.name; return el; } )); diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 01317c18..533053fe 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -1,6 +1,8 @@ import { LatLng } from 'leaflet'; import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..'; import { SpawnOptions } from '../controls/mapcontextmenu'; +import { GeneralSettings, Radio, TACAN } from '../@types/unit'; +import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants'; var connected: boolean = false; var paused: boolean = false; @@ -37,19 +39,21 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?: if (options?.time != undefined) optionsString = `time=${options.time}`; - xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true); if (credentials) xmlHttp.setRequestHeader("Authorization", "Basic " + credentials); + + if (uri === UNITS_URI) + xmlHttp.responseType = "arraybuffer"; + xmlHttp.onload = function (e) { if (xmlHttp.status == 200) { - var data = JSON.parse(xmlHttp.responseText); - if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime) { + setConnected(true); + if (xmlHttp.responseType == 'arraybuffer') + callback(xmlHttp.response); + else { + var data = JSON.parse(xmlHttp.responseText); callback(data); - lastUpdateTime = parseInt(data.time); - if (isNaN(lastUpdateTime)) - lastUpdateTime = 0; - setConnected(true); } } else if (xmlHttp.status == 401) { console.error("Incorrect username/password"); @@ -95,6 +99,10 @@ export function setAddress(address: string, port: number) { console.log(`Setting REST address to ${REST_ADDRESS}`) } +export function setLastUpdateTime(newLastUpdateTime: number) { + lastUpdateTime = newLastUpdateTime; +} + export function getAirbases(callback: CallableFunction) { GET(callback, AIRBASES_URI); } @@ -134,13 +142,13 @@ export function spawnExplosion(intensity: number, latlng: LatLng) { } export function spawnGroundUnit(spawnOptions: SpawnOptions) { - var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition }; + var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false }; var data = { "spawnGround": command } POST(data, () => { }); } export function spawnAircraft(spawnOptions: SpawnOptions) { - var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "" }; + var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "", "immediate": spawnOptions.immediate? true: false }; var data = { "spawnAir": command } POST(data, () => { }); } @@ -222,19 +230,19 @@ export function createFormation(ID: number, isLeader: boolean, wingmenIDs: numbe } export function setROE(ID: number, ROE: string) { - var command = { "ID": ID, "ROE": ROE } + var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) } var data = { "setROE": command } POST(data, () => { }); } export function setReactionToThreat(ID: number, reactionToThreat: string) { - var command = { "ID": ID, "reactionToThreat": reactionToThreat } + var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) } var data = { "setReactionToThreat": command } POST(data, () => { }); } export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) { - var command = { "ID": ID, "emissionsCountermeasures": emissionCountermeasure } + var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) } var data = { "setEmissionsCountermeasures": command } POST(data, () => { }); } @@ -299,8 +307,11 @@ export function startUpdate() { /* On the first connection, force request of full data */ getAirbases((data: AirbasesData) => getMissionData()?.update(data)); getBullseye((data: BullseyesData) => getMissionData()?.update(data)); - getMission((data: any) => { getMissionData()?.update(data) }); - getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */); + getMission((data: any) => { + getMissionData()?.update(data); + checkSessionHash(data.sessionHash); + }); + getUnits((buffer: ArrayBuffer) => getUnitsManager()?.update(buffer), true /* Does a full refresh */); requestUpdate(); requestRefresh(); @@ -308,10 +319,9 @@ export function startUpdate() { export function requestUpdate() { /* Main update rate = 250ms is minimum time, equal to server update time. */ - getUnits((data: UnitsData) => { + getUnits((buffer: ArrayBuffer) => { if (!getPaused()) { - getUnitsManager()?.update(data); - checkSessionHash(data.sessionHash); + getUnitsManager()?.update(buffer); } }, false); window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); @@ -321,22 +331,19 @@ export function requestUpdate() { export function requestRefresh() { /* Main refresh rate = 5000ms. */ - getUnits((data: UnitsData) => { - if (!getPaused()) { - getUnitsManager()?.update(data); - getAirbases((data: AirbasesData) => getMissionData()?.update(data)); - getBullseye((data: BullseyesData) => getMissionData()?.update(data)); - getMission((data: any) => { - getMissionData()?.update(data) - }); - - // Update the list of existing units - getUnitDataTable()?.update(); - + + if (!getPaused()) { + getAirbases((data: AirbasesData) => getMissionData()?.update(data)); + getBullseye((data: BullseyesData) => getMissionData()?.update(data)); + getMission((data: any) => { checkSessionHash(data.sessionHash); - } - }, true); - window.setTimeout(() => requestRefresh(), 5000); + getMissionData()?.update(data) + }); + + // Update the list of existing units + getUnitDataTable()?.update(); + } + window.setTimeout(() => requestRefresh(), 1000); } export function checkSessionHash(newSessionHash: string) { diff --git a/client/src/units/dataextractor.ts b/client/src/units/dataextractor.ts new file mode 100644 index 00000000..9390be62 --- /dev/null +++ b/client/src/units/dataextractor.ts @@ -0,0 +1,150 @@ +import { LatLng } from "leaflet"; +import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit"; + +export class DataExtractor { + #seekPosition = 0; + #dataview: DataView; + #decoder: TextDecoder; + #buffer: ArrayBuffer; + + constructor(buffer: ArrayBuffer) { + this.#buffer = buffer; + this.#dataview = new DataView(this.#buffer); + this.#decoder = new TextDecoder("utf-8"); + } + + getSeekPosition() { + return this.#seekPosition; + } + + extractBool() { + const value = this.#dataview.getUint8(this.#seekPosition); + this.#seekPosition += 1; + return value > 0; + } + + extractUInt8() { + const value = this.#dataview.getUint8(this.#seekPosition); + this.#seekPosition += 1; + return value; + } + + extractUInt16() { + const value = this.#dataview.getUint16(this.#seekPosition, true); + this.#seekPosition += 2; + return value; + } + + extractUInt32() { + const value = this.#dataview.getUint32(this.#seekPosition, true); + this.#seekPosition += 4; + return value; + } + + extractUInt64() { + const value = this.#dataview.getBigUint64(this.#seekPosition, true); + this.#seekPosition += 8; + return value; + } + + extractFloat64() { + const value = this.#dataview.getFloat64(this.#seekPosition, true); + this.#seekPosition += 8; + return value; + } + + extractLatLng() { + return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64()) + } + + extractFromBitmask(bitmask: number, position: number) { + return ((bitmask >> position) & 1) > 0; + } + + extractString(length?: number) { + if (length === undefined) + length = this.extractUInt16() + const value = this.#decoder.decode(this.#buffer.slice(this.#seekPosition, this.#seekPosition + length)); + this.#seekPosition += length; + return value; + } + + extractChar() { + return this.extractString(1); + } + + extractTACAN() { + const value: TACAN = { + isOn: this.extractBool(), + channel: this.extractUInt8(), + XY: this.extractChar(), + callsign: this.extractString(4) + } + return value; + } + + extractRadio() { + const value: Radio = { + frequency: this.extractUInt32(), + callsign: this.extractUInt8(), + callsignNumber: this.extractUInt8() + } + return value; + } + + extractGeneralSettings() { + const value: GeneralSettings = { + prohibitJettison: this.extractBool(), + prohibitAA: this.extractBool(), + prohibitAG: this.extractBool(), + prohibitAfterburner: this.extractBool(), + prohibitAirWpn: this.extractBool(), + } + return value; + } + + extractAmmo() { + const value: Ammo[] = []; + const size = this.extractUInt16(); + for (let idx = 0; idx < size; idx++) { + value.push({ + quantity: this.extractUInt16(), + name: this.extractString(33), + guidance: this.extractUInt8(), + category: this.extractUInt8(), + missileCategory: this.extractUInt8() + }); + } + return value; + } + + extractContacts(){ + const value: Contact[] = []; + const size = this.extractUInt16(); + for (let idx = 0; idx < size; idx++) { + value.push({ + ID: this.extractUInt32(), + detectionMethod: this.extractUInt8() + }); + } + return value; + } + + extractActivePath() { + const value: LatLng[] = []; + const size = this.extractUInt16(); + for (let idx = 0; idx < size; idx++) { + value.push(this.extractLatLng()); + } + return value; + } + + extractOffset() { + const value: Offset = { + x: this.extractFloat64(), + y: this.extractFloat64(), + z: this.extractFloat64(), + } + return value; + } +} \ No newline at end of file diff --git a/client/src/units/groundunitsdatabase.ts b/client/src/units/groundunitsdatabase.ts index 5db0a906..3aacebf9 100644 --- a/client/src/units/groundunitsdatabase.ts +++ b/client/src/units/groundunitsdatabase.ts @@ -16,7 +16,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "items": [ ], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -35,7 +35,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -54,7 +54,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -73,7 +73,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -92,7 +92,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -111,7 +111,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -130,7 +130,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Sites" ], "code": "", "name": "Default" @@ -1630,7 +1630,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1655,7 +1655,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1680,7 +1680,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1989,7 +1989,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -2039,7 +2039,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 54e9d37b..05b8772a 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,14 +1,14 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet'; import { getMap, getUnitsManager } from '..'; -import { mToFt, msToKnots, rad2deg } from '../other/utils'; +import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils'; import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server'; -import { aircraftDatabase } from './aircraftdatabase'; -import { groundUnitsDatabase } from './groundunitsdatabase'; import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; -import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../map/map'; import { TargetMarker } from '../map/targetmarker'; +import { BOMBING, CARPET_BOMBING, DataIndexes, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants'; +import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit'; +import { DataExtractor } from './dataextractor'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -19,76 +19,113 @@ var pathIcon = new Icon({ export class Unit extends CustomMarker { ID: number; - #data: UnitData = { - baseData: { - controlled: false, - name: "", - unitName: "", - groupName: "", - alive: true, - category: "", - }, - flightData: { - latitude: 0, - longitude: 0, - altitude: 0, - heading: 0, - speed: 0, - }, - missionData: { - fuel: 0, - flags: {}, - ammo: {}, - contacts: {}, - hasTask: false, - coalition: "", - }, - formationData: { - leaderID: 0 - }, - taskData: { - currentState: "NONE", - currentTask: "", - activePath: {}, - desiredSpeed: 0, - desiredSpeedType: "GS", - desiredAltitude: 0, - desiredAltitudeType: "AGL", - targetLocation: {}, - isTanker: false, - isAWACS: false, - onOff: true, - followRoads: false, - targetID: 0 - }, - optionsData: { - ROE: "", - reactionToThreat: "", - emissionsCountermeasures: "", - TACAN: { isOn: false, channel: 0, XY: "X", callsign: "" }, - radio: { frequency: 0, callsign: 1, callsignNumber: 1}, - generalSettings: { prohibitJettison: false, prohibitAA: false, prohibitAG: false, prohibitAfterburner: false, prohibitAirWpn: false} - } + #alive: boolean = false; + #human: boolean = false; + #controlled: boolean = false; + #coalition: string = "neutral"; + #country: number = 0; + #name: string = ""; + #unitName: string = ""; + #groupName: string = ""; + #state: string = states[0]; + #task: string = "" + #hasTask: boolean = false; + #position: LatLng = new LatLng(0, 0, 0); + #speed: number = 0; + #heading: number = 0; + #isTanker: boolean = false; + #isAWACS: boolean = false; + #onOff: boolean = true; + #followRoads: boolean = false; + #fuel: number = 0; + #desiredSpeed: number = 0; + #desiredSpeedType: string = "GS"; + #desiredAltitude: number = 0; + #desiredAltitudeType: string = "AGL"; + #leaderID: number = 0; + #formationOffset: Offset = { + x: 0, + y: 0, + z: 0 }; + #targetID: number = 0; + #targetPosition: LatLng = new LatLng(0, 0); + #ROE: string = ROEs[1]; + #reactionToThreat: string = reactionsToThreat[2]; + #emissionsCountermeasures: string = emissionsCountermeasures[2]; + #TACAN: TACAN = { + isOn: false, + XY: 'X', + callsign: 'TKR', + channel: 0 + }; + #radio: Radio = { + frequency: 124000000, + callsign: 1, + callsignNumber: 1 + }; + #generalSettings: GeneralSettings = { + prohibitAA: false, + prohibitAfterburner: false, + prohibitAG: false, + prohibitAirWpn: false, + prohibitJettison: false + }; + #ammo: Ammo[] = []; + #contacts: Contact[] = []; + #activePath: LatLng[] = []; #selectable: boolean; #selected: boolean = false; #hidden: boolean = false; #highlighted: boolean = false; - #preventClick: boolean = false; - #pathMarkers: Marker[] = []; #pathPolyline: Polyline; #contactsPolylines: Polyline[]; #miniMapMarker: CircleMarker | null = null; - #targetLocationMarker: TargetMarker; - #targetLocationPolyline: Polyline; - + #targetPositionMarker: TargetMarker; + #targetPositionPolyline: Polyline; #timer: number = 0; - #hotgroup: number | null = null; + getAlive() {return this.#alive}; + getHuman() {return this.#human}; + getControlled() {return this.#controlled}; + getCoalition() {return this.#coalition}; + getCountry() {return this.#country}; + getName() {return this.#name}; + getUnitName() {return this.#unitName}; + getGroupName() {return this.#groupName}; + getState() {return this.#state}; + getTask() {return this.#task}; + getHasTask() {return this.#hasTask}; + getPosition() {return this.#position}; + getSpeed() {return this.#speed}; + getHeading() {return this.#heading}; + getIsTanker() {return this.#isTanker}; + getIsAWACS() {return this.#isAWACS}; + getOnOff() {return this.#onOff}; + getFollowRoads() {return this.#followRoads}; + getFuel() {return this.#fuel}; + getDesiredSpeed() {return this.#desiredSpeed}; + getDesiredSpeedType() {return this.#desiredSpeedType}; + getDesiredAltitude() {return this.#desiredAltitude}; + getDesiredAltitudeType() {return this.#desiredAltitudeType}; + getLeaderID() {return this.#leaderID}; + getFormationOffset() {return this.#formationOffset}; + getTargetID() {return this.#targetID}; + getTargetPosition() {return this.#targetPosition}; + getROE() {return this.#ROE}; + getReactionToThreat() {return this.#reactionToThreat}; + getEmissionsCountermeasures() {return this.#emissionsCountermeasures}; + getTACAN() {return this.#TACAN}; + getRadio() {return this.#radio}; + getGeneralSettings() {return this.#generalSettings}; + getAmmo() {return this.#ammo}; + getContacts() {return this.#contacts}; + getActivePath() {return this.#activePath}; + static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; if (type === "Aircraft") return Aircraft; @@ -98,26 +135,24 @@ export class Unit extends CustomMarker { if (type === "NavyUnit") return NavyUnit; } - constructor(ID: number, data: UpdateData) { + constructor(ID: number) { super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); this.ID = ID; - this.#selectable = true; + this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); + this.#pathPolyline.addTo(getMap()); + this.#contactsPolylines = []; + this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0)); + this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 }); + this.on('click', (e) => this.#onClick(e)); this.on('dblclick', (e) => this.#onDoubleClick(e)); this.on('contextmenu', (e) => this.#onContextMenu(e)); this.on('mouseover', () => { this.setHighlighted(true); }) this.on('mouseout', () => { this.setHighlighted(false); }) - this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); - this.#pathPolyline.addTo(getMap()); - this.#contactsPolylines = []; - - this.#targetLocationMarker = new TargetMarker(new LatLng(0, 0)); - this.#targetLocationPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 }); - /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); @@ -126,19 +161,129 @@ export class Unit extends CustomMarker { document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); }); - - /* Set the unit data */ - this.setData(data); } - getMarkerCategory() { + getCategory() { // Overloaded by child classes return ""; } + /********************** Unit data *************************/ + setData(dataExtractor: DataExtractor) { + var updateMarker = !getMap().hasLayer(this); + + var datumIndex = 0; + while (datumIndex != DataIndexes.endOfData) { + datumIndex = dataExtractor.extractUInt8(); + switch (datumIndex) { + case DataIndexes.category: dataExtractor.extractString(); break; + case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; + case DataIndexes.human: this.#human = dataExtractor.extractBool(); break; + case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break; + case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; + case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; + case DataIndexes.name: this.#name = dataExtractor.extractString(); break; + case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; + case DataIndexes.groupName: this.#groupName = dataExtractor.extractString(); break; + case DataIndexes.state: this.#state = enumToState(dataExtractor.extractUInt8()); updateMarker = true; break; + case DataIndexes.task: this.#task = dataExtractor.extractString(); break; + case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break; + case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; + case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.isTanker: this.#isTanker = dataExtractor.extractBool(); break; + case DataIndexes.isAWACS: this.#isAWACS = dataExtractor.extractBool(); break; + case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; + case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; + case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; + case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; + case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; + case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; + case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractBool() ? "AGL" : "ASL"; break; + case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break; + case DataIndexes.formationOffset: this.#formationOffset = dataExtractor.extractOffset(); break; + case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break; + case DataIndexes.targetPosition: this.#targetPosition = dataExtractor.extractLatLng(); break; + case DataIndexes.ROE: this.#ROE = enumToROE(dataExtractor.extractUInt8()); break; + case DataIndexes.reactionToThreat: this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8()); break; + case DataIndexes.emissionsCountermeasures: this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8()); break; + case DataIndexes.TACAN: this.#TACAN = dataExtractor.extractTACAN(); break; + case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break; + case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break; + case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break; + case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break; + case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break; + } + } + + /* Dead units can't be selected */ + this.setSelected(this.getSelected() && this.#alive && !this.getHidden()) + + if (updateMarker) + this.#updateMarker(); + + // TODO dont delete the polylines of the detected units + this.#clearContacts(); + if (this.getSelected()) { + this.#drawPath(); + this.#drawContacts(); + this.#drawTarget(); + } + else { + this.#clearPath(); + this.#clearTarget(); + } + + document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); + } + + getData() { + return { + alive: this.#alive, + human: this.#human, + controlled: this.#controlled, + coalition: this.#coalition, + country: this.#country, + name: this.#name, + unitName: this.#unitName, + groupName: this.#groupName, + state: this.#state, + task: this.#task, + hasTask: this.#hasTask, + position: this.#position, + speed: this.#speed, + heading: this.#heading, + isTanker: this.#isTanker, + isAWACS: this.#isAWACS, + onOff: this.#onOff, + followRoads: this.#followRoads, + fuel: this.#fuel, + desiredSpeed: this.#desiredSpeed, + desiredSpeedType: this.#desiredSpeedType, + desiredAltitude: this.#desiredAltitude, + desiredAltitudeType: this.#desiredAltitudeType, + leaderID: this.#leaderID, + formationOffset: this.#formationOffset, + targetID: this.#targetID, + targetPosition: this.#targetPosition, + ROE: this.#ROE, + reactionToThreat: this.#reactionToThreat, + emissionsCountermeasures: this.#emissionsCountermeasures, + TACAN: this.#TACAN, + radio: this.#radio, + generalSettings: this.#generalSettings, + ammo: this.#ammo, + contacts: this.#contacts, + activePath: this.#activePath + } + } + + getMarkerCategory(): string { + return getMarkerCategoryByName(this.getName()); + } + getDatabase(): UnitDatabase | null { - // Overloaded by child classes - return null; + return getUnitDatabaseByCategory(this.getMarkerCategory()); } getIconOptions(): UnitIconOptions { @@ -151,14 +296,20 @@ export class Unit extends CustomMarker { showShortLabel: false, showFuel: false, showAmmo: false, - showSummary: false, + showSummary: false, rotateToHeading: false } } + setAlive(newAlive: boolean) { + if (newAlive != this.#alive) + document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); + this.#alive = newAlive; + } + setSelected(selected: boolean) { /* Only alive units can be selected. Some units are not selectable (weapons) */ - if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { + if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected); if (selected) { @@ -167,7 +318,7 @@ export class Unit extends CustomMarker { } else { document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); - this.#clearDetectedUnits(); + this.#clearContacts(); this.#clearPath(); this.#clearTarget(); } @@ -208,83 +359,7 @@ export class Unit extends CustomMarker { } getGroupMembers() { - return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getBaseData().groupName === this.getBaseData().groupName;}); - } - - /********************** Unit data *************************/ - setData(data: UpdateData) { - /* Check if data has changed comparing new values to old values */ - const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)); - const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading); - const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive); - const stateChanged = (data.taskData != undefined && data.taskData.currentState != undefined && this.getTaskData().currentState != data.taskData.currentState); - const controlledChanged = (data.baseData != undefined && data.baseData.controlled != undefined && this.getBaseData().controlled != data.baseData.controlled); - var updateMarker = (positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this)); - - /* Load the data from the received json */ - Object.keys(this.#data).forEach((key1: string) => { - Object.keys(this.#data[key1 as keyof(UnitData)]).forEach((key2: string) => { - if (key1 in data && key2 in data[key1]) { - var value1 = this.#data[key1 as keyof(UnitData)]; - var value2 = value1[key2 as keyof typeof value1]; - if (typeof data[key1][key2] === typeof value2 || typeof value2 === "undefined") - //@ts-ignore - this.#data[key1 as keyof(UnitData)][key2 as keyof typeof struct] = data[key1][key2]; - } - }); - }); - - /* Fire an event when a unit dies */ - if (aliveChanged && this.getBaseData().alive == false) - document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); - - /* Dead units can't be selected */ - this.setSelected(this.getSelected() && this.getBaseData().alive && !this.getHidden()) - - if (updateMarker) - this.#updateMarker(); - - this.#clearDetectedUnits(); - if (this.getSelected()) { - this.#drawPath(); - this.#drawDetectedUnits(); - this.#drawTarget(); - } - else { - this.#clearPath(); - this.#clearTarget(); - } - - - document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); - } - - getData() { - return this.#data; - } - - getBaseData() { - return this.getData().baseData; - } - - getFlightData() { - return this.getData().flightData; - } - - getTaskData() { - return this.getData().taskData; - } - - getMissionData() { - return this.getData().missionData; - } - - getFormationData() { - return this.getData().formationData; - } - - getOptionsData() { - return this.getData().optionsData; + return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; }); } /********************** Icon *************************/ @@ -300,7 +375,7 @@ export class Unit extends CustomMarker { var el = document.createElement("div"); el.classList.add("unit"); el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); - el.setAttribute("data-coalition", this.getMissionData().coalition); + el.setAttribute("data-coalition", this.#coalition); // Generate and append elements depending on active options // Velocity vector @@ -334,7 +409,7 @@ export class Unit extends CustomMarker { } // State icon - if (this.getIconOptions().showState){ + if (this.getIconOptions().showState) { var state = document.createElement("div"); state.classList.add("unit-state"); el.appendChild(state); @@ -344,7 +419,7 @@ export class Unit extends CustomMarker { if (this.getIconOptions().showShortLabel) { var shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = this.getDatabase()?.getByName(this.getBaseData().name)?.shortLabel || ""; + shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || ""; el.append(shortLabel); } @@ -359,7 +434,7 @@ export class Unit extends CustomMarker { } // Ammo indicator - if (this.getIconOptions().showAmmo){ + if (this.getIconOptions().showAmmo) { var ammoIndicator = document.createElement("div"); ammoIndicator.classList.add("unit-ammo"); for (let i = 0; i <= 3; i++) @@ -373,7 +448,7 @@ export class Unit extends CustomMarker { summary.classList.add("unit-summary"); var callsign = document.createElement("div"); callsign.classList.add("unit-callsign"); - callsign.innerText = this.getBaseData().unitName; + callsign.innerText = this.#unitName; var altitude = document.createElement("div"); altitude.classList.add("unit-altitude"); var speed = document.createElement("div"); @@ -391,15 +466,15 @@ export class Unit extends CustomMarker { updateVisibility() { var hidden = false; const hiddenUnits = getUnitsManager().getHiddenTypes(); - if (this.getMissionData().flags.Human && hiddenUnits.includes("human")) + if (this.#human && hiddenUnits.includes("human")) hidden = true; - else if (this.getBaseData().controlled == false && hiddenUnits.includes("dcs")) + else if (this.#controlled == false && hiddenUnits.includes("dcs")) hidden = true; else if (hiddenUnits.includes(this.getMarkerCategory())) hidden = true; - else if (hiddenUnits.includes(this.getMissionData().coalition)) + else if (hiddenUnits.includes(this.#coalition)) hidden = true; - this.setHidden(hidden || !this.getBaseData().alive); + this.setHidden(hidden || !this.#alive); } setHidden(hidden: boolean) { @@ -421,114 +496,114 @@ export class Unit extends CustomMarker { } getLeader() { - return getUnitsManager().getUnitByID(this.getFormationData().leaderID); + return getUnitsManager().getUnitByID(this.#leaderID); } - canRole(roles: string | string[]) { - if (typeof(roles) === "string") + canFulfillRole(roles: string | string[]) { + if (typeof (roles) === "string") roles = [roles]; - return this.getDatabase()?.getByName(this.getBaseData().name)?.loadouts.some((loadout: LoadoutBlueprint) => { - return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)}); + return this.getDatabase()?.getByName(this.#name)?.loadouts.some((loadout: LoadoutBlueprint) => { + return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) }); }); } /********************** Unit commands *************************/ addDestination(latlng: L.LatLng) { - if (!this.getMissionData().flags.Human) { + if (!this.#human) { var path: any = {}; - if (this.getTaskData().activePath != undefined) { - path = this.getTaskData().activePath; - path[(Object.keys(path).length + 1).toString()] = latlng; + if (this.#activePath.length > 0) { + path = this.#activePath; + path[(Object.keys(path).length).toString()] = latlng; } else { - path = { "1": latlng }; + path = [latlng]; } addDestination(this.ID, path); } } clearDestinations() { - if (!this.getMissionData().flags.Human) - this.getTaskData().activePath = undefined; + if (!this.#human) + this.#activePath = []; } attackUnit(targetID: number) { /* Units can't attack themselves */ - if (!this.getMissionData().flags.Human) + if (!this.#human) if (this.ID != targetID) attackUnit(this.ID, targetID); } followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) { /* Units can't follow themselves */ - if (!this.getMissionData().flags.Human) + if (!this.#human) if (this.ID != targetID) followUnit(this.ID, targetID, offset); } landAt(latlng: LatLng) { - if (!this.getMissionData().flags.Human) + if (!this.#human) landAt(this.ID, latlng); } changeSpeed(speedChange: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) changeSpeed(this.ID, speedChange); } changeAltitude(altitudeChange: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) changeAltitude(this.ID, altitudeChange); } setSpeed(speed: number) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setSpeed(this.ID, speed); } setSpeedType(speedType: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setSpeedType(this.ID, speedType); } setAltitude(altitude: number) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setAltitude(this.ID, altitude); } setAltitudeType(altitudeType: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setAltitudeType(this.ID, altitudeType); } setROE(ROE: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setROE(this.ID, ROE); } setReactionToThreat(reactionToThreat: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setReactionToThreat(this.ID, reactionToThreat); } setEmissionsCountermeasures(emissionCountermeasure: string) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setEmissionsCountermeasures(this.ID, emissionCountermeasure); } setLeader(isLeader: boolean, wingmenIDs: number[] = []) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setLeader(this.ID, isLeader, wingmenIDs); } setOnOff(onOff: boolean) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setOnOff(this.ID, onOff); } setFollowRoads(followRoads: boolean) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setFollowRoads(this.ID, followRoads); } @@ -537,12 +612,12 @@ export class Unit extends CustomMarker { } refuel() { - if (!this.getMissionData().flags.Human) + if (!this.#human) refuel(this.ID); } setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) { - if (!this.getMissionData().flags.Human) + if (!this.#human) setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings); } @@ -565,7 +640,8 @@ export class Unit extends CustomMarker { /***********************************************/ onAdd(map: Map): this { super.onAdd(map); - getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + /* If this is the first time adding this unit to the map, remove the temporary marker */ + getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng)); return this; } @@ -573,7 +649,7 @@ export class Unit extends CustomMarker { #onClick(e: any) { if (!this.#preventClick) { if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { - if (!e.originalEvent.ctrlKey) + if (!e.originalEvent.ctrlKey) getUnitsManager().deselectAllUnits(); this.setSelected(!this.getSelected()); } @@ -590,34 +666,33 @@ export class Unit extends CustomMarker { } #onContextMenu(e: any) { - var options: {[key: string]: {text: string, tooltip: string}} = {}; + var options: { [key: string]: { text: string, tooltip: string } } = {}; const selectedUnits = getUnitsManager().getSelectedUnits(); const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes(); - options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"}; + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" }; if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) { - options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"}; + options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" }; if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft") - options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};; + options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };; } else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { - if (this.getBaseData().category == "Aircraft") { - options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR + if (this.getCategory() == "Aircraft") { + options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR } } - if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) - { - if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["CAS", "Strike"])})) { - options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"}; - options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"}; + if ((selectedUnits.length === 0 && this.getCategory() == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) { + if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) { + options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" }; + options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" }; } } - if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { - if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])})) - options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"}; + if ((selectedUnits.length === 0 && this.getCategory() == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { + if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"]) })) + options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" }; } if (Object.keys(options).length > 0) { @@ -647,17 +722,17 @@ export class Unit extends CustomMarker { } #showFollowOptions(e: any) { - var options: {[key: string]: {text: string, tooltip: string}} = {}; + var options: { [key: string]: { text: string, tooltip: string } } = {}; options = { - 'trail': {text: "Trail", tooltip: "Follow unit in trail formation"}, - 'echelon-lh': {text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation"}, - 'echelon-rh': {text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation"}, - 'line-abreast-lh': {text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation"}, - 'line-abreast-rh': {text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation"}, - 'front': {text: "Front", tooltip: "Fly in front of unit"}, - 'diamond': {text: "Diamond", tooltip: "Follow unit in diamond formation"}, - 'custom': {text: "Custom", tooltip: "Set a custom formation position"}, + 'trail': { text: "Trail", tooltip: "Follow unit in trail formation" }, + 'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" }, + 'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" }, + 'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" }, + 'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" }, + 'front': { text: "Front", tooltip: "Fly in front of unit" }, + 'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" }, + 'custom': { text: "Custom", tooltip: "Set a custom formation position" }, } getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -683,12 +758,12 @@ export class Unit extends CustomMarker { this.updateVisibility(); /* Draw the minimap marker */ - if (this.getBaseData().alive) { + if (this.#alive) { if (this.#miniMapMarker == null) { - this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), { radius: 0.5 }); - if (this.getMissionData().coalition == "neutral") + this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 }); + if (this.#coalition == "neutral") this.#miniMapMarker.setStyle({ color: "#CFD9E8" }); - else if (this.getMissionData().coalition == "red") + else if (this.#coalition == "red") this.#miniMapMarker.setStyle({ color: "#ff5858" }); else this.#miniMapMarker.setStyle({ color: "#247be2" }); @@ -696,8 +771,10 @@ export class Unit extends CustomMarker { this.#miniMapMarker.bringToBack(); } else { - this.#miniMapMarker.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); - this.#miniMapMarker.bringToBack(); + if (this.#miniMapMarker.getLatLng().lat !== this.getPosition().lat || this.#miniMapMarker.getLatLng().lng !== this.getPosition().lng) { + this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); + this.#miniMapMarker.bringToBack(); + } } } else { @@ -709,39 +786,41 @@ export class Unit extends CustomMarker { /* Draw the marker */ if (!this.getHidden()) { - this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) { + this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); + } var element = this.getElement(); if (element != null) { /* Draw the velocity vector */ - element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`); + element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`); /* Set fuel data */ - element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`); - element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20); + element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`); + element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20); /* Set dead/alive flag */ - element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive); + element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); /* Set current unit state */ - if (this.getMissionData().flags.Human) // Unit is human + if (this.#human) // Unit is human element.querySelector(".unit")?.setAttribute("data-state", "human"); - else if (!this.getBaseData().controlled) // Unit is under DCS control (not Olympus) + else if (!this.#controlled) // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); - else if ((this.getBaseData().category == "Aircraft" || this.getBaseData().category == "Helicopter") && !this.getMissionData().hasTask) + else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask) element.querySelector(".unit")?.setAttribute("data-state", "no-task"); - else // Unit is under Olympus control - element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); + else // Unit is under Olympus control + element.querySelector(".unit")?.setAttribute("data-state", this.#state.toLowerCase()); /* Set altitude and speed */ if (element.querySelector(".unit-altitude")) - (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getFlightData().altitude) / 100)); + (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.#position.alt as number) / 100)); if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getFlightData().speed))) + "GS"; + (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS"; /* Rotate elements according to heading */ element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { - const headingDeg = rad2deg(this.getFlightData().heading); + const headingDeg = rad2deg(this.#heading); let currentStyle = el.getAttribute("style") || ""; el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); }); @@ -756,13 +835,13 @@ export class Unit extends CustomMarker { var newHasFox2 = false; var newHasFox3 = false; var newHasOtherAmmo = false; - Object.values(this.getMissionData().ammo).forEach((ammo: any) => { - if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) { - if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) + Object.values(this.#ammo).forEach((ammo: Ammo) => { + if (ammo.category == 1 && ammo.missileCategory == 1) { + if (ammo.guidance == 4 || ammo.guidance == 5) newHasFox1 = true; - else if (ammo.desc.guidance == 2) + else if (ammo.guidance == 2) newHasFox2 = true; - else if (ammo.desc.guidance == 3) + else if (ammo.guidance == 3) newHasFox3 = true; } else @@ -786,31 +865,31 @@ export class Unit extends CustomMarker { /* Set vertical offset for altitude stacking */ var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); + this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); } } #drawPath() { - if (this.getTaskData().activePath != undefined) { + if (this.#activePath != undefined) { var points = []; - points.push(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + points.push(new LatLng(this.#position.lat, this.#position.lng)); /* Add markers if missing */ - while (this.#pathMarkers.length < Object.keys(this.getTaskData().activePath).length) { + while (this.#pathMarkers.length < Object.keys(this.#activePath).length) { var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap()); this.#pathMarkers.push(marker); } /* Remove markers if too many */ - while (this.#pathMarkers.length > Object.keys(this.getTaskData().activePath).length) { + while (this.#pathMarkers.length > Object.keys(this.#activePath).length) { getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]); this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1) } /* Update the position of the existing markers (to avoid creating markers uselessly) */ - for (let WP in this.getTaskData().activePath) { - var destination = this.getTaskData().activePath[WP]; - this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]); + for (let WP in this.#activePath) { + var destination = this.#activePath[WP]; + this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]); points.push(new LatLng(destination.lat, destination.lng)); this.#pathPolyline.setLatLngs(points); } @@ -828,73 +907,64 @@ export class Unit extends CustomMarker { this.#pathPolyline.setLatLngs([]); } - #drawDetectedUnits() { - for (let index in this.getMissionData().contacts) { - var targetData = this.getMissionData().contacts[index]; - if (targetData.object != undefined){ - var target = getUnitsManager().getUnitByID(targetData.object["id_"]) - if (target != null) { - var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude) - var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude) + #drawContacts() { + for (let index in this.#contacts) { + var contactData = this.#contacts[index]; + var contact = getUnitsManager().getUnitByID(contactData.ID) + if (contact != null) { + var startLatLng = new LatLng(this.#position.lat, this.#position.lng) + var endLatLng = new LatLng(contact.#position.lat, contact.#position.lng) - var color; - if (targetData.detectionMethod === "RADAR") - color = "#FFFF00"; - else if (targetData.detectionMethod === "VISUAL") - color = "#FF00FF"; - else if (targetData.detectionMethod === "RWR") - color = "#00FF00"; - else - color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); - targetPolyline.addTo(getMap()); - this.#contactsPolylines.push(targetPolyline) - } + var color; + if (contactData.detectionMethod === 1) + color = "#FF00FF"; + else if (contactData.detectionMethod === 4) + color = "#FFFF00"; + else if (contactData.detectionMethod === 16) + color = "#00FF00"; + else + color = "#FFFFFF"; + var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); + contactPolyline.addTo(getMap()); + this.#contactsPolylines.push(contactPolyline) } } } - #clearDetectedUnits() { + #clearContacts() { for (let index in this.#contactsPolylines) { getMap().removeLayer(this.#contactsPolylines[index]) } } #drawTarget() { - const targetLocation = this.getTaskData().targetLocation; - - if (targetLocation.latitude && targetLocation.longitude && targetLocation.latitude != 0 && targetLocation.longitude != 0) { - const lat = targetLocation.latitude; - const lng = targetLocation.longitude; - if (lat && lng) - this.#drawTargetLocation(new LatLng(lat, lng)); + if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0) { + this.#drawtargetPosition(this.#targetPosition); } - else if (this.getTaskData().targetID != 0 && getUnitsManager().getUnitByID(this.getTaskData().targetID)) { - const flightData = getUnitsManager().getUnitByID(this.getTaskData().targetID)?.getFlightData(); - const lat = flightData?.latitude; - const lng = flightData?.longitude; - if (lat && lng) - this.#drawTargetLocation(new LatLng(lat, lng)); + else if (this.#targetID != 0 && getUnitsManager().getUnitByID(this.#targetID)) { + const position = getUnitsManager().getUnitByID(this.#targetID)?.getPosition(); + if (position) + this.#drawtargetPosition(position); } - else + else this.#clearTarget(); } - #drawTargetLocation(targetLocation: LatLng) { - if (!getMap().hasLayer(this.#targetLocationMarker)) - this.#targetLocationMarker.addTo(getMap()); - if (!getMap().hasLayer(this.#targetLocationPolyline)) - this.#targetLocationPolyline.addTo(getMap()); - this.#targetLocationMarker.setLatLng(new LatLng(targetLocation.lat, targetLocation.lng)); - this.#targetLocationPolyline.setLatLngs([new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), new LatLng(targetLocation.lat, targetLocation.lng)]) - } + #drawtargetPosition(targetPosition: LatLng) { + if (!getMap().hasLayer(this.#targetPositionMarker)) + this.#targetPositionMarker.addTo(getMap()); + if (!getMap().hasLayer(this.#targetPositionPolyline)) + this.#targetPositionPolyline.addTo(getMap()); + this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng)); + this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)]) + } #clearTarget() { - if (getMap().hasLayer(this.#targetLocationMarker)) - this.#targetLocationMarker.removeFrom(getMap()); - - if (getMap().hasLayer(this.#targetLocationPolyline)) - this.#targetLocationPolyline.removeFrom(getMap()); + if (getMap().hasLayer(this.#targetPositionMarker)) + this.#targetPositionMarker.removeFrom(getMap()); + + if (getMap().hasLayer(this.#targetPositionPolyline)) + this.#targetPositionPolyline.removeFrom(getMap()); } } @@ -915,32 +985,28 @@ export class AirUnit extends Unit { } export class Aircraft extends AirUnit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); } - getMarkerCategory() { - return "aircraft"; - } - - getDatabase(): UnitDatabase | null { - return aircraftDatabase; + getCategory() { + return "Aircraft"; } } export class Helicopter extends AirUnit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); } - getMarkerCategory() { - return "helicopter"; + getCategory() { + return "Helicopter"; } } export class GroundUnit extends Unit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); } getIconOptions() { @@ -957,21 +1023,14 @@ export class GroundUnit extends Unit { }; } - getMarkerCategory() { - // TODO this is very messy - var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other"; - return markerCategory; - } - - getDatabase(): UnitDatabase | null { - return groundUnitsDatabase; + getCategory() { + return "GroundUnit"; } } export class NavyUnit extends Unit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); } getIconOptions() { @@ -988,14 +1047,14 @@ export class NavyUnit extends Unit { }; } - getMarkerCategory() { - return "navyunit"; + getCategory() { + return "NavyUnit"; } } export class Weapon extends Unit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); this.setSelectable(false); } @@ -1015,8 +1074,12 @@ export class Weapon extends Unit { } export class Missile extends Weapon { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Missile"; } getMarkerCategory() { @@ -1025,8 +1088,12 @@ export class Missile extends Weapon { } export class Bomb extends Weapon { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Bomb"; } getMarkerCategory() { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 299c2e0a..5d222291 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,9 +1,13 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from ".."; +import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from ".."; import { Unit } from "./unit"; -import { cloneUnit } from "../server/server"; -import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils"; -import { IDLE, MOVE_UNIT } from "../map/map"; +import { cloneUnit, setLastUpdateTime, spawnGroundUnit } from "../server/server"; +import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; +import { CoalitionArea } from "../map/coalitionarea"; +import { Airbase } from "../missionhandler/airbase"; +import { groundUnitsDatabase } from "./groundunitsdatabase"; +import { DataIndexes, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants"; +import { DataExtractor } from "./dataextractor"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -28,8 +32,7 @@ export class UnitsManager { getSelectableAircraft() { const units = this.getUnits(); return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => { - const baseData = units[unitId].getBaseData(); - if (baseData.category === "Aircraft" && baseData.alive === true) { + if (units[unitId].getCategory() === "Aircraft" && units[unitId].getAlive() === true) { acc[unitId] = units[unitId]; } return acc; @@ -48,15 +51,15 @@ export class UnitsManager { } getUnitsByHotgroup(hotgroup: number) { - return Object.values(this.#units).filter((unit: Unit) => { return unit.getBaseData().alive && unit.getHotgroup() == hotgroup }); + return Object.values(this.#units).filter((unit: Unit) => { return unit.getAlive() && unit.getHotgroup() == hotgroup }); } - addUnit(ID: number, data: UnitData) { - if (data.baseData && data.baseData.category){ - /* The name of the unit category is exactly the same as the constructor name */ - var constructor = Unit.getConstructor(data.baseData.category); + addUnit(ID: number, category: string) { + if (category){ + /* The name of the unit category is exactly the same as the constructor name */ + var constructor = Unit.getConstructor(category); if (constructor != undefined) { - this.#units[ID] = new constructor(ID, data); + this.#units[ID] = new constructor(ID); } } } @@ -65,30 +68,26 @@ export class UnitsManager { } - update(data: UnitsData) { - var updatedUnits: Unit[] = []; - Object.keys(data.units) - .filter((ID: string) => !(ID in this.#units)) - .reduce((timeout: number, ID: string) => { - window.setTimeout(() => { - if (!(ID in this.#units)) - this.addUnit(parseInt(ID), data.units[ID]); - this.#units[parseInt(ID)]?.setData(data.units[ID]); - }, timeout); - return timeout + 10; - }, 10); + update(buffer: ArrayBuffer) { + var dataExtractor = new DataExtractor(buffer); + var updateTime = Number(dataExtractor.extractUInt64()); - Object.keys(data.units) - .filter((ID: string) => ID in this.#units) - .forEach((ID: string) => { - updatedUnits.push(this.#units[parseInt(ID)]); - this.#units[parseInt(ID)]?.setData(data.units[ID]) - }); + while (dataExtractor.getSeekPosition() < buffer.byteLength) { + const ID = dataExtractor.extractUInt32(); + if (!(ID in this.#units)) { + const datumIndex = dataExtractor.extractUInt8(); + if (datumIndex == DataIndexes.category) { + const category = dataExtractor.extractString(); + this.addUnit(ID, category); + } + else { + // TODO request a refresh since we must have missed some packets + } + } + this.#units[ID]?.setData(dataExtractor); + } - this.getSelectedUnits().forEach((unit: Unit) => { - if (!updatedUnits.includes(unit)) - unit.setData({}) - }); + setLastUpdateTime(updateTime); } setHiddenType(key: string, value: boolean) { @@ -115,7 +114,7 @@ export class UnitsManager { this.deselectAllUnits(); for (let ID in this.#units) { if (this.#units[ID].getHidden() == false) { - var latlng = new LatLng(this.#units[ID].getFlightData().latitude, this.#units[ID].getFlightData().longitude); + var latlng = new LatLng(this.#units[ID].getPosition().lat, this.#units[ID].getPosition().lng); if (bounds.contains(latlng)) { this.#units[ID].setSelected(true); } @@ -132,11 +131,11 @@ export class UnitsManager { } if (options) { if (options.excludeHumans) - selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getMissionData().flags.Human }); + selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getHuman() }); if (options.onlyOnePerGroup) { var temp: Unit[] = []; for (let unit of selectedUnits) { - if (!temp.some((otherUnit: Unit) => unit.getBaseData().groupName == otherUnit.getBaseData().groupName)) + if (!temp.some((otherUnit: Unit) => unit.getGroupName() == otherUnit.getGroupName())) temp.push(unit); } selectedUnits = temp; @@ -181,7 +180,7 @@ export class UnitsManager { if (this.getSelectedUnits().length == 0) return undefined; return this.getSelectedUnits().map((unit: Unit) => { - return unit.getMissionData().coalition + return unit.getCoalition() })?.reduce((a: any, b: any) => { return a == b ? a : undefined }); @@ -201,8 +200,8 @@ export class UnitsManager { for (let idx in selectedUnits) { const unit = selectedUnits[idx]; /* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */ - if (unit.getTaskData().currentState === "Follow") { - const leader = this.getUnitByID(unit.getFormationData().leaderID) + if (unit.getState() === "Follow") { + const leader = this.getUnitByID(unit.getLeaderID()) if (leader && leader.getSelected()) leader.addDestination(latlng); else @@ -221,8 +220,8 @@ export class UnitsManager { var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); for (let idx in selectedUnits) { const unit = selectedUnits[idx]; - if (unit.getTaskData().currentState === "Follow") { - const leader = this.getUnitByID(unit.getFormationData().leaderID) + if (unit.getState() === "Follow") { + const leader = this.getUnitByID(unit.getLeaderID()) if (leader && leader.getSelected()) leader.clearDestinations(); else @@ -333,13 +332,13 @@ export class UnitsManager { for (let idx in selectedUnits) { selectedUnits[idx].attackUnit(ID); } - this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); + this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`); } selectedUnitsDelete(explosion: boolean = false) { var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */ const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => { - return unit.getMissionData().flags.Human === true; + return unit.getHuman() === true; }); if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) { @@ -400,7 +399,7 @@ export class UnitsManager { } count++; } - this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); + this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getUnitName()}`); } selectedUnitsSetHotgroup(hotgroup: number) { @@ -422,7 +421,7 @@ export class UnitsManager { /* Compute the center of the group */ var center = { x: 0, y: 0 }; selectedUnits.forEach((unit: Unit) => { - var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); + var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng); center.x += mercator.x / selectedUnits.length; center.y += mercator.y / selectedUnits.length; }); @@ -430,7 +429,7 @@ export class UnitsManager { /* Compute the distances from the center of the group */ var unitDestinations: { [key: number]: LatLng } = {}; selectedUnits.forEach((unit: Unit) => { - var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude); + var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng); var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y }; /* Rotate the distance according to the group rotation */ @@ -490,7 +489,7 @@ export class UnitsManager { if (!this.#pasteDisabled) { for (let idx in this.#copiedUnits) { var unit = this.#copiedUnits[idx]; - getMap().addTemporaryMarker(getMap().getMouseCoordinates()); + //getMap().addTemporaryMarker(getMap().getMouseCoordinates()); cloneUnit(unit.ID, getMap().getMouseCoordinates()); this.#showActionMessage(this.#copiedUnits, `pasted`); } @@ -499,6 +498,31 @@ export class UnitsManager { } } + createIADS(coalitionArea: CoalitionArea, options: {[key: string]: boolean}, density: number) { + const activeRoles = Object.keys(options).filter((key: string) => { return options[key]; }); + const airbases = getMissionHandler().getAirbases(); + const pointsNumber = polygonArea(coalitionArea) / 1e7 * density / 100; + for (let i = 0; i < pointsNumber; i++) { + const latlng = randomPointInPoly(coalitionArea); + var minDistance: number = Infinity; + var maxDistance: number = 0; + Object.values(airbases).forEach((airbase: Airbase) => { + var distance = airbase.getLatLng().distanceTo(latlng); + if (distance < minDistance) minDistance = distance; + if (distance > maxDistance) maxDistance = distance; + }); + + const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; + const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role]; + if (Math.random() < probability){ + const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role); + const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true}; + spawnGroundUnit(spawnOptions); + getMap().addTemporaryMarker(spawnOptions); + } + } + } + /***********************************************/ #onKeyUp(event: KeyboardEvent) { if (!keyEventWasInInput(event) && event.key === "Delete" ) { @@ -535,8 +559,8 @@ export class UnitsManager { #showActionMessage(units: Unit[], message: string) { if (units.length == 1) - getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`); + getInfoPopup().setText(`${units[0].getUnitName()} ${message}`); else if (units.length > 1) - getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`); + getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`); } } \ No newline at end of file diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index 0940f076..eed3f719 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -1,17 +1,17 @@ -
-
+
+
-
- - - - +
+ + + +
-
+
Aircraft role
@@ -54,9 +54,9 @@
- +
-
+
Ground unit role
@@ -74,16 +74,16 @@
- +
-
+
-
+
@@ -96,17 +96,63 @@
-

-
- +

Parking available:

- + - +
+ +
+
+
+
+ + + + +
+
+
+
Unit types
+
+ +
+
+ + + +
+
+
IADS density
+
+ +
+ +
\ No newline at end of file diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index 922fe414..5fa3e35f 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -29,38 +29,44 @@
-
- +
+
-
+ data-on-click-params='{ "coalition": "blue" }'>BLUEFOR
+ data-on-click-params='{ "coalition": "red" }'>REDFOR
+ data-on-click-params='{ "coalition": "neutral" }'>NEUTRAL
-
- -
-
ATC
+
+ data-on-click-params='{"selector": "#strip-board-ground"}' class="off"> + data-on-click-params='{"selector": "#strip-board-tower"}' class="off"> +
+
+ +
+
+ +
\ No newline at end of file diff --git a/client/views/uikit/uikit.ejs b/client/views/uikit/uikit.ejs index e4c0a2f4..224cc7b3 100644 --- a/client/views/uikit/uikit.ejs +++ b/client/views/uikit/uikit.ejs @@ -1047,7 +1047,7 @@
Open air
5
- +
diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 81ccd4ff..7f086d6e 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.3.0-alpha" -local debug = false +local debug = FALSE Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -343,7 +343,7 @@ function Olympus.spawnGroundUnit(coalition, unitType, lat, lng) units = unitTable, country = countryID, category = 'vehicle', - name = "Olympus-" .. Olympus.unitCounter, + name = "Ground-" .. Olympus.unitCounter, } mist.dynAdd(vars) Olympus.unitCounter = Olympus.unitCounter + 1 diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json deleted file mode 100644 index e8ff2462..00000000 --- a/src/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files.associations": { - "*.ejs": "html", - "xstring": "cpp", - "vector": "cpp", - "list": "cpp" - } -} \ No newline at end of file diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 4759cb4f..2b70e07f 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -36,6 +36,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 00e7a412..50797e65 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -48,6 +48,9 @@ Header Files + + Header Files + @@ -92,5 +95,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/src/core/include/aircraft.h b/src/core/include/aircraft.h index 7fd3f615..be27e35b 100644 --- a/src/core/include/aircraft.h +++ b/src/core/include/aircraft.h @@ -4,10 +4,8 @@ class Aircraft : public AirUnit { public: - Aircraft(json::value json, int ID); + Aircraft(json::value json, unsigned int ID); - virtual wstring getCategory() { return L"Aircraft"; }; - - virtual void changeSpeed(wstring change); - virtual void changeAltitude(wstring change); + virtual void changeSpeed(string change); + virtual void changeAltitude(string change); }; \ No newline at end of file diff --git a/src/core/include/airunit.h b/src/core/include/airunit.h index cabd7b9e..04ebb1ee 100644 --- a/src/core/include/airunit.h +++ b/src/core/include/airunit.h @@ -5,18 +5,17 @@ #include "luatools.h" #include "Unit.h" -#define AIR_DEST_DIST_THR 2000 +#define AIR_DEST_DIST_THR 2000 // Meters class AirUnit : public Unit { public: - AirUnit(json::value json, int ID); + AirUnit(json::value json, unsigned int ID); - virtual void setState(int newState); + virtual void setState(unsigned char newState); - virtual wstring getCategory() = 0; - virtual void changeSpeed(wstring change) = 0; - virtual void changeAltitude(wstring change) = 0; + virtual void changeSpeed(string change) = 0; + virtual void changeAltitude(string change) = 0; protected: virtual void AIloop(); diff --git a/src/core/include/commands.h b/src/core/include/commands.h index d4e0bab3..5c1769e0 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -5,7 +5,7 @@ #include "logger.h" namespace CommandPriority { - enum CommandPriorities { LOW, MEDIUM, HIGH }; + enum CommandPriorities { LOW, MEDIUM, HIGH, IMMEDIATE }; }; namespace SetCommandType { @@ -54,6 +54,15 @@ namespace ReactionToThreat { }; } +namespace EmissionCountermeasure { + enum ReactionsToThreat { + SILENT = 0, + ATTACK = 1, + DEFEND = 2, + FREE = 3 + }; +} + namespace RadarUse { enum RadarUses { NEVER = 0, @@ -85,19 +94,19 @@ namespace ECMUse { class Command { public: - int getPriority() { return priority; } - virtual wstring getString(lua_State* L) = 0; - virtual int getLoad() = 0; + unsigned int getPriority() { return priority; } + virtual string getString(lua_State* L) = 0; + virtual unsigned int getLoad() = 0; protected: - int priority = CommandPriority::LOW; + unsigned int priority = CommandPriority::LOW; }; /* Simple low priority move command (from user click) */ class Move : public Command { public: - Move(wstring groupName, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions, wstring category): + Move(string groupName, Coords destination, double speed, string speedType, double altitude, string altitudeType, string taskOptions, string category): groupName(groupName), destination(destination), speed(speed), @@ -109,35 +118,35 @@ public: { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 5; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 5; } private: - const wstring groupName; + const string groupName; const Coords destination; const double speed; - const wstring speedType; + const string speedType; const double altitude; - const wstring altitudeType; - const wstring taskOptions; - const wstring category; + const string altitudeType; + const string taskOptions; + const string category; }; /* Smoke command */ class Smoke : public Command { public: - Smoke(wstring color, Coords location) : + Smoke(string color, Coords location) : color(color), location(location) { priority = CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 5; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 5; } private: - const wstring color; + const string color; const Coords location; }; @@ -145,61 +154,65 @@ private: class SpawnGroundUnit : public Command { public: - SpawnGroundUnit(wstring coalition, wstring unitType, Coords location) : + SpawnGroundUnit(string coalition, string unitType, Coords location, bool immediate) : coalition(coalition), unitType(unitType), - location(location) + location(location), + immediate(immediate) { - priority = CommandPriority::LOW; + priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 100 * !immediate; } private: - const wstring coalition; - const wstring unitType; + const string coalition; + const string unitType; const Coords location; + const bool immediate; }; /* Spawn air unit command */ class SpawnAircraft : public Command { public: - SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) : + SpawnAircraft(string coalition, string unitType, Coords location, string payloadName, string airbaseName, bool immediate) : coalition(coalition), unitType(unitType), location(location), payloadName(payloadName), - airbaseName(airbaseName) + airbaseName(airbaseName), + immediate(immediate) { - priority = CommandPriority::LOW; + priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 100 * !immediate; } private: - const wstring coalition; - const wstring unitType; + const string coalition; + const string unitType; const Coords location; - const wstring payloadName; - const wstring airbaseName; + const string payloadName; + const string airbaseName; + const bool immediate; }; /* Clone unit command */ class Clone : public Command { public: - Clone(int ID, Coords location) : + Clone(unsigned int ID, Coords location) : ID(ID), location(location) { priority = CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 100; } private: - const int ID; + const unsigned int ID; const Coords location; }; @@ -207,77 +220,77 @@ private: class Delete : public Command { public: - Delete(int ID, bool explosion) : + Delete(unsigned int ID, bool explosion) : ID(ID), explosion(explosion) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 20; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 20; } private: - const int ID; + const unsigned int ID; const bool explosion; }; -/* Follow command */ +/* SetTask command */ class SetTask : public Command { public: - SetTask(wstring groupName, wstring task) : + SetTask(string groupName, string task) : groupName(groupName), task(task) { priority = CommandPriority::MEDIUM; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; - const wstring task; + const string groupName; + const string task; }; /* Reset task command */ class ResetTask : public Command { public: - ResetTask(wstring groupName) : + ResetTask(string groupName) : groupName(groupName) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; + const string groupName; }; /* Set command */ class SetCommand : public Command { public: - SetCommand(wstring groupName, wstring command) : + SetCommand(string groupName, string command) : groupName(groupName), command(command) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; - const wstring command; + const string groupName; + const string command; }; /* Set option command */ class SetOption : public Command { public: - SetOption(wstring groupName, int optionID, int optionValue) : + SetOption(string groupName, unsigned int optionID, unsigned int optionValue) : groupName(groupName), optionID(optionID), optionValue(optionValue), @@ -287,7 +300,7 @@ public: priority = CommandPriority::HIGH; }; - SetOption(wstring groupName, int optionID, bool optionBool) : + SetOption(string groupName, unsigned int optionID, bool optionBool) : groupName(groupName), optionID(optionID), optionValue(0), @@ -296,13 +309,13 @@ public: { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; - const int optionID; - const int optionValue; + const string groupName; + const unsigned int optionID; + const unsigned int optionValue; const bool optionBool; const bool isBoolean; }; @@ -311,17 +324,17 @@ private: class SetOnOff : public Command { public: - SetOnOff(wstring groupName, bool onOff) : + SetOnOff(string groupName, bool onOff) : groupName(groupName), onOff(onOff) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; + const string groupName; const bool onOff; }; @@ -329,16 +342,16 @@ private: class Explosion : public Command { public: - Explosion(int intensity, Coords location) : + Explosion(unsigned int intensity, Coords location) : location(location), intensity(intensity) { priority = CommandPriority::MEDIUM; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 10; } private: const Coords location; - const int intensity; + const unsigned int intensity; }; diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h new file mode 100644 index 00000000..14eb91b8 --- /dev/null +++ b/src/core/include/datatypes.h @@ -0,0 +1,49 @@ +#pragma once +#include "framework.h" + +#pragma pack(push, 1) +namespace DataTypes { + struct TACAN + { + bool isOn = false; + unsigned char channel = 40; + char XY = 'X'; + char callsign[4]; + }; + + struct Radio + { + unsigned int frequency = 124000000; // MHz + unsigned char callsign = 1; + unsigned char callsignNumber = 1; + }; + + struct GeneralSettings + { + bool prohibitJettison = false; + bool prohibitAA = false; + bool prohibitAG = false; + bool prohibitAfterburner = false; + bool prohibitAirWpn = false; + }; + + struct Ammo { + unsigned short quantity = 0; + char name[33]; + unsigned char guidance = 0; + unsigned char category = 0; + unsigned char missileCategory = 0; + }; + + struct Contact { + unsigned int ID = 0; + unsigned char detectionMethod = 0; + }; +} +#pragma pack(pop) + +bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs); +bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs); +bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs); +bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs); +bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs); diff --git a/src/core/include/groundunit.h b/src/core/include/groundunit.h index 6b5930b0..13b236e2 100644 --- a/src/core/include/groundunit.h +++ b/src/core/include/groundunit.h @@ -6,12 +6,11 @@ class GroundUnit : public Unit { public: - GroundUnit(json::value json, int ID); - virtual wstring getCategory() { return L"GroundUnit"; }; + GroundUnit(json::value json, unsigned int ID); - virtual void setState(int newState); + virtual void setState(unsigned char newState); - virtual void changeSpeed(wstring change); + virtual void changeSpeed(string change); virtual void setOnOff(bool newOnOff); virtual void setFollowRoads(bool newFollowRoads); diff --git a/src/core/include/helicopter.h b/src/core/include/helicopter.h index 097990b3..3c72693b 100644 --- a/src/core/include/helicopter.h +++ b/src/core/include/helicopter.h @@ -4,10 +4,8 @@ class Helicopter : public AirUnit { public: - Helicopter(json::value json, int ID); + Helicopter(json::value json, unsigned int ID); - virtual wstring getCategory() { return L"Helicopter"; }; - - virtual void changeSpeed(wstring change); - virtual void changeAltitude(wstring change); + virtual void changeSpeed(string change); + virtual void changeAltitude(string change); }; \ No newline at end of file diff --git a/src/core/include/navyunit.h b/src/core/include/navyunit.h index a27b5a31..c82ee83d 100644 --- a/src/core/include/navyunit.h +++ b/src/core/include/navyunit.h @@ -4,10 +4,9 @@ class NavyUnit : public Unit { public: - NavyUnit(json::value json, int ID); + NavyUnit(json::value json, unsigned int ID); virtual void AIloop(); - virtual wstring getCategory() { return L"NavyUnit"; }; - virtual void changeSpeed(wstring change); + virtual void changeSpeed(string change); }; \ No newline at end of file diff --git a/src/core/include/scheduler.h b/src/core/include/scheduler.h index d5ebfcf6..0b931e1c 100644 --- a/src/core/include/scheduler.h +++ b/src/core/include/scheduler.h @@ -11,10 +11,10 @@ public: void appendCommand(Command* command); void execute(lua_State* L); - void handleRequest(wstring key, json::value value); + void handleRequest(string key, json::value value); private: list commands; - int load; + unsigned int load; }; diff --git a/src/core/include/server.h b/src/core/include/server.h index ec08d264..6880f7b4 100644 --- a/src/core/include/server.h +++ b/src/core/include/server.h @@ -28,6 +28,6 @@ private: atomic runListener; - wstring password = L""; + string password = ""; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 817beb29..b3c12707 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -5,9 +5,59 @@ #include "luatools.h" #include "measure.h" #include "logger.h" +#include "commands.h" +#include "datatypes.h" + +#include +using namespace std::chrono; #define TASK_CHECK_INIT_VALUE 10 +namespace DataIndex { + enum DataIndexes { + startOfData = 0, + category, + alive, + human, + controlled, + coalition, + country, + name, + unitName, + groupName, + state, + task, + hasTask, + position, + speed, + heading, + isTanker, + isAWACS, + onOff, + followRoads, + fuel, + desiredSpeed, + desiredSpeedType, + desiredAltitude, + desiredAltitudeType, + leaderID, + formationOffset, + targetID, + targetPosition, + ROE, + reactionToThreat, + emissionsCountermeasures, + TACAN, + radio, + generalSettings, + ammo, + contacts, + activePath, + lastIndex, + endOfData = 255 + }; +} + namespace State { enum States @@ -28,235 +78,224 @@ namespace State }; }; -namespace Options { - struct TACAN - { - bool isOn = false; - int channel = 40; - wstring XY = L"X"; - wstring callsign = L"TKR"; - }; - - struct Radio - { - int frequency = 124000000; // MHz - int callsign = 1; - int callsignNumber = 1; - }; - - struct GeneralSettings - { - bool prohibitJettison = false; - bool prohibitAA = false; - bool prohibitAG = false; - bool prohibitAfterburner = false; - bool prohibitAirWpn = false; - }; -} - class Unit { public: - Unit(json::value json, int ID); + Unit(json::value json, unsigned int ID); ~Unit(); - /********** Public methods **********/ + /********** Methods **********/ void initialize(json::value json); void setDefaults(bool force = false); - int getID() { return ID; } + void runAILoop(); - void updateExportData(json::value json); + + void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); - json::value getData(long long time, bool getAll = false); - virtual wstring getCategory() { return L"No category"; }; - /********** Base data **********/ - void setControlled(bool newControlled) { controlled = newControlled; addMeasure(L"controlled", json::value(newControlled)); } - void setName(wstring newName) { name = newName; addMeasure(L"name", json::value(newName));} - void setUnitName(wstring newUnitName) { unitName = newUnitName; addMeasure(L"unitName", json::value(newUnitName));} - void setGroupName(wstring newGroupName) { groupName = newGroupName; addMeasure(L"groupName", json::value(newGroupName));} - void setAlive(bool newAlive) { alive = newAlive; addMeasure(L"alive", json::value(newAlive));} - void setType(json::value newType) { type = newType; addMeasure(L"type", newType);} - void setCountry(int newCountry) { country = newCountry; addMeasure(L"country", json::value(newCountry));} - - bool getControlled() { return controlled; } - wstring getName() { return name; } - wstring getUnitName() { return unitName; } - wstring getGroupName() { return groupName; } - bool getAlive() { return alive; } - json::value getType() { return type; } - int getCountry() { return country; } - - /********** Flight data **********/ - void setLatitude(double newLatitude) {latitude = newLatitude; addMeasure(L"latitude", json::value(newLatitude));} - void setLongitude(double newLongitude) {longitude = newLongitude; addMeasure(L"longitude", json::value(newLongitude));} - void setAltitude(double newAltitude) {altitude = newAltitude; addMeasure(L"altitude", json::value(newAltitude));} - void setHeading(double newHeading) {heading = newHeading; addMeasure(L"heading", json::value(newHeading));} - void setSpeed(double newSpeed) {speed = newSpeed; addMeasure(L"speed", json::value(newSpeed));} - - double getLatitude() { return latitude; } - double getLongitude() { return longitude; } - double getAltitude() { return altitude; } - double getHeading() { return heading; } - double getSpeed() { return speed; } - - /********** Mission data **********/ - void setFuel(double newFuel) { fuel = newFuel; addMeasure(L"fuel", json::value(newFuel));} - void setAmmo(json::value newAmmo) { ammo = newAmmo; addMeasure(L"ammo", json::value(newAmmo));} - void setContacts(json::value newContacts) {contacts = newContacts; addMeasure(L"contacts", json::value(newContacts));} - void setHasTask(bool newHasTask); - void setCoalitionID(int newCoalitionID); - void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));} - - double getFuel() { return fuel; } - json::value getAmmo() { return ammo; } - json::value getTargets() { return contacts; } - bool getHasTask() { return hasTask; } - wstring getCoalition() { return coalition; } - int getCoalitionID(); - json::value getFlags() { return flags; } - - /********** Formation data **********/ - void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); } - void setFormationOffset(Offset formationOffset); - - int getLeaderID() { return leaderID; } - Offset getFormationoffset() { return formationOffset; } - - /********** Task data **********/ - void setCurrentTask(wstring newCurrentTask) { currentTask = newCurrentTask; addMeasure(L"currentTask", json::value(newCurrentTask)); } - void setDesiredSpeed(double newDesiredSpeed); - void setDesiredAltitude(double newDesiredAltitude); - void setDesiredSpeedType(wstring newDesiredSpeedType); - void setDesiredAltitudeType(wstring newDesiredAltitudeType); - void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; addMeasure(L"activeDestination", json::value("")); } // TODO fix - void setActivePath(list newActivePath); - void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} - void setTargetLocation(Coords newTargetLocation); - void setIsTanker(bool newIsTanker); - void setIsAWACS(bool newIsAWACS); - virtual void setOnOff(bool newOnOff) { onOff = newOnOff; addMeasure(L"onOff", json::value(newOnOff));}; - virtual void setFollowRoads(bool newFollowRoads) { followRoads = newFollowRoads; addMeasure(L"followRoads", json::value(newFollowRoads)); }; - - wstring getCurrentTask() { return currentTask; } - virtual double getDesiredSpeed() { return desiredSpeed; }; - virtual double getDesiredAltitude() { return desiredAltitude; }; - virtual wstring getDesiredSpeedType() { return desiredSpeedType; }; - virtual wstring getDesiredAltitudeType() { return desiredAltitudeType; }; + unsigned int getDataPacket(char*& data); + unsigned int getID() { return ID; } + void getData(stringstream& ss, unsigned long long time); Coords getActiveDestination() { return activeDestination; } - list getActivePath() { return activePath; } - int getTargetID() { return targetID; } - Coords getTargetLocation() { return targetLocation; } - bool getIsTanker() { return isTanker; } - bool getIsAWACS() { return isAWACS; } - bool getOnOff() { return onOff; }; - bool getFollowRoads() { return followRoads; }; - /********** Options data **********/ - void setROE(wstring newROE, bool force = false); - void setReactionToThreat(wstring newReactionToThreat, bool force = false); - void setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force = false); - void setTACAN(Options::TACAN newTACAN, bool force = false); - void setRadio(Options::Radio newradio, bool force = false); - void setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force = false); - void setEPLRS(bool newEPLRS, bool force = false); - - wstring getROE() { return ROE; } - wstring getReactionToThreat() { return reactionToThreat; } - wstring getEmissionsCountermeasures() { return emissionsCountermeasures; }; - Options::TACAN getTACAN() { return TACAN; } - Options::Radio getRadio() { return radio; } - Options::GeneralSettings getGeneralSettings() { return generalSettings; } - bool getEPLRS() { return EPLRS; } - - /********** Control functions **********/ - void landAt(Coords loc); - virtual void changeSpeed(wstring change) {}; - virtual void changeAltitude(wstring change) {}; + virtual void changeSpeed(string change) {}; + virtual void changeAltitude(string change) {}; + bool setActiveDestination(); void resetActiveDestination(); - virtual void setState(int newState) { state = newState; }; - void resetTask(); + void landAt(Coords loc); + + bool updateActivePath(bool looping); void clearActivePath(); void pushActivePathFront(Coords newActivePathFront); void pushActivePathBack(Coords newActivePathBack); void popActivePathFront(); + void goToDestination(string enrouteTask = "nil"); + bool isDestinationReached(double threshold); + + string getTargetName(); + string getLeaderName(); + bool isTargetAlive(); + bool isLeaderAlive(); + + void resetTask(); + bool checkTaskFailed(); + void resetTaskFailedCounter(); + + void triggerUpdate(unsigned char datumIndex); + + bool hasFreshData(unsigned long long time); + bool checkFreshness(unsigned char datumIndex, unsigned long long time); + + /********** Setters **********/ + virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); } + virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); } + virtual void setHuman(bool newValue) { updateValue(human, newValue, DataIndex::human); } + virtual void setControlled(bool newValue) { updateValue(controlled, newValue, DataIndex::controlled); } + virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); } + virtual void setCountry(unsigned char newValue) { updateValue(country, newValue, DataIndex::country); } + virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); } + virtual void setUnitName(string newValue) { updateValue(unitName, newValue, DataIndex::unitName); } + virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); } + virtual void setState(unsigned char newValue) { updateValue(state, newValue, DataIndex::state); }; + virtual void setTask(string newValue) { updateValue(task, newValue, DataIndex::task); } + virtual void setHasTask(bool newValue) { updateValue(hasTask, newValue, DataIndex::hasTask); } + virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); } + virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); } + virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); } + virtual void setIsTanker(bool newValue); + virtual void setIsAWACS(bool newValue); + virtual void setOnOff(bool newValue) { updateValue(onOff, newValue, DataIndex::onOff); }; + virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue, DataIndex::followRoads); }; + virtual void setFuel(unsigned short newValue) { updateValue(fuel, newValue, DataIndex::fuel); } + virtual void setDesiredSpeed(double newValue); + virtual void setDesiredSpeedType(string newValue); + virtual void setDesiredAltitude(double newValue); + virtual void setDesiredAltitudeType(string newValue); + virtual void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue, DataIndex::leaderID); } + virtual void setFormationOffset(Offset formationOffset); + virtual void setTargetID(unsigned int newValue) { updateValue(targetID, newValue, DataIndex::targetID); } + virtual void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue, DataIndex::targetPosition); } + virtual void setROE(unsigned char newValue, bool force = false); + virtual void setReactionToThreat(unsigned char newValue, bool force = false); + virtual void setEmissionsCountermeasures(unsigned char newValue, bool force = false); + virtual void setTACAN(DataTypes::TACAN newValue, bool force = false); + virtual void setRadio(DataTypes::Radio newValue, bool force = false); + virtual void setGeneralSettings(DataTypes::GeneralSettings newValue, bool force = false); + virtual void setAmmo(vector newValue); + virtual void setContacts(vector newValue); + virtual void setActivePath(list newValue); + + /********** Getters **********/ + virtual string getCategory() { return category; }; + virtual bool getAlive() { return alive; } + virtual bool getHuman() { return human; } + virtual bool getControlled() { return controlled; } + virtual unsigned char getCoalition() { return coalition; } + virtual unsigned char getCountry() { return country; } + virtual string getName() { return name; } + virtual string getUnitName() { return unitName; } + virtual string getGroupName() { return groupName; } + virtual unsigned char getState() { return state; } + virtual string getTask() { return task; } + virtual bool getHasTask() { return hasTask; } + virtual Coords getPosition() { return position; } + virtual double getSpeed() { return speed; } + virtual double getHeading() { return heading; } + virtual bool getIsTanker() { return isTanker; } + virtual bool getIsAWACS() { return isAWACS; } + virtual bool getOnOff() { return onOff; }; + virtual bool getFollowRoads() { return followRoads; }; + virtual unsigned short getFuel() { return fuel; } + virtual double getDesiredSpeed() { return desiredSpeed; }; + virtual bool getDesiredSpeedType() { return desiredSpeedType; }; + virtual double getDesiredAltitude() { return desiredAltitude; }; + virtual bool getDesiredAltitudeType() { return desiredAltitudeType; }; + virtual unsigned int getLeaderID() { return leaderID; } + virtual Offset getFormationoffset() { return formationOffset; } + virtual unsigned int getTargetID() { return targetID; } + virtual Coords getTargetPosition() { return targetPosition; } + virtual unsigned char getROE() { return ROE; } + virtual unsigned char getReactionToThreat() { return reactionToThreat; } + virtual unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; }; + virtual DataTypes::TACAN getTACAN() { return TACAN; } + virtual DataTypes::Radio getRadio() { return radio; } + virtual DataTypes::GeneralSettings getGeneralSettings() { return generalSettings; } + virtual vector getAmmo() { return ammo; } + virtual vector getTargets() { return contacts; } + virtual list getActivePath() { return activePath; } protected: - int ID; + unsigned int ID; - map measures; - int taskCheckCounter = 0; - - /********** Base data **********/ + string category; + bool alive = false; + bool human = false; bool controlled = false; - wstring name = L"undefined"; - wstring unitName = L"undefined"; - wstring groupName = L"undefined"; - bool alive = true; - json::value type = json::value::null(); - int country = NULL; - - /********** Flight data **********/ - double latitude = NULL; - double longitude = NULL; - double altitude = NULL; + unsigned char coalition = NULL; + unsigned char country = NULL; + string name = ""; + string unitName = ""; + string groupName = ""; + unsigned char state = State::NONE; + string task = ""; + bool hasTask = false; + Coords position = Coords(NULL); double speed = NULL; double heading = NULL; - - /********** Mission data **********/ - double fuel = 0; - double initialFuel = 0; // Used internally to detect refueling completed - json::value ammo = json::value::null(); - json::value contacts = json::value::null(); - bool hasTask = false; - wstring coalition = L""; - json::value flags = json::value::null(); - - /********** Formation data **********/ - int leaderID = NULL; - Offset formationOffset = Offset(NULL); - - /********** Task data **********/ - wstring currentTask = L""; - double desiredSpeed = 0; - double desiredAltitude = 0; - wstring desiredSpeedType = L"GS"; - wstring desiredAltitudeType = L"AGL"; - list activePath; - Coords activeDestination = Coords(NULL); - int targetID = NULL; - Coords targetLocation = Coords(NULL); bool isTanker = false; bool isAWACS = false; bool onOff = true; bool followRoads = false; - - /********** Options data **********/ - wstring ROE = L"Designated"; - wstring reactionToThreat = L"Evade"; - wstring emissionsCountermeasures = L"Defend"; - Options::TACAN TACAN; - Options::Radio radio; - Options::GeneralSettings generalSettings; - bool EPLRS = false; - - /********** State machine **********/ - int state = State::NONE; + unsigned short fuel = 0; + double desiredSpeed = 0; + bool desiredSpeedType = 1; + double desiredAltitude = 0; + bool desiredAltitudeType = 1; + unsigned int leaderID = NULL; + Offset formationOffset = Offset(NULL); + unsigned int targetID = NULL; + Coords targetPosition = Coords(NULL); + unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE; + unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE; + unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND; + DataTypes::TACAN TACAN; + DataTypes::Radio radio; + DataTypes::GeneralSettings generalSettings; + vector ammo; + vector contacts; + list activePath; /********** Other **********/ - Coords oldPosition = Coords(0); // Used to approximate speed + unsigned int taskCheckCounter = 0; + Coords activeDestination = Coords(NULL); + double initialFuel = 0; + Coords oldPosition = Coords(0); + map updateTimeMap; - /********** Functions **********/ - wstring getTargetName(); - wstring getLeaderName(); - bool isTargetAlive(); - bool isLeaderAlive(); + /********** Private methods **********/ virtual void AIloop() = 0; - void addMeasure(wstring key, json::value value); - bool isDestinationReached(double threshold); - bool setActiveDestination(); - bool updateActivePath(bool looping); - void goToDestination(wstring enrouteTask = L"nil"); - bool checkTaskFailed(); - void resetTaskFailedCounter(); + + void appendString(stringstream& ss, const unsigned char& datumIndex, const string& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + ss << datumValue; + } + + /********** Template methods **********/ + template + void updateValue(T& value, T& newValue, unsigned char datumIndex) + { + if (newValue != value) + { + triggerUpdate(datumIndex); + value = newValue; + } + } + + template + void appendNumeric(stringstream& ss, const unsigned char& datumIndex, T& datumValue) { + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&datumValue, sizeof(T)); + } + + template + void appendVector(stringstream& ss, const unsigned char& datumIndex, vector& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + + for (auto& el : datumValue) + ss.write((const char*)&el, sizeof(T)); + } + + template + void appendList(stringstream& ss, const unsigned char& datumIndex, list& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + + for (auto& el: datumValue) + ss.write((const char*)&el, sizeof(T)); + } }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 871c9e74..71d5f247 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -10,21 +10,22 @@ public: UnitsManager(lua_State* L); ~UnitsManager(); - Unit* getUnit(int ID); + map& getUnits() { return units; }; + Unit* getUnit(unsigned int ID); bool isUnitInGroup(Unit* unit); bool isUnitGroupLeader(Unit* unit); - Unit* getGroupLeader(int ID); + Unit* getGroupLeader(unsigned int ID); Unit* getGroupLeader(Unit* unit); - vector getGroupMembers(wstring groupName); - void updateExportData(lua_State* L); + vector getGroupMembers(string groupName); + void updateExportData(lua_State* L, double dt = 0); void updateMissionData(json::value missionData); void runAILoop(); - void getData(json::value& answer, long long time); - void deleteUnit(int ID, bool explosion); - void acquireControl(int ID); + string getUnitData(stringstream &ss, unsigned long long time); + void deleteUnit(unsigned int ID, bool explosion); + void acquireControl(unsigned int ID); private: - map units; + map units; json::value missionDB; }; diff --git a/src/core/include/weapon.h b/src/core/include/weapon.h index a1097bcb..96744423 100644 --- a/src/core/include/weapon.h +++ b/src/core/include/weapon.h @@ -4,10 +4,7 @@ class Weapon : public Unit { public: - Weapon(json::value json, int ID); - - virtual wstring getCategory() = 0; - + Weapon(json::value json, unsigned int ID); protected: /* Weapons are not controllable and have no AIloop */ virtual void AIloop() {}; @@ -16,15 +13,11 @@ protected: class Missile : public Weapon { public: - Missile(json::value json, int ID); - - virtual wstring getCategory() { return L"Missile"; }; + Missile(json::value json, unsigned int ID); }; class Bomb : public Weapon { public: - Bomb(json::value json, int ID); - - virtual wstring getCategory() { return L"Bomb"; }; + Bomb(json::value json, unsigned int ID); }; \ No newline at end of file diff --git a/src/core/src/aircraft.cpp b/src/core/src/aircraft.cpp index 1284f903..e8a932dd 100644 --- a/src/core/src/aircraft.cpp +++ b/src/core/src/aircraft.cpp @@ -13,24 +13,23 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Aircraft */ -Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID) +Aircraft::Aircraft(json::value json, unsigned int ID) : AirUnit(json, ID) { log("New Aircraft created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); - double desiredSpeed = knotsToMs(300); - double desiredAltitude = ftToM(20000); - setDesiredSpeed(desiredSpeed); - setDesiredAltitude(desiredAltitude); + setCategory("Aircraft"); + setDesiredSpeed(knotsToMs(300)); + setDesiredAltitude(ftToM(20000)); + }; -void Aircraft::changeSpeed(wstring change) +void Aircraft::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) setState(State::IDLE); - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) setDesiredSpeed(getDesiredSpeed() - knotsToMs(25)); - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) setDesiredSpeed(getDesiredSpeed() + knotsToMs(25)); if (getDesiredSpeed() < knotsToMs(50)) @@ -42,16 +41,16 @@ void Aircraft::changeSpeed(wstring change) goToDestination(); /* Send the command to reach the destination */ } -void Aircraft::changeAltitude(wstring change) +void Aircraft::changeAltitude(string change) { - if (change.compare(L"descend") == 0) + if (change.compare("descend") == 0) { if (getDesiredAltitude() > 5000) setDesiredAltitude(getDesiredAltitude() - ftToM(2500)); else if (getDesiredAltitude() > 0) setDesiredAltitude(getDesiredAltitude() - ftToM(500)); } - else if (change.compare(L"climb") == 0) + else if (change.compare("climb") == 0) { if (getDesiredAltitude() > 5000) setDesiredAltitude(getDesiredAltitude() + ftToM(2500)); diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 06c0808a..9f32981d 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -13,12 +13,12 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Air unit */ -AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID) +AirUnit::AirUnit(json::value json, unsigned int ID) : Unit(json, ID) { }; -void AirUnit::setState(int newState) +void AirUnit::setState(unsigned char newState) { /************ Perform any action required when LEAVING a state ************/ if (newState != state) { @@ -46,7 +46,7 @@ void AirUnit::setState(int newState) case State::BOMB_POINT: case State::CARPET_BOMB: case State::BOMB_BUILDING: { - setTargetLocation(Coords(NULL)); + setTargetPosition(Coords(NULL)); break; } default: @@ -59,57 +59,48 @@ void AirUnit::setState(int newState) case State::IDLE: { clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Idle")); break; } case State::REACH_DESTINATION: { resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Reach destination")); break; } case State::ATTACK: { if (isTargetAlive()) { Unit* target = unitsManager->getUnit(targetID); - Coords targetPosition = Coords(target->getLatitude(), target->getLongitude(), 0); + Coords targetPosition = Coords(target->getPosition().lat, target->getPosition().lng, 0); clearActivePath(); pushActivePathFront(targetPosition); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Attack")); } break; } case State::FOLLOW: { clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Follow")); break; } case State::LAND: { resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Land")); break; } case State::REFUEL: { initialFuel = fuel; clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Refuel")); break; } case State::BOMB_POINT: { - addMeasure(L"currentState", json::value(L"Bombing point")); clearActivePath(); resetActiveDestination(); break; } case State::CARPET_BOMB: { - addMeasure(L"currentState", json::value(L"Carpet bombing")); clearActivePath(); resetActiveDestination(); break; } case State::BOMB_BUILDING: { - addMeasure(L"currentState", json::value(L"Bombing building")); clearActivePath(); resetActiveDestination(); break; @@ -120,8 +111,10 @@ void AirUnit::setState(int newState) resetTask(); - log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState)); + log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState)); state = newState; + + triggerUpdate(DataIndex::state); } void AirUnit::AIloop() @@ -129,11 +122,11 @@ void AirUnit::AIloop() /* State machine */ switch (state) { case State::IDLE: { - currentTask = L"Idle"; + setTask("Idle"); if (!getHasTask()) { - std::wostringstream taskSS; + std::ostringstream taskSS; if (isTanker) { taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " << desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" << desiredAltitudeType << "' } }"; } @@ -150,23 +143,23 @@ void AirUnit::AIloop() break; } case State::REACH_DESTINATION: { - wstring enrouteTask = L""; + string enrouteTask = ""; bool looping = false; if (isTanker) { - enrouteTask = L"{ id = 'Tanker' }"; - currentTask = L"Tanker"; + enrouteTask = "{ id = 'Tanker' }"; + setTask("Tanker"); } else if (isAWACS) { - enrouteTask = L"{ id = 'AWACS' }"; - currentTask = L"AWACS"; + enrouteTask = "{ id = 'AWACS' }"; + setTask("AWACS"); } else { - enrouteTask = L"nil"; - currentTask = L"Reaching destination"; + enrouteTask = "nil"; + setTask("Reaching destination"); } if (activeDestination == NULL || !getHasTask()) @@ -187,8 +180,8 @@ void AirUnit::AIloop() break; } case State::LAND: { - wstring enrouteTask = L"{ id = 'Land' }"; - currentTask = L"Landing"; + string enrouteTask = "{ id = 'Land' }"; + setTask("Landing"); if (activeDestination == NULL) { @@ -206,13 +199,13 @@ void AirUnit::AIloop() /* Attack state is an "enroute" task, meaning the unit will keep trying to attack even if a new destination is set. This is useful to manoeuvre the unit so that it can detect and engage the target. */ - std::wostringstream enrouteTaskSS; + std::ostringstream enrouteTaskSS; enrouteTaskSS << "{" << "id = 'EngageUnit'" << "," << "targetID = " << targetID << "," << "}"; - wstring enrouteTask = enrouteTaskSS.str(); - currentTask = L"Attacking " + getTargetName(); + string enrouteTask = enrouteTaskSS.str(); + setTask("Attacking " + getTargetName()); if (!getHasTask()) { @@ -232,13 +225,13 @@ void AirUnit::AIloop() break; } - currentTask = L"Following " + getTargetName(); + setTask("Following " + getTargetName()); Unit* leader = unitsManager->getUnit(leaderID); if (!getHasTask()) { if (leader != nullptr && leader->getAlive() && formationOffset != NULL) { - std::wostringstream taskSS; + std::ostringstream taskSS; taskSS << "{" << "id = 'FollowUnit'" << ", " << "leaderID = " << leader->getID() << "," @@ -256,11 +249,11 @@ void AirUnit::AIloop() break; } case State::REFUEL: { - currentTask = L"Refueling"; + setTask("Refueling"); if (!getHasTask()) { if (fuel <= initialFuel) { - std::wostringstream taskSS; + std::ostringstream taskSS; taskSS << "{" << "id = 'Refuel'" << "}"; @@ -274,22 +267,22 @@ void AirUnit::AIloop() } } case State::BOMB_POINT: { - currentTask = L"Bombing point"; + setTask("Bombing point"); if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'Bombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + std::ostringstream taskSS; + taskSS << "{id = 'Bombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } } case State::CARPET_BOMB: { - currentTask = L"Carpet bombing"; + setTask("Carpet bombing"); if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'CarpetBombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + std::ostringstream taskSS; + taskSS << "{id = 'CarpetBombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); @@ -297,11 +290,11 @@ void AirUnit::AIloop() break; } case State::BOMB_BUILDING: { - currentTask = L"Bombing building"; + setTask("Bombing building"); if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'AttackMapObject', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + std::ostringstream taskSS; + taskSS << "{id = 'AttackMapObject', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); @@ -311,6 +304,4 @@ void AirUnit::AIloop() default: break; } - - addMeasure(L"currentTask", json::value(currentTask)); } \ No newline at end of file diff --git a/src/core/src/commands.cpp b/src/core/src/commands.cpp index 87f33218..fbd6da60 100644 --- a/src/core/src/commands.cpp +++ b/src/core/src/commands.cpp @@ -7,10 +7,10 @@ extern UnitsManager* unitsManager; /* Move command */ -wstring Move::getString(lua_State* L) +string Move::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.move, " << "\"" << groupName << "\"" << ", " @@ -26,9 +26,9 @@ wstring Move::getString(lua_State* L) } /* Smoke command */ -wstring Smoke::getString(lua_State* L) +string Smoke::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.smoke, " << "\"" << color << "\"" << ", " @@ -38,9 +38,9 @@ wstring Smoke::getString(lua_State* L) } /* Spawn ground command */ -wstring SpawnGroundUnit::getString(lua_State* L) +string SpawnGroundUnit::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.spawnGroundUnit, " << "\"" << coalition << "\"" << ", " @@ -51,16 +51,16 @@ wstring SpawnGroundUnit::getString(lua_State* L) } /* Spawn air command */ -wstring SpawnAircraft::getString(lua_State* L) +string SpawnAircraft::getString(lua_State* L) { - std::wostringstream optionsSS; + std::ostringstream optionsSS; optionsSS.precision(10); optionsSS << "{" << "payloadName = \"" << payloadName << "\", " << "airbaseName = \"" << airbaseName << "\", " << "}"; - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.spawnAircraft, " << "\"" << coalition << "\"" << ", " @@ -73,12 +73,12 @@ wstring SpawnAircraft::getString(lua_State* L) } /* Clone unit command */ -wstring Clone::getString(lua_State* L) +string Clone::getString(lua_State* L) { Unit* unit = unitsManager->getUnit(ID); if (unit != nullptr) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.clone, " << ID << ", " @@ -89,14 +89,14 @@ wstring Clone::getString(lua_State* L) } else { - return L""; + return ""; } } /* Delete unit command */ -wstring Delete::getString(lua_State* L) +string Delete::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.delete, " << ID << ", " @@ -105,9 +105,9 @@ wstring Delete::getString(lua_State* L) } /* Set task command */ -wstring SetTask::getString(lua_State* L) +string SetTask::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setTask, " << "\"" << groupName << "\"" << ", " @@ -117,9 +117,9 @@ wstring SetTask::getString(lua_State* L) } /* Reset task command */ -wstring ResetTask::getString(lua_State* L) +string ResetTask::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.resetTask, " << "\"" << groupName << "\""; @@ -128,9 +128,9 @@ wstring ResetTask::getString(lua_State* L) } /* Set command command */ -wstring SetCommand::getString(lua_State* L) +string SetCommand::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setCommand, " << "\"" << groupName << "\"" << ", " @@ -140,9 +140,9 @@ wstring SetCommand::getString(lua_State* L) } /* Set option command */ -wstring SetOption::getString(lua_State* L) +string SetOption::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); if (!isBoolean) { @@ -160,9 +160,9 @@ wstring SetOption::getString(lua_State* L) } /* Set onOff command */ -wstring SetOnOff::getString(lua_State* L) +string SetOnOff::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setOnOff, " @@ -173,9 +173,9 @@ wstring SetOnOff::getString(lua_State* L) } /* Explosion command */ -wstring Explosion::getString(lua_State* L) +string Explosion::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.explosion, " << intensity << ", " diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 38e490f2..b2e7263d 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -6,18 +6,29 @@ #include "scheduler.h" #include "scriptLoader.h" #include "luatools.h" +#include +using namespace std::chrono; auto before = std::chrono::system_clock::now(); + +/* Singleton objects */ UnitsManager* unitsManager = nullptr; Server* server = nullptr; Scheduler* scheduler = nullptr; + +/* Data jsons */ json::value airbases; json::value bullseyes; json::value mission; + mutex mutexLock; -bool initialized = false; string sessionHash; +bool initialized = false; + +unsigned int frameCounter = 0; +double frameRate = 30; + /* Called when DCS simulation stops. All singleton instances are deleted. */ extern "C" DllExport int coreDeinit(lua_State* L) { @@ -58,25 +69,31 @@ extern "C" DllExport int coreFrame(lua_State* L) if (!initialized) return (0); - /* Lock for thread safety */ - lock_guard guard(mutexLock); + frameCounter++; + /* Slow down the update rate if the frameRate is very low since it means DCS is struggling to keep up */ const std::chrono::duration duration = std::chrono::system_clock::now() - before; - - /* TODO make intervals editable */ - if (duration.count() > UPDATE_TIME_INTERVAL) + if (duration.count() > UPDATE_TIME_INTERVAL * (60.0 / frameRate)) { - if (unitsManager != nullptr) - { - unitsManager->updateExportData(L); + /* Lock for thread safety */ + lock_guard guard(mutexLock); + + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + if (duration.count() > 0) + frameRate = frameCounter / duration.count(); + frameCounter = 0; + + if (unitsManager != nullptr) { + unitsManager->updateExportData(L, duration.count()); unitsManager->runAILoop(); + } before = std::chrono::system_clock::now(); } if (scheduler != nullptr) scheduler->execute(L); - + return(0); } @@ -88,18 +105,27 @@ extern "C" DllExport int coreMissionData(lua_State * L) /* Lock for thread safety */ lock_guard guard(mutexLock); - lua_getglobal(L, "Olympus"); - lua_getfield(L, -1, "missionData"); - json::value missionData = luaTableToJSON(L, -1); + try + { + lua_getglobal(L, "Olympus"); + lua_getfield(L, -1, "missionData"); + json::value missionData = luaTableToJSON(L, -1); - if (missionData.has_object_field(L"unitsData")) - unitsManager->updateMissionData(missionData[L"unitsData"]); - if (missionData.has_object_field(L"airbases")) - airbases = missionData[L"airbases"]; - if (missionData.has_object_field(L"bullseyes")) - bullseyes = missionData[L"bullseyes"]; - if (missionData.has_object_field(L"mission")) - mission = missionData[L"mission"]; + if (missionData.has_object_field(L"unitsData")) + unitsManager->updateMissionData(missionData[L"unitsData"]); + if (missionData.has_object_field(L"airbases")) + airbases = missionData[L"airbases"]; + if (missionData.has_object_field(L"bullseyes")) + bullseyes = missionData[L"bullseyes"]; + if (missionData.has_object_field(L"mission")) + mission = missionData[L"mission"]; + } + catch (exception const& e) + { + log(e.what()); + } + + return(0); } diff --git a/src/core/src/datatypes.cpp b/src/core/src/datatypes.cpp new file mode 100644 index 00000000..eb0c52cd --- /dev/null +++ b/src/core/src/datatypes.cpp @@ -0,0 +1,30 @@ +#include "datatypes.h" + +bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs) +{ + return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && strcmp(lhs.callsign, rhs.callsign) == 0; +} + +bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs) +{ + return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber; +} + +bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs) +{ + return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG && + lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; +} + +bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs) +{ + return lhs.category == rhs.category && lhs.guidance == rhs.guidance && lhs.missileCategory == rhs.missileCategory && + lhs.quantity == rhs.quantity && strcmp(lhs.name, rhs.name) == 0; +} + +bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs) +{ + return lhs.detectionMethod == rhs.detectionMethod && lhs.ID == rhs.ID; +} + + diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 58b8fd50..e8294d83 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -13,16 +13,15 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Ground unit */ -GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID) +GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID) { log("New Ground Unit created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); - double desiredSpeed = 10; - setDesiredSpeed(desiredSpeed); + setCategory("GroundUnit"); + setDesiredSpeed(10); }; -void GroundUnit::setState(int newState) +void GroundUnit::setState(unsigned char newState) { /************ Perform any action required when LEAVING a state ************/ if (newState != state) { @@ -34,7 +33,7 @@ void GroundUnit::setState(int newState) break; } case State::FIRE_AT_AREA: { - setTargetLocation(Coords(NULL)); + setTargetPosition(Coords(NULL)); break; } default: @@ -47,16 +46,13 @@ void GroundUnit::setState(int newState) case State::IDLE: { clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Idle")); break; } case State::REACH_DESTINATION: { resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Reach destination")); break; } case State::FIRE_AT_AREA: { - addMeasure(L"currentState", json::value(L"Firing at area")); clearActivePath(); resetActiveDestination(); break; @@ -67,24 +63,26 @@ void GroundUnit::setState(int newState) resetTask(); - log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState)); + log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState)); state = newState; + + triggerUpdate(DataIndex::state); } void GroundUnit::AIloop() { switch (state) { case State::IDLE: { - currentTask = L"Idle"; + setTask("Idle"); if (getHasTask()) resetTask(); break; } case State::REACH_DESTINATION: { - wstring enrouteTask = L""; + string enrouteTask = ""; bool looping = false; - std::wostringstream taskSS; + std::ostringstream taskSS; taskSS << "{ id = 'FollowRoads', value = " << (getFollowRoads() ? "true" : "false") << " }"; enrouteTask = taskSS.str(); @@ -106,11 +104,11 @@ void GroundUnit::AIloop() break; } case State::FIRE_AT_AREA: { - currentTask = L"Firing at area"; + setTask("Firing at area"); if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'FireAtPoint', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << ", radius = 1000}"; + std::ostringstream taskSS; + taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 1000}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); @@ -119,17 +117,15 @@ void GroundUnit::AIloop() default: break; } - - addMeasure(L"currentTask", json::value(currentTask)); } -void GroundUnit::changeSpeed(wstring change) +void GroundUnit::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) setState(State::IDLE); - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) setDesiredSpeed(getDesiredSpeed() - knotsToMs(5)); - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) setDesiredSpeed(getDesiredSpeed() + knotsToMs(5)); if (getDesiredSpeed() < 0) diff --git a/src/core/src/helicopter.cpp b/src/core/src/helicopter.cpp index d728f4c5..6d24022e 100644 --- a/src/core/src/helicopter.cpp +++ b/src/core/src/helicopter.cpp @@ -13,27 +13,25 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Helicopter */ -Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID) +Helicopter::Helicopter(json::value json, unsigned int ID) : AirUnit(json, ID) { log("New Helicopter created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); - double desiredSpeed = knotsToMs(100); - double desiredAltitude = ftToM(5000); - setDesiredSpeed(desiredSpeed); - setDesiredAltitude(desiredAltitude); + setCategory("Helicopter"); + setDesiredSpeed(knotsToMs(100)); + setDesiredAltitude(ftToM(5000)); }; -void Helicopter::changeSpeed(wstring change) +void Helicopter::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) { /* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */ clearActivePath(); } - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) desiredSpeed -= knotsToMs(10); - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) desiredSpeed += knotsToMs(10); if (desiredSpeed < 0) desiredSpeed = 0; @@ -41,16 +39,16 @@ void Helicopter::changeSpeed(wstring change) goToDestination(); /* Send the command to reach the destination */ } -void Helicopter::changeAltitude(wstring change) +void Helicopter::changeAltitude(string change) { - if (change.compare(L"descend") == 0) + if (change.compare("descend") == 0) { if (desiredAltitude > 100) desiredAltitude -= ftToM(100); else if (desiredAltitude > 0) desiredAltitude -= ftToM(10); } - else if (change.compare(L"climb") == 0) + else if (change.compare("climb") == 0) { if (desiredAltitude > 100) desiredAltitude += ftToM(100); diff --git a/src/core/src/navyunit.cpp b/src/core/src/navyunit.cpp index dc96f7a1..7b86fb72 100644 --- a/src/core/src/navyunit.cpp +++ b/src/core/src/navyunit.cpp @@ -13,13 +13,12 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Navy Unit */ -NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID) +NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID) { log("New Navy Unit created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); - double desiredSpeed = 10; - setDesiredSpeed(desiredSpeed); + setCategory("NavyUnit"); + setDesiredSpeed(10); }; void NavyUnit::AIloop() @@ -27,17 +26,17 @@ void NavyUnit::AIloop() /* TODO */ } -void NavyUnit::changeSpeed(wstring change) +void NavyUnit::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) { } - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) { } - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) { } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index e7a7de4e..2346c1ea 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -7,7 +7,7 @@ extern UnitsManager* unitsManager; -Scheduler::Scheduler(lua_State* L): +Scheduler::Scheduler(lua_State* L) : load(0) { LogInfo(L, "Scheduler constructor called successfully"); @@ -25,106 +25,110 @@ void Scheduler::appendCommand(Command* command) void Scheduler::execute(lua_State* L) { - /* Decrease the active computation load. New commands can be sent only if the load has reached 0. + /* Decrease the active computation load. New commands can be sent only if the load has reached 0. This is needed to avoid server lag. */ if (load > 0) { load--; return; } - int priority = CommandPriority::HIGH; - while (priority >= CommandPriority::LOW) - { + int priority = CommandPriority::IMMEDIATE; + while (priority >= CommandPriority::LOW) { for (auto command : commands) { if (command->getPriority() == priority) { - wstring commandString = L"Olympus.protectedCall(" + command->getString(L) + L")"; - if (dostring_in(L, "server", to_string(commandString))) - log(L"Error executing command " + commandString); + string commandString = "Olympus.protectedCall(" + command->getString(L) + ")"; + if (dostring_in(L, "server", (commandString))) + log("Error executing command " + commandString); + else + log("Command '" + commandString + "' executed correctly, current load " + to_string(load)); load = command->getLoad(); commands.remove(command); return; } } priority--; - } + }; } -void Scheduler::handleRequest(wstring key, json::value value) +void Scheduler::handleRequest(string key, json::value value) { Command* command = nullptr; - log(L"Received request with ID: " + key); - if (key.compare(L"setPath") == 0) + log("Received request with ID: " + key); + log(value.serialize()); + if (key.compare("setPath") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { - wstring unitName = unit->getUnitName(); + string unitName = unit->getUnitName(); json::value path = value[L"path"]; list newPath; - for (int i = 1; i <= path.as_object().size(); i++) + for (unsigned int i = 0; i < path.as_array().size(); i++) { - wstring WP = to_wstring(i); - double lat = path[WP][L"lat"].as_double(); - double lng = path[WP][L"lng"].as_double(); - log(unitName + L" set path destination " + WP + L" (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + string WP = to_string(i); + double lat = path[i][L"lat"].as_double(); + double lng = path[i][L"lng"].as_double(); + log(unitName + " set path destination " + WP + " (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords dest; dest.lat = lat; dest.lng = lng; newPath.push_back(dest); } unit->setActivePath(newPath); unit->setState(State::REACH_DESTINATION); - log(unitName + L" new path set successfully"); + log(unitName + " new path set successfully"); } } - else if (key.compare(L"smoke") == 0) + else if (key.compare("smoke") == 0) { - wstring color = value[L"color"].as_string(); + string color = to_string(value[L"color"]); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - log(L"Adding " + color + L" smoke at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + log("Adding " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Smoke(color, loc)); } - else if (key.compare(L"spawnGround") == 0) + else if (key.compare("spawnGround") == 0) { - wstring coalition = value[L"coalition"].as_string(); - wstring type = value[L"type"].as_string(); + bool immediate = value[L"immediate"].as_bool(); + string coalition = to_string(value[L"coalition"]); + string type = to_string(value[L"type"]); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - log(L"Spawning " + coalition + L" ground unit of type " + type + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + log("Spawning " + coalition + " ground unit of type " + type + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords loc; loc.lat = lat; loc.lng = lng; - command = dynamic_cast(new SpawnGroundUnit(coalition, type, loc)); + command = dynamic_cast(new SpawnGroundUnit(coalition, type, loc, immediate)); } - else if (key.compare(L"spawnAir") == 0) + else if (key.compare("spawnAir") == 0) { - wstring coalition = value[L"coalition"].as_string(); - wstring type = value[L"type"].as_string(); + bool immediate = value[L"immediate"].as_bool(); + string coalition = to_string(value[L"coalition"]); + string type = to_string(value[L"type"]); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); double altitude = value[L"altitude"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = altitude; - wstring payloadName = value[L"payloadName"].as_string(); - wstring airbaseName = value[L"airbaseName"].as_string(); - log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")"); - command = dynamic_cast(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName)); + string payloadName = to_string(value[L"payloadName"]); + string airbaseName = to_string(value[L"airbaseName"]); + log("Spawning " + coalition + " air unit of type " + type + " with payload " + payloadName + " at (" + to_string(lat) + ", " + to_string(lng) + " " + airbaseName + ")"); + command = dynamic_cast(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName, immediate)); } - else if (key.compare(L"attackUnit") == 0) + else if (key.compare("attackUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); - int targetID = value[L"targetID"].as_integer(); + unsigned int targetID = value[L"targetID"].as_integer(); Unit* unit = unitsManager->getGroupLeader(ID); Unit* target = unitsManager->getUnit(targetID); - wstring unitName; - wstring targetName; - + string unitName; + string targetName; + if (unit != nullptr) unitName = unit->getUnitName(); else @@ -135,24 +139,24 @@ void Scheduler::handleRequest(wstring key, json::value value) else return; - log(L"Unit " + unitName + L" attacking unit " + targetName); + log("Unit " + unitName + " attacking unit " + targetName); unit->setTargetID(targetID); unit->setState(State::ATTACK); } - else if (key.compare(L"followUnit") == 0) + else if (key.compare("followUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); - int leaderID = value[L"targetID"].as_integer(); - int offsetX = value[L"offsetX"].as_integer(); - int offsetY = value[L"offsetY"].as_integer(); - int offsetZ = value[L"offsetZ"].as_integer(); + unsigned int leaderID = value[L"targetID"].as_double(); + double offsetX = value[L"offsetX"].as_double(); + double offsetY = value[L"offsetY"].as_double(); + double offsetZ = value[L"offsetZ"].as_double(); Unit* unit = unitsManager->getGroupLeader(ID); Unit* leader = unitsManager->getUnit(leaderID); - wstring unitName; - wstring leaderName; + string unitName; + string leaderName; if (unit != nullptr) unitName = unit->getUnitName(); @@ -164,95 +168,95 @@ void Scheduler::handleRequest(wstring key, json::value value) else return; - log(L"Unit " + unitName + L" following unit " + leaderName); + log("Unit " + unitName + " following unit " + leaderName); unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ)); unit->setLeaderID(leaderID); unit->setState(State::FOLLOW); } - else if (key.compare(L"changeSpeed") == 0) + else if (key.compare("changeSpeed") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->changeSpeed(value[L"change"].as_string()); + unit->changeSpeed(to_string(value[L"change"])); } - else if (key.compare(L"changeAltitude") == 0) + else if (key.compare("changeAltitude") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->changeAltitude(value[L"change"].as_string()); + unit->changeAltitude(to_string(value[L"change"])); } - else if (key.compare(L"setSpeed") == 0) + else if (key.compare("setSpeed") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setDesiredSpeed(value[L"speed"].as_double()); } - else if (key.compare(L"setSpeedType") == 0) + else if (key.compare("setSpeedType") == 0) { - int ID = value[L"ID"].as_integer(); - unitsManager->acquireControl(ID); - Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) - unit->setDesiredSpeedType(value[L"speedType"].as_string()); + unsigned int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + if (unit != nullptr) + unit->setDesiredSpeedType(to_string(value[L"speedType"])); } - else if (key.compare(L"setAltitude") == 0) + else if (key.compare("setAltitude") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setDesiredAltitude(value[L"altitude"].as_double()); } - else if (key.compare(L"setAltitudeType") == 0) + else if (key.compare("setAltitudeType") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->setDesiredAltitudeType(value[L"altitudeType"].as_string()); + unit->setDesiredAltitudeType(to_string(value[L"altitudeType"])); } - else if (key.compare(L"cloneUnit") == 0) + else if (key.compare("cloneUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Clone(ID, loc)); - log(L"Cloning unit " + to_wstring(ID)); + log("Cloning unit " + to_string(ID)); } - else if (key.compare(L"setROE") == 0) + else if (key.compare("setROE") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - wstring ROE = value[L"ROE"].as_string(); + unsigned char ROE = value[L"ROE"].as_number().to_uint32(); unit->setROE(ROE); } - else if (key.compare(L"setReactionToThreat") == 0) + else if (key.compare("setReactionToThreat") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - wstring reactionToThreat = value[L"reactionToThreat"].as_string(); + unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().to_uint32(); unit->setReactionToThreat(reactionToThreat); } - else if (key.compare(L"setEmissionsCountermeasures") == 0) + else if (key.compare("setEmissionsCountermeasures") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - wstring emissionsCountermeasures = value[L"emissionsCountermeasures"].as_string(); + unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().to_uint32(); unit->setEmissionsCountermeasures(emissionsCountermeasures); } - else if (key.compare(L"landAt") == 0) + else if (key.compare("landAt") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); double lat = value[L"location"][L"lat"].as_double(); @@ -260,22 +264,22 @@ void Scheduler::handleRequest(wstring key, json::value value) Coords loc; loc.lat = lat; loc.lng = lng; unit->landAt(loc); } - else if (key.compare(L"deleteUnit") == 0) + else if (key.compare("deleteUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); bool explosion = value[L"explosion"].as_bool(); unitsManager->deleteUnit(ID, explosion); } - else if (key.compare(L"refuel") == 0) + else if (key.compare("refuel") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::REFUEL); } - else if (key.compare(L"setAdvancedOptions") == 0) + else if (key.compare("setAdvancedOptions") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) @@ -285,18 +289,21 @@ void Scheduler::handleRequest(wstring key, json::value value) unit->setIsAWACS(value[L"isAWACS"].as_bool()); /* TACAN Options */ - auto TACAN = value[L"TACAN"]; - unit->setTACAN({ TACAN[L"isOn"].as_bool(), - TACAN[L"channel"].as_number().to_int32(), - TACAN[L"XY"].as_string(), - TACAN[L"callsign"].as_string() - }); + DataTypes::TACAN TACAN; + TACAN.isOn = value[L"TACAN"][L"isOn"].as_bool(); + TACAN.channel = static_cast(value[L"TACAN"][L"channel"].as_number().to_uint32()); + TACAN.XY = to_string(value[L"TACAN"][L"XY"]).at(0); + string callsign = to_string(value[L"TACAN"][L"callsign"]); + if (callsign.length() > 3) + callsign = callsign.substr(0, 3); + strcpy_s(TACAN.callsign, 4, callsign.c_str()); + unit->setTACAN(TACAN); /* Radio Options */ auto radio = value[L"radio"]; - unit->setRadio({ radio[L"frequency"].as_number().to_int32(), - radio[L"callsign"].as_number().to_int32(), - radio[L"callsignNumber"].as_number().to_int32() + unit->setRadio({ radio[L"frequency"].as_number().to_uint32(), + static_cast(radio[L"callsign"].as_number().to_uint32()), + static_cast(radio[L"callsignNumber"].as_number().to_uint32()) }); /* General Settings */ @@ -311,83 +318,84 @@ void Scheduler::handleRequest(wstring key, json::value value) unit->resetActiveDestination(); } } - else if (key.compare(L"setFollowRoads") == 0) - { - int ID = value[L"ID"].as_integer(); + else if (key.compare("setFollowRoads") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); bool followRoads = value[L"followRoads"].as_bool(); Unit* unit = unitsManager->getGroupLeader(ID); unit->setFollowRoads(followRoads); } - else if (key.compare(L"setOnOff") == 0) - { - int ID = value[L"ID"].as_integer(); + else if (key.compare("setOnOff") == 0) + { + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); bool onOff = value[L"onOff"].as_bool(); Unit* unit = unitsManager->getGroupLeader(ID); unit->setOnOff(onOff); } - else if (key.compare(L"explosion") == 0) + else if (key.compare("explosion") == 0) { - int intensity = value[L"intensity"].as_integer(); + unsigned int intensity = value[L"intensity"].as_integer(); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - log(L"Adding " + to_wstring(intensity) + L" explosion at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + log("Adding " + to_string(intensity) + " explosion at (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Explosion(intensity, loc)); } - else if (key.compare(L"bombPoint") == 0) + else if (key.compare("bombPoint") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::BOMB_POINT); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } - else if (key.compare(L"carpetBomb") == 0) + else if (key.compare("carpetBomb") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::CARPET_BOMB); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } - else if (key.compare(L"bombBuilding") == 0) + else if (key.compare("bombBuilding") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::BOMB_BUILDING); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } - else if (key.compare(L"fireAtArea") == 0) + else if (key.compare("fireAtArea") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::FIRE_AT_AREA); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } else { - log(L"Unknown command: " + key); + log("Unknown command: " + key); } - + if (command != nullptr) { appendCommand(command); + log("New command appended correctly to stack. Current server load: " + to_string(load)); } } diff --git a/src/core/src/scriptloader.cpp b/src/core/src/scriptloader.cpp index 0509463e..12ed68c8 100644 --- a/src/core/src/scriptloader.cpp +++ b/src/core/src/scriptloader.cpp @@ -13,10 +13,12 @@ bool executeLuaScript(lua_State* L, string path) if (dostring_in(L, "server", str.c_str()) != 0) { log("Error registering " + path); + return false; } else { log(path + " registered successfully"); + return true; } } diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index a2b01978..580b24d7 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -36,7 +36,7 @@ Server::Server(lua_State* L): serverThread(nullptr), runListener(true) { - + } void Server::start(lua_State* L) @@ -69,9 +69,11 @@ void Server::handle_get(http_request request) /* Lock for thread safety */ lock_guard guard(mutexLock); + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + http_response response(status_codes::OK); - string authorization = to_base64("admin:" + to_string(password)); - if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + string authorization = to_base64("admin:" + password); + if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0)) { std::exception_ptr eptr; try { @@ -80,7 +82,8 @@ void Server::handle_get(http_request request) if (path.size() > 0) { - if (path[0] == UNITS_URI) + string URI = to_string(path[0]); + if (URI.compare(UNITS_URI) == 0) { map query = request.relative_uri().split_query(request.relative_uri().query()); long long time = 0; @@ -93,27 +96,33 @@ void Server::handle_get(http_request request) time = 0; } } - unitsManager->getData(answer, time); - } - else if (path[0] == LOGS_URI) - { - auto logs = json::value::object(); - getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries - answer[L"logs"] = logs; - } - else if (path[0] == AIRBASES_URI) - answer[L"airbases"] = airbases; - else if (path[0] == BULLSEYE_URI) - answer[L"bullseyes"] = bullseyes; - else if (path[0] == MISSION_URI) - answer[L"mission"] = mission; + unsigned long long updateTime = ms.count(); - milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - answer[L"time"] = json::value::string(to_wstring(ms.count())); - answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); + stringstream ss; + ss.write((char*)&updateTime, sizeof(updateTime)); + unitsManager->getUnitData(ss, time); + response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); + } + else { + if (URI.compare(LOGS_URI) == 0) + { + auto logs = json::value::object(); + getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries + answer[L"logs"] = logs; + } + else if (URI.compare(AIRBASES_URI) == 0) + answer[L"airbases"] = airbases; + else if (URI.compare(BULLSEYE_URI) == 0) + answer[L"bullseyes"] = bullseyes; + else if (URI.compare(MISSION_URI) == 0) + answer[L"mission"] = mission; + + + answer[L"time"] = json::value::string(to_wstring(ms.count())); + answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); + response.set_body(answer); + } } - - response.set_body(answer); } catch (...) { eptr = std::current_exception(); // capture @@ -135,8 +144,8 @@ void Server::handle_get(http_request request) void Server::handle_request(http_request request, function action) { http_response response(status_codes::OK); - string authorization = to_base64("admin:" + to_string(password)); - if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + string authorization = to_base64("admin:" + password); + if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0)) { auto answer = json::value::object(); request.extract_json().then([&answer, &action](pplx::task task) @@ -144,11 +153,8 @@ void Server::handle_request(http_request request, functionhandleRequest(key, value); + scheduler->handleRequest(to_string(key), value); } catch (...) { eptr = std::current_exception(); // capture @@ -196,8 +202,8 @@ void Server::handle_put(http_request request) void Server::task() { - wstring address = wstring(REST_ADDRESS); - wstring modLocation; + string address = REST_ADDRESS; + string modLocation; char* buf = nullptr; size_t sz = 0; if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) @@ -206,31 +212,31 @@ void Server::task() std::stringstream ss; ss << ifstream.rdbuf(); std::error_code errorCode; - json::value config = json::value::parse(to_wstring(ss.str()), errorCode); + json::value config = json::value::parse(ss.str(), errorCode); if (config.is_object() && config.has_object_field(L"server") && config[L"server"].has_string_field(L"address") && config[L"server"].has_number_field(L"port")) { - address = L"http://" + config[L"server"][L"address"].as_string() + L":" + to_wstring(config[L"server"][L"port"].as_number().to_int32()); - log(L"Starting server on " + address); + address = "http://" + to_string(config[L"server"][L"address"]) + ":" + to_string(config[L"server"][L"port"].as_number().to_int32()); + log("Starting server on " + address); } else - log(L"Error reading configuration file. Starting server on " + address); + log("Error reading configuration file. Starting server on " + address); if (config.is_object() && config.has_object_field(L"authentication") && config[L"authentication"].has_string_field(L"password")) { - password = config[L"authentication"][L"password"].as_string(); + password = to_string(config[L"authentication"][L"password"]); } else - log(L"Error reading configuration file. No password set."); + log("Error reading configuration file. No password set."); free(buf); } else { - log(L"DCSOLYMPUS_PATH environment variable is missing, starting server on " + address); + log("DCSOLYMPUS_PATH environment variable is missing, starting server on " + address); } - http_listener listener(address + L"/" + wstring(REST_URI)); + http_listener listener(to_wstring(address + "/" + REST_URI)); std::function handle_options = std::bind(&Server::handle_options, this, std::placeholders::_1); std::function handle_get = std::bind(&Server::handle_get, this, std::placeholders::_1); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 4b18efc7..dd912cf7 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -15,24 +15,7 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; -// TODO: Make dedicated file -bool operator==(const Options::TACAN& lhs, const Options::TACAN& rhs) -{ - return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && lhs.callsign == rhs.callsign; -} - -bool operator==(const Options::Radio& lhs, const Options::Radio& rhs) -{ - return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber; -} - -bool operator==(const Options::GeneralSettings& lhs, const Options::GeneralSettings& rhs) -{ - return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG && - lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; -} - -Unit::Unit(json::value json, int ID) : +Unit::Unit(json::value json, unsigned int ID) : ID(ID) { log("Creating unit with ID: " + to_string(ID)); @@ -45,215 +28,259 @@ Unit::~Unit() void Unit::initialize(json::value json) { + if (json.has_string_field(L"Name")) + setName(to_string(json[L"Name"])); + if (json.has_string_field(L"UnitName")) + setUnitName(to_string(json[L"UnitName"])); + if (json.has_string_field(L"GroupName")) + setGroupName(to_string(json[L"GroupName"])); + if (json.has_number_field(L"Country")) + setCountry(json[L"Country"].as_number().to_int32()); + if (json.has_number_field(L"CoalitionID")) + setCoalition(json[L"CoalitionID"].as_number().to_int32()); + + if (json.has_object_field(L"Flags")) + setHuman(json[L"Flags"][L"Human"].as_bool()); + + /* All units which contain the name "Olympus" are automatically under AI control */ + if (getUnitName().find("Olympus") != string::npos) + setControlled(true); + updateExportData(json); setDefaults(); } void Unit::setDefaults(bool force) { - const bool isUnitControlledByOlympus = getControlled(); - const bool isUnitAlive = getAlive(); - const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); - const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); - const bool isUnitHuman = getFlags()[L"Human"].as_bool(); - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) { - /* Set the default IDLE state */ - setState(State::IDLE); + if (!getControlled()) return; + if (!unitsManager->isUnitGroupLeader(this)) return; + if (!(getAlive() || unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this))) return; + if (getHuman()) return; - /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ - setDesiredAltitude(altitude); + /* Set the default IDLE state */ + setState(State::IDLE); - /* Set the default options (these are all defaults so will only affect the export data, no DCS command will be sent) */ - setROE(L"Designated", force); - setReactionToThreat(L"Evade", force); - setEmissionsCountermeasures(L"Defend", force); - setTACAN(TACAN, force); - setRadio(radio, force); - setEPLRS(EPLRS, force); - setGeneralSettings(generalSettings, force); - setOnOff(onOff); - setFollowRoads(followRoads); - } -} + /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ + setDesiredAltitude(position.alt); -void Unit::addMeasure(wstring key, json::value value) -{ - milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - if (measures.find(key) == measures.end()) - measures[key] = new Measure(value, ms.count()); - else - { - if (measures[key]->getValue() != value) - { - measures[key]->setValue(value); - measures[key]->setTime(ms.count()); - } - } + /* Set the default options */ + setROE(ROE::OPEN_FIRE_WEAPON_FREE, force); + setReactionToThreat(ReactionToThreat::EVADE_FIRE, force); + setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force); + strcpy_s(TACAN.callsign, 4, "TKR"); + setTACAN(TACAN, force); + setRadio(radio, force); + setGeneralSettings(generalSettings, force); } void Unit::runAILoop() { - /* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ - const bool isUnitControlledByOlympus = getControlled(); + /* If the unit is alive, controlled and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ + if (!getControlled()) return; + if (!unitsManager->isUnitGroupLeader(this)) return; + if (human) return; + + /* Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it */ const bool isUnitAlive = getAlive(); - const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); - const bool isUnitHuman = getFlags()[L"Human"].as_bool(); + if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return; - // Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) - { - if (checkTaskFailed() && state != State::IDLE && State::LAND) - setState(State::IDLE); + if (checkTaskFailed() && state != State::IDLE && State::LAND) + setState(State::IDLE); - AIloop(); - } + AIloop(); } -void Unit::updateExportData(json::value json) +void Unit::updateExportData(json::value json, double dt) { + Coords newPosition = Coords(NULL); + double newHeading = 0; + double newSpeed = 0; + + if (json.has_object_field(L"LatLongAlt")) + { + setPosition({ + json[L"LatLongAlt"][L"Lat"].as_number().to_double(), + json[L"LatLongAlt"][L"Long"].as_number().to_double(), + json[L"LatLongAlt"][L"Alt"].as_number().to_double() + }); + } + if (json.has_number_field(L"Heading")) + setHeading(json[L"Heading"].as_number().to_double()); + /* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */ if (oldPosition != NULL) { double dist = 0; - Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist); - setSpeed(getSpeed() * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05); + Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, oldPosition.lat, oldPosition.lng, dist); + if (dt > 0) + setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } - oldPosition = Coords(latitude, longitude, altitude); - if (json.has_string_field(L"Name")) - setName(json[L"Name"].as_string()); - if (json.has_string_field(L"UnitName")) - setUnitName(json[L"UnitName"].as_string()); - if (json.has_string_field(L"GroupName")) - setGroupName(json[L"GroupName"].as_string()); - if (json.has_object_field(L"Type")) - setType(json[L"Type"]); - if (json.has_number_field(L"Country")) - setCountry(json[L"Country"].as_number().to_int32()); - if (json.has_number_field(L"CoalitionID")) - setCoalitionID(json[L"CoalitionID"].as_number().to_int32()); - if (json.has_object_field(L"LatLongAlt")) - { - setLatitude(json[L"LatLongAlt"][L"Lat"].as_number().to_double()); - setLongitude(json[L"LatLongAlt"][L"Long"].as_number().to_double()); - setAltitude(json[L"LatLongAlt"][L"Alt"].as_number().to_double()); - } - if (json.has_number_field(L"Heading")) - setHeading(json[L"Heading"].as_number().to_double()); - if (json.has_object_field(L"Flags")) - setFlags(json[L"Flags"]); - - /* All units which contain the name "Olympus" are automatically under AI control */ - if (getUnitName().find(L"Olympus") != wstring::npos) - setControlled(true); + oldPosition = position; } void Unit::updateMissionData(json::value json) { - if (json.has_number_field(L"fuel")) - setFuel(int(json[L"fuel"].as_number().to_double() * 100)); - if (json.has_object_field(L"ammo")) - setAmmo(json[L"ammo"]); - if (json.has_object_field(L"contacts")) - setContacts(json[L"contacts"]); + if (json.has_number_field(L"fuel")) { + setFuel(short(json[L"fuel"].as_number().to_double() * 100)); + } + + if (json.has_object_field(L"ammo")) { + vector ammo; + for (auto const& el : json[L"ammo"].as_object()) { + DataTypes::Ammo ammoItem; + auto ammoJson = el.second; + ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); + string name = to_string(ammoJson[L"desc"][L"displayName"].as_string()).substr(0, sizeof(ammoItem.name) - 1); + strcpy_s(ammoItem.name, sizeof(ammoItem.name), name.c_str()); + + if (ammoJson[L"desc"].has_number_field(L"guidance")) + ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); + + if (ammoJson[L"desc"].has_number_field(L"category")) + ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); + + if (ammoJson[L"desc"].has_number_field(L"missileCategory")) + ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); + ammo.push_back(ammoItem); + } + setAmmo(ammo); + } + + if (json.has_object_field(L"contacts")) { + vector contacts; + for (auto const& el : json[L"contacts"].as_object()) { + DataTypes::Contact contactItem; + auto contactJson = el.second; + contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); + + string detectionMethod = to_string(contactJson[L"detectionMethod"]); + if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1; + else if (detectionMethod.compare("OPTIC") == 0) contactItem.detectionMethod = 2; + else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4; + else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8; + else if (detectionMethod.compare("RWR") == 0) contactItem.detectionMethod = 16; + else if (detectionMethod.compare("DLINK") == 0) contactItem.detectionMethod = 32; + contacts.push_back(contactItem); + } + setContacts(contacts); + } + if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); } -json::value Unit::getData(long long time, bool sendAll) +bool Unit::checkFreshness(unsigned char datumIndex, unsigned long long time) { + auto it = updateTimeMap.find(datumIndex); + if (it == updateTimeMap.end()) + return false; + else + return it->second > time; +} + +bool Unit::hasFreshData(unsigned long long time) { + for (auto it : updateTimeMap) + if (it.second > time) + return true; + return false; +} + +void Unit::getData(stringstream& ss, unsigned long long time) { - auto json = json::value::object(); + Unit* leader = this; + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) + leader = unitsManager->getGroupLeader(this); - /* If the unit is in a group, task & option data is given by the group leader */ - if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) - json = unitsManager->getGroupLeader(this)->getData(time, true); - - /********** Base data **********/ - json[L"baseData"] = json::value::object(); - for (auto key : { L"controlled", L"name", L"unitName", L"groupName", L"alive", L"category"}) + if (!leader->hasFreshData(time)) return; + + const unsigned char endOfData = DataIndex::endOfData; + ss.write((const char*)&ID, sizeof(ID)); + for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++) { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"baseData"][key] = measures[key]->getValue(); - } - if (json[L"baseData"].size() == 0) - json.erase(L"baseData"); - - if (alive || sendAll) { - /********** Flight data **********/ - json[L"flightData"] = json::value::object(); - for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"flightData"][key] = measures[key]->getValue(); - } - if (json[L"flightData"].size() == 0) - json.erase(L"flightData"); - - /********** Mission data **********/ - json[L"missionData"] = json::value::object(); - for (auto key : { L"fuel", L"ammo", L"contacts", L"hasTask", L"coalition", L"flags" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"missionData"][key] = measures[key]->getValue(); - } - if (json[L"missionData"].size() == 0) - json.erase(L"missionData"); - - /********** Formation data **********/ - json[L"formationData"] = json::value::object(); - for (auto key : { L"leaderID" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"formationData"][key] = measures[key]->getValue(); - } - if (json[L"formationData"].size() == 0) - json.erase(L"formationData"); - - /* If the unit is in a group, task & option data is given by the group leader */ - if (unitsManager->isUnitGroupLeader(this)) { - /********** Task data **********/ - json[L"taskData"] = json::value::object(); - for (auto key : { L"currentState", L"currentTask", L"desiredSpeed", L"desiredAltitude", L"desiredSpeedType", L"desiredAltitudeType", L"activePath", L"isTanker", L"isAWACS", L"onOff", L"followRoads", L"targetID", L"targetLocation" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"taskData"][key] = measures[key]->getValue(); - } - if (json[L"taskData"].size() == 0) - json.erase(L"taskData"); - - /********** Options data **********/ - json[L"optionsData"] = json::value::object(); - for (auto key : { L"ROE", L"reactionToThreat", L"emissionsCountermeasures", L"TACAN", L"radio", L"generalSettings" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"optionsData"][key] = measures[key]->getValue(); - } - if (json[L"optionsData"].size() == 0) - json.erase(L"optionsData"); + /* When units are in a group, most data comes from the group leader */ + switch (datumIndex) { + case DataIndex::category: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, category); break; + case DataIndex::alive: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, alive); break; + case DataIndex::human: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->human); break; + case DataIndex::controlled: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->controlled); break; + case DataIndex::coalition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->coalition); break; + case DataIndex::country: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->country); break; + case DataIndex::name: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, name); break; + case DataIndex::unitName: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, unitName); break; + case DataIndex::groupName: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->groupName); break; + case DataIndex::state: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->state); break; + case DataIndex::task: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->task); break; + case DataIndex::hasTask: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->hasTask); break; + case DataIndex::position: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, position); break; + case DataIndex::speed: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, speed); break; + case DataIndex::heading: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, heading); break; + case DataIndex::isTanker: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isTanker); break; + case DataIndex::isAWACS: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isAWACS); break; + case DataIndex::onOff: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->onOff); break; + case DataIndex::followRoads: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->followRoads); break; + case DataIndex::fuel: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, fuel); break; + case DataIndex::desiredSpeed: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeed); break; + case DataIndex::desiredSpeedType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeedType); break; + case DataIndex::desiredAltitude: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitude); break; + case DataIndex::desiredAltitudeType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitudeType); break; + case DataIndex::leaderID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->leaderID); break; + case DataIndex::formationOffset: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->formationOffset); break; + case DataIndex::targetID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetID); break; + case DataIndex::targetPosition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetPosition); break; + case DataIndex::ROE: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->ROE); break; + case DataIndex::reactionToThreat: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->reactionToThreat); break; + case DataIndex::emissionsCountermeasures: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->emissionsCountermeasures); break; + case DataIndex::TACAN: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->TACAN); break; + case DataIndex::radio: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->radio); break; + case DataIndex::generalSettings: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->generalSettings); break; + case DataIndex::ammo: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, ammo); break; + case DataIndex::contacts: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, contacts); break; + case DataIndex::activePath: if (leader->checkFreshness(datumIndex, time)) appendList(ss, datumIndex, leader->activePath); break; } } + ss.write((const char*)&endOfData, sizeof(endOfData)); +} - return json; +void Unit::setAmmo(vector newValue) +{ + if (ammo.size() == newValue.size()) { + bool equal = true; + for (int i = 0; i < ammo.size(); i++) { + if (ammo.at(i) != newValue.at(i)) + { + equal = false; + break; + } + } + if (equal) + return; + } + ammo = newValue; + triggerUpdate(DataIndex::ammo); +} + +void Unit::setContacts(vector newValue) +{ + if (contacts.size() == newValue.size()) { + bool equal = true; + for (int i = 0; i < contacts.size(); i++) { + if (contacts.at(i) != newValue.at(i)) + { + equal = false; + break; + } + } + if (equal) + return; + } + contacts = newValue; + triggerUpdate(DataIndex::contacts); } void Unit::setActivePath(list newPath) { activePath = newPath; resetActiveDestination(); - - auto path = json::value::object(); - if (activePath.size() > 0) { - int count = 1; - for (auto& destination : activePath) - { - auto json = json::value::object(); - json[L"lat"] = destination.lat; - json[L"lng"] = destination.lng; - json[L"alt"] = destination.alt; - path[to_wstring(count++)] = json; - } - } - addMeasure(L"activePath", path); } void Unit::clearActivePath() @@ -283,28 +310,7 @@ void Unit::popActivePathFront() setActivePath(path); } -void Unit::setCoalitionID(int newCoalitionID) -{ - if (newCoalitionID == 0) - coalition = L"neutral"; - else if (newCoalitionID == 1) - coalition = L"red"; - else - coalition = L"blue"; - addMeasure(L"coalition", json::value(coalition)); -} - -int Unit::getCoalitionID() -{ - if (coalition == L"neutral") - return 0; - else if (coalition == L"red") - return 1; - else - return 2; -} - -wstring Unit::getTargetName() +string Unit::getTargetName() { if (isTargetAlive()) { @@ -312,7 +318,7 @@ wstring Unit::getTargetName() if (target != nullptr) return target->getUnitName(); } - return L""; + return ""; } bool Unit::isTargetAlive() @@ -327,7 +333,7 @@ bool Unit::isTargetAlive() return false; } -wstring Unit::getLeaderName() +string Unit::getLeaderName() { if (isLeaderAlive()) { @@ -335,7 +341,7 @@ wstring Unit::getLeaderName() if (leader != nullptr) return leader->getUnitName(); } - return L""; + return ""; } bool Unit::isLeaderAlive() @@ -367,86 +373,60 @@ void Unit::setFormationOffset(Offset newFormationOffset) { formationOffset = newFormationOffset; resetTask(); + + triggerUpdate(DataIndex::formationOffset); } -void Unit::setROE(wstring newROE, bool force) { - addMeasure(L"ROE", json::value(newROE)); - +void Unit::setROE(unsigned char newROE, bool force) +{ if (ROE != newROE || force) { ROE = newROE; - - int ROEEnum; - if (ROE.compare(L"Free") == 0) - ROEEnum = ROE::WEAPON_FREE; - else if (ROE.compare(L"Designated free") == 0) - ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE; - else if (ROE.compare(L"Designated") == 0) - ROEEnum = ROE::OPEN_FIRE; - else if (ROE.compare(L"Return") == 0) - ROEEnum = ROE::RETURN_FIRE; - else if (ROE.compare(L"Hold") == 0) - ROEEnum = ROE::WEAPON_HOLD; - else - return; - - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROEEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, static_cast(ROE))); scheduler->appendCommand(command); + + triggerUpdate(DataIndex::ROE); } } -void Unit::setReactionToThreat(wstring newReactionToThreat, bool force) { - addMeasure(L"reactionToThreat", json::value(newReactionToThreat)); - +void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force) +{ if (reactionToThreat != newReactionToThreat || force) { reactionToThreat = newReactionToThreat; - int reactionToThreatEnum; - if (reactionToThreat.compare(L"None") == 0) - reactionToThreatEnum = ReactionToThreat::NO_REACTION; - else if (reactionToThreat.compare(L"Passive") == 0) - reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE; - else if (reactionToThreat.compare(L"Evade") == 0) - reactionToThreatEnum = ReactionToThreat::EVADE_FIRE; - else if (reactionToThreat.compare(L"Escape") == 0) - reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE; - else if (reactionToThreat.compare(L"Abort") == 0) - reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION; - else - return; - - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast(reactionToThreat))); scheduler->appendCommand(command); + + triggerUpdate(DataIndex::reactionToThreat); } } -void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force) { - addMeasure(L"emissionsCountermeasures", json::value(newEmissionsCountermeasures)); - +void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures, bool force) +{ if (emissionsCountermeasures != newEmissionsCountermeasures || force) { emissionsCountermeasures = newEmissionsCountermeasures; - int radarEnum; - int flareEnum; - int ECMEnum; - if (emissionsCountermeasures.compare(L"Silent") == 0) + unsigned int radarEnum; + unsigned int flareEnum; + unsigned int ECMEnum; + if (emissionsCountermeasures == EmissionCountermeasure::SILENT) { radarEnum = RadarUse::NEVER; flareEnum = FlareUse::NEVER; ECMEnum = ECMUse::NEVER_USE; } - else if (emissionsCountermeasures.compare(L"Attack") == 0) + else if (emissionsCountermeasures == EmissionCountermeasure::ATTACK) { radarEnum = RadarUse::FOR_ATTACK_ONLY; flareEnum = FlareUse::AGAINST_FIRED_MISSILE; ECMEnum = ECMUse::USE_IF_ONLY_LOCK_BY_RADAR; } - else if (emissionsCountermeasures.compare(L"Defend") == 0) + else if (emissionsCountermeasures == EmissionCountermeasure::DEFEND) { radarEnum = RadarUse::FOR_SEARCH_IF_REQUIRED; flareEnum = FlareUse::WHEN_FLYING_IN_SAM_WEZ; ECMEnum = ECMUse::USE_IF_DETECTED_LOCK_BY_RADAR; } - else if (emissionsCountermeasures.compare(L"Free") == 0) + else if (emissionsCountermeasures == EmissionCountermeasure::FREE) { radarEnum = RadarUse::FOR_CONTINUOUS_SEARCH; flareEnum = FlareUse::WHEN_FLYING_NEAR_ENEMIES; @@ -465,45 +445,49 @@ void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool command = dynamic_cast(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum)); scheduler->appendCommand(command); + + triggerUpdate(DataIndex::emissionsCountermeasures); } } -void Unit::landAt(Coords loc) { +void Unit::landAt(Coords loc) +{ clearActivePath(); pushActivePathBack(loc); setState(State::LAND); } -void Unit::setIsTanker(bool newIsTanker) { - isTanker = newIsTanker; - resetTask(); - addMeasure(L"isTanker", json::value(newIsTanker)); +void Unit::setIsTanker(bool newIsTanker) +{ + if (isTanker != newIsTanker) { + isTanker = newIsTanker; + resetTask(); + + triggerUpdate(DataIndex::isTanker); + } } -void Unit::setIsAWACS(bool newIsAWACS) { - isAWACS = newIsAWACS; - resetTask(); - addMeasure(L"isAWACS", json::value(newIsAWACS)); - setEPLRS(isAWACS); +void Unit::setIsAWACS(bool newIsAWACS) +{ + if (isAWACS != newIsAWACS) { + isAWACS = newIsAWACS; + resetTask(); + + triggerUpdate(DataIndex::isAWACS); + } } -void Unit::setTACAN(Options::TACAN newTACAN, bool force) { - auto json = json::value(); - json[L"isOn"] = json::value(newTACAN.isOn); - json[L"channel"] = json::value(newTACAN.channel); - json[L"XY"] = json::value(newTACAN.XY); - json[L"callsign"] = json::value(newTACAN.callsign); - addMeasure(L"TACAN", json); - +void Unit::setTACAN(DataTypes::TACAN newTACAN, bool force) +{ if (TACAN != newTACAN || force) { TACAN = newTACAN; if (TACAN.isOn) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS << "{" << "id = 'ActivateBeacon'," << "params = {" - << "type = " << ((TACAN.XY.compare(L"X") == 0) ? 4 : 5) << "," + << "type = " << ((TACAN.XY == 'X' == 0) ? 4 : 5) << "," << "system = 3," << "name = \"Olympus_TACAN\"," << "callsign = \"" << TACAN.callsign << "\", " @@ -514,7 +498,7 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) { scheduler->appendCommand(command); } else { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS << "{" << "id = 'DeactivateBeacon'," << "params = {" @@ -523,22 +507,18 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) { Command* command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } + + triggerUpdate(DataIndex::TACAN); } } -void Unit::setRadio(Options::Radio newRadio, bool force) { - - auto json = json::value(); - json[L"frequency"] = json::value(newRadio.frequency); - json[L"callsign"] = json::value(newRadio.callsign); - json[L"callsignNumber"] = json::value(newRadio.callsignNumber); - addMeasure(L"radio", json); - +void Unit::setRadio(DataTypes::Radio newRadio, bool force) +{ if (radio != newRadio || force) { radio = newRadio; - std::wostringstream commandSS; + std::ostringstream commandSS; Command* command; commandSS << "{" @@ -552,7 +532,7 @@ void Unit::setRadio(Options::Radio newRadio, bool force) { scheduler->appendCommand(command); // Clear the stringstream - commandSS.str(wstring()); + commandSS.str(string("")); commandSS << "{" << "id = 'SetCallsign'," @@ -563,38 +543,13 @@ void Unit::setRadio(Options::Radio newRadio, bool force) { << "}"; command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); + + triggerUpdate(DataIndex::radio); } } -void Unit::setEPLRS(bool newEPLRS, bool force) +void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, bool force) { - //addMeasure(L"EPLRS", json::value(newEPLRS)); - // - //if (EPLRS != newEPLRS || force) { - // EPLRS = newEPLRS; - // - // std::wostringstream commandSS; - // commandSS << "{" - // << "id = 'EPLRS'," - // << "params = {" - // << "value = " << (EPLRS ? "true" : "false") << ", " - // << "}" - // << "}"; - // Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); - // scheduler->appendCommand(command); - //} -} - -void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force) { - - auto json = json::value(); - json[L"prohibitJettison"] = json::value(newGeneralSettings.prohibitJettison); - json[L"prohibitAA"] = json::value(newGeneralSettings.prohibitAA); - json[L"prohibitAG"] = json::value(newGeneralSettings.prohibitAG); - json[L"prohibitAfterburner"] = json::value(newGeneralSettings.prohibitAfterburner); - json[L"prohibitAirWpn"] = json::value(newGeneralSettings.prohibitAirWpn); - addMeasure(L"generalSettings", json); - if (generalSettings != newGeneralSettings) { generalSettings = newGeneralSettings; @@ -610,50 +565,60 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool scheduler->appendCommand(command); command = dynamic_cast(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); scheduler->appendCommand(command); + + triggerUpdate(DataIndex::generalSettings); } } -void Unit::setDesiredSpeed(double newDesiredSpeed) { - desiredSpeed = newDesiredSpeed; - addMeasure(L"desiredSpeed", json::value(newDesiredSpeed)); +void Unit::setDesiredSpeed(double newDesiredSpeed) +{ + desiredSpeed = newDesiredSpeed; if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(DataIndex::desiredSpeed); } -void Unit::setDesiredAltitude(double newDesiredAltitude) { +void Unit::setDesiredAltitude(double newDesiredAltitude) +{ desiredAltitude = newDesiredAltitude; - addMeasure(L"desiredAltitude", json::value(newDesiredAltitude)); if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(DataIndex::desiredAltitude); } -void Unit::setDesiredSpeedType(wstring newDesiredSpeedType) { - desiredSpeedType = newDesiredSpeedType; - addMeasure(L"desiredSpeedType", json::value(newDesiredSpeedType)); +void Unit::setDesiredSpeedType(string newDesiredSpeedType) +{ + desiredSpeedType = newDesiredSpeedType.compare("GS") == 0; if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(DataIndex::desiredSpeedType); } -void Unit::setDesiredAltitudeType(wstring newDesiredAltitudeType) { - desiredAltitudeType = newDesiredAltitudeType; - addMeasure(L"desiredAltitudeType", json::value(newDesiredAltitudeType)); +void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) +{ + desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0; if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(DataIndex::desiredAltitudeType); } -void Unit::goToDestination(wstring enrouteTask) +void Unit::goToDestination(string enrouteTask) { if (activeDestination != NULL) { - Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType(), getDesiredAltitude(), getDesiredAltitudeType(), enrouteTask, getCategory())); + Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType() ? "GS" : "CAS", getDesiredAltitude(), getDesiredAltitudeType() ? "AGL" : "ASL", enrouteTask, getCategory())); scheduler->appendCommand(command); setHasTask(true); } @@ -664,19 +629,20 @@ bool Unit::isDestinationReached(double threshold) if (activeDestination != NULL) { /* Check if any unit in the group has reached the point */ - for (auto const& p: unitsManager->getGroupMembers(groupName)) + for (auto const& p : unitsManager->getGroupMembers(groupName)) { double dist = 0; - Geodesic::WGS84().Inverse(p->getLatitude(), p->getLongitude(), activeDestination.lat, activeDestination.lng, dist); + Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist); if (dist < threshold) { - log(unitName + L" destination reached"); + log(unitName + " destination reached"); return true; } else { return false; } - } + } + return false; } else return true; @@ -687,13 +653,17 @@ bool Unit::setActiveDestination() if (activePath.size() > 0) { activeDestination = activePath.front(); - log(unitName + L" active destination set to queue front"); + log(unitName + " active destination set to queue front"); + + triggerUpdate(DataIndex::activePath); return true; } else { activeDestination = Coords(0); - log(unitName + L" active destination set to NULL"); + log(unitName + " active destination set to NULL"); + + triggerUpdate(DataIndex::activePath); return false; } } @@ -706,7 +676,7 @@ bool Unit::updateActivePath(bool looping) if (looping) pushActivePathBack(activePath.front()); popActivePathFront(); - log(unitName + L" active path front popped"); + log(unitName + " active path front popped"); return true; } else { @@ -714,16 +684,9 @@ bool Unit::updateActivePath(bool looping) } } -void Unit::setTargetLocation(Coords newTargetLocation) { - targetLocation = newTargetLocation; - auto json = json::value(); - json[L"latitude"] = json::value(newTargetLocation.lat); - json[L"longitude"] = json::value(newTargetLocation.lng); - addMeasure(L"targetLocation", json::value(json)); -} - -bool Unit::checkTaskFailed() { - if (getHasTask()) +bool Unit::checkTaskFailed() +{ + if (getHasTask()) return false; else { if (taskCheckCounter > 0) @@ -736,7 +699,6 @@ void Unit::resetTaskFailedCounter() { taskCheckCounter = TASK_CHECK_INIT_VALUE; } -void Unit::setHasTask(bool newHasTask) { - hasTask = newHasTask; - addMeasure(L"hasTask", json::value(newHasTask)); -} \ No newline at end of file +void Unit::triggerUpdate(unsigned char datumIndex) { + updateTimeMap[datumIndex] = duration_cast(system_clock::now().time_since_epoch()).count(); +} diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 2c683858..3c96ef9c 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -10,6 +10,9 @@ #include "commands.h" #include "scheduler.h" +#include "base64.hpp" +using namespace base64; + extern Scheduler* scheduler; UnitsManager::UnitsManager(lua_State* L) @@ -22,7 +25,7 @@ UnitsManager::~UnitsManager() } -Unit* UnitsManager::getUnit(int ID) +Unit* UnitsManager::getUnit(unsigned int ID) { if (units.find(ID) == units.end()) { return nullptr; @@ -35,7 +38,7 @@ Unit* UnitsManager::getUnit(int ID) bool UnitsManager::isUnitInGroup(Unit* unit) { if (unit != nullptr) { - wstring groupName = unit->getGroupName(); + string groupName = unit->getGroupName(); for (auto const& p : units) { if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit) @@ -57,26 +60,19 @@ bool UnitsManager::isUnitGroupLeader(Unit* unit) Unit* UnitsManager::getGroupLeader(Unit* unit) { if (unit != nullptr) { - wstring groupName = unit->getGroupName(); - - /* Get the unit IDs in order */ - std::vector keys; - for (auto const& p : units) - keys.push_back(p.first); - sort(keys.begin(), keys.end()); + string groupName = unit->getGroupName(); /* Find the first unit that has the same groupName */ - for (auto const& tempID : keys) + for (auto const& p : units) { - Unit* tempUnit = getUnit(tempID); - if (tempUnit != nullptr && tempUnit->getGroupName().compare(groupName) == 0) - return tempUnit; + if (p.second->getGroupName().compare(groupName) == 0) + return p.second; } } return nullptr; } -vector UnitsManager::getGroupMembers(wstring groupName) +vector UnitsManager::getGroupMembers(string groupName) { vector members; for (auto const& p : units) @@ -87,20 +83,20 @@ vector UnitsManager::getGroupMembers(wstring groupName) return members; } -Unit* UnitsManager::getGroupLeader(int ID) +Unit* UnitsManager::getGroupLeader(unsigned int ID) { Unit* unit = getUnit(ID); return getGroupLeader(unit); } -void UnitsManager::updateExportData(lua_State* L) +void UnitsManager::updateExportData(lua_State* L, double dt) { - map unitJSONs = getAllUnits(L); + map unitJSONs = getAllUnits(L); /* Update all units, create them if needed TODO: move code to get constructor in dedicated function */ for (auto const& p : unitJSONs) { - int ID = p.first; + unsigned int ID = p.first; if (units.count(ID) == 0) { json::value type = static_cast(p.second)[L"Type"]; @@ -132,15 +128,13 @@ void UnitsManager::updateExportData(lua_State* L) else { /* Update the unit if present*/ if (units.count(ID) != 0) - units[ID]->updateExportData(p.second); + units[ID]->updateExportData(p.second, dt); } } /* Set the units that are not present in the JSON as dead (probably have been destroyed) */ for (auto const& unit : units) - { unit.second->setAlive(unitJSONs.find(unit.first) != unitJSONs.end()); - } } void UnitsManager::updateMissionData(json::value missionData) @@ -148,35 +142,26 @@ void UnitsManager::updateMissionData(json::value missionData) /* Update all units */ for (auto const& p : units) { - int ID = p.first; + unsigned int ID = p.first; if (missionData.has_field(to_wstring(ID))) - { p.second->updateMissionData(missionData[to_wstring(ID)]); - } } } void UnitsManager::runAILoop() { /* Run the AI Loop on all units */ for (auto const& unit : units) - { unit.second->runAILoop(); - } } -void UnitsManager::getData(json::value& answer, long long time) +string UnitsManager::getUnitData(stringstream &ss, unsigned long long time) { - auto unitsJson = json::value::object(); for (auto const& p : units) - { - auto unitJson = p.second->getData(time); - if (unitJson.size() > 0) - unitsJson[to_wstring(p.first)] = p.second->getData(time); - } - answer[L"units"] = unitsJson; + p.second->getData(ss, time); + return to_base64(ss.str()); } -void UnitsManager::deleteUnit(int ID, bool explosion) +void UnitsManager::deleteUnit(unsigned int ID, bool explosion) { if (getUnit(ID) != nullptr) { @@ -185,7 +170,7 @@ void UnitsManager::deleteUnit(int ID, bool explosion) } } -void UnitsManager::acquireControl(int ID) { +void UnitsManager::acquireControl(unsigned int ID) { Unit* unit = getUnit(ID); if (unit != nullptr) { for (auto const& groupMember : getGroupMembers(unit->getGroupName())) { diff --git a/src/core/src/weapon.cpp b/src/core/src/weapon.cpp index 7705b710..64a1850a 100644 --- a/src/core/src/weapon.cpp +++ b/src/core/src/weapon.cpp @@ -13,21 +13,21 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Weapon */ -Weapon::Weapon(json::value json, int ID) : Unit(json, ID) +Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID) { }; /* Missile */ -Missile::Missile(json::value json, int ID) : Weapon(json, ID) +Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID) { log("New Missile created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); + setCategory("Missile"); }; /* Bomb */ -Bomb::Bomb(json::value json, int ID) : Weapon(json, ID) +Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID) { log("New Bomb created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); + setCategory("Bomb"); }; \ No newline at end of file diff --git a/src/dcstools/include/dcstools.h b/src/dcstools/include/dcstools.h index db8ff519..5547f686 100644 --- a/src/dcstools/include/dcstools.h +++ b/src/dcstools/include/dcstools.h @@ -5,8 +5,8 @@ void DllExport LogInfo(lua_State* L, string message); void DllExport LogWarning(lua_State* L, string message); void DllExport LogError(lua_State* L, string message); -void DllExport Log(lua_State* L, string message, int level); +void DllExport Log(lua_State* L, string message, unsigned int level); int DllExport dostring_in(lua_State* L, string target, string command); -map DllExport getAllUnits(lua_State* L); -int DllExport TACANChannelToFrequency(int channel, wstring XY); +map DllExport getAllUnits(lua_State* L); +unsigned int DllExport TACANChannelToFrequency(unsigned int channel, char XY); diff --git a/src/dcstools/src/dcstools.cpp b/src/dcstools/src/dcstools.cpp index 64184a98..515d9e7e 100644 --- a/src/dcstools/src/dcstools.cpp +++ b/src/dcstools/src/dcstools.cpp @@ -42,7 +42,7 @@ void LogError(lua_State* L, string message) Log(L, message, errorLevel); } -void Log(lua_State* L, string message, int level) +void Log(lua_State* L, string message, unsigned int level) { STACK_INIT; @@ -56,10 +56,10 @@ void Log(lua_State* L, string message, int level) STACK_CLEAN; } -map getAllUnits(lua_State* L) +map getAllUnits(lua_State* L) { - int res = 0; - map units; + unsigned int res = 0; + map units; STACK_INIT; @@ -83,7 +83,8 @@ map getAllUnits(lua_State* L) lua_pushnil(L); while (lua_next(L, 2) != 0) { - int ID = lua_tonumber(L, -2); + unsigned int ID = lua_tonumber(L, -2); + // TODO more efficient method can be used, converting all the lua data to a json object may be overkill units[ID] = luaTableToJSON(L, -1); STACK_POP(1) } @@ -103,8 +104,8 @@ int dostring_in(lua_State* L, string target, string command) return lua_pcall(L, 2, 0, 0); } -int TACANChannelToFrequency(int channel, wstring XY) +unsigned int TACANChannelToFrequency(unsigned int channel, char XY) { - int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087: 961; + unsigned int basef = (XY == 'X' && channel > 63) || (XY == 'Y' && channel < 64) ? 1087 : 961; return (basef + channel) * 1000000; } \ No newline at end of file diff --git a/src/logger/include/interface.h b/src/logger/include/interface.h index f5dbe800..bc65f27d 100644 --- a/src/logger/include/interface.h +++ b/src/logger/include/interface.h @@ -3,4 +3,4 @@ void DllExport log(const std::string& sMessage); void DllExport log(const std::wstring& sMessage); -void DllExport getLogsJSON(json::value& json, int logsNumber = NULL); +void DllExport getLogsJSON(json::value& json, unsigned int logsNumber = NULL); diff --git a/src/logger/include/logger.h b/src/logger/include/logger.h index 20e17d71..6de74ec8 100644 --- a/src/logger/include/logger.h +++ b/src/logger/include/logger.h @@ -7,7 +7,7 @@ class Logger public: void log(const string& sMessage); void log(const wstring& sMessage); - void toJSON(json::value& json, int logsNumber = NULL); + void toJSON(json::value& json, unsigned int logsNumber = NULL); static Logger* GetLogger(); diff --git a/src/logger/src/interface.cpp b/src/logger/src/interface.cpp index 77c743b5..dd1200e0 100644 --- a/src/logger/src/interface.cpp +++ b/src/logger/src/interface.cpp @@ -14,7 +14,7 @@ void log(const wstring& message) LOGGER->log(message); } -void getLogsJSON(json::value& json, int logsNumber) +void getLogsJSON(json::value& json, unsigned int logsNumber) { LOGGER->toJSON(json, logsNumber); } \ No newline at end of file diff --git a/src/logger/src/logger.cpp b/src/logger/src/logger.cpp index 4c9810d3..cb3174ed 100644 --- a/src/logger/src/logger.cpp +++ b/src/logger/src/logger.cpp @@ -32,10 +32,10 @@ void Logger::Close() m_Logfile.close(); } -void Logger::toJSON(json::value& json, int logsNumber) +void Logger::toJSON(json::value& json, unsigned int logsNumber) { lock_guard guard(mutexLock); - int i = 0; + unsigned int i = 0; for (auto itr = m_logs.end(); itr != m_logs.begin(); --itr) { json[to_wstring(m_logs.size() - 1 - i)] = json::value::string(to_wstring(*itr)); diff --git a/src/shared/include/defines.h b/src/shared/include/defines.h index 9289e51d..de0a36ba 100644 --- a/src/shared/include/defines.h +++ b/src/shared/include/defines.h @@ -2,12 +2,12 @@ #define VERSION "v0.2.1" #define LOG_NAME "Olympus_log.txt" -#define REST_ADDRESS L"http://localhost:30000" -#define REST_URI L"olympus" -#define UNITS_URI L"units" -#define LOGS_URI L"logs" -#define AIRBASES_URI L"airbases" -#define BULLSEYE_URI L"bullseyes" -#define MISSION_URI L"mission" +#define REST_ADDRESS "http://localhost:30000" +#define REST_URI "olympus" +#define UNITS_URI "units" +#define LOGS_URI "logs" +#define AIRBASES_URI "airbases" +#define BULLSEYE_URI "bullseyes" +#define MISSION_URI "mission" #define UPDATE_TIME_INTERVAL 0.25 \ No newline at end of file diff --git a/src/shared/include/framework.h b/src/shared/include/framework.h index c1b6de88..60157cb4 100644 --- a/src/shared/include/framework.h +++ b/src/shared/include/framework.h @@ -20,6 +20,7 @@ #include #include #include +#include #include using namespace std; diff --git a/src/utils/include/utils.h b/src/utils/include/utils.h index 4534a8de..16b16c68 100644 --- a/src/utils/include/utils.h +++ b/src/utils/include/utils.h @@ -1,6 +1,7 @@ #pragma once #include "framework.h" +#pragma pack(push, 1) struct Coords { double lat = 0; double lng = 0; @@ -12,22 +13,24 @@ struct Offset { double y = 0; double z = 0; }; +#pragma pack(pop) // Get current date/time, format is YYYY-MM-DD.HH:mm:ss const DllExport std::string CurrentDateTime(); std::wstring DllExport to_wstring(const std::string& str); +std::string DllExport to_string(json::value& value); std::string DllExport to_string(const std::wstring& wstr); std::string DllExport random_string(size_t length); bool DllExport operator== (const Coords& a, const Coords& b); bool DllExport operator!= (const Coords& a, const Coords& b); -bool DllExport operator== (const Coords& a, const int& b); -bool DllExport operator!= (const Coords& a, const int& b); +bool DllExport operator== (const Coords& a, const double& b); +bool DllExport operator!= (const Coords& a, const double& b); bool DllExport operator== (const Offset& a, const Offset& b); bool DllExport operator!= (const Offset& a, const Offset& b); -bool DllExport operator== (const Offset& a, const int& b); -bool DllExport operator!= (const Offset& a, const int& b); +bool DllExport operator== (const Offset& a, const double& b); +bool DllExport operator!= (const Offset& a, const double& b); double DllExport knotsToMs(const double knots); double DllExport msToKnots(const double ms); diff --git a/src/utils/src/utils.cpp b/src/utils/src/utils.cpp index 9d91fbe7..f6b0a556 100644 --- a/src/utils/src/utils.cpp +++ b/src/utils/src/utils.cpp @@ -14,12 +14,16 @@ const std::string CurrentDateTime() std::wstring to_wstring(const std::string& str) { - int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + unsigned int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (unsigned int)str.size(), NULL, 0); std::wstring wstrTo(size_needed, 0); - MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed); + MultiByteToWideChar(CP_UTF8, 0, &str[0], (unsigned int)str.size(), &wstrTo[0], size_needed); return wstrTo; } +std::string to_string(json::value& value) { + return to_string(value.as_string()); +} + std::string to_string(const std::wstring& wstr) { if (wstr.empty()) @@ -27,14 +31,14 @@ std::string to_string(const std::wstring& wstr) return ""; } - const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (int)wstr.size(), nullptr, 0, nullptr, nullptr); + const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (unsigned int)wstr.size(), nullptr, 0, nullptr, nullptr); if (size_needed <= 0) { throw std::runtime_error("WideCharToMultiByte() failed: " + std::to_string(size_needed)); } std::string result(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (int)wstr.size(), &result.at(0), size_needed, nullptr, nullptr); + WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (unsigned int)wstr.size(), &result.at(0), size_needed, nullptr, nullptr); return result; } @@ -56,13 +60,13 @@ std::string random_string(size_t length) bool operator== (const Coords& a, const Coords& b) { return a.lat == b.lat && a.lng == b.lng && a.alt == b.alt; } bool operator!= (const Coords& a, const Coords& b) { return !(a == b); } -bool operator== (const Coords& a, const int& b) { return a.lat == b && a.lng == b && a.alt == b; } -bool operator!= (const Coords& a, const int& b) { return !(a == b); } +bool operator== (const Coords& a, const double& b) { return a.lat == b && a.lng == b && a.alt == b; } +bool operator!= (const Coords& a, const double& b) { return !(a == b); } bool operator== (const Offset& a, const Offset& b) { return a.x == b.x && a.y == b.y && a.z == b.z; } bool operator!= (const Offset& a, const Offset& b) { return !(a == b); } -bool operator== (const Offset& a, const int& b) { return a.x == b && a.y == b && a.z == b; } -bool operator!= (const Offset& a, const int& b) { return !(a == b); } +bool operator== (const Offset& a, const double& b) { return a.x == b && a.y == b && a.z == b; } +bool operator!= (const Offset& a, const double& b) { return !(a == b); } double knotsToMs(const double knots) { @@ -79,4 +83,4 @@ double ftToM(const double ft) { double mToFt(const double m) { return m / 0.3048; -} \ No newline at end of file +} diff --git a/third-party/base64/include/base64.hpp b/third-party/base64/include/base64.hpp index adcf1c04..9e3d1565 100644 --- a/third-party/base64/include/base64.hpp +++ b/third-party/base64/include/base64.hpp @@ -6,76 +6,82 @@ namespace base64 { -inline std::string get_base64_chars() { - static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - return base64_chars; -} + inline std::string get_base64_chars() { + static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return base64_chars; + } + + inline std::string to_base64(const char* data, size_t size) { + int counter = 0; + uint32_t bit_stream = 0; + const std::string base64_chars = get_base64_chars(); + std::string encoded; + encoded.reserve(ceil(4.0 / 3.0 * size)); + int offset = 0; + for (unsigned int idx = 0; idx < size; idx++) { + unsigned char c = data[idx]; + auto num_val = static_cast(c); + offset = 16 - counter % 3 * 8; + bit_stream += num_val << offset; + if (offset == 16) { + encoded += base64_chars.at(bit_stream >> 18 & 0x3f); + } + if (offset == 8) { + encoded += base64_chars.at(bit_stream >> 12 & 0x3f); + } + if (offset == 0 && counter != 3) { + encoded += base64_chars.at(bit_stream >> 6 & 0x3f); + encoded += base64_chars.at(bit_stream & 0x3f); + bit_stream = 0; + } + counter++; + } + if (offset == 16) { + encoded += base64_chars.at(bit_stream >> 12 & 0x3f); + encoded += "=="; + } + if (offset == 8) { + encoded += base64_chars.at(bit_stream >> 6 & 0x3f); + encoded += '='; + } + return encoded; + } -inline std::string to_base64(std::string const &data) { - int counter = 0; - uint32_t bit_stream = 0; - const std::string base64_chars = get_base64_chars(); - std::string encoded; - int offset = 0; - for (unsigned char c : data) { - auto num_val = static_cast(c); - offset = 16 - counter % 3 * 8; - bit_stream += num_val << offset; - if (offset == 16) { - encoded += base64_chars.at(bit_stream >> 18 & 0x3f); - } - if (offset == 8) { - encoded += base64_chars.at(bit_stream >> 12 & 0x3f); - } - if (offset == 0 && counter != 3) { - encoded += base64_chars.at(bit_stream >> 6 & 0x3f); - encoded += base64_chars.at(bit_stream & 0x3f); - bit_stream = 0; - } - counter++; - } - if (offset == 16) { - encoded += base64_chars.at(bit_stream >> 12 & 0x3f); - encoded += "=="; - } - if (offset == 8) { - encoded += base64_chars.at(bit_stream >> 6 & 0x3f); - encoded += '='; - } - return encoded; -} - -inline std::string from_base64(std::string const &data) { - int counter = 0; - uint32_t bit_stream = 0; - std::string decoded; - int offset = 0; - const std::string base64_chars = get_base64_chars(); - for (unsigned char c : data) { - auto num_val = base64_chars.find(c); - if (num_val != std::string::npos) { - offset = 18 - counter % 4 * 6; - bit_stream += num_val << offset; - if (offset == 12) { - decoded += static_cast(bit_stream >> 16 & 0xff); - } - if (offset == 6) { - decoded += static_cast(bit_stream >> 8 & 0xff); - } - if (offset == 0 && counter != 4) { - decoded += static_cast(bit_stream & 0xff); - bit_stream = 0; - } - } else if (c != '=') { - return std::string(); - } - counter++; - } - return decoded; -} + inline std::string to_base64(std::string const& data) { + return to_base64(data.c_str(), data.length()); + } + inline std::string from_base64(std::string const& data) { + int counter = 0; + uint32_t bit_stream = 0; + std::string decoded; + int offset = 0; + const std::string base64_chars = get_base64_chars(); + for (unsigned char c : data) { + auto num_val = base64_chars.find(c); + if (num_val != std::string::npos) { + offset = 18 - counter % 4 * 6; + bit_stream += num_val << offset; + if (offset == 12) { + decoded += static_cast(bit_stream >> 16 & 0xff); + } + if (offset == 6) { + decoded += static_cast(bit_stream >> 8 & 0xff); + } + if (offset == 0 && counter != 4) { + decoded += static_cast(bit_stream & 0xff); + bit_stream = 0; + } + } + else if (c != '=') { + return std::string(); + } + counter++; + } + return decoded; + } } #endif // BASE_64_HPP \ No newline at end of file