gridtracker/package.nw/lib/arc.js
Tag Loomis 5469779198 Safety commit
Better processing indicator

Async load local adif files

BIGCTY update 0105

Baby step

another step

Lint 1

Removed debug and dead code

DO9KW

DO9KW II

Lint free safety commit

big baby step

Remove uneeded timer

more speed opt

Massive speed improvements

Lint

Fixed WSPR grid colors and dimming

Auto-mute on single or no audio devices.

lint

Chinese Google Maps and OAMS spot/decode metering

Decodes are small and once a minute.

Properly save *all* settings on language change

Proper method name and code reduction

Moved trackQSO to gtCommon in prep for log event

30 minutes inactivity

WSJT-X log event not a heavy hit to stat parsing

BIGCTY 0125

version bump for testing

A bigger hit on uls lookups, but they won't get stuck as un resolved this way

Dumped ULS websql database

Save lookupssettings and fix changelog

lint fix

whitespace?

roll version, changelog fix

Fix really old bug, always set the liveCallsigns with stations info

Moved eslint disabled code to bottom of file

Removed last traces of .rect, cleaned up vector layer accessors

Removed dead code

Local ADIF loads now add to startup log list and auto load when added via dialogs
2024-02-11 13:05:44 -08:00

352 lines
8.7 KiB
JavaScript

