Coverage Report

Created: 2026-08-14 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwnurbscurve.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) 2025 PostGIS contributors
22
 * Basic NURBS curve support for PostGIS
23
 *
24
 **********************************************************************/
25
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include <math.h>
30
#include "liblwgeom_internal.h"
31
#include "lwgeom_log.h"
32
33
/**
34
 * Construct a new NURBS curve.
35
 *
36
 * Creates a NURBS (Non-Uniform Rational B-Spline) curve object from the
37
 * provided control points, optional weights, and optional knot vector.
38
 *
39
 * Validation and ownership:
40
 * - Returns NULL if degree is outside [1,10].
41
 * - If `weights` is non-NULL, `nweights` must equal the number of control points.
42
 * - If `knots` is non-NULL, `nknots` must equal npoints + degree + 1.
43
 * - Ownership of `points` is transferred to the returned curve (caller must not free it).
44
 * - `weights` and `knots`, if provided, are deep-copied into the new curve.
45
 *
46
 * Behavioral notes:
47
 * - If `weights` is NULL, the curve is treated as non-rational (implicit weights = 1.0).
48
 * - If `knots` is NULL, a uniform clamped knot vector will be generated on demand.
49
 *
50
 * @param srid Spatial reference identifier for the curve.
51
 * @param bbox Bounding box for the curve (can be NULL).
52
 * @param degree Polynomial degree of the curve (1..10).
53
 * @param points Control points array; ownership is transferred to the returned curve.
54
 * @param weights Optional weights array; copied when non-NULL.
55
 * @param knots Optional knot vector; copied when non-NULL.
56
 * @param nweights Number of entries in `weights` (must match point count if `weights` provided).
57
 * @param nknots Number of entries in `knots` (must equal npoints + degree + 1 if `knots` provided).
58
 * @return Pointer to the newly allocated LWNURBSCURVE, or NULL on invalid parameters.
59
 */
60
LWNURBSCURVE *
61
lwnurbscurve_construct(int32_t srid, GBOX *bbox, uint32_t degree, POINTARRAY *points,
62
                      const double *weights, const double *knots, uint32_t nweights, uint32_t nknots)
63
0
{
64
0
  LWNURBSCURVE *result;
65
66
  /* Validate degree: must be between 1 and 10 */
67
0
  if (degree < 1 || degree > 10)
68
0
  {
69
0
    lwerror("NURBS: degree %u outside valid range [1,10]", degree);
70
0
    return NULL;
71
0
  }
72
73
  /* Basic invariants */
74
0
  if (points && points->npoints > 0 && points->npoints < degree + 1)
75
0
  {
76
0
    lwerror("NURBS: npoints (%u) must be at least degree + 1 (%u)", points->npoints, degree + 1);
77
0
    return NULL;
78
0
  }
79
0
  if (points && weights && nweights != points->npoints)
80
0
  {
81
0
    lwerror("NURBS: nweights (%u) must equal number of control points (%u)", nweights, points->npoints);
82
0
    return NULL;
83
0
  }
84
0
  if (points && knots && nknots != points->npoints + degree + 1)
85
0
  {
86
0
    lwerror("NURBS: nknots (%u) must equal npoints + degree + 1 (%u)", nknots, points->npoints + degree + 1);
87
0
    return NULL;
88
0
  }
89
90
  /* Validate weight values */
91
0
  if (nweights > 0 && !weights)
92
0
  {
93
0
    lwerror("NURBS: weights is NULL but nweights = %u", nweights);
94
0
    return NULL;
95
0
  }
96
0
  for (uint32_t i = 0; i < nweights; i++)
97
0
  {
98
0
    if (!isfinite(weights[i]) || weights[i] <= 0.0)
99
0
    {
100
0
      lwerror("NURBS: weight[%u] = %g must be finite and > 0", i, weights[i]);
101
0
      return NULL;
102
0
    }
103
0
  }
104
105
  /* Validate knot vector monotonicity */
106
0
  if (nknots > 0 && !knots)
107
0
  {
108
0
    lwerror("NURBS: knots is NULL but nknots = %u", nknots);
109
0
    return NULL;
110
0
  }
111
0
  for (uint32_t i = 1; i < nknots; i++)
112
0
  {
113
0
    if (!isfinite(knots[i]) || knots[i] < knots[i - 1])
114
0
    {
115
0
      lwerror("NURBS: knot[%u] = %g must be finite and >= knot[%u] = %g",
116
0
              i, knots[i], i - 1, knots[i - 1]);
117
0
      return NULL;
118
0
    }
119
0
  }
120
121
0
  result = lwalloc(sizeof(LWNURBSCURVE));
122
0
  result->type = NURBSCURVETYPE;
123
0
  result->srid = srid;
124
0
  result->bbox = bbox;
125
0
  result->degree = degree;
126
0
  result->nweights = nweights;
127
0
  result->nknots = nknots;
128
129
  /* Inherit dimensional flags from control points */
130
0
  if (points) {
131
0
    result->flags = points->flags;
132
0
    result->points = points; /* Take ownership - caller must not free */
133
0
  } else {
134
    /* Empty curve: no dimensional information available */
135
0
    result->flags = 0;
136
0
    result->points = NULL;
137
0
  }
138
139
  /* Deep copy weights array for rational NURBS */
140
0
  if (weights && nweights > 0) {
141
0
    result->weights = lwalloc(sizeof(double) * nweights);
142
0
    memcpy(result->weights, weights, sizeof(double) * nweights);
143
0
  } else {
144
    /* Non-rational NURBS: all weights implicitly 1.0 */
145
0
    result->weights = NULL;
146
0
  }
147
148
  /* Deep copy knot vector for explicit parameterization */
149
0
  if (knots && nknots > 0) {
150
0
    result->knots = lwalloc(sizeof(double) * nknots);
151
0
    memcpy(result->knots, knots, sizeof(double) * nknots);
152
0
  } else {
153
    /* No knots: uniform parameterization will be generated when needed */
154
0
    result->knots = NULL;
155
0
  }
156
157
0
  return result;
158
0
}
159
160
/**
161
 * Construct an empty NURBS curve
162
 *
163
 * Creates a valid but empty NURBS curve with specified dimensional flags.
164
 * Empty curves contain no control points but maintain proper structure
165
 * for dimensional consistency in PostGIS operations.
166
 *
167
 * @param srid Spatial reference system identifier
168
 * @param hasz True if curve should support Z coordinates
169
 * @param hasm True if curve should support M coordinates
170
 * @return Empty NURBS curve with specified dimensions
171
 */
