Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEw ST_Angle function, with test and doc #97

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
66 changes: 66 additions & 0 deletions doc/reference_measure.xml
Expand Up @@ -819,6 +819,72 @@ SELECT degrees(ST_Azimuth(ST_Point(25, 45), ST_Point(75, 100))) AS degA_B,
<para><xref linkend="ST_Point" />, <xref linkend="ST_Translate" />, <xref linkend="ST_Project" />, <ulink url="http://www.postgresql.org/docs/current/interactive/functions-math.html">PostgreSQL Math Functions</ulink></para>
</refsection>

</refentry>

<refentry id="ST_Angle">
<refnamediv>
<refname>ST_Angle</refname>

<refpurpose>Returns the angle between 3 points, or between 2 vectors (4 points or 2 lines).</refpurpose>
</refnamediv>
<refsynopsisdiv>
<funcsynopsis>
<funcprototype>
<funcdef>float <function>ST_Angle</function></funcdef>
<paramdef><type>geometry </type><parameter>point1</parameter></paramdef>
<paramdef><type>geometry </type><parameter>point2</parameter></paramdef>
<paramdef><type>geometry </type><parameter>point3</parameter></paramdef>
<paramdef choice="opt"><type>geometry </type><parameter>point4</parameter></paramdef>
</funcprototype>
<funcprototype>
<funcdef>float <function>ST_Angle</function></funcdef>
<paramdef><type>geometry </type><parameter>line1</parameter></paramdef>
<paramdef><type>geometry </type><parameter>line2</parameter></paramdef>
</funcprototype>
</funcsynopsis>
</refsynopsisdiv>
<refsection>
<title>Description</title>

<para> For 3 points, computes the angle measured clockwise of P1P2P3.
If input are 2 lines, get first and last point of the lines as 4 points.
For 4 points,compute the angle measured clockwise of P1P2,P3P4.
Results are always positive, between 0 and 2*Pi radians.

Uses azimuth of pairs or points.
</para>
<para>ST_Angle(P1,P2,P3) = ST_Angle(P2,P1,P2,P3)</para>
<para>Result is in radian and can be converted to degrees using a built-in PostgreSQL function degrees(), as shown in the example.</para>
<para>Availability: 2.5.0</para>
</refsection>

<refsection>
<title>Examples</title>
<para>Geometry Azimuth in degrees </para>
<programlisting>
WITH rand AS (
SELECT s, random() * 2 * PI() AS rad1
, random() * 2 * PI() AS rad2
FROM generate_series(1,2,2) AS s
)
, points AS (
SELECT s, rad1,rad2, ST_MakePoint(cos1+s,sin1+s) as p1, ST_MakePoint(s,s) AS p2, ST_MakePoint(cos2+s,sin2+s) as p3
FROM rand
,cos(rad1) cos1, sin(rad1) sin1
,cos(rad2) cos2, sin(rad2) sin2
)
SELECT s, ST_AsText(ST_SnapToGrid(ST_MakeLine(ARRAY[p1,p2,p3]),0.001)) AS line
, degrees(ST_Angle(p1,p2,p3)) as computed_angle
, round(degrees(2*PI()-rad2 -2*PI()+rad1+2*PI()))::int%360 AS reference
, round(degrees(2*PI()-rad2 -2*PI()+rad1+2*PI()))::int%360 AS reference
FROM points ;

1 | line | computed_angle | reference
------------------+------------------
1 | LINESTRING(1.511 1.86,1 1,0.896 0.005) | 155.27033848688 | 155

</programlisting>
</refsection>
</refentry>

<refentry id="ST_Centroid">
Expand Down
126 changes: 126 additions & 0 deletions postgis/lwgeom_functions_basic.c
Expand Up @@ -102,6 +102,7 @@ Datum LWGEOM_setpoint_linestring(PG_FUNCTION_ARGS);
Datum LWGEOM_asEWKT(PG_FUNCTION_ARGS);
Datum LWGEOM_hasBBOX(PG_FUNCTION_ARGS);
Datum LWGEOM_azimuth(PG_FUNCTION_ARGS);
Datum LWGEOM_angle(PG_FUNCTION_ARGS);
Datum LWGEOM_affine(PG_FUNCTION_ARGS);
Datum LWGEOM_longitude_shift(PG_FUNCTION_ARGS);
Datum optimistic_overlap(PG_FUNCTION_ARGS);
Expand Down Expand Up @@ -2420,6 +2421,131 @@ Datum LWGEOM_azimuth(PG_FUNCTION_ARGS)
PG_RETURN_FLOAT8(result);
}

