Issues with POSTGIS ST_DISTANCE function and results - postgresql

Hi I have a bit of issues with postgis and calculations.
Using ST_DISTANCE causes a bit of confusion:
SELECT st_distance(
ST_SetSRID(ST_MakePoint(16.0420,45.8250), 4326),
ST_SetSRID(ST_MakePoint(16.1675,45.8344), 4326));
returns 0.12585153952177025 as an result.
The result seems a bit odd as on a simple visible checking those points are quite far away and result
should be in meters.
For instance :
select st_distance(
'POINT(15.651955 73.712769 )'::geography,
'POINT(14.806993 74.131451 )'::geography) AS d;
returns 53536.74349675 which seems to work properly.
Can you tell me what I'm doing wrong?

st_distance returns a distance in the unit of the used coordinate system. For 4326, the unit is degree, so the distance is in degrees (which is meaningless).
As you have done, you can cast to geography first, which returns a result in meters. You can also use ST_DistanceSpheroid to get a distance in meters, or you can use a suitable coordinate system whose unit is in meters.

Related

PostGIS convert meters to degree using epsg 5186

I have a few questions about geometry and geography in PostGIS.
I am currently using PostGIS and Postgresql.
Most of my spatial data is from Korea which is basically latitude and longitude.
For testing, I have created two tables with the same latitude and longitude data but different data types, one for geography with SRID 4326 and the other for geometry with SRID 5186.
create table geometry_stores
(
id serial primary key,
location geometry(POINT, 5186) not null
);
create table geography_stores
(
id serial primary key,
location geography(POINT, 4326) not null
);
You can find more details of EPSG 5186 on this link https://epsg.io/5186
Here is a list of question I have got:
PostGIS has this method
ST_DWithin(geometry g1, geometry g2, double precision distance_of_srid);
is distance_of_srid a unit of EPSG? Is there any way I can convert meters (e.g. 1km) to distance_of_srid with EPSG 5186?
I understand that geography calculation measures the distances between points as true paths over a sphere while geometry calculation measures the distances between points as true paths over a Cartesian plane. Then if I give exactly the same distance to the following queries, they are supposed to yield different results or same results? because my understanding is that geometry with SRID 5186 is already projected with distortion of earth, then they should yield the same results?
select *
from geography_stores
where st_dwithin(location, st_setsrid(st_point(126.970769, 37.555479), 4326), same_distance_meter)
select *
from geometry_stores
where st_dwithin(location, st_setsrid(st_point(126.970769, 37.555479), 5186), same_distance_degree)
When I calculate distance on the geometry table with the following query, it gives me a degree, not meters. Is there any way I can convert this degree to meters with consideration of distortion of the earth?
select st_distance(location, st_setsrid(st_point(126.970769, 37.555479), 5186))
from geometry_stores
where id = 1;
I have tried with this query but got some error of Only lon/lat coordinate systems are supported in geography. Where: SQL function "st_distancesphere" during inlining
select st_distancesphere(location, st_setsrid(st_point(126.970769, 37.555479), 5186))
from geometry_stores
where id = 1;
I have read documents at the PostGIS website and some questions in StackOverflow but still got those three questions. Thank you guys for your help.
------------------------------- UPDATED -----------------------------------------
The column for my spatial data is geometry(POINT, 5186) so the table definition would be as following. Note that it is not geometry(POINT, 4326) not to convert it to geometry(POINT, 5186) on calculation. Should I store my data in geometry(POINT, 4326) and convert it on calculation?
create table geometry_stores
(
id serial primary key,
location geometry(POINT, 5186) not null
);
I executed following query and got results as follows:
select st_distance(st_setsrid(st_makepoint(126.808183, 37.463557), 4326)::geography,
st_setsrid(st_makepoint(126.970769, 37.555479), 4326)::geography);
st_distance
--------------
17627.3138509
select st_distance(st_setsrid(st_makepoint(126.808183, 37.463557), 5186)::geometry,
st_setsrid(st_makepoint(126.970769, 37.555479), 5186)::geometry)
st_distance
--------------
0.186772218169622
It seems that the second's query gives me degree while the first one gives me meters. Am I doing something wrong in my query, please?
For st_within, I populated 3M data in geometry_stores table and the spatial data spread over at least 10km. I executed the following query.
select *
from users
where st_dwithin(location, st_setsrid(st_point(126.970769, 37.555479), 5186), 0.001)
This query gives me 158 rows and geometry viewer displays as per picture below.
Let's execute the same query with distance 1 not 0.0001
select *
from users
where st_dwithin(location, st_setsrid(st_point(126.970769, 37.555479), 5186), 1)
This query gives me 32792923 rows which is all data in the table.
Considering that the spatial data spread over at least 10km, it seems that the st_within query calculates the distance between two geometries with a unit (degeree) of EPSG5186 not meters. Then, I'd like to know if I can convert meters to the unit (degree) of EPSG5186 because I'd like to query with meters, not degree which I don't know how far a unit (degree) of EPSG5186 is.
Is distance_of_srid a unit of EPSG?
Yes. Distances using geometry type geometries are calculated using the unit of measurement from the corresponding Spatial Reference System.
Is there any way I can convert meters (e.g. 1km) to distance_of_srid with EPSG 5186?
According to the documentation, the unit of EPSG:5186 is already metres, so you don't have to convert anything. Bur also keep in mind that distances using geography type geometries are also calculated using metres, e.g.
SELECT
ST_Distance(
'SRID=4326;POINT(127.49 36.65)'::geometry,
'SRID=4326;POINT(128.06 36.43)'::geometry) AS geometry_distance,
ST_Distance(
'SRID=4326;POINT(127.49 36.65)'::geography,
'SRID=4326;POINT(128.06 36.43)'::geography) AS geography_distance
;
geometry_distance | geography_distance
-------------------+--------------------
0.610982814815612 | 56578.57823391
(1 Zeile)
Then if I give exactly the same distance to the following queries, they are supposed to yield different results or same results? because my understanding is that geometry with SRID 5186 is already projected with distortion of earth, then they should yield the same results?
The results will differ. They might have the same unit of measurement, but they aren't projected on the same surface. The following example transforms the coordinates from 4326 to 5186 and calculates the distance:
SELECT
ST_Distance(
'SRID=4326;POINT(127.49 36.65)'::geography,
'SRID=4326;POINT(128.06 36.43)'::geography),
ST_Distance(
ST_Transform('SRID=4326;POINT(127.49 36.65)'::geometry,5186),
ST_Transform('SRID=4326;POINT(128.06 36.43)'::geometry,5186));
st_distance | st_distance
----------------+------------------
56578.57823391 | 56582.0899018353
(1 Zeile)
When I calculate distance on the geometry table with the following query, it gives me a degree, not meters. Is there any way I can convert this degree to meters with consideration of distortion of the earth?
Isn't the data type geography what you're looking for? As the documentation says:
Regardless which spatial reference system you use, the units returned by the measurement (ST_Distance, ST_Length, ST_Perimeter, ST_Area) and for input of ST_DWithin are in meters.
Just for fun, the following query calculates the distance between two points explicitly defining the 4326 spheroid, using ST_DistanceSpheroid, and casting the coordinates from geometry to geography, which basically does the same:
SELECT
ST_DistanceSpheroid(
'POINT(127.49 36.65)',
'POINT(128.06 36.43)',
'SPHEROID["WGS 84",6378137,298.257223563]'),
ST_Distance(
'SRID=4326;POINT(127.49 36.65)'::geography,
'SRID=4326;POINT(128.06 36.43)'::geography);
st_distancespheroid | st_distance
---------------------+----------------
56578.5782339123 | 56578.57823391
Regarding when to use geometry or geography the documentation says:
"The type you choose should be conditioned on the expected working area of the application you are building. Will your data span the globe or a large continental area, or is it local to a state, county or municipality?"
Things to consider:
use case covers small area: stick to geometry and use a SRS that better suits your area.
use case covers large areas (countries/continents): use geography - although it might be a bit slower.
Do the functions you wanna use support geography? Most PostGIS functions do not support it! Check this matrix for more details. If the functions you wanna use do not support geography, you have no other choice but to use geometry ;-) Since your use case mostly covers Korea, I see no problem in using EPSG 5186.
EDIT: Regarding the question Update.
You cannot simply change the SRID of geometries to get it transformed into another reference system! What you've done was to get a WGS84 coordinate pairs and simply exchange its SRID, which is not the way it works. You have to always use ST_Transform for that. Take a look what the coordinates look like after you apply it:
SELECT
ST_AsText(ST_Transform('SRID=4326;POINT(126.808183 37.463557)'::geometry,5186));
st_astext
------------------------------------------
POINT(183030.248454493 540476.713582621)
(1 Zeile)
It means that POINT(183030.248454493 540476.713582621) and POINT(126.808183 37.463557) are the same coordinate pairs, but in different reference systems. The following query will make it clear that both geography and 5186 return results in metres:
SELECT
--Transforming from 4326 to 5186 and calculating the distance
ST_Distance(
ST_Transform('SRID=4326;POINT(126.808183 37.463557)'::geometry, 5186),
ST_Transform('SRID=4326;POINT(126.970769 37.555479)'::geometry, 5186)),
-- Distance using geography
ST_Distance(
'SRID=4326;POINT(126.808183 37.463557)'::geography,
'SRID=4326;POINT(126.970769 37.555479)'::geography);
st_distance | st_distance
------------------+---------------
17627.3383377316 | 17627.3138509

