Coverage Report

Created: 2026-07-16 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwlinearreferencing.c
Line
Count
Source
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 (C) 2015 Sandro Santilli <strk@kbt.io>
22
 * Copyright (C) 2011 Paul Ramsey
23
 *
24
 **********************************************************************/
25
26
#include "liblwgeom_internal.h"
27
#include "lwgeom_log.h"
28
#include "measures3d.h"
29
30
static int
31
segment_locate_along(const POINT4D *p1, const POINT4D *p2, double m, double offset, POINT4D *pn)
32
0
{
33
0
  double m1 = p1->m;
34
0
  double m2 = p2->m;
35
0
  double mprop;
36
37
  /* M is out of range, no new point generated. */
38
0
  if ((m < FP_MIN(m1, m2)) || (m > FP_MAX(m1, m2)))
39
0
  {
40
0
    return LW_FALSE;
41
0
  }
42
43
0
  if (m1 == m2)
44
0
  {
45
    /* Degenerate case: same M on both points.
46
       If they are the same point we just return one of them. */
47
0
    if (p4d_same(p1, p2))
48
0
    {
49
0
      *pn = *p1;
50
0
      return LW_TRUE;
51
0
    }
52
    /* If the points are different we split the difference */
53
0
    mprop = 0.5;
54
0
  }
55
0
  else
56
0
  {
57
0
    mprop = (m - m1) / (m2 - m1);
58
0
  }
59
60
  /* M is in range, new point to be generated. */
61
0
  pn->x = p1->x + (p2->x - p1->x) * mprop;
62
0
  pn->y = p1->y + (p2->y - p1->y) * mprop;
63
0
  pn->z = p1->z + (p2->z - p1->z) * mprop;
64
0
  pn->m = m;
65
66
  /* Offset to the left or right, if necessary. */
67
0
  if (offset != 0.0)
68
0
  {
69
0
    double theta = atan2(p2->y - p1->y, p2->x - p1->x);
70
0
    pn->x -= sin(theta) * offset;
71
0
    pn->y += cos(theta) * offset;
72
0
  }
73
74
0
  return LW_TRUE;
75
0
}
76
77
static POINTARRAY *
78
ptarray_locate_along(const POINTARRAY *pa, double m, double offset)
79
0
{
80
0
  uint32_t i;
81
0
  POINT4D p1, p2, pn;
82
0
  POINTARRAY *dpa = NULL;
83
84
  /* Can't do anything with degenerate point arrays */
85
0
  if (!pa || pa->npoints < 2)
86
0
    return NULL;
87
88
  /* Walk through each segment in the point array */
89
0
  for (i = 1; i < pa->npoints; i++)
90
0
  {
91
0
    getPoint4d_p(pa, i - 1, &p1);
92
0
    getPoint4d_p(pa, i, &p2);
93
94
    /* No derived point? Move to next segment. */
95
0
    if (segment_locate_along(&p1, &p2, m, offset, &pn) == LW_FALSE)
96
0
      continue;
97
98
    /* No pointarray, make a fresh one */
99
0
    if (dpa == NULL)
100
0
      dpa = ptarray_construct_empty(ptarray_has_z(pa), ptarray_has_m(pa), 8);
101
102
    /* Add our new point to the array */
103
0
    ptarray_append_point(dpa, &pn, 0);
104
0
  }
105
106
0
  return dpa;
107
0
}
108
109
static LWMPOINT *
110
lwline_locate_along(const LWLINE *lwline, double m, double offset)
111
0
{
112
0
  POINTARRAY *opa = NULL;
113
0
  LWMPOINT *mp = NULL;
114
0
  LWGEOM *lwg = lwline_as_lwgeom(lwline);
115
0
  int hasz, hasm, srid;
116
117
  /* Return degenerates upwards */
118
0
  if (!lwline)
119
0
    return NULL;
120
121
  /* Create empty return shell */
122
0
  srid = lwgeom_get_srid(lwg);
123
0
  hasz = lwgeom_has_z(lwg);
124
0
  hasm = lwgeom_has_m(lwg);
125
126
0
  if (hasm)
127
0
  {
128
    /* Find points along */
129
0
    opa = ptarray_locate_along(lwline->points, m, offset);
130
0
  }
131
0
  else
132
0
  {
133
0
    LWLINE *lwline_measured = lwline_measured_from_lwline(lwline, 0.0, 1.0);
134
0
    opa = ptarray_locate_along(lwline_measured->points, m, offset);
135
0
    lwline_free(lwline_measured);
136
0
  }
137
138
  /* Return NULL as EMPTY */
139
0
  if (!opa)
140
0
    return lwmpoint_construct_empty(srid, hasz, hasm);
141
142
  /* Convert pointarray into a multipoint */
143
0
  mp = lwmpoint_construct(srid, opa);
144
0
  ptarray_free(opa);
145
0
  return mp;
146
0
}
147
148
static LWMPOINT *
149
lwmline_locate_along(const LWMLINE *lwmline, double m, double offset)
150
0
{
151
0
  LWMPOINT *lwmpoint = NULL;
152
0
  LWGEOM *lwg = lwmline_as_lwgeom(lwmline);
153
0
  uint32_t i, j;
154
155
  /* Return degenerates upwards */
156
0
  if ((!lwmline) || (lwmline->ngeoms < 1))
157
0
    return NULL;
158
159
  /* Construct return */
160
0
  lwmpoint = lwmpoint_construct_empty(lwgeom_get_srid(lwg), lwgeom_has_z(lwg), lwgeom_has_m(lwg));
161
162
  /* Locate along each sub-line */
163
0
  for (i = 0; i < lwmline->ngeoms; i++)
164
0
  {
165
0
    LWMPOINT *along = lwline_locate_along(lwmline->geoms[i], m, offset);
166
0
    if (along)
167
0
    {
168
0
      if (!lwgeom_is_empty((LWGEOM *)along))
169
0
      {
170
0
        for (j = 0; j < along->ngeoms; j++)
171
0
        {
172
0
          lwmpoint_add_lwpoint(lwmpoint, along->geoms[j]);
173
0
        }
174
0
      }
175
      /* Free the containing geometry, but leave the sub-geometries around */
176
0
      along->ngeoms = 0;
177
0
      lwmpoint_free(along);
178
0
    }
179
0
  }
180
0
  return lwmpoint;
181
0
}
182
183
static LWMPOINT *
184
lwpoint_locate_along(const LWPOINT *lwpoint, double m, __attribute__((__unused__)) double offset)
185
0
{
186
0
  double point_m = lwpoint_get_m(lwpoint);
187
0
  LWGEOM *lwg = lwpoint_as_lwgeom(lwpoint);
188
0
  LWMPOINT *r = lwmpoint_construct_empty(lwgeom_get_srid(lwg), lwgeom_has_z(lwg), lwgeom_has_m(lwg));
189
0
  if (FP_EQUALS(m, point_m))
190
0
  {
191
0
    lwmpoint_add_lwpoint(r, lwpoint_clone(lwpoint));
192
0
  }
193
0
  return r;
194
0
}
195
196
static LWMPOINT *
197
lwmpoint_locate_along(const LWMPOINT *lwin, double m, __attribute__((__unused__)) double offset)
198
0
{
199
0
  LWGEOM *lwg = lwmpoint_as_lwgeom(lwin);
200
0
  LWMPOINT *lwout = NULL;
201
0
  uint32_t i;
202
203
  /* Construct return */
204
0
  lwout = lwmpoint_construct_empty(lwgeom_get_srid(lwg), lwgeom_has_z(lwg), lwgeom_has_m(lwg));
205
206
0
  for (i = 0; i < lwin->ngeoms; i++)
207
0
  {
208
0
    double point_m = lwpoint_get_m(lwin->geoms[i]);
209
0
    if (FP_EQUALS(m, point_m))
210
0
    {
211
0
      lwmpoint_add_lwpoint(lwout, lwpoint_clone(lwin->geoms[i]));
212
0
    }
213
0
  }
214
215
0
  return lwout;
216
0
}
217
218
LWGEOM *
219
lwgeom_locate_along(const LWGEOM *lwin, double m, double offset)
220
0
{
221
0
  if (!lwin)
222
0
    return NULL;
223
224
0
  if (!lwgeom_has_m(lwin))
225
0
    lwerror("Input geometry does not have a measure dimension");
226
227
0
  switch (lwin->type)
228
0
  {
229
0
  case POINTTYPE:
230
0
    return (LWGEOM *)lwpoint_locate_along((LWPOINT *)lwin, m, offset);
231
0
  case MULTIPOINTTYPE:
232
0
    return (LWGEOM *)lwmpoint_locate_along((LWMPOINT *)lwin, m, offset);
233
0
  case LINETYPE:
234
0
    return (LWGEOM *)lwline_locate_along((LWLINE *)lwin, m, offset);
235
0
  case MULTILINETYPE:
236
0
    return (LWGEOM *)lwmline_locate_along((LWMLINE *)lwin, m, offset);
237
  /* Only line types supported right now */
238
  /* TO DO: CurveString, CompoundCurve, MultiCurve */
239
  /* TO DO: Point, MultiPoint */
240
0
  default:
241
0
    lwerror("Only linear geometries are supported, %s provided.", lwtype_name(lwin->type));
242
0
    return NULL;
243
0
  }
244
0
  return NULL;
245
0
}
246
247
/**
248
 * Given a POINT4D and an ordinate number, return
249
 * the value of the ordinate.
250
 * @param p input point
251
 * @param ordinate number (1=x, 2=y, 3=z, 4=m)
252
 * @return d value at that ordinate
253
 */
