My question probably has an easy answer but I can't seem to find a solution for my problem.
I have some code to do a put request to the phillips hue api and it requires:
{"xy":[0.300,0.300]} but JSON.stringify() returns {"xy":["0.300","0.300"]}
how can I get it to return the correct values?
Here is my code:
function AllRed() {
var url = 'http://192.168.168.124/api/husm18zj4JGeeHxoUKwDrCVfKRw6nicB25dLnYHX/groups/*/action';
var r = 255;
var g = 0;
var b = 0;
var model = 'LCT001';
var data = JSON.stringify({'xy': RGBtoXY(r, g, b, model)});//RGBtoXY(r, g, b, model)
$.put(url, data);
}
And here is the code for RGBtoXY which I got from another thread here on stackoverflow, unfortunately I can't find the specific thread anymore.
function XYPoint(x, y)
{
if (this instanceof XYPoint)
{
this.x = x;
this.y = y;
} else
{
return new XYPoint(x, y);
}
}
/**
* Get Color points according to light model
* #param model string Ex: LLC010
* #returns {Array}
*/
function colorPointsForModel(model)
{
var colorPoints = [];
if (model === 'LCT001')
{
colorPoints.push(XYPoint(0.500, 0.322));
colorPoints.push(XYPoint(0.4091, 0.518));
colorPoints.push(XYPoint(0.167, 0.04));
} else if (model === 'LLC006' || model === 'LLC007')
{
colorPoints.push(XYPoint(0.704, 0.296));
colorPoints.push(XYPoint(0.2151, 0.7106));
colorPoints.push(XYPoint(0.138, 0.08));
} else
{
// Default construct triangle wich contains all values
colorPoints.push(XYPoint(1.0, 0.0));
colorPoints.push(XYPoint(0.0, 1.0));
colorPoints.push(XYPoint(0.0, 0.0));
}
return colorPoints;
}
/**
* Method to see if the given XY value is within the reach of the lamps.
*
* #param p the point containing the X,Y value
* #param colorPoints color points array containing RGB XYPoints
* #return true if within reach, false otherwise.
*/
function checkPointInLampsReach(p, colorPoints)
{
var red = colorPoints[0];
var green = colorPoints[1];
var blue = colorPoints[2];
var v1 = XYPoint(green.x - red.x, green.y - red.y);
var v2 = XYPoint(blue.x - red.x, blue.y - red.y);
var q = XYPoint(p.x - red.x, p.y - red.y);
var s = crossProduct(q, v2) / crossProduct(v1, v2);
var t = crossProduct(v1, q) / crossProduct(v1, v2);
return ((s >= 0.0) && (t >= 0.0) && (s + t <= 1.0));
}
/**
* Is Not a number?
* Note: NaN is the only JavaScript value that is treated as unequal to itself
* #param val
* #returns {boolean}
*/
function isNaN(val)
{
return val !== val;
}
/**
* Calculates crossProduct of two 2D vectors / points.
*
* #param p1 first point used as vector
* #param p2 second point used as vector
* #return crossProduct of vectors
*/
function crossProduct(p1, p2)
{
return (p1.x * p2.y - p1.y * p2.x);
}
/**
* Converts RGB to XY and Brightness
* #param r integer 0-255
* #param g integer 0-255
* #param b integer 0-255
* #param model string
*/
function RGBtoXY(red, green, blue, model)
{
if (red > 1 || green > 1 || blue > 1)
{
red /= 255;
green /= 255;
blue /= 255;
}
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
var X = red * 0.649926 + green * 0.103455 + blue * 0.197109;
var Y = red * 0.234327 + green * 0.743075 + blue * 0.022598;
var Z = red * 0.0000000 + green * 0.053077 + blue * 1.035763;
var cx = X / (X + Y + Z);
var cy = Y / (X + Y + Z);
if (isNaN(cx)) {
cx = 0.0;
}
if (isNaN(cy)) {
cy = 0.0;
}
//Check if the given XY value is within the colourreach of our lamps.
var xyPoint = XYPoint(cx, cy);
var colorPoints = colorPointsForModel(model);
var inReachOfLamps = checkPointInLampsReach(xyPoint, colorPoints);
if (!inReachOfLamps)
{
//It seems the colour is out of reach
//let's find the closest colour we can produce with our lamp and send this XY value out.
//Find the closest point on each line in the triangle.
var pAB = getClosestPointToPoints(colorPoints[cptRED], colorPoints[cptGREEN], xyPoint);
var pAC = getClosestPointToPoints(colorPoints[cptBLUE], colorPoints[cptRED], xyPoint);
var pBC = getClosestPointToPoints(colorPoints[cptGREEN], colorPoints[cptBLUE], xyPoint);
//Get the distances per point and see which point is closer to our Point.
var dAB = getDistanceBetweenTwoPoints(xyPoint, pAB);
var dAC = getDistanceBetweenTwoPoints(xyPoint, pAC);
var dBC = getDistanceBetweenTwoPoints(xyPoint, pBC);
var lowest = dAB;
var closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
lowest = dBC;
closestPoint = pBC;
}
//Change the xy value to a value which is within the reach of the lamp.
cx = closestPoint.x;
cy = closestPoint.y;
}
retval = [cx.toPrecision(3), cy.toPrecision(3)];
return retval;
}
/**
* Find the closest point on a line.
* This point will be within reach of the lamp.
*
* #param A the point where the line starts
* #param B the point where the line ends
* #param P the point which is close to a line.
* #return the point which is on the line.
*/
function getClosestPointToPoints(A, B, P)
{
var AP = XYPoint(P.x - A.x, P.y - A.y);
var AB = XYPoint(B.x - A.x, B.y - A.y);
var ab2 = AB.x * AB.x + AB.y * AB.y;
var ap_ab = AP.x * AB.x + AP.y * AB.y;
var t = ap_ab / ab2;
if (t < 0.0) {
t = 0.0;
} else if (t > 1.0) {
t = 1.0;
}
return XYPoint(A.x + AB.x * t, A.y + AB.y * t);
}
/**
* Find the distance between two points.
*
* #param one
* #param two
* #return the distance between point one and two
*/
// + (float)getDistanceBetweenTwoPoints:(CGPoint)one point2:(CGPoint)two {
function getDistanceBetweenTwoPoints(one, two)
{
var dx = one.x - two.x; // horizontal difference
var dy = one.y - two.y; // vertical difference
return Math.sqrt(dx * dx + dy * dy);
}
function XYtoRGB(x, y, brightness, model)
{
var xy = XYPoint(x, y);
var colorPoints = colorPointsForModel(model);
var inReachOfLamps = checkPointInLampsReach(xy, colorPoints);
console.log('inReachOfLamps', inReachOfLamps);
if (!inReachOfLamps) {
//It seems the colour is out of reach
//let's find the closest colour we can produce with our lamp and send this XY value out.
//Find the closest point on each line in the triangle.
var pAB = getClosestPointToPoints(colorPoints[cptRED], colorPoints[cptGREEN], xy);
var pAC = getClosestPointToPoints(colorPoints[cptBLUE], colorPoints[cptRED], xy);
var pBC = getClosestPointToPoints(colorPoints[cptGREEN], colorPoints[cptBLUE], xy);
//Get the distances per point and see which point is closer to our Point.
var dAB = getDistanceBetweenTwoPoints(xy, pAB);
var dAC = getDistanceBetweenTwoPoints(xy, pAC);
var dBC = getDistanceBetweenTwoPoints(xy, pBC);
var lowest = dAB;
var closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
lowest = dBC;
closestPoint = pBC;
}
//Change the xy value to a value which is within the reach of the lamp.
xy.x = closestPoint.x;
xy.y = closestPoint.y;
}
var x = xy.x;
var y = xy.y;
var z = 1.0 - x - y;
var Y = brightness;
var X = (Y / y) * x;
var Z = (Y / y) * z;
var r = X * 3.2410 - Y * 1.5374 - Z * 0.4986;
var g = -X * 0.9692 + Y * 1.8760 + Z * 0.0416;
var b = X * 0.0556 - Y * 0.2040 + Z * 1.0570;
r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055;
g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055;
b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055;
if (r < 0)
{
r = 0;
}
if (g < 0)
{
g = 0;
}
if (b < 0)
{
b = 0;
}
if (r > 1 || g > 1 || b > 1)
{
var max = Math.max(r, g, b);
r /= max;
g /= max;
b /= max;
}
r *= 255;
g *= 255;
b *= 255;
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
return {
r: r,
g: g,
b: b
};
}
Your "problem" is this line
retval = [cx.toPrecision(3), cy.toPrecision(3)];
As already noted in comments toPrecision() returns a string, which when serialized will result in the ".
So either drop the toPrecision() calls here, or convert to a number again later:
var xy = RGBtoXY(r, g, b, model);
// convert to numbers again
xy = xy.map( Number );
var data = JSON.stringify({'xy': xy });//RGBtoXY(r, g, b, model)
Related
I tried using this:
function getRandomInRange(from, to, fixed) {
return parseFloat((Math.random() * (to - from) + from).toFixed(fixed));
}
var latLongPairs = 4;
for(var i =0; i<latLongPairs; i++) {
console.log(`${getRandomInRange(-180, 180, 3)}, ${getRandomInRange(-180, 180, 3)}`);
}
That's ok for random numbers like 39.21988,9.124741 but they might end up not valid coordinates. And I need 100 of them.
Your latitude range is incorrect - it should be ±90.
function getRandomInRange(from, to) {
return Math.random() * (to - from) + from;
}
const latLongPairs = 4;
for (let i = 0; i < latLongPairs; ++i) {
let lat = getRandomInRange(-90, 90);
let lon = getRandomInRange(-180, 180);
console.log(`${lat.toFixed(3)}, ${lon.toFixed(3)}`);
}
You might need to provide some initial Latitude and longitude with some radius (in meters)
var getRandomLocation = function(latitude, longitude, radiusInMeters) {
var getRandomCoordinates = function(radius, uniform) {
// Generate two random numbers
var a = Math.random(),
b = Math.random();
// Flip for more uniformity.
if (uniform) {
if (b < a) {
var c = b;
b = a;
a = c;
}
}
// It's all triangles.
return [
b * radius * Math.cos(2 * Math.PI * a / b),
b * radius * Math.sin(2 * Math.PI * a / b)
];
};
var randomCoordinates = getRandomCoordinates(radiusInMeters, true);
// Earths radius in meters via WGS 84 model.
var earth = 6378137;
// Offsets in meters.
var northOffset = randomCoordinates[0],
eastOffset = randomCoordinates[1];
// Offset coordinates in radians.
var offsetLatitude = northOffset / earth,
offsetLongitude = eastOffset / (earth * Math.cos(Math.PI * (latitude / 180)));
return `${latitude + (offsetLatitude * (180 / Math.PI))}, ${longitude + (offsetLongitude * (180 / Math.PI))}`
};
for (i = 0; i < 182; i++) {
console.log(getRandomLocation(41.8819, -87.6278, 50))
}
This may be more of a mathematics problem, but maybe there is a simple javascript solution that I am missing.
I want to plot an ellipse on html canvas from user input of a center point, radius of the major (longest) axis, and 2 points will fall on the ellipse.
This should potentially create 2 possible ellipse paths, both of which will center around the center point, and cross through the 2 points.
So for example, if the center = [2, 1] major axis radius a = 10, point 1 u = [4, 2] and point 2 v = [5, 6], what is the minor axis radius b and angle of rotation theta?
So far I have tried to implement an equation that I found from https://math.stackexchange.com/questions/3210414/find-the-angle-of-rotation-and-minor-axis-length-of-ellipse-from-major-axis-leng,
but it does not return valid values. My javascript code looks like this:
function getEllipseFrom2Points(center, u, v, a) {
function getSlope(plusOrMinus) {
return Math.sqrt(((uy * vx - ux * vy) ** 2) / (-ux * uy * (a * (v2x + v2y) - 1) + vx * vy * (a * (u2x + u2y) - 1) - plusOrMinus * (uy * vx - ux * vy) * q) / (u2x * (1 - a * v2y) + v2x * (a * u2y - 1)));
}
function getMinorAxis(plusOrMinus) {
return (u2x + u2y + v2x + v2y - a * (2 * u2x * v2x + 2 * u2y * v2y + 2 * ux * uy * vx * vy + u2y * v2x + u2x * v2y) + plusOrMinus * 2 * (ux * vx + uy * vy) * q);
}
var vx = v[0],
vy = v[1],
ux = u[0],
uy = u[1],
v2x = vx ** 2,
v2y = vy ** 2,
u2x = ux ** 2,
u2y = uy ** 2,
q = Math.sqrt((1 - a * (u2x + u2y)) * (1 - a * (v2x + v2y))),
ellipse1 = { rx: a, ry: getMinorAxis(1), origin: center, rotation: getSlope(1) },
ellipse2 = { rx: a, ry: getMinorAxis(-1), origin: center, rotation: getSlope(-1) };
}
Either the equation that I am following is wrong, or I have implemented it wrong
In case anyone is interested, here is my solution to the problem, which isn't really "the" solution. If anyone can solve this I would still be happy to know.
Since I can't solve for both slope of the major axis and length of the minor axis, I just take a guess at slope and then test how close it is, and then refine the result by trying in a smaller and smaller region. Since the final ellipse that gets drawn is actually an estimation constructed from bezier curves, I can get close enough in a reasonable amount of time.
function getEllipseFrom2Points (center, u, v, a) {
function getSemiMinorAxis([x, y], a, t) {
// equation for rotated ellipse
// b = a(ycos(t) - xsin(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t)) and
// b = a(xsin(t) - ycos(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t))
// where a^2 !== (xcos(t) + ysin(t))^2
// and aycos(t) !== axsin(t)
if (a ** 2 !== (x * Math.cos(t) + y * Math.sin(t)) ** 2 &&
a * y * Math.cos(t) !== a * x * Math.sin(t)) {
var b = [],
q = (Math.sqrt(a ** 2 - x ** 2 * (Math.cos(t)) ** 2 - 2 * x * y * Math.sin(t) * Math.cos(t) - y ** 2 * (Math.sin(t)) ** 2));
b[0] = (a * (y * Math.cos(t) - x * Math.sin(t))) / q;
b[1] = (a * (x * Math.sin(t) - y * Math.cos(t))) / q;
return b;
}
}
function getAngle_radians(point1, point2){
return Math.atan2(point2[1] - point1[1], point2[0] - point1[0]);
}
function getDistance(point1, point2) {
return Math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2);
}
function rotatePoint(point, center, radians) {
var x = (point[0] - center[0]) * Math.cos(radians) - (point[1] - center[1]) * Math.sin(radians) + center[0];
var y = (point[1] - center[1]) * Math.cos(radians) + (point[0] - center[0]) * Math.sin(radians) + center[1];
return [x, y];
}
function measure(ellipseRotation, pointOnEllipse, minorAxisLength) {
var d = getDistance(point, pointOnEllipse);
if (d < bestDistanceBetweenPointAndEllipse) {
bestDistanceBetweenPointAndEllipse = d;
bestEstimationOfB = minorAxisLength;
bestEstimationOfR = ellipseRotation;
}
}
function getBestEstimate(min, max) {
var testIncrement = (max - min) / 10;
for (let r = min; r < max; r = r + testIncrement) {
if (radPoint1 < r && radPoint2 < r || radPoint1 > r && radPoint2 > r) {//points both on same side of ellipse
semiMinorAxis = getSemiMinorAxis(v, a, r);
if (semiMinorAxis) {
for (let t = 0; t < circle; t = t + degree) {
ellipsePoint1 = [a * Math.cos(t), semiMinorAxis[0] * Math.sin(t)];
ellipsePoint2 = [a * Math.cos(t), semiMinorAxis[1] * Math.sin(t)];
point = rotatePoint(u, [0, 0], -r);
measure(r, ellipsePoint1, semiMinorAxis[0]);
measure(r, ellipsePoint2, semiMinorAxis[1]);
}
}
}
}
count++;
if (new Date().getTime() - startTime < 200 && count < 10) //refine estimate
getBestEstimate(bestEstimationOfR - testIncrement, bestEstimationOfR + testIncrement);
}
if (center instanceof Array &&
typeof center[0] === "number" &&
typeof center[1] === "number" &&
u instanceof Array &&
typeof u[0] === "number" &&
typeof u[1] === "number" &&
v instanceof Array &&
typeof v[0] === "number" &&
typeof v[1] === "number" &&
typeof a === "number") {
// translate points
u = [u[0] - center[0], u[1] - center[1]];
v = [v[0] - center[0], v[1] - center[1]];
var bestDistanceBetweenPointAndEllipse = a,
point,
semiMinorAxis,
ellipsePoint1,
ellipsePoint2,
bestEstimationOfB,
bestEstimationOfR,
radPoint1 = getAngle_radians([0, 0], v),
radPoint2 = getAngle_radians([0, 0], u),
circle = 2 * Math.PI,
degree = circle / 360,
startTime = new Date().getTime(),
count = 0;
getBestEstimate(0, circle);
var ellipseModel = MakerJs.$(new MakerJs.models.Ellipse(a, bestEstimationOfB))
.rotate(MakerJs.angle.toDegrees(bestEstimationOfR), [0, 0])
.move(center)
.originate([0, 0])
.$result;
return ellipseModel;
}
I have the following mesh which is generated by random points and creating triangles using Delaunay triangulation. Then I apply spring force per triangle on each of its vertices. But for some reason the equilibrium is always shifted to the left.
Here is a video of the behaviour:
https://youtu.be/gb5aj05zkIc
Why this is happening?
Here is the code for the physics:
for ( let i=0; i < mesh.geometry.faces.length; i++) {
let face = mesh.geometry.faces[i];
let a = mesh.geometry.vertices[face.a];
let b = mesh.geometry.vertices[face.b];
let c = mesh.geometry.vertices[face.c];
let p1 = Vertcies[face.a];
let p2 = Vertcies[face.b];
let p3 = Vertcies[face.c];
update_force_points(p1, p2, a, b);
update_force_points(p1, p3, a, c);
update_force_points(p2, p3, b, c);
}
function update_force_points(p1, p2, p1p, p2p) {
// get all the verticies
var dx = (p1.x - p2.x);
var dy = (p1.y - p2.y);
var len = Math.sqrt(dx*dx + dy*dy);
let fx = (ks * (len - r) * (dx/len)) + ((kd * p2.vx - p1.vx));
let fy = (ks * (len - r) * (dy/len)) + ((kd * p2.vy - p1.vy));
if ( ! p1.fixed ) {
p1.fx = (ks * (len - r) * (dx/len)) + ((kd * p2.vx - p1.vx));
p1.fy = (ks * (len - r) * (dy/len)) + ((kd * p2.vy - p1.vy));
}
if ( ! p2.fixed ) {
p2.fx = -1 * p1.fx;
p2.fy = -1 * p1.fy;
}
p1.vx += p1.fx / mass;
p1.vy += p1.fy / mass;
p2.vx += p2.fx / mass;
p2.vy += p2.fy / mass;
p1.x += p1.vx;
p1.y += p1.vy;
p2.x += p2.vx;
p2.y += p2.vy;
p1p.x = p1.x;
p1p.y = p1.y;
p2p.x = p2.x;
p2p.y = p2.y;
p2p.z = 0.0;
p1p.z = 0.0;
}
At the moment you're doing velocity calculations and assigning new positions at the same time, so the balance will change depending on the order that you cycle through points in. I would guess that points at the bottom left are either at the beginning of the vertex list, or at the end.
try doing all the p#.vx calculations linearly, then do a second pass where you just do p#.x += p#.vx
that way you calculate all necessary velocities based on a snapshot of where points were the previous frame, then you update their positions after all points have new velocities.
So do:
for(var i = 0; i < #; i++){
updateforces(bla,bla,bla) //don't assign position in here, just add forces to the velocity
}
for(var i =0; i < #; i++){
updateposition(bla,bla,bla)
}
I am using this library to track colored objects: http://trackingjs.com/examples/color_camera.html.
The library seems to be iterating over an array of pixels, that is feed off the webcam and track the colors.
The issue is that this algorithm is painstakingly slow. I am trying
to use this detection as part of a simple 2D game I am building on my
canvas and it slows it down to a crawl.
The game I am building will be run on only 1 PC, so I have full
control over which browser I can use.
It's obvious that this performance impact is caused by the loops that
are searching for the colors on the canvas.
I have figured that maybe I can use Google's NaCl (Native Client) to iterate over the 'pixel arrays' instead of Javascript. From what I know, a compiled language such as C++ is much faster than an interpreted language such as Javascript.
So the scenario is that I take the webcam feed, post it as a message to NaCl and then get back the detection coordinates.
The question is this:
Would I benefit from such a scenario?
PS: The reason I am asking instead of directly testing is because I never touched compiled languages. I come strictly from a WebDev background and I need to get some suggestions if this thing is worth trying or not, before I get my hands dirty with a brand new paradigm
The current source code:
In any case, this is the source code where the color tracking happens:
tracking.ColorTracker.prototype.calculateDimensions_ = function(cloud, total) {
var maxx = -1;
var maxy = -1;
var minx = Infinity;
var miny = Infinity;
for (var c = 0; c < total; c += 2) {
var x = cloud[c];
var y = cloud[c + 1];
if (x < minx) {
minx = x;
}
if (x > maxx) {
maxx = x;
}
if (y < miny) {
miny = y;
}
if (y > maxy) {
maxy = y;
}
}
return {
width: maxx - minx,
height: maxy - miny,
x: minx,
y: miny
};
};
/**
* Gets the colors being tracked by the `ColorTracker` instance.
* #return {Array.<string>}
*/
tracking.ColorTracker.prototype.getColors = function() {
return this.colors;
};
/**
* Gets the minimum dimension to classify a rectangle.
* #return {number}
*/
tracking.ColorTracker.prototype.getMinDimension = function() {
return this.minDimension;
};
/**
* Gets the maximum dimension to classify a rectangle.
* #return {number}
*/
tracking.ColorTracker.prototype.getMaxDimension = function() {
return this.maxDimension;
};
/**
* Gets the minimum group size to be classified as a rectangle.
* #return {number}
*/
tracking.ColorTracker.prototype.getMinGroupSize = function() {
return this.minGroupSize;
};
/**
* Gets the eight offset values of the neighbours surrounding a pixel.
* #param {number} width The image width.
* #return {array} Array with the eight offset values of the neighbours
* surrounding a pixel.
* #private
*/
tracking.ColorTracker.prototype.getNeighboursForWidth_ = function(width) {
if (tracking.ColorTracker.neighbours_[width]) {
return tracking.ColorTracker.neighbours_[width];
}
var neighbours = new Int32Array(8);
neighbours[0] = -width * 4;
neighbours[1] = -width * 4 + 4;
neighbours[2] = 4;
neighbours[3] = width * 4 + 4;
neighbours[4] = width * 4;
neighbours[5] = width * 4 - 4;
neighbours[6] = -4;
neighbours[7] = -width * 4 - 4;
tracking.ColorTracker.neighbours_[width] = neighbours;
return neighbours;
};
/**
* Unites groups whose bounding box intersect with each other.
* #param {Array.<Object>} rects
* #private
*/
tracking.ColorTracker.prototype.mergeRectangles_ = function(rects) {
var intersects;
var results = [];
var minDimension = this.getMinDimension();
var maxDimension = this.getMaxDimension();
for (var r = 0; r < rects.length; r++) {
var r1 = rects[r];
intersects = true;
for (var s = r + 1; s < rects.length; s++) {
var r2 = rects[s];
if (tracking.Math.intersectRect(r1.x, r1.y, r1.x + r1.width, r1.y + r1.height, r2.x, r2.y, r2.x + r2.width, r2.y + r2.height)) {
intersects = false;
var x1 = Math.min(r1.x, r2.x);
var y1 = Math.min(r1.y, r2.y);
var x2 = Math.max(r1.x + r1.width, r2.x + r2.width);
var y2 = Math.max(r1.y + r1.height, r2.y + r2.height);
r2.height = y2 - y1;
r2.width = x2 - x1;
r2.x = x1;
r2.y = y1;
break;
}
}
if (intersects) {
if (r1.width >= minDimension && r1.height >= minDimension) {
if (r1.width <= maxDimension && r1.height <= maxDimension) {
results.push(r1);
}
}
}
}
return results;
};
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).