How to create a circle in meters in postgis? - postgis

I would like to ask how to create a circle with radius=4km. I have tried the ST_Buffer function but it creates a larger circle. (I see the created circle by inserting its polygon into an new kml file.)
This is what i am trying.
INSERT INTO camera(geom_circle) VALUES(geometry(ST_Buffer(georgaphy(ST_GeomFromText('POINT(21.304116745663165 38.68607570952619)')), 4000)))
The center of the circle is a lon lat point but I don't know its SRID because I have imported it from a kml file.
Do I need the SRID in order to transform the geometries etc?

KML files are always lat/long and use SRID=4326. This SRID is implied if you use geography. Geography is a good way to mix-in the 4 km metric measure on lat/long data ... excellent you tried this!
Try this statement to fix up the casts, and use a parameterized point constructor:
SELECT ST_Buffer(ST_MakePoint(21.304116745663165, 38.68607570952619)::geography, 4000);
And if you need to cast this back to geometry, add a ::geometry cast to the end.
Update on accuracy
The previous answer internally re-projects the geometry (usually) to a UTM zone that the point fits within (see ST_Buffer). This may cause minor distortions if the point is on the edge of two UTM boundaries. Most folks won't care about the size of these errors, but it will often be several meters. However, if you require sub millimeter precision, consider building a dynamic azimuthal equidistant projection. This requires PostGIS 2.3's ST_Transform, and is adapted from another answer:
CREATE OR REPLACE FUNCTION geodesic_buffer(geom geometry, dist double precision,
num_seg_quarter_circle integer)
RETURNS geometry AS $$
SELECT ST_Transform(
ST_Buffer(ST_Point(0, 0), $2, $3),
('+proj=aeqd +x_0=0 +y_0=0 +lat_0='
|| ST_Y(ST_Centroid($1))::text || ' +lon_0=' || ST_X(ST_Centroid($1))::text),
ST_SRID($1))
$$ LANGUAGE sql IMMUTABLE STRICT COST 100;
CREATE OR REPLACE FUNCTION geodesic_buffer(geom geometry, dist double precision)
RETURNS geometry AS 'SELECT geodesic_buffer($1, $2, 8)'
LANGUAGE sql IMMUTABLE STRICT COST 100;
-- Optional warppers for geography type
CREATE OR REPLACE FUNCTION geodesic_buffer(geog geography, dist double precision)
RETURNS geography AS 'SELECT geodesic_buffer($1::geometry, $2)::geography'
LANGUAGE sql IMMUTABLE STRICT COST 100;
CREATE OR REPLACE FUNCTION geodesic_buffer(geog geography, dist double precision,
num_seg_quarter_circle integer)
RETURNS geography AS 'SELECT geodesic_buffer($1::geometry, $2, $3)::geography'
LANGUAGE sql IMMUTABLE STRICT COST 100;
A simple example to run one of the functions is:
SELECT geodesic_buffer(ST_MakePoint(21.304116745663165, 38.68607570952619)::geography, 4000);
And to compare the distances to each of the buffered points, here are the lengths of each geodesic (shortest path on an ellipsoid of revolution, i.e. WGS84). First this function:
SELECT count(*), min(buff_dist), avg(buff_dist), max(buff_dist)
FROM (
SELECT ST_Distance((ST_DumpPoints(geodesic_buffer(poi, dist)::geometry)).geom, poi) AS buff_dist
FROM (SELECT ST_MakePoint(21.304116745663165, 38.68607570952619)::geography AS poi, 4000 AS dist) AS f
) AS f;
count | min | avg | max
-------+----------------+-----------------+----------------
33 | 3999.999999953 | 3999.9999999743 | 4000.000000001
Compare this to ST_Buffer (first part of answer), that shows it's off by about 1.56 m:
SELECT count(*), min(buff_dist), avg(buff_dist), max(buff_dist)
FROM (
SELECT ST_Distance((ST_DumpPoints(ST_Buffer(poi, dist)::geometry)).geom, poi) AS buff_dist
FROM (SELECT ST_MakePoint(21.304116745663165, 38.68607570952619)::geography AS poi, 4000 AS dist) AS f
) AS f;
count | min | avg | max
-------+----------------+------------------+----------------
33 | 4001.560675049 | 4001.56585986067 | 4001.571105793

Related

SQL Server Polygon format

