mirror of
https://gitlab.com/gridtracker.org/gridtracker
synced 2026-08-13 17:52:40 -04:00
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
352 lines
8.7 KiB
JavaScript
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;
|
|
}
|