Finding Points in a Rectangle or Circle with mysql - php

I have a mysql database table with a list points with their Co-ordinates (x,y)
I want to find the list of points which fall inside the rectangle. This would have been simple had any one side of the rectangle been aligned parallel or perpendicular to any axis. But is not. Which means the rectangle is rotated.
I also have to find the points inside a circle.
Known Data for Rectangle
-Coordinates for all the four points
Known Data for Circle
-Co-ordinates for the center and the radius.
How do I query the mysql table to find the points falling in the rectangle and the circle?
If it matters then the front end I am using is PHP.

A rectangle can be defined by two points representing the opposing corners, eg: A(x,y) and B(x,y). If you have a point C(x,y) that you want to test to see if it is inside the rectangle then:
IF( (Cx BETWEEN Ax AND Bx) AND (Cy BETWEEN Ay AND By) ) THEN
point C is in the rectangle defined by points A and B
ELSE
nope
ENDIF
A circle can be defined by a single point C(x,y) and a radius R. If the distance D between the center and the point P(x,y) is less than the radius R, then it is inside the circle:
And of course you remember the Pythagorean Theoreom, right?
C² = A² + B² SO C = SQRT(A² + B²)
So:
D = SQRT( ABS(Cx - Px)² + ABS(Cy - Py)²)
IF( D <= R ) THEN
point P is inside the circle with center C and radius R
ELSE
nope
ENDIF
edit:
The algorithm for checking if a point is within a polygon is a bit more complex than what I'd prefer to write in a SQL query or stored procedure, but it is entirely possible. It's worth noting that it runs in constant-time and is very lightweight. [requires roughly 6 arithmetic ops and maybe 2 or 3 logic ops for each point in the poly]
To pare down the number calculations required you can simply write your select to get points within a rough bounding box before procesing them further:
WHERE
x BETWEEN MIN(x1,x2,x3,x4) AND MAX(x1,x2,x3,x4)
AND
y BETWEEN MIN(y1,y2,y3,y4) AND MAX(y1,y2,y3,y4)
Assuming the columns containing the x and y values are indexed this might use a few less CPU cycles than simply doing the math, but it's debatable and I'm inclined to call it a wash.
As for the circle you can't possibly get more efficient than
WHERE
SQRT( POW(ABS($Cx - x),2) + POW(ABS($Cy - y),2) ) < $radius
You're far too concerned with the perceived cost of these calculations, just write the code and get it working. This is not the stage to be performing such niggling optimizations.

