303 lines
9.4 KiB
TypeScript

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 "";
}