JavaScript and Trigonometry/Oval - javascript

I have been using this this oval function (that I found at Wikipedia http://en.wikipedia.org/wiki/Ellipse) for plotting points in a layout. I have been laying things out with two to five points plotted around an oval without any problem. The function has a parameter called 'steps'; the 'steps' parameter sets the quantity of points to plot around the oval.
Here's the main problem: If 'steps' (the number of points to plot) is equal to the numbers 7, 11, 13 or 14, it breaks. It's been a few years since I took trigonometry, so basically I am stuck.
Second minor issue: I had the code printing out all points, but when I copied/pasted and removed extraneous code to post here, it only prints out the last plotting point (not sure why).
<html>
<head>
<script type="text/javascript">
var elipticalLayout=new Array();
for (i=0; i <36; i++){
elipticalLayout[i]=new Array(2);
}
/*
* This functions returns an array containing the specified
* number of 'steps' (points) to draw an ellipse.
*
* #param x {double} X coordinate
* #param y {double} Y coordinate
* #param a {double} Semimajor axis
* #param b {double} Semiminor axis
* #param angle {double} Angle of the ellipse
*
* Attribution: This function is from http://en.wikipedia.org/wiki/Ellipse
*/
function calculateEllipticalLayout(x, y, a, b, angle, steps) {
var points = [];
// Angle is given by Degree Value
var beta = -angle * (Math.PI / 180); //(Math.PI/180) converts Degree Value into Radians
var sinbeta = Math.sin(beta);
var cosbeta = Math.cos(beta);
for (var i = 0; i < 360; i += 360 / steps) //{
var alpha = i * (Math.PI / 180) ;
var sinalpha = Math.sin(alpha);
var cosalpha = Math.cos(alpha);
var X = x + (a * cosalpha * cosbeta - b * sinalpha * sinbeta);
var Y = y + (a * cosalpha * sinbeta + b * sinalpha * cosbeta);
elipticalLayout[i/(360/steps)][0]=X;
elipticalLayout[i/(360/steps)][1]=Y;
}
</script>
</head>
<body>
<script type="text/javascript">
calculateEllipticalLayout(300, 300, 245, 125, 15, 15);
for (i=0; i<elipticalLayout.length; i++){
document.write(i + ", " + elipticalLayout[i][0] + ", " + elipticalLayout[i][1] + "<br>");
}
</script>
</body>
</html>

I would declare elipticalLayout within the function and return it. Below is how I'd write the function.
Note that there is no such thing as a "double" or even integer in javascript, a variable's type is defined by its value. Numbers are just numbers, they can be primitives or Number objects (almost never required).
If you are using the returned values to plot positions in pixels, you likely want to round them to integers first. I've used a simple truncation method to convert them to integers.
var elipticalLayout = [];
/*
* This functions returns an array containing the specified
* number of 'steps' (points) to draw an ellipse.
*
* #param x - X coordinate
* #param y - Y coordinate
* #param a - Semimajor axis
* #param b - Semiminor axis
* #param angle - Angle of the ellipse
*
* Attribution: This function is from http://en.wikipedia.org/wiki/Ellipse
*/
function calculateEllipticalLayout(x, y, a, b, angle, steps) {
var points = [];
var step = 360 / steps;
var k = Math.PI/180; // Convert deg to rad
var cos = Math.cos;
var sin = Math.sin;
var i = 0;
// Convert angle to radians
var beta = -angle * k;
var sinbeta = sin(beta);
var cosbeta = cos(beta);
while (i < 360) {
var alpha = i * k;
var sinalpha = sin(alpha);
var cosalpha = cos(alpha);
var X = x + (a * cosalpha * cosbeta - b * sinalpha * sinbeta);
var Y = y + (a * cosalpha * sinbeta + b * sinalpha * cosbeta);
// Truncate numbers to integer and put into array
elipticalLayout.push([X|0, Y|0]);
// Keep X,Y as floats
// elipticalLayout.push([X, Y]);
i += step;
}
}

You fill only steps number of elements in elipticalLayout.
But trying to output 36 of them.
This elipticalLayout[i/(360/steps)][0]=X; is wrong as will lead to "holes" in the sequence due to float to int rounding.
You should have something like this:
var n = 0;
for(...)
elipticalLayout[n][0]=X;
elipticalLayout[n][1]=Y;
++n;

Related

svg & javascript: detect element intersection

I'm working on an SVG image describing a grid (all elements are grouped on <g id='map'>) with green/red/yellow rectangles and a "scratch like" area (with elements grouped on <g id='edit'>) with a list of circle filled in violet.
https://jsfiddle.net/3xz04ab8/
Is there a way, with javascript, to detect which elements of <g id='map'> (in violet) group are below/in common with the elements of <g id='edit'> one?
Easiest way to find intersecting elements is to iterate over them and check intersection one by one.
But that's not optimal, as each iteration will have to read and parse DOM attributes again and again.
Since you know that the map is static and will not change, you can gather info beforehand and prepare data for quick lookups.
If we assume that all rects on the map are of the same size, we can make quick calculation to get positions of rects intersecting with area of a circle.
Since your SVG is too big for including in code snippets, code samples below are JavaScript-only, with additional links to fiddles.
Easy, sub-optimal implementation
/**
* #typedef Area
* #property {number} x1 X position of top-left
* #property {number} y1 Y position of top-left
* #property {number} x2 X position of bottom-right
* #property {number} y2 Y position of bottom-right
*/
/**
* Based on https://stackoverflow.com/a/2752387/6352710
* #param {SVGElement} $rect
* #param {Area} area
* #return {boolean}
*/
function areIntersecting ($rect, area) {
const x1 = parseFloat($rect.getAttribute('x'));
const y1 = parseFloat($rect.getAttribute('y'));
const x2 = x1 + parseFloat($rect.getAttribute('width')) + parseFloat($rect.getAttribute('stroke-width'));
const y2 = y1 + parseFloat($rect.getAttribute('height'));
return !(x1 > area.x2 ||
x2 < area.x1 ||
y1 > area.y2 ||
y2 < area.y1);
}
/**
* #param {SVGElement[]} rects
* #param {SVGElement} $circle
* #return {SVGElement[]}
*/
function findIntersectingRects (rects, $circle) {
let x = parseFloat($circle.getAttribute('cx'));
let y = parseFloat($circle.getAttribute('cy'));
let r = parseFloat($circle.getAttribute('r'));
let box = {
x1: x - r,
y1: y - r,
x2: x + r,
y2: y + r
};
return rects.filter($rect => areIntersecting($rect, box));
}
/*
* Following code is just for the example.
*/
// Get array of `RECT` elements
const $map = document.getElementById('map');
const rects = Array.from($map.querySelectorAll('rect'));
// Get array of `CIRCLE` elements
const $edit = document.getElementById('edit');
const circles = Array.from($edit.querySelectorAll('circle'));
// Change opacity of `RECT` elements that are
// intersecting with `CIRCLE` elements.
circles.forEach($circle => {
findIntersectingRects(rects, $circle).forEach($rect => $rect.setAttribute('style', 'fill-opacity: 0.3'))
});
Test it at https://jsfiddle.net/subw6reL/.
A bit faster implementation
/**
* #typedef Area
* #property {number} x1 X position of top-left
* #property {number} y1 Y position of top-left
* #property {number} x2 X position of bottom-right
* #property {number} y2 Y position of bottom-right
* #property {SVGElement} [$e] optional reference to SVG element
*/
/**
* Besides properties defined below, grid may contain multiple
* objects named after X value of area, and those object may contain
* multiple Areas, named after Y value of those areas.
*
* #typedef Grid
* #property {number} x X position of top-left
* #property {number} y Y position of top-left
* #property {number} w Width of each rect in grid
* #property {number} h Height of each rect in grid
*/
/**
* #param {Grid} grid
* #param {SVGElement} $circle
* #return {SVGElement[]}
*/
function findIntersectingRects (grid, $circle) {
let r = parseFloat($circle.getAttribute('r'));
let x1 = parseFloat($circle.getAttribute('cx')) - r;
let y1 = parseFloat($circle.getAttribute('cy')) - r;
let x2 = x1 + r + r;
let y2 = y1 + r + r;
let gX = x1 - ((x1 - grid.x) % grid.w);
let gY = y1 - ((y1 - grid.y) % grid.h);
var result = [];
while (gX <= x2) {
let y = gY;
let row = grid[gX];
while (row && y <= y2) {
if (row[y]) {
result.push(row[y].$e);
}
y += grid.h;
}
gX += grid.w;
}
return result;
}
/**
* #param {SVGElement[]} rects
* #return {Grid}
*/
function loadGrid (rects) {
const grid = {
x: Infinity,
y: Infinity,
w: Infinity,
h: Infinity
};
rects.forEach($rect => {
let x = parseFloat($rect.getAttribute('x'));
let y = parseFloat($rect.getAttribute('y'));
let w = parseFloat($rect.getAttribute('width')) + parseFloat($rect.getAttribute('stroke-width'));
let h = parseFloat($rect.getAttribute('height'));
grid[x] = grid[x] || {};
grid[x][y] = grid[x][y] || {
x1: x,
y1: y,
x2: x + w,
y2: y + h,
$e: $rect
};
if (grid.w === Infinity) {
grid.w = w;
}
else if (grid.w !== w) {
console.error($rect, 'has different width');
}
if (grid.h === Infinity) {
grid.h = h;
}
else if (grid.h !== h) {
console.error($rect, 'has different height');
}
if (x < grid.x) {
grid.x = x;
}
if (y < grid.y) {
grid.y = y;
}
});
return grid;
}
/*
* Following code is just for the example.
*/
// Get array of `RECT` elements
const $map = document.getElementById('map');
const grid = loadGrid(Array.from($map.querySelectorAll('rect')));
// Get array of `CIRCLE` elements
const $edit = document.getElementById('edit');
const circles = Array.from($edit.querySelectorAll('circle'));
// Change opacity of `RECT` elements that are
// intersecting with `CIRCLE` elements.
circles.forEach($circle => {
findIntersectingRects(grid, $circle).forEach($rect => $rect.setAttribute('style', 'fill-opacity: 0.3'))
});
Test it at https://jsfiddle.net/f2xLq3ka/.
More optimizations possible
Instead of using regular Object for a grid, one could use Array by calculating x and y somewhat like: arrayGrid[rect.x / grid.w][rect.y / grid.h].
Example code above does not make sure that values are rounded, so Math.floor and Math.ceil should be used on calculated values.
If you don't know if map elements will always be of the same size, you could check that at initialization and then prepare findIntersectingRects function optimized for given situation.
Tricks
There's also a trick to draw the grid on canvas, each rectangle with different color (based on rectangle's x and y), and then get the color of pixel at the circle's position/area ;). I doubt that would be faster, but it can be useful in a bit more complicated situations (multi-layered map, with irregular shapes, for example).

