Calculate vertical bearing between two GPS coordinates with altitudes - gps

I am planning to build an antenna tracker. I need to get bearing and tilt from GPS point A with altitude and GPS point B with altitude.
This is the example points:
latA = 39.099912
lonA = -94.581213
altA = 273.543
latB = 38.627089
lonB = -90.200203
altB = 1380.245
I've already got the formula for horizontal bearing and it gives me 97.89138167122422
This is the code:
function toRadian(num) {
return num * (Math.PI / 180);
}
function toDegree(num) {
return num * (180 / Math.PI);
}
function getHorizontalBearing(fromLat, fromLon, toLat, toLon) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let dLon = toLon - fromLon;
let x = Math.tan(toLat / 2 + Math.PI / 4);
let y = Math.tan(fromLat / 2 + Math.PI / 4);
let dPhi = Math.log(x / y);
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0.0) {
dLon = -(2 * Math.PI - dLon);
} else {
dLon = (2 * Math.PI + dLon);
}
}
return (toDegree(Math.atan2(dLon, dPhi)) + 360) % 360;
}
let n = getHorizontalBearing(39.099912, -94.581213, 38.627089, -90.200203);
console.info(n);
But I don't know how to find the tilt angle. Anyone could help me?

I think I got the answer after searching around.
This is the complete code, if you think this is wrong, feel free to correct me.
function toRadian(num) {
return num * (Math.PI / 180);
}
function toDegree(num) {
return num * (180 / Math.PI);
}
// North is 0 degree, South is 180 degree
function getHorizontalBearing(fromLat, fromLon, toLat, toLon, currentBearing) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let dLon = toLon - fromLon;
let x = Math.tan(toLat / 2 + Math.PI / 4);
let y = Math.tan(fromLat / 2 + Math.PI / 4);
let dPhi = Math.log(x / y);
if (Math.abs(dLon) > Math.PI) {
if (dLon > 0.0) {
dLon = -(2 * Math.PI - dLon);
} else {
dLon = (2 * Math.PI + dLon);
}
}
let targetBearing = (toDegree(Math.atan2(dLon, dPhi)) + 360) % 360;
return targetBearing - currentBearing;
}
// Horizon is 0 degree, Up is 90 degree
function getVerticalBearing(fromLat, fromLon, fromAlt, toLat, toLon, toAlt, currentElevation) {
fromLat = toRadian(fromLat);
fromLon = toRadian(fromLon);
toLat = toRadian(toLat);
toLon = toRadian(toLon);
let fromECEF = getECEF(fromLat, fromLon, fromAlt);
let toECEF = getECEF(toLat, toLon, toAlt);
let deltaECEF = getDeltaECEF(fromECEF, toECEF);
let d = (fromECEF[0] * deltaECEF[0] + fromECEF[1] * deltaECEF[1] + fromECEF[2] * deltaECEF[2]);
let a = ((fromECEF[0] * fromECEF[0]) + (fromECEF[1] * fromECEF[1]) + (fromECEF[2] * fromECEF[2]));
let b = ((deltaECEF[0] * deltaECEF[0]) + (deltaECEF[2] * deltaECEF[2]) + (deltaECEF[2] * deltaECEF[2]));
let elevation = toDegree(Math.acos(d / Math.sqrt(a * b)));
elevation = 90 - elevation;
return elevation - currentElevation;
}
function getDeltaECEF(from, to) {
let X = to[0] - from[0];
let Y = to[1] - from[1];
let Z = to[2] - from[2];
return [X, Y, Z];
}
function getECEF(lat, lon, alt) {
let radius = 6378137;
let flatteningDenom = 298.257223563;
let flattening = 0.003352811;
let polarRadius = 6356752.312106893;
let asqr = radius * radius;
let bsqr = polarRadius * polarRadius;
let e = Math.sqrt((asqr-bsqr)/asqr);
// let eprime = Math.sqrt((asqr-bsqr)/bsqr);
let N = getN(radius, e, lat);
let ratio = (bsqr / asqr);
let X = (N + alt) * Math.cos(lat) * Math.cos(lon);
let Y = (N + alt) * Math.cos(lat) * Math.sin(lon);
let Z = (ratio * N + alt) * Math.sin(lat);
return [X, Y, Z];
}
function getN(a, e, latitude) {
let sinlatitude = Math.sin(latitude);
let denom = Math.sqrt(1 - e * e * sinlatitude * sinlatitude);
return a / denom;
}
let n = getHorizontalBearing(39.099912, -94.581213, 39.099912, -94.588032, 0.00);
console.info("Horizontal bearing:\t", n);
let m = getVerticalBearing(39.099912, -94.581213, 273.543, 39.099912, -94.588032, 873.543, 0.0);
console.info("Vertical bearing:\t", m);

