Postgis - ST_ShortestLine not correct with lat lon - postgresql

I'm calculating the shortest line between a Line and a Point for very short distances (some meters), using Postgis ST_ShortestLine:
SELECT ST_AsText(
ST_ShortestLine(ST_GeomFromText('POINT(2.33123610021 48.87902639841)', 4326),
ST_GeomFromText('LINESTRING ( 2.33122725689 48.87902421718, 2.33123229444 48.87901190847)', 4326))
) As sline;
I get a result which does not seem coherent, the given line not being the shortest one:
LINESTRING(2.33123610021 48.87902639841,2.331227760998549 48.87902298544515)
Here is a drawing of the result, using the Mercator projection (JOSM).
What could explain this?

If you're relying on your eyes to determine if the drawn line is the shortest one you might have been mislead to this conclusion. ST_ShortestLine will return a line with exact same length of ST_Distance, which is the minimum 2D cartesian distance of two geometries. And it is exactly what is happening:
WITH j (line,point) AS (
VALUES ('SRID=4326;POINT(2.33123610021 48.87902639841)',
'SRID=4326;LINESTRING(2.33122725689 48.87902421718, 2.33123229444 48.87901190847)')
)
SELECT
ST_Length(ST_ShortestLine(point,line)), -- length of the shortest line
ST_Distance(line,point), -- distance between 'point' and 'line'
ST_AsEWKT(ST_ShortestLine(point,line)) -- the shortest line as EWKT
FROM j;
st_length | st_distance | st_asewkt
-----------------------+-----------------------+----------------------------------------------------------------------------------------
9.010592472335791e-06 | 9.010592472335791e-06 | SRID=4326;LINESTRING(2.331227760998549 48.87902298544515,2.33123610021 48.87902639841)
(1 row)
Perhaps you share the result you're expecting and we can go from there.

Related

Different ST_Distance calculation with degrees and metres in PostGIS

I have noticed that sometimes the results from ST_Distance for geometry types do not correspond correctly to those for geography types.
For example:
SELECT ST_Distance('SRID=4326;MULTIPOLYGON(((13.1654379639367 48.0163296656575,
13.1654405823308 48.0163326202901,13.1654809135407 48.0163781648167,
13.1655095556032 48.0164104945946,13.1656825124596 48.0166031792699,
13.1658285825017 48.0167559797112,13.1658385904811 48.0167667179682,
13.1660097634653 48.0169315381006,13.1661737540995 48.0170911295992,
13.166336100685 48.0172329598378,13.1677079127931 48.0150783894135,
13.1677278111466 48.0150450062427,13.1670716137939 48.0148839705059,
13.1667911667995 48.0148062288149,13.1665512255895 48.0147411405409,
13.1665145733757 48.0147311909654,13.1654379639367 48.0163296656575)))'::geometry,
'SRID=4326;POINT(16.096346 47.2786129)'::geometry);
returns 3.0197908442784636 as a result in degrees.
But, when computing the distance of the same shapes in metres:
SELECT ST_Distance(gg1,gg2) from (select 'SRID=4326;MULTIPOLYGON(((13.1654379639367 48.0163296656575,
13.1654405823308 48.0163326202901,13.1654809135407 48.0163781648167,13.1655095556032 48.0164104945946,
13.1656825124596 48.0166031792699,13.1658285825017 48.0167559797112,13.1658385904811 48.0167667179682,
13.1660097634653 48.0169315381006,13.1661737540995 48.0170911295992,13.166336100685 48.0172329598378,
13.1677079127931 48.0150783894135,13.1677278111466 48.0150450062427,13.1670716137939 48.0148839705059,
13.1667911667995 48.0148062288149,13.1665512255895 48.0147411405409,13.1665145733757 48.0147311909654,
13.1654379639367 48.0163296656575)))'::geography as gg1,
'SRID=4326;POINT(16.096346 47.2786129)'::geography as gg2) as foo;
it returns 0 metres. This can't be right. By looking the shapes in wkt playground the distance is indeed much more than 0 metres.
Any idea of what I could be doing wrong?
Thank you!

Create area that cell tower covers