172
LWNURBSCURVE *
173
lwnurbscurve_construct_empty(int32_t srid, char hasz, char hasm)
174
0
{
175
0
  LWNURBSCURVE *result = lwalloc(sizeof(LWNURBSCURVE));
176
0
  result->type = NURBSCURVETYPE;
177
0
  result->flags = lwflags(hasz, hasm, 0);
178
0
  result->srid = srid;
179
0
  result->bbox = NULL;
180
0
  result->degree = 1;
181
0
  result->points = ptarray_construct_empty(hasz, hasm, 1);
182
0
  result->weights = NULL;
183
0
  result->nweights = 0;
184
0
  result->knots = NULL;
185
0
  result->nknots = 0;
186
0
  return result;
187
0
}
188
189
/**
190
 * Free NURBS curve memory
191
 *
192
 * Recursively frees all memory associated with a NURBS curve,
193
 * including bounding box, control points, weights, and knot vector.
194
 * Safe to call with NULL pointer.
195
 *
196
 * @param curve NURBS curve to free (can be NULL)
197
 */
198
void
199
lwnurbscurve_free(LWNURBSCURVE *curve)
200
0
{
201
0
  if (!curve) return;
202
203
0
  if (curve->bbox)
204
0
    lwfree(curve->bbox);
205
206
0
  if (curve->points)
207
0
    ptarray_free(curve->points);
208
209
0
  if (curve->weights)
210
0
    lwfree(curve->weights);
211
212
0
  if (curve->knots)
213
0
    lwfree(curve->knots);
214
215
0
  lwfree(curve);
216
0
}
217
218
/**
219
 * Deep clone NURBS curve
220
 *
221
 * Creates a complete independent copy of a NURBS curve.
222
 * All arrays (points, weights, knots) are deep-copied,
223
 * ensuring the clone can be modified without affecting the original.
224
 *
225
 * @param curve Source NURBS curve to clone
226
 * @return Deep copy of the curve, or NULL if input is NULL
227
 */
