postgis/postgis/lwgeom_functions_analytic.c
Raúl Marín Rodríguez 86057e2e46 Speed up ST_Simplify
Closes #4510
Closes https://github.com/postgis/postgis/pull/480



git-svn-id: http://svn.osgeo.org/postgis/trunk@17821 b70326c6-7e19-0410-871a-916f4a2858ee
2019-09-24 10:58:30 +00:00

1362 lines
35 KiB
C

/**********************************************************************
*
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
*
* PostGIS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* PostGIS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
*
**********************************************************************
*
* Copyright (C) 2001-2005 Refractions Research Inc.
*
**********************************************************************/
#include "postgres.h"
#include "funcapi.h"
#include "fmgr.h"
#include "liblwgeom.h"
#include "liblwgeom_internal.h" /* For FP comparators. */
#include "lwgeom_pg.h"
#include "math.h"
#include "lwgeom_rtree.h"
#include "lwgeom_functions_analytic.h"
#include "access/htup_details.h"
/* Prototypes */
Datum LWGEOM_simplify2d(PG_FUNCTION_ARGS);
Datum LWGEOM_SetEffectiveArea(PG_FUNCTION_ARGS);
Datum LWGEOM_line_interpolate_point(PG_FUNCTION_ARGS);
Datum ST_LineCrossingDirection(PG_FUNCTION_ARGS);
Datum ST_MinimumBoundingRadius(PG_FUNCTION_ARGS);
Datum ST_MinimumBoundingCircle(PG_FUNCTION_ARGS);
Datum ST_GeometricMedian(PG_FUNCTION_ARGS);
Datum ST_IsPolygonCCW(PG_FUNCTION_ARGS);
Datum ST_IsPolygonCW(PG_FUNCTION_ARGS);
static double determineSide(const POINT2D *seg1, const POINT2D *seg2, const POINT2D *point);
static int isOnSegment(const POINT2D *seg1, const POINT2D *seg2, const POINT2D *point);
static int point_in_ring(POINTARRAY *pts, const POINT2D *point);
static int point_in_ring_rtree(RTREE_NODE *root, const POINT2D *point);
/***********************************************************************
* Simple Douglas-Peucker line simplification.
* No checks are done to avoid introduction of self-intersections.
* No topology relations are considered.
*
* --strk@kbt.io;
***********************************************************************/
PG_FUNCTION_INFO_V1(LWGEOM_simplify2d);
Datum LWGEOM_simplify2d(PG_FUNCTION_ARGS)
{
GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P_COPY(0);
double dist = PG_GETARG_FLOAT8(1);
GSERIALIZED *result;
int type = gserialized_get_type(geom);
LWGEOM *in;
bool preserve_collapsed = false;
int modified = LW_FALSE;
/* Can't simplify points! */
if ( type == POINTTYPE || type == MULTIPOINTTYPE )
PG_RETURN_POINTER(geom);
/* Handle optional argument to preserve collapsed features */
if ((PG_NARGS() > 2) && (!PG_ARGISNULL(2)))
preserve_collapsed = PG_GETARG_BOOL(2);
in = lwgeom_from_gserialized(geom);
modified = lwgeom_simplify_in_place(in, dist, preserve_collapsed);
if (!modified)
PG_RETURN_POINTER(geom);
if (!in || lwgeom_is_empty(in))
PG_RETURN_NULL();
result = geometry_serialize(in);
PG_RETURN_POINTER(result);
}
PG_FUNCTION_INFO_V1(LWGEOM_SetEffectiveArea);
Datum LWGEOM_SetEffectiveArea(PG_FUNCTION_ARGS)
{
GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
GSERIALIZED *result;
int type = gserialized_get_type(geom);
LWGEOM *in;
LWGEOM *out;
double area=0;
int set_area=0;
if ( type == POINTTYPE || type == MULTIPOINTTYPE )
PG_RETURN_POINTER(geom);
if ( (PG_NARGS()>1) && (!PG_ARGISNULL(1)) )
area = PG_GETARG_FLOAT8(1);
if ( (PG_NARGS()>2) && (!PG_ARGISNULL(2)) )
set_area = PG_GETARG_INT32(2);
in = lwgeom_from_gserialized(geom);
out = lwgeom_set_effective_area(in,set_area, area);
if ( ! out ) PG_RETURN_NULL();
/* COMPUTE_BBOX TAINTING */
if ( in->bbox ) lwgeom_add_bbox(out);
result = geometry_serialize(out);
lwgeom_free(out);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_POINTER(result);
}
PG_FUNCTION_INFO_V1(LWGEOM_ChaikinSmoothing);
Datum LWGEOM_ChaikinSmoothing(PG_FUNCTION_ARGS)
{
GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
GSERIALIZED *result;
int type = gserialized_get_type(geom);
LWGEOM *in;
LWGEOM *out;
int preserve_endpoints=1;
int n_iterations=1;
if ( type == POINTTYPE || type == MULTIPOINTTYPE )
PG_RETURN_POINTER(geom);
if ( (PG_NARGS()>1) && (!PG_ARGISNULL(1)) )
n_iterations = PG_GETARG_INT32(1);
if (n_iterations< 1 || n_iterations>5)
elog(ERROR,"Number of iterations must be between 1 and 5 : %s", __func__);
if ( (PG_NARGS()>2) && (!PG_ARGISNULL(2)) )
{
if(PG_GETARG_BOOL(2))
preserve_endpoints = 1;
else
preserve_endpoints = 0;
}
in = lwgeom_from_gserialized(geom);
out = lwgeom_chaikin(in, n_iterations, preserve_endpoints);
if ( ! out ) PG_RETURN_NULL();
/* COMPUTE_BBOX TAINTING */
if ( in->bbox ) lwgeom_add_bbox(out);
result = geometry_serialize(out);
lwgeom_free(out);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_POINTER(result);
}
/***********************************************************************
* --strk@kbt.io;
***********************************************************************/
/***********************************************************************
* Interpolate a point along a line, useful for Geocoding applications
* SELECT line_interpolate_point( 'LINESTRING( 0 0, 2 2'::geometry, .5 )
* returns POINT( 1 1 ).
***********************************************************************/
PG_FUNCTION_INFO_V1(LWGEOM_line_interpolate_point);
Datum LWGEOM_line_interpolate_point(PG_FUNCTION_ARGS)
{
GSERIALIZED *gser = PG_GETARG_GSERIALIZED_P(0);
GSERIALIZED *result;
double distance_fraction = PG_GETARG_FLOAT8(1);
int repeat = PG_NARGS() > 2 && PG_GETARG_BOOL(2);
int32_t srid = gserialized_get_srid(gser);
LWLINE* lwline;
LWGEOM* lwresult;
POINTARRAY* opa;
if ( distance_fraction < 0 || distance_fraction > 1 )
{
elog(ERROR,"line_interpolate_point: 2nd arg isn't within [0,1]");
PG_FREE_IF_COPY(gser, 0);
PG_RETURN_NULL();
}
if ( gserialized_get_type(gser) != LINETYPE )
{
elog(ERROR,"line_interpolate_point: 1st arg isn't a line");
PG_FREE_IF_COPY(gser, 0);
PG_RETURN_NULL();
}
lwline = lwgeom_as_lwline(lwgeom_from_gserialized(gser));
opa = lwline_interpolate_points(lwline, distance_fraction, repeat);
lwgeom_free(lwline_as_lwgeom(lwline));
PG_FREE_IF_COPY(gser, 0);
if (opa->npoints <= 1)
{
lwresult = lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, opa));
} else {
lwresult = lwmpoint_as_lwgeom(lwmpoint_construct(srid, opa));
}
result = geometry_serialize(lwresult);
lwgeom_free(lwresult);
PG_RETURN_POINTER(result);
}
/***********************************************************************
* Interpolate a point along a line 3D version
* --vincent.mora@oslandia.com;
***********************************************************************/
Datum ST_3DLineInterpolatePoint(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(ST_3DLineInterpolatePoint);
Datum ST_3DLineInterpolatePoint(PG_FUNCTION_ARGS)
{
GSERIALIZED *gser = PG_GETARG_GSERIALIZED_P(0);
GSERIALIZED *result;
double distance = PG_GETARG_FLOAT8(1);
LWLINE *line;
LWGEOM *geom;
LWPOINT *point;
if (distance < 0 || distance > 1)
{
elog(ERROR, "line_interpolate_point: 2nd arg isn't within [0,1]");
PG_RETURN_NULL();
}
if (gserialized_get_type(gser) != LINETYPE)
{
elog(ERROR, "line_interpolate_point: 1st arg isn't a line");
PG_RETURN_NULL();
}
geom = lwgeom_from_gserialized(gser);
line = lwgeom_as_lwline(geom);
point = lwline_interpolate_point_3d(line, distance);
lwgeom_free(geom);
PG_FREE_IF_COPY(gser, 0);
result = geometry_serialize(lwpoint_as_lwgeom(point));
lwpoint_free(point);
PG_RETURN_POINTER(result);
}
/***********************************************************************
* --jsunday@rochgrp.com;
***********************************************************************/
/***********************************************************************
*
* Grid application function for postgis.
*
* WHAT IS
* -------
*
* This is a grid application function for postgis.
* You use it to stick all of a geometry points to
* a custom grid defined by its origin and cell size
* in geometry units.
*
* Points reduction is obtained by collapsing all
* consecutive points falling on the same grid node and
* removing all consecutive segments S1,S2 having
* S2.startpoint = S1.endpoint and S2.endpoint = S1.startpoint.
*
* ISSUES
* ------
*
* Only 2D is contemplated in grid application.
*
* Consecutive coincident segments removal does not work
* on first/last segments (They are not considered consecutive).
*
* Grid application occurs on a geometry subobject basis, thus no
* points reduction occurs for multipoint geometries.
*
* USAGE TIPS
* ----------
*
* Run like this:
*
* SELECT SnapToGrid(<geometry>, <originX>, <originY>, <sizeX>, <sizeY>);
*
* Grid cells are not visible on a screen as long as the screen
* point size is equal or bigger then the grid cell size.
* This assumption may be used to reduce the number of points in
* a map for a given display scale.
*
* Keeping multiple resolution versions of the same data may be used
* in conjunction with MINSCALE/MAXSCALE keywords of mapserv to speed
* up rendering.
*
* Check also the use of DP simplification to reduce grid visibility.
* I'm still researching about the relationship between grid size and
* DP epsilon values - please tell me if you know more about this.
*
*
* --strk@kbt.io;
*
***********************************************************************/
#define CHECK_RING_IS_CLOSE
#define SAMEPOINT(a,b) ((a)->x==(b)->x&&(a)->y==(b)->y)
/* Forward declarations */
Datum LWGEOM_snaptogrid(PG_FUNCTION_ARGS);
Datum LWGEOM_snaptogrid_pointoff(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(LWGEOM_snaptogrid);
Datum LWGEOM_snaptogrid(PG_FUNCTION_ARGS)
{
LWGEOM *in_lwgeom;
GSERIALIZED *out_geom = NULL;
LWGEOM *out_lwgeom;
gridspec grid;
GSERIALIZED *in_geom = PG_GETARG_GSERIALIZED_P(0);
/* Set grid values to zero to start */
memset(&grid, 0, sizeof(gridspec));
grid.ipx = PG_GETARG_FLOAT8(1);
grid.ipy = PG_GETARG_FLOAT8(2);
grid.xsize = PG_GETARG_FLOAT8(3);
grid.ysize = PG_GETARG_FLOAT8(4);
/* Return input geometry if input geometry is empty */
if ( gserialized_is_empty(in_geom) )
{
PG_RETURN_POINTER(in_geom);
}
/* Return input geometry if input grid is meaningless */
if ( grid.xsize==0 && grid.ysize==0 && grid.zsize==0 && grid.msize==0 )
{
PG_RETURN_POINTER(in_geom);
}
in_lwgeom = lwgeom_from_gserialized(in_geom);
POSTGIS_DEBUGF(3, "SnapToGrid got a %s", lwtype_name(in_lwgeom->type));
out_lwgeom = lwgeom_grid(in_lwgeom, &grid);
if ( out_lwgeom == NULL ) PG_RETURN_NULL();
/* COMPUTE_BBOX TAINTING */
if ( in_lwgeom->bbox )
lwgeom_refresh_bbox(out_lwgeom);
POSTGIS_DEBUGF(3, "SnapToGrid made a %s", lwtype_name(out_lwgeom->type));
out_geom = geometry_serialize(out_lwgeom);
PG_RETURN_POINTER(out_geom);
}
#if POSTGIS_DEBUG_LEVEL >= 4
/* Print grid using given reporter */
static void
grid_print(const gridspec *grid)
{
lwpgnotice("GRID(%g %g %g %g, %g %g %g %g)",
grid->ipx, grid->ipy, grid->ipz, grid->ipm,
grid->xsize, grid->ysize, grid->zsize, grid->msize);
}
#endif
PG_FUNCTION_INFO_V1(LWGEOM_snaptogrid_pointoff);
Datum LWGEOM_snaptogrid_pointoff(PG_FUNCTION_ARGS)
{
GSERIALIZED *in_geom, *in_point;
LWGEOM *in_lwgeom;
LWPOINT *in_lwpoint;
GSERIALIZED *out_geom = NULL;
LWGEOM *out_lwgeom;
gridspec grid;
/* BOX3D box3d; */
POINT4D offsetpoint;
in_geom = PG_GETARG_GSERIALIZED_P(0);
/* Return input geometry if input geometry is empty */
if ( gserialized_is_empty(in_geom) )
{
PG_RETURN_POINTER(in_geom);
}
in_point = PG_GETARG_GSERIALIZED_P(1);
in_lwpoint = lwgeom_as_lwpoint(lwgeom_from_gserialized(in_point));
if ( in_lwpoint == NULL )
{
lwpgerror("Offset geometry must be a point");
}
grid.xsize = PG_GETARG_FLOAT8(2);
grid.ysize = PG_GETARG_FLOAT8(3);
grid.zsize = PG_GETARG_FLOAT8(4);
grid.msize = PG_GETARG_FLOAT8(5);
/* Take offsets from point geometry */
getPoint4d_p(in_lwpoint->point, 0, &offsetpoint);
grid.ipx = offsetpoint.x;
grid.ipy = offsetpoint.y;
grid.ipz = lwgeom_has_z((LWGEOM*)in_lwpoint) ? offsetpoint.z : 0;
grid.ipm = lwgeom_has_m((LWGEOM*)in_lwpoint) ? offsetpoint.m : 0;
#if POSTGIS_DEBUG_LEVEL >= 4
grid_print(&grid);
#endif
/* Return input geometry if input grid is meaningless */
if ( grid.xsize==0 && grid.ysize==0 && grid.zsize==0 && grid.msize==0 )
{
PG_RETURN_POINTER(in_geom);
}
in_lwgeom = lwgeom_from_gserialized(in_geom);
POSTGIS_DEBUGF(3, "SnapToGrid got a %s", lwtype_name(in_lwgeom->type));
out_lwgeom = lwgeom_grid(in_lwgeom, &grid);
if ( out_lwgeom == NULL ) PG_RETURN_NULL();
/* COMPUTE_BBOX TAINTING */
if (in_lwgeom->bbox)
{
lwgeom_refresh_bbox(out_lwgeom);
}
POSTGIS_DEBUGF(3, "SnapToGrid made a %s", lwtype_name(out_lwgeom->type));
out_geom = geometry_serialize(out_lwgeom);
PG_RETURN_POINTER(out_geom);
}
/*
** crossingDirection(line1, line2)
**
** Determines crossing direction of line2 relative to line1.
** Only accepts LINESTRING as parameters!
*/
PG_FUNCTION_INFO_V1(ST_LineCrossingDirection);
Datum ST_LineCrossingDirection(PG_FUNCTION_ARGS)
{
int type1, type2, rv;
LWLINE *l1 = NULL;
LWLINE *l2 = NULL;
GSERIALIZED *geom1 = PG_GETARG_GSERIALIZED_P(0);
GSERIALIZED *geom2 = PG_GETARG_GSERIALIZED_P(1);
gserialized_error_if_srid_mismatch(geom1, geom2, __func__);
type1 = gserialized_get_type(geom1);
type2 = gserialized_get_type(geom2);
if ( type1 != LINETYPE || type2 != LINETYPE )
{
elog(ERROR,"This function only accepts LINESTRING as arguments.");
PG_RETURN_NULL();
}
l1 = lwgeom_as_lwline(lwgeom_from_gserialized(geom1));
l2 = lwgeom_as_lwline(lwgeom_from_gserialized(geom2));
rv = lwline_crossing_direction(l1, l2);
PG_FREE_IF_COPY(geom1, 0);
PG_FREE_IF_COPY(geom2, 1);
PG_RETURN_INT32(rv);
}
/***********************************************************************
* --strk@kbt.io
***********************************************************************/
Datum LWGEOM_line_substring(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(LWGEOM_line_substring);
Datum LWGEOM_line_substring(PG_FUNCTION_ARGS)
{
GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
double from = PG_GETARG_FLOAT8(1);
double to = PG_GETARG_FLOAT8(2);
LWGEOM *olwgeom;
POINTARRAY *ipa, *opa;
GSERIALIZED *ret;
int type = gserialized_get_type(geom);
if ( from < 0 || from > 1 )
{
elog(ERROR,"line_interpolate_point: 2nd arg isn't within [0,1]");
PG_RETURN_NULL();
}
if ( to < 0 || to > 1 )
{
elog(ERROR,"line_interpolate_point: 3rd arg isn't within [0,1]");
PG_RETURN_NULL();
}
if ( from > to )
{
elog(ERROR, "2nd arg must be smaller then 3rd arg");
PG_RETURN_NULL();
}
if ( type == LINETYPE )
{
LWLINE *iline = lwgeom_as_lwline(lwgeom_from_gserialized(geom));
if ( lwgeom_is_empty((LWGEOM*)iline) )
{
/* TODO return empty line */
lwline_release(iline);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_NULL();
}
ipa = iline->points;
opa = ptarray_substring(ipa, from, to, 0);
if ( opa->npoints == 1 ) /* Point returned */
olwgeom = (LWGEOM *)lwpoint_construct(iline->srid, NULL, opa);
else
olwgeom = (LWGEOM *)lwline_construct(iline->srid, NULL, opa);
}
else if ( type == MULTILINETYPE )
{
LWMLINE *iline;
uint32_t i = 0, g = 0;
int homogeneous = LW_TRUE;
LWGEOM **geoms = NULL;
double length = 0.0, sublength = 0.0, minprop = 0.0, maxprop = 0.0;
iline = lwgeom_as_lwmline(lwgeom_from_gserialized(geom));
if ( lwgeom_is_empty((LWGEOM*)iline) )
{
/* TODO return empty collection */
lwmline_release(iline);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_NULL();
}
/* Calculate the total length of the mline */
for ( i = 0; i < iline->ngeoms; i++ )
{
LWLINE *subline = (LWLINE*)iline->geoms[i];
if ( subline->points && subline->points->npoints > 1 )
length += ptarray_length_2d(subline->points);
}
geoms = lwalloc(sizeof(LWGEOM*) * iline->ngeoms);
/* Slice each sub-geometry of the multiline */
for ( i = 0; i < iline->ngeoms; i++ )
{
LWLINE *subline = (LWLINE*)iline->geoms[i];
double subfrom = 0.0, subto = 0.0;
if ( subline->points && subline->points->npoints > 1 )
sublength += ptarray_length_2d(subline->points);
/* Calculate proportions for this subline */
minprop = maxprop;
maxprop = sublength / length;
/* This subline doesn't reach the lowest proportion requested
or is beyond the highest proporton */
if ( from > maxprop || to < minprop )
continue;
if ( from <= minprop )
subfrom = 0.0;
if ( to >= maxprop )
subto = 1.0;
if ( from > minprop && from <= maxprop )
subfrom = (from - minprop) / (maxprop - minprop);
if ( to < maxprop && to >= minprop )
subto = (to - minprop) / (maxprop - minprop);
opa = ptarray_substring(subline->points, subfrom, subto, 0);
if ( opa && opa->npoints > 0 )
{
if ( opa->npoints == 1 ) /* Point returned */
{
geoms[g] = (LWGEOM *)lwpoint_construct(SRID_UNKNOWN, NULL, opa);
homogeneous = LW_FALSE;
}
else
{
geoms[g] = (LWGEOM *)lwline_construct(SRID_UNKNOWN, NULL, opa);
}
g++;
}
}
/* If we got any points, we need to return a GEOMETRYCOLLECTION */
if ( ! homogeneous )
type = COLLECTIONTYPE;
olwgeom = (LWGEOM*)lwcollection_construct(type, iline->srid, NULL, g, geoms);
}
else
{
elog(ERROR,"line_substring: 1st arg isn't a line");
PG_RETURN_NULL();
}
ret = geometry_serialize(olwgeom);
lwgeom_free(olwgeom);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_POINTER(ret);
}
/*******************************************************************************
* The following is based on the "Fast Winding Number Inclusion of a Point
* in a Polygon" algorithm by Dan Sunday.
* http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm#Winding%20Number
******************************************************************************/
/*
* returns: >0 for a point to the left of the segment,
* <0 for a point to the right of the segment,
* 0 for a point on the segment
*/
static double determineSide(const POINT2D *seg1, const POINT2D *seg2, const POINT2D *point)
{
return ((seg2->x-seg1->x)*(point->y-seg1->y)-(point->x-seg1->x)*(seg2->y-seg1->y));
}
/*
* This function doesn't test that the point falls on the line defined by
* the two points. It assumes that that has already been determined
* by having determineSide return within the tolerance. It simply checks
* that if the point is on the line, it is within the endpoints.
*
* returns: 1 if the point is not outside the bounds of the segment
* 0 if it is
*/
static int isOnSegment(const POINT2D *seg1, const POINT2D *seg2, const POINT2D *point)
{
double maxX;
double maxY;
double minX;
double minY;
if (seg1->x > seg2->x)
{
maxX = seg1->x;
minX = seg2->x;
}
else
{
maxX = seg2->x;
minX = seg1->x;
}
if (seg1->y > seg2->y)
{
maxY = seg1->y;
minY = seg2->y;
}
else
{
maxY = seg2->y;
minY = seg1->y;
}
POSTGIS_DEBUGF(3, "maxX minX/maxY minY: %.8f %.8f/%.8f %.8f", maxX, minX, maxY, minY);
if (maxX < point->x || minX > point->x)
{
POSTGIS_DEBUGF(3, "X value %.8f falls outside the range %.8f-%.8f", point->x, minX, maxX);
return 0;
}
else if (maxY < point->y || minY > point->y)
{
POSTGIS_DEBUGF(3, "Y value %.8f falls outside the range %.8f-%.8f", point->y, minY, maxY);
return 0;
}
return 1;
}
/*
* return -1 iff point is outside ring pts
* return 1 iff point is inside ring pts
* return 0 iff point is on ring pts
*/
static int point_in_ring_rtree(RTREE_NODE *root, const POINT2D *point)
{
int wn = 0;
uint32_t i;
double side;
const POINT2D *seg1;
const POINT2D *seg2;
LWMLINE *lines;
POSTGIS_DEBUG(2, "point_in_ring called.");
lines = RTreeFindLineSegments(root, point->y);
if (!lines)
return -1;
for (i=0; i<lines->ngeoms; i++)
{
seg1 = getPoint2d_cp(lines->geoms[i]->points, 0);
seg2 = getPoint2d_cp(lines->geoms[i]->points, 1);
side = determineSide(seg1, seg2, point);
POSTGIS_DEBUGF(3, "segment: (%.8f, %.8f),(%.8f, %.8f)", seg1->x, seg1->y, seg2->x, seg2->y);
POSTGIS_DEBUGF(3, "side result: %.8f", side);
POSTGIS_DEBUGF(3, "counterclockwise wrap %d, clockwise wrap %d", FP_CONTAINS_BOTTOM(seg1->y, point->y, seg2->y), FP_CONTAINS_BOTTOM(seg2->y, point->y, seg1->y));
/* zero length segments are ignored. */
if (((seg2->x - seg1->x)*(seg2->x - seg1->x) + (seg2->y - seg1->y)*(seg2->y - seg1->y)) < 1e-12*1e-12)
{
POSTGIS_DEBUG(3, "segment is zero length... ignoring.");
continue;
}
/* a point on the boundary of a ring is not contained. */
/* WAS: if (fabs(side) < 1e-12), see #852 */
if (side == 0.0)
{
if (isOnSegment(seg1, seg2, point) == 1)
{
POSTGIS_DEBUGF(3, "point on ring boundary between points %d, %d", i, i+1);
return 0;
}
}
/*
* If the point is to the left of the line, and it's rising,
* then the line is to the right of the point and
* circling counter-clockwise, so increment.
*/
if ((seg1->y <= point->y) && (point->y < seg2->y) && (side > 0))
{
POSTGIS_DEBUG(3, "incrementing winding number.");
++wn;
}
/*
* If the point is to the right of the line, and it's falling,
* then the line is to the right of the point and circling
* clockwise, so decrement.
*/
else if ((seg2->y <= point->y) && (point->y < seg1->y) && (side < 0))
{
POSTGIS_DEBUG(3, "decrementing winding number.");
--wn;
}
}
POSTGIS_DEBUGF(3, "winding number %d", wn);
if (wn == 0)
return -1;
return 1;
}
/*
* return -1 iff point is outside ring pts
* return 1 iff point is inside ring pts
* return 0 iff point is on ring pts
*/
static int point_in_ring(POINTARRAY *pts, const POINT2D *point)
{
int wn = 0;
uint32_t i;
double side;
const POINT2D* seg1;
const POINT2D* seg2;
POSTGIS_DEBUG(2, "point_in_ring called.");
seg2 = getPoint2d_cp(pts, 0);
for (i=0; i<pts->npoints-1; i++)
{
seg1 = seg2;
seg2 = getPoint2d_cp(pts, i+1);
side = determineSide(seg1, seg2, point);
POSTGIS_DEBUGF(3, "segment: (%.8f, %.8f),(%.8f, %.8f)", seg1->x, seg1->y, seg2->x, seg2->y);
POSTGIS_DEBUGF(3, "side result: %.8f", side);
POSTGIS_DEBUGF(3, "counterclockwise wrap %d, clockwise wrap %d", FP_CONTAINS_BOTTOM(seg1->y, point->y, seg2->y), FP_CONTAINS_BOTTOM(seg2->y, point->y, seg1->y));
/* zero length segments are ignored. */
if ((seg2->x == seg1->x) && (seg2->y == seg1->y))
{
POSTGIS_DEBUG(3, "segment is zero length... ignoring.");
continue;
}
/* a point on the boundary of a ring is not contained. */
/* WAS: if (fabs(side) < 1e-12), see #852 */
if (side == 0.0)
{
if (isOnSegment(seg1, seg2, point) == 1)
{
POSTGIS_DEBUGF(3, "point on ring boundary between points %d, %d", i, i+1);
return 0;
}
}
/*
* If the point is to the left of the line, and it's rising,
* then the line is to the right of the point and
* circling counter-clockwise, so increment.
*/
if ((seg1->y <= point->y) && (point->y < seg2->y) && (side > 0))
{
POSTGIS_DEBUG(3, "incrementing winding number.");
++wn;
}
/*
* If the point is to the right of the line, and it's falling,
* then the line is to the right of the point and circling
* clockwise, so decrement.
*/
else if ((seg2->y <= point->y) && (point->y < seg1->y) && (side < 0))
{
POSTGIS_DEBUG(3, "decrementing winding number.");
--wn;
}
}
POSTGIS_DEBUGF(3, "winding number %d", wn);
if (wn == 0)
return -1;
return 1;
}
/*
* return 0 iff point outside polygon or on boundary
* return 1 iff point inside polygon
*/
int point_in_polygon_rtree(RTREE_NODE **root, int ringCount, LWPOINT *point)
{
int i;
POINT2D pt;
POSTGIS_DEBUGF(2, "point_in_polygon called for %p %d %p.", root, ringCount, point);
getPoint2d_p(point->point, 0, &pt);
/* assume bbox short-circuit has already been attempted */
if (point_in_ring_rtree(root[0], &pt) != 1)
{
POSTGIS_DEBUG(3, "point_in_polygon_rtree: outside exterior ring.");
return 0;
}
for (i=1; i<ringCount; i++)
{
if (point_in_ring_rtree(root[i], &pt) != -1)
{
POSTGIS_DEBUGF(3, "point_in_polygon_rtree: within hole %d.", i);
return 0;
}
}
return 1;
}
/*
* return -1 if point outside polygon
* return 0 if point on boundary
* return 1 if point inside polygon
*
* Expected **root order is each exterior ring followed by its holes, eg. EIIEIIEI
*/
int point_in_multipolygon_rtree(RTREE_NODE **root, int polyCount, int *ringCounts, LWPOINT *point)
{
int i, p, r, in_ring;
POINT2D pt;
int result = -1;
POSTGIS_DEBUGF(2, "point_in_multipolygon_rtree called for %p %d %p.", root, polyCount, point);
getPoint2d_p(point->point, 0, &pt);
/* assume bbox short-circuit has already been attempted */
i = 0; /* the current index into the root array */
/* is the point inside any of the sub-polygons? */
for ( p = 0; p < polyCount; p++ )
{
in_ring = point_in_ring_rtree(root[i], &pt);
POSTGIS_DEBUGF(4, "point_in_multipolygon_rtree: exterior ring (%d), point_in_ring returned %d", p, in_ring);
if ( in_ring == -1 ) /* outside the exterior ring */
{
POSTGIS_DEBUG(3, "point_in_multipolygon_rtree: outside exterior ring.");
}
else if ( in_ring == 0 ) /* on the boundary */
{
POSTGIS_DEBUGF(3, "point_in_multipolygon_rtree: on edge of exterior ring %d", p);
return 0;
} else {
result = in_ring;
for(r=1; r<ringCounts[p]; r++)
{
in_ring = point_in_ring_rtree(root[i+r], &pt);
POSTGIS_DEBUGF(4, "point_in_multipolygon_rtree: interior ring (%d), point_in_ring returned %d", r, in_ring);
if (in_ring == 1) /* inside a hole => outside the polygon */
{
POSTGIS_DEBUGF(3, "point_in_multipolygon_rtree: within hole %d of exterior ring %d", r, p);
result = -1;
break;
}
if (in_ring == 0) /* on the edge of a hole */
{
POSTGIS_DEBUGF(3, "point_in_multipolygon_rtree: on edge of hole %d of exterior ring %d", r, p);
return 0;
}
}
/* if we have a positive result, we can short-circuit and return it */
if ( result != -1)
{
return result;
}
}
/* increment the index by the total number of rings in the sub-poly */
/* we do this here in case we short-cutted out of the poly before looking at all the rings */
i += ringCounts[p];
}
return result; /* -1 = outside, 0 = boundary, 1 = inside */
}
/*
* return -1 iff point outside polygon
* return 0 iff point on boundary
* return 1 iff point inside polygon
*/
int point_in_polygon(LWPOLY *polygon, LWPOINT *point)
{
uint32_t i;
int result, in_ring;
POINT2D pt;
POSTGIS_DEBUG(2, "point_in_polygon called.");
getPoint2d_p(point->point, 0, &pt);
/* assume bbox short-circuit has already been attempted */
/* everything is outside of an empty polygon */
if ( polygon->nrings == 0 ) return -1;
in_ring = point_in_ring(polygon->rings[0], &pt);
if ( in_ring == -1) /* outside the exterior ring */
{
POSTGIS_DEBUG(3, "point_in_polygon: outside exterior ring.");
return -1;
}
result = in_ring;
for (i=1; i<polygon->nrings; i++)
{
in_ring = point_in_ring(polygon->rings[i], &pt);
if (in_ring == 1) /* inside a hole => outside the polygon */
{
POSTGIS_DEBUGF(3, "point_in_polygon: within hole %d.", i);
return -1;
}
if (in_ring == 0) /* on the edge of a hole */
{
POSTGIS_DEBUGF(3, "point_in_polygon: on edge of hole %d.", i);
return 0;
}
}
return result; /* -1 = outside, 0 = boundary, 1 = inside */
}
/*
* return -1 iff point outside multipolygon
* return 0 iff point on multipolygon boundary
* return 1 iff point inside multipolygon
*/
int point_in_multipolygon(LWMPOLY *mpolygon, LWPOINT *point)
{
uint32_t i, j;
int result, in_ring;
POINT2D pt;
POSTGIS_DEBUG(2, "point_in_polygon called.");
getPoint2d_p(point->point, 0, &pt);
/* assume bbox short-circuit has already been attempted */
result = -1;
for (j = 0; j < mpolygon->ngeoms; j++ )
{
LWPOLY *polygon = mpolygon->geoms[j];
/* everything is outside of an empty polygon */
if ( polygon->nrings == 0 ) continue;
in_ring = point_in_ring(polygon->rings[0], &pt);
if ( in_ring == -1) /* outside the exterior ring */
{
POSTGIS_DEBUG(3, "point_in_polygon: outside exterior ring.");
continue;
}
if ( in_ring == 0 )
{
return 0;
}
result = in_ring;
for (i=1; i<polygon->nrings; i++)
{
in_ring = point_in_ring(polygon->rings[i], &pt);
if (in_ring == 1) /* inside a hole => outside the polygon */
{
POSTGIS_DEBUGF(3, "point_in_polygon: within hole %d.", i);
result = -1;
break;
}
if (in_ring == 0) /* on the edge of a hole */
{
POSTGIS_DEBUGF(3, "point_in_polygon: on edge of hole %d.", i);
return 0;
}
}
if ( result != -1)
{
return result;
}
}
return result;
}
/*******************************************************************************
* End of "Fast Winding Number Inclusion of a Point in a Polygon" derivative.
******************************************************************************/
/**********************************************************************
*
* ST_MinimumBoundingRadius
*
**********************************************************************/
PG_FUNCTION_INFO_V1(ST_MinimumBoundingRadius);
Datum ST_MinimumBoundingRadius(PG_FUNCTION_ARGS)
{
GSERIALIZED* geom;
LWGEOM* input;
LWBOUNDINGCIRCLE* mbc = NULL;
LWGEOM* lwcenter;
GSERIALIZED* center;
TupleDesc resultTupleDesc;
HeapTuple resultTuple;
Datum result;
Datum result_values[2];
bool result_is_null[2];
double radius = 0;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
geom = PG_GETARG_GSERIALIZED_P(0);
/* Empty geometry? Return POINT EMPTY with zero radius */
if (gserialized_is_empty(geom))
{
lwcenter = (LWGEOM*) lwpoint_construct_empty(gserialized_get_srid(geom), LW_FALSE, LW_FALSE);
}
else
{
input = lwgeom_from_gserialized(geom);
mbc = lwgeom_calculate_mbc(input);
if (!(mbc && mbc->center))
{
lwpgerror("Error calculating minimum bounding circle.");
lwgeom_free(input);
PG_RETURN_NULL();
}
lwcenter = (LWGEOM*) lwpoint_make2d(input->srid, mbc->center->x, mbc->center->y);
radius = mbc->radius;
lwboundingcircle_destroy(mbc);
lwgeom_free(input);
}
center = geometry_serialize(lwcenter);
lwgeom_free(lwcenter);
get_call_result_type(fcinfo, NULL, &resultTupleDesc);
BlessTupleDesc(resultTupleDesc);
result_values[0] = PointerGetDatum(center);
result_is_null[0] = false;
result_values[1] = Float8GetDatum(radius);
result_is_null[1] = false;
resultTuple = heap_form_tuple(resultTupleDesc, result_values, result_is_null);
result = HeapTupleGetDatum(resultTuple);
PG_RETURN_DATUM(result);
}
/**********************************************************************
*
* ST_MinimumBoundingCircle
*
**********************************************************************/
PG_FUNCTION_INFO_V1(ST_MinimumBoundingCircle);
Datum ST_MinimumBoundingCircle(PG_FUNCTION_ARGS)
{
GSERIALIZED* geom;
LWGEOM* input;
LWBOUNDINGCIRCLE* mbc = NULL;
LWGEOM* lwcircle;
GSERIALIZED* center;
int segs_per_quarter;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
geom = PG_GETARG_GSERIALIZED_P(0);
segs_per_quarter = PG_GETARG_INT32(1);
/* Empty geometry? Return POINT EMPTY */
if (gserialized_is_empty(geom))
{
lwcircle = (LWGEOM*) lwpoint_construct_empty(gserialized_get_srid(geom), LW_FALSE, LW_FALSE);
}
else
{
input = lwgeom_from_gserialized(geom);
mbc = lwgeom_calculate_mbc(input);
if (!(mbc && mbc->center))
{
lwpgerror("Error calculating minimum bounding circle.");
lwgeom_free(input);
PG_RETURN_NULL();
}
/* Zero radius? Return a point. */
if (mbc->radius == 0)
lwcircle = lwpoint_as_lwgeom(lwpoint_make2d(input->srid, mbc->center->x, mbc->center->y));
else
lwcircle = lwpoly_as_lwgeom(lwpoly_construct_circle(input->srid, mbc->center->x, mbc->center->y, mbc->radius, segs_per_quarter, LW_TRUE));
lwboundingcircle_destroy(mbc);
lwgeom_free(input);
}
center = geometry_serialize(lwcircle);
lwgeom_free(lwcircle);
PG_RETURN_POINTER(center);
}
/**********************************************************************
*
* ST_GeometricMedian
*
**********************************************************************/
PG_FUNCTION_INFO_V1(ST_GeometricMedian);
Datum ST_GeometricMedian(PG_FUNCTION_ARGS)
{
GSERIALIZED* geom;
GSERIALIZED* result;
LWGEOM* input;
LWPOINT* lwresult;
static const double min_default_tolerance = 1e-8;
double tolerance = min_default_tolerance;
bool compute_tolerance_from_box;
bool fail_if_not_converged;
int max_iter;
/* Read and validate our input arguments */
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
compute_tolerance_from_box = PG_ARGISNULL(1);
if (!compute_tolerance_from_box)
{
tolerance = PG_GETARG_FLOAT8(1);
if (tolerance < 0)
{
lwpgerror("Tolerance must be positive.");
PG_RETURN_NULL();
}
}
max_iter = PG_ARGISNULL(2) ? -1 : PG_GETARG_INT32(2);
fail_if_not_converged = PG_ARGISNULL(3) ? LW_FALSE : PG_GETARG_BOOL(3);
if (max_iter < 0)
{
lwpgerror("Maximum iterations must be positive.");
PG_RETURN_NULL();
}
/* OK, inputs are valid. */
geom = PG_GETARG_GSERIALIZED_P(0);
input = lwgeom_from_gserialized(geom);
if (compute_tolerance_from_box)
{
/* Compute a default tolerance based on the smallest dimension
* of the geometry's bounding box.
*/
static const double tolerance_coefficient = 1e-6;
const GBOX* box = lwgeom_get_bbox(input);
if (box)
{
double min_dim = FP_MIN(box->xmax - box->xmin, box->ymax - box->ymin);
if (lwgeom_has_z(input))
min_dim = FP_MIN(min_dim, box->zmax - box->zmin);
/* Apply a lower bound to the computed default tolerance to
* avoid a tolerance of zero in the case of collinear
* points.
*/
tolerance = FP_MAX(min_default_tolerance, tolerance_coefficient * min_dim);
}
}
lwresult = lwgeom_median(input, tolerance, max_iter, fail_if_not_converged);
lwgeom_free(input);
if(!lwresult)
{
lwpgerror("Error computing geometric median.");
PG_RETURN_NULL();
}
result = geometry_serialize(lwpoint_as_lwgeom(lwresult));
PG_RETURN_POINTER(result);
}
/**********************************************************************
*
* ST_IsPolygonCW
*
**********************************************************************/
PG_FUNCTION_INFO_V1(ST_IsPolygonCW);
Datum ST_IsPolygonCW(PG_FUNCTION_ARGS)
{
GSERIALIZED* geom;
LWGEOM* input;
bool is_clockwise;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
geom = PG_GETARG_GSERIALIZED_P(0);
input = lwgeom_from_gserialized(geom);
is_clockwise = lwgeom_is_clockwise(input);
lwgeom_free(input);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_BOOL(is_clockwise);
}
/**********************************************************************
*
* ST_IsPolygonCCW
*
**********************************************************************/
PG_FUNCTION_INFO_V1(ST_IsPolygonCCW);
Datum ST_IsPolygonCCW(PG_FUNCTION_ARGS)
{
GSERIALIZED* geom;
LWGEOM* input;
bool is_ccw;
if (PG_ARGISNULL(0))
PG_RETURN_NULL();
geom = PG_GETARG_GSERIALIZED_P_COPY(0);
input = lwgeom_from_gserialized(geom);
lwgeom_reverse_in_place(input);
is_ccw = lwgeom_is_clockwise(input);
lwgeom_free(input);
PG_FREE_IF_COPY(geom, 0);
PG_RETURN_BOOL(is_ccw);
}