Don Cross's javascript code produces good results. It takes into consideration the curvature of the earth plus the fact that the earth is oblate.
Example:
var elDegrees = calculateElevationAngleCosineKitty(
{latitude: 35.346257, longitude: -97.863801, altitudeMetres: 10},
{latitude: 34.450545, longitude: -96.500167, altitudeMetres: 9873}
);
console.log("El: " + elDegrees);
/***********************************
Code by Don Cross at cosinekitty.com
http://cosinekitty.com/compass.html
************************************/
function calculateElevationAngleCosineKitty(source, target)
{
var oblate = true;
var a = {'lat':source.latitude, 'lon':source.longitude, 'elv':source.altitudeMetres};
var b = {'lat':target.latitude, 'lon':target.longitude, 'elv':target.altitudeMetres};
var ap = LocationToPoint(a, oblate);
var bp = LocationToPoint(b, oblate);
var bma = NormalizeVectorDiff(bp, ap);
var elevation = 90.0 - (180.0 / Math.PI)*Math.acos(bma.x*ap.nx + bma.y*ap.ny + bma.z*ap.nz);
return elevation;
}
function NormalizeVectorDiff(b, a)
{
// Calculate norm(b-a), where norm divides a vector by its length to produce a unit vector.
var dx = b.x - a.x;
var dy = b.y - a.y;
var dz = b.z - a.z;
var dist2 = dx*dx + dy*dy + dz*dz;
if (dist2 == 0) {
return null;
}
var dist = Math.sqrt(dist2);
return { 'x':(dx/dist), 'y':(dy/dist), 'z':(dz/dist), 'radius':1.0 };
}
function EarthRadiusInMeters (latitudeRadians) // latitude is geodetic, i.e. that reported by GPS
{
// http://en.wikipedia.org/wiki/Earth_radius
var a = 6378137.0; // equatorial radius in meters
var b = 6356752.3; // polar radius in meters
var cos = Math.cos (latitudeRadians);
var sin = Math.sin (latitudeRadians);
var t1 = a * a * cos;
var t2 = b * b * sin;
var t3 = a * cos;
var t4 = b * sin;
return Math.sqrt ((t1*t1 + t2*t2) / (t3*t3 + t4*t4));
}
function GeocentricLatitude(lat)
{
// Convert geodetic latitude 'lat' to a geocentric latitude 'clat'.
// Geodetic latitude is the latitude as given by GPS.
// Geocentric latitude is the angle measured from center of Earth between a point and the equator.
// https://en.wikipedia.org/wiki/Latitude#Geocentric_latitude
var e2 = 0.00669437999014;
var clat = Math.atan((1.0 - e2) * Math.tan(lat));
return clat;
}
function LocationToPoint(c, oblate)
{
// Convert (lat, lon, elv) to (x, y, z).
var lat = c.lat * Math.PI / 180.0;
var lon = c.lon * Math.PI / 180.0;
var radius = oblate ? EarthRadiusInMeters(lat) : 6371009;
var clat = oblate ? GeocentricLatitude(lat) : lat;
var cosLon = Math.cos(lon);
var sinLon = Math.sin(lon);
var cosLat = Math.cos(clat);
var sinLat = Math.sin(clat);
var x = radius * cosLon * cosLat;
var y = radius * sinLon * cosLat;
var z = radius * sinLat;
// We used geocentric latitude to calculate (x,y,z) on the Earth's ellipsoid.
// Now we use geodetic latitude to calculate normal vector from the surface, to correct for elevation.
var cosGlat = Math.cos(lat);
var sinGlat = Math.sin(lat);
var nx = cosGlat * cosLon;
var ny = cosGlat * sinLon;
var nz = sinGlat;
x += c.elv * nx;
y += c.elv * ny;
z += c.elv * nz;
return {'x':x, 'y':y, 'z':z, 'radius':radius, 'nx':nx, 'ny':ny, 'nz':nz};
}
/***********************
END cosinekitty.com code
************************/

Related

THREE.JS + Delaunator.JS Keeping faces indexing