228
LWNURBSCURVE *
229
lwnurbscurve_clone_deep(const LWNURBSCURVE *curve)
230
0
{
231
0
  if (!curve) return NULL;
232
233
  /* Deep clone control points array */
234
0
  POINTARRAY *points = curve->points ? ptarray_clone_deep(curve->points) : NULL;
235
236
  /* Clone bbox if it exists */
237
0
  GBOX *bbox = curve->bbox ? gbox_copy(curve->bbox) : NULL;
238
239
  /* lwnurbscurve_construct will deep-copy weights and knots arrays */
240
0
  LWNURBSCURVE *result = lwnurbscurve_construct(curve->srid, bbox, curve->degree, points,
241
0
                               curve->weights, curve->knots,
242
0
                               curve->nweights, curve->nknots);
243
0
  if (!result)
244
0
  {
245
0
    ptarray_free(points);
246
0
    if (bbox) lwfree(bbox);
247
0
  }
248
0
  return result;
249
0
}
250
251
LWNURBSCURVE *
252
lwnurbscurve_force_dims(const LWNURBSCURVE *curve, int hasz, int hasm, double zval, double mval)
253
0
{
254
0
  POINTARRAY *points;
255
256
0
  if (!curve)
257
0
    return NULL;
258
259
0
  if (!curve->points || curve->points->npoints == 0)
260
0
    return lwnurbscurve_construct_empty(curve->srid, hasz, hasm);
261
262
0
  points = ptarray_force_dims(curve->points, hasz, hasm, zval, mval);
263
264
0
  LWNURBSCURVE *result = lwnurbscurve_construct(curve->srid, NULL, curve->degree, points,
265
0
                                curve->weights, curve->knots,
266
0
                                curve->nweights, curve->nknots);
267
0
  if (!result)
268
0
  {
269
0
    ptarray_free(points);
270
0
  }
271
0
  return result;
272
0
}
273
274
/**
275
 * Generate uniform knot vector for NURBS curve
276
 *
277
 * Creates a clamped uniform knot vector suitable for standard NURBS curves.
278
 * The knot vector has:
279
 * - (degree+1) repeated knots at the start (value 0.0) for clamping
280
 * - (degree+1) repeated knots at the end (value 1.0) for clamping
281
 * - Internal knots uniformly distributed between 0 and 1
282
 *
283
 * This ensures the curve starts at the first control point and ends
284
 * at the last control point (clamped behavior).
285
 *
286
 * Mathematical constraint: nknots = npoints + degree + 1
287
 *
288
 * @param degree Polynomial degree of the curve
289
 * @param npoints Number of control points
290
 * @param nknots_out Output parameter for knot vector size
291
 * @return Newly allocated uniform knot vector
292
 */
293
static double *
294
lwnurbscurve_generate_uniform_knots(uint32_t degree, uint32_t npoints, uint32_t *nknots_out)
295
0
{
296
0
  double *knots;
297
0
  uint32_t nknots, i;
298
299
  /* Input validation to prevent underflow */
300
0
  if (degree == 0 || npoints < degree + 1) {
301
0
    if (nknots_out) *nknots_out = 0;
302
0
    return NULL;
303
0
  }
304
305
0
  nknots = npoints + degree + 1;
306
307
0
  knots = lwalloc(sizeof(double) * nknots);
308
0
  if (!knots) {
309
0
    *nknots_out = 0;
310
0
    return NULL;
311
0
  }
312
313
  /* Clamp start: first (degree+1) knots set to 0.0 */
314
0
  for (i = 0; i <= degree; i++) {
315
0
    knots[i] = 0.0;
316
0
  }
317
318
  /* Clamp end: last (degree+1) knots set to 1.0 */
319
0
  for (i = nknots - degree - 1; i < nknots; i++) {
320
0
    knots[i] = 1.0;
321
0
  }
322
323
  /* Distribute internal knots uniformly in (0,1) interval */
324
0
  if (nknots > 2 * (degree + 1)) {
325
0
    uint32_t internal_knots = nknots - 2 * (degree + 1);
326
0
    for (i = 0; i < internal_knots; i++) {
327
      /* Uniform distribution: 1/(n+1), 2/(n+1), ..., n/(n+1) */
328
0
      knots[degree + 1 + i] = (double)(i + 1) / (internal_knots + 1);
329
0
    }
330
0
  }
331
332
0
  *nknots_out = nknots;
333
0
  return knots;
334
0
}
335
336
/**
337
 * Retrieve a knot vector suitable for WKB serialization.
338
 *
339
 * Returns a newly-allocated knot vector for the given NURBS curve. If the curve
340
 * has an explicit knot vector it is deep-copied; otherwise a clamped uniform
341
 * knot vector is generated from the curve degree and number of control points.
342
 *
343
 * The caller is responsible for freeing the returned array. If `curve` is NULL
344
 * or has no control points, `*nknots_out` is set to 0 and NULL is returned.
345
 *
346
 * @param curve Source NURBS curve.
347
 * @param nknots_out Output location which will be set to the number of knots
348
 *                   in the returned array (set to 0 on NULL/empty input).
349
 * @return Newly allocated array of knots, or NULL for invalid/empty curves.
350
 */