SELECT TOP 1 KMLLocation FROM Polygon
Result: 0xE61000000104210000004ACE893DB4BF19403EB2B96A9EFF534082919735B1C0194039807EDFBFFF534052F17F4754C81940976F7D586F0054405AD76839D0D31940AED3484BE50054409E961FB8CAD3194087BF266BD40054403F90BC7328D3194071C806D2C5005440844A5CC7B8D21940E4D70FB1C100544014CE6E2D93D1194062BF27D6A90054404FB2D5E594D01940E27668588C005440E910381268D01940E7E26F7B820054409AEAC9FCA3CF1940A54E4013610054409432A9A10DD01940CD902A8A5700544015E126A3CAD01940FE0E45813E0054407FBDC282FBD11940C30DF8FC30005440EB1B98DC28D2194038312427130054403065E08096CE19404A7CEE04FBFF534082035ABA82CD1940C2F7FE06EDFF5340462234828DCB19409373620FEDFF534031B2648EE5CD1940E338F06AB9FF5340CFDC43C2F7CE1940321EA5129EFF5340B98C9B1A68CE194055A3570394FF5340A8E15B5837CE1940639CBF0985FF53403868AF3E1ECA1940001FBC7669FF534029CB10C7BAC81940ED9C668176FF5340C6DB4AAFCDC61940AD18AE0E80FF534047CCECF318C51940F390291F82FF5340EDB776A224C41940FD851E317AFF5340E4F90CA837C31940C440D7BE80FF5340DF1AD82AC1C21940462234828DFF53409C69C2F693C1194007D2C5A695FF5340C5387F130AC119406AFB57569AFF5340D1E7A38CB8C01940574277499CFF53404ACE893DB4BF19403EB2B96A9EFF534001000000020000000001000000FFFFFFFF0000000003
SELECT TOP 1 KMLLocation .ToString() FROM Polygon
Result:
POLYGON ((79.994044 6.437211, 79.996086 6.438176, 80.006796 6.445634, 80.013995 6.456849...
Expected Result:
POLYGON ((6.437211 79.994044, 6.438176 79.996086, 6.445634 80.006796, 6.456849, 80.013995...
How do I swap Lat and Long from geography polygon?
Here's what I came up with:
DECLARE #g GEOGRAPHY = /* your hex representation from above */
#new VARCHAR(MAX);
WITH points AS (
SELECT #g.STPointN(n.n) AS p, n
FROM tempdb.dbo.Numbers AS n
WHERE n <= #g.STNumPoints()
)
SELECT #new = CONCAT('POLYGON((', (
SELECT STRING_AGG(CONCAT(p.Lat, ' ', p.Long), ',') WITHIN GROUP (ORDER BY p.n)
FROM points AS p
), '))')
SELECT geography::STGeomFromText(#new, #g.STSrid).ReorientObject();
It's pretty straightforward. I'm creating a common table expression (CTE) to decompose the existing polygon into its constituent corners using a numbers (or tally) table. All that is is a table with one column with integers from 1 to some large number. It's helpful for cases like this. From there, I'm swapping the latitude and longitude of the points and then re-assembling them into the WKT for the polygon. Lastly, I'm creating an actual geography instance from the WKT. Note - I'm calling ReorientObject() as the new polygon appears to suffer from a ring orientation problem insofar as it defined the whole globe with a small hole in it (the shape of your polygon).

Fastest way to calculate distances between two coordinates?

We currently use the Geography type to calculate distance between a current location and the coordinates in our tsql table. Our code is based on this sqlauthority.com example.
Is there a faster way to retrieve the distance between two points? These calls will be done by a mobile phone app, so they should ideally be very fast.
After testing it with a distance I know, looping 100 times per batch and running the batch 15 times to make sure the 10 runs the client statistics stores in SSMS are cycled past initial query plan generation so it doesn't skew the results. Here are the averages of the remaining. The calculation method seems to be twice as fast as the geography option.
With a difference in distance returned of 0.0000000020044.
Calculation script used (returned miles: 41.9013152732833)
set nocount on;
declare
#lat1 float = 45.489614
,#lon1 float = -122.650021
,#lat2 float = 44.94404
,#lon2 float = -123.025739
select 3959.1825574 * acos(sin(#lat1/57.295779513082323) * sin(#lat2/57.295779513082323) + cos(#lat1/57.295779513082323) * cos(#lat2/57.295779513082323) * cos((#lon2-#lon1)/57.295779513082323)) distance_in_miles
GO 100
Geography script used (returned miles: 41.9013152752877)
set nocount on;
declare
#g geography = geography::Point(45.489614, -122.650021, 4326)
,#h geography = geography::Point(44.94404, -123.025739, 4326)
select #h.STDistance(#g) / 1609.344 distance_in_miles -- 1609.344 is meters in mile. STDistance = meters.
GO 100
Fair warning, doing it in a non-system function will still have unpredictable performance. I would recommend doing it inline for calculation.
Here's a raw calculation example.
Working example of inline syntax for miles. It is the easiest, most accurate and shortest syntax I could find.
adjusted for accuracy
if object_id('tempdb..#LatLongInfo','U') is not null
begin
drop table #LatLongInfo;
end;
create table #LatLongInfo (
lat1 float,
lon1 float,
lat2 float,
lon2 float
);
insert into #LatLongInfo
values (21, -76, 23, -72);
select
3959.1825574 * acos(sin(lat1/57.295779513082323) * sin(lat2/57.295779513082323) + cos(lat1/57.295779513082323) * cos(lat2/57.295779513082323) * cos((lon2-lon1)/57.295779513082323)) distance_in_miles
from #LatLongInfo;
Hope this helps. I used something like this to find the doctors within a given range for patients back when sql2000 was released, it's been a while. Google was a newborn, no maps, nothing but a search box and one button. You have me all nostalgic now...I remember reading this when I coded that the first time.

linestring created from coordinates POSTGIS

Can someone help ?
Here is the part of my code (sql) which doesn't work :
SELECT ST_LENGTH(geom) into distance FROM
SELECT ST_GeographyFromText('srid=4326;linestring(lon_bus lat_bus, lon_stop lat_stop)') AS geom)
AS dis;
lon_bus, lat_bus, lon_stop and lat_stop are coordinates I get from my database. When I try this, I have an error of parsing. But when I replace these variable by numeric, it works. Can someone help me on it? I would like to keep these variables in my code.
It doesn't work because the WKT with variables is invalid. Remember, WKT is just regular text, so don't confuse WKT with SQL.
You can make a LineString from two point geometries, then cast it to ::geography.
SELECT ST_MakeLine(ST_MakePoint(lon_bus, lat_bus),
ST_MakePoint(lon_stop, lat_stop))::geography AS geog
FROM (
SELECT 1 AS lon_bus, 2 AS lat_bus, 3 AS lon_stop, 4 AS lat_stop
) AS data;
To get the geodesic length, use ST_Length on the geography.
Based on the usage, the question isn't about how to make a linestring, but how to calculated the distance between two geographic positions. There are several ways to do this:
SELECT
ST_Distance(bus, stop) AS cartesian_distance,
ST_Distance_Sphere(bus, stop) AS sphere_distance,
ST_Distance(bus::geography, stop::geography) AS geography_distance,
ST_Length(ST_MakeLine(bus, stop)::geography) AS geography_length
FROM (
SELECT ST_MakePoint(lon_bus, lat_bus) AS bus, ST_MakePoint(lon_stop, lat_stop) AS stop
FROM (SELECT 1 AS lon_bus, 2 AS lat_bus, 3 AS lon_stop, 4 AS lat_stop) AS data
) AS data;
-[ RECORD 1 ]------+-----------------
cartesian_distance | 2.82842712474619
sphere_distance | 314283.687770102
geography_distance | 313588.397192902
geography_length | 313588.397192902
The last two get the same result. If you don't need the linestring (e.g. to draw on a map), then the simplest method is used for geography_distance.

Can't get a simple Entity Framework spatial query to work

This query on my sql server returns lots of rows:
declare #referencepoint Geography = Geography::Point(48.208173, 16.373813, 4326);
SELECT *
FROM myTable
WHERE Location.STDistance(#referencepoint) < 20000
but the equivalent in EF returns none:
DbGeography referencepoint = DbGeography.PointFromText("POINT(48.208173 16.373813)", 4326);
var records = (from r in db.myTable
where r.Location.Distance(referencepoint ) <= 20000
select r).ToList();
Looking at the query generated via profiler I see this:
declare #p3 sys.geography
set #p3=convert(sys.geography,0xE6100000010CD4D17135B25F30408274B169A51A4840)
SELECT *
FROM [myTable]
WHERE ([Location].STDistance(#p3)) <= 20000
Does EF have an issue here, or do I?
OP has the issue here :) Both SQL and EF are working as expected. OP's statement was incorrect.
SQL Point Syntax:
declare #referencepoint Geography = Geography::Point(48.208173, 16.373813, 4326);
is actually equivalent to .Net:
DbGeography referencepoint = DbGeography.PointFromText("POINT(16.373813 48.208173)", 4326);
// Note the parameters are reversed from OP's statement
In SQL and EF (.Net) the Geography data type uses a standard WellKnownText notation to define points and polygons and other structures internally.
In WellKnownText format a Point is specified as POINT(X Y) on a Cartesian plane.
- Note the lack of a comma, the values are only delimited by a space
When we want to express the location on the earth as a point on a Cartesian plane, the X axis is the equator, the Y axis is then a Meridian line running between the North and South Poles.
Longitute, by definition is the east-west position on the surface of the Earth (so parallel with the equator, the X ordinate)
Latitude, by definition is the north-south position on the surface of the Earth (perpendicular to the equator, the Y ordinate)
Therefore to express a Point on the earth in WellKnownText format as if it were a point on a Cartesian plane we must use this syntax:
POINT(Longitude Latitude)
What confuses the issue is that in most verbal and written forms we refer to Latitude and Longitude in that order, so in SQL we have a helper function that takes the parameters in that order, because this was supposed to make it less confusing. And in a way it is, because the parameters are named appropriately. To further explain the point I have expanded out OP's statements with the correction
SQL
DECLARE #latitude float = 48.208173
DECLARE #longitude float = 16.373813
DECLARE #srid int = 4326
DECLARE #referencepoint Geography = Geography::Point(#latitude, #longitude, #srid);
SELECT *
FROM myTable
WHERE Location.STDistance(#referencepoint) < 20000
.Net
double latitude = 48.208173;
double longitude = 16.373813;
int srid = 4326;
DbGeography referencepoint = DbGeography.PointFromText($"POINT({longitude} {latitude})", srid);
var records = (from r in db.myTable
where r.Location.Distance(referencepoint) <= 20000
select r).ToList();
I can't even find a good reference explaining why we generally refer to Latitude and Longitude (in that order) I suspect it's based on the fact that LatLon rolls off the tongue better or because latitude was discovered/measured first?

PostGIS ST_DWithin distance_of_srid Using Degrees, When Should Be Meters

I have run into a problem using PostGIS ST_DWithin using geometry points that has me completely stumped. Even though I'm using geometry points with SRID 3857 (see below), ST_DWithin interprets seems to interpret the third argument ( double precision distance_of_srid ) as DEGREES. Here's an example.
Using this table test_person_avg_lngs_lats:
Column | Type | Modifiers
---------------------+----------------------+-----------
avg_lng | double precision |
avg_lat | double precision |
person_avg_location | geometry(Point,3857) |
store_lng | double precision |
store_lat | double precision |
store_location | geometry(Point,3857) |
and the following query:
SELECT avg_lat, avg_lng, store_lng, store_lat,
ST_Distance_Spheroid(person_avg_location, store_location, CAST('SPHEROID[\"WGS 84\",6378137,298.257223563,AUTHORITY[\"EPSG","7030\"]]' AS spheroid))/1000 AS distance,
ST_DWithin(person_avg_location, store_location, 1) AS dwithin
FROM test_person_avg_lngs_lats
WHERE ST_DWithin(person_avg_location, store_location, 1)
The query returns results that interpret the third argument to ST_DWithin as 1 DEGREE versus 1 METER, even though I'm using geometry points with SRID 3857 which I have confirmed, uses meters units. Whatever N I pass as the third argument to ST_DWithin, the results consistently return distances around N * 100 km ( ~ 66 miles ). That's why I'm assuming ST_DWithin is interpreting it as 1 DEGREE.
Here is a sample result that should be interpreted as one meter (distance is in miles):
avg_lat avg_lng store_lng store_lat distance dwithin
43.3275959623, -71.1169553872, -71.0626, 42.3291, 68.9794023576, true
This is the closest I've come to something on the subject: ST_DWithin takes parameter as degree , not meters , why?
Any ideas as to what might be causing this, or what I might look for to move forward in analyzing the problem?
I'm using:
postgis_full_version
------------------------------------------------------------------
POSTGIS="2.0.0 r9605" GEOS="3.3.3-CAPI-1.7.4" PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.9.2, released 2012/10/08" LIBXML="2.7.8" LIBJSON="UNKNOWN" RASTER
(1 row)
version
-------------------------------------------------------------
PostgreSQL 9.1.9 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit
(1 row)
Obviously, distance_of_srid uses the same distance units of the SRID, which is typically either degrees or meters. With the geometry type, distances are calculated on a flat Cartesian plane using maths familiar to most high school students. The units are not interpreted for the geometry type.
However, this assumes that the data are actually projected with the correct SRID. If you mix a projected SRID like 3857 with lat/long coordinates expressed as degrees than you will get unexplainable garbage. Review how you populated the person_avg_location and store_location columns, because I'm 99.9% certain that there was an error there.

Resources