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); const λ1 = deg2rad(lon1); // φ, λ in radians const λ2 = deg2rad(lon2); const y = Math.sin(λ2 - λ1) * Math.cos(φ2); const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1); const θ = Math.atan2(y, x); const brng = (rad2deg(θ) + 360) % 360; // in degrees return brng; } export function distance(lat1: number, lon1: number, lat2: number, lon2: number) { const R = 6371e3; // metres const φ1 = deg2rad(lat1); // φ, λ in radians const φ2 = deg2rad(lat2); const Δφ = deg2rad(lat2 - lat1); const Δλ = deg2rad(lon2 - lon1); const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; // in metres return d; } export function coordinatesFromBearingAndDistance(lat1: number, lon1: number, brng: number, dist: number) { const R = 6371e3; // metres const φ1 = deg2rad(lat1); // φ, λ in radians const λ1 = deg2rad(lon1); const φ2 = Math.asin( Math.sin(φ1)*Math.cos(dist/R) + Math.cos(φ1)*Math.sin(dist/R)*Math.cos(brng) ); const λ2 = λ1 + Math.atan2(Math.sin(brng)*Math.sin(dist/R)*Math.cos(φ1), Math.cos(dist/R)-Math.sin(φ1)*Math.sin(φ2)); return {lat: rad2deg(φ2), lng: rad2deg(λ2)}; } export function ConvertDDToDMS(D: number, lng: boolean) { var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N"; var deg = 0 | (D < 0 ? (D = -D) : D); var min = 0 | (((D += 1e-9) % 1) * 60); var sec = (0 | (((D * 60) % 1) * 6000)) / 100; var dec = Math.round((sec - Math.floor(sec)) * 100); var sec = Math.floor(sec); if (lng) return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; else return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\""; } export function dataPointMap( container:HTMLElement, data:any) { Object.keys( data ).forEach( ( key ) => { const val = "" + data[ key ]; // Ensure a string container.querySelectorAll( `[data-point="${key}"]`).forEach( el => { // We could probably have options here if ( el instanceof HTMLInputElement ) { el.value = val; } else if ( el instanceof HTMLElement ) { el.innerText = val; } }); }); } export function deg2rad(deg: number) { var pi = Math.PI; return deg * (pi / 180); } export function rad2deg(rad: number) { var pi = Math.PI; return rad / (pi / 180); } export function generateUUIDv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } export function keyEventWasInInput( event:KeyboardEvent ) { const target = event.target; return ( target instanceof HTMLElement && ( [ "INPUT", "TEXTAREA" ].includes( target.nodeName ) ) ); } export function reciprocalHeading(heading: number): number { return heading > 180? heading - 180: heading + 180; } export const zeroAppend = function (num: number, places: number, decimal: boolean = false) { var string = decimal? num.toFixed(2): String(num); while (string.length < places) { string = "0" + string; } return string; } export const zeroPad = function (num: number, places: number) { var string = String(num); while (string.length < places) { string += "0"; } return string; } export function similarity(s1: string, s2: string) { var longer = s1; var shorter = s2; if (s1.length < s2.length) { longer = s2; shorter = s1; } var longerLength = longer.length; if (longerLength == 0) { return 1.0; } return (longerLength - editDistance(longer, shorter)) / longerLength; } export function editDistance(s1: string, s2: string) { s1 = s1.toLowerCase(); s2 = s2.toLowerCase(); var costs = new Array(); for (var i = 0; i <= s1.length; i++) { var lastValue = i; for (var j = 0; j <= s2.length; j++) { if (i == 0) costs[j] = j; else { if (j > 0) { var newValue = costs[j - 1]; if (s1.charAt(i - 1) != s2.charAt(j - 1)) newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; costs[j - 1] = lastValue; lastValue = newValue; } } } if (i > 0) costs[s2.length] = lastValue; } return costs[s2.length]; } export function latLngToMercator(lat: number, lng: number): {x: number, y: number} { var rMajor = 6378137; //Equatorial Radius, WGS84 var shift = Math.PI * rMajor; var x = lng * shift / 180; var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180); y = y * shift / 180; return {x: x, y: y}; } export function mercatorToLatLng(x: number, y: number) { var rMajor = 6378137; //Equatorial Radius, WGS84 var shift = Math.PI * rMajor; var lng = x / shift * 180.0; var lat = y / shift * 180.0; lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); return { lng: lng, lat: lat }; } export function createDivWithClass(className: string) { var el = document.createElement("div"); el.classList.add(className); return el; } export function knotsToMs(knots: number) { return knots / 1.94384; } export function msToKnots(ms: number) { return ms * 1.94384; } export function ftToM(ft: number) { return ft * 0.3048; } export function mToFt(m: number) { return m / 0.3048; } export function mToNm(m: number) { return m * 0.000539957; } 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 ""; }