Rotate around a changing origin - Javascript

So I have an object rotating around an origin point. Once I rotate and then change the origin point. My object seems to jump positions. After the jump it rotates fine... Need help finding the pattern/why it's jumping and what I need to do to stop it.
Here's the rotation code:
adjustMapTransform = function (_x, _y) {
var x = _x + (map.width/2);
var y = _y + (map.height/2);
//apply scale here
var originPoint = {
x:originXInt,
y:originYInt
};
var mapOrigin = {
x:map.x + (map.width/2),
y:map.y + (map.height/2)
};
//at scale 1
var difference = {
x:mapOrigin.x - originPoint.x,
y:mapOrigin.y - originPoint.y
};
x += (difference.x * scale) - difference.x;
y += (difference.y * scale) - difference.y;
var viewportMapCentre = {
x: originXInt,
y: originYInt
}
var rotatedPoint = {};
var angle = (rotation) * Math.PI / 180.0;
var s = Math.sin(angle);
var c = Math.cos(angle);
// translate point back to origin:
x -= viewportMapCentre.x;
y -= viewportMapCentre.y;
// rotate point
var xnew = x * c - y * s;
var ynew = x * s + y * c;
// translate point back:
x = xnew + viewportMapCentre.x - (map.width/2);
y = ynew + viewportMapCentre.y - (map.height/2);
var coords = {
x:x,
y:y
};
return coords;
}
Also here is a JS Fiddle project that you can play around in to give you a better idea of what's happening.
EDITED LINK - Got rid of the originY bug and scaling bug
https://jsfiddle.net/fionoble/6k8sfkdL/13/
Thanks!
The direction of rotation is a consequence of the sign you pick for the elements in your rotation matrix. [This is Rodrigues formula for rotation in two dimensions]. So to rotate in the opposite direction simply subtract your y cosine term rather than your y sine term.
Also you might try looking at different potential representations of your data.
If you use the symmetric representation of the line between your points you can avoid shifting and instead simply transform your coordinates.
Take your origin [with respect to your rotation], c_0, to be the constant offset in the symmetric form.
You have for a point p relative to c_0:
var A = (p.x - c_0.x);
var B = (p.y - c_0.y);
//This is the symmetric form.
(p.x - c_0.x)/A = (p.y - c_0.y)/B
which will be true under a change of coordinates and for any point on the line (which also takes care of scaling/dilation).
Then after the change of coordinates for rotation you have [noting that this rotation has the opposite sense, not the same as yours].
//This is the symmetric form of the line incident on your rotated point
//and on the center of its rotation
((p.x - c_0.x) * c + (p.y - c_0.y) * s)/A = ((p.x - c_0.x) * s - (p.y - c_0.y) * c)/B
so, multiplying out we get
(pn.x - c_0.x) * B * c + (pn.y - c_0.y) * B * s = (pn.x - c_0.x) * A * s - (pn.y - c_0.y) * A * c
rearrangement gives
(pn.x - c_0.x) * (B * c - A * s) = - (pn.y - c_0.y) * (B * s + A * c)
pn.y = -(pn.x - c_0.x) * (B * c - A * s) / (B * s + A * c) + c_0.y;
for any scaling.