/**
* Compute the angle defined by 3 points or the angle between 2 vectors
* defined by 4 points
* given Point geometries.
* @return NULL on exception (same point).
* Return radians otherwise (always positive).
*/
PG_FUNCTION_INFO_V1(LWGEOM_angle);
Datum LWGEOM_angle(PG_FUNCTION_ARGS)
{
GSERIALIZED * seri_geoms[4];
LWGEOM *geom_unser;
LWPOINT *lwpoint;
POINT2D points[4];
double az1,az2 ;
double result;
int srids[4];
int i = 0 ;
int j = 0;
int err_code = 0;
int n_args = PG_NARGS();

/* no deserialize, checking for common error first*/
for(i=0; i<n_args; i++)
{
seri_geoms[i] = PG_GETARG_GSERIALIZED_P(i);
if (gserialized_is_empty(seri_geoms[i]) )
{/* empty geom */
if (i==3)
{
n_args = 3 ;
}
else
{
err_code = 1 ;
break ;
}
} else
{
if(gserialized_get_type(seri_geoms[i]) != POINTTYPE)
{/* geom type */
err_code = 2 ;
break;
}
else
{
srids[i] = gserialized_get_srid(seri_geoms[i]) ;
if(srids[0] != srids[i])
{/* error on srid*/
err_code = 3 ;
break;
}
}
}
}
if (err_code >0)
switch (err_code){
default: /*always executed*/
for (j=0;j<=i;j++)
PG_FREE_IF_COPY(seri_geoms[j], j);

case 1:
lwpgerror("Empty geometry");
PG_RETURN_NULL() ;
break;

case 2:
lwpgerror("Argument must be POINT geometries");
PG_RETURN_NULL();
break;

case 3:
lwpgerror("Operation on mixed SRID geometries");
PG_RETURN_NULL();
break;
}
/* extract points */
for(i=0; i<n_args; i++)
{
geom_unser = lwgeom_from_gserialized(seri_geoms[i]) ;
lwpoint = lwgeom_as_lwpoint(geom_unser);
if (!lwpoint)
{
for (j=0;j<n_args;j++)
PG_FREE_IF_COPY(seri_geoms[j], j);
lwpgerror("Error unserializing geometry");
PG_RETURN_NULL() ;
}

if ( ! getPoint2d_p(lwpoint->point, 0, &points[i]) )
{
/* // can't free serialized geom, it might be needed by lw
for (j=0;j<n_args;j++)
PG_FREE_IF_COPY(seri_geoms[j], j); */
lwpgerror("Error extracting point");
PG_RETURN_NULL();
}
/* lwfree(geom_unser);don't do, lw may rely on this memory
lwpoint_free(lwpoint); dont do , this memory is needed ! */
}
/* // can't free serialized geom, it might be needed by lw
for (j=0;j<n_args;j++)
PG_FREE_IF_COPY(seri_geoms[j], j); */

/* compute azimuth for the 2 pairs of points
* note that angle is not defined identically for 3 points or 4 points*/
if (n_args == 3)
{/* we rely on azimuth to complain if points are identical */
if ( ! azimuth_pt_pt(&points[0], &points[1], &az1) )
PG_RETURN_NULL();
if ( ! azimuth_pt_pt(&points[2], &points[1], &az2) )
PG_RETURN_NULL();
} else
{
if ( ! azimuth_pt_pt(&points[0], &points[1], &az1) )
PG_RETURN_NULL();
if ( ! azimuth_pt_pt(&points[2], &points[3], &az2) )
PG_RETURN_NULL();
}
result = az2-az1 ;
result += (result<0) * 2 * M_PI ; /* we dont want negative angle*/
PG_RETURN_FLOAT8(result);
}


