Related
I'm trying to create a haversine "distance calculator," where I can input two lat/long coordinates and it'll give the distance between them, through use of the haversine formula. It's in Lua, and here's the code:
local R = 6371000 -- metres
local lat1 = la1 * math.pi/180
local lat2 = la2 * math.pi/180
local dlat = (lat2 - lat1) * math.pi/180
local dlong = (long2 - long1) * math.pi/180
local a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(lat1) * math.cos(lat2) * math.sin(dlong/2) * math.sin(dlong/2)
local c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
local d = R * c -- in metres
d = d / 1000 -- in kilometers
print(d)
I've tested it against multiple online distance checkers which use this formula, and this is always off by a large margin. And also, I took the code from here, and then changed it for Lua: https://www.movable-type.co.uk/scripts/latlong.html
Any idea why this isn't working? Thank you.
Your problem, assuming you just omitted the lines, in which you set the coordinates, is that you multiply dlat by math.pi/180, although lat1 and lat2 are already in radians, not degrees. local dlat should be lat2 - lat1.
Below is an improved variant of the code:
local function haversine (lat1, lat2, long1, long2) -- in radians.
local cos, sin = math.cos, math.sin
local dlat, dlong = lat2 - lat1, long2 - long1
return sin (dlat / 2) ^ 2 + cos (lat1) * cos (lat2) * sin (dlong / 2) ^ 2
end
local function distance (p1, p2) -- in degrees.
local pi, arcsin, sqrt = math.pi, math.asin, math.sqrt
local d2r = pi / 180
local R = 6371000 -- in metres
local lat1, lat2 = p1[1] * d2r, p2[1] * d2r
local long1, long2 = p1[2] * d2r, p2[2] * d2r
local a = haversine (lat1, lat2, long1, long2)
return 2 * R * arcsin (sqrt (a)) / 1000 -- in km
end
local Moscow, Novokuznetsk = {55.7558, 37.6173}, {53.7596, 87.1216}
print (distance (Moscow, Novokuznetsk)) -- 3126 km as reported by https://www.distancefromto.net/distance-from-moscow-to-novokuznetsk-ru.
distance and haversine are moved to functions,
functions from math are localised for tidier look and performance,
degrees to radians conversion rate is pre-calculated for performance,
^ is used for power,
arctangent is replaced with arcsine, to simplify the formula.
I am trying to generate random coordinates (lat,long) that lies within a circle with 5 kilometer radius where center point is located at some coordinates (x, y). I am trying to code this in ruby and I'm using the method but somehow i get the results that are NOT within specified 5 km radius.
def location(lat, lng, max_dist_meters)
max_radius = Math.sqrt((max_dist_meters ** 2) / 2.0)
lat_offset = rand(10 ** (Math.log10(max_radius / 1.11)-5))
lng_offset = rand(10 ** (Math.log10(max_radius / 1.11)-5))
lat += [1,-1].sample * lat_offset
lng += [1,-1].sample * lng_offset
lat = [[-90, lat].max, 90].min
lng = [[-180, lng].max, 180].min
[lat, lng]
end
Your code
max_radius = Math.sqrt((max_dist_meters ** 2) / 2.0)
This is just max_dist_meters.abs / Math.sqrt(2) or max_dist_meters * 0.7071067811865475.
10 ** (Math.log10(max_radius / 1.11)-5)
This can be written 9.00901E-6 * max_radius, so it's 6.370325E−6 * max_dist_meters.
rand(10 ** (Math.log10(max_radius / 1.11)-5))
Now for the fun part : rand(x) is just rand() if x is between -1 and 1. So if max_dist_meters is smaller than 1/6.370325E−6 ~ 156977.86, all your 3 first lines do is :
lat_offset = rand()
lng_offset = rand()
So for max_dist_meters = 5000, your method will return a random point that could be 1° longitude and 1° latitude away. At most, it would be a bit more than 157km.
Worse, if x is between 156978 and 313955, your code is equivalent to :
lat_offset = lng_offset = 0
Since Ruby 2.4
[[-90, lat].max, 90].min
can be written lat.clamp(-90, 90)
Possible solution
To get a uniform distribution of random points on the disk of radius max_radius, you need a non-uniform distribution of random radii :
def random_point_in_disk(max_radius)
r = max_radius * rand ** 0.5
theta = rand * 2 * Math::PI
[r * Math.cos(theta), r * Math.sin(theta)]
end
Here's a plot with a million random points :
Here's the same plot with #Schwern's code :
Once you have this method, you can apply some basic math to convert meters to latitude and longitude. Just remember that 1° of latitude is always 111.2km but 1° of longitude is 111.2km at the equator but 0km at the poles :
def random_point_in_disk(max_radius)
r = max_radius * rand**0.5
theta = rand * 2 * Math::PI
[r * Math.cos(theta), r * Math.sin(theta)]
end
EarthRadius = 6371 # km
OneDegree = EarthRadius * 2 * Math::PI / 360 * 1000 # 1° latitude in meters
def random_location(lon, lat, max_radius)
dx, dy = random_point_in_disk(max_radius)
random_lat = lat + dy / OneDegree
random_lon = lon + dx / ( OneDegree * Math::cos(lat * Math::PI / 180) )
[random_lon, random_lat]
end
For this kind of calculation, there's no need to install a 800-pound GIS gorilla.
A few points:
We usually talk about latitude first and longitude second, but in GIS, it's usually lon first because x comes before y.
cos(lat) is considered to be constant so max_radius shouldn't be too big. A few dozens kilometers shouldn't pose any problem. The shape of a disk on a sphere becomes weird with a large radius.
Don't use this method too close to the poles, you'd get arbitrarily large coordinates otherwise.
To test it, let's create random points on 200km disks at different locations:
10_000.times do
[-120, -60, 0, 60, 120].each do |lon|
[-85, -45, 0, 45, 85].each do |lat|
puts random_location(lon, lat, 200_000).join(' ')
end
end
end
With gnuplot, here's the resulting diagram:
Yay! I just reinvented Tissot's indicatrix, 150 years too late :
def location(x_origin, y_origin, radius)
x_offset, y_offset = nil, nil
rad = radius.to_f
begin
x_offset = rand(-rad..rad)
y_offset = rand(-rad..rad)
end until Math.hypot(x_offset, y_offset) < radius
[x_origin + x_offset, y_origin + y_offset]
end
I'd suggest generating a random radius and a random angle. Then you can use those to generate a coordinate with Math.sin and Math.cos.
def location(max_radius)
# 0 to max radius.
# Using a range ensures max_radius is included.
radius = Random.rand(0.0..max_radius)
# 0 to 2 Pi excluding 2 Pi because that's just 0.
# Using Random.rand() because Kernel#rand() does not honor floats.
radians = Random.rand(2 * Math::PI)
# Math.cos/sin work in radians, not degrees.
x = radius * Math.cos(radians)
y = radius * Math.sin(radians)
return [x, y]
end
I'll leave converting this to lat/long and adding a center for you.
Really what I'd suggest is finding a geometry library that supports operations like "give me a random point inside this shape", "is this point inside this shape" and will do lat/long conversions for you because this stuff is very easy to get subtly wrong.
You could build on top of the Ruby geometry gem which provides you with classes for basic shapes. Or if your data is in a database, many support geometric types like PostgreSQL's geometry types, the more powerful PostGIS add-on, or even MySQL has spatial data types.
There's a gem that does what you need.
https://github.com/sauloperez/random-location
Just install the gem and require it on your code:
gem install random-location
require 'random-location'
RandomLocation.near_by(41.38506, 2.17340, 10000)
I try to bend a plane mesh using spherical coordinates. I token formulas on wikipedia and it almost work !
But some vertices are positionned to the same place.
The mesh plane is placed at 0,0,0, you can watch the result here :
Before : http://hpics.li/78c0871
After : http://hpics.li/19ada1a
And here is my code :
#radius = 4
#oPhi = 0
#oTheta = 0
projection : (vertice) ->
p = Math.sqrt(vertice.x ** 2 + vertice.y ** 2)
c = Math.asin(p/#radius)
phi = Math.asin(Math.cos(c) * Math.sin(#oPhi) + (vertice.y * Math.sin(c) * Math.cos(#oPhi) / p))
theta = #oTheta + Math.atan((vertice.x * Math.sin(c)) / (p * Math.cos(#oPhi) * Math.cos(c) - vertice.y * Math.sin(#oPhi) * Math.sin(c)))
vertice.x = #radius * Math.sin(phi) * Math.cos(theta)
vertice.z = #radius * Math.sin(phi) * Math.sin(theta)
vertice.y = #radius * Math.cos(phi)
Thanks for help !
Sorry for that, but the formulas concern le orthographic projection as you can see here :
http://fr.wikipedia.org/wiki/Projection_orthographique
I am using the equations and data for Mars here
http://ssd.jpl.nasa.gov/txt/aprx_pos_planets.pdf
and the solution for the eccentric anomaly kepler equation given here at the top of page four
http://murison.alpheratz.net/dynamics/twobody/KeplerIterations_summary.pdf
And checking the output by modifying the date in the get_centuries_past to the following dates and looking at page E-7 for the actual x,y,z coordinates of Mars (sample data below, but link for the curious:
http://books.google.com/books/about/Astronomical_Almanac_for_the_Year_2013_a.html?id=7fl_-DLwJ8YC)
date 2456320.5 is 2013, 1, 28 and should output
x = 1.283762
y = -0.450111
z = -0.241123
date 2456357.5 is 2013, 3, 6 and should output
x = 1.300366
y = 0.533593
z = 0.209626
date 2456539.500000 is 2013, 9, 4 and should output
x = - 0.325604
y = 1.418110
z = 0.659236
I tested mean anomaly equation and it was fine. However, I cannot get a good set of x,y,z coordinates. I have been tweaking my kepler and coordinate function but cannot get them to match the tables in the astronomical almanac.
Any suggestions or advice on solving the positions of the stars is greatly appreciated. The code below can be put in a .rb file and running it on the command line will output the x,y,z values.
def get_centuries_past_j2000()
#second number is from DateTime.new(2000,1,1,12).amjd.to_f - 1 the modified julian date for the J2000 Epoch
#Date.today.jd.to_f - 51544.5
(DateTime.new(2013,1,28).amjd.to_f - 51544.5)/36525
end
class Planet
attr_accessor :semi_major_axis, :semi_major_axis_delta, :eccentricity, :eccentricity_delta,
:inclination, :inclination_delta, :mean_longitude, :mean_longitude_delta, :longitude_of_perihelion,
:longitude_of_perihelion_delta, :longitude_of_ascending_node, :longitude_of_ascending_node_delta, :time_delta
def initialize(semi_major_axis, semi_major_axis_delta, eccentricity, eccentricity_delta,
inclination, inclination_delta, mean_longitude, mean_longitude_delta, longitude_of_perihelion,
longitude_of_perihelion_delta, longitude_of_ascending_node, longitude_of_ascending_node_delta, time_delta)
#semi_major_axis = semi_major_axis + (semi_major_axis_delta * time_delta)
#eccentricity = eccentricity + (eccentricity_delta * time_delta)
#inclination = inclination + (inclination_delta * time_delta)
#mean_longitude = mean_longitude + (mean_longitude_delta * time_delta)
#longitude_of_perihelion = longitude_of_perihelion + (longitude_of_perihelion_delta * time_delta)
#longitude_of_ascending_node = longitude_of_ascending_node + (longitude_of_ascending_node_delta * time_delta)
#argument_of_perhelion = #longitude_of_perihelion - #longitude_of_ascending_node
end
def mean_anomaly
((#mean_longitude - #longitude_of_perihelion)%360).round(8)
end
def eccentric_anomaly
mod_mean_anomaly = mean_anomaly
if mod_mean_anomaly > 180
mod_mean_anomaly = mod_mean_anomaly - 360
elsif mod_mean_anomaly < -180
mod_mean_anomaly = mod_mean_anomaly + 360
end
e34 = #eccentricity**2
e35 = #eccentricity*e34
e33 = Math.cos(mod_mean_anomaly*Math::PI/180)
mod_mean_anomaly + (-0.5 * e35 + #eccentricity + (e34 + 1.5 * e33 * e35) * e33) * Math.sin(mod_mean_anomaly*Math::PI/180)
end
def J2000_ecliptic_plane
x_prime = #semi_major_axis * (Math.cos(eccentric_anomaly*Math::PI/180) - #eccentricity)
y_prime = #semi_major_axis * Math.sqrt(1-#eccentricity**2) * Math.sin(eccentric_anomaly*Math::PI/180)
z_prime = 0
x = x_prime * (Math.cos(#argument_of_perhelion*Math::PI/180) * Math.cos(#longitude_of_ascending_node*Math::PI/180) - Math.sin(#argument_of_perhelion * Math::PI/180) * Math.sin(#longitude_of_ascending_node * Math::PI/180) * Math.cos(#inclination * Math::PI/180)) + y_prime * (-Math.sin(#argument_of_perhelion* Math::PI/180) * Math.cos(#longitude_of_ascending_node * Math::PI/180) - Math.cos(#argument_of_perhelion * Math::PI/180) * Math.sin(#longitude_of_ascending_node * Math::PI/180) * Math.cos(#inclination * Math::PI/180))
y = x_prime * (Math.cos(#argument_of_perhelion*Math::PI/180) * Math.sin(#longitude_of_ascending_node*Math::PI/180) + Math.sin(#argument_of_perhelion * Math::PI/180) * Math.cos(#longitude_of_ascending_node * Math::PI/180) * Math.cos(#inclination * Math::PI/180)) + y_prime * (-Math.sin(#argument_of_perhelion* Math::PI/180) * Math.sin(#longitude_of_ascending_node * Math::PI/180) + Math.cos(#argument_of_perhelion * Math::PI/180) * Math.cos(#longitude_of_ascending_node * Math::PI/180) * Math.cos(#inclination * Math::PI/180))
z = x_prime * Math.sin(#argument_of_perhelion*Math::PI/180) * Math.sin(#inclination*Math::PI/180) + y_prime * Math.cos(#argument_of_perhelion*Math::PI/180) * Math.sin(#inclination*Math::PI/180)
return x, y, z
end
end
time = get_centuries_past_j2000
mars = Planet.new(1.52371034, 0.00001847, 0.09339410, 0.00007882, 1.84969142, -0.00813131, -4.553443205, 19140.30268499, -23.94362959, 0.44441088, 49.55952891, -0.29257343, time)
puts time
puts mars.mean_anomaly
puts mars.eccentric_anomaly
puts mars.J2000_ecliptic_plane
This may be helpful although I don't agree with arguments of perihelion for Earth. Longitude of perihelion is fine. The inclination is so small that it doesn't really apply for Earth as it does for other planets. And finding values for Omega is challenging. The perihelion is ever changing. FYI in 1248 AD it coincided with the winter solstice.
Firstly IAU has free SOFA C and FORTRAN lib's with standardized astronomical functions. Matric tables are included in certain routines so you don't have to go look them up.
But if you are so inclined to use old school methods then this site has what you need http://www.stjarnhimlen.se/comp/tutorial.html
NOVA C and JAVA, MICA, JPL catalogs, Jean Meeus book, AA USNO and a host of others beside wikipedia have tons of information. It looks like you want rectangular values so I think Paul Schlyter can help you out.
SOFA has these as well but documentation on how to use them is not going to teach the techniques. It will take a lot of research to understand them.
It looks like you are using Ruby and there is a wrapper gem for the SOFA lib named Celes. Just gem install celes and you'll have it.
Try looking at the fundamental arguments which all begin with fa:
** iauFal03 mean anomaly of the Moon
** iauFaf03 mean argument of the latitude of the Moon
** iauFaom03 mean longitude of the Moon's ascending node
** iauFame03 mean longitude of Mercury
** iauFave03 mean longitude of Venus
** iauFae03 mean longitude of Earth
** iauFama03 mean longitude of Mars
** iauFaju03 mean longitude of Jupiter
** iauFasa03 mean longitude of Saturn
** iauFaur03 mean longitude of Uranus
** iauFapa03 general accumulated precession in longitude
Have fun!
Edit Update:
Two functions in this gem that will give you heliocentric and barycentric x,y,z for Earth.
p is position and v is velocity.
h is heliocentric and b is barycentric.
pvh = Celes.epv00(jd_now, 0.0)[0]
pvb = Celes.epv00(jd_now, 0.0)[1]
sc = Celes.pv2s(pvh)
sc means spherical coordinates.
As you can see, all you need to provide is a JD time value.
So lots of good stuff in that gem and the SOFA C code.
I have yet to learn how to use them all.
I'm wondering if there's a way to calculate the distance of two GPS coordinates without relying on Google Maps API.
My app may receive the coordinates in float or I would have to do reverse GEO on the addresses.
Distance between two coordinates on earth is usually calculated using Haversine formula. This formula takes into consideration earth shape and radius. This is the code I use to calculate distance in meters.
def distance(loc1, loc2)
rad_per_deg = Math::PI/180 # PI / 180
rkm = 6371 # Earth radius in kilometers
rm = rkm * 1000 # Radius in meters
dlat_rad = (loc2[0]-loc1[0]) * rad_per_deg # Delta, converted to rad
dlon_rad = (loc2[1]-loc1[1]) * rad_per_deg
lat1_rad, lon1_rad = loc1.map {|i| i * rad_per_deg }
lat2_rad, lon2_rad = loc2.map {|i| i * rad_per_deg }
a = Math.sin(dlat_rad/2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad/2)**2
c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1-a))
rm * c # Delta in meters
end
puts distance([46.3625, 15.114444],[46.055556, 14.508333])
# => 57794.35510874037
You can use the geokit ruby gem. It does these calculations internally, but also supports resolving addresses via google and other services if you need it to.
require 'geokit'
current_location = Geokit::LatLng.new(37.79363,-122.396116)
destination = "37.786217,-122.41619"
current_location.distance_to(destination)
# Returns distance in miles: 1.211200074136264
You can also find out the bearing_to (direction expressed as a float in degrees between 0-360) and midpoint_to (returns an object you can run .latitude and .longitude methods on).
Just a little shorter & separated parameter version of #Lunivore's answer
RAD_PER_DEG = Math::PI / 180
RM = 6371000 # Earth radius in meters
def distance_between(lat1, lon1, lat2, lon2)
lat1_rad, lat2_rad = lat1 * RAD_PER_DEG, lat2 * RAD_PER_DEG
lon1_rad, lon2_rad = lon1 * RAD_PER_DEG, lon2 * RAD_PER_DEG
a = Math.sin((lat2_rad - lat1_rad) / 2) ** 2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin((lon2_rad - lon1_rad) / 2) ** 2
c = 2 * Math::atan2(Math::sqrt(a), Math::sqrt(1 - a))
RM * c # Delta in meters
end
I'm not sure of any prepackaged solution, but it seems a fairly straightforward calculation: http://www.movable-type.co.uk/scripts/latlong.html
You can use the loc gem like this :
require 'loc'
loc1 = Loc::Location[49.1, 2]
loc2 = Loc::Location[50, 3]
loc1.distance_to(loc2)
=> 123364.76538823603 # km
Look at gem Geocoder(railscast)
If you store your coordinates in db, it calculate distance using database. But works good in other cases too.
Converted the accepted answer to Swift 3.1 (works on Xcode 8.3), in case anyone needs it:
public static func calculateDistanceMeters(departure: CLLocationCoordinate2D, arrival: CLLocationCoordinate2D) -> Double {
let rad_per_deg = Double.pi / 180.0 // PI / 180
let rkm = 6371.0 // Earth radius in kilometers
let rm = rkm * 1000.0 // Radius in meters
let dlat_rad = (arrival.latitude - departure.latitude) * rad_per_deg // Delta, converted to rad
let dlon_rad = (arrival.longitude - departure.longitude) * rad_per_deg
let lat1_rad = departure.latitude * rad_per_deg
let lat2_rad = arrival.latitude * rad_per_deg
let sinDlat = sin(dlat_rad/2)
let sinDlon = sin(dlon_rad/2)
let a = sinDlat * sinDlat + cos(lat1_rad) * cos(lat2_rad) * sinDlon * sinDlon
let c = 2.0 * atan2(sqrt(a), sqrt(1-a))
return rm * c
}
require 'rgeo'
point_1 = RGeo::Cartesian.factory.point(0, 0)
point_2 = RGeo::Cartesian.factory.point(0, 2)
p point_1.distance(point_2)
# => 2.0