Strange difference with ST_DWithin - Geography vs Geometry - postgresql

(EDIT: Sorry. Forgot to mention. Postgresql 13, PostGIS: 3.1 USE_GEOS=1 USE_PROJ=1 USE_STATS=1. Sorry about that).
The following 2 queries, in my opinion, should return "true" in both cases.
The point is clearly within the polygon (square). Displaying both certainly indicates so.
I am starting to wonder what fondamental thing I am missing here.
To be honest, I have been circling around this issue for years, as I am never able to find something "final" to understand what is happening. In this case, the point is somewhat near the edge of the polygon, but from time to time I had the same issues with points that were well within the polygon.
The only difference between the 2 queries is that one uses geometry the other uses geography. As you can see, we use 0.0 for the distance value. This is like an intersects.
OH, and by the way, using intersects yields the same results. I am very confused about this.
select
ST_Dwithin(
St_GeometryFromText(
'POLYGON((-95.979486 41.676556,-81.432269 41.676556,-81.432269 30.196043,-95.979486 30.196043,-95.979486 41.676556))', 4326),
ST_GeometryFromText('POINT (-87.70551 30.24481)', 4326),
0
)
select
ST_Dwithin(
St_GeographyFromText(
'POLYGON((-95.979486 41.676556,-81.432269 41.676556,-81.432269 30.196043,-95.979486 30.196043,-95.979486 41.676556))'),
St_GeographyFromText('POINT (-87.70551 30.24481)'),
0
)
EDIT.
Sorry, not sure of the right etiquette. Following the great responses, I am adding some more context, in case somebody finds it interesting.
I am working on a "world" dataset containing country polygons. Since some of these are huge, I have elected to st_subdivide them.
![st_subdivide usa] https://imgur.com/IQRz8vg
And when zooming we can see EXACTLY what is explained in the answer below:
![zoom st_subdivide usa] https://imgur.com/fgmpBx0
So wow, perfect explanation, thank you very much.
I know that I should not use geography, well, "should not". However, I also need to work a lot with distances and expansion, and geom(4326) is quite annoying with units I find.

