mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge pull request #882 from Pax1601/856-camera-control-produces-jerky-movement-on-browsers-different-from-chrome
Fixed camera control on Firefox, added slider to control zoom level, …
This commit is contained in:
commit
61fb80d67f
@ -28,6 +28,7 @@
|
||||
--accent-light-red: #F5B6B6;
|
||||
|
||||
--background-grey: #3d4651;
|
||||
--background-dark-grey: #35393d;
|
||||
--background-slate-blue: #363c43;
|
||||
--background-offwhite: #f2f2f3;
|
||||
--background-steel: #202831;
|
||||
|
||||
@ -256,6 +256,7 @@ export const SHOW_UNIT_CONTACTS = "Show selected units contact lines";
|
||||
export const SHOW_UNIT_PATHS = "Show selected unit paths";
|
||||
export const SHOW_UNIT_TARGETS = "Show selected unit targets";
|
||||
export const DCS_LINK_PORT = "DCS Camera link port";
|
||||
export const DCS_LINK_RATIO = "DCS Camera zoom";
|
||||
|
||||
export enum DataIndexes {
|
||||
startOfData = 0,
|
||||
|
||||
@ -104,7 +104,7 @@ export class Slider extends Control {
|
||||
|
||||
/* Update the position of the slider */
|
||||
var percentValue = parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * 90 + 5;
|
||||
this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-grey) ${percentValue}%, var(--background-grey) 100%)`
|
||||
this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-dark-grey) ${percentValue}%, var(--background-dark-grey) 100%)`
|
||||
}
|
||||
this.setActive(true);
|
||||
}
|
||||
|
||||
@ -7,12 +7,12 @@ import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Airbase } from "../mission/airbase";
|
||||
import { Unit } from "../unit/unit";
|
||||
import { bearing, createCheckboxOption, createTextInputOption, deg2rad, getGroundElevation, polyContains } from "../other/utils";
|
||||
import { bearing, createCheckboxOption, createSliderInputOption, createTextInputOption, deg2rad, getGroundElevation, polyContains } from "../other/utils";
|
||||
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { SVGInjector } from '@tanem/svg-injector'
|
||||
import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS, DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre } from "../constants/constants";
|
||||
import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS, DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre, DCS_LINK_RATIO } from "../constants/constants";
|
||||
import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./coalitionarea/drawingcursor";
|
||||
@ -103,6 +103,9 @@ export class Map extends L.Map {
|
||||
#visibilityOptions: { [key: string]: boolean | string | number } = {}
|
||||
#hiddenTypes: string[] = [];
|
||||
#layerName: string = "";
|
||||
#cameraOptionsXmlHttp: XMLHttpRequest | null = null;
|
||||
#bradcastPositionXmlHttp: XMLHttpRequest | null = null;
|
||||
#cameraZoomRatio: number = 1.0;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -211,6 +214,14 @@ export class Map extends L.Map {
|
||||
document.addEventListener("mapOptionsChanged", () => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
|
||||
this.#cameraControlPort = this.getVisibilityOptions()[DCS_LINK_PORT] as number;
|
||||
this.#cameraZoomRatio = 50 / (20 + (this.getVisibilityOptions()[DCS_LINK_RATIO] as number));
|
||||
|
||||
if (this.#slaveDCSCamera) {
|
||||
this.#broadcastPosition();
|
||||
window.setTimeout(() => {
|
||||
this.#broadcastPosition();
|
||||
}, 500); // DCS does not always apply the altitude correctly at the first set when changing map type
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("configLoaded", () => {
|
||||
@ -254,6 +265,7 @@ export class Map extends L.Map {
|
||||
|
||||
/* Create the checkboxes to select the advanced visibility options */
|
||||
this.addVisibilityOption(DCS_LINK_PORT, 3003, { min: 1024, max: 65535 });
|
||||
this.addVisibilityOption(DCS_LINK_RATIO, 50, { min: 0, max: 100, slider: true });
|
||||
|
||||
this.#mapVisibilityOptionsDropdown.addHorizontalDivider();
|
||||
|
||||
@ -270,12 +282,16 @@ export class Map extends L.Map {
|
||||
|
||||
addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { [key: string]: any }) {
|
||||
this.#visibilityOptions[option] = defaultValue;
|
||||
if (typeof defaultValue === 'boolean')
|
||||
if (typeof defaultValue === 'boolean') {
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue as boolean, (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
|
||||
else if (typeof defaultValue === 'number')
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
|
||||
else
|
||||
} else if (typeof defaultValue === 'number') {
|
||||
if (options !== undefined && options?.slider === true)
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createSliderInputOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
|
||||
else
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
|
||||
} else {
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue, 'text', (ev: any) => { this.#setVisibilityOption(option, ev); }, options));
|
||||
}
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
@ -593,19 +609,43 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
setSlaveDCSCamera(newSlaveDCSCamera: boolean) {
|
||||
// if (this.#slaveDCSCameraAvailable || !newSlaveDCSCamera) { // Commented to experiment with usability
|
||||
this.#slaveDCSCamera = newSlaveDCSCamera;
|
||||
let button = document.getElementById("camera-link-control");
|
||||
button?.classList.toggle("off", !newSlaveDCSCamera);
|
||||
if (newSlaveDCSCamera)
|
||||
this.#slaveDCSCamera = newSlaveDCSCamera;
|
||||
let button = document.getElementById("camera-link-control");
|
||||
button?.classList.toggle("off", !newSlaveDCSCamera);
|
||||
if (this.#slaveDCSCamera) {
|
||||
this.#broadcastPosition();
|
||||
window.setTimeout(() => {
|
||||
this.#broadcastPosition();
|
||||
// }
|
||||
}, 500); // DCS does not always apply the altitude correctly at the first set when changing map type
|
||||
}
|
||||
}
|
||||
|
||||
setCameraControlMode(newCameraControlMode: string) {
|
||||
this.#cameraControlMode = newCameraControlMode;
|
||||
if (this.#slaveDCSCamera)
|
||||
if (this.#slaveDCSCamera) {
|
||||
this.#broadcastPosition();
|
||||
window.setTimeout(() => {
|
||||
this.#broadcastPosition();
|
||||
}, 500); // DCS does not always apply the altitude correctly at the first set when changing map type
|
||||
}
|
||||
}
|
||||
|
||||
increaseCameraZoom() {
|
||||
const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`);
|
||||
if (slider instanceof HTMLInputElement) {
|
||||
slider.value = String(Math.min(Number(slider.max), Number(slider.value) + 10));
|
||||
slider.dispatchEvent(new Event('input'));
|
||||
slider.dispatchEvent(new Event('mouseup'));
|
||||
}
|
||||
}
|
||||
|
||||
decreaseCameraZoom() {
|
||||
const slider = document.querySelector(`label[title="${DCS_LINK_RATIO}"] input`);
|
||||
if (slider instanceof HTMLInputElement) {
|
||||
slider.value = String(Math.max(Number(slider.min), Number(slider.value) - 10));
|
||||
slider.dispatchEvent(new Event('input'));
|
||||
slider.dispatchEvent(new Event('mouseup'));
|
||||
}
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
@ -791,21 +831,24 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
#broadcastPosition() {
|
||||
if (this.#bradcastPositionXmlHttp?.readyState !== 4 && this.#bradcastPositionXmlHttp !== null)
|
||||
return
|
||||
|
||||
getGroundElevation(this.getCenter(), (response: string) => {
|
||||
var groundElevation: number | null = null;
|
||||
try {
|
||||
groundElevation = parseFloat(response);
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("PUT", `http://127.0.0.1:${this.#cameraControlPort}`);
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
this.#bradcastPositionXmlHttp = new XMLHttpRequest();
|
||||
/* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the
|
||||
browser if it first tries to send the request on the IPv6 address for localhost */
|
||||
this.#bradcastPositionXmlHttp.open("POST", `http://127.0.0.1:${this.#cameraControlPort}`);
|
||||
|
||||
const C = 40075016.686;
|
||||
let mpp = C * Math.cos(deg2rad(this.getCenter().lat)) / Math.pow(2, this.getZoom() + 8);
|
||||
let d = mpp * 1920;
|
||||
let alt = d / 2 * 1 / Math.tan(deg2rad(40));
|
||||
if (alt > 100000)
|
||||
alt = 100000;
|
||||
xmlHttp.send(JSON.stringify({ lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation, mode: this.#cameraControlMode }));
|
||||
let alt = d / 2 * 1 / Math.tan(deg2rad(40)) * this.#cameraZoomRatio;
|
||||
alt = Math.min(alt, 50000);
|
||||
this.#bradcastPositionXmlHttp.send(JSON.stringify({ lat: this.getCenter().lat, lng: this.getCenter().lng, alt: alt + groundElevation, mode: this.#cameraControlMode }));
|
||||
} catch {
|
||||
console.warn("broadcastPosition: could not retrieve ground elevation")
|
||||
}
|
||||
@ -1002,23 +1045,30 @@ export class Map extends L.Map {
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the camera control plugin is available. Right now this will only change the color of the button, no changes in functionality */
|
||||
#checkCameraPort(){
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`);
|
||||
xmlHttp.onload = (res: any) => {
|
||||
if (xmlHttp.status == 200)
|
||||
this.#setSlaveDCSCameraAvailable(true);
|
||||
else
|
||||
this.#setSlaveDCSCameraAvailable(false);
|
||||
};
|
||||
xmlHttp.onerror = (res: any) => {
|
||||
if (this.#cameraOptionsXmlHttp?.readyState !== 4)
|
||||
this.#cameraOptionsXmlHttp?.abort()
|
||||
|
||||
this.#cameraOptionsXmlHttp = new XMLHttpRequest();
|
||||
|
||||
/* Using 127.0.0.1 instead of localhost because the LuaSocket version used in DCS only listens to IPv4. This avoids the lag caused by the
|
||||
browser if it first tries to send the request on the IPv6 address for localhost */
|
||||
this.#cameraOptionsXmlHttp.open("OPTIONS", `http://127.0.0.1:${this.#cameraControlPort}`);
|
||||
this.#cameraOptionsXmlHttp.onload = (res: any) => {
|
||||
if (this.#cameraOptionsXmlHttp !== null && this.#cameraOptionsXmlHttp.status == 204)
|
||||
this.#setSlaveDCSCameraAvailable(true);
|
||||
else
|
||||
this.#setSlaveDCSCameraAvailable(false);
|
||||
}
|
||||
xmlHttp.ontimeout = (res: any) => {
|
||||
this.#setSlaveDCSCameraAvailable(false);
|
||||
}
|
||||
xmlHttp.timeout = 500;
|
||||
xmlHttp.send("");
|
||||
};
|
||||
this.#cameraOptionsXmlHttp.onerror = (res: any) => {
|
||||
this.#setSlaveDCSCameraAvailable(false);
|
||||
}
|
||||
this.#cameraOptionsXmlHttp.ontimeout = (res: any) => {
|
||||
this.#setSlaveDCSCameraAvailable(false);
|
||||
}
|
||||
this.#cameraOptionsXmlHttp.timeout = 500;
|
||||
this.#cameraOptionsXmlHttp.send("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -368,6 +368,24 @@ export class OlympusApp {
|
||||
"context": "olympus",
|
||||
"ctrlKey": false,
|
||||
"shiftKey": false
|
||||
}).addKeyboardShortcut("increaseCameraZoom", {
|
||||
"altKey": true,
|
||||
"callback": () => {
|
||||
this.getMap().increaseCameraZoom();
|
||||
},
|
||||
"code": "Equal",
|
||||
"context": "olympus",
|
||||
"ctrlKey": false,
|
||||
"shiftKey": false
|
||||
}).addKeyboardShortcut("decreaseCameraZoom", {
|
||||
"altKey": true,
|
||||
"callback": () => {
|
||||
this.getMap().decreaseCameraZoom();
|
||||
},
|
||||
"code": "Minus",
|
||||
"context": "olympus",
|
||||
"ctrlKey": false,
|
||||
"shiftKey": false
|
||||
});
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
|
||||
@ -9,9 +9,10 @@ import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reaction
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
|
||||
import { DateAndTime, UnitBlueprint } from "../interfaces";
|
||||
import { Slider } from "../controls/slider";
|
||||
|
||||
// comment
|
||||
const usng = require( "usng.js" );
|
||||
const usng = require("usng.js");
|
||||
|
||||
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
@ -45,8 +46,8 @@ export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: numbe
|
||||
const R = 6371e3; // metres
|
||||
const φ1 = deg2rad(lat); // φ, λ in radians
|
||||
const λ1 = deg2rad(lon);
|
||||
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));
|
||||
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 new LatLng(rad2deg(φ2), rad2deg(λ2));
|
||||
}
|
||||
@ -64,14 +65,14 @@ export function ConvertDDToDMS(D: number, lng: boolean) {
|
||||
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 => {
|
||||
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 ) {
|
||||
if (el instanceof HTMLInputElement) {
|
||||
el.value = val;
|
||||
} else if ( el instanceof HTMLElement ) {
|
||||
} else if (el instanceof HTMLElement) {
|
||||
el.innerText = val;
|
||||
}
|
||||
});
|
||||
@ -89,19 +90,19 @@ export function rad2deg(rad: number) {
|
||||
}
|
||||
|
||||
export function generateUUIDv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
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 ) {
|
||||
export function keyEventWasInInput(event: KeyboardEvent) {
|
||||
const target = event.target;
|
||||
return ( target instanceof HTMLElement && ( [ "INPUT", "TEXTAREA" ].includes( target.nodeName ) ) );
|
||||
return (target instanceof HTMLElement && (["INPUT", "TEXTAREA"].includes(target.nodeName)));
|
||||
}
|
||||
|
||||
export function reciprocalHeading(heading: number): number {
|
||||
return heading > 180? heading - 180: heading + 180;
|
||||
return heading > 180 ? heading - 180 : heading + 180;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,7 +114,7 @@ export function reciprocalHeading(heading: number): number {
|
||||
*
|
||||
* */
|
||||
export const zeroAppend = function (num: number, places: number, decimal: boolean = false) {
|
||||
var string = decimal? num.toFixed(2): String(num);
|
||||
var string = decimal ? num.toFixed(2) : String(num);
|
||||
while (string.length < places) {
|
||||
string = "0" + string;
|
||||
}
|
||||
@ -181,22 +182,22 @@ export type MGRS = {
|
||||
zoneNumber: string
|
||||
}
|
||||
|
||||
export function latLngToMGRS( lat:number, lng:number, precision:number = 4 ): MGRS | false {
|
||||
export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false {
|
||||
|
||||
if ( precision < 0 || precision > 6 ) {
|
||||
console.error( "latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision );
|
||||
if (precision < 0 || precision > 6) {
|
||||
console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision);
|
||||
return false;
|
||||
}
|
||||
|
||||
const mgrs = new usng.Converter().LLtoMGRS( lat, lng, precision );
|
||||
const match = mgrs.match( new RegExp( `^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$` ) );
|
||||
const easting = match[5].substr(0,match[5].length/2);
|
||||
const northing = match[5].substr(match[5].length/2);
|
||||
|
||||
let output:MGRS = {
|
||||
const mgrs = new usng.Converter().LLtoMGRS(lat, lng, precision);
|
||||
const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`));
|
||||
const easting = match[5].substr(0, match[5].length / 2);
|
||||
const northing = match[5].substr(match[5].length / 2);
|
||||
|
||||
let output: MGRS = {
|
||||
bandLetter: match[2],
|
||||
columnLetter: match[3],
|
||||
groups: [ match[1] + match[2], match[3] + match[4], easting, northing ],
|
||||
groups: [match[1] + match[2], match[3] + match[4], easting, northing],
|
||||
easting: easting,
|
||||
northing: northing,
|
||||
precision: precision,
|
||||
@ -204,32 +205,32 @@ export function latLngToMGRS( lat:number, lng:number, precision:number = 4 ): MG
|
||||
string: match[0],
|
||||
zoneNumber: match[1]
|
||||
}
|
||||
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
export function latLngToUTM( lat:number, lng:number ) {
|
||||
return new usng.Converter().LLtoUTM( lat, lng );
|
||||
export function latLngToUTM(lat: number, lng: number) {
|
||||
return new usng.Converter().LLtoUTM(lat, lng);
|
||||
}
|
||||
|
||||
export function latLngToMercator(lat: number, lng: number): {x: number, y: number} {
|
||||
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);
|
||||
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};
|
||||
|
||||
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;
|
||||
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 };
|
||||
return { lng: lng, lat: lat };
|
||||
}
|
||||
|
||||
export function createDivWithClass(className: string) {
|
||||
@ -267,21 +268,21 @@ export function nmToFt(nm: number) {
|
||||
}
|
||||
|
||||
export function polyContains(latlng: LatLng, polygon: Polygon) {
|
||||
var poly = polygon.toGeoJSON();
|
||||
var poly = polygon.toGeoJSON();
|
||||
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
|
||||
}
|
||||
|
||||
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 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 poly = polygon.toGeoJSON();
|
||||
var inside = turf.inside(turf.point([lng, lat]), poly);
|
||||
|
||||
if (inside) {
|
||||
@ -296,7 +297,7 @@ export function polygonArea(polygon: Polygon) {
|
||||
return turf.area(poly);
|
||||
}
|
||||
|
||||
export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string} ) {
|
||||
export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: { type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string }) {
|
||||
/* Start from all the unit blueprints in the database */
|
||||
var unitBlueprints = Object.values(unitDatabase.getBlueprints());
|
||||
|
||||
@ -315,13 +316,13 @@ export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?:
|
||||
|
||||
/* Keep only the units that have a range included in the requested values */
|
||||
if (options.ranges) {
|
||||
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
|
||||
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
|
||||
var rangeType = "";
|
||||
var range = unitBlueprint.acquisitionRange ;
|
||||
var range = unitBlueprint.acquisitionRange;
|
||||
if (range !== undefined) {
|
||||
if (range >= 0 && range < 10000)
|
||||
if (range >= 0 && range < 10000)
|
||||
rangeType = "Short range";
|
||||
else if (range >= 10000 && range < 100000)
|
||||
else if (range >= 10000 && range < 100000)
|
||||
rangeType = "Medium range";
|
||||
else if (range >= 100000 && range < 999999)
|
||||
rangeType = "Long range";
|
||||
@ -332,15 +333,15 @@ export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: {type?:
|
||||
|
||||
/* Keep only the units that have an era included in the requested values */
|
||||
if (options.eras) {
|
||||
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
|
||||
return unitBlueprint.era? options.eras?.includes(unitBlueprint.era): true;
|
||||
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
|
||||
return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true;
|
||||
});
|
||||
}
|
||||
|
||||
/* Keep only the units that have the correct coalition, if selected */
|
||||
if (options.coalition) {
|
||||
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
|
||||
return (unitBlueprint.coalition && unitBlueprint.coalition !== "")? options.coalition === unitBlueprint.coalition: true;
|
||||
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
|
||||
return (unitBlueprint.coalition && unitBlueprint.coalition !== "") ? options.coalition === unitBlueprint.coalition : true;
|
||||
});
|
||||
}
|
||||
|
||||
@ -353,7 +354,7 @@ export function getMarkerCategoryByName(name: string) {
|
||||
return "aircraft";
|
||||
else if (helicopterDatabase.getByName(name) != null)
|
||||
return "helicopter";
|
||||
else if (groundUnitDatabase.getByName(name) != null){
|
||||
else if (groundUnitDatabase.getByName(name) != null) {
|
||||
var type = groundUnitDatabase.getByName(name)?.type ?? "";
|
||||
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type))
|
||||
return "groundunit-sam";
|
||||
@ -362,7 +363,7 @@ export function getMarkerCategoryByName(name: string) {
|
||||
}
|
||||
else if (navyUnitDatabase.getByName(name) != null)
|
||||
return "navyunit";
|
||||
else
|
||||
else
|
||||
return "aircraft"; // TODO add other unit types
|
||||
}
|
||||
|
||||
@ -379,7 +380,7 @@ export function getUnitDatabaseByCategory(category: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCategoryBlueprintIconSVG(category:string, unitName:string) {
|
||||
export function getCategoryBlueprintIconSVG(category: string, unitName: string) {
|
||||
|
||||
const path = "/resources/theme/images/buttons/visibility/";
|
||||
|
||||
@ -398,35 +399,35 @@ export function base64ToBytes(base64: string) {
|
||||
}
|
||||
|
||||
export function enumToState(state: number) {
|
||||
if (state < states.length)
|
||||
if (state < states.length)
|
||||
return states[state];
|
||||
else
|
||||
else
|
||||
return states[0];
|
||||
}
|
||||
|
||||
export function enumToROE(ROE: number) {
|
||||
if (ROE < ROEs.length)
|
||||
if (ROE < ROEs.length)
|
||||
return ROEs[ROE];
|
||||
else
|
||||
else
|
||||
return ROEs[0];
|
||||
}
|
||||
|
||||
export function enumToReactionToThreat(reactionToThreat: number) {
|
||||
if (reactionToThreat < reactionsToThreat.length)
|
||||
if (reactionToThreat < reactionsToThreat.length)
|
||||
return reactionsToThreat[reactionToThreat];
|
||||
else
|
||||
else
|
||||
return reactionsToThreat[0];
|
||||
}
|
||||
|
||||
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
|
||||
if (emissionCountermeasure < emissionsCountermeasures.length)
|
||||
if (emissionCountermeasure < emissionsCountermeasures.length)
|
||||
return emissionsCountermeasures[emissionCountermeasure];
|
||||
else
|
||||
else
|
||||
return emissionsCountermeasures[0];
|
||||
}
|
||||
|
||||
export function enumToCoalition(coalitionID: number) {
|
||||
switch (coalitionID){
|
||||
switch (coalitionID) {
|
||||
case 0: return "neutral";
|
||||
case 1: return "red";
|
||||
case 2: return "blue";
|
||||
@ -435,7 +436,7 @@ export function enumToCoalition(coalitionID: number) {
|
||||
}
|
||||
|
||||
export function coalitionToEnum(coalition: string) {
|
||||
switch (coalition){
|
||||
switch (coalition) {
|
||||
case "neutral": return 0;
|
||||
case "red": return 1;
|
||||
case "blue": return 2;
|
||||
@ -463,7 +464,7 @@ export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
|
||||
return new Date(year, month, date.Day, time.h, time.m, time.s);
|
||||
}
|
||||
|
||||
export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => {}, options?:any) {
|
||||
export function createCheckboxOption(text: string, description: string, checked: boolean = true, callback: CallableFunction = (ev: any) => { }, options?: any) {
|
||||
options = {
|
||||
"disabled": false,
|
||||
"name": "",
|
||||
@ -477,11 +478,11 @@ export function createCheckboxOption(text: string, description: string, checked:
|
||||
label.title = description;
|
||||
var input = document.createElement("input");
|
||||
input.type = "checkbox";
|
||||
input.checked = checked;
|
||||
input.name = options.name;
|
||||
input.disabled = options.disabled;
|
||||
input.readOnly = options.readOnly;
|
||||
input.value = options.value;
|
||||
input.checked = checked;
|
||||
input.name = options.name;
|
||||
input.disabled = options.disabled;
|
||||
input.readOnly = options.readOnly;
|
||||
input.value = options.value;
|
||||
var span = document.createElement("span");
|
||||
span.innerText = text;
|
||||
label.appendChild(input);
|
||||
@ -504,7 +505,7 @@ export function getCheckboxOptions(dropdown: Dropdown) {
|
||||
return values;
|
||||
}
|
||||
|
||||
export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => {}, options?:any) {
|
||||
export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback: CallableFunction = (ev: any) => { }, options?: any) {
|
||||
options = {
|
||||
"disabled": false,
|
||||
"name": "",
|
||||
@ -516,15 +517,15 @@ export function createTextInputOption(text: string, description: string, initial
|
||||
var label = document.createElement("label");
|
||||
label.title = description;
|
||||
var input = document.createElement("input");
|
||||
input.type = type;
|
||||
input.name = options.name;
|
||||
input.disabled = options.disabled;
|
||||
input.readOnly = options.readOnly;
|
||||
input.type = type;
|
||||
input.name = options.name;
|
||||
input.disabled = options.disabled;
|
||||
input.readOnly = options.readOnly;
|
||||
if (options.min)
|
||||
input.min = options.min;
|
||||
if (options.max)
|
||||
input.max = options.max;
|
||||
input.value = initialValue;
|
||||
input.value = initialValue;
|
||||
input.style.width = "80px";
|
||||
var span = document.createElement("span");
|
||||
span.innerText = text;
|
||||
@ -543,6 +544,28 @@ export function createTextInputOption(text: string, description: string, initial
|
||||
return div as HTMLElement;
|
||||
}
|
||||
|
||||
export function createSliderInputOption(text: string, description: string, initialValue: number, callback: CallableFunction = (ev: any) => { }, options?: any) {
|
||||
var div = document.createElement("div");
|
||||
var label = document.createElement("label");
|
||||
label.title = description;
|
||||
var input = new Slider(null, options.min ?? 0, options.max ?? 100, "", (val: number) => {
|
||||
callback({currentTarget: {value: val}});
|
||||
});
|
||||
input.setValue(initialValue);
|
||||
input.getContainer()?.querySelector(".ol-data-grid")?.classList.add("hide");
|
||||
var span = document.createElement("span");
|
||||
span.innerText = text;
|
||||
span.style.width = "100%";
|
||||
span.style.margin = "auto";
|
||||
label.appendChild(span);
|
||||
label.appendChild(input.getContainer() as HTMLElement);
|
||||
label.style.display = "flex";
|
||||
label.style.alignContent = "center";
|
||||
label.style.width = "100%";
|
||||
div.appendChild(label);
|
||||
return div as HTMLElement;
|
||||
}
|
||||
|
||||
export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
|
||||
/* Get the ground elevation from the server endpoint */
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
@ -27,18 +27,18 @@ export class ShortcutManager extends Manager {
|
||||
|
||||
}
|
||||
|
||||
add( name: string, shortcut:any ) {
|
||||
console.error( "ShortcutManager:add() cannot be used. Use addKeyboardShortcut or addMouseShortcut." );
|
||||
add(name: string, shortcut: any) {
|
||||
console.error("ShortcutManager:add() cannot be used. Use addKeyboardShortcut or addMouseShortcut.");
|
||||
return this;
|
||||
}
|
||||
|
||||
addKeyboardShortcut( name:string, shortcutKeyboardOptions:ShortcutKeyboardOptions ) {
|
||||
super.add( name, new ShortcutKeyboard( shortcutKeyboardOptions ) );
|
||||
addKeyboardShortcut(name: string, shortcutKeyboardOptions: ShortcutKeyboardOptions) {
|
||||
super.add(name, new ShortcutKeyboard(shortcutKeyboardOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
addMouseShortcut( name:string, shortcutMouseOptions:ShortcutMouseOptions ) {
|
||||
super.add( name, new ShortcutMouse( shortcutMouseOptions ) );
|
||||
addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions) {
|
||||
super.add(name, new ShortcutMouse(shortcutMouseOptions));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ local _prevLuaExportStop = LuaExportStop
|
||||
|
||||
local server = nil
|
||||
local port = 3003
|
||||
local headers = "Access-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: PUT, OPTIONS\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\nKeep-Alive: timeout=2, max=100\r\nConnection: Keep-Alive\r\n\r\n"
|
||||
local headers = "Access-Control-Allow-Private-Network: true\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: *\r\nAccess-Control-Allow-Headers: *\r\nAccess-Control-Max-Age: 86400\r\nVary: Accept-Encoding, Origin\r\n\r\n"
|
||||
|
||||
function startTCPServer()
|
||||
log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'Starting TCP Server')
|
||||
@ -30,11 +30,13 @@ function receiveTCP()
|
||||
|
||||
if client then
|
||||
-- Set the timeout of the connection to 5ms
|
||||
client:settimeout(0)
|
||||
client:settimeout(0.005)
|
||||
client:setoption("tcp-nodelay", true)
|
||||
|
||||
local acc = ""
|
||||
local data = ""
|
||||
|
||||
log.write('OLYMPUSCAMERACONTROL.EXPORT.LUA', log.INFO, 'CONNECTION')
|
||||
|
||||
-- Start receiving data, accumulate it in acc
|
||||
while data ~= nil do
|
||||
@ -45,11 +47,11 @@ function receiveTCP()
|
||||
if data == "" then
|
||||
-- Is this an OPTIONS request?
|
||||
if string.find(acc, "OPTIONS") ~= nil then
|
||||
client:send("HTTP/1.1 200 OK\r\n" .. headers)
|
||||
client:send("HTTP/1.1 204 OK\r\n" .. headers)
|
||||
client:close()
|
||||
|
||||
-- Is this a PUT request?
|
||||
elseif string.find(acc, "PUT") ~= nil then
|
||||
elseif string.find(acc, "POST") ~= nil then
|
||||
-- Extract the length of the body
|
||||
local contentLength = string.match(acc, "Content%-Length: (%d+)")
|
||||
if contentLength ~= nil then
|
||||
@ -62,7 +64,7 @@ function receiveTCP()
|
||||
local mode = string.match(body, '"mode":%s*"(%a+)"%s*[},]')
|
||||
|
||||
if lat ~= nil and lng ~= nil then
|
||||
client:send("HTTP/1.1 200 OK\r\n" .. headers)
|
||||
client:send("HTTP/1.1 200 OK\r\n" .. "Content-Type: application/json\r\n" .. headers)
|
||||
|
||||
local position = {}
|
||||
position["lat"] = tonumber(lat)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user