I have a dataset with cell tower information, as you can see below. The lat and lon fields are the location of the tower.
The objective is to get the area (geometry) of the sector the cell tower covers, from start_angle to end_angle. As you can see in the next image, using the first row of the dataset as example, I can obtain the lines from start_angle 275 and end_angle 35, but I want the rest of the buffer to disappear.
Query used:
This first query is used to create and rotate the lines from start_angle and end_angle and also a line with 0 degrees.
WITH vertices AS
(SELECT
id,
start_angle,
end_angle,
(ST_DumpPoints(geom)).path[1] AS v_id,
(ST_DumpPoints(geom)).geom AS vertex
FROM celulas
), teste as
(SELECT
id,
v_id,
ST_SetSRID(ST_Translate(ST_Rotate(ST_MakeLine(ST_MakePoint( 1.0,0.0),
ST_MakePoint(-1.0,0.0)),
radians(start_angle * -1)), ST_X(vertex), ST_Y(Vertex)),
ST_SRID(vertex)) AS startline,
ST_SetSRID(ST_Translate(ST_Rotate(ST_MakeLine(ST_MakePoint( 1.0,0.0),
ST_MakePoint(-1.0,0.0)),
radians(end_angle * -1)), ST_X(vertex), ST_Y(Vertex)),
ST_SRID(vertex)) AS endline,
ST_SetSRID(ST_Translate(ST_Rotate(ST_MakeLine(ST_MakePoint( 1.0,0.0),
ST_MakePoint(-1.0,0.0)),
radians(0)), ST_X(vertex), ST_Y(Vertex)),
ST_SRID(vertex)) AS midline
FROM vertices
)
I also used the next query to union all geometries: the radius buffer and the lines
select St_intersection(st_split(buffer, midline), st_split(buffer, angulo))
from angulo
Let's create us a handy inline function on the composite type of your relation specifically.
Also, and first off, let's create two equally handy support functions for all sorts of use cases in this context; getting the clockwise (CW) (and, since we're at it, also the counterclockwise (CW)) angular difference in degree between two azimuths:
CREATE OR REPLACE FUNCTION CWAngle(
IN sdeg FLOAT,
IN edeg FLOAT,
OUT ddeg FLOAT
) LANGUAGE SQL AS
$$
SELECT CASE (sdeg <= edeg)
WHEN TRUE THEN
edeg - sdeg
ELSE
360.0 - sdeg + edeg
END;
$$
;
CREATE OR REPLACE FUNCTION CCWAngle(
IN sdeg FLOAT,
IN edeg FLOAT,
OUT ddeg FLOAT
) LANGUAGE SQL AS
$$
SELECT 360.0 - CWAngle(sdeg, edeg);
$$
;
There may or may not be more efficient ways, or more idiomatic code; this works well enough.
Now, the concept of the following function is to create a 'wedge', your sector, of a circle by projecting the center point along the circle segment between start_angle and end_angle at the given "radius"; we will project <degrees_between_angles>/FLOOR(<degrees_between_angles>) points, so every 1 <= step < 2, with step being degrees (which is a lot; ST_Buffer defaults to a circle having 8 vertices per quarter circle):
CREATE OR REPLACE FUNCTION sector(
rec spatial.celulas
) RETURNS GEOMETRY(POLYGON, 4236) AS
$$
DECLARE
delta FLOAT := CWAngle(rec.start_angle, rec.end_angle);
step FLOAT := delta / FLOOR(delta);
wedge GEOMETRY(POINT)[];
BEGIN
wedge := wedge || ST_SetSRID(ST_MakePoint(rec.lon, rec.lat), 4326);
FOR n IN 0..FLOOR(delta)
LOOP
wedge := wedge || ST_Project(wedge[1]::GEOGRAPHY, rec."radius", RADIANS(MOD(rec.start_angle+(n*step)::NUMERIC, 360.0::NUMERIC)))::GEOMETRY;
END LOOP
;
wedge := wedge || wedge[1];
RETURN ST_MakePolygon(ST_SetSRID(ST_MakeLine(wedge), 4326));
END;
$$
LANGUAGE 'plpgsql';
This assumes
that the CWAngle function is present; change to CCWAngle in this function if needed
that start_angle -> end_angle is in clockwise direction
that "radius" is in meter
the exact relation and column names of your example, i.e celulas.lat, celulas.lon, celulas."radius", celulas.start_angle, celulas.end_angle; you need to alter all occurences of these identifiers in the function accordingly if they are actually different
This function is thus specific to the relation in your example, and you can call it like a relation qualified column:
SELECT [*,] celulas.sector AS geom
FROM celulas
;
Alternatively, here is a more general function based on the same concept, which also allows to set the max vertices per quarter circle, and an optional inner radius.

Get point along a linestring that is two feet away from the first point