First of all, you need to understand the difference between geometry and geography.
Geometry, where it assumes all of your data lives on a Cartesian plane.
Geography, where it assumes that your data is made up of points on the earth's surface.
The problem is the following. When you are projecting the geometry, PostGIS is assuming that the edges of the geometry are not planar (since the earth's surface is not planar) and transforming them to spherical (this process is called tessellation). Therefore, when you try to intersect them the point is not inside de polygon.
If the geographies are small enough, the difference between spherical and planar edges can be ignored, but in your case, the polygon is a huge one.
You are projecting your geometry on the EPSG 4326 in order to project it on the earth's surface, however although the point is inside the square in a planar coordinate system, it is not in a spherical one.
To sum up, you need to know what type, geometry or geography, works depending on your use case.
You can solve this issue by forcing the edges to be planar (in some implementations of PostGIS as BigQuery):
St_GeogFromText(
'POLYGON((-95.979486 41.676556,-81.432269 41.676556,-81.432269 30.196043,-95.979486 30.196043,-95.979486 41.676556))',
planar => True)

In addition to the answer from #sergiomahi - which should be accepted - I would like to add a few examples to make his point clearer. The issue isn't the type geography itself, but in which surface the features are projected.
Given your polygon and point, if we ST_Distance using geometry the distance is zero, since it will use a Cartesian plane, but using geography it returns 16524.35577956 metres due to the spherical edges and the size of your polygon, as pointed out by #sergiomahi. So, they no longer overlap.
WITH j (pol,poi) AS (
VALUES
('SRID=4326;POLYGON((-95.979486 41.676556,-81.432269 41.676556,-81.432269 30.196043,-95.979486 30.196043,-95.979486 41.676556))',
'SRID=4326;POINT (-87.70551 30.24481)')
)
SELECT
ST_Distance(poi::geometry,pol::geometry) AS geom_dist,
ST_Distance(poi::geography,pol::geography) AS geog_dist
FROM j;
geom_dist | geog_dist
-----------+----------------
0 | 16524.35577956
However, if you stick to geometry and tell PostGIS that the distance is supposed to be calculated using a sphere you get similar results as with geography:
WITH j (pol,poi) AS (
VALUES
('SRID=4326;POLYGON((-95.979486 41.676556,-81.432269 41.676556,-81.432269 30.196043,-95.979486 30.196043,-95.979486 41.676556))',
'SRID=4326;POINT(-87.70551 30.24481)'))
SELECT
ST_Distance(poi::geography,pol::geography) AS geog_dist,
ST_DistanceSphere(poi::geometry,pol::geometry) AS geom_dist_sphere
FROM j;
geog_dist | geom_dist_sphere
----------------+------------------
16524.35577956 | 16574.61755546
(1 Zeile)

Related

Can someone tell me why this keeps returning degrees instead of meters?

Can someone tell me why this keeps returning degrees instead of meters? I’m transforming the geometry SRID to 32613, which measures in meters. Thanks
SELECT storm_date, hail_size_inches,
ST_Distance(
ST_Transform(geom32613, 32613),
ST_SetSRID(
ST_MakePoint(-104.89907, 39.66643),
32613)
) distance
FROM hail.hail_swaths
WHERE storm_date >= '2021/06/01'
You are using lat-long coordinates (4326) as if they were in 32613.
ST_SetSRID(ST_MakePoint(-104.89907, 39.66643), 32613) --> replace with ST_Transform(ST_SetSRID(ST_MakePoint(-104.89907, 39.66643), 4326),32613);
Also double check what values are stored in the column geom32613. If they are indeed in 32613, there is no need to reproject them
Example for 1 degree, near the central meridian for this projection:
SELECT ST_Distance(ST_Transform('SRID=4326;POINT(-105 40)',32613),
jgtest(> ST_Transform('SRID=4326;POINT(-106 40)',32613));
st_distance
------------------
85361.8049211818
Welcome to SO.
Your problem might be somewhere else. ST_Distance with two geometries using the SRS 32613 returns the distance in metres:
SELECT ST_Distance('SRID=32613;POINT(508654.55672303465 4390740.143711988)',
'SRID=32613;POINT(508654.55672303480 4390740.143711988)');
st_distance
----------------------
1.74622982740402e-10
(1 row)
It also works using ST_Transform
SELECT ST_Distance(
ST_Transform('SRID=4326;POINT(-104.89910 39.66643)',32613),
ST_Transform('SRID=4326;POINT(-104.89907 39.66643)',32613));
st_distance
------------------
2.57321026907276
(1 row)
Demo: db<>fiddle
Are you perhaps mixing the order of the coordinate pairs? Remember, it is longitude, latitude, not the other way around. If the geometries are correct, please post a WKT literal from both geometries, so that we can reproduce your environment. Another option would be to use geography instead of geometry, which would automatically return the result in metres, but you would need to transform the geometries encoded in 32613 in a lon/lat coordinate system to make the cast work, such as 4326.
EDIT: Read carefully the answer of #JGH - he might have found the real issue. You're probably using the coordinates with a wrong SRS!

Calculate distance between 2 points (approximately)

Using PostGis 3.0, Postgres 12.4, I'm trying to find the distance bewteen two points in the Netherlands. According to Google Maps, this should be around 5 Km (straight line, see image below).
The functions below all give about 8 Km, which is pretty far off.
The calcDistance stored procedure I found somewhere does a pretty decent job (5 Km), but that's a pretty slow way when querying a table with a lot of records, sorted on the least distance from a random point somewhere.
I've tried to find the right spheroid, but I can't seem to find the right solution. Should I use some other spheroid ('SPHEROID["WGS 84", 6378137, 298.257223563]' was the only one I could find) for distance between longitude lines in the Netherlands (latitude > 52.0)?
SELECT
"calcDistance"(52.2209471, 6.8945723, 52.221687, 6.969117) as calcDistance,
st_distancespheroid('POINT(52.2209471 6.8945723)'::geometry,
'POINT(52.221687 6.969117)'::geometry,
'SPHEROID["WGS 84", 6378137, 298.257223563]')/1000 as distancespheroid,
st_distancesphere('POINT(52.2209471 6.8945723)'::geometry,
'POINT(52.221687 6.969117)'::geometry) as distancesphere,
st_distance(
ST_GeographyFromText('POINT(52.2209471 6.8945723)'),
ST_GeographyFromText('POINT(52.2216870 6.9691170)')
);
This is what Google Maps tells me:
The coordinates you show are not in the Netherlands, but in the sea somewhere off the Horn of Africa. Don't forget that in PostGIS, longitude is the first coordinate.
If you swap the coordinates, things will start to look better:
SELECT st_distance(
ST_GeographyFromText('POINT(6.8945723 52.2209471)'),
ST_GeographyFromText('POINT(6.9691170 52.2216870)')
);
st_distance
═══════════════
5094.96164985
(1 row)
You switched x and y. It is longitude, latitude, not the other way around.:
SELECT ST_Distance('POINT(6.8945723 52.2209471)'::geography,
'POINT(6.969117 52.221687)'::geography);
st_distance
---------------
5094.96164985
(1 Zeile)
Demo: db<>fiddle

Geopoints: From Single Coordinate to Bounds on map

I try to figure out how to come from a single given coordinate (lat/lon) to the nearest bounds which enclose this coordinate on a map e.g. streets or sea.
Here two examples to give you a better understanding of what I mean:
What i tried already or thought about:
Setting up a Nominatim server and search for the given coordinate via the reverse-function to get the bbox and/or the geojson polygon of this coordinate. -> this only works when the given coordinate is within a POI or for example directly on a street.
Writing an algorithm to walk in all 4 or 8 directions (n/e/s/w) and 'stop' when the map layer/surface changes (change = stop for this direction and mark a bounding-point)
Building up an image-recognition system using TensorFlow to detect the different colors and 'draw' the polygon. Worked with TensorFlow a couple of times but this seems to be the most tricky solution to implement (but at my current understanding the most precise one)
Does someone of you have any other ideas to get a solution for this problem? Would appreciate any kind of approaches
Cheers!
If I got your question right, you might wanna first select all polygons in which the given point is inside of using ST_Contains, and then compute the distance to this point using ST_Distance. If you ORDER BY distance and LIMIT to 1 result you'll get the nearest polygon, e.g.
Data Sample
CREATE TABLE t (gid int, geom geometry);
INSERT INTO t VALUES
(1,'POLYGON((-4.47 54.26,-4.44 54.28,-4.41 54.24,-4.46 54.23,-4.47 54.26))'),
(2,'POLYGON((-4.48 54.25,-4.40 54.25,-4.41 54.23,-4.48 54.23,-4.48 54.25))'),
(3,'POLYGON((-4.53 54.23,-4.44 54.29,-4.38 54.22,-4.53 54.23))');
Query
SELECT gid,ST_AsText(geom) FROM t
WHERE ST_Contains(geom,ST_MakePoint(-4.45, 54.25))
ORDER BY ST_Distance(geom,ST_MakePoint(-4.45, 54.25))
LIMIT 1;
gid | st_astext
-----+------------------------------------------------------------------------
1 | POLYGON((-4.47 54.26,-4.44 54.28,-4.41 54.24,-4.46 54.23,-4.47 54.26))
(1 Zeile)

Postgresql postgis ST_DWithin always return true

I need to calculate if one point is not more distant than a given radius from another point. I used the function ST_DWithin, in google maps I get lanLot using "what is here" section of two points. First: (43.2137617, 76.8598076) and second (43.220109 76.865100). The distance between them is 1.25km. My query
SELECT ST_DWithin (
ST_GeomFromText('POINT(76.8598076 43.2137617)',3857),
ST_GeomFromText('POINT(76.865100 43.220109)',3857),
100
);
And it always returns true. I think that I put radius 100 meters and used SRID 3875 to use meters. What is wrong?
The coordinates you are using are not in CRS 3857 but are rather unprojected lat-long, i.e. CRS 4326, so you are looking for points within 100 degrees of each others.
You would need to create the point in 4326, project it in 3857 using ST_Transform and then make the distance computation in meters.
SELECT ST_DWithin (
ST_Transform(ST_GeomFromText('POINT(76.8598076 43.2137617)',4326),3857),
ST_Transform(ST_GeomFromText('POINT(76.865100 43.220109)',4326),3857),
100
);
CRS 3857 is a projection that does not preserve distances that well as you move away from the equator. You may want to use ST_Distance_Sphere instead. Comparing the two methods, the first one gives 1134m between the points and the second one 825m... quite a difference!
SELECT ST_Distance_Sphere (
ST_GeomFromText('POINT(76.8598076 43.2137617)',4326),
ST_GeomFromText('POINT(76.865100 43.220109)',4326))
<= 100;

Get metric distance between two points via a PostgreSQL/PostGIS request

I have a question about the use of postgreSQL/postGIS.
I would like to display markers on a map (stored in a database) which are some distance away from the user (coordinates given to the request).
The type of the field of the markers is POINT (I store lat/long).
The user position is detetermined by the Google Map API.
Here is the actual request :
SELECT * FROM geo_points WHERE ST_distance(ST_SetSRID(geo_points.coords::geometry,4326),ST_GeomFromEWKT('SRID=4326;POINT(45.0653944 4.859764599999996)')) > 65
I know (after some research on internet) that the function ST_distance gives me the distance in degree between markers and the user position and that I test the distance in km.
I think I have to use the function ST_tranform to transform the points in metric coordinates.
So my questions are :
- what is the SRID for France
- how can I make this dynamically for the entire world according to the user position ?
I also kow that the function ST_within exists and that could do this. But I anticipate the fact that later, I could need the distance.
Any help would be greatly appreciated
ps: there are maybe solutions in other post, but all the answers I have found during my researches were not really meeting my needs.
Firstly, pay attention to the axis order of coordinates used by PostGIS, it should be long/lat. Currently you are searching in Somalia. Swapping to the coordinates, you would be searching in France.
You can use a geodesic calculation with the geography type, or use geodesic functions like ST_Distance_Spheroid. With the geography type, you may want to use ST_DWithin for higher performance.
Here are geo_points 65 m away or less from the point of interest in France (not Somalia):
SELECT * FROM geo_points
WHERE ST_Distance_Spheroid(
ST_Transform(geo_points.coords::geometry, 4326),
ST_SetSRID(ST_MakePoint(4.859764599999996, 45.0653944), 4326),
'SPHEROID["WGS 84",6378137,298.257223563]') < 65.0;
However, it will be very slow, since it needs to find the distance to every geo_points, so only do this if you don't care about performance and have less than a few thousand points.
If you change and transform geo_points.coords to store lon/lat (WGS84) as a geography type:
SELECT * FROM geo_points
WHERE ST_DWithin(
geo_points::geography,
ST_SetSRID(ST_MakePoint(4.859764599999996, 45.0653944), 4326)::geography,
65.0);