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.
Related
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.
I have performed ST_Area on a shapefile but the resulting numbers are VERY long. Need to reduce them to two decimals. This is the code so far:
SELECT mtn_name, ST_Area(geom) / 1000000 AS km2 FROM mountain ORDER BY 2 DESC;
This is what I get:
mtn_name KM2
character varying double precision
1 Monte del Pueblo de Jerez del Marquesado 6.9435657067528e-9
2 Monte de La Peza 6.113288075418532e-9
I tried ROUND() but it brings KM to 0.00
Since it is not simply possible to round a decimal value (Decimal Precision problem) you will not get a double value which is exactly 6.94e-9. It would be something like 6.9400000001e-9 after rounding.
You can do:
demos:db<>fiddle
If the exponent is always the same (in your example it is always e-9) you can round with a fixed value. With double values, this results in the problem described above.
SELECT
round(area * 10e8 * 100) / 100 / 10e8
FROM area_result
To avoid these precision problems, you can use numeric type
SELECT
round(area * 10e8 * 100)::numeric / 100 / 10e8
FROM area_result
If you have different exponents, you have to calculate the multiplicator first. According to this solution you can do:
For double output
SELECT
round(area / mul * 100) * mul / 100
FROM (
SELECT
area,
pow(10, floor(log10(area))) as mul
FROM area_result
) s
For numeric output
SELECT
round((area / mul) * 100)::numeric * mul / 100
FROM (
SELECT
area,
pow(10, floor(log10(area)))::numeric as mul
FROM area_result
) s
However, your exponential result is just a view of the values. This can vary from database tool to database tool. Internally they are not stored as the view. So, if you fetch these values, you will, in fact, get a value like 0.00000000694 and not 6.94e-9, which is just a textual representation.
If you want to ensure to get exactly this textual representation, you can use number formatting to_char() for this, which, of course, returns a type text, not a number anymore:
SELECT
to_char(area, '9.99EEEE')
FROM area_result
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!
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))
I found this tutorial how to find something in specified the radius. My question is what algorithm was used to implement it?
If you mean the earth_box, the idea is to come up with a data type that can be useful with a GIST index (inverted search tree):
http://www.postgresql.org/docs/current/static/gist-intro.html
See in particular the links at the bottom of the maintainers' page:
http://www.sai.msu.su/~megera/postgres/gist/
One leads to:
The GiST is a balanced tree structure like a B-tree, containing pairs. But keys in the GiST are not integers like the keys in a B-tree. Instead, a GiST key is a member of a user-defined class, and represents some property that is true of all data items reachable from the pointer associated with the key. For example, keys in a B+-tree-like GiST are ranges of numbers ("all data items below this pointer are between 4 and 6"); keys in an R-tree-like GiST are bounding boxes, ("all data items below this pointer are in Calfornia"); keys in an RD-tree-like GiST are sets ("all data items below this pointer are subsets of {1,6,7,9,11,12,13,72}"); etc. To make a GiST work, you just have to figure out what to represent in the keys, and then write 4 methods for the key class that help the tree do insertion, deletion, and search.
http://gist.cs.berkeley.edu/gist1.html
If you mean the earth distance itself, the meaty part of source is:
/* compute difference in longitudes - want < 180 degrees */
longdiff = fabs(long1 - long2);
if (longdiff > M_PI)
longdiff = TWO_PI - longdiff;
sino = sqrt(sin(fabs(lat1 - lat2) / 2.) * sin(fabs(lat1 - lat2) / 2.) +
cos(lat1) * cos(lat2) * sin(longdiff / 2.) * sin(longdiff / 2.));
if (sino > 1.)
sino = 1.;
return 2. * EARTH_RADIUS * asin(sino);
https://github.com/postgres/postgres/blob/master/contrib/earthdistance/earthdistance.c#L50
My math is too rusty to be affirmative on what the above does exactly, but my guess would be that it's computing the distance between two points on the surface of a sphere (without considering the height of the two points). In other words, nautical miles.