I have the following linestring:
SELECT ST_GeomFromText('LINESTRING(-97.83396022 29.98609860,-97.83391790 29.98613790)',4326);
I need to add a point between the linestring, which is 2 feet from the first point
You can use ST_LineInterpolatePoint.
Since it takes a proportion of the line, the first step is to compute it: convert 2ft to meters, divide by the length of the line in meters, that you get by casting it to geography.
WITH src(geom) AS (values (ST_GeomFromText('LINESTRING(-97.83396022 29.98609860,-97.83391790 29.98613790)',4326)))
SELECT ST_AsText(ST_LineInterpolatePoint(geom, 2 * 0.3048/ st_length(geom::geography)))
FROM src;
st_astext
-------------------------------------------
POINT(-97.8339558996568 29.9861026120389)
To create a line from the start that is 2ft long, you can use st_lineSubstring instead
SELECT st_lineSubstring(geom, 0, 2 * 0.3048/ st_length(geom::geography))

Find points inside the intersection of polygons in PostgreSQL/PostGIS

I want to find the points inside the intersection (Figure 1) of polygons in PostgreSQL.
Figure 1 example
I use psycopg2 and the code that I used is:
intersects = """select ST_Intersects( ST_GeographyFromText('SRID=4326; POLYGON(( 32.0361328 33.6877818, 31.9042969 33.5780147,33.5742188 11.3507967,66.2695313 20.4270128, 51.9433594 34.270836, 32.0361328 33.6877818))'),
ST_GeographyFromText('SRID=4326; POLYGON((33.7060547 37.1953306,36.6943359 16.0880422,64.9072266 12.4258478,64.8632813 37.0551771,33.5742188 37.1953306,33.7060547 37.1953306))')), col.vessel_hash,ST_X(col.the_geom) AS long, ST_Y(col.the_geom) AS lat
from samplecol as col"""
cursor.execute(intersects)
pointsINtw = cursor.fetchall()
count = 0;
shipsrecords = open("/home/antonis/Desktop/testme1.txt", "w")
for ex in pointsINtw:
if str(ex[0])=='True':
count = count + 1
shipsrecords.write(str(ex) + "\n")
print (CBLUE + "Number of returned results: " + CBLUEEND), count
Example record:
vessel_hash | speed | latitude | longitude | course | heading | timestamp | the_geom
--------------+--------+---------+-------+-------------+-------------+--------+---------+--------------------------+----------------------------------------------------
103079215239 | 5 | -5.41844510 | 36.12160900 | 314 | 511 | 2016-06-12T06:31:04.000Z | 0101000020E61000001BF33AE2900F424090AF4EDF7CAC15C0
The problem is that above code does not work properly. I create two polygons like Figure 1 and I know that inside the intersection exist some points. But the code always returns all points from db.
If I create two polygons that do not intersect then the algorithm seems to work properly as it does not return any point.
Does anyone know what am I doing wrong?
demo:db<>fiddle (of your query, with your polygons, own points),
visualisation of the situation (maybe Chrome necessary)
ST_Intersects() only checks if the two given polygons share some space. It is true, they share. But there is no part within your query that includes the check with the points. You only call the intersection check but without any usage of your point column.
I believe you need to calculate the intersection polygon (ST_Intersection()) instead of only check for its existance. After that you can take this result to check whether your points are in it or not (ST_Contains()):
Pseudocode:
SELECT
ST_Contains(
ST_Intersection(my_geometry1, my_geometry2),
my_pointgeometry
)
...
demo:db<>fiddle
(Demo uses geometry instead of geography and the polygon needs to get valid for some reasons; so you need to adapt this to your use case)

postgis convert Points to polygon

what is the easy way to convert points to polygon?
i've tried this query
SELECT ST_GeomFromText('POLYGON((157 -536.0,157 -537.0,157 -538.0,157 -539.0,157 -540.0,157 -541.0,157 -542.0,157 -543.0,157 -544.0,157 -545.0,158 -545.0,159 -545.0,160 -545.0,161 -545.0,162 -545.0,163 -545.0,164 -545.0,165 -545.0,165 -544.0,165 -543.0,165 -542.0,165 -541.0,165 -540.0,165 -539.0,165 -538.0,165 -537.0,165 -536.0,164 -536.0,163 -536.0,162 -536.0,161 -536.0,160 -536.0,159 -536.0,158 -536.0,157.0 -536.0))');
but its results are not as expected as shown below
which is supposed to be like this
Obviously your points are not in the correct order to define a polygon., and as the commenter pointed out, you have more than one polygons.
You could divide them into sets that make each polygon (manually?), and construct a multipolygon as follows:
SELECT ST_AsText(ST_Collect(ARRAY[ST_GeomFromText('POLYGON(..first polygon...)'),ST_GeomFromText('POLYGON(..2nd polygon...)',...,ST_GeomFromText('POLYGON(..last polygon...)')]));