254
inline double
255
lwpoint_get_ordinate(const POINT4D *p, char ordinate)
256
0
{
257
0
  if (!p)
258
0
  {
259
0
    lwerror("Null input geometry.");
260
0
    return 0.0;
261
0
  }
262
263
0
  switch (ordinate)
264
0
  {
265
0
  case 'X':
266
0
    return p->x;
267
0
  case 'Y':
268
0
    return p->y;
269
0
  case 'Z':
270
0
    return p->z;
271
0
  case 'M':
272
0
    return p->m;
273
0
  }
274
0
  lwerror("Cannot extract %c ordinate.", ordinate);
275
0
  return 0.0;
276
0
}
277
278
/**
279
 * Given a point, ordinate number and value, set that ordinate on the
280
 * point.
281
 */
282
inline void
283
lwpoint_set_ordinate(POINT4D *p, char ordinate, double value)
284
0
{
285
0
  if (!p)
286
0
  {
287
0
    lwerror("Null input geometry.");
288
0
    return;
289
0
  }
290
291
0
  switch (ordinate)
292
0
  {
293
0
  case 'X':
294
0
    p->x = value;
295
0
    return;
296
0
  case 'Y':
297
0
    p->y = value;
298
0
    return;
299
0
  case 'Z':
300
0
    p->z = value;
301
0
    return;
302
0
  case 'M':
303
0
    p->m = value;
304
0
    return;
305
0
  }
306
0
  lwerror("Cannot set %c ordinate.", ordinate);
307
0
  return;
308
0
}
309
310
/**
311
 * Given two points, a dimensionality, an ordinate, and an interpolation value
312
 * generate a new point that is proportionally between the input points,
313
 * using the values in the provided dimension as the scaling factors.
314
 */
315
inline int
316
point_interpolate(const POINT4D *p1,
317
      const POINT4D *p2,
318
      POINT4D *p,
319
      int hasz,
320
      int hasm,
321
      char ordinate,
322
      double interpolation_value)
323
0
{
324
0
  static char *dims = "XYZM";
325
0
  double p1_value = lwpoint_get_ordinate(p1, ordinate);
326
0
  double p2_value = lwpoint_get_ordinate(p2, ordinate);
327
0
  double proportion;
328
0
  int i = 0;
329
330
0
  assert(ordinate == 'X' || ordinate == 'Y' ||
331
0
         ordinate == 'Z' || ordinate == 'M');
332
0
  assert(FP_MIN(p1_value, p2_value) <= interpolation_value &&
333
0
         FP_MAX(p1_value, p2_value) >= interpolation_value);
334
335
0
  proportion = (interpolation_value - p1_value) / (p2_value - p1_value);
336
337
0
  for (i = 0; i < 4; i++)
338
0
  {
339
0
    if (dims[i] == 'Z' && !hasz)
340
0
      continue;
341
0
    if (dims[i] == 'M' && !hasm)
342
0
      continue;
343
0
    if (dims[i] == ordinate)
344
0
      lwpoint_set_ordinate(p, dims[i], interpolation_value);
345
0
    else
346
0
    {
347
0
      double newordinate = 0.0;
348
0
      p1_value = lwpoint_get_ordinate(p1, dims[i]);
349
0
      p2_value = lwpoint_get_ordinate(p2, dims[i]);
350
0
      newordinate = p1_value + proportion * (p2_value - p1_value);
351
0
      lwpoint_set_ordinate(p, dims[i], newordinate);
352
0
    }
353
0
  }
354
355
0
  return LW_SUCCESS;
356
0
}
357
358
/**
359
 * Clip an input POINT between two values, on any ordinate input.
360
 */
361
static inline LWCOLLECTION *
362
lwpoint_clip_to_ordinate_range(const LWPOINT *point, char ordinate, double from, double to)
363
0
{
364
0
  LWCOLLECTION *lwgeom_out = NULL;
365
0
  char hasz, hasm;
366
0
  POINT4D p4d;
367
0
  double ordinate_value;
368
369
  /* Read Z/M info */
370
0
  hasz = lwgeom_has_z(lwpoint_as_lwgeom(point));
371
0
  hasm = lwgeom_has_m(lwpoint_as_lwgeom(point));
372
373
  /* Prepare return object */
374
0
  lwgeom_out = lwcollection_construct_empty(MULTIPOINTTYPE, point->srid, hasz, hasm);
375
376
  /* Test if ordinate is in range */
377
0
  lwpoint_getPoint4d_p(point, &p4d);
378
0
  ordinate_value = lwpoint_get_ordinate(&p4d, ordinate);
379
0
  if (from <= ordinate_value && to >= ordinate_value)
380
0
  {
381
0
    LWPOINT *lwp = lwpoint_clone(point);
382
0
    lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(lwp));
383
0
  }
384
385
0
  return lwgeom_out;
386
0
}
387
388
/**
389
 * Clip an input MULTIPOINT between two values, on any ordinate input.
390
 */
391
static inline LWCOLLECTION *
392
lwmpoint_clip_to_ordinate_range(const LWMPOINT *mpoint, char ordinate, double from, double to)
393
0
{
394
0
  LWCOLLECTION *lwgeom_out = NULL;
395
0
  char hasz, hasm;
396
0
  uint32_t i;
397
398
  /* Read Z/M info */
399
0
  hasz = lwgeom_has_z(lwmpoint_as_lwgeom(mpoint));
400
0
  hasm = lwgeom_has_m(lwmpoint_as_lwgeom(mpoint));
401
402
  /* Prepare return object */
403
0
  lwgeom_out = lwcollection_construct_empty(MULTIPOINTTYPE, mpoint->srid, hasz, hasm);
404
405
  /* For each point, is its ordinate value between from and to? */
406
0
  for (i = 0; i < mpoint->ngeoms; i++)
407
0
  {
408
0
    POINT4D p4d;
409
0
    double ordinate_value;
410
411
0
    lwpoint_getPoint4d_p(mpoint->geoms[i], &p4d);
412
0
    ordinate_value = lwpoint_get_ordinate(&p4d, ordinate);
413
414
0
    if (from <= ordinate_value && to >= ordinate_value)
415
0
    {
416
0
      LWPOINT *lwp = lwpoint_clone(mpoint->geoms[i]);
417
0
      lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(lwp));
418
0
    }
419
0
  }