One thing to add to #Sammitch's answer is, calculating haversine distance in case you are looking at latitudes and longitudes on world map (distance calculation on a spherical surface, since Earth is a sphere ;) https://en.wikipedia.org/wiki/Haversine_formula)
Here's a vanilla Javascript example for calculating that:
function calculateHaversineDistance(lat1x, lon1, lat2x, lon2) {
var R = 6371; // km
var dLat = toRad(lat2x-lat1x);
var dLon = toRad(lon2-lon1);
var lat1 = toRad(lat1x);
var lat2 = toRad(lat2x);
var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
function toRad(x) {
return x * Math.PI / 180;
}
EDIT->
Here's a php version I wrote:
function toRad($x) {
return $x * pi() / 180;
}
function calculateHaversineDistance($lat1, $lon1, $lat2, $lon2) {
$R = 6371; // km
$dLat = $this->toRad($lat2-$lat1);
$dLon = $this->toRad($lon2-$lon1);
$lat1 = $this->toRad($lat1);
$lat2 = $this->toRad($lat2);
$a = sin($dLat/2) * sin($dLat/2) +
sin($dLon/2) * sin($dLon/2) * cos($lat1) * cos($lat2);
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
return $R * $c;
}

Related

Get Random XYZ on surface of Sphere from Sphere Loc and Radius

I am trying to get random X Y Z coordinates on the surface of a sphere, based on the spheres center in X Y Z, and it's radius in PHP. Currently, my system is just purely random, hoping for a position on the planets center. I'm inept in math so please bear with me (dyscalculia and dyslexia).
Right now I have just random chaos as follows (note randint is a custom function that checks for rand_int, mt_rand and rand and uses the newest.
$nx = randint( abs( $poo['x'] - $max_distance_x ), abs( $poo['x'] + $max_distance_x ) );
$ny = randint( abs( $poo['y'] - $max_distance_y ), abs( $poo['y'] + $max_distance_y ) );
$nz = randint( abs( $poo['z'] - $max_distance_z ), abs( $poo['z'] + $max_distance_z ) );
but I have the planet radius $pradius, and planet XYZ center array $poo poo(x, y, z), and I think I can get random surface X Y Z, just not sure. I've looked at other languages but am having trouble understanding anything to port.
Update
Based on the two methods provided in an answer I have come up with the following two functions. However both to not function correctly.
The following function only produces craters (points) on the top of the planetoid (north pole), even though it should be calculating from planetoid center at it's radius.
function basic_spheroid_point( $cx, $cy, $cz, $r ) {
$x = randint(-$r, $r);
$y = randint(-$r, $r);
$z = randint(-$r, $r);
$dx = $x - $cx;
$dy = $y - $cy;
$dz = $z - $cz;
$dd = sqrt( ( $dx * $dx ) + ( $dy * $dy ) + ( $dz * $dz ) );
if ( $dd > $r )
return basic_spheroid_point( $cx, $cy, $cz, $r );
$dx = $r * $dx / $dd;
$dy = $r * $dy / $dd;
$dz = $r * $dz / $dd;
return array( $cx + $dx, $cy + $dy, $cz + $dz );
}
This function bunches craters at the North Pole of the planet, though there is also global coverage craters so it is working partially.
function uniform_spheroid_point($cx, $cy, $cz, $r) {
$ap = randint( 0, 359 );
$aq = randint( 0, 359 );
$dx = $r* cos( $ap ) * cos( $aq );
$dy = $r * sin( $aq );
$dz = $r * sin( $ap ) * cos( $aq );
return array( $cx + $dx, $cy + $dy, $cz + $dz );
}
You're currently selecting a point at random from the interior of a parallelepiped with side lengths 2*max_distance_x, 2*max_distance_y and 2*max_distance_z. Let us assume that these are all the same and equivalent to the radius r of the sphere on whose surface you'd like to randomly select points. Then, your method would select randomly from a cube of side length 2r. Essentially none of your random points will actually be on the surface of the sphere of radius r when selected in this way.
However - given a point in the cube, what you can do is this:
calculate the vector from the center point to your random point (the "delta"). For example, if your center point is 100,100,100 and your radius is 100 and you randomly pick point 50,100,150, the vector you're looking for is -50,0,50.
calculate the length of the vector from step 1. This uses the formula for the distance between two points, extended so that it contemplates the point's three coordinates: sqrt(dx^2 + dy^2 +dz^2). For example, our distance is sqrt((-50)^2 + 0^2 + 50^2) = sqrt(2500 + 0 + 2500) = 50sqrt(2).
scale the vector from step 1 by a factor equal to r/d where d is the vector's length as determined in step 2. For our example, we will scale the vector by a factor of r/d = 100/(50sqrt(2)) = 2/sqrt(2) = sqrt(2). This gives -50sqrt(2), 0, 50sqrt(2).
Now, add the scaled vector from step 3 to the center point to get a point on the surface of the sphere of radius r. In our example, the point on the surface of the sphere is 100-50sqrt(2), 100, 100+50sqrt(2).
Now the only issue with this is that some points are more likely to be chosen than others. The reason for this is that some points on the surface of the sphere have more of the cube outside them than other points do. Specifically, a point of the sphere lying on the bounding cube has no points further outside it, but the point on the sphere that intersects a line connecting the center of the cube and one of its corners has a lot of space outside the sphere). To get a truly uniform distribution of points, you'd need to exclude any points selected at random which do not lie inside or on the surface of the sphere. If you do that, you will get a uniform distribution of points by the above method. Because the volume of the cube is 8r^3 and the volume of the sphere is 4/3pir^3, and because 4/3pi ~ 4, you have about a 50% chance at each draw of getting a point that you have to throw away. On average, you'd expect to get one good point in every two draws. You should not typically need many random draws to get a good one, but it is technically unbounded.
If you want to make sure every random draw is a good one, I might suggest selecting two angles uniformly at random from 0 to 360 degrees. Then, use those angles to identify a point on the sphere. So, for instance, suppose you first draw angle p and then angle q. Angle p can determine the plain from which the point will be taken. This plane will intersect the sphere in a circular cross section. Angle q can then determine which point on this intersected circle is returned as the random point. Suppose these angles give point (x', y', z'). Well...
y' = r*sin(q) … since nothing else determines the y coordinate except q
x' = r*cos(p)*cos(q)
z' = r*sin(p)*cos(q)
This has the advantage of not needing to reject any random samples, and the disadvantage of requiring relatively more costly trigonometric operations.
EDIT: Pseudocode for each method
Method 1:
RandomPointOnSphere(centerX, centerY, centerZ, radius)
1. x = random(-radius, radius)
2. y = random(-radius, radius)
3. z = random(-radius, radius)
4. dx = x - centerX
5. dy = y - centerY
6. dz = z - centerZ
7. dd = sqrt(dx*dx + dy*dy + dz*dz)
8. if dd > radius then return RandomPointOnSphere(centerX, centerY, centerZ, radius)
9. dx = radius * dx / dd
10. dy = radius * dy / dd
11. dz = radius * dz / dd
12. return (centerX + dx, centerY + dy, centerZ + dz)
Method 2:
RandomPointOnSphere(centerX, centerY, centerZ, radius)
1. angleP = random(0, 359)
2. angleQ = random(0, 359)
3. dx = radius* cos(angleP) * cos(angleQ)
4. dy = radius * sin(angleQ)
5. dz = radius * sin(angleP) * cos(angleQ)
6. return (centerX + dx, centerY + dy, centerZ + dz)

Distance between street and point, if latitude and longitude

I have 3 latitudes and longitude:
x :
1.000000000
1.000000000
y :
2.000000000
2.000000000
z :
3.000000000
3.000000000
I need to calculate the smallest distance between Z and the line formed by X -> Y, PLEASE HELP
PHP code that solve my problem until now:
function calc ($a, $ay, $b, $by,$c, $cy) {
$a = array($a, $ay, 0);// i use 0 altitude always
$b = array($b, $by, 0);
$c = array($c, $cy, 0);
$ab = array(
(($a[1] * $b[2]) - ($b[1] * $a[2])),
(($a[2] * $b[0]) - ($b[2] * $a[0])),
(($a[0] * $b[1]) - ($b[0] * $a[1]))
);
$normal = pow(pow($ab[0],2)+pow($ab[1],2)+pow($ab[2],2),0.5);
$d = array(
($ab[0]/$normal),
($ab[1]/$normal),
($ab[2]/$normal)
);
$e_ = (($d[0] * $c[0]) + ($d[1] * $c[1]) + ($d[2] * $c[2]));
$e = acos($e_);
$res = pi()/2 - $e;
$res = $res * 6378.1;
return $res;
}
we need to do some spherical geometry here. If we were just working in the plane the question would be easy https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line things are trickier on a sphere.
First what do we mean by the line formed by X -> Y. We probably mean a great circle https://en.wikipedia.org/wiki/Great_circle
for example the equator.
Assume for a moment that the points X, Y were on the equator, then the distance between Z and the equator would be proportional to the latitude of Z.
One way to solve the problem would be to rotate our points so that X and Y lie on the equator and then find the latitude of Z. A procedure for doing this is (set up axis so x-axis is out through 0,0, the y-axis is due east and the z-axis due north)
Rotate around z-axis X -> X1, Y->Y1, Z->Z1 so the longitude of X1 is zero.
Rotate around y-axis X1 -> X2, Y1->Y2, Z1->Z2 so the latitude of X2 is zero.
Rotate around the z-axis X2->X3, Y2->Y3, Z2->Z3 so the latitude of Y3 is zero
We now have both X3 and Y3 on the equator and can find the latitude of Z3. The actual distance will be radius-of-earth * latitude-of-Z3-in-radians
Curiously #abiessu comment is not actually correct the great circle through 1N 1E and 2N 2E does not quite run through 3N 3E. I make the distance 14cm.
An easier way to calculate this is to find the cross product of X and Y, W=X ^ Y. This vector is the normal to the plane through X, Y and the center of the sphere. Now find the dot product W . Z this is angle-between-W-and-Z * len(W) * len(Z). So divide the dot product by the two lengths to give a and find pi/2-a the angle of inclination of Z above the plane. Multiply this by the radius to get the distance.
Taking the example of points at 1N 1E take points on the unit sphere
a 1.0N 1.0E (0.999695, 0.017450, 0.017452 )
b 2.0N 2.0E (0.998782, 0.034878, 0.034899 )
c 3.0N 3.0E (0.997261, 0.052264, 0.052336 )
a^b (0.000000, -0.017458, 0.017439 )
d=unit(a^b) (0.000011 , -0.707484, 0.706730 )
d . c 0.000022543731833669922
e = acos(d . c) = 1.570773783063061 in radians
pi/2 - e = 0.000022543731835522607
0.14378617602014673km distance on earth with radius 6378.1km

Check on which side a lat,lng is to another lat,lng on the map

I have a map whose South-West and North-East bounds I take and use it to get places between them.
Because of the date-line sometimes the bounds doesn't come as they should as Explained here.
So I thought of working on:
Get which side the North-East lat lng is compared to the South-West,
if its on the right side its fine, but if it's on the left, I have to
do something.
So to know the side, I calculated the bearing(the angle between the line connecting these SW and NE points and a vertical line). So, if the bearing is between 0-90 its proper or else not.
But the problem is:
Here the top point qualifies for a North-East with both lat and lng
positive: 78.52379, 158.70952
and
The bottom point qualifies for a South-West with both lat and lng
negative: -32.1087, -150.3139
Still the map tries to connect the points in reverse direction(may be tries for least distance) and give the bearing as 342 which I will consider as a improper points and try to reverse 1 of them :(
Looks like this an expected way to calculate the bearing, if so is there a way to solve/achieve what I wanted?
EDIT:
function _getBearing($lat1, $lon1, $lat2, $lon2) {
//difference in longitudinal coordinates
$dLon = deg2rad($lon2) - deg2rad($lon1);
//difference in the phi of latitudinal coordinates
$dPhi = log(tan(deg2rad($lat2) / 2 + pi() / 4) / tan(deg2rad($lat1) / 2 + pi() / 4));
//we need to recalculate $dLon if it is greater than pi
if(abs($dLon) > pi()) {
if($dLon > 0) {
$dLon = (2 * pi() - $dLon) * -1;
}
else {
$dLon = 2 * pi() + $dLon;
}
}
//return the angle, normalized
return (rad2deg(atan2($dLon, $dPhi)) + 360) % 360;
}

latitude / longitude formula in php solution for determining a NSWE corners from a central point

lat/long outward distance approx 800ft on each side from center.
I have a central point of:
Latitude:38.6806353
Longitude:-96.5001029
I am trying to resolve a formula with php how to get the latitude / longitude to the NWSE corners approx 800ft outward from a center point.
So I would end up with a result similar to (but not correct):
Central:
38.6806353 -96.5001029
N: 38.6806353 -96.5001029
W: 38.6806353 -96.5001029
S: 38.6806353 -96.5001029
E: 38.6806353 -96.5001029
I've been trying to reverse engineer a few Javascripts that I found, but having absolutly no luck.
Is there a php Class available that does this math or similar that would require minor revisions? I can't find one thus far...
I found this function and I've been toying with it. I can get a nice array output like:
Array ( [0] => -112.35301079549 [1] => 36.105603064867 [2] => -112.25722008867 [3] => 36.105603064867 )
But I can't get a N W S E coordinates set to generate? Anyone know what I am doing wrong with this? I need 4 sets of values instead of two like:
N: 38.6806353 -96.5001029
W: 38.6806353 -96.5001029
S: 38.6806353 -96.5001029
E: 38.6806353 -96.5001029
<?php function getBoundingBox($lon_degrees,$lat_degrees,$distance_in_miles) {
$radius = 3963.1; // of earth in miles
// bearings
$due_north = 0;
$due_south = 180;
$due_east = 90;
$due_west = 270;
// convert latitude and longitude into radians
$lat_r = deg2rad($lat_degrees);
$lon_r = deg2rad($lon_degrees);
// find the northmost, southmost, eastmost and westmost corners $distance_in_miles away
// original formula from
// http://www.movable-type.co.uk/scripts/latlong.html
$northmost = asin(sin($lat_r) * cos($distance_in_miles/$radius) + cos($lat_r) * sin ($distance_in_miles/$radius) * cos($due_north));
$southmost = asin(sin($lat_r) * cos($distance_in_miles/$radius) + cos($lat_r) * sin ($distance_in_miles/$radius) * cos($due_south));
$eastmost = $lon_r + atan2(sin($due_east)*sin($distance_in_miles/$radius)*cos($lat_r),cos($distance_in_miles/$radius)-sin($lat_r)*sin($lat_r));
$westmost = $lon_r + atan2(sin($due_west)*sin($distance_in_miles/$radius)*cos($lat_r),cos($distance_in_miles/$radius)-sin($lat_r)*sin($lat_r));
$northmost = rad2deg($northmost);
$southmost = rad2deg($southmost);
$eastmost = rad2deg($eastmost);
$westmost = rad2deg($westmost);
// sort the lat and long so that we can use them for a between query
if ($northmost > $southmost) {
$lat1 = $southmost;
$lat2 = $northmost;
} else {
$lat1 = $northmost;
$lat2 = $southmost;
}
if ($eastmost > $westmost) {
$lon1 = $westmost;
$lon2 = $eastmost;
} else {
$lon1 = $eastmost;
$lon2 = $westmost;
}
return array($lon1,$lat1,$lon2,$lat1);
}
?>
I noticed that $due_north, $due_south, etc are in degrees but you have sin($due_east) without a conversion of $due_east to radians.
For 90 deg bearing (east), θ= pi/2 (90 deg), d/R will be 800ft / 5280 ft/mi / 3959 miles (radius of earth in miles), lat1/lon1 are your center point lat/lon in radians.
east_lat = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
east_lon = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
Convert back to degrees then repeat for the other 3 corners.
The comment in your code references the website:
http://www.movable-type.co.uk/scripts/latlong.html
Go to the section Destination point given distance and bearing from start point. You can check your calculations with the calculator on the web page.
The bounding box must satisfy some conditions you didn't mentioned. For example Google Maps uses a z curve and 21 zoom level to subdivide the map into smaller tiles. I don't know how big is a single tile in distance but I use the script from John Brafford to convert from geo coordinate to WGS84 Datum. There is also a method to return the bounding box of a tile. You can find the script here: http://bafford.com/software/aggregate-map-tools/GlobalMapTiles.php.txt.

How to move a marker 100 meters with coordinates

I have 2 coordinates. Coordinate 1 is a 'person'. Coordinate 2 is a destination.
How do I move coordinate 1 100 meters closer to coordinate 2?
This would be used in a cron job, so only php and mysql included.
For example:
Person is at: 51.26667, 3.45417
Destination is: 51.575001, 4.83889
How would i calculate the new coordinates for Person to be 100 meters closer?
Use Haversine to calculate the difference between the two points in metres; then adjust the value of the person coordinates proportionally.
$radius = 6378100; // radius of earth in meters
$latDist = $lat - $lat2;
$lngDist = $lng - $lng2;
$latDistRad = deg2rad($latDist);
$lngDistRad = deg2rad($lngDist);
$sinLatD = sin($latDistRad);
$sinLngD = sin($lngDistRad);
$cosLat1 = cos(deg2rad($lat));
$cosLat2 = cos(deg2rad($lat2));
$a = ($sinLatD/2)*($sinLatD/2) + $cosLat1*$cosLat2*($sinLngD/2)*($sinLngD/2);
if($a<0) $a = -1*$a;
$c = 2*atan2(sqrt($a), sqrt(1-$a));
$distance = $radius*$c;
Feeding your values of:
$lat = 51.26667; // Just South of Aardenburg in Belgium
$lng = 3.45417;
$lat2 = 51.575001; // To the East of Breda in Holland
$lng2 = 4.83889;
gives a result of 102059.82251083 metres, 102.06 kilometers
The ratio to adjust by is 100 / 102059.82251083 = 0.0009798174985988102859004569070625
$newLat = $lat + (($lat2 - $lat) * $ratio);
$newLng = $lng + (($lng2 - $lng) * $ratio);
Gives a new latitude of 51.266972108109 and longitude of 3.4555267728867
Find the angle theta between the x-axis and the vector from person to destination.
theta = Atan2(dest.y-person.y, dest.x-person.x).
Now use theta and the amount you want to advance the point to calculate the new point.
newPoint.x = advanceDistance * cos(theta) + person.x
newPoint.y = advanceDistance * sin(theta) + person.y
If you understand JavaScript, you may want to check out the moveTowards() method in the following Stack Overflow post:
How to add markers on Google Maps polylines based on distance along the line?
This method returns the destination point when given a start point, an end point, and the distance to travel along that line. You can use point 1 as the starting point, point 2 as the end point, and a distance of 100 meters. It's written in JavaScript, but I'm sure it can be easily ported to PHP or MySQL.
You may also want to check out this other Stack Overflow post which implements a part of the above JavaScript implementation, as a user defined function for SQL Server 2008, called func_MoveTowardsPoint:
Moving a Point along a Path in SQL Server 2008
The above uses SQL Server 2008's in-built geography data type. However you can easily use two decimal data types for latitude and longitude in place of the single geography data type.
Both the SQL Server and the JavaScript examples were based on implementations from Chris Veness's article Calculate distance, bearing and more between Latitude/Longitude points.

Categories