Query PostGIS radius giving me odd results - postgresql

I'm new to PostGIS and I'm trying to create a radius query. I have a table with a geometry field (position) and also have the latitude and longitude values on separate fields.
I'm trying to find points on a 10 km radius from lat: 40.753777, lon: -73.981568.
with:
SELECT postcode, lat, lon, st_asgeojson(position) geojson, ST_Distance(ST_MakePoint(lat, lon), ST_MakePoint(40.753777, -73.981568)) distance FROM addresses WHERE ST_DWithin(ST_MakePoint(lat, lon), ST_MakePoint(40.753777, -73.981568), 10000) order by id limit 10;
The results give me very far a way points. The same query with earth distance using the lat and lon directly give me much closer results.
SELECT postcode, lat, lon, st_asgeojson(position) geojson FROM addresses WHERE earth_box(ll_to_earth(40.753777, -73.981568), 10000) #> ll_to_earth(addresses.lat, addresses.lon) order by id limit 10;
But I really don't know if this is right either, what's wrong with the PostGIS query?

A few notes:
I think you swapped the lat and lon when you made a point, your line says ST_MakePoint(40.753777, -73.981568), but the definition is:
geometry ST_MakePoint(double precision x, double precision y);
Note: x is longitude and y is latitude
So it should have been ST_MakePoint(-73.981568, 40.753777) instead.
As a simple solution you can use ST_Distance_Spheroid function ( http://www.postgis.org/docs/ST_Distance_Spheroid.html ):
SELECT
postcode, lat, lon, st_asgeojson(position) AS geojson,
ST_Distance_Spheroid(
position,
ST_GeomFromText('POINT(-73.981568 40.753777)',
4326), 'SPHEROID["WGS 84",6378137,298.257223563]'
) as distance
FROM addresses
WHERE distance < 10000 LIMIT 10;
For a more precise distances, add a new column of type geography from you existing column position of type geometry:
ALTER TABLE addresses ADD COLUMN position_geog geography(Point,4326);
UPDATE addresses SET position_geog = position::geography;
CREATE INDEX ON addresses USING gist(position_geog);
-- and now you can use ST_DWITHIN with meters...

Related

Indexing issue in postgres

It is impossible to speed up the database due to indexing.
I create a table:
CREATE TABLE IF NOT EXISTS coordinate( Id serial primary key,
Lat DECIMAL(9,6),
Lon DECIMAL(9,6));
After that I add indexing:
CREATE INDEX indeLat ON coordinate(Lat);
CREATE INDEX indeLon ON coordinate(Lon);
Then the table is filled in:
INSERT INTO coordinate (Lat, Lon) VALUES(48.685444, 44.474254);
Fill in 100k random coordinates.
Now I need to return all coordinates that are included in a radius of N km from a given coordinate.
SELECT id, Lat, Lon
FROM coordinate
WHERE acos(sin(radians(48.704578))*sin(radians(Lat)) + cos(radians(48.704578))*cos(radians(Lat))*cos(radians(Lon)-radians(44.507112))) * 6371 < 50;
The test execution time is approximately 0.2 seconds, and if you do not do CREATE INDEX, the time does not change. I suspect that there is an error in the request, maybe you need to rebuild it somehow?
I'm sorry for my english
An index can only be used if the indexed expression is exactly what you have on the non-constant side of the operator. That is obviously not the case here.
For operations like this, you need to use the PostGIS extension. Then you can define a table like:
CREATE TABLE coordinate (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
p geography NOT NULL
);
and query like this:
SELECT id, p
FROM coordinate
WHERE ST_DWithin(p, 'POINT(48.704578 44.507112)'::geography, 50);
This index would speed up the query:
CREATE INDEX ON coordinate USING gist (p);

Create new column with coordinates from centroids using st_transform in PostGIS / PostgreSQL

I have geometry data that I'm trying to transform to lon lat and store in a new column. I have created a new column called 'coordinates' and written the following query:
UPDATE places_centroids
SET coordinates = st_y(st_transform(new_centroid, 4326)) AS lat,
st_x(st_transform(new_centroid, 4326)) AS lon;
But it returns: ERROR: ERROR: syntax error at or near
LINE 2: ...coordinates = st_y(st_transform(new_centroid, 4326)) AS lat,
However, it works fine when i writing the query with a select statement:
SELECT st_y(st_transform(new_centroid, 4326)) AS lat,
st_x(st_transform(new_centroid, 4326)) AS lon
FROM places_centroids;
Can anyone see what's wrong with my query?
You can create two columns, lat and lng for example of type double precision and do this:
UPDATE places_centroids
SET lat = st_y(st_transform(new_centroid, 4326)),
lng= st_x(st_transform(new_centroid, 4326));
or define coordinates as the native point type:
UPDATE places_centroids
SET coordinates = point(st_y(st_transform(new_centroid, 4326)),
st_x(st_transform(new_centroid, 4326)));
I personally prefer to store in two columns.
Best regards,
Bjarni

PostgreSQL select query to extract latitude and longitude from a point

What SELECT query should be used to extract latitude and longitude from a point?
I cannot use PostGIS.
Example point (point type value) stored in the database:
my_point
--------------
(50.850,4.383)
Expected result after executing the query:
lat | lng
---------------
50.850 | 4.383
The query below works fine but it does not look efficient.
SELECT
split_part(trim(my_point::text, '()'), ',', 1)::float AS lat,
split_part(trim(my_point::text, '()'), ',', 2)::float AS lng
FROM my_table;
Always Read The Fine Manuals
It is possible to access the two component numbers of a point as though the point were an array with indexes 0 and 1. For example, if t.p is a point column then SELECT p[0] FROM t retrieves the X coordinate and UPDATE t SET p1 = ... changes the Y coordinate. In the same way, a value of type box or lseg can be treated as an array of two point values.
Another option would be:
SELECT
ST_X(point) as longitude,
ST_Y(point) as latitude
FROM your_table_name

Unit of return value of ST_Distance

I need to calculate the distance between
all buildings and
all hospitals
in a map imported from OSM.
I use following query:
SELECT building_id, hospital_id, ST_Distance(building_centroid, hospital_location)
FROM
(
select planet_osm_polygon.osm_id building_id, ST_Centroid(planet_osm_polygon.way) building_centroid
from planet_osm_polygon
where building = 'yes'
) buildings,
(
select planet_osm_point.osm_id hospital_id, planet_osm_point.way hospital_location
from planet_osm_point
where amenity = 'hospital') hospitals
I get strange results - the distance is always smaller than 1.
How can I get the to know the unit, in which these values are reported?
Update 1: Sample result:
Update 2: This query seems to work
SELECT building_id, hospital_id, ST_Distance_sphere(building_centroid, hospital_location) distance
FROM
(
select planet_osm_polygon.osm_id building_id, ST_Centroid(planet_osm_polygon.way) building_centroid
from planet_osm_polygon
where building = 'yes'
) buildings,
(
select planet_osm_point.osm_id hospital_id, planet_osm_point.way hospital_location
from planet_osm_point
where amenity = 'hospital') hospitals
ORDER BY distance
The general rule for units is that the output length units are the same as the input length units.
The OSM way geometry data has length units of degrees of latitude and longitude (SRID=4326). Therefore, the output units from ST_Distance will also have lenth units of degrees, which are not really useful.
There are several things you can do:
Use ST_Distance_Sphere for fast/approximate distances in metres
Use ST_Distance_Spheroid for accurace distances in metres
Convert the lat/long geometry data types to geography, which automagically makes ST_Distance and other functions to use linear units of metres

Finding and Ordering Latitude and Longitude in SQL

I have a SQL database where I store longitude and latitude from an iPhone application. I need to query all the records starting from a given location to the other far most location.
For example, I have longitude x and latitude y. I want all the records first whose longitude matches x the most closely and whose latitude matches y the most closely. I need to all the records one by one in the chain from nearest to farthest. The more distant the location, the greater the value of longitude and latitude will be than x and y.
I hope you got the point and I am waiting for the answer.
Distance with latitude and longitude is not a simple calculation, but one requiring spherical trigonometry.
acos(cos(lat1)*cos(lon1)*cos(lat2)*cos(lon2) +
cos(lat1)*sin(lon1)*cos(lat2)*sin(lon2) +
sin(lat1)*sin(lat2)) * R(adius of the earth)
So this query
select locID, locName, locDesc, lat, lon, locDiffMeters
from (select locID, locName, locDesc, lat, lon,
acos(cos($lat)*cos($lon)*cos(lat)*cos(lon) +
cos($lat)*sin($lon)*cos(lat)*sin(lon) +
sin($lat)*sin(lat) ) * 6,371,000 -- earths radius in meters
as locDiffMeters
from locationTable
where locID <> $ID
) a
order by locDiffMeters
Is probably the right answer, assuming you have that capable of a math library.
Similar to Fosco, but using Pythagoras' Theorem:
select locID, locName, locDesc, lat, lon, locDiff from
(select locID, locName, locDesc, lat, lon,
sqrt((lat - $LAT)*(lat - $LAT) + (lon - $LON)*(lon - $LON)) as locDiff
from locationTable
where locID <> $ID) a
order by locDiff
For really large distances (or locations far from the equator) you should ideally use a geodesic.
Assuming that you've queried the start location lat/lon and location ID... I am using $LAT, $LON, and $ID as placeholders:
select locID, locName, locDesc, lat, lon, locDiff
from (
select locID, locName, locDesc, lat, lon, ABS(lat - $LAT) + ABS(lon - $LON) as locDiff
from locationTable
where locID <> $ID
) a
order by locDiff
Hopefully this helps... may not be the most optimized method, but it should be pretty close.
If you don't have to deal with large distances (see Adam's answer), you might consider using PostgreSQL's geometric types and associated functions.
If you're using Postgresql, add the PostGIS extension and check out the ST_Distance_Sphere
ST_Distance_Sphere — Returns minimum distance in meters between two lon/lat geometries. Uses a spherical earth and radius of 6370986 meters. Faster than ST_Distance_Spheroid, but less accurate. PostGIS versions prior to 1.5 only implemented for points.
SELECT round(CAST(ST_Distance_Sphere(ST_Centroid(the_geom), ST_GeomFromText('POINT(-118 38)',4326)) As numeric),2) As dist_meter ...