/src/gdal/alg/contour.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Contour Generation |
4 | | * Purpose: Core algorithm implementation for contour line generation. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com> |
9 | | * Copyright (c) 2003, Applied Coherent Technology Corporation, www.actgate.com |
10 | | * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com> |
11 | | * Copyright (c) 2018, Oslandia <infos at oslandia dot com> |
12 | | * |
13 | | * SPDX-License-Identifier: MIT |
14 | | ****************************************************************************/ |
15 | | |
16 | | #include "level_generator.h" |
17 | | #include "polygon_ring_appender.h" |
18 | | #include "utility.h" |
19 | | #include "contour_generator.h" |
20 | | #include "segment_merger.h" |
21 | | #include <algorithm> |
22 | | |
23 | | #include "gdal.h" |
24 | | #include "gdal_alg.h" |
25 | | #include "cpl_conv.h" |
26 | | #include "cpl_string.h" |
27 | | #include "ogr_api.h" |
28 | | #include "ogr_srs_api.h" |
29 | | #include "ogr_geometry.h" |
30 | | |
31 | | #include <climits> |
32 | | #include <limits> |
33 | | |
34 | | static CPLErr OGRPolygonContourWriter(double dfLevelMin, double dfLevelMax, |
35 | | const OGRMultiPolygon &multipoly, |
36 | | void *pInfo) |
37 | | |
38 | 0 | { |
39 | 0 | OGRContourWriterInfo *poInfo = static_cast<OGRContourWriterInfo *>(pInfo); |
40 | |
|
41 | 0 | OGRFeatureDefnH hFDefn = |
42 | 0 | OGR_L_GetLayerDefn(static_cast<OGRLayerH>(poInfo->hLayer)); |
43 | |
|
44 | 0 | OGRFeatureH hFeat = OGR_F_Create(hFDefn); |
45 | |
|
46 | 0 | if (poInfo->nIDField != -1) |
47 | 0 | OGR_F_SetFieldInteger(hFeat, poInfo->nIDField, poInfo->nNextID++); |
48 | |
|
49 | 0 | if (poInfo->nElevFieldMin != -1) |
50 | 0 | OGR_F_SetFieldDouble(hFeat, poInfo->nElevFieldMin, dfLevelMin); |
51 | |
|
52 | 0 | if (poInfo->nElevFieldMax != -1) |
53 | 0 | OGR_F_SetFieldDouble(hFeat, poInfo->nElevFieldMax, dfLevelMax); |
54 | |
|
55 | 0 | const bool bHasZ = wkbHasZ(OGR_FD_GetGeomType(hFDefn)); |
56 | 0 | OGRGeometryH hGeom = |
57 | 0 | OGR_G_CreateGeometry(bHasZ ? wkbMultiPolygon25D : wkbMultiPolygon); |
58 | |
|
59 | 0 | for (int iPart = 0; iPart < multipoly.getNumGeometries(); iPart++) |
60 | 0 | { |
61 | 0 | OGRPolygon *poNewPoly = new OGRPolygon(); |
62 | 0 | const OGRPolygon *poPolygon = |
63 | 0 | static_cast<const OGRPolygon *>(multipoly.getGeometryRef(iPart)); |
64 | |
|
65 | 0 | for (int iRing = 0; iRing < poPolygon->getNumInteriorRings() + 1; |
66 | 0 | iRing++) |
67 | 0 | { |
68 | 0 | const OGRLinearRing *poRing = |
69 | 0 | iRing == 0 ? poPolygon->getExteriorRing() |
70 | 0 | : poPolygon->getInteriorRing(iRing - 1); |
71 | |
|
72 | 0 | OGRLinearRing *poNewRing = new OGRLinearRing(); |
73 | 0 | for (int iPoint = 0; iPoint < poRing->getNumPoints(); iPoint++) |
74 | 0 | { |
75 | 0 | const double dfX = |
76 | 0 | poInfo->adfGeoTransform[0] + |
77 | 0 | poInfo->adfGeoTransform[1] * poRing->getX(iPoint) + |
78 | 0 | poInfo->adfGeoTransform[2] * poRing->getY(iPoint); |
79 | 0 | const double dfY = |
80 | 0 | poInfo->adfGeoTransform[3] + |
81 | 0 | poInfo->adfGeoTransform[4] * poRing->getX(iPoint) + |
82 | 0 | poInfo->adfGeoTransform[5] * poRing->getY(iPoint); |
83 | 0 | if (bHasZ) |
84 | 0 | OGR_G_SetPoint(OGRGeometry::ToHandle(poNewRing), iPoint, |
85 | 0 | dfX, dfY, dfLevelMax); |
86 | 0 | else |
87 | 0 | OGR_G_SetPoint_2D(OGRGeometry::ToHandle(poNewRing), iPoint, |
88 | 0 | dfX, dfY); |
89 | 0 | } |
90 | 0 | poNewPoly->addRingDirectly(poNewRing); |
91 | 0 | } |
92 | 0 | OGR_G_AddGeometryDirectly(hGeom, OGRGeometry::ToHandle(poNewPoly)); |
93 | 0 | } |
94 | |
|
95 | 0 | OGR_F_SetGeometryDirectly(hFeat, hGeom); |
96 | |
|
97 | 0 | OGRErr eErr = |
98 | 0 | OGR_L_CreateFeature(static_cast<OGRLayerH>(poInfo->hLayer), hFeat); |
99 | 0 | OGR_F_Destroy(hFeat); |
100 | |
|
101 | 0 | if (eErr == OGRERR_NONE && poInfo->nTransactionCommitInterval > 0) |
102 | 0 | { |
103 | 0 | if (++poInfo->nWrittenFeatureCountSinceLastCommit == |
104 | 0 | poInfo->nTransactionCommitInterval) |
105 | 0 | { |
106 | 0 | poInfo->nWrittenFeatureCountSinceLastCommit = 0; |
107 | | // CPLDebug("CONTOUR", "Flush transaction"); |
108 | 0 | eErr = |
109 | 0 | OGR_L_CommitTransaction(static_cast<OGRLayerH>(poInfo->hLayer)); |
110 | 0 | if (eErr == OGRERR_NONE) |
111 | 0 | { |
112 | 0 | eErr = OGR_L_StartTransaction( |
113 | 0 | static_cast<OGRLayerH>(poInfo->hLayer)); |
114 | 0 | } |
115 | 0 | } |
116 | 0 | } |
117 | |
|
118 | 0 | return eErr == OGRERR_NONE ? CE_None : CE_Failure; |
119 | 0 | } |
120 | | |
121 | | struct PolygonContourWriter |
122 | | { |
123 | | CPL_DISALLOW_COPY_ASSIGN(PolygonContourWriter) |
124 | | |
125 | | explicit PolygonContourWriter(OGRContourWriterInfo *poInfo, double minLevel) |
126 | 0 | : poInfo_(poInfo), currentLevel_(minLevel) |
127 | 0 | { |
128 | 0 | } |
129 | | |
130 | | void startPolygon(double level) |
131 | 0 | { |
132 | 0 | previousLevel_ = currentLevel_; |
133 | 0 | currentGeometry_.reset(new OGRMultiPolygon()); |
134 | 0 | currentLevel_ = level; |
135 | 0 | } |
136 | | |
137 | | void endPolygon() |
138 | 0 | { |
139 | 0 | if (currentPart_) |
140 | 0 | { |
141 | 0 | currentGeometry_->addGeometryDirectly(currentPart_); |
142 | 0 | } |
143 | |
|
144 | 0 | if (currentGeometry_->getNumGeometries() > 0) |
145 | 0 | { |
146 | 0 | OGRPolygonContourWriter(previousLevel_, currentLevel_, |
147 | 0 | *currentGeometry_, poInfo_); |
148 | 0 | } |
149 | |
|
150 | 0 | currentGeometry_.reset(nullptr); |
151 | 0 | currentPart_ = nullptr; |
152 | 0 | } |
153 | | |
154 | | void addPart(const marching_squares::LineString &ring) |
155 | 0 | { |
156 | 0 | if (currentPart_) |
157 | 0 | { |
158 | 0 | currentGeometry_->addGeometryDirectly(currentPart_); |
159 | 0 | } |
160 | |
|
161 | 0 | OGRLinearRing *poNewRing = new OGRLinearRing(); |
162 | 0 | poNewRing->setNumPoints(int(ring.size())); |
163 | 0 | int iPoint = 0; |
164 | 0 | for (const auto &p : ring) |
165 | 0 | { |
166 | 0 | poNewRing->setPoint(iPoint, p.x, p.y); |
167 | 0 | iPoint++; |
168 | 0 | } |
169 | 0 | currentPart_ = new OGRPolygon(); |
170 | 0 | currentPart_->addRingDirectly(poNewRing); |
171 | 0 | } |
172 | | |
173 | | void addInteriorRing(const marching_squares::LineString &ring) |
174 | 0 | { |
175 | 0 | OGRLinearRing *poNewRing = new OGRLinearRing(); |
176 | 0 | for (const auto &p : ring) |
177 | 0 | { |
178 | 0 | poNewRing->addPoint(p.x, p.y); |
179 | 0 | } |
180 | 0 | currentPart_->addRingDirectly(poNewRing); |
181 | 0 | } |
182 | | |
183 | | std::unique_ptr<OGRMultiPolygon> currentGeometry_ = {}; |
184 | | OGRPolygon *currentPart_ = nullptr; |
185 | | OGRContourWriterInfo *poInfo_ = nullptr; |
186 | | double currentLevel_ = 0; |
187 | | double previousLevel_ = 0; |
188 | | }; |
189 | | |
190 | | struct GDALRingAppender |
191 | | { |
192 | | CPL_DISALLOW_COPY_ASSIGN(GDALRingAppender) |
193 | | |
194 | | GDALRingAppender(GDALContourWriter write, void *data) |
195 | 0 | : write_(write), data_(data) |
196 | 0 | { |
197 | 0 | } |
198 | | |
199 | | void addLine(double level, marching_squares::LineString &ls, |
200 | | bool /*closed*/) |
201 | 0 | { |
202 | 0 | const size_t sz = ls.size(); |
203 | 0 | std::vector<double> xs(sz), ys(sz); |
204 | 0 | size_t i = 0; |
205 | 0 | for (const auto &pt : ls) |
206 | 0 | { |
207 | 0 | xs[i] = pt.x; |
208 | 0 | ys[i] = pt.y; |
209 | 0 | i++; |
210 | 0 | } |
211 | |
|
212 | 0 | if (write_(level, int(sz), &xs[0], &ys[0], data_) != CE_None) |
213 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "cannot write linestring"); |
214 | 0 | } |
215 | | |
216 | | private: |
217 | | GDALContourWriter write_; |
218 | | void *data_; |
219 | | }; |
220 | | |
221 | | /************************************************************************/ |
222 | | /* ==================================================================== */ |
223 | | /* Additional C Callable Functions */ |
224 | | /* ==================================================================== */ |
225 | | /************************************************************************/ |
226 | | |
227 | | /************************************************************************/ |
228 | | /* OGRContourWriter() */ |
229 | | /************************************************************************/ |
230 | | |
231 | | CPLErr OGRContourWriter(double dfLevel, int nPoints, double *padfX, |
232 | | double *padfY, void *pInfo) |
233 | | |
234 | 0 | { |
235 | 0 | OGRContourWriterInfo *poInfo = static_cast<OGRContourWriterInfo *>(pInfo); |
236 | |
|
237 | 0 | OGRFeatureDefnH hFDefn = |
238 | 0 | OGR_L_GetLayerDefn(static_cast<OGRLayerH>(poInfo->hLayer)); |
239 | |
|
240 | 0 | OGRFeatureH hFeat = OGR_F_Create(hFDefn); |
241 | |
|
242 | 0 | if (poInfo->nIDField != -1) |
243 | 0 | OGR_F_SetFieldInteger(hFeat, poInfo->nIDField, poInfo->nNextID++); |
244 | |
|
245 | 0 | if (poInfo->nElevField != -1) |
246 | 0 | OGR_F_SetFieldDouble(hFeat, poInfo->nElevField, dfLevel); |
247 | |
|
248 | 0 | const bool bHasZ = wkbHasZ(OGR_FD_GetGeomType(hFDefn)); |
249 | 0 | OGRGeometryH hGeom = |
250 | 0 | OGR_G_CreateGeometry(bHasZ ? wkbLineString25D : wkbLineString); |
251 | |
|
252 | 0 | for (int iPoint = nPoints - 1; iPoint >= 0; iPoint--) |
253 | 0 | { |
254 | 0 | const double dfX = poInfo->adfGeoTransform[0] + |
255 | 0 | poInfo->adfGeoTransform[1] * padfX[iPoint] + |
256 | 0 | poInfo->adfGeoTransform[2] * padfY[iPoint]; |
257 | 0 | const double dfY = poInfo->adfGeoTransform[3] + |
258 | 0 | poInfo->adfGeoTransform[4] * padfX[iPoint] + |
259 | 0 | poInfo->adfGeoTransform[5] * padfY[iPoint]; |
260 | 0 | if (bHasZ) |
261 | 0 | OGR_G_SetPoint(hGeom, iPoint, dfX, dfY, dfLevel); |
262 | 0 | else |
263 | 0 | OGR_G_SetPoint_2D(hGeom, iPoint, dfX, dfY); |
264 | 0 | } |
265 | |
|
266 | 0 | OGR_F_SetGeometryDirectly(hFeat, hGeom); |
267 | |
|
268 | 0 | const OGRErr eErr = |
269 | 0 | OGR_L_CreateFeature(static_cast<OGRLayerH>(poInfo->hLayer), hFeat); |
270 | 0 | OGR_F_Destroy(hFeat); |
271 | |
|
272 | 0 | return eErr == OGRERR_NONE ? CE_None : CE_Failure; |
273 | 0 | } |
274 | | |
275 | | /************************************************************************/ |
276 | | /* GDALContourGenerate() */ |
277 | | /************************************************************************/ |
278 | | |
279 | | /** |
280 | | * Create vector contours from raster DEM. |
281 | | * |
282 | | * This function is kept for compatibility reason and will call the new |
283 | | * variant GDALContourGenerateEx that is more extensible and provide more |
284 | | * options. |
285 | | * |
286 | | * Details about the algorithm are also given in the documentation of the |
287 | | * new GDALContourenerateEx function. |
288 | | * |
289 | | * @param hBand The band to read raster data from. The whole band will be |
290 | | * processed. |
291 | | * |
292 | | * @param dfContourInterval The elevation interval between contours generated. |
293 | | * |
294 | | * @param dfContourBase The "base" relative to which contour intervals are |
295 | | * applied. This is normally zero, but could be different. To generate 10m |
296 | | * contours at 5, 15, 25, ... the ContourBase would be 5. |
297 | | * |
298 | | * @param nFixedLevelCount The number of fixed levels. If this is greater than |
299 | | * zero, then fixed levels will be used, and ContourInterval and ContourBase |
300 | | * are ignored. |
301 | | * |
302 | | * @param padfFixedLevels The list of fixed contour levels at which contours |
303 | | * should be generated. It will contain FixedLevelCount entries, and may be |
304 | | * NULL if fixed levels are disabled (FixedLevelCount = 0). |
305 | | * |
306 | | * @param bUseNoData If TRUE the dfNoDataValue will be used. |
307 | | * |
308 | | * @param dfNoDataValue The value to use as a "nodata" value. That is, a |
309 | | * pixel value which should be ignored in generating contours as if the value |
310 | | * of the pixel were not known. |
311 | | * |
312 | | * @param hLayer The layer to which new contour vectors will be written. |
313 | | * Each contour will have a LINESTRING geometry attached to it. This |
314 | | * is really of type OGRLayerH, but void * is used to avoid pulling the |
315 | | * ogr_api.h file in here. |
316 | | * |
317 | | * @param iIDField If not -1 this will be used as a field index to indicate |
318 | | * where a unique id should be written for each feature (contour) written. |
319 | | * |
320 | | * @param iElevField If not -1 this will be used as a field index to indicate |
321 | | * where the elevation value of the contour should be written. |
322 | | * |
323 | | * @param pfnProgress A GDALProgressFunc that may be used to report progress |
324 | | * to the user, or to interrupt the algorithm. May be NULL if not required. |
325 | | * |
326 | | * @param pProgressArg The callback data for the pfnProgress function. |
327 | | * |
328 | | * @return CE_None on success or CE_Failure if an error occurs. |
329 | | */ |
330 | | |
331 | | CPLErr GDALContourGenerate(GDALRasterBandH hBand, double dfContourInterval, |
332 | | double dfContourBase, int nFixedLevelCount, |
333 | | double *padfFixedLevels, int bUseNoData, |
334 | | double dfNoDataValue, void *hLayer, int iIDField, |
335 | | int iElevField, GDALProgressFunc pfnProgress, |
336 | | void *pProgressArg) |
337 | 0 | { |
338 | 0 | char **options = nullptr; |
339 | 0 | if (nFixedLevelCount > 0) |
340 | 0 | { |
341 | 0 | std::string values = "FIXED_LEVELS="; |
342 | 0 | for (int i = 0; i < nFixedLevelCount; i++) |
343 | 0 | { |
344 | 0 | const int sz = 32; |
345 | 0 | char *newValue = new char[sz + 1]; |
346 | 0 | if (i == nFixedLevelCount - 1) |
347 | 0 | { |
348 | 0 | CPLsnprintf(newValue, sz + 1, "%f", padfFixedLevels[i]); |
349 | 0 | } |
350 | 0 | else |
351 | 0 | { |
352 | 0 | CPLsnprintf(newValue, sz + 1, "%f,", padfFixedLevels[i]); |
353 | 0 | } |
354 | 0 | values = values + std::string(newValue); |
355 | 0 | delete[] newValue; |
356 | 0 | } |
357 | 0 | options = CSLAddString(options, values.c_str()); |
358 | 0 | } |
359 | 0 | else if (dfContourInterval != 0.0) |
360 | 0 | { |
361 | 0 | options = |
362 | 0 | CSLAppendPrintf(options, "LEVEL_INTERVAL=%f", dfContourInterval); |
363 | 0 | } |
364 | |
|
365 | 0 | if (dfContourBase != 0.0) |
366 | 0 | { |
367 | 0 | options = CSLAppendPrintf(options, "LEVEL_BASE=%f", dfContourBase); |
368 | 0 | } |
369 | |
|
370 | 0 | if (bUseNoData) |
371 | 0 | { |
372 | 0 | options = CSLAppendPrintf(options, "NODATA=%.19g", dfNoDataValue); |
373 | 0 | } |
374 | 0 | if (iIDField != -1) |
375 | 0 | { |
376 | 0 | options = CSLAppendPrintf(options, "ID_FIELD=%d", iIDField); |
377 | 0 | } |
378 | 0 | if (iElevField != -1) |
379 | 0 | { |
380 | 0 | options = CSLAppendPrintf(options, "ELEV_FIELD=%d", iElevField); |
381 | 0 | } |
382 | |
|
383 | 0 | CPLErr err = GDALContourGenerateEx(hBand, hLayer, options, pfnProgress, |
384 | 0 | pProgressArg); |
385 | 0 | CSLDestroy(options); |
386 | |
|
387 | 0 | return err; |
388 | 0 | } |
389 | | |
390 | | /** |
391 | | * Create vector contours from raster DEM. |
392 | | * |
393 | | * This algorithm is an implementation of "Marching squares" [1] that will |
394 | | * generate contour vectors for the input raster band on the requested set |
395 | | * of contour levels. The vector contours are written to the passed in OGR |
396 | | * vector layer. Also, a NODATA value may be specified to identify pixels |
397 | | * that should not be considered in contour line generation. |
398 | | * |
399 | | * The gdal/apps/gdal_contour_bin.cpp mainline can be used as an example of |
400 | | * how to use this function. |
401 | | * |
402 | | * [1] see https://en.wikipedia.org/wiki/Marching_squares |
403 | | * |
404 | | * ALGORITHM RULES |
405 | | |
406 | | For contouring purposes raster pixel values are assumed to represent a point |
407 | | value at the center of the corresponding pixel region. For the purpose of |
408 | | contour generation we virtually connect each pixel center to the values to |
409 | | the left, right, top and bottom. We assume that the pixel value is linearly |
410 | | interpolated between the pixel centers along each line, and determine where |
411 | | (if any) contour lines will appear along these line segments. Then the |
412 | | contour crossings are connected. |
413 | | |
414 | | This means that contour lines' nodes will not actually be on pixel edges, but |
415 | | rather along vertical and horizontal lines connecting the pixel centers. |
416 | | |
417 | | \verbatim |
418 | | General Case: |
419 | | |
420 | | 5 | | 3 |
421 | | -- + ---------------- + -- |
422 | | | | |
423 | | | | |
424 | | | | |
425 | | | | |
426 | | 10 + | |
427 | | |\ | |
428 | | | \ | |
429 | | -- + -+-------------- + -- |
430 | | 12 | 10 | 1 |
431 | | |
432 | | Saddle Point: |
433 | | |
434 | | 5 | | 12 |
435 | | -- + -------------+-- + -- |
436 | | | \ | |
437 | | | \| |
438 | | | + |
439 | | | | |
440 | | + | |
441 | | |\ | |
442 | | | \ | |
443 | | -- + -+-------------- + -- |
444 | | 12 | | 1 |
445 | | |
446 | | or: |
447 | | |
448 | | 5 | | 12 |
449 | | -- + -------------+-- + -- |
450 | | | __/ | |
451 | | | ___/ | |
452 | | | ___/ __+ |
453 | | | / __/ | |
454 | | +' __/ | |
455 | | | __/ | |
456 | | | ,__/ | |
457 | | -- + -+-------------- + -- |
458 | | 12 | | 1 |
459 | | \endverbatim |
460 | | |
461 | | Nodata: |
462 | | |
463 | | In the "nodata" case we treat the whole nodata pixel as a no-mans land. |
464 | | We extend the corner pixels near the nodata out to half way and then |
465 | | construct extra lines from those points to the center which is assigned |
466 | | an averaged value from the two nearby points (in this case (12+3+5)/3). |
467 | | |
468 | | \verbatim |
469 | | 5 | | 3 |
470 | | -- + ---------------- + -- |
471 | | | | |
472 | | | | |
473 | | | 6.7 | |
474 | | | +---------+ 3 |
475 | | 10 +___ | |
476 | | | \____+ 10 |
477 | | | | |
478 | | -- + -------+ + |
479 | | 12 | 12 (nodata) |
480 | | |
481 | | \endverbatim |
482 | | |
483 | | * |
484 | | * @param hBand The band to read raster data from. The whole band will be |
485 | | * processed. |
486 | | * |
487 | | * @param hLayer The layer to which new contour vectors will be written. |
488 | | * Each contour will have a LINESTRING geometry attached to it |
489 | | * (or POLYGON if POLYGONIZE=YES). This is really of type OGRLayerH, but |
490 | | * void * is used to avoid pulling the ogr_api.h file in here. |
491 | | * |
492 | | * @param pfnProgress A GDALProgressFunc that may be used to report progress |
493 | | * to the user, or to interrupt the algorithm. May be NULL if not required. |
494 | | * |
495 | | * @param pProgressArg The callback data for the pfnProgress function. |
496 | | * |
497 | | * @param options List of options |
498 | | * |
499 | | * Options: |
500 | | * |
501 | | * LEVEL_INTERVAL=f |
502 | | * |
503 | | * The elevation interval between contours generated. |
504 | | * |
505 | | * LEVEL_BASE=f |
506 | | * |
507 | | * The "base" relative to which contour intervals are |
508 | | * applied. This is normally zero, but could be different. To generate 10m |
509 | | * contours at 5, 15, 25, ... the LEVEL_BASE would be 5. |
510 | | * |
511 | | * LEVEL_EXP_BASE=f |
512 | | * |
513 | | * If greater than 0, contour levels are generated on an |
514 | | * exponential scale. Levels will then be generated by LEVEL_EXP_BASE^k |
515 | | * where k is a positive integer. |
516 | | * |
517 | | * FIXED_LEVELS=f[,f]* |
518 | | * |
519 | | * The list of fixed contour levels at which contours should be generated. |
520 | | * This option has precedence on LEVEL_INTERVAL. MIN and MAX can be used |
521 | | * as special values to represent the minimum and maximum values of the |
522 | | * raster. |
523 | | * |
524 | | * NODATA=f |
525 | | * |
526 | | * The value to use as a "nodata" value. That is, a pixel value which |
527 | | * should be ignored in generating contours as if the value of the pixel |
528 | | * were not known. |
529 | | * |
530 | | * ID_FIELD=d |
531 | | * |
532 | | * This will be used as a field index to indicate where a unique id should |
533 | | * be written for each feature (contour) written. |
534 | | * |
535 | | * ELEV_FIELD=d |
536 | | * |
537 | | * This will be used as a field index to indicate where the elevation value |
538 | | * of the contour should be written. Only used in line contouring mode. |
539 | | * |
540 | | * ELEV_FIELD_MIN=d |
541 | | * |
542 | | * This will be used as a field index to indicate where the minimum elevation |
543 | | value |
544 | | * of the polygon contour should be written. Only used in polygonal contouring |
545 | | mode. |
546 | | * |
547 | | * ELEV_FIELD_MAX=d |
548 | | * |
549 | | * This will be used as a field index to indicate where the maximum elevation |
550 | | value |
551 | | * of the polygon contour should be written. Only used in polygonal contouring |
552 | | mode. |
553 | | * |
554 | | * POLYGONIZE=YES|NO |
555 | | * |
556 | | * If YES, contour polygons will be created, rather than polygon lines. |
557 | | * |
558 | | * |
559 | | * COMMIT_INTERVAL=num |
560 | | * |
561 | | * (GDAL >= 3.10) Interval in number of features at which transactions must be |
562 | | * flushed. The default value of 0 means that no transactions are opened. |
563 | | * A negative value means a single transaction. The function takes care of |
564 | | * issuing the starting transaction and committing the final one. |
565 | | * |
566 | | * @return CE_None on success or CE_Failure if an error occurs. |
567 | | */ |
568 | | CPLErr GDALContourGenerateEx(GDALRasterBandH hBand, void *hLayer, |
569 | | CSLConstList options, GDALProgressFunc pfnProgress, |
570 | | void *pProgressArg) |
571 | 0 | { |
572 | 0 | VALIDATE_POINTER1(hBand, "GDALContourGenerateEx", CE_Failure); |
573 | | |
574 | 0 | if (pfnProgress == nullptr) |
575 | 0 | pfnProgress = GDALDummyProgress; |
576 | |
|
577 | 0 | double contourInterval = 0.0; |
578 | 0 | const char *opt = CSLFetchNameValue(options, "LEVEL_INTERVAL"); |
579 | 0 | if (opt) |
580 | 0 | { |
581 | 0 | contourInterval = CPLAtof(opt); |
582 | | // Written this way to catch NaN as well. |
583 | 0 | if (!(contourInterval > 0)) |
584 | 0 | { |
585 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
586 | 0 | "Invalid value for LEVEL_INTERVAL. Should be strictly " |
587 | 0 | "positive."); |
588 | 0 | return CE_Failure; |
589 | 0 | } |
590 | 0 | } |
591 | | |
592 | 0 | double contourBase = 0.0; |
593 | 0 | opt = CSLFetchNameValue(options, "LEVEL_BASE"); |
594 | 0 | if (opt) |
595 | 0 | { |
596 | 0 | contourBase = CPLAtof(opt); |
597 | 0 | } |
598 | |
|
599 | 0 | double expBase = 0.0; |
600 | 0 | opt = CSLFetchNameValue(options, "LEVEL_EXP_BASE"); |
601 | 0 | if (opt) |
602 | 0 | { |
603 | 0 | expBase = CPLAtof(opt); |
604 | 0 | } |
605 | |
|
606 | 0 | std::vector<double> fixedLevels; |
607 | 0 | opt = CSLFetchNameValue(options, "FIXED_LEVELS"); |
608 | 0 | if (opt) |
609 | 0 | { |
610 | 0 | const CPLStringList aosLevels( |
611 | 0 | CSLTokenizeStringComplex(opt, ",", FALSE, FALSE)); |
612 | 0 | fixedLevels.resize(aosLevels.size()); |
613 | 0 | for (size_t i = 0; i < fixedLevels.size(); i++) |
614 | 0 | { |
615 | | // Handle min/max values |
616 | 0 | if (EQUAL(aosLevels[i], "MIN")) |
617 | 0 | { |
618 | 0 | fixedLevels[i] = std::numeric_limits<double>::lowest(); |
619 | 0 | } |
620 | 0 | else if (EQUAL(aosLevels[i], "MAX")) |
621 | 0 | { |
622 | 0 | fixedLevels[i] = std::numeric_limits<double>::max(); |
623 | 0 | } |
624 | 0 | else |
625 | 0 | { |
626 | 0 | fixedLevels[i] = CPLAtof(aosLevels[i]); |
627 | 0 | } |
628 | 0 | if (i > 0 && !(fixedLevels[i] >= fixedLevels[i - 1])) |
629 | 0 | { |
630 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
631 | 0 | "FIXED_LEVELS should be strictly increasing"); |
632 | 0 | return CE_Failure; |
633 | 0 | } |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | 0 | bool useNoData = false; |
638 | 0 | double noDataValue = 0.0; |
639 | 0 | opt = CSLFetchNameValue(options, "NODATA"); |
640 | 0 | if (opt) |
641 | 0 | { |
642 | 0 | useNoData = true; |
643 | 0 | noDataValue = CPLAtof(opt); |
644 | 0 | if (GDALGetRasterDataType(hBand) == GDT_Float32) |
645 | 0 | { |
646 | 0 | int bClamped = FALSE; |
647 | 0 | int bRounded = FALSE; |
648 | 0 | noDataValue = GDALAdjustValueToDataType(GDT_Float32, noDataValue, |
649 | 0 | &bClamped, &bRounded); |
650 | 0 | } |
651 | 0 | } |
652 | |
|
653 | 0 | int idField = -1; |
654 | 0 | opt = CSLFetchNameValue(options, "ID_FIELD"); |
655 | 0 | if (opt) |
656 | 0 | { |
657 | 0 | idField = atoi(opt); |
658 | 0 | } |
659 | |
|
660 | 0 | int elevField = -1; |
661 | 0 | opt = CSLFetchNameValue(options, "ELEV_FIELD"); |
662 | 0 | if (opt) |
663 | 0 | { |
664 | 0 | elevField = atoi(opt); |
665 | 0 | } |
666 | |
|
667 | 0 | int elevFieldMin = -1; |
668 | 0 | opt = CSLFetchNameValue(options, "ELEV_FIELD_MIN"); |
669 | 0 | if (opt) |
670 | 0 | { |
671 | 0 | elevFieldMin = atoi(opt); |
672 | 0 | } |
673 | |
|
674 | 0 | int elevFieldMax = -1; |
675 | 0 | opt = CSLFetchNameValue(options, "ELEV_FIELD_MAX"); |
676 | 0 | if (opt) |
677 | 0 | { |
678 | 0 | elevFieldMax = atoi(opt); |
679 | 0 | } |
680 | |
|
681 | 0 | bool polygonize = CPLFetchBool(options, "POLYGONIZE", false); |
682 | |
|
683 | 0 | using namespace marching_squares; |
684 | |
|
685 | 0 | OGRContourWriterInfo oCWI; |
686 | 0 | oCWI.hLayer = static_cast<OGRLayerH>(hLayer); |
687 | 0 | oCWI.nElevField = elevField; |
688 | 0 | oCWI.nElevFieldMin = elevFieldMin; |
689 | 0 | oCWI.nElevFieldMax = elevFieldMax; |
690 | 0 | oCWI.nIDField = idField; |
691 | 0 | oCWI.adfGeoTransform[0] = 0.0; |
692 | 0 | oCWI.adfGeoTransform[1] = 1.0; |
693 | 0 | oCWI.adfGeoTransform[2] = 0.0; |
694 | 0 | oCWI.adfGeoTransform[3] = 0.0; |
695 | 0 | oCWI.adfGeoTransform[4] = 0.0; |
696 | 0 | oCWI.adfGeoTransform[5] = 1.0; |
697 | 0 | GDALDatasetH hSrcDS = GDALGetBandDataset(hBand); |
698 | 0 | if (hSrcDS != nullptr) |
699 | 0 | GDALGetGeoTransform(hSrcDS, oCWI.adfGeoTransform); |
700 | 0 | oCWI.nNextID = 0; |
701 | 0 | oCWI.nWrittenFeatureCountSinceLastCommit = 0; |
702 | 0 | oCWI.nTransactionCommitInterval = |
703 | 0 | CPLAtoGIntBig(CSLFetchNameValueDef(options, "COMMIT_INTERVAL", "0")); |
704 | |
|
705 | 0 | if (oCWI.nTransactionCommitInterval < 0) |
706 | 0 | oCWI.nTransactionCommitInterval = std::numeric_limits<GIntBig>::max(); |
707 | 0 | if (oCWI.nTransactionCommitInterval > 0) |
708 | 0 | { |
709 | 0 | if (OGR_L_StartTransaction(static_cast<OGRLayerH>(hLayer)) != |
710 | 0 | OGRERR_NONE) |
711 | 0 | { |
712 | 0 | oCWI.nTransactionCommitInterval = 0; |
713 | 0 | } |
714 | 0 | } |
715 | |
|
716 | 0 | int bSuccessMin = FALSE; |
717 | 0 | double dfMinimum = GDALGetRasterMinimum(hBand, &bSuccessMin); |
718 | 0 | int bSuccessMax = FALSE; |
719 | 0 | double dfMaximum = GDALGetRasterMaximum(hBand, &bSuccessMax); |
720 | 0 | if ((!bSuccessMin || !bSuccessMax)) |
721 | 0 | { |
722 | 0 | double adfMinMax[2]; |
723 | 0 | if (GDALComputeRasterMinMax(hBand, false, adfMinMax) == CE_None) |
724 | 0 | { |
725 | 0 | dfMinimum = adfMinMax[0]; |
726 | 0 | dfMaximum = adfMinMax[1]; |
727 | 0 | } |
728 | 0 | } |
729 | |
|
730 | 0 | bool ok = true; |
731 | | |
732 | | // Replace fixed levels min/max values with raster min/max values |
733 | 0 | if (!fixedLevels.empty()) |
734 | 0 | { |
735 | 0 | if (fixedLevels[0] == std::numeric_limits<double>::lowest()) |
736 | 0 | { |
737 | 0 | fixedLevels[0] = dfMinimum; |
738 | 0 | } |
739 | 0 | if (fixedLevels.back() == std::numeric_limits<double>::max()) |
740 | 0 | { |
741 | 0 | fixedLevels.back() = dfMaximum; |
742 | 0 | } |
743 | 0 | } |
744 | |
|
745 | 0 | try |
746 | 0 | { |
747 | 0 | if (polygonize) |
748 | 0 | { |
749 | |
|
750 | 0 | if (!fixedLevels.empty()) |
751 | 0 | { |
752 | | |
753 | | // If the minimum raster value is larger than the first requested |
754 | | // level, select the requested level that is just below the |
755 | | // minimum raster value |
756 | 0 | if (fixedLevels[0] < dfMinimum) |
757 | 0 | { |
758 | 0 | for (size_t i = 1; i < fixedLevels.size(); ++i) |
759 | 0 | { |
760 | 0 | if (fixedLevels[i] >= dfMinimum) |
761 | 0 | { |
762 | 0 | dfMinimum = fixedLevels[i - 1]; |
763 | 0 | break; |
764 | 0 | } |
765 | 0 | } |
766 | 0 | } |
767 | | |
768 | | // Largest requested level (levels are sorted) |
769 | 0 | const double maxLevel{fixedLevels.back()}; |
770 | | |
771 | | // If the maximum raster value is smaller than the last requested |
772 | | // level, select the requested level that is just above the |
773 | | // maximum raster value |
774 | 0 | if (maxLevel > dfMaximum) |
775 | 0 | { |
776 | 0 | for (size_t i = fixedLevels.size() - 1; i > 0; --i) |
777 | 0 | { |
778 | 0 | if (fixedLevels[i] <= dfMaximum) |
779 | 0 | { |
780 | 0 | dfMaximum = fixedLevels[i + 1]; |
781 | 0 | break; |
782 | 0 | } |
783 | 0 | } |
784 | 0 | } |
785 | | |
786 | | // If the maximum raster value is equal to the last requested |
787 | | // level, add a small value to the maximum to avoid skipping the |
788 | | // last level polygons |
789 | 0 | if (maxLevel == dfMaximum) |
790 | 0 | { |
791 | 0 | dfMaximum = std::nextafter( |
792 | 0 | dfMaximum, std::numeric_limits<double>::infinity()); |
793 | 0 | } |
794 | 0 | } |
795 | |
|
796 | 0 | PolygonContourWriter w(&oCWI, dfMinimum); |
797 | 0 | typedef PolygonRingAppender<PolygonContourWriter> RingAppender; |
798 | 0 | RingAppender appender(w); |
799 | |
|
800 | 0 | if (expBase > 0.0) |
801 | 0 | { |
802 | | // Do not provide the actual minimum value to level iterator |
803 | | // in polygonal case, otherwise it can result in a polygon |
804 | | // with a degenerate min=max range. |
805 | 0 | ExponentialLevelRangeIterator generator( |
806 | 0 | expBase, -std::numeric_limits<double>::infinity()); |
807 | 0 | auto levelIt{generator.range(dfMinimum, dfMaximum)}; |
808 | 0 | for (auto i = levelIt.begin(); i != levelIt.end(); ++i) |
809 | 0 | { |
810 | 0 | const double level = (*i).second; |
811 | 0 | fixedLevels.push_back(level); |
812 | 0 | } |
813 | | // Append minimum value to fixed levels |
814 | 0 | fixedLevels.push_back(dfMinimum); |
815 | | // Append maximum value to fixed levels |
816 | 0 | fixedLevels.push_back(dfMaximum); |
817 | 0 | } |
818 | 0 | else if (contourInterval != 0) |
819 | 0 | { |
820 | | // Do not provide the actual minimum value to level iterator |
821 | | // in polygonal case, otherwise it can result in a polygon |
822 | | // with a degenerate min=max range. |
823 | 0 | IntervalLevelRangeIterator generator( |
824 | 0 | contourBase, contourInterval, |
825 | 0 | -std::numeric_limits<double>::infinity()); |
826 | 0 | auto levelIt{generator.range(dfMinimum, dfMaximum)}; |
827 | 0 | for (auto i = levelIt.begin(); i != levelIt.end(); ++i) |
828 | 0 | { |
829 | 0 | const double level = (*i).second; |
830 | 0 | fixedLevels.push_back(level); |
831 | 0 | } |
832 | | // Append minimum value to fixed levels |
833 | 0 | fixedLevels.push_back(dfMinimum); |
834 | | // Append maximum value to fixed levels |
835 | 0 | fixedLevels.push_back(dfMaximum); |
836 | 0 | } |
837 | |
|
838 | 0 | if (!fixedLevels.empty()) |
839 | 0 | { |
840 | 0 | std::sort(fixedLevels.begin(), fixedLevels.end()); |
841 | 0 | auto uniqueIt = |
842 | 0 | std::unique(fixedLevels.begin(), fixedLevels.end()); |
843 | 0 | fixedLevels.erase(uniqueIt, fixedLevels.end()); |
844 | | // Do not provide the actual minimum value to level iterator |
845 | | // in polygonal case, otherwise it can result in a polygon |
846 | | // with a degenerate min=max range. |
847 | 0 | FixedLevelRangeIterator levels( |
848 | 0 | &fixedLevels[0], fixedLevels.size(), |
849 | 0 | -std::numeric_limits<double>::infinity(), dfMaximum); |
850 | 0 | SegmentMerger<RingAppender, FixedLevelRangeIterator> writer( |
851 | 0 | appender, levels, /* polygonize */ true); |
852 | 0 | std::vector<int> aoiSkipLevels; |
853 | | // Skip first and last levels (min/max) in polygonal case |
854 | 0 | aoiSkipLevels.push_back(0); |
855 | 0 | aoiSkipLevels.push_back(static_cast<int>(levels.levelsCount())); |
856 | 0 | writer.setSkipLevels(aoiSkipLevels); |
857 | 0 | ContourGeneratorFromRaster<decltype(writer), |
858 | 0 | FixedLevelRangeIterator> |
859 | 0 | cg(hBand, useNoData, noDataValue, writer, levels); |
860 | 0 | ok = cg.process(pfnProgress, pProgressArg); |
861 | 0 | } |
862 | 0 | } |
863 | 0 | else |
864 | 0 | { |
865 | 0 | GDALRingAppender appender(OGRContourWriter, &oCWI); |
866 | | |
867 | | // Append all exp levels to fixed levels |
868 | 0 | if (expBase > 0.0) |
869 | 0 | { |
870 | 0 | ExponentialLevelRangeIterator generator(expBase, dfMinimum); |
871 | 0 | auto levelIt{generator.range(dfMinimum, dfMaximum)}; |
872 | 0 | for (auto i = levelIt.begin(); i != levelIt.end(); ++i) |
873 | 0 | { |
874 | 0 | const double level = (*i).second; |
875 | 0 | fixedLevels.push_back(level); |
876 | 0 | } |
877 | 0 | } |
878 | 0 | else if (contourInterval != 0) |
879 | 0 | { |
880 | 0 | IntervalLevelRangeIterator levels(contourBase, contourInterval, |
881 | 0 | dfMinimum); |
882 | 0 | auto levelIt{levels.range(dfMinimum, dfMaximum)}; |
883 | 0 | for (auto i = levelIt.begin(); i != levelIt.end(); ++i) |
884 | 0 | { |
885 | 0 | const double level = (*i).second; |
886 | 0 | fixedLevels.push_back(level); |
887 | 0 | } |
888 | 0 | } |
889 | |
|
890 | 0 | if (!fixedLevels.empty()) |
891 | 0 | { |
892 | 0 | std::sort(fixedLevels.begin(), fixedLevels.end()); |
893 | 0 | auto uniqueIt = |
894 | 0 | std::unique(fixedLevels.begin(), fixedLevels.end()); |
895 | 0 | fixedLevels.erase(uniqueIt, fixedLevels.end()); |
896 | 0 | FixedLevelRangeIterator levels( |
897 | 0 | &fixedLevels[0], fixedLevels.size(), dfMinimum, dfMaximum); |
898 | 0 | SegmentMerger<GDALRingAppender, FixedLevelRangeIterator> writer( |
899 | 0 | appender, levels, /* polygonize */ false); |
900 | 0 | ContourGeneratorFromRaster<decltype(writer), |
901 | 0 | FixedLevelRangeIterator> |
902 | 0 | cg(hBand, useNoData, noDataValue, writer, levels); |
903 | 0 | ok = cg.process(pfnProgress, pProgressArg); |
904 | 0 | } |
905 | 0 | } |
906 | 0 | } |
907 | 0 | catch (const std::exception &e) |
908 | 0 | { |
909 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what()); |
910 | 0 | return CE_Failure; |
911 | 0 | } |
912 | | |
913 | 0 | if (oCWI.nTransactionCommitInterval > 0) |
914 | 0 | { |
915 | | // CPLDebug("CONTOUR", "Flush transaction"); |
916 | 0 | if (OGR_L_CommitTransaction(static_cast<OGRLayerH>(hLayer)) != |
917 | 0 | OGRERR_NONE) |
918 | 0 | { |
919 | 0 | ok = false; |
920 | 0 | } |
921 | 0 | } |
922 | |
|
923 | 0 | if (ok) |
924 | 0 | { |
925 | 0 | pfnProgress(1.0, "", pProgressArg); |
926 | 0 | } |
927 | |
|
928 | 0 | return ok ? CE_None : CE_Failure; |
929 | 0 | } |
930 | | |
931 | | /************************************************************************/ |
932 | | /* GDAL_CG_Create() */ |
933 | | /************************************************************************/ |
934 | | |
935 | | namespace marching_squares |
936 | | { |
937 | | |
938 | | // Opaque type used by the C API |
939 | | struct ContourGeneratorOpaque |
940 | | { |
941 | | typedef SegmentMerger<GDALRingAppender, IntervalLevelRangeIterator> |
942 | | SegmentMergerT; |
943 | | typedef ContourGenerator<SegmentMergerT, IntervalLevelRangeIterator> |
944 | | ContourGeneratorT; |
945 | | |
946 | | ContourGeneratorOpaque(int nWidth, int nHeight, int bNoDataSet, |
947 | | double dfNoDataValue, double dfContourInterval, |
948 | | double dfContourBase, GDALContourWriter pfnWriter, |
949 | | void *pCBData) |
950 | 0 | : levels(dfContourBase, dfContourInterval, |
951 | 0 | -std::numeric_limits<double>::infinity()), |
952 | 0 | writer(pfnWriter, pCBData), |
953 | 0 | merger(writer, levels, /* polygonize */ false), |
954 | 0 | contourGenerator(nWidth, nHeight, bNoDataSet != 0, dfNoDataValue, |
955 | 0 | merger, levels) |
956 | 0 | { |
957 | 0 | } |
958 | | |
959 | | IntervalLevelRangeIterator levels; |
960 | | GDALRingAppender writer; |
961 | | SegmentMergerT merger; |
962 | | ContourGeneratorT contourGenerator; |
963 | | }; |
964 | | |
965 | | } // namespace marching_squares |
966 | | |
967 | | /** Create contour generator */ |
968 | | GDALContourGeneratorH GDAL_CG_Create(int nWidth, int nHeight, int bNoDataSet, |
969 | | double dfNoDataValue, |
970 | | double dfContourInterval, |
971 | | double dfContourBase, |
972 | | GDALContourWriter pfnWriter, void *pCBData) |
973 | | |
974 | 0 | { |
975 | 0 | auto cg = new marching_squares::ContourGeneratorOpaque( |
976 | 0 | nWidth, nHeight, bNoDataSet, dfNoDataValue, dfContourInterval, |
977 | 0 | dfContourBase, pfnWriter, pCBData); |
978 | |
|
979 | 0 | return reinterpret_cast<GDALContourGeneratorH>(cg); |
980 | 0 | } |
981 | | |
982 | | /************************************************************************/ |
983 | | /* GDAL_CG_FeedLine() */ |
984 | | /************************************************************************/ |
985 | | |
986 | | /** Feed a line to the contour generator */ |
987 | | CPLErr GDAL_CG_FeedLine(GDALContourGeneratorH hCG, double *padfScanline) |
988 | | |
989 | 0 | { |
990 | 0 | VALIDATE_POINTER1(hCG, "GDAL_CG_FeedLine", CE_Failure); |
991 | 0 | return reinterpret_cast<marching_squares::ContourGeneratorOpaque *>(hCG) |
992 | 0 | ->contourGenerator.feedLine(padfScanline); |
993 | 0 | } |
994 | | |
995 | | /************************************************************************/ |
996 | | /* GDAL_CG_Destroy() */ |
997 | | /************************************************************************/ |
998 | | |
999 | | /** Destroy contour generator */ |
1000 | | void GDAL_CG_Destroy(GDALContourGeneratorH hCG) |
1001 | | |
1002 | 0 | { |
1003 | 0 | delete reinterpret_cast<marching_squares::ContourGeneratorOpaque *>(hCG); |
1004 | 0 | } |