/*
* optimistic_overlap(Polygon P1, Multipolygon MP2, double dist)
* returns true if P1 overlaps MP2
Expand Down
19 changes: 19 additions & 0 deletions postgis/postgis.sql.in
Expand Up @@ -1290,7 +1290,15 @@ CREATE OR REPLACE FUNCTION ST_azimuth(geom1 geometry, geom2 geometry)
RETURNS float8
AS 'MODULE_PATHNAME', 'LWGEOM_azimuth'
LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL;

-- Availability: 2.5.0
CREATE OR REPLACE FUNCTION ST_Angle(pt1 geometry, pt2 geometry, pt3 geometry, pt4 geometry default 'POINT EMPTY'::geometry)
RETURNS float8
AS 'MODULE_PATHNAME', 'LWGEOM_angle'
LANGUAGE 'c' IMMUTABLE STRICT;



------------------------------------------------------------------------
-- MISC
------------------------------------------------------------------------
Expand Down Expand Up @@ -5903,6 +5911,17 @@ CREATE OR REPLACE FUNCTION ST_AsX3D(geom geometry, maxdecimaldigits integer DEFA
AS $$SELECT @extschema@._ST_AsX3D(3,$1,$2,$3,'');$$
LANGUAGE 'sql' IMMUTABLE _PARALLEL;


-----------------------------------------------------------------------
-- ST_Angle
-----------------------------------------------------------------------
-- Availability: 2.3.0
-- has to be here because need ST_StartPoint
CREATE OR REPLACE FUNCTION ST_Angle(line1 geometry, line2 geometry)
RETURNS float8 AS 'SELECT ST_Angle(St_StartPoint($1), ST_EndPoint($1), St_StartPoint($2), ST_EndPoint($2))'
LANGUAGE 'sql' IMMUTABLE STRICT;


-- make views and spatial_ref_sys public viewable --
GRANT SELECT ON TABLE geography_columns TO public;
GRANT SELECT ON TABLE geometry_columns TO public;
Expand Down
34 changes: 34 additions & 0 deletions regress/lwgeom_regress.sql
Expand Up @@ -135,3 +135,37 @@ SELECT 'BoundingDiagonal5', ST_AsEwkt(ST_BoundingDiagonal(
SELECT 'BoundingDiagonal6', ST_AsEwkt(ST_BoundingDiagonal(
'SRID=3857;POLYGON M EMPTY'::geometry
));


--- ST_Azimuth
SELECT 'ST_Azimuth_regular' , round(ST_Azimuth(geom1,geom2)::numeric,4)
FROM CAST('POINT(0 1)' AS geometry) AS geom1, CAST('POINT(1 0)' AS geometry) AS geom2 ;
SELECT 'ST_Azimuth_same_point' , ST_Azimuth(geom1,geom1)
FROM CAST('POINT(0 1)' AS geometry) AS geom1 ;
SELECT 'ST_Azimuth_mixed_srid' , ST_Azimuth(geom1,geom2)
FROM CAST('POINT(0 1)' AS geometry) AS geom1, ST_GeomFromText('POINT(1 0)',4326) AS geom2;
SELECT 'ST_Azimuth_not_point' , ST_Azimuth(geom1,geom2)
FROM CAST('POINT(0 1)' AS geometry) AS geom1, ST_GeomFromText('LINESTRING(1 0 ,2 0)',4326) AS geom2;
SELECT 'ST_Azimuth_null_geom' , ST_Azimuth(geom1,geom2)
FROM CAST('POINT(0 1)' AS geometry) AS geom1, ST_GeomFromText('EMPTY') AS geom2;

--- ST_Angle( points)
SELECT 'ST_Angle_4_pts', St_Angle(p1,p2,p3,p4)
FROM ST_GeomFromtext('POINT(0 1)') AS p1, ST_GeomFromtext('POINT(0 0)') AS p2
, ST_GeomFromtext('POINT(1 0)') AS p3, ST_GeomFromtext('POINT(2 0)') AS p4 ;
SELECT 'ST_Angle_4_pts', St_Angle(p1,p2,p3,p4)
FROM ST_GeomFromtext('POINT(2 0)') AS p1, ST_GeomFromtext('POINT(1 0)') AS p2
, ST_GeomFromtext('POINT(1 -1)') AS p3, ST_GeomFromtext('POINT(0 0)') AS p4 ;
SELECT 'ST_Angle_3_pts', St_Angle(p1,p2,p3)
FROM ST_GeomFromtext('POINT(0 1)') AS p1, ST_GeomFromtext('POINT(0 0)') AS p2
, ST_GeomFromtext('POINT(1 0)') AS p3, ST_GeomFromtext('POINT(2 0)') AS p4 ;
SELECT 'ST_Angle_mixed_srid', St_Angle(p1,p2,p3,p4)
FROM ST_GeomFromtext('POINT(0 1)') AS p1, ST_GeomFromtext('POINT(0 0)') AS p2
, ST_GeomFromtext('POINT(1 0)',4326) AS p3, ST_GeomFromtext('POINT(2 0)') AS p4 ;
SELECT 'ST_Angle_empty' , St_Angle(p1,p2,p3,p4)
FROM ST_GeomFromtext('POINT EMPTY') AS p1, ST_GeomFromtext('POINT(0 0)') AS p2
, ST_GeomFromtext('POINT(1 0)',4326) AS p3, ST_GeomFromtext('POINT(2 0)') AS p4 ;
--- ST_Angle( lines)
SELECT 'ST_Angle_2_lines', St_Angle(l1,l2)
FROM ST_GeomFromtext('LINESTRING(0 1,0 0)') AS l1
, ST_GeomFromtext('LINESTRING(1 0, 2 0)') AS l2 ;
11 changes: 11 additions & 0 deletions regress/lwgeom_regress_expected
Expand Up @@ -21,3 +21,14 @@ BoundingDiagonal3|SRID=4326;LINESTRING(999999986991104 999999986991104,999999986
BoundingDiagonal4|SRID=3857;LINESTRING(-1 -2 -8 2,1 2 3 9)
BoundingDiagonal5|SRID=3857;LINESTRINGM(4 4 0,5 4 1)
BoundingDiagonal6|SRID=3857;LINESTRINGM EMPTY
ST_Azimuth_regular|2.3562
ST_Azimuth_same_point|
ERROR: Operation on mixed SRID geometries
ERROR: Argument must be POINT geometries
ERROR: parse error - invalid geometry
ST_Angle_4_pts|4.71238898038469
ST_Angle_4_pts|0.785398163397448
ST_Angle_3_pts|1.5707963267949
ERROR: Operation on mixed SRID geometries
ERROR: Empty geometry
ST_Angle_2_lines|4.71238898038469