PostGIS 3.7.0dev-r@@SVN_REVISION@@
Loading...
Searching...
No Matches
lwgeom_median.c
Go to the documentation of this file.
1/**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 *
6 * PostGIS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * PostGIS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with PostGIS. If not, see <http://www.gnu.org/licenses/>.
18 *
19 **********************************************************************
20 *
21 * Copyright 2015 Daniel Baston <dbaston@gmail.com>
22 * Copyright 2017 Darafei Praliaskouski <me@komzpa.net>
23 *
24 **********************************************************************/
25
26#include <assert.h>
27#include <float.h>
28
29#include "liblwgeom_internal.h"
30#include "lwgeom_log.h"
31
32static double
33calc_weighted_distances_3d(const POINT3D* curr, const POINT4D* points, uint32_t npoints, double* distances)
34{
35 uint32_t i;
36 double weight = 0.0;
37 for (i = 0; i < npoints; i++)
38 {
39 double dist = distance3d_pt_pt(curr, (POINT3D*)&points[i]);
40 distances[i] = dist / points[i].m;
41 weight += dist * points[i].m;
42 }
43
44 return weight;
45}
46
47static uint32_t
48iterate_4d(POINT3D* curr, const POINT4D* points, const uint32_t npoints, const uint32_t max_iter, const double tol)
49{
50 uint32_t i, iter;
51 double delta;
52 double sum_curr = 0, sum_next = 0;
53 int hit = LW_FALSE;
54 double *distances = lwalloc(npoints * sizeof(double));
55
56 sum_curr = calc_weighted_distances_3d(curr, points, npoints, distances);
57
58 for (iter = 0; iter < max_iter; iter++)
59 {
60 POINT3D next = { 0, 0, 0 };
61 double denom = 0;
62
64 for (i = 0; i < npoints; i++)
65 {
66 /* we need to use lower epsilon than in FP_IS_ZERO in the loop for calculation to converge */
67 if (distances[i] > DBL_EPSILON)
68 {
69 next.x += points[i].x / distances[i];
70 next.y += points[i].y / distances[i];
71 next.z += points[i].z / distances[i];
72 denom += 1.0 / distances[i];
73 }
74 else
75 {
76 hit = LW_TRUE;
77 }
78 }
79
80 if (denom < DBL_EPSILON)
81 {
82 /* No movement - Final point */
83 break;
84 }
85
86 /* Calculate the new point */
87 next.x /= denom;
88 next.y /= denom;
89 next.z /= denom;
90
91 /* If any of the intermediate points in the calculation is found in the
92 * set of input points, the standard Weiszfeld method gets stuck with a
93 * divide-by-zero.
94 *
95 * To get ourselves out of the hole, we follow an alternate procedure to
96 * get the next iteration, as described in:
97 *
98 * Vardi, Y. and Zhang, C. (2011) "A modified Weiszfeld algorithm for the
99 * Fermat-Weber location problem." Math. Program., Ser. A 90: 559-566.
100 * DOI 10.1007/s101070100222
101 *
102 * Available online at the time of this writing at
103 * http://www.stat.rutgers.edu/home/cunhui/papers/43.pdf
104 */
105 if (hit)
106 {
107 double dx = 0, dy = 0, dz = 0;
108 double d_sqr;
109 hit = LW_FALSE;
110
111 for (i = 0; i < npoints; i++)
112 {
113 if (distances[i] > DBL_EPSILON)
114 {
115 dx += (points[i].x - curr->x) / distances[i];
116 dy += (points[i].y - curr->y) / distances[i];
117 dz += (points[i].z - curr->z) / distances[i];
118 }
119 }
120
121 d_sqr = sqrt(dx*dx + dy*dy + dz*dz);
122 if (d_sqr > DBL_EPSILON)
123 {
124 double r_inv = FP_MAX(0, 1.0 / d_sqr);
125 next.x = (1.0 - r_inv)*next.x + r_inv*curr->x;
126 next.y = (1.0 - r_inv)*next.y + r_inv*curr->y;
127 next.z = (1.0 - r_inv)*next.z + r_inv*curr->z;
128 }
129 }
130
131 /* Check movement with next point */
132 sum_next = calc_weighted_distances_3d(&next, points, npoints, distances);
133 delta = sum_curr - sum_next;
134 if (delta < tol)
135 {
136 break;
137 }
138 else
139 {
140 curr->x = next.x;
141 curr->y = next.y;
142 curr->z = next.z;
143 sum_curr = sum_next;
144 }
145 }
146
147 lwfree(distances);
148 return iter;
149}
150
151static POINT3D
152init_guess(const POINT4D* points, uint32_t npoints)
153{
154 assert(npoints > 0);
155 POINT3D guess = { 0, 0, 0 };
156 double mass = 0;
157 uint32_t i;
158 for (i = 0; i < npoints; i++)
159 {
160 guess.x += points[i].x * points[i].m;
161 guess.y += points[i].y * points[i].m;
162 guess.z += points[i].z * points[i].m;
163 mass += points[i].m;
164 }
165 guess.x /= mass;
166 guess.y /= mass;
167 guess.z /= mass;
168 return guess;
169}
170
171POINT4D*
172lwmpoint_extract_points_4d(const LWMPOINT* g, uint32_t* npoints, int* input_empty)
173{
174 uint32_t n = 0;
175 POINT4D* points = lwalloc(g->ngeoms * sizeof(POINT4D));
176 int has_m = lwgeom_has_m((LWGEOM*) g);
177
178 for (uint32_t i = 0; i < g->ngeoms; i++)
179 {
180 const LWGEOM* subg = lwcollection_getsubgeom((LWCOLLECTION*) g, i);
181 if (!lwgeom_is_empty(subg))
182 {
183 *input_empty = LW_FALSE;
184 if (!getPoint4d_p(((LWPOINT*) subg)->point, 0, &points[n]))
185 {
186 lwerror("Geometric median: getPoint4d_p reported failure on point (POINT(%g %g %g %g), number %d of %d in input).", points[n].x, points[n].y, points[n].z, points[n].m, i, g->ngeoms);
187 lwfree(points);
188 return NULL;
189 }
190 if (has_m)
191 {
192 /* This limitation on non-negativity can be lifted
193 * by replacing Weiszfeld algorithm with different one.
194 * Possible option described in:
195 *
196 * Drezner, Zvi & O. Wesolowsky, George. (1991).
197 * The Weber Problem On The Plane With Some Negative Weights.
198 * INFOR. Information Systems and Operational Research.
199 * 29. 10.1080/03155986.1991.11732158.
200 */
201 if (points[n].m < 0)
202 {
203 lwerror("Geometric median input contains points with negative weights (POINT(%g %g %g %g), number %d of %d in input). Implementation can't guarantee global minimum convergence.", points[n].x, points[n].y, points[n].z, points[n].m, i, g->ngeoms);
204 lwfree(points);
205 return NULL;
206 }
207
208 /* points with zero weight are not going to affect calculation, drop them early */
209 if (points[n].m > DBL_EPSILON) n++;
210 }
211 else
212 {
213 points[n].m = 1.0;
214 n++;
215 }
216 }
217 }
218
219#ifndef NDEBUG
220 /* check Z=0 for 2D inputs */
221 if (!lwgeom_has_z((LWGEOM*) g))
222 for (uint32_t i = 0; i < n; i++)
223 assert(points[i].z == 0);
224#endif
225
226 *npoints = n;
227 return points;
228}
229
230
231LWPOINT*
232lwmpoint_median(const LWMPOINT* g, double tol, uint32_t max_iter, char fail_if_not_converged)
233{
234 /* m ordinate is considered weight, if defined */
235 uint32_t npoints = 0; /* we need to count this ourselves so we can exclude empties and weightless points */
236 uint32_t i;
237 int input_empty = LW_TRUE;
238 POINT3D median;
239 POINT4D* points = lwmpoint_extract_points_4d(g, &npoints, &input_empty);
240
241 /* input validation failed, error reported already */
242 if (points == NULL) return NULL;
243
244 if (npoints == 0)
245 {
246 lwfree(points);
247 if (input_empty)
248 {
249 return lwpoint_construct_empty(g->srid, 0, 0);
250 }
251 else
252 {
253 lwerror("Median failed to find non-empty input points with positive weight.");
254 return NULL;
255 }
256 }
257
258 median = init_guess(points, npoints);
259
260 i = iterate_4d(&median, points, npoints, max_iter, tol);
261
262 lwfree(points);
263
264 if (fail_if_not_converged && i >= max_iter)
265 {
266 lwerror("Median failed to converge within %g after %d iterations.", tol, max_iter);
267 return NULL;
268 }
269
270 if (lwgeom_has_z((LWGEOM*) g))
271 {
272 return lwpoint_make3dz(g->srid, median.x, median.y, median.z);
273 }
274 else
275 {
276 return lwpoint_make2d(g->srid, median.x, median.y);
277 }
278}
279
280LWPOINT*
281lwgeom_median(const LWGEOM* g, double tol, uint32_t max_iter, char fail_if_not_converged)
282{
283 switch (g->type)
284 {
285 case POINTTYPE:
287 case MULTIPOINTTYPE:
288 return lwmpoint_median(lwgeom_as_lwmpoint(g), tol, max_iter, fail_if_not_converged);
289 default:
290 lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(g->type));
291 return NULL;
292 }
293}
294
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition lwutil.c:216
#define LW_FALSE
Definition liblwgeom.h:94
LWPOINT * lwpoint_make3dz(int32_t srid, double x, double y, double z)
Definition lwpoint.c:173
#define MULTIPOINTTYPE
Definition liblwgeom.h:105
int lwgeom_has_z(const LWGEOM *geom)
Return LW_TRUE if geometry has Z ordinates.
Definition lwgeom.c:962
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition liblwgeom.h:102
void * lwalloc(size_t size)
Definition lwutil.c:227
double distance3d_pt_pt(const POINT3D *p1, const POINT3D *p2)
void lwfree(void *mem)
Definition lwutil.c:248
LWPOINT * lwpoint_make2d(int32_t srid, double x, double y)
Definition lwpoint.c:163
const LWGEOM * lwcollection_getsubgeom(LWCOLLECTION *col, uint32_t gnum)
LWPOINT * lwpoint_construct_empty(int32_t srid, char hasz, char hasm)
Definition lwpoint.c:151
int getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *point)
Definition lwgeom_api.c:125
LWMPOINT * lwgeom_as_lwmpoint(const LWGEOM *lwgeom)
Definition lwgeom.c:270
#define LW_TRUE
Return types for functions with status returns.
Definition liblwgeom.h:93
int lwgeom_has_m(const LWGEOM *geom)
Return LW_TRUE if geometry has M ordinates.
Definition lwgeom.c:969
LWPOINT * lwpoint_clone(const LWPOINT *lwgeom)
Definition lwpoint.c:239
#define FP_MAX(A, B)
void void lwerror(const char *fmt,...) __attribute__((format(printf
Write a notice out to the error handler.
LWPOINT * lwmpoint_median(const LWMPOINT *g, double tol, uint32_t max_iter, char fail_if_not_converged)
LWPOINT * lwgeom_median(const LWGEOM *g, double tol, uint32_t max_iter, char fail_if_not_converged)
static double calc_weighted_distances_3d(const POINT3D *curr, const POINT4D *points, uint32_t npoints, double *distances)
POINT4D * lwmpoint_extract_points_4d(const LWMPOINT *g, uint32_t *npoints, int *input_empty)
static POINT3D init_guess(const POINT4D *points, uint32_t npoints)
static uint32_t iterate_4d(POINT3D *curr, const POINT4D *points, const uint32_t npoints, const uint32_t max_iter, const double tol)
static LWPOINT * lwgeom_as_lwpoint(const LWGEOM *lwgeom)
Definition lwinline.h:127
static int lwgeom_is_empty(const LWGEOM *geom)
Return true or false depending on whether a geometry is an "empty" geometry (no vertices members)
Definition lwinline.h:199
uint32_t ngeoms
Definition liblwgeom.h:580
uint8_t type
Definition liblwgeom.h:462
int32_t srid
Definition liblwgeom.h:534
uint32_t ngeoms
Definition liblwgeom.h:538
double z
Definition liblwgeom.h:402
double x
Definition liblwgeom.h:402
double y
Definition liblwgeom.h:402
double m
Definition liblwgeom.h:414
double x
Definition liblwgeom.h:414
double z
Definition liblwgeom.h:414
double y
Definition liblwgeom.h:414