Use ST_DWithin to query pairs of geometry points within 400 miles in a PostgreSQL table

I have a table called h2combines in Postgres that has two points-geometry fields: geom1 and geom2, and some other fields. I want to select all records that geom1 and gemo2 are with 400 miles. I tried this:
SELECT * from h2combines WHERE ST_DWithin(geom1, geom2, 643738);
However, it returned all rows. Seems I misunderstand something here.
Thanks!
Welcome to SO.
To get the distance in meters/miles you have to cast your geometries to geography, e.g.
SELECT * FROM h2combines
WHERE ST_DWithin(geom1::geography, geom2::geography, 643737.6);
Keep in mind that calculations using GEOMETRY and GEOGRAPHY are made differently, and so are their results. GEOGRAPHY calculates the coordinates over an spherical surface (which can be much slower than GEOMETRY) and uses meters as unit of measurement, while GEOMETRY uses a planar projection and uses the SRS unit.

How to configure PostgreSQL with Postgis to calculate distances

I know that it might be dumb question, but I'm searching for some time and can't find proper answer.
I have PostgreSQL database with PostGIS installed. In one table I have entries with lon lat (let's assume that columns are place, lon, lat).
What should I add to this table or/and what procedure I can use, to be able to count distance between those places in meters.
I've read that it is necessary to know SRID of a place to be able to count distance. Is it possible to not know/use it and still be able to count distance in meters basing only on lon lat?
Short answer:
Just convert your x,y values on the fly using ST_MakePoint (mind the overhead!) and calculate the distance from a given point, the default SRS will be WGS84:
SELECT ST_Distance(ST_MakePoint(lon,lat)::GEOGRAPHY,
ST_MakePoint(23.73,37.99)::GEOGRAPHY) FROM places;
Using GEOGRAPHY you will get the result in meters, while using GEOMETRY will give it in degrees. Of course, knowing the SRS of coordinate pairs is imperative for calculating distances, but if you have control of the data quality and the coordinates are consistent (in this case, omitting the SRS), there is not much to worry about. It will start getting tricky if you're planing to perform operations using external data, from which you're also unaware of the SRS and it might differ from yours.
Long answer:
Well, if you're using PostGIS you shouldn't be using x,y in separated columns in the first place. You can easily add a geometry / geography column doing something like this.
This is your table ...
CREATE TABLE places (place TEXT, lon NUMERIC, lat NUMERIC);
Containing the following data ..
INSERT INTO places VALUES ('Budva',18.84,42.92),
('Ohrid',20.80,41.14);
Here is how you add a geography type column:
ALTER TABLE places ADD COLUMN geo GEOGRAPHY;
Once your column is added, this is how you convert your coordinates to geography / geometry and update your table:
UPDATE places SET geo = ST_MakePoint(lon,lat);
To compute the distance you just need to use the function ST_Distance, as follows (distance in meters):
SELECT ST_Distance(geo,ST_MakePoint(23.73,37.99)) FROM places;
st_distance
-----------------
686560.16822422
430876.07368955
(2 Zeilen)
If you have your location parameter in WKT, you can also use:
SELECT ST_Distance(geo,'POINT(23.73 37.99)') FROM places;
st_distance
-----------------
686560.16822422
430876.07368955
(2 Zeilen)

Most efficient way to find points within a certain radius from a given point

I've read several questions + answers here on SO about this theme, but I can't understand which is the common way (if there is one...) to find all the points whithin a "circle" having a certain radius, centered on a given point.
In particular I found two ways that seem the most convincing:
select id, point
from my_table
where st_Distance(point, st_PointFromText('POINT(-116.768347 33.911404)', 4326)) < 10000;
and:
select id, point
from my_table
where st_Within(point, st_Buffer(st_PointFromText('POINT(-116.768347 33.911404)', 4326), 10000));
Which is the most efficient way to query my database? Is there some other option to consider?
Creating a buffer to find the points is a definite no-no because of (1) the overhead of creating the geometry that represents the buffer, and (2) the point-in-polygon calculation is much less efficient than a simple distance calculation.
You are obviously working with (longitude, latitude) data so you should convert that to an appropriate Cartesian coordinate system which has the same unit of measure as your distance of 10,000. If that distance is in meter, then you could also cast the point from the table to geography and calculate directly on the (long, lat) coordinates. Since you only want to identify the points that are within the specified distance, you could use the ST_DWithin() function with calculation on the sphere for added speed (don't do this when at very high latitudes or with very long distances):
SELECT id, point
FROM my_table
WHERE ST_DWithin(point::geography,
ST_GeogFromText('POINT(-116.768347 33.911404)'),
10000, false);
I have used following query
SELECT *, ACOS(SIN(latitude) * SIN(Lat)) + COS(latitude) * COS(Lat) * COS(longitude) - (Long)) ) * 6380 AS distance FROM Table_tab WHERE ACOS( SIN(latitude) * SIN(Lat) + COS(latitude) * COS(Lat) * COS(longitude) - Long )) * 6380 < 10
In above query latitude and longitude are from database and lat, long are the points from we want to search.
WORKING : it will calculate the distance(In KM) between all the points in database from search points and check if the distance is less then 10 KM. It will return all the co-ordinates within 10 KM.
I do not know how postgis does it best, but in general:
Depending on your data it might be best to first search in a square bounding box (which contains the search area circle) in order to eliminate a lot of candidates, this should be extremely fast as you can use simple range operators on lon/lat which are ideally indexed properly for this.
In a second step search using the radius.
Also if your limit max points is relatively low and you know you have a lot of candidates, you may simply do a first 'optimistic' attempt with a box inside your circle, if you find enough points you are done !