351
double *
352
lwnurbscurve_get_or_generate_knots(const LWNURBSCURVE *curve, uint32_t *nknots_out)
353
0
{
354
  /* Validate input parameters */
355
0
  if (!curve || !curve->points) {
356
0
    *nknots_out = 0;
357
0
    return NULL;
358
0
  }
359
360
  /* Use explicit knot vector if available */
361
0
  if (curve->knots && curve->nknots > 0) {
362
0
    double *knots = lwalloc(sizeof(double) * curve->nknots);
363
0
    memcpy(knots, curve->knots, sizeof(double) * curve->nknots);
364
0
    *nknots_out = curve->nknots;
365
0
    return knots;
366
0
  }
367
368
  /* Generate uniform knot vector on demand */
369
0
  return lwnurbscurve_generate_uniform_knots(curve->degree, curve->points->npoints, nknots_out);
370
0
}
371
372
/**
373
 * Cast a NURBS curve to the generic LWGEOM type.
374
 *
375
 * This performs a safe, no-op pointer cast from LWNURBSCURVE to LWGEOM
376
 * so the curve can be used with APIs expecting LWGEOM without changing
377
 * ownership or copying data.
378
 *
379
 * @param obj NURBS curve to cast (may be NULL)
380
 * @return The same pointer value cast to LWGEOM*, or NULL if `obj` is NULL
381
 */
382
LWGEOM *
383
lwnurbscurve_as_lwgeom(const LWNURBSCURVE *obj)
384
0
{
385
0
  return (LWGEOM *)obj;
386
0
}
387
388
/**
389
 * Cast a generic LWGEOM to a NURBS curve.
390
 *
391
 * This performs a safe type check and cast from LWGEOM to LWNURBSCURVE.
392
 * Returns NULL if the input is not a NURBS curve type.
393
 *
394
 * @param lwgeom Geometry to cast (may be NULL)
395
 * @return The same pointer cast to LWNURBSCURVE*, or NULL if not a NURBSCURVE
396
 */
397
LWNURBSCURVE *
398
lwgeom_as_lwnurbscurve(const LWGEOM *lwgeom)
399
0
{
400
0
  if ( lwgeom == NULL ) return NULL;
401
0
  if ( lwgeom->type == NURBSCURVETYPE )
402
0
    return (LWNURBSCURVE *)lwgeom;
403
0
  else return NULL;
404
0
}
405
406
/**
407
 * Get the control points from a NURBS curve.
408
 *
409
 * Returns the POINTARRAY containing the control points that define
410
 * the NURBS curve. The returned pointer points directly to the curve's
411
 * internal data structure and should not be modified or freed.
412
 *
413
 * @param curve NURBS curve to extract control points from (must not be NULL)
414
 * @return Pointer to the control points array, or NULL if curve is NULL
415
 */
416
POINTARRAY *
417
lwnurbscurve_get_control_points(const LWNURBSCURVE *curve)
418
0
{
419
0
  if ( curve == NULL ) return NULL;
420
0
  return curve->points;
421
0
}
422
423
/**
424
 * Find the knot span index for parameter u (Algorithm A2.1 from "The NURBS Book").
425
 *
426
 * Uses binary search to find the knot span [u_i, u_{i+1}) containing parameter u.
427
 * This is the index i such that knots[i] <= u < knots[i+1].
428
 *
429
 * For a NURBS curve with n control points and degree p, the knot vector has
430
 * n + p + 1 knots. Valid parameter values are in [knots[p], knots[n]].
431
 *
432
 * @param degree polynomial degree of the NURBS curve
433
 * @param u parameter value to locate
434
 * @param knots knot vector array
435
 * @param npoints number of control points
436
 * @return knot span index i where knots[i] <= u < knots[i+1]
437
 */