420
421
  /* Set the bbox, if necessary */
422
0
  if (mpoint->bbox)
423
0
    lwgeom_refresh_bbox((LWGEOM *)lwgeom_out);
424
425
0
  return lwgeom_out;
426
0
}
427
428
static inline POINTARRAY *
429
ptarray_clamp_to_ordinate_range(const POINTARRAY *ipa, char ordinate, double from, double to, uint8_t is_closed)
430
0
{
431
0
  POINT4D p1, p2;
432
0
  POINTARRAY *opa;
433
0
  double ovp1, ovp2;
434
0
  POINT4D *t;
435
0
  int8_t p1out, p2out; /* -1 - smaller than from, 0 - in range, 1 - larger than to */
436
0
  uint32_t i;
437
0
  uint8_t hasz = FLAGS_GET_Z(ipa->flags);
438
0
  uint8_t hasm = FLAGS_GET_M(ipa->flags);
439
440
0
  assert(from <= to);
441
442
0
  t = lwalloc(sizeof(POINT4D));
443
444
  /* Initial storage */
445
0
  opa = ptarray_construct_empty(hasz, hasm, ipa->npoints);
446
447
  /* Add first point */
448
0
  getPoint4d_p(ipa, 0, &p1);
449
0
  ovp1 = lwpoint_get_ordinate(&p1, ordinate);
450
451
0
  p1out = (ovp1 < from) ? -1 : ((ovp1 > to) ? 1 : 0);
452
453
0
  if (from <= ovp1 && ovp1 <= to)
454
0
    ptarray_append_point(opa, &p1, LW_FALSE);
455
456
  /* Loop on all other input points */
457
0
  for (i = 1; i < ipa->npoints; i++)
458
0
  {
459
0
    getPoint4d_p(ipa, i, &p2);
460
0
    ovp2 = lwpoint_get_ordinate(&p2, ordinate);
461
0
    p2out = (ovp2 < from) ? -1 : ((ovp2 > to) ? 1 : 0);
462
463
0
    if (p1out == 0 && p2out == 0) /* both visible */
464
0
    {
465
0
      ptarray_append_point(opa, &p2, LW_FALSE);
466
0
    }
467
0
    else if (p1out == p2out && p1out != 0) /* both invisible on the same side */
468
0
    {
469
      /* skip */
470
0
    }
471
0
    else if (p1out == -1 && p2out == 0)
472
0
    {
473
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from);
474
0
      ptarray_append_point(opa, t, LW_FALSE);
475
0
      ptarray_append_point(opa, &p2, LW_FALSE);
476
0
    }
477
0
    else if (p1out == -1 && p2out == 1)
478
0
    {
479
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from);
480
0
      ptarray_append_point(opa, t, LW_FALSE);
481
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to);
482
0
      ptarray_append_point(opa, t, LW_FALSE);
483
0
    }
484
0
    else if (p1out == 0 && p2out == -1)
485
0
    {
486
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from);
487
0
      ptarray_append_point(opa, t, LW_FALSE);
488
0
    }
489
0
    else if (p1out == 0 && p2out == 1)
490
0
    {
491
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to);
492
0
      ptarray_append_point(opa, t, LW_FALSE);
493
0
    }
494
0
    else if (p1out == 1 && p2out == -1)
495
0
    {
496
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to);
497
0
      ptarray_append_point(opa, t, LW_FALSE);
498
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from);
499
0
      ptarray_append_point(opa, t, LW_FALSE);
500
0
    }
501
0
    else if (p1out == 1 && p2out == 0)
502
0
    {
503
0
      point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to);
504
0
      ptarray_append_point(opa, t, LW_FALSE);
505
0
      ptarray_append_point(opa, &p2, LW_FALSE);
506
0
    }
507
508
0
    p1 = p2;
509
0
    p1out = p2out;
510
0
    LW_ON_INTERRUPT(ptarray_free(opa); return NULL);
511
0
  }
512
513
0
  if (is_closed && opa->npoints > 2)
514
0
  {
515
0
    getPoint4d_p(opa, 0, &p1);
516
0
    ptarray_append_point(opa, &p1, LW_FALSE);
517
0
  }
518
0
  lwfree(t);
519
520
0
  return opa;
521
0
}
522
523
/**
524
 * Take in a LINESTRING and return a MULTILINESTRING of those portions of the
525
 * LINESTRING between the from/to range for the specified ordinate (XYZM)
526
 */
