PostGIS  3.7.0dev-r@@SVN_REVISION@@
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 
32 static double
33 calc_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 
47 static uint32_t
48 iterate_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 
151 static POINT3D
152 init_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 
171 POINT4D*
172 lwmpoint_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 
231 LWPOINT*
232 lwmpoint_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 
280 LWPOINT*
281 lwgeom_median(const LWGEOM* g, double tol, uint32_t max_iter, char fail_if_not_converged)
282 {
283  switch (g->type)
284  {
285  case POINTTYPE:
286  return lwpoint_clone(lwgeom_as_lwpoint(g));
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 
LWPOINT * lwpoint_construct_empty(int32_t srid, char hasz, char hasm)
Definition: lwpoint.c:151
#define LW_FALSE
Definition: liblwgeom.h:94
LWPOINT * lwpoint_make2d(int32_t srid, double x, double y)
Definition: lwpoint.c:163
LWMPOINT * lwgeom_as_lwmpoint(const LWGEOM *lwgeom)
Definition: lwgeom.c:242
#define MULTIPOINTTYPE
Definition: liblwgeom.h:105
int lwgeom_has_z(const LWGEOM *geom)
Return LW_TRUE if geometry has Z ordinates.
Definition: lwgeom.c:934
#define POINTTYPE
LWTYPE numbers, used internally by PostGIS.
Definition: liblwgeom.h:102
const LWGEOM * lwcollection_getsubgeom(LWCOLLECTION *col, uint32_t gnum)
Definition: lwcollection.c:115
double distance3d_pt_pt(const POINT3D *p1, const POINT3D *p2)
Definition: measures3d.c:1066
void lwfree(void *mem)
Definition: lwutil.c:248
LWPOINT * lwpoint_make3dz(int32_t srid, double x, double y, double z)
Definition: lwpoint.c:173
const char * lwtype_name(uint8_t type)
Return the type name string associated with a type number (e.g.
Definition: lwutil.c:216
int getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *point)
Definition: lwgeom_api.c:125
void * lwalloc(size_t size)
Definition: lwutil.c:227
#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:941
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)
static double calc_weighted_distances_3d(const POINT3D *curr, const POINT4D *points, uint32_t npoints, double *distances)
Definition: lwgeom_median.c:33
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)
Definition: lwgeom_median.c:48
LWPOINT * lwgeom_median(const LWGEOM *g, double tol, uint32_t max_iter, char fail_if_not_converged)
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
static LWPOINT * lwgeom_as_lwpoint(const LWGEOM *lwgeom)
Definition: lwinline.h:127
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