Probably this question is for Mapbox team and Delaunator.JS developers, but I hope that somebody could help me here.
I have a pseudo quadtree geometry (THREE.BufferGeometry) set by series of points having seven partitions with their individual draw call (geometry.addGroup() method) and material.
//pseudo quadtree
var points = [], indices = [], quad_uvs = [], groups = [], materials = [];
getSegmentSubpoints(0, new THREE.Vector3(-1024, 0, -1024), 1024, 1024, 4);
getSegmentSubpoints(1, new THREE.Vector3(0, 0, -1024), 1024, 1024, 4);
getSegmentSubpoints(2, new THREE.Vector3(-1024, 0, 0), 1024, 1024, 4);
getSegmentSubpoints(3, new THREE.Vector3(0, 0, 0), 512, 512, 8);
getSegmentSubpoints(4, new THREE.Vector3(512, 0, 0), 512, 512, 8);
getSegmentSubpoints(5, new THREE.Vector3(0, 0, 512), 512, 512, 8);
getSegmentSubpoints(6, new THREE.Vector3(512, 0, 512), 512, 512, 8);
var geometry = new THREE.BufferGeometry().setFromPoints(points);
geometry.setIndex(indices);
geometry.setAttribute( 'uv', new THREE.BufferAttribute(new Float32Array(quad_uvs), 2 ) );
geometry.computeVertexNormals();
var colors = [new THREE.Color(0xe6194b), new THREE.Color(0x3cb44b), new THREE.Color(0xffe119), new THREE.Color(0x4363d8), new THREE.Color(0xf58231), new THREE.Color(0x911eb4), new THREE.Color(0x46f0f0) ]
groups.forEach(function(g_, i_){ geometry.addGroup(g_.start, g_.end, g_.id); });
var plane = new THREE.Mesh(geometry, materials);
plane.rotation.set(Math.PI, 0, 0);
plane.position.set(-1152, 0, 0);
scene.add(plane);
function getSegmentSubpoints(id_, lt_, w_, h_, level_){
var subpoints = [];
var subindices = [];
var subquad = [];
var lastIndex = points.length;
var lastIndex2 = indices.length;
var step = {x: w_ / level_, z: h_ / level_ };
var stepT = {x: 1.0 / level_, z: 1.0 / level_ };
for(var z = 0; z <= level_; z++){
for(var x = 0; x <= level_; x++){
var dx = lt_.x + step.x * x;
var dz = lt_.z + step.z * z;
var dy = noise.simplex2(dx / 512.0, dz / 512.0) * 32.0;
subquad.push(...[stepT.x * x, stepT.z * z]);
subpoints.push(new THREE.Vector3(dx, dy, dz));
}
}
for(var i = 0; i < subpoints.length - level_ - 2; i++) {
if(i % (level_ + 1) != (level_)){
subindices.push(lastIndex + i, lastIndex + i + 1, lastIndex + i + level_ + 2, lastIndex + i + level_ + 2, lastIndex + i + level_ + 1, lastIndex + i);
}
}
points.push(...subpoints);
indices.push(...subindices);
quad_uvs.push(...subquad);
groups.push({id: id_, start: lastIndex2, end: subindices.length});
materials.push(new THREE.MeshBasicMaterial({ wireframe: true, map: new THREE.TextureLoader().load("textures/" + id_ + ".jpg")}));
}
Everything is fine, but for my project I have to process the current geometry through Delaunator.JS and it seems that the output has different triangles/faces indexing starting from the center.
There is dynamic colors for each segment to visualize indexing. Eventually, it has to be the same as previous one with seven materials and individual draw calls.
//delaunay
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var indexDelaunay = Delaunator.from(points.map(v => { return [v.x, v.z]; }) );
var meshIndex = [];
for (let i = 0; i < indexDelaunay.triangles.length; i++){ meshIndex.push(indexDelaunay.triangles[i]); }
geometry.setIndex(meshIndex);
geometry.computeVertexNormals();
count = 0;
for(var i = 0; i < meshIndex.length; i += 6){
geometry.addGroup(i, 6, i / 6);
materials.push(new THREE.MeshBasicMaterial({ wireframe: true, color: new THREE.Color("hsl(" + (360 / 316 * count) + ",80%, 80%)")}))
count++;
}
var plane = new THREE.Mesh(geometry, materials);
plane.rotation.set(Math.PI, 0, 0)
plane.position.set(1152, 0, 0);
scene.add(plane);
So is there a way to re-index faces back so I could get the same result?
Yes, I could go through each Delaunator.triangles check its position/area for if it fits the certain partition, but it's not an elegant and fast solution. I bet it's possible to tweak Delaunator.JS code for right indexing on the fly.
The snippet is attached as well.
//https://hofk.de/main/discourse.threejs/2018/Triangulation/Triangulation.html
////by Mapbox https://github.com/mapbox/delaunator
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Delaunator = factory());
}(this, (function () { 'use strict';
var EPSILON = Math.pow(2, -52);
var Delaunator = function Delaunator(coords) {
var this$1 = this;
var n = coords.length >> 1;
if (n > 0 && typeof coords[0] !== 'number') { throw new Error('Expected coords to contain numbers.'); }
this.coords = coords;
var maxTriangles = 2 * n - 5;
var triangles = this.triangles = new Uint32Array(maxTriangles * 3);
var halfedges = this.halfedges = new Int32Array(maxTriangles * 3);
this._hashSize = Math.ceil(Math.sqrt(n));
var hullPrev = this.hullPrev = new Uint32Array(n);
var hullNext = this.hullNext = new Uint32Array(n);
var hullTri = this.hullTri = new Uint32Array(n);
var hullHash = new Int32Array(this._hashSize).fill(-1);
var ids = new Uint32Array(n);
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
for (var i = 0; i < n; i++) {
var x = coords[2 * i];
var y = coords[2 * i + 1];
if (x < minX) { minX = x; }
if (y < minY) { minY = y; }
if (x > maxX) { maxX = x; }
if (y > maxY) { maxY = y; }
ids[i] = i;
}
var cx = (minX + maxX) / 2;
var cy = (minY + maxY) / 2;
var minDist = Infinity;
var i0, i1, i2;
for (var i$1 = 0; i$1 < n; i$1++) {
var d = dist(cx, cy, coords[2 * i$1], coords[2 * i$1 + 1]);
if (d < minDist) {
i0 = i$1;
minDist = d;
}
}
var i0x = coords[2 * i0];
var i0y = coords[2 * i0 + 1];
minDist = Infinity;
for (var i$2 = 0; i$2 < n; i$2++) {
if (i$2 === i0) { continue; }
var d$1 = dist(i0x, i0y, coords[2 * i$2], coords[2 * i$2 + 1]);
if (d$1 < minDist && d$1 > 0) {
i1 = i$2;
minDist = d$1;
}
}
var i1x = coords[2 * i1];
var i1y = coords[2 * i1 + 1];
var minRadius = Infinity;
for (var i$3 = 0; i$3 < n; i$3++) {
if (i$3 === i0 || i$3 === i1) { continue; }
var r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i$3], coords[2 * i$3 + 1]);
if (r < minRadius) {
i2 = i$3;
minRadius = r;
}
}
var i2x = coords[2 * i2];
var i2y = coords[2 * i2 + 1];
if (minRadius === Infinity) {
throw new Error('No Delaunay triangulation exists for this input.');
}
if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) {
var i$4 = i1;
var x$1 = i1x;
var y$1 = i1y;
i1 = i2;
i1x = i2x;
i1y = i2y;
i2 = i$4;
i2x = x$1;
i2y = y$1;
}
var center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
this._cx = center.x;
this._cy = center.y;
var dists = new Float64Array(n);
for (var i$5 = 0; i$5 < n; i$5++) {
dists[i$5] = dist(coords[2 * i$5], coords[2 * i$5 + 1], center.x, center.y);
}
quicksort(ids, dists, 0, n - 1);
this.hullStart = i0;
var hullSize = 3;
hullNext[i0] = hullPrev[i2] = i1;
hullNext[i1] = hullPrev[i0] = i2;
hullNext[i2] = hullPrev[i1] = i0;
hullTri[i0] = 0;
hullTri[i1] = 1;
hullTri[i2] = 2;
hullHash[this._hashKey(i0x, i0y)] = i0;
hullHash[this._hashKey(i1x, i1y)] = i1;
hullHash[this._hashKey(i2x, i2y)] = i2;
this.trianglesLen = 0;
this._addTriangle(i0, i1, i2, -1, -1, -1);
for (var k = 0, xp = (void 0), yp = (void 0); k < ids.length; k++) {
var i$6 = ids[k];
var x$2 = coords[2 * i$6];
var y$2 = coords[2 * i$6 + 1];
if (k > 0 && Math.abs(x$2 - xp) <= EPSILON && Math.abs(y$2 - yp) <= EPSILON) { continue; }
xp = x$2;
yp = y$2;
if (i$6 === i0 || i$6 === i1 || i$6 === i2) { continue; }
var start = 0;
for (var j = 0, key = this._hashKey(x$2, y$2); j < this._hashSize; j++) {
start = hullHash[(key + j) % this$1._hashSize];
if (start !== -1 && start !== hullNext[start]) { break; }
}
start = hullPrev[start];
var e = start, q = (void 0);
while (q = hullNext[e], !orient(x$2, y$2, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) {
e = q;
if (e === start) {
e = -1;
break;
}
}
if (e === -1) { continue; }
var t = this$1._addTriangle(e, i$6, hullNext[e], -1, -1, hullTri[e]);
hullTri[i$6] = this$1._legalize(t + 2);
hullTri[e] = t;
hullSize++;
var n$1 = hullNext[e];
while (q = hullNext[n$1], orient(x$2, y$2, coords[2 * n$1], coords[2 * n$1 + 1], coords[2 * q], coords[2 * q + 1])) {
t = this$1._addTriangle(n$1, i$6, q, hullTri[i$6], -1, hullTri[n$1]);
hullTri[i$6] = this$1._legalize(t + 2);
hullNext[n$1] = n$1;
hullSize--;
n$1 = q;
}
if (e === start) {
while (q = hullPrev[e], orient(x$2, y$2, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) {
t = this$1._addTriangle(q, i$6, e, -1, hullTri[e], hullTri[q]);
this$1._legalize(t + 2);
hullTri[q] = t;
hullNext[e] = e;
hullSize--;
e = q;
}
}
this$1.hullStart = hullPrev[i$6] = e;
hullNext[e] = hullPrev[n$1] = i$6;
hullNext[i$6] = n$1;
hullHash[this$1._hashKey(x$2, y$2)] = i$6;
hullHash[this$1._hashKey(coords[2 * e], coords[2 * e + 1])] = e;
}
this.hull = new Uint32Array(hullSize);
for (var i$7 = 0, e$1 = this.hullStart; i$7 < hullSize; i$7++) {
this$1.hull[i$7] = e$1;
e$1 = hullNext[e$1];
}
this.hullPrev = this.hullNext = this.hullTri = null;
this.triangles = triangles.subarray(0, this.trianglesLen);
this.halfedges = halfedges.subarray(0, this.trianglesLen);
};
Delaunator.from = function from (points, getX, getY) {
if ( getX === void 0 ) getX = defaultGetX;
if ( getY === void 0 ) getY = defaultGetY;
var n = points.length;
var coords = new Float64Array(n * 2);
for (var i = 0; i < n; i++) {
var p = points[i];
coords[2 * i] = getX(p);
coords[2 * i + 1] = getY(p);
}
return new Delaunator(coords);
};
Delaunator.prototype._hashKey = function _hashKey (x, y) {
return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize;
};
Delaunator.prototype._legalize = function _legalize (a) {
var this$1 = this;
var ref = this;
var triangles = ref.triangles;
var coords = ref.coords;
var halfedges = ref.halfedges;
var b = halfedges[a];
var a0 = a - a % 3;
var b0 = b - b % 3;
var al = a0 + (a + 1) % 3;
var ar = a0 + (a + 2) % 3;
var bl = b0 + (b + 2) % 3;
if (b === -1) { return ar; }
var p0 = triangles[ar];
var pr = triangles[a];
var pl = triangles[al];
var p1 = triangles[bl];
var illegal = inCircle(
coords[2 * p0], coords[2 * p0 + 1],
coords[2 * pr], coords[2 * pr + 1],
coords[2 * pl], coords[2 * pl + 1],
coords[2 * p1], coords[2 * p1 + 1]);
if (illegal) {
triangles[a] = p1;
triangles[b] = p0;
var hbl = halfedges[bl];
if (hbl === -1) {
var e = this.hullStart;
do {
if (this$1.hullTri[e] === bl) {
this$1.hullTri[e] = a;
break;
}
e = this$1.hullNext[e];
} while (e !== this.hullStart);
}
this._link(a, hbl);
this._link(b, halfedges[ar]);
this._link(ar, bl);
var br = b0 + (b + 1) % 3;
this._legalize(a);
return this._legalize(br);
}
return ar;
};
Delaunator.prototype._link = function _link (a, b) {
this.halfedges[a] = b;
if (b !== -1) { this.halfedges[b] = a; }
};
Delaunator.prototype._addTriangle = function _addTriangle (i0, i1, i2, a, b, c) {
var t = this.trianglesLen;
this.triangles[t] = i0;
this.triangles[t + 1] = i1;
this.triangles[t + 2] = i2;
this._link(t, a);
this._link(t + 1, b);
this._link(t + 2, c);
this.trianglesLen += 3;
return t;
};
function pseudoAngle(dx, dy) {
var p = dx / (Math.abs(dx) + Math.abs(dy));
return (dy > 0 ? 3 - p : 1 + p) / 4;
}
function dist(ax, ay, bx, by) {
var dx = ax - bx;
var dy = ay - by;
return dx * dx + dy * dy;
}
function orient(px, py, qx, qy, rx, ry) {
return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) < 0;
}
function inCircle(ax, ay, bx, by, cx, cy, px, py) {
var dx = ax - px;
var dy = ay - py;
var ex = bx - px;
var ey = by - py;
var fx = cx - px;
var fy = cy - py;
var ap = dx * dx + dy * dy;
var bp = ex * ex + ey * ey;
var cp = fx * fx + fy * fy;
return dx * (ey * cp - bp * fy) -
dy * (ex * cp - bp * fx) +
ap * (ex * fy - ey * fx) < 0;
}
function circumradius(ax, ay, bx, by, cx, cy) {
var dx = bx - ax;
var dy = by - ay;
var ex = cx - ax;
var ey = cy - ay;
var bl = dx * dx + dy * dy;
var cl = ex * ex + ey * ey;
var d = 0.5 / (dx * ey - dy * ex);
var x = (ey * bl - dy * cl) * d;
var y = (dx * cl - ex * bl) * d;
return x * x + y * y;
}
function circumcenter(ax, ay, bx, by, cx, cy) {
var dx = bx - ax;
var dy = by - ay;
var ex = cx - ax;
var ey = cy - ay;
var bl = dx * dx + dy * dy;
var cl = ex * ex + ey * ey;
var d = 0.5 / (dx * ey - dy * ex);
var x = ax + (ey * bl - dy * cl) * d;
var y = ay + (dx * cl - ex * bl) * d;
return {x: x, y: y};
}
function quicksort(ids, dists, left, right) {
if (right - left <= 20) {
for (var i = left + 1; i <= right; i++) {
var temp = ids[i];
var tempDist = dists[temp];
var j = i - 1;
while (j >= left && dists[ids[j]] > tempDist) { ids[j + 1] = ids[j--]; }
ids[j + 1] = temp;
}
} else {
var median = (left + right) >> 1;
var i$1 = left + 1;
var j$1 = right;
swap(ids, median, i$1);
if (dists[ids[left]] > dists[ids[right]]) { swap(ids, left, right); }
if (dists[ids[i$1]] > dists[ids[right]]) { swap(ids, i$1, right); }
if (dists[ids[left]] > dists[ids[i$1]]) { swap(ids, left, i$1); }
var temp$1 = ids[i$1];
var tempDist$1 = dists[temp$1];
while (true) {
do { i$1++; } while (dists[ids[i$1]] < tempDist$1);
do { j$1--; } while (dists[ids[j$1]] > tempDist$1);
if (j$1 < i$1) { break; }
swap(ids, i$1, j$1);
}
ids[left + 1] = ids[j$1];
ids[j$1] = temp$1;
if (right - i$1 + 1 >= j$1 - left) {
quicksort(ids, dists, i$1, right);
quicksort(ids, dists, left, j$1 - 1);
} else {
quicksort(ids, dists, left, j$1 - 1);
quicksort(ids, dists, i$1, right);
}
}
}
function swap(arr, i, j) {
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
function defaultGetX(p) {
return p[0];
}
function defaultGetY(p) {
return p[1];
}
return Delaunator;
})));
var renderer, scene, camera, controls, loader, terrain, glsl, uniforms, root, tree;
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000);
document.body.appendChild(renderer.domElement);
scene = new THREE.Scene();
loader = new THREE.TextureLoader();
loader.crossOrigin = "";
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 51200);
camera.position.set(-3072, 2048, -3072);
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 8;
controls.maxDistance = 10240;
controls.maxPolarAngle = Math.PI / 2;
//simple pseudo quadtree
var points = [], indices = [], quad_uvs = [], groups = [], materials = [];
getSegmentSubpoints(0, new THREE.Vector3(-1024, 0, -1024), 1024, 1024, 4);
getSegmentSubpoints(1, new THREE.Vector3(0, 0, -1024), 1024, 1024, 4);
getSegmentSubpoints(2, new THREE.Vector3(-1024, 0, 0), 1024, 1024, 4);
getSegmentSubpoints(3, new THREE.Vector3(0, 0, 0), 512, 512, 8);
getSegmentSubpoints(4, new THREE.Vector3(512, 0, 0), 512, 512, 8);
getSegmentSubpoints(5, new THREE.Vector3(0, 0, 512), 512, 512, 8);
getSegmentSubpoints(6, new THREE.Vector3(512, 0, 512), 512, 512, 8);
var geometry = new THREE.BufferGeometry().setFromPoints(points);
geometry.setIndex(indices);
geometry.setAttribute( 'uv', new THREE.BufferAttribute(new Float32Array(quad_uvs), 2 ) );
geometry.computeVertexNormals();
var materials = [];
var colors = [new THREE.Color(0xe6194b), new THREE.Color(0x3cb44b), new THREE.Color(0xffe119), new THREE.Color(0x4363d8), new THREE.Color(0xf58231), new THREE.Color(0x911eb4), new THREE.Color(0x46f0f0) ]
groups.forEach(function(g_, i_){ geometry.addGroup(g_.start, g_.end, g_.id); materials.push(new THREE.MeshBasicMaterial({wireframe: true, color: colors[i_]})) });
var plane = new THREE.Mesh(geometry, materials);
plane.rotation.set(Math.PI, 0, 0);
plane.position.set(-1152, 0, 0);
scene.add(plane);
//delaunay
materials = [];
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var indexDelaunay = Delaunator.from(points.map(v => { return [v.x, v.z]; }) );
var meshIndex = [];
for (let i = 0; i < indexDelaunay.triangles.length; i++){ meshIndex.push(indexDelaunay.triangles[i]); }
geometry.setIndex(meshIndex);
geometry.computeVertexNormals();
count = 0;
for(var i = 0; i < meshIndex.length; i += 6){
geometry.addGroup(i, 6, i / 6);
materials.push(new THREE.MeshBasicMaterial({ wireframe: true, color: new THREE.Color("hsl(" + (360 / 316 * count) + ",80%, 80%)")}))
count++;
}
var plane = new THREE.Mesh(geometry, materials); //new THREE.MeshBasicMaterial({color: 0xFF00FF, side: THREE.DoubleSide, wireframe: true}) );
plane.rotation.set(Math.PI, 0, 0)
plane.position.set(1152, 0, 0);
scene.add(plane);
animate();
function getSegmentSubpoints(id_, lt_, w_, h_, level_){
var subpoints = [];
var subindices = [];
var subquad = [];
var lastIndex = points.length;
var lastIndex2 = indices.length;
var step = {x: w_ / level_, z: h_ / level_ };
var stepT = {x: 1.0 / level_, z: 1.0 / level_ };
for(var z = 0; z <= level_; z++){
for(var x = 0; x <= level_; x++){
var dx = lt_.x + step.x * x;
var dz = lt_.z + step.z * z;
var dy = 0;
subquad.push(...[stepT.x * x, stepT.z * z]);
subpoints.push(new THREE.Vector3(dx, dy, dz));
}
}
for(var i = 0; i < subpoints.length - level_ - 2; i++) {
if(i % (level_ + 1) != (level_)){
subindices.push(lastIndex + i, lastIndex + i + 1, lastIndex + i + level_ + 2, lastIndex + i + level_ + 2, lastIndex + i + level_ + 1, lastIndex + i);
}
}
points.push(...subpoints);
indices.push(...subindices);
quad_uvs.push(...subquad);
groups.push({id: id_, start: lastIndex2, end: subindices.length});
materials.push(new THREE.MeshBasicMaterial({ wireframe: true, map: new THREE.TextureLoader().load("textures/" + id_ + ".jpg")}));
}
function animate(){
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
body { margin: 0; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GLSL Intersection</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://unpkg.com/three#0.116.0/build/three.min.js"></script>
<script src="https://unpkg.com/three#0.116.0/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
</body>
</html>

How calulate the area of a polygon in react-native-maps?

I am passing the array of co-ordinates to the polygon and want to find the area of the polygon through that co-ordinates. I have checked the documentation of the react-native-maps but there is no function provided.
Is there is any way to calculate the area.
Thanks in advance.
Library will not give you this functionality.
Try this
function calcArea(locations) {
if (!locations.length) {
return 0;
}
if (locations.length < 3) {
return 0;
}
let radius = 6371000;
const diameter = radius * 2;
const circumference = diameter * Math.PI;
const listY = [];
const listX = [];
const listArea = [];
// calculate segment x and y in degrees for each point
const latitudeRef = locations[0].latitude;
const longitudeRef = locations[0].longitude;
for (let i = 1; i < locations.length; i++) {
let latitude = locations[i].latitude;
let longitude = locations[i].longitude;
listY.push(this.calculateYSegment(latitudeRef, latitude, circumference));
listX.push(this.calculateXSegment(longitudeRef, longitude, latitude, circumference));
}
// calculate areas for each triangle segment
for (let i = 1; i < listX.length; i++) {
let x1 = listX[i - 1];
let y1 = listY[i - 1];
let x2 = listX[i];
let y2 = listY[i];
listArea.push(this.calculateAreaInSquareMeters(x1, x2, y1, y2));
}
// sum areas of all triangle segments
let areasSum = 0;
listArea.forEach(area => areasSum = areasSum + area)
// get abolute value of area, it can't be negative
let areaCalc = Math.abs(areasSum);// Math.sqrt(areasSum * areasSum);
return areaCalc;
}
function calculateAreaInSquareMeters(x1, x2, y1, y2) {
return (y1 * x2 - x1 * y2) / 2;
}
function calculateYSegment(latitudeRef, latitude, circumference) {
return (latitude - latitudeRef) * circumference / 360.0;
}
function calculateXSegment(longitudeRef, longitude, latitude, circumference) {
return (longitude - longitudeRef) * circumference * Math.cos((latitude * (Math.PI / 180))) / 360.0;
}
Reference

Calculate distance between two latitude-longitude points? - Titanium

I tried with this code.
function distance(lon1, lat1, lon2, lat2) {
var R = 6371; // Radius of the earth in km
var dLat = (lat2-lat1).toRad(); // Javascript functions in radians
var dLon = (lon2-lon1).toRad();
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) *
Math.sin(dLon/2) * Math.sin(dLon/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var d = R * c; // Distance in km
return d;
}
/** Converts numeric degrees to radians */
if (typeof(Number.prototype.toRad) === "undefined") {
Number.prototype.toRad = function() {
return this * Math.PI / 180;
};
}
Titanium.Geolocation.getCurrentPosition(function(e) {
Ti.API.info(distance(e.coords.longitude, e.coords.latitude, 10.0009768, 76.2369564));
});
But, it does not showing the actual distance. Some one please help me to find a solution.
This is working for me.
function getDistance(lat1, lon1, lat2, lon2) {
Ti.API.info('***Distance ='+lat1, lon1, lat2, lon2);
var radlat1 = Math.PI * lat1 / 180;
var radlat2 = Math.PI * lat2 / 180;
var theta = lon1 - lon2;
var radtheta = Math.PI * theta / 180;
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
dist = Math.acos(dist);
dist = dist * 180 / Math.PI;
dist = dist * 60 * 1.1515;
dist = dist.toFixed(0);
Ti.API.info('dist ='+dist);
return dist;
}
Thank you.
in your first code use this :
var c = 2 * Math.asin(Math.sqrt(a))
http://www.codecodex.com/wiki/Calculate_Distance_Between_Two_Points_on_a_Globe

Answer to converting lat/long to x/y on mercator not working for me when changing location from Germany to US

I'm using an answer from Raphael from this post (https://stackoverflow.com/a/10401734/3321095) to convert lat/long to xy coordinates plotted on a mercator map. Raphael's example uses an area in Hamburg, Germany. I tested it and it does work. I then changed it to find a point within the United States but the coordinates are always beyond the size of the image. Can someone help?
<script type="text/javascript">
var mapWidth = 749; //1500;
var mapHeight = 462; //1577;
var mapLonLeft = 125; //9.8;
var mapLonRight = 65 //10.2;
var mapLonDelta = mapLonRight - mapLonLeft;
var mapLatBottom = 25 //53.45;
var mapLatBottomDegree = mapLatBottom * Math.PI / 180;
function convertGeoToPixel(lat, lon)
{
var position = new Array(2);
var x = (lon - mapLonLeft) * (mapWidth / mapLonDelta);
var lat = lat * Math.PI / 180;
var worldMapWidth = ((mapWidth / mapLonDelta) * 360) / (2 * Math.PI);
var mapOffsetY = (worldMapWidth / 2 * Math.log((1 + Math.sin(mapLatBottomDegree)) / (1 - Math.sin(mapLatBottomDegree))));
var y = mapHeight - ((worldMapWidth / 2 * Math.log((1 + Math.sin(lat)) / (1 - Math.sin(lat)))) - mapOffsetY);
position[0] = x;
position[1] = y;
return position;
}
var coordinates = convertGeoToPixel(30.274333164300643, -97.74064064025879); //convertGeoToPixel(53.7, 9.95);
alert("x: " + coordinates[0] + " y: " + coordinates[1]);
</script>
Hope you figured this out in the last year. Your code helped me with a similar project. Your code is missing a minus sign and should look like this:
var mapLonLeft = -125; //9.8;
var mapLonRight = -65 //10.2;
Longitude is negative in the USA.

Determining Midpoint Between 2 Coordinates

I am trying to determine the midpoint between two locations in an MKMapView. I am following the method outlined here (and here) and rewrote it in Objective-C, but the map is being centered somewhere northeast of Baffin Island, which is no where near the two points.
My method based on the java method linked above:
+(CLLocationCoordinate2D)findCenterPoint:(CLLocationCoordinate2D)_lo1 :(CLLocationCoordinate2D)_loc2 {
CLLocationCoordinate2D center;
double lon1 = _lo1.longitude * M_PI / 180;
double lon2 = _loc2.longitude * M_PI / 100;
double lat1 = _lo1.latitude * M_PI / 180;
double lat2 = _loc2.latitude * M_PI / 100;
double dLon = lon2 - lon1;
double x = cos(lat2) * cos(dLon);
double y = cos(lat2) * sin(dLon);
double lat3 = atan2( sin(lat1) + sin(lat2), sqrt((cos(lat1) + x) * (cos(lat1) + x) + y * y) );
double lon3 = lon1 + atan2(y, cos(lat1) + x);
center.latitude = lat3 * 180 / M_PI;
center.longitude = lon3 * 180 / M_PI;
return center;
}
The 2 parameters have the following data:
_loc1:
latitude = 45.4959839
longitude = -73.67826455
_loc2:
latitude = 45.482889
longitude = -73.57522299
The above are correctly place on the map (in and around Montreal). I am trying to center the map in the midpoint between the 2, yet my method return the following:
latitude = 65.29055
longitude = -82.55425
which somewhere in the arctic, when it should be around 500 miles south.
In case someone need code in Swift, I have written library function in Swift to calculate the midpoint between MULTIPLE coordinates:
// /** Degrees to Radian **/
class func degreeToRadian(angle:CLLocationDegrees) -> CGFloat {
return ( (CGFloat(angle)) / 180.0 * CGFloat(M_PI) )
}
// /** Radians to Degrees **/
class func radianToDegree(radian:CGFloat) -> CLLocationDegrees {
return CLLocationDegrees( radian * CGFloat(180.0 / M_PI) )
}
class func middlePointOfListMarkers(listCoords: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D {
var x = 0.0 as CGFloat
var y = 0.0 as CGFloat
var z = 0.0 as CGFloat
for coordinate in listCoords{
var lat:CGFloat = degreeToRadian(coordinate.latitude)
var lon:CGFloat = degreeToRadian(coordinate.longitude)
x = x + cos(lat) * cos(lon)
y = y + cos(lat) * sin(lon)
z = z + sin(lat)
}
x = x/CGFloat(listCoords.count)
y = y/CGFloat(listCoords.count)
z = z/CGFloat(listCoords.count)
var resultLon: CGFloat = atan2(y, x)
var resultHyp: CGFloat = sqrt(x*x+y*y)
var resultLat:CGFloat = atan2(z, resultHyp)
var newLat = radianToDegree(resultLat)
var newLon = radianToDegree(resultLon)
var result:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: newLat, longitude: newLon)
return result
}
Detailed answer can be found here
Updated For Swift 5
func geographicMidpoint(betweenCoordinates coordinates: [CLLocationCoordinate2D]) -> CLLocationCoordinate2D {
guard coordinates.count > 1 else {
return coordinates.first ?? // return the only coordinate
CLLocationCoordinate2D(latitude: 0, longitude: 0) // return null island if no coordinates were given
}
var x = Double(0)
var y = Double(0)
var z = Double(0)
for coordinate in coordinates {
let lat = coordinate.latitude.toRadians()
let lon = coordinate.longitude.toRadians()
x += cos(lat) * cos(lon)
y += cos(lat) * sin(lon)
z += sin(lat)
}
x /= Double(coordinates.count)
y /= Double(coordinates.count)
z /= Double(coordinates.count)
let lon = atan2(y, x)
let hyp = sqrt(x * x + y * y)
let lat = atan2(z, hyp)
return CLLocationCoordinate2D(latitude: lat.toDegrees(), longitude: lon.toDegrees())
}
}
Just a hunch, but I noticed your lon2 and lat2 variables are being computed with M_PI/100 and not M_PI/180.
double lon1 = _lo1.longitude * M_PI / 180;
double lon2 = _loc2.longitude * M_PI / 100;
double lat1 = _lo1.latitude * M_PI / 180;
double lat2 = _loc2.latitude * M_PI / 100;
Changing those to 180 might help you out a bit.
For swift users, corrected variant as #dinjas suggest
import Foundation
import MapKit
extension CLLocationCoordinate2D {
// MARK: CLLocationCoordinate2D+MidPoint
func middleLocationWith(location:CLLocationCoordinate2D) -> CLLocationCoordinate2D {
let lon1 = longitude * M_PI / 180
let lon2 = location.longitude * M_PI / 180
let lat1 = latitude * M_PI / 180
let lat2 = location.latitude * M_PI / 180
let dLon = lon2 - lon1
let x = cos(lat2) * cos(dLon)
let y = cos(lat2) * sin(dLon)
let lat3 = atan2( sin(lat1) + sin(lat2), sqrt((cos(lat1) + x) * (cos(lat1) + x) + y * y) )
let lon3 = lon1 + atan2(y, cos(lat1) + x)
let center:CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat3 * 180 / M_PI, lon3 * 180 / M_PI)
return center
}
}
It's important to say that the formula the OP used to calculate geographic midpoint is based on this formula which explains the cos/sin/sqrt calculation.
This formula will give you the geographic midpoint for any long distance including the four quarters and the prime meridian.
But, if your calculation is for short-range around 1 Kilometer, using a simple average will produce the same midpoint results.
i.e:
let firstPoint = CLLocation(....)
let secondPoint = CLLocation(....)
let midPointLat = (firstPoint.coordinate.latitude + secondPoint.coordinate.latitude) / 2
let midPointLong = (firstPoint.coordinate.longitude + secondPoint.coordinate.longitude) / 2
You can actually use it for 10km but expect a deviation - if you only need an estimation for a short range midpoint with a fast solution it will be sufficient.
I think you are over thinking it a bit. Just do:
float lon3 = ((lon1 + lon2) / 2)
float lat3 = ((lat1 + lat2) / 2)
lat3 and lon3 will be the center point.