"use strict";
var D2R = Math.PI / 180;
var R2D = 180 / Math.PI;
var Coord = function (lon, lat)
{
this.lon = lon;
this.lat = lat;
this.x = D2R * lon;
this.y = D2R * lat;
};
Coord.prototype.view = function ()
{
return String(this.lon).slice(0, 4) + "," + String(this.lat).slice(0, 4);
};
Coord.prototype.antipode = function ()
{
var anti_lat = -1 * this.lat;
var anti_lon = this.lon < 0 ? 180 + this.lon : (180 - this.lon) * -1;
return new Coord(anti_lon, anti_lat);
};
var LineString = function ()
{
this.coords = [];
this.length = 0;
};
LineString.prototype.move_to = function (coord)
{
this.length++;
this.coords.push(coord);
};
var Arc = function (properties)
{
this.properties = properties || {};
this.geometries = [];
};
Arc.prototype.json = function ()
{
if (this.geometries.length <= 0)
{
return {
geometry: { type: "LineString", coordinates: null },
type: "Feature",
properties: this.properties
};
}
else if (this.geometries.length == 1)
{
return {
geometry: { type: "LineString", coordinates: this.geometries[0].coords },
type: "Feature",
properties: this.properties
};
}
else
{
var multiline = [];
for (var i = 0; i < this.geometries.length; i++)
{
multiline.push(this.geometries[i].coords);
}
return {
geometry: { type: "MultiLineString", coordinates: multiline },
type: "Feature",
properties: this.properties
};
}
};
// TODO - output proper multilinestring
Arc.prototype.wkt = function ()
{
var wkt_string = "";
var wkt = "LINESTRING(";
var collect = function (c)
{
wkt += c[0] + " " + c[1] + ",";
};
for (var i = 0; i < this.geometries.length; i++)
{
if (this.geometries[i].coords.length == 0)
{
return "LINESTRING(empty)";
}
else
{
var coords = this.geometries[i].coords;
coords.forEach(collect);
wkt_string += wkt.substring(0, wkt.length - 1) + ")";
}
}
return wkt_string;
};
/*
* http://en.wikipedia.org/wiki/Great-circle_distance
*
*/
var GreatCircle = function (start, end, properties)
{
if (!start || start.x == undefined || start.y == undefined)
{
throw new Error(
"GreatCircle constructor expects two args: start and end objects with x and y properties"
);
}
if (!end || end.x == undefined || end.y == undefined)
{
throw new Error(
"GreatCircle constructor expects two args: start and end objects with x and y properties"
);
}
this.start = new Coord(start.x, start.y);
this.end = new Coord(end.x, end.y);
this.properties = properties || {};
var w = this.start.x - this.end.x;
var h = this.start.y - this.end.y;
var z =
Math.pow(Math.sin(h / 2.0), 2) +
Math.cos(this.start.y) *
Math.cos(this.end.y) *
Math.pow(Math.sin(w / 2.0), 2);
this.g = 2.0 * Math.asin(Math.sqrt(z));
if (this.g == Math.PI)
{
throw new Error(
"it appears " +
this.start +
" and " +
this.end +
" are 'antipodal', e.g diametrically opposite, thus there is no single route but rather infinite"
);
}
else if (isNaN(this.g))
{
throw new Error(
"could not calculate great circle between " + start + " and " + end
);
}
};
/*
* http://williams.best.vwh.net/avform.htm#Intermediate
*/
GreatCircle.prototype.interpolate = function (f)
{
var A = Math.sin((1 - f) * this.g) / Math.sin(this.g);
var B = Math.sin(f * this.g) / Math.sin(this.g);
var x =
A * Math.cos(this.start.y) * Math.cos(this.start.x) +
B * Math.cos(this.end.y) * Math.cos(this.end.x);
var y =
A * Math.cos(this.start.y) * Math.sin(this.start.x) +
B * Math.cos(this.end.y) * Math.sin(this.end.x);
var z = A * Math.sin(this.start.y) + B * Math.sin(this.end.y);
var lat = R2D * Math.atan2(z, Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)));
var lon = R2D * Math.atan2(y, x);
return [lon, lat];
};
/*
* Generate points along the great circle
*/
GreatCircle.prototype.Arc = function (npoints, options)
{
var first_pass = [];
if (!npoints || npoints <= 2)
{
first_pass.push([this.start.lon, this.start.lat]);
first_pass.push([this.end.lon, this.end.lat]);
}
else
{
var delta = 1.0 / (npoints - 1);
for (var i = 0; i < npoints; ++i)
{
var step = delta * i;
var pair = this.interpolate(step);
first_pass.push(pair);
}
}
/* partial port of dateline handling from:
gdal/ogr/ogrgeometryfactory.cpp
TODO - does not handle all wrapping scenarios yet
*/
var bHasBigDiff = false;
var dfMaxSmallDiffLong = 0;
// from http://www.gdal.org/ogr2ogr.html
// -datelineoffset:
// (starting with GDAL 1.10) offset from dateline in degrees (default long. = +/- 10deg, geometries within 170deg to -170deg will be splited)
var dfDateLineOffset = options && options.offset ? options.offset : 10;
var dfLeftBorderX = 180 - dfDateLineOffset;
var dfRightBorderX = -180 + dfDateLineOffset;
var dfDiffSpace = 360 - dfDateLineOffset;
// https://github.com/OSGeo/gdal/blob/7bfb9c452a59aac958bff0c8386b891edf8154ca/gdal/ogr/ogrgeometryfactory.cpp#L2342
for (var j = 1; j < first_pass.length; ++j)
{
var dfPrevX = first_pass[j - 1][0];
var dfX = first_pass[j][0];
var dfDiffLong = Math.abs(dfX - dfPrevX);
if (
dfDiffLong > dfDiffSpace &&
((dfX > dfLeftBorderX && dfPrevX < dfRightBorderX) ||
(dfPrevX > dfLeftBorderX && dfX < dfRightBorderX))
)
{
bHasBigDiff = true;
}
else if (dfDiffLong > dfMaxSmallDiffLong)
{
dfMaxSmallDiffLong = dfDiffLong;
}
}
var poMulti = [];
if (bHasBigDiff && dfMaxSmallDiffLong < dfDateLineOffset)
{
var poNewLS = [];
poMulti.push(poNewLS);
for (var k = 0; k < first_pass.length; ++k)
{
var dfX0 = parseFloat(first_pass[k][0]);
if (k > 0 && Math.abs(dfX0 - first_pass[k - 1][0]) > dfDiffSpace)
{
var dfX1 = parseFloat(first_pass[k - 1][0]);
var dfY1 = parseFloat(first_pass[k - 1][1]);
var dfX2 = parseFloat(first_pass[k][0]);
var dfY2 = parseFloat(first_pass[k][1]);
if (
dfX1 > -180 &&
dfX1 < dfRightBorderX &&
dfX2 == 180 &&
k + 1 < first_pass.length &&
first_pass[k - 1][0] > -180 &&
first_pass[k - 1][0] < dfRightBorderX
)
{
poNewLS.push([-180, first_pass[k][1]]);
k++;
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
continue;
}
else if (
dfX1 > dfLeftBorderX &&
dfX1 < 180 &&
dfX2 == -180 &&
k + 1 < first_pass.length &&
first_pass[k - 1][0] > dfLeftBorderX &&
first_pass[k - 1][0] < 180
)
{
poNewLS.push([180, first_pass[k][1]]);
k++;
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
continue;
}
if (dfX1 < dfRightBorderX && dfX2 > dfLeftBorderX)
{
// swap dfX1, dfX2
var tmpX = dfX1;
dfX1 = dfX2;
dfX2 = tmpX;
// swap dfY1, dfY2
var tmpY = dfY1;
dfY1 = dfY2;
dfY2 = tmpY;
}
if (dfX1 > dfLeftBorderX && dfX2 < dfRightBorderX)
{
dfX2 += 360;
}
if (dfX1 <= 180 && dfX2 >= 180 && dfX1 < dfX2)
{
var dfRatio = (180 - dfX1) / (dfX2 - dfX1);
var dfY = dfRatio * dfY2 + (1 - dfRatio) * dfY1;
poNewLS.push([
first_pass[k - 1][0] > dfLeftBorderX ? 180 : -180,
dfY
]);
poNewLS = [];
poNewLS.push([
first_pass[k - 1][0] > dfLeftBorderX ? -180 : 180,
dfY
]);
poMulti.push(poNewLS);
}
else
{
poNewLS = [];
poMulti.push(poNewLS);
}
poNewLS.push([dfX0, first_pass[k][1]]);
}
else
{
poNewLS.push([first_pass[k][0], first_pass[k][1]]);
}
}
}
else
{
// add normally
var poNewLS0 = [];
poMulti.push(poNewLS0);
for (var l = 0; l < first_pass.length; ++l)
{
poNewLS0.push([first_pass[l][0], first_pass[l][1]]);
}
}
var arc = new Arc(this.properties);
for (var m = 0; m < poMulti.length; ++m)
{
var line = new LineString();
arc.geometries.push(line);
var points = poMulti[m];
for (var j0 = 0; j0 < points.length; ++j0)
{
line.move_to(points[j0]);
}
}
return arc;
};
if (typeof module !== "undefined" && typeof module.exports !== "undefined")
{
// nodejs
module.exports.Coord = Coord;
module.exports.Arc = Arc;
module.exports.GreatCircle = GreatCircle;
}
else
{
// browser
var arc = {};
arc.Coord = Coord;
arc.Arc = Arc;
arc.GreatCircle = GreatCircle;
}