527
static inline LWCOLLECTION *
528
lwline_clip_to_ordinate_range(const LWLINE *line, char ordinate, double from, double to)
529
0
{
530
0
  POINTARRAY *pa_in = NULL;
531
0
  LWCOLLECTION *lwgeom_out = NULL;
532
0
  POINTARRAY *dp = NULL;
533
0
  uint32_t i;
534
0
  int added_last_point = 0;
535
0
  POINT4D *p = NULL, *q = NULL, *r = NULL;
536
0
  double ordinate_value_p = 0.0, ordinate_value_q = 0.0;
537
0
  char hasz, hasm;
538
0
  char dims;
539
540
  /* Null input, nothing we can do. */
541
0
  assert(line);
542
0
  hasz = lwgeom_has_z(lwline_as_lwgeom(line));
543
0
  hasm = lwgeom_has_m(lwline_as_lwgeom(line));
544
0
  dims = FLAGS_NDIMS(line->flags);
545
546
  /* Asking for an ordinate we don't have. Error. */
547
0
  if ((ordinate == 'Z' && !hasz) || (ordinate == 'M' && !hasm))
548
0
  {
549
0
    lwerror("Cannot clip on ordinate %d in a %d-d geometry.", ordinate, dims);
550
0
    return NULL;
551
0
  }
552
553
  /* Prepare our working point objects. */
554
0
  p = lwalloc(sizeof(POINT4D));
555
0
  q = lwalloc(sizeof(POINT4D));
556
0
  r = lwalloc(sizeof(POINT4D));
557
558
  /* Construct a collection to hold our outputs. */
559
0
  lwgeom_out = lwcollection_construct_empty(MULTILINETYPE, line->srid, hasz, hasm);
560
561
  /* Get our input point array */
562
0
  pa_in = line->points;
563
564
0
  for (i = 0; i < pa_in->npoints; i++)
565
0
  {
566
0
    if (i > 0)
567
0
    {
568
0
      *q = *p;
569
0
      ordinate_value_q = ordinate_value_p;
570
0
    }
571
0
    getPoint4d_p(pa_in, i, p);
572
0
    ordinate_value_p = lwpoint_get_ordinate(p, ordinate);
573
574
    /* Is this point inside the ordinate range? Yes. */
575
0
    if (ordinate_value_p >= from && ordinate_value_p <= to)
576
0
    {
577
578
0
      if (!added_last_point)
579
0
      {
580
        /* We didn't add the previous point, so this is a new segment.
581
         *  Make a new point array. */
582
0
        dp = ptarray_construct_empty(hasz, hasm, 32);
583
584
        /* We're transiting into the range so add an interpolated
585
         *  point at the range boundary.
586
         *  If we're on a boundary and crossing from the far side,
587
         *  we also need an interpolated point. */
588
0
        if (i > 0 &&
589
0
            (/* Don't try to interpolate if this is the first point */
590
0
             (ordinate_value_p > from && ordinate_value_p < to) ||  /* Inside */
591
0
             (ordinate_value_p == from && ordinate_value_q > to) || /* Hopping from above */
592
0
             (ordinate_value_p == to && ordinate_value_q < from)))  /* Hopping from below */
593
0
        {
594
0
          double interpolation_value;
595
0
          (ordinate_value_q > to) ? (interpolation_value = to)
596
0
                : (interpolation_value = from);
597
0
          point_interpolate(q, p, r, hasz, hasm, ordinate, interpolation_value);
598
0
          ptarray_append_point(dp, r, LW_FALSE);
599
0
        }
600
0
      }
601
      /* Add the current vertex to the point array. */
602
0
      ptarray_append_point(dp, p, LW_FALSE);
603
0
      if (ordinate_value_p == from || ordinate_value_p == to)
604
0
        added_last_point = 2; /* Added on boundary. */
605
0
      else
606
0
        added_last_point = 1; /* Added inside range. */
607
0
    }
608
    /* Is this point inside the ordinate range? No. */
609
0
    else
610
0
    {
611
0
      if (added_last_point == 1)
612
0
      {
613
        /* We're transiting out of the range, so add an interpolated point
614
         *  to the point array at the range boundary. */
615
0
        double interpolation_value;
616
0
        (ordinate_value_p > to) ? (interpolation_value = to) : (interpolation_value = from);
617
0
        point_interpolate(q, p, r, hasz, hasm, ordinate, interpolation_value);
618
0
        ptarray_append_point(dp, r, LW_FALSE);
619
0
      }
620
0
      else if (added_last_point == 2)
621
0
      {
622
        /* We're out and the last point was on the boundary.
623
         *  If the last point was the near boundary, nothing to do.
624
         *  If it was the far boundary, we need an interpolated point. */
625
0
        if (from != to && ((ordinate_value_q == from && ordinate_value_p > from) ||
626
0
               (ordinate_value_q == to && ordinate_value_p < to)))
627
0
        {
628
0
          double interpolation_value;
629
0
          (ordinate_value_p > to) ? (interpolation_value = to)
630
0
                : (interpolation_value = from);
631
0
          point_interpolate(q, p, r, hasz, hasm, ordinate, interpolation_value);
632
0
          ptarray_append_point(dp, r, LW_FALSE);
633
0
        }
634
0
      }
635
0
      else if (i && ordinate_value_q < from && ordinate_value_p > to)
636
0
      {
637
        /* We just hopped over the whole range, from bottom to top,
638
         *  so we need to add *two* interpolated points! */
639
0
        dp = ptarray_construct(hasz, hasm, 2);
640
        /* Interpolate lower point. */
641
0
        point_interpolate(p, q, r, hasz, hasm, ordinate, from);
642
0
        ptarray_set_point4d(dp, 0, r);
643
        /* Interpolate upper point. */
644
0
        point_interpolate(p, q, r, hasz, hasm, ordinate, to);
645
0
        ptarray_set_point4d(dp, 1, r);
646
0
      }
647
0
      else if (i && ordinate_value_q > to && ordinate_value_p < from)
648
0
      {
649
        /* We just hopped over the whole range, from top to bottom,
650
         *  so we need to add *two* interpolated points! */
651
0
        dp = ptarray_construct(hasz, hasm, 2);
652
        /* Interpolate upper point. */
653
0
        point_interpolate(p, q, r, hasz, hasm, ordinate, to);
654
0
        ptarray_set_point4d(dp, 0, r);
655
        /* Interpolate lower point. */
656
0
        point_interpolate(p, q, r, hasz, hasm, ordinate, from);
657
0
        ptarray_set_point4d(dp, 1, r);
658
0
      }
659
      /* We have an extant point-array, save it out to a multi-line. */
660
0
      if (dp)
661
0
      {
662
        /* Only one point, so we have to make an lwpoint to hold this
663
         *  and set the overall output type to a generic collection. */
664
0
        if (dp->npoints == 1)
665
0
        {
666
0
          LWPOINT *opoint = lwpoint_construct(line->srid, NULL, dp);
667
0
          lwgeom_out->type = COLLECTIONTYPE;
668
0
          lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(opoint));
669
0
        }
670
0
        else
671
0
        {
672
0
          LWLINE *oline = lwline_construct(line->srid, NULL, dp);
673
0
          lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwline_as_lwgeom(oline));
674
0
        }
675
676
        /* Pointarray is now owned by lwgeom_out, so drop reference to it */
677
0
        dp = NULL;
678
0
      }
679
0
      added_last_point = 0;
680
0
    }
681
0
  }
682
683
  /* Still some points left to be saved out. */
684
0
  if (dp)
685
0
  {
686
0
    if (dp->npoints == 1)
687
0
    {
688
0
      LWPOINT *opoint = lwpoint_construct(line->srid, NULL, dp);
689
0
      lwgeom_out->type = COLLECTIONTYPE;
690
0
      lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(opoint));
691
0
    }
692
0
    else if (dp->npoints > 1)
693
0
    {
694
0
      LWLINE *oline = lwline_construct(line->srid, NULL, dp);
695
0
      lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwline_as_lwgeom(oline));
696
0
    }
697
0
    else
698
0
      ptarray_free(dp);
699
0
  }
700
701
0
  lwfree(p);
702
0
  lwfree(q);
703
0
  lwfree(r);
704
705
0
  if (line->bbox && lwgeom_out->ngeoms > 0)
706
0
    lwgeom_refresh_bbox((LWGEOM *)lwgeom_out);
707
708
0
  return lwgeom_out;
709
0
}
710
711
/**
712
 * Clip an input LWPOLY between two values, on any ordinate input.
713
 */
714
static inline LWCOLLECTION *
715
lwpoly_clip_to_ordinate_range(const LWPOLY *poly, char ordinate, double from, double to)
716
0
{
717
0
  assert(poly);
718
0
  char hasz = FLAGS_GET_Z(poly->flags), hasm = FLAGS_GET_M(poly->flags);
719
0
  LWPOLY *poly_res = lwpoly_construct_empty(poly->srid, hasz, hasm);
720
0
  LWCOLLECTION *lwgeom_out = lwcollection_construct_empty(MULTIPOLYGONTYPE, poly->srid, hasz, hasm);
721
722
0
  for (uint32_t i = 0; i < poly->nrings; i++)
723
0
  {
724
    /* Ret number of points */
725
0
    POINTARRAY *pa = ptarray_clamp_to_ordinate_range(poly->rings[i], ordinate, from, to, LW_TRUE);
726
727
0
    if (!pa)
728
0
      return NULL;
729
730
0
    if (pa->npoints >= 4)
731
0
      lwpoly_add_ring(poly_res, pa);
732
0
    else
733
0
    {
734
0
      ptarray_free(pa);
735
0
      if (i == 0)
736
0
        break;
737
0
    }
738
0
  }
739
0
  if (poly_res->nrings > 0)
740
0
    lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, (LWGEOM *)poly_res);
741
0
  else
742
0
    lwpoly_free(poly_res);
743
744
0
  return lwgeom_out;
745
0
}
746
747
/**
748
 * Clip an input LWTRIANGLE between two values, on any ordinate input.
749
 */
750
static inline LWCOLLECTION *
751
lwtriangle_clip_to_ordinate_range(const LWTRIANGLE *tri, char ordinate, double from, double to)
752
0
{
753
0
  assert(tri);
754
0
  char hasz = FLAGS_GET_Z(tri->flags), hasm = FLAGS_GET_M(tri->flags);
755
0
  LWCOLLECTION *lwgeom_out = lwcollection_construct_empty(TINTYPE, tri->srid, hasz, hasm);
756
0
  POINTARRAY *pa = ptarray_clamp_to_ordinate_range(tri->points, ordinate, from, to, LW_TRUE);
757
758
0
  if (!pa)
759
0
    return NULL;
760
761
0
  if (pa->npoints >= 4)
762
0
  {
763
0
    POINT4D first = getPoint4d(pa, 0);
764
0
    for (uint32_t i = 1; i < pa->npoints - 2; i++)
765
0
    {
766
0
      POINT4D p;
767
0
      POINTARRAY *tpa = ptarray_construct_empty(hasz, hasm, 4);
768
0
      ptarray_append_point(tpa, &first, LW_TRUE);
769
0
      getPoint4d_p(pa, i, &p);
770
0
      ptarray_append_point(tpa, &p, LW_TRUE);
771
0
      getPoint4d_p(pa, i + 1, &p);
772
0
      ptarray_append_point(tpa, &p, LW_TRUE);
773
0
      ptarray_append_point(tpa, &first, LW_TRUE);
774
0
      LWTRIANGLE *otri = lwtriangle_construct(tri->srid, NULL, tpa);
775
0
      lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, (LWGEOM *)otri);
776
0
    }
777
0
  }
