I'm just playing around with a dataset of my region generated by JOSM. I moved it into a mySQL DB with the 0.6 API scheme using Osmosis and now I'm desperately trying the following:
I want to get all streets of a city.
AFAIK there is no tag/relation in the OSM data to determine this so I tried it using a proximity search to get all nodes in a radius around a node representing the city center.
Most of the time I looked at the approaches here
What I got is the following SQL code that should get me the closest 100 nodes around the node with id 36187002 and within a radius of 10km.
set #nodeid = 36187002;
set #dist = 10;
select longitude, latitude into #mylon, #mylat from nodes where id=#nodeid limit 1;
SELECT id, ( 6371 * acos( cos( radians(#mylon) ) * cos( radians( latitude ) ) *
cos( radians( longitude ) - radians(#mylat) ) + sin( radians(#mylon) ) * sin( radians( latitude ) ) ) )
AS distance
FROM nodes HAVING distance < #dist ORDER BY distance LIMIT 0 , 100;
Well.. it doesn't work. :( I guess the main problem is that OSM lats/lons are multiplied by 10.000.000 and I don't know how I can correct this function to make it work.
Any ideas about this? All solutions/alternatives are very welcome!
It may be faster to add extra columns to your table for the latitude and longitude expressed as the double data type (so the trigonometric functions stand a chance) - you may want to go further and precalculate the xaxis, yaxis and zaxis as columns (again, stored as double)
So, your new columns are loosely (you may need to add data type conversions as required):
XAxis = cos(radians(Latitude / 10000000)) * cos(radians(Longitude / 10000000))
YAxis = cos(radians(Latitude / 10000000)) * sin(radians(Longitude / 10000000))
ZAxis = sin(radians(Latitude / 10000000))
Then, your proximity search becomes:
set #nodeid = 36187002;
set #dist = 10;
SELECT XAxis, YAxis, ZAxis
INTO #CntXAxis, #CntYAxis, #CntZAxis
FROM nodes
WHERE id=#nodeid limit 1;
SELECT id, ( 6371 * acos(
CASE
WHEN nodes.XAxis * #CntXAxis
+ nodes.YAxis * #CntYAxis
+ nodes.ZAxis * #CntZAxis > 1.0 THEN 1.0
ELSE nodes.XAxis * #CntXAxis
+ nodes.YAxis * #CntYAxis
+ nodes.ZAxis * #CntZAxis
END
) AS Distance
FROM nodes
HAVING Distance < #dist
ORDER BY distance LIMIT 0 , 100;
I modified the query a little and it works.
Here my code:
set #nodeid = 122317;
set #dist = 10;
select lon, lat into #mylon, #mylat from nodes where id=#nodeid limit 1;
SELECT id, ( 6371 * acos(
sin(radians(#mylat)) * sin(radians(lat)) +
cos(radians(#mylat)) * cos( radians(lat)) *
cos(radians(lon) - radians(#mylon))
))
AS distance
FROM nodes having distance <#dist
i´ve got the formula from the german wikipedia and it works fine. I've had in in some ruby code fist, but its also work as an sql-query.
To select some special nodes i added this
(select nodes.id,lat,lon,k,v from nodes join node_tags on nodes.id=node_tags.id where k='public_transport') as stations
as the FROM condition to specify the tags of the nodes. (Of course it changes the lat/log access to stations.lat/stations.log in the code above.
Related
I have an array of Lats and Longs that i would like to sort by their distance between each other. I dont have a start or an end lat and long, i just want to sort the array by ascending distance.
What i have doesnt work because i dont have the $lat and $long.
SELECT DISTINCT dist.lat,
dist.lon,
( 111.045 * Degrees(Acos(Cos(Radians($lat)) * Cos(
Radians(dist.lat)) * Cos(
Radians(dist.lon) -
Radians($lon)) +
Sin(Radians($lat)) * Sin(
Radians(dist.lat)))) )AS
distance_in_km
FROM dist
ORDER BY distance_in_km ASC;
What I have:
[[23.454445,-12.33342],[34.45665,-34.555566],[21.222233,-43.444453],
[23.444555,-44.556666],[32.445554,-33.44555788],[23.445598,-67.88909],[54.556677,-66.776655]]
I want to sort this array by proximity.
P.S. These coordinates are not real ones.
Is it possible to search nearest places in mysql by using Bookshelf.js, or Knex? And, if yes, how to do it correct?
UPD:
I'm not strong with raw mysql queries, and can't find how to search with lat, lng in Bookshelf and knex.
For example, i have places table with:
title
address
lat
lng.
What query i should use to get all places in 10 miles radius from Point(lat, lng)?
thanks.
After some research i found working solution:
const {lat, lng} = req.params;
const X1 = +lat - 10,
Y1 = +lng - 10,
X2 = +lat + 10,
Y2 = +lng + 10;
const distanceInMilesSql = `( 3959 * acos( cos( radians(${+lat}) )
* cos( radians( place.lat ) )
* cos( radians( place.lng ) - radians(${+lng}) )
+ sin( radians(${+lat}) )
* sin( radians( place.lat ) ) ) ) AS distance
`;
const places = await new Place().query(qb => {
qb.whereBetween('lat', [X1, X2]);
qb.whereBetween('lng', [Y1, Y2]);
qb.column(['*', Bookshelf.knex.raw(distanceInMilesSql)]);
qb.having('distance', '<', 10);
}).fetchAll({withRelated: [
'place_specific_fields',
'state',
'media'
]});
Demo
http://rextester.com/NDOR4479
SQL
SELECT *,
ACOS(SIN(RADIANS(:lat)) * SIN(RADIANS(lat)) + COS(RADIANS(:lat)) * COS(RADIANS(lat))
* COS(RADIANS(lng - :lng))) * 3959 AS distance
FROM places
WHERE ACOS(SIN(RADIANS(:lat)) * SIN(RADIANS(lat)) + COS(RADIANS(:lat)) * COS(RADIANS(lat))
* COS(RADIANS(lng - :lng))) * 3959 <= 10
ORDER BY distance;
Notes
The above uses the Spherical Law of Cosines where :lat and :lng are the latitude and longitude values from Point and 3959 is the Earth radius in miles.
If it helps anyone, here's some code I use (Strapi + MySQL):
Query for events
Join locations into events, where id matches
Gets anything within 5km (5000 meters) of the user
On a side note: As a Strapi user, a raw query like this negates the high-level functionality that Strapi auto-builds for the events model. ie Strapi url filtering like &Cost_gte=20 won't work. I'm still working on how to get around this and avoid having to build an entire custom controller from scratch (all just to have distance filtering?).
Code:
const knex = strapi.connections.default
const result = await knex('events')
.join('locations', 'events.Location', 'locations.id')
.where(knex.raw(
`round(st_distance_sphere(
st_geomfromtext(CONCAT('POINT(',locations.Longitude, ' ',locations.Latitude,')')),
st_geomfromtext(CONCAT('POINT(` + ctx.query.Longitude + ` ` + ctx.query.Latitude + `)'))
)) <= 5000`
))
return result
You don't make it clear in your question, but I believe that the Bookshelf.js library works as a layer above PostgreSQL, which means that you have access to geometric data types. These can make use of these special operations. The search could be entirely SQL based, so without further context I can simply offer the following:
Combine the latitude and longitude into a single data piece of type point (using the decimal form of the latitude and longitude). Here I will refer to that as coord.
With the proper data in order, you can do a query of:
SELECT p.*, (point'(#origin_long,$origin_lat)' <-> `coord`) distance
FROM `places` p
WHERE circle '((#origin_long, #origin_lat),#radius)' #> `coord`
ORDER BY distance;
If the data structures are set right, this should return a list, ordered by distance, of all pairs of coordinates within a given radius. You can set the coordinates at the top of this function, or maybe even wrap it in a stored procedure (if you don't mind the overhead).
For further reading, I know that PostgreSQL has an "Earth Distance" function as well, so if you are looking to include the calculation of true distance traveled between points, it might be useful!
There's a table called user, and in it, there are some records as following
name -- age -- longitude -- latitude
jack1 24 12.00000 13.0000
jack2 23 16.00000 11.0000
jack3 22 10.00000 11.0000
jack4 25 12.00000 13.0000
...//more records like above
and I have another longitude and latitude, I want to select the name, age, distance from the user order by distance asc, the distance is the user's longitude and latitude with mine, so how can I write this?
You can calculate distance (in miles) this way from latitude & longitude. Say your latitude & longitude is 40.5,80.5 respectively.See related example for idea here Fastest Way to Find Distance Between Two Lat/Long Points
SELECT name, age, (3959 * acos(cos( radians(40.5)) * cos(radians(latitude))
* cos(radians(longitude) - radians(80.5)) + sin( radians(40.5)) *
sin(radians(latitude)))) AS distance
FROM user
ORDER BY distance ASC
Haversine Formula
You can use great circle distance formula. Haversine.
Assuming your lat/lon is 37,-122
SELECT name, age, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) )
* cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin(radians(lat)) ) )
AS distance
FROM `user`
ORDER BY distance
As is already mentioned, the Haversine Formula is what you want to use to calculate distances between two lat/long points. You can implement it as a stored procedure like so:
delimiter //
create function DistanceInKm(
lat1 FLOAT, lon1 FLOAT,
lat2 FLOAT, lon2 FLOAT
) returns float
NO SQL DETERMINISTIC
begin
return degrees(acos(
cos(radians(lat1)) *
cos(radians(lat2)) *
cos(radians(lon2) - radians(lon1)) +
sin(radians(lat1)) * sin(radians(lat2))
)) * 111.045;
END//
delimiter ;
Use 69 instead of 111.045 if you want the distance in miles instead of kilometers.
You can then use this stored procedure in your query in the following way:
select *, DistanceInKm(TARGET_LAT, TARGET_LONG, user.latitude, user.longitude) distance
from user
order by distance asc;
Where TARGET_LAT and TARGET_LONG are the coordinates of the point you are comparing against. Using a stored procedure in the query instead of the formula adds a ton of readability, and also saves you from any bugs introduced by a typo in your formula (so long as you get the stored proc right, of course)
I want to convert the following MySQL to MDB query. I am not at all familiar with access and .mdb databases but I have no choice on this project. I have set up the database in .mdb and can make simple queries to it.
I am trying to convert the following query to mdb query.
SELECT name
, lat
, lng
, ( 3959 * acos( cos( radians('21.222') ) * cos( radians( lat ) )
* cos( radians( lng ) - radians('44.333') )
+ sin( radians('21.222') )
* sin( radians( lat ) ) ) ) AS distance
FROM markers
HAVING distance < '25'
ORDER BY distance ASC LIMIT 1
The query is basically querying a table markers with latitute , longitude information for each record and returns the place which is within 25 miles of the dummy place with latitude,longitude as (21.222,44.333)
Thanks,
Nikhil
You will need to create your own functions for those built in MySQL Mathematic functions, since access won't recognize them. Also the TOP 1 syntax is slightly different. These functions will be only available within the access application itself, so you might have to get really creative how to call this from your PHP script.
Put these math functions into a VBA code module:
Public Function acos(x As Double) As Double
'gets the inverse cosine
acos = Atn(-x / Sqr(-x * x + 1)) + 2 * Atn(1)
End Function
Public Function cos(x As Double) As Double
'gets the cosine
cos = Math.cos(x)
End Function
Public Function radians(degrees As Double) As Double
'returns a degrees measure in radians
Const PI = 3.1415926535
radians = degrees * PI / 180
End Function
Public Function sin(x As Double) As Double
'gets the sine
sin = Math.sin(x)
End Function
Your Access SQL query will look like this (Built using the query builder):
SELECT TOP 1 markers.name, markers.lat, markers.lng, (3959*acos(Cos(radians(21.222))*Cos(radians([lat]))*Cos(radians([lng])-radians(44.333))+Sin(radians(21.222))*Sin(radians([lat])))) AS distance
FROM markers
GROUP BY markers.name, markers.lat, markers.lng, (3959*acos(Cos(radians(21.222))*Cos(radians([lat]))*Cos(radians([lng])-radians(44.333))+Sin(radians(21.222))*Sin(radians([lat]))))
HAVING ((((3959*acos(Cos(radians(21.222))*Cos(radians([lat]))*Cos(radians([lng])-radians(44.333))+Sin(radians(21.222))*Sin(radians([lat])))))<25))
ORDER BY (3959*acos(Cos(radians(21.222))*Cos(radians([lat]))*Cos(radians([lng])-radians(44.333))+Sin(radians(21.222))*Sin(radians([lat]))));
We have a large location DB - with lat long specified for each row. The DB is hosted in MySQL.
We need to run two type of queries:
places nearby (sort by distance)
places nearby by category (where category is a column)
With the number of records growing, this query seems to slow down drastically.
SELECT *, ( 3959 * acos( cos( radians(40.759105) ) * cos( radians( Latitude ) ) * cos( radians( longitude) - radians(-73.984654) ) + sin( radians(40.759105) ) * sin( radians( Latitude ) ) ) ) as distance FROM mcw_in WHERE Latitude <> '' ORDER BY distance LIMIT 0,20
How can I create an index in MySQL to address the slowness? Is there any other solution - like using any geospatial data types?
MySQL Manual :: Introduction to MySQL Spatial Support
MySQL Manual :: Creating Spatial Indexes
But really this won't work in MySQL since they haven't really implemented the functions.
If you are open to it, I would reccomend using PostGIS or Spatialiate (running on Postgresql and SQLLite respectively) or even mongodb or geocouch. These have a much larger suite of implemented spatial functions. If you look at the MySQL documentation it mostly says "not implemented" for the spatial functions.
It is better to use range queries by defining a bounding box surrounding the center. The following query searches the nearest 20 locations within distance $dist from the center ($lat0, $lng0), with the result sorted by the distance. You need two indexes, one on 'lat' and one on 'lng'. Some explanations can be found here.
SELECT *,
( 6371 * acos(
cos(radians($lat0)) * cos(radians(lat)) * cos(radians(lng) - radians($lng0)) +
sin(radians($lat0)) * sin(radians(lat))
) ) AS distance
FROM `locations`
WHERE lat < degrees( asin( sin(radians($lat0)) * cos($dist / 6371) +
cos(radians($lat0)) * sin($dist / 6371) * cos(radians(0)) ))
AND lat > degrees( asin( sin(radians($lat0)) * cos($dist / 6371) +
cos(radians($lat0)) * sin($dist / 6371) * cos(radians(180)) ))
AND lng < $lng0 - degrees( atan2(sin(radians(90)) * sin(radians($dist / 6371)) * cos(radians($lat0)),
cos(radians($dist / 6371)) - sin(radians($lat0)) * sin(radians($lat0))) )
AND lng > $lng0 + degrees( atan2(sin(radians(90)) * sin(radians($dist / 6371)) * cos(radians($lat0)),
cos(radians($dist / 6371)) - sin(radians($lat0)) * sin(radians($lat0))) )
ORDER BY distance LIMIT 20;