I'd like to have a function that accepts a geo location (Latitude, Longitude) and generates random sets of coordinates around it but also takes these parameters as a part of the calculation:
Number Of Random Coordinates To Make
Radius to generate in
Min distance between the random coordinates in meters
The root coordinates to generate the locations around it.
Example of how the generation would be:
What's a good approach to achieve this?
Generate random coordinates around a location
function generateRandomPoint($centre, $radius) {
$radius_earth = 3959; //miles
//Pick random distance within $distance;
$distance = lcg_value()*$radius;
//Convert degrees to radians.
$centre_rads = array_map( 'deg2rad', $centre );
//First suppose our point is the north pole.
//Find a random point $distance miles away
$lat_rads = (pi()/2) - $distance/$radius_earth;
$lng_rads = lcg_value()*2*pi();
//($lat_rads,$lng_rads) is a point on the circle which is
//$distance miles from the north pole. Convert to Cartesian
$x1 = cos( $lat_rads ) * sin( $lng_rads );
$y1 = cos( $lat_rads ) * cos( $lng_rads );
$z1 = sin( $lat_rads );
//Rotate that sphere so that the north pole is now at $centre.
//Rotate in x axis by $rot = (pi()/2) - $centre_rads[0];
$rot = (pi()/2) - $centre_rads[0];
$x2 = $x1;
$y2 = $y1 * cos( $rot ) + $z1 * sin( $rot );
$z2 = -$y1 * sin( $rot ) + $z1 * cos( $rot );
//Rotate in z axis by $rot = $centre_rads[1]
$rot = $centre_rads[1];
$x3 = $x2 * cos( $rot ) + $y2 * sin( $rot );
$y3 = -$x2 * sin( $rot ) + $y2 * cos( $rot );
$z3 = $z2;
//Finally convert this point to polar co-ords
$lng_rads = atan2( $x3, $y3 );
$lat_rads = asin( $z3 );
return array_map( 'rad2deg', array( $lat_rads, $lng_rads ) );
}
Usage
generateRandomPoint(array(3.1528, 101.7038), 4);
A brute force method should be good enough.
for each point to generate "n"
find a random angle
get the x and y from the angle * a random radius up to max radius
for each point already generated "p"
calculate the distance between "n" and "p"
if "n" satisfies the min distance
add new point "n"
In PHP, generating a new point is easy
$angle = deg2rad(mt_rand(0, 359));
$pointRadius = mt_rand(0, $radius);
$point = array(
'x' => sin($angle) * $pointRadius,
'y' => cos($angle) * $pointRadius
);
Then calculating the distance between two points
$distance = sqrt(pow($n['x'] - $p['x'], 2) + pow($n['y'] - $p['y'], 2));
** Edit **
For the sake of clarifying what others have said, and after doing some further research (I'm not a mathematician, but the comments did make me wonder), here the most simple definition of a gaussian distribution :
If you were in 1 dimension, then $pointRadius = $x * mt_rand(0,
$radius); would be OK since there is no distinction between
$radius and $x when $x has a gaussian distribution.
In 2 or more dimensions, however, if the coordinates ($x,$y,...) have
gaussian distributions then the radius $radius does not have a
gaussian distribution.
In fact the distribution of $radius^2 in 2 dimensions [or k
dimensions] is what is called the "chi-squared distribution with 2 [or
k] degrees of freedom", provided the ($x,$y,...) are independent and
have zero means and equal variances.
Therefore, to have a normal distribution, you'd have to change the line of the generated radius to
$pointRadius = sqrt(mt_rand(0, $radius*$radius));
as others have suggested.
as the other answer says, the simplest approach is going to be generating random points and then discarding ones that are too close to others (don't forget to check for min distance to central point too, if necessary).
however, generating the random points is harder than explained. first, you need to select the radius at random. second, you need to have more points at large radii (because there's "more room" out there). so you cannot just make radius a uniform random number.
instead, choose a number between 0 and $radius * $radius. then take the sqrt() of that to find the radius to plot at (this works because area is proportional to square of the radius).
i don't know php (see the correction by Karolis in the comments), but from the other answer i think that would mean:
$angle = deg2rad(mt_rand(0, 359));
$radius = sqrt(mt_rand(0, $max_radius * $max_radius));
then check that against the previous points as already described.
finally, don't forget that you can reach a state where you can generate no more points, so you may want to put an upper limit on the "try and discard" loop to avoid hitting an infinite loop when the space is (close to) full.
ps as a comment says on another answer, this is O(n^2) and so unsuitable for large numbers of points. you can address that to some extent by sorting the points by radius and only considering those within a difference of $min_distance, as long as $min_distance << $max_radius (as it is in your drawing); doing better than that requires a more complex solution (for example, at larger radii also using angle, or using a separate quad tree to store and compare positions). but for tens of points i imagine that would not be necessary.
Others have already explained the math you need. But I think the most problematic part is the performance. The brute force method to check the distances between the points can be good enough when you have 50 points only. But too slow when you have 1000 points or even more. For 1000 points this requires at least half a million operations.
Therefore my suggestion would be to save all randomly generated points into B-tree or binary search tree (by x value and by y value). Using an ordered tree you will be able to get the points which are in the area [x ± min_distance, y ± min_distance] efficiently. And these are the only points that need to be checked, drastically reducing the number of needed operations.
Related
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)
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;
}
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;
}
I am using the following scripting that I found on the net to grab all postal codes between a given set coordinates.
When using it my concern is that when some postal codes being grab are greater than the distance entered; not by much - about 20 KM off.
function GetPostalCodes($latitude, $longitude, $range) {
$radius = 3959;
$north = rad2deg(asin(sin(deg2rad($latitude)) * cos($range / $radius) + cos(deg2rad($latitude)) * sin($range / $radius) * cos(deg2rad(0))));
$south = rad2deg(asin(sin(deg2rad($latitude)) * cos($range / $radius) + cos(deg2rad($latitude)) * sin($range / $radius) * cos(deg2rad(180))));
$east = rad2deg(deg2rad($longitude) + atan2(sin(deg2rad(90)) * sin($range / $radius) * cos(deg2rad($latitude)), cos($range / $radius) - sin(deg2rad($latitude)) * sin(deg2rad($north))));
$west = rad2deg(deg2rad($longitude) + atan2(sin(deg2rad(270)) * sin($range / $radius) * cos(deg2rad($latitude)), cos($range / $radius) - sin(deg2rad($latitude)) * sin(deg2rad($north))));
$return = DBSelectAllArrays("SELECT postal FROM postalcodes WHERE (latitude <= $north AND latitude >= $south AND longitude <= $east AND longitude >= $west)");
krsort($return);
if (empty($return)) return false;
return $return;
}
Is there something I am missing to get a more accurate result?
Given your comments:
$radius = 6371.0; // mean radius of Earth in km
This is taken from wikipedia, but I've seen it within a +/- 3km tolerance from other sources.
I began to question whether you were using great circle distance calculations, but this is more important for accuracy over longer distances due to the curvature of the earths surface.
Tim, you started by using a bounding box (rectangle) and then with the Haversine formula, you'll get a radius (circle), which generally is much better if you just want people within a certain distance. You don't state your purpose, but if you're looking for people who may travel a certain distance to you, you may want to consider metropolitan areas, which vary in shape. If so, look at: Canadian Metro Areas data
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.