778
0
  ptarray_free(pa);
779
0
  return lwgeom_out;
780
0
}
781
782
/**
783
 * Clip an input COLLECTION between two values, on any ordinate input.
784
 */
785
static inline LWCOLLECTION *
786
lwcollection_clip_to_ordinate_range(const LWCOLLECTION *icol, char ordinate, double from, double to)
787
0
{
788
0
  LWCOLLECTION *lwgeom_out;
789
790
0
  assert(icol);
791
0
  if (icol->ngeoms == 1)
792
0
    lwgeom_out = lwgeom_clip_to_ordinate_range(icol->geoms[0], ordinate, from, to, 0);
793
0
  else
794
0
  {
795
0
    LWCOLLECTION *col;
796
0
    char hasz = lwgeom_has_z(lwcollection_as_lwgeom(icol));
797
0
    char hasm = lwgeom_has_m(lwcollection_as_lwgeom(icol));
798
0
    uint32_t i;
799
0
    lwgeom_out = lwcollection_construct_empty(icol->type, icol->srid, hasz, hasm);
800
0
    FLAGS_SET_Z(lwgeom_out->flags, hasz);
801
0
    FLAGS_SET_M(lwgeom_out->flags, hasm);
802
0
    for (i = 0; i < icol->ngeoms; i++)
803
0
    {
804
0
      col = lwgeom_clip_to_ordinate_range(icol->geoms[i], ordinate, from, to, 0);
805
0
      if (col)
806
0
      {
807
0
        if (col->type != icol->type)
808
0
          lwgeom_out->type = COLLECTIONTYPE;
809
0
        lwgeom_out = lwcollection_concat_in_place(lwgeom_out, col);
810
0
        lwfree(col->geoms);
811
0
        lwcollection_release(col);
812
0
      }
813
0
    }
814
0
  }
815
816
0
  if (icol->bbox)
817
0
    lwgeom_refresh_bbox((LWGEOM *)lwgeom_out);
818
819
0
  return lwgeom_out;
820
0
}
821
822
LWCOLLECTION *
823
lwgeom_clip_to_ordinate_range(const LWGEOM *lwin, char ordinate, double from, double to, double offset)
824
0
{
825
0
  LWCOLLECTION *out_col;
826
0
  LWCOLLECTION *out_offset;
827
0
  uint32_t i;
828
829
  /* Ensure 'from' is less than 'to'. */
830
0
  if (to < from)
831
0
  {
832
0
    double t = from;
833
0
    from = to;
834
0
    to = t;
835
0
  }
836
837
0
  if (!lwin)
838
0
    lwerror("lwgeom_clip_to_ordinate_range: null input geometry!");
839
840
0
  switch (lwin->type)
841
0
  {
842
0
  case LINETYPE:
843
0
    out_col = lwline_clip_to_ordinate_range((LWLINE *)lwin, ordinate, from, to);
844
0
    break;
845
0
  case MULTIPOINTTYPE:
846
0
    out_col = lwmpoint_clip_to_ordinate_range((LWMPOINT *)lwin, ordinate, from, to);
847
0
    break;
848
0
  case POINTTYPE:
849
0
    out_col = lwpoint_clip_to_ordinate_range((LWPOINT *)lwin, ordinate, from, to);
850
0
    break;
851
0
  case POLYGONTYPE:
852
0
    out_col = lwpoly_clip_to_ordinate_range((LWPOLY *)lwin, ordinate, from, to);
853
0
    break;
854
0
  case TRIANGLETYPE:
855
0
    out_col = lwtriangle_clip_to_ordinate_range((LWTRIANGLE *)lwin, ordinate, from, to);
856
0
    break;
857
0
  case TINTYPE:
858
0
  case MULTILINETYPE:
859
0
  case MULTIPOLYGONTYPE:
860
0
  case COLLECTIONTYPE:
861
0
  case POLYHEDRALSURFACETYPE:
862
0
    out_col = lwcollection_clip_to_ordinate_range((LWCOLLECTION *)lwin, ordinate, from, to);
863
0
    break;
864
0
  default:
865
0
    lwerror("This function does not accept %s geometries.", lwtype_name(lwin->type));
866
0
    return NULL;
867
0
  }
868
869
  /* Stop if result is NULL */
870
0
  if (!out_col)
871
0
    lwerror("lwgeom_clip_to_ordinate_range clipping routine returned NULL");
872
873
  /* Return if we aren't going to offset the result */
874
0
  if (FP_IS_ZERO(offset) || lwgeom_is_empty(lwcollection_as_lwgeom(out_col)))
875
0
    return out_col;
876
877
  /* Construct a collection to hold our outputs. */
878
  /* Things get ugly: GEOS offset drops Z's and M's so we have to drop ours */
879
0
  out_offset = lwcollection_construct_empty(MULTILINETYPE, lwin->srid, 0, 0);
880
881
  /* Try and offset the linear portions of the return value */
882
0
  for (i = 0; i < out_col->ngeoms; i++)
883
0
  {
884
0
    int type = out_col->geoms[i]->type;
885
0
    if (type == POINTTYPE)
886
0
    {
887
0
      lwnotice("lwgeom_clip_to_ordinate_range cannot offset a clipped point");
888
0
      continue;
889
0
    }
890
0
    else if (type == LINETYPE)
891
0
    {
892
      /* lwgeom_offsetcurve(line, offset, quadsegs, joinstyle (round), mitrelimit) */
893
0
      LWGEOM *lwoff = lwgeom_offsetcurve(out_col->geoms[i], offset, 8, 1, 5.0);
894
0
      if (!lwoff)
895
0
      {
896
0
        lwerror("lwgeom_offsetcurve returned null");
897
0
      }
898
0
      lwcollection_add_lwgeom(out_offset, lwoff);
899
0
    }
900
0
    else
901
0
    {
902
0
      lwerror("lwgeom_clip_to_ordinate_range found an unexpected type (%s) in the offset routine",
903
0
        lwtype_name(type));
904
0
    }
905
0
  }
906
907
0
  return out_offset;
908
0
}
909
910
LWCOLLECTION *
911
lwgeom_locate_between(const LWGEOM *lwin, double from, double to, double offset)
912
0
{
913
0
  if (!lwgeom_has_m(lwin))
914
0
    lwerror("Input geometry does not have a measure dimension");
915
916
0
  return lwgeom_clip_to_ordinate_range(lwin, 'M', from, to, offset);
917
0
}
918
919
double
920
lwgeom_interpolate_point(const LWGEOM *lwin, const LWPOINT *lwpt)
921
0
{
922
0
  POINT4D p, p_proj;
923
0
  double ret = 0.0;
924
925
0
  if (!lwin)
926
0
    lwerror("lwgeom_interpolate_point: null input geometry!");
927
928
0
  if (!lwgeom_has_m(lwin))
929
0
    lwerror("Input geometry does not have a measure dimension");
930
931
0
  if (lwgeom_is_empty(lwin) || lwpoint_is_empty(lwpt))
932
0
    lwerror("Input geometry is empty");
933
934
0
  switch (lwin->type)
935
0
  {
936
0
  case LINETYPE:
937
0
  {
938
0
    LWLINE *lwline = lwgeom_as_lwline(lwin);
939
0
    lwpoint_getPoint4d_p(lwpt, &p);
940
0
    ret = ptarray_locate_point(lwline->points, &p, NULL, &p_proj);
941
0
    ret = p_proj.m;
942
0
    break;
943
0
  }
944
0
  default:
945
0
    lwerror("This function does not accept %s geometries.", lwtype_name(lwin->type));
946
0
  }
947
0
  return ret;
948
0
}
949
950
double
951
lwgeom_interpolate_point_3d(const LWGEOM *lwin, const LWPOINT *lwpt)
952
0
{
953
0
  POINT4D p, A, B, closest;
954
0
  double mindist2 = DBL_MAX;
955
0
  double best_m = 0.0;
956
0
  uint32_t i;
957
958
0
  if (!lwin)
959
0
    lwerror("lwgeom_interpolate_point_3d: null input geometry!");
960
961
0
  if (!lwgeom_has_m(lwin))
962
0
    lwerror("Input geometry does not have a measure dimension");
963
964
0
  if (!lwgeom_has_z(lwin))
965
0
    lwerror("Input geometry does not have a Z dimension");
966
967
0
  if (lwgeom_is_empty(lwin) || lwpoint_is_empty(lwpt))
968
0
    lwerror("Input geometry is empty");
969
970
0
  switch (lwin->type)
971
0
  {
972
0
  case LINETYPE:
973
0
  {
974
0
    LWLINE *lwline = lwgeom_as_lwline(lwin);
975
0
    POINTARRAY *pa = lwline->points;
976
977
0
    lwpoint_getPoint4d_p(lwpt, &p);
978
979
0
    if (pa->npoints == 1)
980
0
    {
981
0
      getPoint4d_p(pa, 0, &closest);
982
0
      return closest.m;
983
0
    }
984
985
0
    for (i = 0; i < pa->npoints - 1; i++)
986
0
    {
987
0
      double dx, dy, dz, len2, r;
988
0
      double cx, cy, cz, d2;
989
990
0
      getPoint4d_p(pa, i, &A);
991
0
      getPoint4d_p(pa, i + 1, &B);
992
993
0
      dx = B.x - A.x;
994
0
      dy = B.y - A.y;
995
0
      dz = B.z - A.z;
996
0
      len2 = dx * dx + dy * dy + dz * dz;
997
998
0
      if (len2 == 0.0)
999
0
      {
1000
        /* Degenerate segment: distance to point A */
1001
0
        r = 0.0;
1002
0
      }
1003
0
      else
1004
0
      {
1005
0
        r = ((p.x - A.x) * dx + (p.y - A.y) * dy + (p.z - A.z) * dz) / len2;
1006
0
        if (r < 0.0) r = 0.0;
1007
0
        if (r > 1.0) r = 1.0;
1008
0
      }
1009
1010
0
      cx = A.x + r * dx;
1011
0
      cy = A.y + r * dy;
1012
0
      cz = A.z + r * dz;
1013
1014
0
      d2 = (p.x - cx) * (p.x - cx) + (p.y - cy) * (p.y - cy) + (p.z - cz) * (p.z - cz);
1015
1016
0
      if (d2 < mindist2)
1017
0
      {
1018
0
        mindist2 = d2;
1019
0
        best_m = A.m + r * (B.m - A.m);
1020
0
      }
1021
0
    }
1022
0
    break;
1023
0
  }
1024
0
  default:
1025
0
    lwerror("This function does not accept %s geometries.", lwtype_name(lwin->type));
1026
0
  }
1027
0
  return best_m;
1028
0
}
1029
1030
/*
1031
 * Time of closest point of approach
1032
 *
1033
 * Given two vectors (p1-p2 and q1-q2) and
1034
 * a time range (t1-t2) return the time in which
1035
 * a point p is closest to a point q on their
1036
 * respective vectors, and the actual points
1037
 *
1038
 * Here we use algorithm from softsurfer.com
1039
 * that can be found here
1040
 * http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm
1041
 *
1042
 * @param p0 start of first segment, will be set to actual
1043
 *           closest point of approach on segment.
1044
 * @param p1 end of first segment
1045
 * @param q0 start of second segment, will be set to actual
1046
 *           closest point of approach on segment.
1047
 * @param q1 end of second segment
1048
 * @param t0 start of travel time
1049
 * @param t1 end of travel time
1050
 *
1051
 * @return time of closest point of approach
1052
 *
1053
 */