How can I create a WebGL basic firework

I kinda have like a fountain of particles, but I want to make them ''explode'' making more of them where I click like a firework.
var nFireworks = 10000;
function initParticleSystem() {
var particlesData = [];
for (var i= 0; i < nFireworks; i++) {
// angulos del cono
var theta = Math.PI / 6.0 * Math.random();
var phi = 5.0 * Math.PI * Math.random();
// direccion
var x1 = Math.sin(theta) * Math.cos(phi) ;
var y1 = velocity;
var z1 = 0.0;
// velocidad
var alpha = Math.random();
var velocity = (1.4 * alpha) + (0.80 * (1.0 - alpha));
particlesData[i * 4 + 0] = x1 * velocity;
particlesData[i * 4 + 1] = y1 * velocity;
particlesData[i * 4 + 2] = z1 * velocity;
particlesData[i * 4 + 3] = i * 0.095;
}
}
Your code is a bit odd, it uses velocity before it defines it, and you don't actually show the step function or anything else, but hey, I'll give it a go.
Your code (probably) generates a cone of particles where they all move along y at a constant velocity, and the x velocity is spread randomly in a PI/6 wide cone. If you want your particles to spread out in all directions randomly I would suggest starting by changing it like this:
before your for loop, set velocity to a constant first instead of all that nonsense:
var velocity = 5;
Then, you want particles to move outwards from the point in all equally random x and y directions, so change your x and y values to:
var x1 = ((Math.random() - 0.5) * velocity) * 2;
var y1 = ((Math.random() - 0.5) * velocity) * 2;
to form particles where their x and y velocities are random between -velocity and +velocity
Then, I don't know why your code generates particle data in a single array like that, I would make it
particleData.push([x1,y1,z1,i]);
and then reference each particle that way, or a possibly less performant but much more readable:
particleData.push({x: x1, y: y1, z: z1, brightness: i]};
(I'm just going to guess that i is brightness there).
Good luck buddy, it's not really a WebGL question, it's just you asking someone how to write your code for you, but hopefully that helps.

How do I move the starting point of X,Y coordinates plotted on a circle perimeter?

I want to plot a range of points on the lower left section (6 to 9 o'clock) of the perimeter of a circle. However, the starting point of rendering X,Y coordinates always begins at 3 o'clock.
!https://dl.dropboxusercontent.com/u/55849501/plotting-xy.png
Here is the rendering portion of my code:
var items = 5;
for(var i = 0; i < items; i++) {
var x = 96 + 100 * Math.cos(0.665 * Math.PI * i / items);
var y = 96 + 100 * Math.sin(0.665 * Math.PI * i / items);
$("#center").append("<div class='point' style='left:"+ x +"px;top:"+ y +"px'></div>");
}
And here is a jsfiddle of the code in action: http://jsfiddle.net/jE26S/198/
In summary:
I want the points to render starting at the 6 o'clock position instead of the 3 o'clock position.
What you are really doing here is interpolating between two values of theta. In your case, you want to start at Pi/2 and end at Pi. I took the liberty of re-writing your snippet using this interpolation paradigm. Also, you can adjust how far you want the dots/items away from the circle using outerCircleRadius.
var items = 5;
var startTheta = .5 * Math.PI;
var endTheta = 1 * Math.PI;
var outerCircleRadius = 112;
var cx = 90;
var cy = 90;
for(var i = 0; i < items; i++) {
var theta = startTheta + (endTheta - startTheta) * i / (items - 1)
var x = cx + outerCircleRadius * Math.cos(theta);
var y = cy + outerCircleRadius * Math.sin(theta);
$("#center").append("<div class='point' style='left:"+ x +"px;top:"+ y +"px'></div>");
}
Something like this?
var x = 86 + 100 * Math.cos(0.665 * Math.PI * (items-1+i-0.5) / items);
var y = 96 + 100 * Math.sin(0.665 * Math.PI * (items-1+i-0.5) / items);
Everything in Richard Shurtz's answer, except for the "items - 1" in the first line in the loop.
This worked for any number of items:
var items = 5;
var startTheta = .5 * Math.PI;
var endTheta = 1 * Math.PI;
var outerCircleRadius = 112;
var cx = 90;
var cy = 90;
for(var i = 0; i < items; i++) {
var theta = startTheta + (endTheta - startTheta) * i / items
var x = cx + outerCircleRadius * Math.cos(theta);
var y = cy + outerCircleRadius * Math.sin(theta);
$("#center").append("<div class='point' style='left:"+ x +"px;top:"+ y +"px'></div>");
}

Find a point in a polyline which is closest to a latlng

i have a polyine which i have drawn with latlngs obtained from google maps directions service.
Now i want to find a point on the polyline that is closest to a given point.
The obvious way (to me) is to kind of loop through all the points in the polyline and find the distance between them and the given point, however this is inefficient because the points on the polyline can potentially be large.
I would be glad to hear any alternatives of doing this.
Thanks in advance.
I needed a cleaner version that was ported to V3, so here it is:
/**
* Snap marker to closest point on a line.
*
* Based on Distance to line example by
* Marcelo, maps.forum.nu - http://maps.forum.nu/gm_mouse_dist_to_line.html
* Then
* # work of Björn Brala - Swis BV who wrapped the algorithm in a class operating on GMap Objects
* And now
* Bill Chadwick, who factored the basic algorithm out of the class (removing much intermediate storage of results)
* and added distance along line to nearest point calculation
* Followed by
* Robert Crowe, who ported it to v3 of the Google Maps API and factored out the marker to make it more general.
*
* Usage:
*
* Create the class
* var oSnap = new cSnapToRoute();
*
* Initialize the subjects
* oSnap.init(oMap, oPolyline);
*
**/
function cSnapToRoute() {
this.routePoints = Array();
this.routePixels = Array();
this._oMap;
this._oPolyline;
/**
* #desc Initialize the objects.
* #param Map object
* #param GPolyline object - the 'route'
**/
this.init = function (oMap, oPolyline) {
this._oMap = oMap;
this._oPolyline = oPolyline;
this.loadRouteData(); // Load needed data for point calculations
}
/**
* #desc internal use only, Load route points into RoutePixel array for calculations, do this whenever zoom changes
**/
this.loadRouteData = function () {
this.routePixels = new Array();
var proj = this._oMap.getProjection();
for (var i = 0; i < this._oPolyline.getPath().getLength(); i++) {
var Px = proj.fromLatLngToPoint(this._oPolyline.getPath().getAt(i));
this.routePixels.push(Px);
}
}
/**
* #desc Get closest point on route to test point
* #param GLatLng() the test point
* #return new GLatLng();
**/
this.getClosestLatLng = function (latlng) {
var r = this.distanceToLines(latlng);
var proj = this._oMap.getProjection();
return proj.fromPointToLatLng(new google.maps.Point(r.x, r.y));
}
/**
* #desc Get distance along route in meters of closest point on route to test point
* #param GLatLng() the test point
* #return distance in meters;
**/
this.getDistAlongRoute = function (latlng) {
var r = this.distanceToLines(latlng);
return this.getDistToLine(r.i, r.fTo);
}
/**
* #desc internal use only, gets test point xy and then calls fundamental algorithm
**/
this.distanceToLines = function (thisLatLng) {
var tm = this._oMap;
var proj = this._oMap.getProjection();
var thisPx = proj.fromLatLngToPoint(thisLatLng);
var routePixels = this.routePixels;
return getClosestPointOnLines(thisPx, routePixels);
}
/**
* #desc internal use only, find distance along route to point nearest test point
**/
this.getDistToLine = function (iLine, fTo) {
var routeOverlay = this._oPolyline;
var d = 0;
for (var n = 1 ; n < iLine ; n++) {
d += routeOverlay.getPath().getAt(n - 1).distanceFrom(routeOverlay.getPath().getAt(n));
}
d += routeOverlay.getPath().getAt(iLine - 1).distanceFrom(routeOverlay.getPath().getAt(iLine)) * fTo;
return d;
}
}
/* desc Static function. Find point on lines nearest test point
test point pXy with properties .x and .y
lines defined by array aXys with nodes having properties .x and .y
return is object with .x and .y properties and property i indicating nearest segment in aXys
and property fFrom the fractional distance of the returned point from aXy[i-1]
and property fTo the fractional distance of the returned point from aXy[i] */
function getClosestPointOnLines(pXy, aXys) {
var minDist;
var fTo;
var fFrom;
var x;
var y;
var i;
var dist;
if (aXys.length > 1) {
for (var n = 1 ; n < aXys.length ; n++) {
if (aXys[n].x != aXys[n - 1].x) {
var a = (aXys[n].y - aXys[n - 1].y) / (aXys[n].x - aXys[n - 1].x);
var b = aXys[n].y - a * aXys[n].x;
dist = Math.abs(a * pXy.x + b - pXy.y) / Math.sqrt(a * a + 1);
}
else
dist = Math.abs(pXy.x - aXys[n].x)
// length^2 of line segment
var rl2 = Math.pow(aXys[n].y - aXys[n - 1].y, 2) + Math.pow(aXys[n].x - aXys[n - 1].x, 2);
// distance^2 of pt to end line segment
var ln2 = Math.pow(aXys[n].y - pXy.y, 2) + Math.pow(aXys[n].x - pXy.x, 2);
// distance^2 of pt to begin line segment
var lnm12 = Math.pow(aXys[n - 1].y - pXy.y, 2) + Math.pow(aXys[n - 1].x - pXy.x, 2);
// minimum distance^2 of pt to infinite line
var dist2 = Math.pow(dist, 2);
// calculated length^2 of line segment
var calcrl2 = ln2 - dist2 + lnm12 - dist2;
// redefine minimum distance to line segment (not infinite line) if necessary
if (calcrl2 > rl2)
dist = Math.sqrt(Math.min(ln2, lnm12));
if ((minDist == null) || (minDist > dist)) {
if (calcrl2 > rl2) {
if (lnm12 < ln2) {
fTo = 0;//nearer to previous point
fFrom = 1;
}
else {
fFrom = 0;//nearer to current point
fTo = 1;
}
}
else {
// perpendicular from point intersects line segment
fTo = ((Math.sqrt(lnm12 - dist2)) / Math.sqrt(rl2));
fFrom = ((Math.sqrt(ln2 - dist2)) / Math.sqrt(rl2));
}
minDist = dist;
i = n;
}
}
var dx = aXys[i - 1].x - aXys[i].x;
var dy = aXys[i - 1].y - aXys[i].y;
x = aXys[i - 1].x - (dx * fTo);
y = aXys[i - 1].y - (dy * fTo);
}
return { 'x': x, 'y': y, 'i': i, 'fTo': fTo, 'fFrom': fFrom };
}
See Bill Chadwick's example here:
http://www.bdcc.co.uk/Gmaps/BdccGmapBits.htm
above example ported to v3 (code at bottom of this answer)
on his page under:
DISTANCE POINT TO POLYLINE OR POLYGON
from that post:
There is a similar, better demo here http://wtp2.appspot.com/cSnapToRouteDemo.html
It is finding the closest point on the line to the mouse. Also note that it is a Google Maps API v2 example (but the principle with v3 would be the same).
// Code to find the distance in metres between a lat/lng point and a polyline of lat/lng points
// All in WGS84. Free for any use.
//
// Bill Chadwick 2007
// updated to Google Maps API v3, Lawrence Ross 2014
// Construct a bdccGeo from its latitude and longitude in degrees
function bdccGeo(lat, lon)
{
var theta = (lon * Math.PI / 180.0);
var rlat = bdccGeoGeocentricLatitude(lat * Math.PI / 180.0);
var c = Math.cos(rlat);
this.x = c * Math.cos(theta);
this.y = c * Math.sin(theta);
this.z = Math.sin(rlat);
}
bdccGeo.prototype = new bdccGeo();
// internal helper functions =========================================
// Convert from geographic to geocentric latitude (radians).
function bdccGeoGeocentricLatitude(geographicLatitude)
{
var flattening = 1.0 / 298.257223563;//WGS84
var f = (1.0 - flattening) * (1.0 - flattening);
return Math.atan((Math.tan(geographicLatitude) * f));
}
// Returns the two antipodal points of intersection of two great
// circles defined by the arcs geo1 to geo2 and
// geo3 to geo4. Returns a point as a Geo, use .antipode to get the other point
function bdccGeoGetIntersection( geo1, geo2, geo3, geo4)
{
var geoCross1 = geo1.crossNormalize(geo2);
var geoCross2 = geo3.crossNormalize(geo4);
return geoCross1.crossNormalize(geoCross2);
}
//from Radians to Meters
function bdccGeoRadiansToMeters(rad)
{
return rad * 6378137.0; // WGS84 Equatorial Radius in Meters
}
//from Meters to Radians
function bdccGeoMetersToRadians(m)
{
return m / 6378137.0; // WGS84 Equatorial Radius in Meters
}
// properties =================================================
bdccGeo.prototype.getLatitudeRadians = function()
{
return (bdccGeoGeographicLatitude(Math.atan2(this.z,
Math.sqrt((this.x * this.x) + (this.y * this.y)))));
}
bdccGeo.prototype.getLongitudeRadians = function()
{
return (Math.atan2(this.y, this.x));
}
bdccGeo.prototype.getLatitude = function()
{
return this.getLatitudeRadians() * 180.0 / Math.PI;
}
bdccGeo.prototype.getLongitude = function()
{
return this.getLongitudeRadians() * 180.0 / Math.PI ;
}
// Methods =================================================
//Maths
bdccGeo.prototype.dot = function( b)
{
return ((this.x * b.x) + (this.y * b.y) + (this.z * b.z));
}
//More Maths
bdccGeo.prototype.crossLength = function( b)
{
var x = (this.y * b.z) - (this.z * b.y);
var y = (this.z * b.x) - (this.x * b.z);
var z = (this.x * b.y) - (this.y * b.x);
return Math.sqrt((x * x) + (y * y) + (z * z));
}
//More Maths
bdccGeo.prototype.scale = function( s)
{
var r = new bdccGeo(0,0);
r.x = this.x * s;
r.y = this.y * s;
r.z = this.z * s;
return r;
}
// More Maths
bdccGeo.prototype.crossNormalize = function( b)
{
var x = (this.y * b.z) - (this.z * b.y);
var y = (this.z * b.x) - (this.x * b.z);
var z = (this.x * b.y) - (this.y * b.x);
var L = Math.sqrt((x * x) + (y * y) + (z * z));
var r = new bdccGeo(0,0);
r.x = x / L;
r.y = y / L;
r.z = z / L;
return r;
}
// point on opposite side of the world to this point
bdccGeo.prototype.antipode = function()
{
return this.scale(-1.0);
}
//distance in radians from this point to point v2
bdccGeo.prototype.distance = function( v2)
{
return Math.atan2(v2.crossLength(this), v2.dot(this));
}
//returns in meters the minimum of the perpendicular distance of this point from the line segment geo1-geo2
//and the distance from this point to the line segment ends in geo1 and geo2
bdccGeo.prototype.distanceToLineSegMtrs = function(geo1, geo2)
{
//point on unit sphere above origin and normal to plane of geo1,geo2
//could be either side of the plane
var p2 = geo1.crossNormalize(geo2);
// intersection of GC normal to geo1/geo2 passing through p with GC geo1/geo2
var ip = bdccGeoGetIntersection(geo1,geo2,this,p2);
//need to check that ip or its antipode is between p1 and p2
var d = geo1.distance(geo2);
var d1p = geo1.distance(ip);
var d2p = geo2.distance(ip);
//window.status = d + ", " + d1p + ", " + d2p;
if ((d >= d1p) && (d >= d2p))
return bdccGeoRadiansToMeters(this.distance(ip));
else
{
ip = ip.antipode();
d1p = geo1.distance(ip);
d2p = geo2.distance(ip);
}
if ((d >= d1p) && (d >= d2p))
return bdccGeoRadiansToMeters(this.distance(ip));
else
return bdccGeoRadiansToMeters(Math.min(geo1.distance(this),geo2.distance(this)));
}
// distance in meters from GLatLng point to GPolyline or GPolygon poly
function bdccGeoDistanceToPolyMtrs(poly, point)
{
var d = 999999999;
var i;
var p = new bdccGeo(point.lat(),point.lng());
for(i=0; i<(poly.getPath().getLength()-1); i++)
{
var p1 = poly.getPath().getAt(i);
var l1 = new bdccGeo(p1.lat(),p1.lng());
var p2 = poly.getPath().getAt(i+1);
var l2 = new bdccGeo(p2.lat(),p2.lng());
var dp = p.distanceToLineSegMtrs(l1,l2);
if(dp < d)
d = dp;
}
return d;
}
// get a new GLatLng distanceMeters away on the compass bearing azimuthDegrees
// from the GLatLng point - accurate to better than 200m in 140km (20m in 14km) in the UK
function bdccGeoPointAtRangeAndBearing (point, distanceMeters, azimuthDegrees)
{
var latr = point.lat() * Math.PI / 180.0;
var lonr = point.lng() * Math.PI / 180.0;
var coslat = Math.cos(latr);
var sinlat = Math.sin(latr);
var az = azimuthDegrees* Math.PI / 180.0;
var cosaz = Math.cos(az);
var sinaz = Math.sin(az);
var dr = distanceMeters / 6378137.0; // distance in radians using WGS84 Equatorial Radius
var sind = Math.sin(dr);
var cosd = Math.cos(dr);
return new google.maps.LatLng(Math.asin((sinlat * cosd) + (coslat * sind * cosaz)) * 180.0 / Math.PI,
(Math.atan2((sind * sinaz), (coslat * cosd) - (sinlat * sind * cosaz)) + lonr) * 180.0 / Math.PI);
}
I do not think you can avoid checking all the points.
What if the not checked point is the nearest one?
If you have to do this operation many times, you can choose a data structure that is optimized for such a search, quadtree for example.
Note that you should not use lat lng as Descartes coordinates.
See also Finding nearest point in an efficient way
That is for the 2D plane, and not for lat lng, but you can approximate: https://stackoverflow.com/a/16271669/59019
Inspired by jmihalicza's answer, i came up with this function to find the closest point in an array of LatLngs to a given LatLng.
function closest takes a LatLng(llng) and an array of LatLngs (listData) and finds the distance between each latlng in the array and the given latlng, it then finds the least distance and returns the Latlng from the list which provided that distance.
function closest(llng, listData) {
var arr = listData;
var pnt = llng;
var distArr = [];
var dist = google.maps.geometry.spherical.computeDistanceBetween;
for (index in arr)
distArr.push([arr[index], dist(pnt, arr[index])]);
return distArr.sort(function(a,b){
return a[1]-b[1];
})[0][0];
}
EDIT
If you don't have access to the array of LatLngs which make up the polyline, but have access to the polyline itself, you can use polyline's getPath method to get the path which is an MVC array so you can use .getArray() to return an array of LatLngs to use with the above function (closest).

Categories