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)
Related
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
How can I calculate every Lat/Long coordinates between two Lat/Long coordinates in PHP?
Lets say I have coordinates A:
(39.126331, -84.113288)
and coordinates B:
(39.526331, -84.213288)
How would I calculate every possible coordinates between those two Lat/Long coordinates (in a direct line) up to five decimal places (e.g. 39.12633, -84.11328) and get list of coordinates between the two?
In addition, I have another set of coordinates (Coordinates C) that are slightly off and not on the track of coordinates between A and B.
How could I calculate the distance between coordinates C and the closest coordinates between A and B?
You can compute a voronoi diagram from all the lat lon pairs and then look for adjacent cell. Also note that lat lon are angles and not world coordinate or cartesian coordinates. You can download my PHP class voronoi diagram # phpclasses.org.
Here is what solved this for me,
function point_to_line_segment_distance($startX,$startY, $endX,$endY, $pointX,$pointY)
{
$r_numerator = ($pointX - $startX) * ($endX - $startX) + ($pointY - $startY) * ($endY - $startY);
$r_denominator = ($endX - $startX) * ($endX - $startX) + ($endY - $startY) * ($endY - $startY);
$r = $r_numerator / $r_denominator;
$px = $startX + $r * ($endX - $startX);
$py = $startY + $r * ($endY - $startY);
$closest_point_on_segment_X = $px;
$closest_point_on_segment_Y = $py;
$distance = user_bomb_distance_calc($closest_point_on_segment_X, $closest_point_on_segment_Y, $pointX, $pointY);
return array($distance, $closest_point_on_segment_X, $closest_point_on_segment_Y);
}
function user_bomb_distance_calc($uLat , $uLong , $bLat , $bLong)
{
$earthRadius = 6371; #km
$dLat = deg2rad((double)$bLat - (double) $uLat);
$dlong = deg2rad((double)$bLong - (double) $uLong);
$a = sin($dLat / 2 ) * sin($dLat / 2 ) + cos(deg2rad((double)$uLat)) * cos(deg2rad((double)$bLat)) * sin($dlong / 2) * sin($dlong / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$distance = $earthRadius * $c;
$meter = 1000; //convert to meter 1KM = 1000M
return intval( $distance * $meter ) ;
}
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;
}
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.
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.