1054
static double
1055
segments_tcpa(POINT4D *p0, const POINT4D *p1, POINT4D *q0, const POINT4D *q1, double t0, double t1)
1056
0
{
1057
0
  POINT3DZ pv; /* velocity of p, aka u */
1058
0
  POINT3DZ qv; /* velocity of q, aka v */
1059
0
  POINT3DZ dv; /* velocity difference */
1060
0
  POINT3DZ w0; /* vector between first points */
1061
1062
  /*
1063
    lwnotice("FROM %g,%g,%g,%g -- %g,%g,%g,%g",
1064
      p0->x, p0->y, p0->z, p0->m,
1065
      p1->x, p1->y, p1->z, p1->m);
1066
    lwnotice("  TO %g,%g,%g,%g -- %g,%g,%g,%g",
1067
      q0->x, q0->y, q0->z, q0->m,
1068
      q1->x, q1->y, q1->z, q1->m);
1069
  */
1070
1071
  /* PV aka U */
1072
0
  pv.x = (p1->x - p0->x);
1073
0
  pv.y = (p1->y - p0->y);
1074
0
  pv.z = (p1->z - p0->z);
1075
  /*lwnotice("PV:  %g, %g, %g", pv.x, pv.y, pv.z);*/
1076
1077
  /* QV aka V */
1078
0
  qv.x = (q1->x - q0->x);
1079
0
  qv.y = (q1->y - q0->y);
1080
0
  qv.z = (q1->z - q0->z);
1081
  /*lwnotice("QV:  %g, %g, %g", qv.x, qv.y, qv.z);*/
1082
1083
0
  dv.x = pv.x - qv.x;
1084
0
  dv.y = pv.y - qv.y;
1085
0
  dv.z = pv.z - qv.z;
1086
  /*lwnotice("DV:  %g, %g, %g", dv.x, dv.y, dv.z);*/
1087
1088
0
  double dv2 = DOT(dv, dv);
1089
  /*lwnotice("DOT: %g", dv2);*/
1090
1091
0
  if (dv2 == 0.0)
1092
0
  {
1093
    /* Distance is the same at any time, we pick the earliest */
1094
0
    return t0;
1095
0
  }
1096
1097
  /* Distance at any given time, with t0 */
1098
0
  w0.x = (p0->x - q0->x);
1099
0
  w0.y = (p0->y - q0->y);
1100
0
  w0.z = (p0->z - q0->z);
1101
1102
  /*lwnotice("W0:  %g, %g, %g", w0.x, w0.y, w0.z);*/
1103
1104
  /* Check that at distance dt w0 is distance */
1105
1106
  /* This is the fraction of measure difference */
1107
0
  double t = -DOT(w0, dv) / dv2;
1108
  /*lwnotice("CLOSEST TIME (fraction): %g", t);*/
1109
1110
0
  if (t > 1.0)
1111
0
  {
1112
    /* Getting closer as we move to the end */
1113
    /*lwnotice("Converging");*/
1114
0
    t = 1;
1115
0
  }
1116
0
  else if (t < 0.0)
1117
0
  {
1118
    /*lwnotice("Diverging");*/
1119
0
    t = 0;
1120
0
  }
1121
1122
  /* Interpolate the actual points now */
1123
1124
0
  p0->x += pv.x * t;
1125
0
  p0->y += pv.y * t;
1126
0
  p0->z += pv.z * t;
1127
1128
0
  q0->x += qv.x * t;
1129
0
  q0->y += qv.y * t;
1130
0
  q0->z += qv.z * t;
1131
1132
0
  t = t0 + (t1 - t0) * t;
1133
  /*lwnotice("CLOSEST TIME (real): %g", t);*/
1134
1135
0
  return t;
1136
0
}
1137
1138
static int
1139
ptarray_collect_mvals(const POINTARRAY *pa, double tmin, double tmax, double *mvals)
1140
0
{
1141
0
  POINT4D pbuf;
1142
0
  uint32_t i, n = 0;
1143
0
  for (i = 0; i < pa->npoints; ++i)
1144
0
  {
1145
0
    getPoint4d_p(pa, i, &pbuf); /* could be optimized */
1146
0
    if (pbuf.m >= tmin && pbuf.m <= tmax)
1147
0
      mvals[n++] = pbuf.m;
1148
0
  }
1149
0
  return n;
1150
0
}
1151
1152
static int
1153
compare_double(const void *pa, const void *pb)
1154
0
{
1155
0
  double a = *((double *)pa);
1156
0
  double b = *((double *)pb);
1157
0
  if (a < b)
1158
0
    return -1;
1159
0
  else if (a > b)
1160
0
    return 1;
1161
0
  else
1162
0
    return 0;
1163
0
}
1164
1165
/* Return number of elements in unique array */
1166
static int
1167
uniq(double *vals, int nvals)
1168
0
{
1169
0
  int i, last = 0;
1170
0
  for (i = 1; i < nvals; ++i)
1171
0
  {
1172
    // lwnotice("(I%d):%g", i, vals[i]);
1173
0
    if (vals[i] != vals[last])
1174
0
    {
1175
0
      vals[++last] = vals[i];
1176
      // lwnotice("(O%d):%g", last, vals[last]);
1177
0
    }
1178
0
  }
1179
0
  return last + 1;
1180
0
}
1181
1182
/*
1183
 * Find point at a given measure
1184
 *
1185
 * The function assumes measures are linear so that always a single point
1186
 * is returned for a single measure.
1187
 *
1188
 * @param pa the point array to perform search on
1189
 * @param m the measure to search for
1190
 * @param p the point to write result into
1191
 * @param from the segment number to start from
1192
 *
1193
 * @return the segment number the point was found into
1194
 *         or -1 if given measure was out of the known range.
1195
 */
