Related
I have a code that takes the starting lat/lon, a bearing (direction), and then distance (in km) to find the new lat lon on a spherical earth. The code looks like:
get_new_lat_lon_from_distance_bearing_lat_lon(lat0,lon0,bearing,d):
import math
# Quick constant
R = 6378.1
# Convert to radians
lat1 = math.radians(lat0)
lon1 = math.radians(lon0)
brng = math.radians(bearing)
# Do some math for lat and lon
lat2 = math.asin( math.sin(lat1)*math.cos(d/R) + math.cos(lat1)*math.sin(d/R)*math.cos(brng))
lon2 = lon1 + math.atan2(math.sin(brng)*math.sin(d/R)*math.cos(lat1),math.cos(d/R)-math.sin(lat1)*math.sin(lat2))
# Reconvert to degrees
lat2 = math.degrees(lat2)
lon2 = math.degrees(lon2)
return lat2,lon2
I can then call this such that:
lat_s,lon_s = get_new_lat_lon_from_distance_bearing_lat_lon(yll,xll,180,cellsize*r)
lat_e,lon_e = get_new_lat_lon_from_distance_bearing_lat_lon(yll,xll,90,cellsize*c)
where yll = 130, and xll = 55
I want to move every 500m from this starting lat/lon position south (bearing = 180) and then also to the east (bearing = 90). The East direction should be 14,000 times, and then the South should be traversed 7000 times. In other words, we can loop through these such that:
nrows = 7000
ncols = 14000
c = 0.5 (0.5km)
# Pre-allocate
biglats = []
biglons = []
# Traverse south
for r in range(0,nrows):
lat_s,lon_s = get_new_lat_lon_from_distance_bearing_lat_lon(yll,xll,180,cellsize*r)
biglats.append(lat_s)
# Traverse East
for c in range(0,ncols):
lat_e,lon_e = get_new_lat_lon_from_distance_bearing_lat_lon(yll,xll,90,cellsize*c)
biglons.append(lon_e)
However, when I print the first and last values of each:
55.000000000147004
23.56327426583246
-130.00000000007
-56.372396480687385
23.56 should be 20, and -56.37 should be -60. The end goal would be to create a meshgrid of lat/lon with a [14000,7000] array. However, the calculations are wrong. What could be done to get a more correct lat/lon and/or some sort of 'meshgrid' of 14000,7000 values of lat/lon equally spaced 500m apart given the starting lat/lon provided?
Given an existing point in lat/long, distance in (in KM) and bearing (in degrees converted to radians), I would like to calculate the new lat/long. This site crops up over and over again, but I just can't get the formula to work for me.
The formulas as taken the above link are:
lat2 = asin(sin(lat1)*cos(d/R) + cos(lat1)*sin(d/R)*cos(θ))
lon2 = lon1 + atan2(sin(θ)*sin(d/R)*cos(lat1), cos(d/R)−sin(lat1)*sin(lat2))
The above formula is for MSExcel where-
asin = arc sin()
d = distance (in any unit)
R = Radius of the earth (in the same unit as above)
and hence d/r = is the angular distance (in radians)
atan2(a,b) = arc tan(b/a)
θ is the bearing (in radians, clockwise from north);
Here's the code I've got in Python.
import math
R = 6378.1 #Radius of the Earth
brng = 1.57 #Bearing is 90 degrees converted to radians.
d = 15 #Distance in km
#lat2 52.20444 - the lat result I'm hoping for
#lon2 0.36056 - the long result I'm hoping for.
lat1 = 52.20472 * (math.pi * 180) #Current lat point converted to radians
lon1 = 0.14056 * (math.pi * 180) #Current long point converted to radians
lat2 = math.asin( math.sin(lat1)*math.cos(d/R) +
math.cos(lat1)*math.sin(d/R)*math.cos(brng))
lon2 = lon1 + math.atan2(math.sin(brng)*math.sin(d/R)*math.cos(lat1),
math.cos(d/R)-math.sin(lat1)*math.sin(lat2))
print(lat2)
print(lon2)
I get
lat2 = 0.472492248844
lon2 = 79.4821662373
Needed to convert answers from radians back to degrees. Working code below:
from math import asin, atan2, cos, degrees, radians, sin
def get_point_at_distance(lat1, lon1, d, bearing, R=6371):
"""
lat: initial latitude, in degrees
lon: initial longitude, in degrees
d: target distance from initial
bearing: (true) heading in degrees
R: optional radius of sphere, defaults to mean radius of earth
Returns new lat/lon coordinate {d}km from initial, in degrees
"""
lat1 = radians(lat1)
lon1 = radians(lon1)
a = radians(bearing)
lat2 = asin(sin(lat1) * cos(d/R) + cos(lat1) * sin(d/R) * cos(a))
lon2 = lon1 + atan2(
sin(a) * sin(d/R) * cos(lat1),
cos(d/R) - sin(lat1) * sin(lat2)
)
return (degrees(lat2), degrees(lon2),)
lat = 52.20472
lon = 0.14056
distance = 15
bearing = 90
lat2, lon2 = get_point_at_distance(lat, lon, distance, bearing)
# lat2 52.20444 - the lat result I'm hoping for
# lon2 0.36056 - the long result I'm hoping for.
print(lat2, lon2)
# prints "52.20451523755824 0.36067845713550956"
The geopy library supports this:
import geopy
from geopy.distance import VincentyDistance
# given: lat1, lon1, b = bearing in degrees, d = distance in kilometers
origin = geopy.Point(lat1, lon1)
destination = VincentyDistance(kilometers=d).destination(origin, b)
lat2, lon2 = destination.latitude, destination.longitude
Found via https://stackoverflow.com/a/4531227/37610
This question is known as the direct problem in the study of geodesy.
This is indeed a very popular question and one that is a constant cause of confusion. The reason is that most people are looking for a simple and straight-forward answer. But there is none, because most people asking this question are not supplying enough information, simply because they are not aware that:
Earth is not a perfect sphere, since it is flattened/compressed by it poles
Because of (1) earth does not have a constant Radius, R. See here.
Earth is not perfectly smooth (variations in altitude) etc.
Due to tectonic plate movement, a geographic point's lat/lon position may change by several millimeters (at least), every year.
Therefore there are many different assumptions used in the various geometric models that apply differently, depending on your needed accuracy. So to answer the question you need to consider to what accuracy you would like to have your result.
Some examples:
I'm just looking for an approximate location to the nearest few kilometers for small ( < 100 km) distances of in latitudes between 0-70 deg N|S. (Earth is ~flat model.)
I want an answer that is good anywhere on the globe, but only accurate to about a few meters
I want a super accurate positioning that is valid down to atomic scales of nanometers [nm].
I want answers that is very fast and easy to calculate and not computationally intensive.
So you can have many choices in which algorithm to use. In addition each programming language has it's own implementation or "package" multiplied by number of models and the model developers specific needs. For all practical purposes here, it pays off to ignore any other language apart javascript, since it very closely resemble pseudo-code by its nature. Thus it can be easily converted to any other language, with minimal changes.
Then the main models are:
Euclidian/Flat earth model: good for very short distances under ~10 km
Spherical model: good for large longitudinal distances, but with small latitudinal difference. Popular model:
Haversine: meter accuracy on [km] scales, very simple code.
Ellipsoidal models: Most accurate at any lat/lon and distance, but is still a numerical approximation that depend on what accuracy you need. Some popular models are:
Lambert: ~10 meter precision over 1000's of km.
Paul D.Thomas: Andoyer-Lambert approximation
Vincenty: millimeter precision and computational efficiency
Kerney: nanometer precision
References:
https://en.wikipedia.org/wiki/Reference_ellipsoid
https://en.wikipedia.org/wiki/Haversine_formula
https://en.wikipedia.org/wiki/Earth_ellipsoid
https://en.wikipedia.org/wiki/Geodesics_on_an_ellipsoid
https://en.wikipedia.org/wiki/Vincenty%27s_formulae
https://geographiclib.sourceforge.io/scripts/geod-calc.html
May be a bit late for answering, but after testing the other answers, it appears they don't work correctly. Here is a PHP code we use for our system. Working in all directions.
PHP code:
lat1 = latitude of start point in degrees
long1 = longitude of start point in degrees
d = distance in KM
angle = bearing in degrees
function get_gps_distance($lat1,$long1,$d,$angle)
{
# Earth Radious in KM
$R = 6378.14;
# Degree to Radian
$latitude1 = $lat1 * (M_PI/180);
$longitude1 = $long1 * (M_PI/180);
$brng = $angle * (M_PI/180);
$latitude2 = asin(sin($latitude1)*cos($d/$R) + cos($latitude1)*sin($d/$R)*cos($brng));
$longitude2 = $longitude1 + atan2(sin($brng)*sin($d/$R)*cos($latitude1),cos($d/$R)-sin($latitude1)*sin($latitude2));
# back to degrees
$latitude2 = $latitude2 * (180/M_PI);
$longitude2 = $longitude2 * (180/M_PI);
# 6 decimal for Leaflet and other system compatibility
$lat2 = round ($latitude2,6);
$long2 = round ($longitude2,6);
// Push in array and get back
$tab[0] = $lat2;
$tab[1] = $long2;
return $tab;
}
I ported answer by Brad to vanilla JS answer, with no Bing maps dependency
https://jsfiddle.net/kodisha/8a3hcjtd/
// ----------------------------------------
// Calculate new Lat/Lng from original points
// on a distance and bearing (angle)
// ----------------------------------------
let llFromDistance = function(latitude, longitude, distance, bearing) {
// taken from: https://stackoverflow.com/a/46410871/13549
// distance in KM, bearing in degrees
const R = 6378.1; // Radius of the Earth
const brng = bearing * Math.PI / 180; // Convert bearing to radian
let lat = latitude * Math.PI / 180; // Current coords to radians
let lon = longitude * Math.PI / 180;
// Do the math magic
lat = Math.asin(Math.sin(lat) * Math.cos(distance / R) + Math.cos(lat) * Math.sin(distance / R) * Math.cos(brng));
lon += Math.atan2(Math.sin(brng) * Math.sin(distance / R) * Math.cos(lat), Math.cos(distance / R) - Math.sin(lat) * Math.sin(lat));
// Coords back to degrees and return
return [(lat * 180 / Math.PI), (lon * 180 / Math.PI)];
}
let pointsOnMapCircle = function(latitude, longitude, distance, numPoints) {
const points = [];
for (let i = 0; i <= numPoints - 1; i++) {
const bearing = Math.round((360 / numPoints) * i);
console.log(bearing, i);
const newPoints = llFromDistance(latitude, longitude, distance, bearing);
points.push(newPoints);
}
return points;
}
const points = pointsOnMapCircle(41.890242042122836, 12.492358982563019, 0.2, 8);
let geoJSON = {
"type": "FeatureCollection",
"features": []
};
points.forEach((p) => {
geoJSON.features.push({
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [
p[1],
p[0]
]
}
});
});
document.getElementById('res').innerHTML = JSON.stringify(geoJSON, true, 2);
In addition, I added geoJSON export, so you can simply paste resulting geoJSON to: http://geojson.io/#map=17/41.89017/12.49171 to see the results instantly.
Result:
Quick way using geopy
from geopy import distance
#distance.distance(unit=15).destination((lat,lon),bering)
#Exemples
distance.distance(nautical=15).destination((-24,-42),90)
distance.distance(miles=15).destination((-24,-42),90)
distance.distance(kilometers=15).destination((-24,-42),90)
lon1 and lat1 in degrees
brng = bearing in radians
d = distance in km
R = radius of the Earth in km
lat2 = math.degrees((d/R) * math.cos(brng)) + lat1
long2 = math.degrees((d/(R*math.sin(math.radians(lat2)))) * math.sin(brng)) + long1
I implemented your algorithm and mine in PHP and benchmarked it. This version ran in about 50% of the time. The results generated were identical, so it seems to be mathematically equivalent.
I didn't test the python code above so there might be syntax errors.
I ported the Python to Javascript. This returns a Bing Maps Location object, you can change to whatever you like.
getLocationXDistanceFromLocation: function(latitude, longitude, distance, bearing) {
// distance in KM, bearing in degrees
var R = 6378.1, // Radius of the Earth
brng = Math.radians(bearing) // Convert bearing to radian
lat = Math.radians(latitude), // Current coords to radians
lon = Math.radians(longitude);
// Do the math magic
lat = Math.asin(Math.sin(lat) * Math.cos(distance / R) + Math.cos(lat) * Math.sin(distance / R) * Math.cos(brng));
lon += Math.atan2(Math.sin(brng) * Math.sin(distance / R) * Math.cos(lat), Math.cos(distance/R)-Math.sin(lat)*Math.sin(lat));
// Coords back to degrees and return
return new Microsoft.Maps.Location(Math.degrees(lat), Math.degrees(lon));
},
Thanks to #kodisha, here is a Swift version, but with improved and more precise calculation for Earth radius:
extension CLLocationCoordinate2D {
func earthRadius() -> CLLocationDistance {
let earthRadiusInMetersAtSeaLevel = 6378137.0
let earthRadiusInMetersAtPole = 6356752.314
let r1 = earthRadiusInMetersAtSeaLevel
let r2 = earthRadiusInMetersAtPole
let beta = latitude
let earthRadiuseAtGivenLatitude = (
( pow(pow(r1, 2) * cos(beta), 2) + pow(pow(r2, 2) * sin(beta), 2) ) /
( pow(r1 * cos(beta), 2) + pow(r2 * sin(beta), 2) )
)
.squareRoot()
return earthRadiuseAtGivenLatitude
}
func locationByAdding(
distance: CLLocationDistance,
bearing: CLLocationDegrees
) -> CLLocationCoordinate2D {
let latitude = self.latitude
let longitude = self.longitude
let earthRadiusInMeters = self.earthRadius()
let brng = bearing.degreesToRadians
var lat = latitude.degreesToRadians
var lon = longitude.degreesToRadians
lat = asin(
sin(lat) * cos(distance / earthRadiusInMeters) +
cos(lat) * sin(distance / earthRadiusInMeters) * cos(brng)
)
lon += atan2(
sin(brng) * sin(distance / earthRadiusInMeters) * cos(lat),
cos(distance / earthRadiusInMeters) - sin(lat) * sin(lat)
)
let newCoordinate = CLLocationCoordinate2D(
latitude: lat.radiansToDegrees,
longitude: lon.radiansToDegrees
)
return newCoordinate
}
}
extension FloatingPoint {
var degreesToRadians: Self { self * .pi / 180 }
var radiansToDegrees: Self { self * 180 / .pi }
}
Also late but for those who might find this, you will get more accurate results using the geographiclib library. Check out the geodesic problem descriptions and the JavaScript examples for an easy introduction to how to use to answer the subject question as well as many others. Implementations in a variety of languages including Python. Far better than coding your own if you care about accuracy; better than VincentyDistance in the earlier "use a library" recommendation. As the documentation says: "The emphasis is on returning accurate results with errors close to round-off (about 5–15 nanometers)."
Just interchange the values in the atan2(y,x) function. Not atan2(x,y)!
I ported the answer from #David M to java if anyone wanted this... I do get a slight different result of 52.20462299620793, 0.360433887489931
double R = 6378.1; //Radius of the Earth
double brng = 1.57; //Bearing is 90 degrees converted to radians.
double d = 15; //Distance in km
double lat2 = 52.20444; // - the lat result I'm hoping for
double lon2 = 0.36056; // - the long result I'm hoping for.
double lat1 = Math.toRadians(52.20472); //Current lat point converted to radians
double lon1 = Math.toRadians(0.14056); //Current long point converted to radians
lat2 = Math.asin( Math.sin(lat1)*Math.cos(d/R) +
Math.cos(lat1)*Math.sin(d/R)*Math.cos(brng));
lon2 = lon1 + Math.atan2(Math.sin(brng)*Math.sin(d/R)*Math.cos(lat1),
Math.cos(d/R)-Math.sin(lat1)*Math.sin(lat2));
lat2 = Math.toDegrees(lat2);
lon2 = Math.toDegrees(lon2);
System.out.println(lat2 + ", " + lon2);
Here is a PHP version based on Ed Williams Aviation Formulary. Modulus is handled a little different in PHP. This works for me.
function get_new_waypoint ( $lat, $lon, $radial, $magvar, $range )
{
// $range in nm.
// $radial is heading to or bearing from
// $magvar for local area.
$range = $range * pi() /(180*60);
$radial = $radial - $magvar ;
if ( $radial < 1 )
{
$radial = 360 + $radial - $magvar;
}
$radial = deg2rad($radial);
$tmp_lat = deg2rad($lat);
$tmp_lon = deg2rad($lon);
$new_lat = asin(sin($tmp_lat)* cos($range) + cos($tmp_lat) * sin($range) * cos($radial));
$new_lat = rad2deg($new_lat);
$new_lon = $tmp_lon - asin(sin($radial) * sin($range)/cos($new_lat))+ pi() % 2 * pi() - pi();
$new_lon = rad2deg($new_lon);
return $new_lat." ".$new_lon;
}
For whoever is interested in a Java solution here is my code:
I noticed that the initial solution needs some tweaks in order to return a proper longitude value, especially when the point is at one of the poles.
Also a round operation is sometimes required as the results on 0 latitude / longitude seem to slightly shift away from 0. For small distances, rounding will help in this regard.
private static final double EARTH_RADIUS = 6371; // average earth radius
/**
* Returns the coordinates of the point situated at the distance specified, in
* the direction specified. Note that the value is an approximation, not an
* exact result.
*
* #param startPointLatitude
* #param startPointLongitude
* #param distanceInKm
* #param bearing: 0 means moving north, 90 moving east, 180 moving
* south, 270 moving west. Max value 360 min value 0;
* #return new point location
*/
public static LocationDTO getPointAt(double startPointLatitude, double startPointLongitude, double distanceInKm,
double bearing) {
if (Math.abs(startPointLatitude) > 90) {
throw new BadRequestException(ExceptionMessages.INVALID_LATITUDE);
} else if (Math.abs(startPointLatitude) == 90) {
startPointLatitude = 89.99999 * Math.signum(startPointLatitude); // we have to do this conversion else the formula doesnt return the correct longitude value
}
if (Math.abs(startPointLongitude) > 180) {
throw new BadRequestException(ExceptionMessages.INVALID_LONGITUDE);
}
double angularDistance = distanceInKm / EARTH_RADIUS;
bearing = deg2rad(bearing);
startPointLatitude = deg2rad(startPointLatitude);
startPointLongitude = deg2rad(startPointLongitude);
double latitude = Math.asin(Math.sin(startPointLatitude) * Math.cos(angularDistance)
+ Math.cos(startPointLatitude) * Math.sin(angularDistance) * Math.cos(bearing));
double longitude = startPointLongitude
+ Math.atan2(Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(startPointLatitude),
Math.cos(angularDistance) - Math.sin(startPointLatitude) * Math.sin(latitude));
longitude = (rad2deg(longitude) + 540) % 360 - 180; // normalize longitude to be in -180 +180 interval
LocationDTO result = new LocationDTO();
result.setLatitude(roundValue(rad2deg(latitude)));
result.setLongitude(roundValue(longitude));
return result;
}
private static double roundValue(double value) {
DecimalFormat df = new DecimalFormat("#.#####");
df.setRoundingMode(RoundingMode.CEILING);
return Double.valueOf(df.format(value));
}
// This function converts decimal degrees to radians
private static double deg2rad(double deg) {
return (deg * Math.PI / 180.0);
}
// This function converts radians to decimal degrees
private static double rad2deg(double rad) {
return (rad * 180.0 / Math.PI);
}
I have a high frequency of gps data which i want to downsample to every 50 meters ie keep gps latitude and longitude every 50 meter and discard inbetween points. I found a python code on the internet which basically calculates the distance between two points. But i am not sure how to basically read from a csv the lat and long values and feed it into the function and calculate the distance. If the distance reaches 50 meter i simply save that gps coordinates. So far, i have the following python code
from math import radians, cos, sin, asin, sqrt
def haversine(lon1, lat1, lon2, lat2):
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
r = 6371 # Radius of earth in kilometers. Use 3956 for miles
return c * r
x1 = 52.19421607
x2 = 52.20000327
y1 = -1.484984011
y2 = -1.48533465
result = haversine(x1,y1,x2,y2) #need to give input from a csv
#if result is greater than 50m , save the coordinates
print(result)
How can i solve the problem?Any direction would be appreciated.
Here is a outline and a working code example - where I made some assumptions about which to keep/drop. I assume the dataframe is sorted.
First calculate distance to next point, indeed use haversine for lat/long pairs. This part is not fast in my implementation - you can find faster.
Use cumsum() of distances, to create distance groups, where group 1 is all distances below 50, group 2 between 50 and 100, etc...
Within each group, keep for instance only the first()
Note that this is approximately each 50 units based on group, so be aware this is different than take a point and jump to next point which is closest to 50 units away and repeat. But for data reduction purposes it should be fine.
Generate some random data around London.
import numpy as np
import sklearn
import pandas as pd
LONDON = (51.509865, -0.118092)
random_gps = np.random.random( (10000,2) ) / 25
random_gps[:,0] += np.arange(random_gps.shape[0]) / 25
random_gps[:,0] += LONDON[0]
random_gps[:,1] += LONDON[1]
gps_data = pd.DataFrame( random_gps, columns=["lat","long"] )
Shift the data to get the lat/long of the next point
gps_data['next_lat'] = gps_data.lat.shift(1)
gps_data['next_long'] = gps_data.long.shift(1)
gps_data.head()
Define the distance metric. This part can be improved in terms of speed by using vector expressions with numpy, so if speed is important change this part.
from sklearn.neighbors import DistanceMetric
dist = DistanceMetric.get_metric('haversine')
EARTH_RADIUS = 6371.009
def haversine_distance(row):
point_a = np.array([[row.lat, row.long]])
point_b = np.array([[row.next_lat, row.next_long]])
return EARTH_RADIUS * dist.pairwise(np.radians(point_a), np.radians(point_b) )[0][0]
and apply our distance function (slow part, which can be improved)
gps_data["distance_to_next"] = gps_data.apply( haversine_distance, axis=1)
gps_data["distance_cumsum"] = gps_data.distance_to_next.cumsum()
Finally, create groups and drop. (!) The haversine is returning the distance in KM - so here i wrongly did an example of 50 km instead of meters.
gps_data["distance_group"] = gps_data.distance_cumsum // 50
filtered = gps_data.groupby(['distance_group']).first()
Given a json file,
{"BusStopCode": "00481", "RoadName": "Woodlands Rd", "Description": "BT PANJANG TEMP BUS PK", "Latitude": 1.383764, "Longitude": 103.7583},
{"BusStopCode": "01012", "RoadName": "Victoria St", "Description": "Hotel Grand Pacific", "Latitude": 1.29684825487647, "Longitude": 103.85253591654006}
, and so on..
of various bus stops, I am trying to find the nearest bus stops based on this list of 5000 bus stops with any user given lat/long using the given formula
import math
R = 6371000 #radius of the Earth in m
x = (lon2 - lon1) * cos(0.5*(lat2+lat1))
y = (lat2 - lat1)
d = R * sqrt( x*x + y*y )
My question would be, for user input of lat1 and lon1, how would i be able to compute all distances between lat1 lon1 and lat2 lon2 (where lat2 lon2 will take the value of all 5000 lat/lon in json file), and then print the lowest 5 distances?
I have thought of using list.sort but am not sure of how i am able to compute all 5000 distances using python.
Thank you so much.
Edit:
With the code from Eric Duminil, the following code works for my needs.
from math import cos, sqrt
import sys
import json
busstops = json.loads(open("stops.json").read())
R = 6371000 #radius of the Earth in m
def distance(lon1, lat1, lon2, lat2):
x = (lon2-lon1) * cos(0.5*(lat2+lat1))
y = (lat2-lat1)
return R * sqrt( x*x + y*y )
buslist = sorted(busstops, key= lambda d: distance(d["Longitude"], d["Latitude"], 103.5, 1.2))
print(buslist[:5])
where 103.5, 1.2 from buslist is an example user input longitude latitude.
You could simply define a function to calculate the distance and use it to sort bus stops with the key argument:
from math import cos, sqrt, pi
R = 6371000 #radius of the Earth in m
def distance(lon1, lat1, lon2, lat2):
x = (lon2 - lon1) * cos(0.5*(lat2+lat1))
y = (lat2 - lat1)
return (2*pi*R/360) * sqrt( x*x + y*y )
bustops = [{"BusStopCode": "00481", "RoadName": "Woodlands Rd", "Description": "BT PANJANG TEMP BUS PK", "Latitude": 1.383764, "Longitude": 103.7583},
{"BusStopCode": "01012", "RoadName": "Victoria St", "Description": "Hotel Grand Pacific", "Latitude": 1.29684825487647, "Longitude": 103.85253591654006}]
print(sorted(bustops, key= lambda d: distance(d["Longitude"], d["Latitude"], 103.5, 1.2)))
# [{'BusStopCode': '01012', 'RoadName': 'Victoria St', 'Description': 'Hotel Grand Pacific', 'Latitude': 1.29684825487647, 'Longitude': 103.85253591654006}, {'BusStopCode': '00481', 'RoadName': 'Woodlands Rd', 'Description': 'BT PANJANG TEMP BUS PK', 'Latitude': 1.383764, 'Longitude': 103.7583}]
Once this list is sorted, you can simply extract the 5 closest bus stops with [:5].
It should be fast enough, even with 5000 bus stops.
Note that if you don't care about the specific distance but only want to sort bus stops, you could use this function as key:
def distance2(lon1, lat1, lon2, lat2):
x = (lon2 - lon1) * cos(0.5*(lat2+lat1))
y = (lat2 - lat1)
return x*x + y*y
I've done the same for such a project, but calculating all the distances for a large dataset can take a lot of time.
I ended up with knn nearest neighbors which is much faster and you don't need to recalculate the distance all the time:
import numpy as np
from sklearn.neighbors import NearestNeighbors
buslist = [{ ...., 'latitude':45.5, 'longitude':7.6}, { ...., 'latitude':48.532, 'longitude':7.451}]
buslist_coords = np.array([[x['latitude'], x['longitude']] for x in buslist]) #extracting x,y coordinates
# training the knn with the xy coordinates
knn = NearestNeighbors(n_neighbors=num_connections)
knn.fit(buslist_coords)
distances, indices = knn.kneighbors(xy_coordinates)
# you can pickle these and load them later to determinate the nearest point to an user
# finding the nearest point for a given coordinate
userlocation = [47.456, 6.25]
userlocation = np.array([[userlocation[0], userlocation[1]]])
distances, indices = knn.kneighbors(userlocation)
# get the 5 nearest stations in a list
nearest_stations = buslist[indices[0][:5]] # the order of the buslist must be the same when training the knn and finding the nearest point
# printing the 5 nearest stations
for station in nearest_stations :
print(station)
After that, I built a graph with networkx with these data, but I'm still using knn.kneighbors(userlocation) to find the nearest point of an user.
I want to find a user location within 500 meters from given lat and long in Python.
Given lat & long = 19.114315,72.911174
And I want to check whether new lat and long is in the range of 500 meters from given lat and long..
new lat and long = 19.112398,72.912743
I am using this formula in python..
math.acos(math.sin(19.114315) * math.sin(19.112398) + math.cos(19.114315) * math.cos(19.112398) * math.cos(72.912743 - (72.911174))) * 6371 <= 0.500
But its not giving me expected results.. Am I missing something?
please help..
You can use the Haversine formula to get the great-circle distance (along a sphere) between two points. There's some problems about treating the earth like a sphere for great distances, but for 500 meters, you're probably fine (assuming that you're not trying to drop medical packages on a boat or something).
from math import radians, sin, cos, asin, sqrt
def haversine(lat1, long1, lat2, long2, EARTH_RADIUS_KM=6372.8):
# get distance between the points
phi_Lat = radians(lat2 - lat1)
phi_Long = radians(long2 - long1)
lat1 = radians(lat1)
lat2 = radians(lat2)
a = sin(phi_Lat/2)**2 + \
cos(lat1) * cos(lat2) * \
sin(phi_Long/2)**2
c = 2 * asin(sqrt(a))
return EARTH_RADIUS_KM * c
if the distance between the two points is less than your threshold, it's within the range:
points_1 = (19.114315,72.911174)
points_2 = (19.112398,72.912743)
threshold_km = 0.5
distance_km = haversine(points_1[0], points_1[1], points_2[0], points_2[1])
if distance_km < threshold_km:
print('within range')
else:
print('outside range')
hint: write re-usable code, and cleanup your math.
it seems you have an error, right near the end of that formula..
math.cos(longRad - (longRad)) == math.cos(0) == 1
im not good with geo stuff, so I won't correct it..
import math
def inRangeRad(latRad, longRad):
sinLatSqrd = math.sin(latRad) * math.sin(latRad)
cosLatSqrd = math.cos(latRad) * math.cos(latRad)
inner = sinLatSqrd + cosLatSqrd * math.cos(longRad - (longRad))
return math.acos( inner ) * 6371 <= 0.500
def inRangeDeg(latDeg, longDeg):
latRad = 0.0174532925 * latDeg
longRad = 0.0174532925 * longDeg
return inRangeRad(latRad, longRad)
print "test"
print "19.114315, 72.911174"
print inRangeDeg(19.114315, 72.911174)
Be very careful with this! You cannot just use cos and sin for distances using GPS coordinates, because the distance will be incorrect!
https://en.wikipedia.org/wiki/Geodesy#Geodetic_problems
In the case of plane geometry (valid for small areas on the Earth's
surface) the solutions to both problems reduce to simple trigonometry.
On the sphere, the solution is significantly more complex, e.g., in
the inverse problem the azimuths will differ between the two end
points of the connecting great circle, arc, i.e. the geodesic.
Look into GeoPy for these calculations, you really do not want to implement that yourself.