438
static uint32_t
439
lwnurbscurve_find_span(uint32_t degree, double u, const double *knots, uint32_t npoints)
440
0
{
441
0
  uint32_t low, high, mid;
442
443
  /* Special case: u at end of parameter range */
444
0
  if (u >= knots[npoints]) {
445
0
    return npoints - 1;
446
0
  }
447
448
  /* Special case: u at start of parameter range */
449
0
  if (u <= knots[degree]) {
450
0
    return degree;
451
0
  }
452
453
  /* Binary search for the knot span */
454
0
  low = degree;
455
0
  high = npoints;
456
0
  mid = (low + high) / 2;
457
458
0
  while (u < knots[mid] || u >= knots[mid + 1]) {
459
0
    if (u < knots[mid]) {
460
0
      high = mid;
461
0
    } else {
462
0
      low = mid;
463
0
    }
464
0
    mid = (low + high) / 2;
465
0
  }
466
467
0
  return mid;
468
0
}
469
470
/**
471
 * Evaluates a NURBS curve at parameter t using De Boor's algorithm.
472
 *
473
 * Implements Algorithm A4.1 from "The NURBS Book" (Piegl & Tiller).
474
 * This iterative algorithm is O(p^2) where p is the degree, compared to
475
 * O(2^p) for the naive recursive Cox-de Boor approach.
476
 *
477
 * For rational NURBS, uses homogeneous coordinates (wx, wy, wz, w) throughout
478
 * the computation, then projects back to Cartesian coordinates at the end.
479
 *
480
 * @param curve NURBS curve to evaluate
481
 * @param t parameter value (typically in [0,1])
482
 * @return point on the curve at parameter t, or empty point if curve is invalid
483
 */
484
LWPOINT *
485
lwnurbscurve_evaluate(const LWNURBSCURVE *curve, double t)
486
0
{
487
0
  POINT4D result;
488
0
  uint32_t j, k;
489
0
  double *knots;
490
0
  uint32_t nknots;
491
0
  char hasz, hasm;
492
0
  LWPOINT *lwpoint;
493
0
  uint32_t span;
494
0
  uint32_t degree;
495
0
  POINT4D *temp;  /* Temporary array for De Boor algorithm */
496
0
  double alpha;
497
0
  double *weights;  /* Temporary weights array for De Boor algorithm */
498
499
  /* Validate input */
500
0
  if (!curve)
501
0
    return lwpoint_construct_empty(SRID_UNKNOWN, 0, 0);
502
0
  if (!curve->points || curve->points->npoints == 0)
503
0
    return lwpoint_construct_empty(curve->srid, FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags));
504
505
  /* Get dimensional flags */
506
0
  hasz = FLAGS_GET_Z(curve->flags);
507
0
  hasm = FLAGS_GET_M(curve->flags);
508
0
  degree = curve->degree;
509
510
  /* Clamp normalized parameter t to [0,1]. Endpoints are still evaluated
511
   * against the knot vector, since unclamped NURBS do not generally pass
512
   * through their first or last control point. */
513
0
  if (t < 0.0)
514
0
    t = 0.0;
515
0
  else if (t > 1.0)
516
0
    t = 1.0;
517
518
  /* Get knot vector for evaluation */
519
0
  knots = lwnurbscurve_get_or_generate_knots(curve, &nknots);
520
0
  if (!knots || nknots == 0) {
521
0
    lwfree(knots);
522
0
    return lwpoint_construct_empty(curve->srid, hasz, hasm);
523
0
  }
524
525
  /* Remap parameter from [0,1] to knot vector range [knots[degree], knots[npoints]] */
526
0
  t = knots[degree] + t * (knots[curve->points->npoints] - knots[degree]);
527
528
  /* Find the knot span containing parameter t (Algorithm A2.1) */
529
0
  span = lwnurbscurve_find_span(degree, t, knots, curve->points->npoints);
530
531
  /* Allocate temporary arrays for De Boor iteration (degree+1 points) */
532
0
  temp = lwalloc(sizeof(POINT4D) * (degree + 1));
533
0
  weights = lwalloc(sizeof(double) * (degree + 1));
534
535
  /* Initialize temp array with control points and weights.
536
   * For rational curves: store (w*x, w*y, w*z, w) in homogeneous coordinates
537
   * For non-rational: weights are 1.0 */
538
0
  for (j = 0; j <= degree; j++) {
539
0
    uint32_t cp_idx = span - degree + j;
540
0
    POINT4D cp;
541
542
0
    getPoint4d_p(curve->points, cp_idx, &cp);
543
0
    weights[j] = (curve->weights && cp_idx < curve->nweights) ?
544
0
                  curve->weights[cp_idx] : 1.0;
545
546
    /* Store in homogeneous coordinates (w*P) */
547
0
    temp[j].x = cp.x * weights[j];
548
0
    temp[j].y = cp.y * weights[j];
549
0
    temp[j].z = hasz ? cp.z * weights[j] : 0.0;
550
0
    temp[j].m = hasm ? cp.m * weights[j] : 0.0;
551
0
  }
