[postgis-tickets] r14747 - Add new files for #3364

Daniel Baston dbaston at gmail.com
Fri Mar 4 17:27:32 PST 2016


Author: dbaston
Date: 2016-03-04 17:27:32 -0800 (Fri, 04 Mar 2016)
New Revision: 14747

Added:
   trunk/doc/html/image_src/st_geometricmedian01.wkt
   trunk/liblwgeom/lwgeom_median.c
   trunk/regress/geometric_median.sql
   trunk/regress/geometric_median_expected
Log:
Add new files for #3364

Added: trunk/doc/html/image_src/st_geometricmedian01.wkt
===================================================================
--- trunk/doc/html/image_src/st_geometricmedian01.wkt	                        (rev 0)
+++ trunk/doc/html/image_src/st_geometricmedian01.wkt	2016-03-05 01:27:32 UTC (rev 14747)
@@ -0,0 +1,4 @@
+Style6;POINT(25 25)
+Style5;POINT(65 65)
+Style7-smallpoint;MULTIPOINT((20 20), (20 30), (30 20), (190 190))
+

Added: trunk/liblwgeom/lwgeom_median.c
===================================================================
--- trunk/liblwgeom/lwgeom_median.c	                        (rev 0)
+++ trunk/liblwgeom/lwgeom_median.c	2016-03-05 01:27:32 UTC (rev 14747)
@@ -0,0 +1,216 @@
+/**********************************************************************
+ *
+ * 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 2015 Daniel Baston <dbaston at gmail.com>
+ *
+ **********************************************************************/
+
+#include <float.h>
+
+#include "liblwgeom_internal.h"
+#include "lwgeom_log.h"
+
+static void
+calc_distances_3d(const POINT3D* curr, const POINT3D* points, uint32_t npoints, double* distances)
+{
+	uint32_t i;
+	for (i = 0; i < npoints; i++)
+	{
+		distances[i] = distance3d_pt_pt(curr, &points[i]);
+	}
+}
+
+static double
+iterate_3d(POINT3D* curr, const POINT3D* points, uint32_t npoints, double* distances)
+{
+	uint32_t i;
+	POINT3D next = { 0, 0, 0 };
+	double delta;
+	double denom = 0;
+	char hit = LW_FALSE;
+
+	calc_distances_3d(curr, points, npoints, distances);
+
+	for (i = 0; i < npoints; i++)
+	{
+		if (distances[i] == 0)
+			hit = LW_TRUE;
+		else
+			denom += 1.0 / distances[i];
+	}
+
+	for (i = 0; i < npoints; i++)
+	{
+		if (distances[i] > 0)
+		{
+			next.x += (points[i].x / distances[i]) / denom;
+			next.y += (points[i].y / distances[i]) / denom;
+			next.z += (points[i].z / distances[i]) / denom;
+		}
+	}
+
+	/* If any of the intermediate points in the calculation is found in the
+	 * set of input points, the standard Weiszfeld method gets stuck with a
+	 * divide-by-zero.
+	 *
+	 * To get ourselves out of the hole, we follow an alternate procedure to
+	 * get the next iteration, as described in:
+	 *
+	 * Vardi, Y. and Zhang, C. (2011) "A modified Weiszfeld algorithm for the
+	 * Fermat-Weber location problem."  Math. Program., Ser. A 90: 559-566.
+	 * DOI 10.1007/s101070100222
+	 *
+	 * Available online at the time of this writing at 
+	 * http://www.stat.rutgers.edu/home/cunhui/papers/43.pdf
+	 */
+	if (hit)
+	{
+		double dx = 0;
+		double dy = 0;
+		double dz = 0;
+		double r_inv;
+		POINT3D alt;
+		for (i = 0; i < npoints; i++)
+		{
+			if (distances[i] > 0)
+			{
+				dx += (points[i].x - curr->x) / distances[i];
+				dy += (points[i].y - curr->y) / distances[i];
+				dz += (points[i].y - curr->z) / distances[i];
+			}
+		}
+
+		r_inv = 1.0 / sqrt ( dx*dx + dy*dy + dz*dz );
+
+		alt.x = FP_MAX(0, 1.0 - r_inv)*next.x + FP_MIN(1.0, r_inv)*curr->x;
+		alt.y = FP_MAX(0, 1.0 - r_inv)*next.y + FP_MIN(1.0, r_inv)*curr->y;
+		alt.z = FP_MAX(0, 1.0 - r_inv)*next.z + FP_MIN(1.0, r_inv)*curr->z;
+
+		next = alt;
+	}
+
+	delta = distance3d_pt_pt(curr, &next);
+
+	curr->x = next.x;
+	curr->y = next.y;
+	curr->z = next.z;
+
+	return delta;
+}
+
+static POINT3D
+init_guess(const POINT3D* points, uint32_t npoints)
+{
+	POINT3D guess = { 0, 0, 0 };
+	uint32_t i;
+	for (i = 0; i < npoints; i++)
+	{
+		guess.x += points[i].x / npoints;
+		guess.y += points[i].y / npoints;
+		guess.z += points[i].z / npoints;
+	}
+
+	return guess;
+}
+
+static POINT3D*
+lwmpoint_extract_points_3d(const LWMPOINT* g, uint32_t* ngeoms)
+{
+	uint32_t i;
+	uint32_t n = 0;
+	int is_3d = lwgeom_has_z((LWGEOM*) g);
+
+	POINT3D* points = lwalloc(g->ngeoms * sizeof(POINT3D));
+	for (i = 0; i < g->ngeoms; i++)
+	{
+		LWGEOM* subg = lwcollection_getsubgeom((LWCOLLECTION*) g, i);
+		if (!lwgeom_is_empty(subg))
+		{
+			getPoint3dz_p(((LWPOINT*) subg)->point, 0, (POINT3DZ*) &points[n++]);
+			if (!is_3d)
+				points[n-1].z = 0.0; /* in case the getPoint functions return NaN in the future for 2d */
+		}
+	}
+
+	if (ngeoms != NULL)
+		*ngeoms = n;
+
+	return points;
+}
+
+LWPOINT*
+lwmpoint_median(const LWMPOINT* g, double tol, uint32_t max_iter, char fail_if_not_converged)
+{
+	uint32_t npoints; /* we need to count this ourselves so we can exclude empties */
+	uint32_t i;
+	double delta = DBL_MAX;
+	double* distances;
+	POINT3D* points = lwmpoint_extract_points_3d(g, &npoints);
+	POINT3D median;
+
+	if (npoints == 0)
+	{
+		lwfree(points);
+		return lwpoint_construct_empty(g->srid, 0, 0);
+	}
+
+	median = init_guess(points, npoints);
+
+	distances = lwalloc(npoints * sizeof(double));
+
+	for (i = 0; i < max_iter && delta > tol; i++)
+	{
+		delta = iterate_3d(&median, points, npoints, distances);
+	}
+
+	lwfree(points);
+	lwfree(distances);
+
+	if (fail_if_not_converged && delta > tol)
+	{
+		lwerror("Median failed to converge within %g after %d iterations.", tol, max_iter);
+		return NULL;
+	}
+
+	if (lwgeom_has_z((LWGEOM*) g))
+	{
+		return lwpoint_make3dz(g->srid, median.x, median.y, median.z);
+	}
+	else
+	{
+		return lwpoint_make2d(g->srid, median.x, median.y);
+	}
+}
+
+LWPOINT*
+lwgeom_median(const LWGEOM* g, double tol, uint32_t max_iter, char fail_if_not_converged)
+{
+	switch( lwgeom_get_type(g) )
+	{
+		case POINTTYPE:
+			return lwpoint_clone(lwgeom_as_lwpoint(g));
+		case MULTIPOINTTYPE:
+			return lwmpoint_median(lwgeom_as_lwmpoint(g), tol, max_iter, fail_if_not_converged);
+		default:
+			lwerror("Unsupported geometry type in lwgeom_median");
+			return NULL;
+	}
+}
+