postgis: point returned in ST_LineLocatePoint not able to detect in ST_Contains

I am using postgis's ST_LineLocatePoint to find out the closest point on a LineString to the given Point, and using ST_LineInterpolatePoint to extract a Point from the returned float number.
ST_LineLocatePoint Query:
SELECT ST_AsText(ST_LineInterpolatePoint(foo.the_line,
ST_LineLocatePoint(foo.the_line,
ST_GeomFromText('POINT(12.962315 77.584841)')))) AS g
FROM (
SELECT ST_GeomFromText('LINESTRING(12.96145 77.58408,12.96219 77.58447,12.96302 77.58489,
12.96316 77.58496,12.96348 77.58511)') AS the_line
) AS foo;
Output:
g
------------------------------------------
POINT(12.9624389808159 77.5845959902924)
Which exactly lies on the linestring I have passed. Demonstration is displayed here.
But when I check whether this point lies in the linestring using ST_Contains it always return false, even though the point lies within.
ST_Contains Query:
SELECT ST_Contains(ST_GeomFromText('LINESTRING(12.96145 77.58408,12.96219 77.58447,
12.96302 77.58489, 12.96316 77.58496, 12.96348 77.58511)'),
ST_GeomFromText('POINT(12.9624389808159 77.5845959902924)'));
Output
st_contains
-------------
f
I am not getting where I am doing wrong. Can anyone help me in this.
Postgresql : 9.4
postgis : 2.1
reference: ST_LineLocatePoint, ST_Contains
I am not getting where I am doing wrong.
I think you're doing good... I had the same issue some time ago... I used ST_ClosestPoint to locate point on linestring and then cut a linestring with this point, but I can't.
Following the documentation:
ST_ClosestPoint — Returns the 2-dimensional point on g1 that is
closest to g2. This is the first point of the shortest line.
So I get situation where one function says - this point is on a line, and other functions says - ok, but I can't cut cause your point is not on a line... I was confused like you're now...
In my case resolution was to draw another line which will intersect first line 'exactly' in given point and after that first line was cutted...
After some research I found issue was about rounding of coordinates counted and writen. I explain it to myself that, according to the definitions line is infinitely thin and point is infinitely small (they do not have the area), so they can easily miss each other - but it's my reasoning and I'm not sure whether it is good. I advice you to use st_intersects, but with very low st_buffer or ST_DWithin function also with very low distance.
To be sure that your point lies on a line it have to be a part of this line (e.g. LINESTRING(0 0, 5 5) points (0 0) and (5 5). Example with point(3 3) works because it's coordinates are counted without any roundings.
This is actually a really common question (most likely a duplicate, but I'm too lazy to find it.)
The issue is related to numerical precision, where the Point is not exactly on the LineString, but is within a very small distance of it. Sort of like how SELECT sin(pi()) is not exactly zero.
Rather than using DE-9IM spatial predicates (like Contains, or Covers, etc.) which normally expect exact noding, it is more robust to use distance-based techniques like ST_DWithin with a small distance threshold. For example:
SELECT ST_Distance(the_point, the_line),
ST_Covers(the_point, the_line),
ST_DWithin(the_point, the_line, 1e-10)
FROM (
SELECT 'POINT(12.9624389808159 77.5845959902924)'::geometry AS the_point,
'LINESTRING(12.96145 77.58408,12.96219 77.58447,12.96302 77.58489,12.96316 77.58496,12.96348 77.58511)'::geometry AS the_line
) AS foo;
-[ RECORD 1 ]----------------------
st_distance | 1.58882185807825e-014
st_covers | f
st_dwithin | t
Here you can see that ST_DWithin indicates that the point is within a very small distance of the line, so it effectively contains the point.
ST_Contains() only returns true if the geometry to test lies within the supplied geometry. In your case the point has to lie within the linestring and this is always false since a linestring does not have an interior.
You should use the ST_Covers() function instead: true if no point of the geometry to test (your point) lies outside the supplied geometry (your linestring).