1196
static int
1197
ptarray_locate_along_linear(const POINTARRAY *pa, double m, POINT4D *p, uint32_t from)
1198
0
{
1199
0
  uint32_t i = from;
1200
0
  POINT4D p1, p2;
1201
1202
  /* Walk through each segment in the point array */
1203
0
  getPoint4d_p(pa, i, &p1);
1204
0
  for (i = from + 1; i < pa->npoints; i++)
1205
0
  {
1206
0
    getPoint4d_p(pa, i, &p2);
1207
1208
0
    if (segment_locate_along(&p1, &p2, m, 0, p) == LW_TRUE)
1209
0
      return i - 1; /* found */
1210
1211
0
    p1 = p2;
1212
0
  }
1213
1214
0
  return -1; /* not found */
1215
0
}
1216
1217
double
1218
lwgeom_tcpa(const LWGEOM *g1, const LWGEOM *g2, double *mindist)
1219
0
{
1220
0
  LWLINE *l1, *l2;
1221
0
  int i;
1222
0
  GBOX gbox1, gbox2;
1223
0
  double tmin, tmax;
1224
0
  double *mvals;
1225
0
  int nmvals = 0;
1226
0
  double mintime;
1227
0
  double mindist2 = FLT_MAX; /* minimum distance, squared */
1228
1229
0
  if (!lwgeom_has_m(g1) || !lwgeom_has_m(g2))
1230
0
  {
1231
0
    lwerror("Both input geometries must have a measure dimension");
1232
0
    return -1;
1233
0
  }
1234
1235
0
  l1 = lwgeom_as_lwline(g1);
1236
0
  l2 = lwgeom_as_lwline(g2);
1237
1238
0
  if (!l1 || !l2)
1239
0
  {
1240
0
    lwerror("Both input geometries must be linestrings");
1241
0
    return -1;
1242
0
  }
1243
1244
0
  if (l1->points->npoints < 2 || l2->points->npoints < 2)
1245
0
  {
1246
0
    lwerror("Both input lines must have at least 2 points");
1247
0
    return -1;
1248
0
  }
1249
1250
  /* We use lwgeom_calculate_gbox() instead of lwgeom_get_gbox() */
1251
  /* because we cannot afford the float rounding inaccuracy when */
1252
  /* we compare the ranges for overlap below */
1253
0
  lwgeom_calculate_gbox(g1, &gbox1);
1254
0
  lwgeom_calculate_gbox(g2, &gbox2);
1255
1256
  /*
1257
   * Find overlapping M range
1258
   * WARNING: may be larger than the real one
1259
   */
1260
1261
0
  tmin = FP_MAX(gbox1.mmin, gbox2.mmin);
1262
0
  tmax = FP_MIN(gbox1.mmax, gbox2.mmax);
1263
1264
0
  if (tmax < tmin)
1265
0
  {
1266
0
    LWDEBUG(1, "Inputs never exist at the same time");
1267
0
    return -2;
1268
0
  }
1269
1270
  // lwnotice("Min:%g, Max:%g", tmin, tmax);
1271
1272
  /*
1273
   * Collect M values in common time range from inputs
1274
   */
1275
1276
0
  mvals = lwalloc(sizeof(double) * (l1->points->npoints + l2->points->npoints));
1277
1278
  /* TODO: also clip the lines ? */
1279
0
  nmvals = ptarray_collect_mvals(l1->points, tmin, tmax, mvals);
1280
0
  nmvals += ptarray_collect_mvals(l2->points, tmin, tmax, mvals + nmvals);
1281
1282
  /* Sort values in ascending order */
1283
0
  qsort(mvals, nmvals, sizeof(double), compare_double);
1284
1285
  /* Remove duplicated values */
1286
0
  nmvals = uniq(mvals, nmvals);
1287
1288
0
  if (nmvals < 2)
1289
0
  {
1290
0
    {
1291
      /* there's a single time, must be that one... */
1292
0
      double t0 = mvals[0];
1293
0
      POINT4D p0, p1;
1294
0
      LWDEBUGF(1, "Inputs only exist both at a single time (%g)", t0);
1295
0
      if (mindist)
1296
0
      {
1297
0
        if (-1 == ptarray_locate_along_linear(l1->points, t0, &p0, 0))
1298
0
        {
1299
0
          lwfree(mvals);
1300
0
          lwerror("Could not find point with M=%g on first geom", t0);
1301
0
          return -1;
1302
0
        }
1303
0
        if (-1 == ptarray_locate_along_linear(l2->points, t0, &p1, 0))
1304
0
        {
1305
0
          lwfree(mvals);
1306
0
          lwerror("Could not find point with M=%g on second geom", t0);
1307
0
          return -1;
1308
0
        }
1309
0
        *mindist = distance3d_pt_pt((POINT3D *)&p0, (POINT3D *)&p1);
1310
0
      }
1311
0
      lwfree(mvals);
1312
0
      return t0;
1313
0
    }
1314
0
  }
1315
1316
  /*
1317
   * For each consecutive pair of measures, compute time of closest point
1318
   * approach and actual distance between points at that time
1319
   */
1320
0
  mintime = tmin;
1321
0
  for (i = 1; i < nmvals; ++i)
1322
0
  {
1323
0
    double t0 = mvals[i - 1];
1324
0
    double t1 = mvals[i];
1325
0
    double t;
1326
0
    POINT4D p0, p1, q0, q1;
1327
0
    int seg;
1328
0
    double dist2;
1329
1330
    // lwnotice("T %g-%g", t0, t1);
1331
1332
0
    seg = ptarray_locate_along_linear(l1->points, t0, &p0, 0);
1333
0
    if (-1 == seg)
1334
0
      continue; /* possible, if GBOX is approximated */
1335
    // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z);
1336
1337
0
    seg = ptarray_locate_along_linear(l1->points, t1, &p1, seg);
1338
0
    if (-1 == seg)
1339
0
      continue; /* possible, if GBOX is approximated */
1340
    // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z);
1341
1342
0
    seg = ptarray_locate_along_linear(l2->points, t0, &q0, 0);
1343
0
    if (-1 == seg)
1344
0
      continue; /* possible, if GBOX is approximated */
1345
    // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z);
1346
1347
0
    seg = ptarray_locate_along_linear(l2->points, t1, &q1, seg);
1348
0
    if (-1 == seg)
1349
0
      continue; /* possible, if GBOX is approximated */
1350
    // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z);
1351
1352
0
    t = segments_tcpa(&p0, &p1, &q0, &q1, t0, t1);
1353
1354
    /*
1355
    lwnotice("Closest points: %g,%g,%g and %g,%g,%g at time %g",
1356
    p0.x, p0.y, p0.z,
1357
    q0.x, q0.y, q0.z, t);
1358
    */
1359
1360
0
    dist2 = (q0.x - p0.x) * (q0.x - p0.x) + (q0.y - p0.y) * (q0.y - p0.y) + (q0.z - p0.z) * (q0.z - p0.z);
1361
0
    if (dist2 < mindist2)
1362
0
    {
1363
0
      mindist2 = dist2;
1364
0
      mintime = t;
1365
      // lwnotice("MINTIME: %g", mintime);
1366
0
    }
1367
0
  }
1368
1369
  /*
1370
   * Release memory
1371
   */
1372
1373
0
  lwfree(mvals);
1374
1375
0
  if (mindist)
1376
0
  {
1377
0
    *mindist = sqrt(mindist2);
1378
0
  }
1379
  /*lwnotice("MINDIST: %g", sqrt(mindist2));*/
1380
1381
0
  return mintime;
1382
0
}
1383
1384
int
1385
lwgeom_cpa_within(const LWGEOM *g1, const LWGEOM *g2, double maxdist)
1386
0
{
1387
0
  LWLINE *l1, *l2;
1388
0
  int i;
1389
0
  GBOX gbox1, gbox2;
1390
0
  double tmin, tmax;
1391
0
  double *mvals;
1392
0
  int nmvals = 0;
1393
0
  double maxdist2 = maxdist * maxdist;
1394
0
  int within = LW_FALSE;
1395
1396
0
  if (!lwgeom_has_m(g1) || !lwgeom_has_m(g2))
1397
0
  {
1398
0
    lwerror("Both input geometries must have a measure dimension");
1399
0
    return LW_FALSE;
1400
0
  }
1401
1402
0
  l1 = lwgeom_as_lwline(g1);
1403
0
  l2 = lwgeom_as_lwline(g2);
1404
1405
0
  if (!l1 || !l2)
1406
0
  {
1407
0
    lwerror("Both input geometries must be linestrings");
1408
0
    return LW_FALSE;
1409
0
  }
1410
1411
0
  if (l1->points->npoints < 2 || l2->points->npoints < 2)
1412
0
  {
1413
    /* TODO: return distance between these two points */
1414
0
    lwerror("Both input lines must have at least 2 points");
1415
0
    return LW_FALSE;
1416
0
  }
1417
1418
  /* We use lwgeom_calculate_gbox() instead of lwgeom_get_gbox() */
1419
  /* because we cannot afford the float rounding inaccuracy when */
1420
  /* we compare the ranges for overlap below */
1421
0
  lwgeom_calculate_gbox(g1, &gbox1);
1422
0
  lwgeom_calculate_gbox(g2, &gbox2);
1423
1424
  /*
1425
   * Find overlapping M range
1426
   * WARNING: may be larger than the real one
1427
   */
1428
1429
0
  tmin = FP_MAX(gbox1.mmin, gbox2.mmin);
1430
0
  tmax = FP_MIN(gbox1.mmax, gbox2.mmax);
1431
1432
0
  if (tmax < tmin)
1433
0
  {
1434
0
    LWDEBUG(1, "Inputs never exist at the same time");
1435
0
    return LW_FALSE;
1436
0
  }
1437
1438
  /*
1439
   * Collect M values in common time range from inputs
1440
   */
1441
1442
0
  mvals = lwalloc(sizeof(double) * (l1->points->npoints + l2->points->npoints));
1443
1444
  /* TODO: also clip the lines ? */
1445
0
  nmvals = ptarray_collect_mvals(l1->points, tmin, tmax, mvals);
1446
0
  nmvals += ptarray_collect_mvals(l2->points, tmin, tmax, mvals + nmvals);
1447
1448
  /* Sort values in ascending order */
1449
0
  qsort(mvals, nmvals, sizeof(double), compare_double);
1450
1451
  /* Remove duplicated values */
1452
0
  nmvals = uniq(mvals, nmvals);
1453
1454
0
  if (nmvals < 2)
1455
0
  {
1456
    /* there's a single time, must be that one... */
1457
0
    double t0 = mvals[0];
1458
0
    POINT4D p0, p1;
1459
0
    LWDEBUGF(1, "Inputs only exist both at a single time (%g)", t0);
1460
0
    if (-1 == ptarray_locate_along_linear(l1->points, t0, &p0, 0))
1461
0
    {
1462
0
      lwnotice("Could not find point with M=%g on first geom", t0);
1463
0
      return LW_FALSE;
1464
0
    }
1465
0
    if (-1 == ptarray_locate_along_linear(l2->points, t0, &p1, 0))
1466
0
    {
1467
0
      lwnotice("Could not find point with M=%g on second geom", t0);
1468
0
      return LW_FALSE;
1469
0
    }
1470
0
    if (distance3d_pt_pt((POINT3D *)&p0, (POINT3D *)&p1) <= maxdist)
1471
0
      within = LW_TRUE;
1472
0
    lwfree(mvals);
1473
0
    return within;
1474
0
  }
1475
1476
  /*
1477
   * For each consecutive pair of measures, compute time of closest point
1478
   * approach and actual distance between points at that time
1479
   */
1480
0
  for (i = 1; i < nmvals; ++i)
1481
0
  {
1482
0
    double t0 = mvals[i - 1];
1483
0
    double t1 = mvals[i];
1484
#if POSTGIS_DEBUG_LEVEL >= 1
1485
    double t;
1486
#endif
1487
0
    POINT4D p0, p1, q0, q1;
1488
0
    int seg;
1489
0
    double dist2;
1490
1491
    // lwnotice("T %g-%g", t0, t1);
1492
1493
0
    seg = ptarray_locate_along_linear(l1->points, t0, &p0, 0);
1494
0
    if (-1 == seg)
1495
0
      continue; /* possible, if GBOX is approximated */
1496
    // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z);
1497
1498
0
    seg = ptarray_locate_along_linear(l1->points, t1, &p1, seg);
1499
0
    if (-1 == seg)
1500
0
      continue; /* possible, if GBOX is approximated */
1501
    // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z);
1502
1503
0
    seg = ptarray_locate_along_linear(l2->points, t0, &q0, 0);
1504
0
    if (-1 == seg)
1505
0
      continue; /* possible, if GBOX is approximated */
1506
    // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z);
1507
1508
0
    seg = ptarray_locate_along_linear(l2->points, t1, &q1, seg);
1509
0
    if (-1 == seg)
1510
0
      continue; /* possible, if GBOX is approximated */
1511
      // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z);
1512
1513
#if POSTGIS_DEBUG_LEVEL >= 1
1514
    t =
1515
#endif
1516
0
        segments_tcpa(&p0, &p1, &q0, &q1, t0, t1);
1517
1518
    /*
1519
    lwnotice("Closest points: %g,%g,%g and %g,%g,%g at time %g",
1520
    p0.x, p0.y, p0.z,
1521
    q0.x, q0.y, q0.z, t);
1522
    */
1523
1524
0
    dist2 = (q0.x - p0.x) * (q0.x - p0.x) + (q0.y - p0.y) * (q0.y - p0.y) + (q0.z - p0.z) * (q0.z - p0.z);
1525
0
    if (dist2 <= maxdist2)
1526
0
    {
1527
0
      LWDEBUGF(1, "Within distance %g at time %g, breaking", sqrt(dist2), t);
1528
0
      within = LW_TRUE;
1529
0
      break;
1530
0
    }
1531
0
  }
1532
1533
  /*
1534
   * Release memory
1535
   */
1536
1537
0
  lwfree(mvals);
1538
1539
0
  return within;
1540
0
}