Added: trunk/regress/geometric_median.sql
===================================================================
--- trunk/regress/geometric_median.sql	                        (rev 0)
+++ trunk/regress/geometric_median.sql	2016-03-05 01:27:32 UTC (rev 14747)
@@ -0,0 +1,16 @@
+-- postgres
+
+SELECT 't1', ST_GeometricMedian(null) IS NULL;
+SELECT 't2', ST_AsText(ST_GeometricMedian('LINESTRING (1 1, 2 2)'));
+SELECT 't3', 'POINT EMPTY' = ST_AsText(ST_GeometricMedian('POINT EMPTY'));
+SELECT 't4', 'POINT EMPTY' = ST_AsText(ST_GeometricMedian('MULTIPOINT EMPTY'));
+SELECT 't5', ST_SRID(ST_GeometricMedian('SRID=32611;POINT (1 1)')) = 32611;
+SELECT 't6', ST_SRID(ST_GeometricMedian('SRID=32611;MULTIPOINT ((1 1), (2 7))')) = 32611;
+SELECT 't7', ST_SRID(ST_GeometricMedian('SRID=32611;MULTIPOINT ((1 1))')) = 32611;
+SELECT 't8', ST_Equals('POINTZ (15 15 15)', ST_AsText(ST_GeometricMedian('MULTIPOINT ((10 10 10), (10 20 10), (20 10 10), (20 20 10), (10 10 20), (10 20 20), (20 10 20), (20 20 20))')));
+-- Doesn't fail even though we don't let it iterate
+SELECT 't9', ST_IsEmpty(ST_GeometricMedian('MULTIPOINT (0 0, 1 1, 0 1, 2 2)', max_iter := 1));
+-- Unless we enfore that it must converge.
+SELECT 't10', ST_IsEmpty(ST_GeometricMedian('MULTIPOINT (0 0, 1 1, 0 1, 2 2)', max_iter := 1, fail_if_not_converged := true));
+-- But if we drop the tolerance, it's OK
+SELECT 't11', ST_IsEmpty(ST_GeometricMedian('MULTIPOINT (0 0, 1 1, 0 1, 2 2)', max_iter := 1, tolerance := 0.1, fail_if_not_converged := true));

Added: trunk/regress/geometric_median_expected
===================================================================
--- trunk/regress/geometric_median_expected	                        (rev 0)
+++ trunk/regress/geometric_median_expected	2016-03-05 01:27:32 UTC (rev 14747)
@@ -0,0 +1,11 @@
+t1|t
+ERROR:  Unsupported geometry type in lwgeom_median
+t3|t
+t4|t
+t5|t
+t6|t
+t7|t
+t8|t
+t9|f
+ERROR:  Median failed to converge within 2e-6 after 1 iterations.
+t11|f



More information about the postgis-tickets mailing list