Add missing files for #2996

git-svn-id: http://svn.osgeo.org/postgis/trunk@14449 b70326c6-7e19-0410-871a-916f4a2858ee
This commit is contained in:
Daniel Baston 2015-11-29 23:16:30 +00:00
parent 44405ba60c
commit 14545487c5
4 changed files with 372 additions and 0 deletions

View file

@ -0,0 +1,80 @@
/**********************************************************************
*
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
* Copyright 2015 Daniel Baston
*
* This is free software; you can redistribute and/or modify it under
* the terms of the GNU General Public Licence. See the COPYING file.
*
**********************************************************************/
#include "CUnit/Basic.h"
#include "cu_tester.h"
#include "../liblwgeom_internal.h"
static void mbc_test(LWGEOM* g)
{
LWBOUNDINGCIRCLE* result = lwgeom_calculate_mbc(g);
CU_ASSERT_TRUE(result != NULL);
LWPOINTITERATOR* it = lwpointiterator_create(g);
POINT2D p;
POINT4D p4;
while (lwpointiterator_next(it, &p4))
{
p.x = p4.x;
p.y = p4.y;
CU_ASSERT_TRUE(distance2d_pt_pt(result->center, &p) <= result->radius);
}
lwboundingcircle_destroy(result);
lwpointiterator_destroy(it);
}
static void basic_test(void)
{
uint32_t i;
char* inputs[] =
{
"POLYGON((26426 65078,26531 65242,26075 65136,26096 65427,26426 65078))",
"POINT (17 253)",
"TRIANGLE ((0 0, 10 0, 10 10, 0 0))",
"LINESTRING (17 253, -44 28, 33 11, 26 44)",
"MULTIPOINT ((0 0), (0 0), (0 0), (0 0))",
"POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
"LINESTRING (-48546889 37039202, -37039202 -48546889)"
};
for (i = 0; i < sizeof(inputs)/sizeof(LWGEOM*); i++)
{
LWGEOM* input = lwgeom_from_wkt(inputs[i], LW_PARSER_CHECK_NONE);
mbc_test(input);
lwgeom_free(input);
}
}
static void test_empty(void)
{
LWGEOM* input = lwgeom_from_wkt("POINT EMPTY", LW_PARSER_CHECK_NONE);
LWBOUNDINGCIRCLE* result = lwgeom_calculate_mbc(input);
CU_ASSERT_TRUE(result == NULL);
lwgeom_free(input);
}
/*
** Used by test harness to register the tests in this file.
*/
void minimum_bounding_circle_suite_setup(void);
void minimum_bounding_circle_suite_setup(void)
{
CU_pSuite suite = CU_add_suite("minimum_bounding_circle", NULL, NULL);
PG_ADD_TEST(suite, basic_test);
PG_ADD_TEST(suite, test_empty);
}

View file