552
553
  /* De Boor iteration (Algorithm A4.1) in homogeneous space */
554
0
  for (k = 1; k <= degree; k++) {
555
0
    for (j = degree; j >= k; j--) {
556
0
      uint32_t knot_idx = span - degree + j;
557
0
      double denom = knots[knot_idx + degree - k + 1] - knots[knot_idx];
558
559
0
      if (denom != 0.0) {
560
0
        alpha = (t - knots[knot_idx]) / denom;
561
562
        /* Linear interpolation in homogeneous space */
563
0
        temp[j].x = (1.0 - alpha) * temp[j-1].x + alpha * temp[j].x;
564
0
        temp[j].y = (1.0 - alpha) * temp[j-1].y + alpha * temp[j].y;
565
0
        if (hasz) temp[j].z = (1.0 - alpha) * temp[j-1].z + alpha * temp[j].z;
566
0
        if (hasm) temp[j].m = (1.0 - alpha) * temp[j-1].m + alpha * temp[j].m;
567
568
        /* Also interpolate weights */
569
0
        weights[j] = (1.0 - alpha) * weights[j-1] + alpha * weights[j];
570
0
      }
571
0
    }
572
0
  }
573
574
  /* Result is in temp[degree], stored in homogeneous coordinates (w*P)
575
   * Project back to Cartesian by dividing by weight */
576
0
  result = temp[degree];
577
0
  if (weights[degree] != 0.0 && weights[degree] != 1.0) {
578
0
    result.x /= weights[degree];
579
0
    result.y /= weights[degree];
580
0
    if (hasz) result.z /= weights[degree];
581
0
    if (hasm) result.m /= weights[degree];
582
0
  }
583
584
0
  lwfree(weights);
585
586
0
  lwfree(temp);
587
0
  lwfree(knots);
588
589
0
  lwpoint = lwpoint_construct(curve->srid, NULL, ptarray_construct_empty(hasz, hasm, 1));
590
0
  ptarray_append_point(lwpoint->point, &result, LW_TRUE);
591
0
  return lwpoint;
592
0
}
593
594
/**
595
 * Converts a NURBS curve to a LineString by uniform sampling.
596
 *
597
 * Evaluates the NURBS curve at uniformly distributed parameter values
598
 * to create a piecewise linear approximation.
599
 *
600
 * @param curve NURBS curve to convert
601
 * @param num_segments number of segments in the resulting LineString
602
 * @return LineString approximation of the NURBS curve
603
 */
604
LWLINE *
605
lwnurbscurve_to_linestring(const LWNURBSCURVE *curve, uint32_t num_segments)
606
0
{
607
0
  POINTARRAY *pts;
608
0
  uint32_t i;
609
0
  char hasz, hasm;
610
611
  /* Get dimensional flags */
612
0
  hasz = curve ? FLAGS_GET_Z(curve->flags) : 0;
613
0
  hasm = curve ? FLAGS_GET_M(curve->flags) : 0;
614
615
  /* Validate input */
616
0
  if (!curve || !curve->points || curve->points->npoints == 0)
617
0
    return lwline_construct_empty(curve ? curve->srid : SRID_UNKNOWN, hasz, hasm);
618
619
  /* Ensure minimum number of segments */
620
0
  if (num_segments < 2) num_segments = 2;
621
622
  /* Create point array for result */
623
0
  pts = ptarray_construct_empty(hasz, hasm, num_segments + 1);
624
625
  /* Sample the curve at uniform parameter intervals */
626
0
  for (i = 0; i <= num_segments; i++) {
627
0
    double t = (double)i / num_segments;
628
0
    LWPOINT *pt = lwnurbscurve_evaluate(curve, t);
629
0
    POINT4D p4d;
630
631
0
    if (pt && pt->point && pt->point->npoints > 0) {
632
0
      getPoint4d_p(pt->point, 0, &p4d);
633
0
      ptarray_append_point(pts, &p4d, LW_TRUE);
634
0
    }
635
636
0
    if (pt) lwpoint_free(pt);
637
0
  }
638
639
  return lwline_construct(curve->srid, NULL, pts);
640
0
}