@ -0,0 +1,280 @@
/**********************************************************************
*
* PostGIS - Spatial Types for PostgreSQL
* http://postgis.net
*
* Copyright 2015 Daniel Baston <dbaston@gmail.com>
*
* This is free software; you can redistribute and/or modify it under
* the terms of the GNU General Public Licence. See the COPYING file.
*
**********************************************************************/
#include <string.h>
#include "liblwgeom_internal.h"
typedef struct {
const POINT2D* p1;
const POINT2D* p2;
const POINT2D* p3;
} SUPPORTING_POINTS;
static SUPPORTING_POINTS*
supporting_points_create()
{
SUPPORTING_POINTS* s = lwalloc(sizeof(SUPPORTING_POINTS));
s->p1 = NULL;
s->p2 = NULL;
s->p3 = NULL;
return s;
}
static void
supporting_points_destroy(SUPPORTING_POINTS* s)
{
lwfree(s);
}
static uint32_t
num_supporting_points(SUPPORTING_POINTS* support)
{
uint32_t N = 0;
if (support->p1 != NULL)
N++;
if (support->p2 != NULL)
N++;
if (support->p3 != NULL)
N++;
return N;
}
static int
add_supporting_point(SUPPORTING_POINTS* support, const POINT2D* p)
{
switch(num_supporting_points(support))
{
case 0: support->p1 = p;
break;
case 1: support->p2 = p;
break;
case 2: support->p3 = p;
break;
default: return LW_FAILURE;
}
return LW_SUCCESS;
}
static int
point_inside_circle(const POINT2D* p, const LWBOUNDINGCIRCLE* c)
{
if (!c)
return LW_FALSE;
if (distance2d_pt_pt(p, c->center) > c->radius)
return LW_FALSE;
return LW_TRUE;
}
static double
det(double m00, double m01, double m10, double m11)
{
return m00 * m11 - m01 * m10;
}
static void
circumcenter(const POINT2D* a, const POINT2D* b, const POINT2D* c, POINT2D* result)
{
double cx = c->x;
double cy = c->y;
double ax = a->x - cx;
double ay = a->y - cy;
double bx = b->x - cx;
double by = b->y - cy;
double denom = 2 * det(ax, ay, bx, by);
double numx = det(ay, ax * ax + ay * ay, by, bx * bx + by * by);
double numy = det(ax, ax * ax + ay * ay, bx, bx * bx + by * by);
result->x = cx - numx / denom;
result->y = cy + numy / denom;
}
static void
calculate_mbc_1(const SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc)
{
mbc->radius = 0;
mbc->center->x = support->p1->x;
mbc->center->y = support->p1->y;
}
static void
calculate_mbc_2(const SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc)
{
double d1, d2;
mbc->center->x = 0.5*(support->p1->x + support->p2->x);
mbc->center->y = 0.5*(support->p1->y + support->p2->y);
d1 = distance2d_pt_pt(mbc->center, support->p1);
d2 = distance2d_pt_pt(mbc->center, support->p2);
mbc->radius = FP_MAX(d1, d2);
}
static void
calculate_mbc_3(const SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc)
{
double d1, d2, d3;
circumcenter(support->p1, support->p2, support->p3, mbc->center);
d1 = distance2d_pt_pt(mbc->center, support->p1);
d2 = distance2d_pt_pt(mbc->center, support->p2);
d3 = distance2d_pt_pt(mbc->center, support->p3);
mbc->radius = FP_MAX(FP_MAX(d1, d2), d3);
}
static int
calculate_mbc_from_support(SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc)
{
switch(num_supporting_points(support))
{
case 0: break;
case 1: calculate_mbc_1(support, mbc);
break;
case 2: calculate_mbc_2(support, mbc);
break;
case 3: calculate_mbc_3(support, mbc);
break;
default: return LW_FAILURE;
}
return LW_SUCCESS;
}
static int
calculate_mbc(const POINT2D** points, uint32_t max_n, SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc)
{
uint32_t i;
if(!calculate_mbc_from_support(support, mbc))
{
return LW_FAILURE;
}
if (num_supporting_points(support) == 3)
{
/* If we're entering the function with three supporting points already, our circle
* is already fully constrained - we couldn't add another supporting point if we
* needed to. So, there's no point in proceeding further. Welzl (1991) provides
* a much better explanation of why this works.
* */
return LW_SUCCESS;
}
for (i = 0; i < max_n; i++)
{
if (!point_inside_circle(points[i], mbc))
{
/* We've run into a point that isn't inside our circle. To fix this, we'll
* go back in time, and re-run the algorithm for each point we've seen so
* far, with the constraint that the current point must be on the boundary
* of the circle. Then, we'll continue on in this loop with the modified
* circle that by definition includes the current point. */
SUPPORTING_POINTS next_support;
memcpy(&next_support, support, sizeof(SUPPORTING_POINTS));
add_supporting_point(&next_support, points[i]);
if (!calculate_mbc(points, i, &next_support, mbc))
{
return LW_FAILURE;
}
}
}
return LW_SUCCESS;
}
static LWBOUNDINGCIRCLE*
lwboundingcircle_create()
{
LWBOUNDINGCIRCLE* c = lwalloc(sizeof(LWBOUNDINGCIRCLE));
c->center = lwalloc(sizeof(POINT2D));
c->radius = 0.0;
c->center->x = 0.0;
c->center->y = 0.0;
return c;
}
void
lwboundingcircle_destroy(LWBOUNDINGCIRCLE* c)
{
lwfree(c->center);
lwfree(c);
}
LWBOUNDINGCIRCLE*
lwgeom_calculate_mbc(const LWGEOM* g)
{
SUPPORTING_POINTS* support;
LWBOUNDINGCIRCLE* result;
LWPOINTITERATOR* it;
uint32_t num_points;
POINT2D** points;
POINT4D p;
uint32_t i;
int success;
if(g == NULL || lwgeom_is_empty(g))
return LW_FAILURE;
num_points = lwgeom_count_vertices(g);
it = lwpointiterator_create(g);
points = lwalloc(num_points * sizeof(POINT2D*));
for (i = 0; i < num_points; i++)
{
if(!lwpointiterator_next(it, &p))
{
uint32_t j;
for (j = 0; j < i; j++)
{
lwfree(points[j]);
}
lwpointiterator_destroy(it);
lwfree(points);
return LW_FAILURE;
}
points[i] = lwalloc(sizeof(POINT2D));
points[i]->x = p.x;
points[i]->y = p.y;
}
lwpointiterator_destroy(it);
support = supporting_points_create();
result = lwboundingcircle_create();
/* Technically, a randomized algorithm would demand that we shuffle the input points
* before we call calculate_mbc(). However, we make the (perhaps poor) assumption that
* the order we happen to find the points is as good as random, or close enough.
* */
success = calculate_mbc((const POINT2D**) points, num_points, support, result);
for (i = 0; i < num_points; i++)
{
lwfree(points[i]);
}
lwfree(points);
supporting_points_destroy(support);
if (!success)
return NULL;
return result;
}

View file

@ -0,0 +1,6 @@
SELECT 't1', ST_MinimumBoundingCircle(NULL::geometry) IS NULL;
SELECT 't2', ST_Equals(ST_MinimumBoundingCircle('POINT EMPTY'), 'POLYGON EMPTY'::geometry);
SELECT 't3', ST_SRID(ST_MinimumBoundingCircle('SRID=32611;POINT(4021690.58034526 6040138.01373556)')) = 32611;
SELECT 't4', ST_SRID(center) = 32611 FROM ST_MinimumBoundingRadius('SRID=32611;POINT(4021690.58034526 6040138.01373556)');
SELECT 't5', ST_Equals(center, 'POINT EMPTY') AND radius = 0 FROM ST_MinimumBoundingRadius('GEOMETRYCOLLECTION EMPTY');
SELECT 't6', ST_Equals(center, 'POINT (0 0.5)') AND radius = 0.5 FROM ST_MinimumBoundingRadius('MULTIPOINT ((0 0 0), (0 1 1))');

View file

@ -0,0 +1,6 @@
t1|t
t2|t
t3|t
t4|t
t5|t
t6|t