Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: High Performance Image Reprojector
4 : * Purpose: Test program for high performance warper API.
5 : * Author: Frank Warmerdam <warmerdam@pobox.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2002, i3 - information integration and imaging
9 : * Fort Collins, CO
10 : * Copyright (c) 2007-2015, Even Rouault <even dot rouault at spatialys.com>
11 : * Copyright (c) 2015, Faza Mahamood
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include "cpl_port.h"
17 : #include "gdal_utils.h"
18 : #include "gdal_utils_priv.h"
19 : #include "gdalargumentparser.h"
20 :
21 : #include <cctype>
22 : #include <cmath>
23 : #include <cstdio>
24 : #include <cstdlib>
25 : #include <cstring>
26 :
27 : #include <algorithm>
28 : #include <array>
29 : #include <limits>
30 : #include <set>
31 : #include <utility>
32 : #include <vector>
33 :
34 : // Suppress deprecation warning for GDALOpenVerticalShiftGrid and
35 : // GDALApplyVerticalShiftGrid
36 : #ifndef CPL_WARN_DEPRECATED_GDALOpenVerticalShiftGrid
37 : #define CPL_WARN_DEPRECATED_GDALOpenVerticalShiftGrid(x)
38 : #define CPL_WARN_DEPRECATED_GDALApplyVerticalShiftGrid(x)
39 : #endif
40 :
41 : #include "commonutils.h"
42 : #include "cpl_conv.h"
43 : #include "cpl_error.h"
44 : #include "cpl_progress.h"
45 : #include "cpl_string.h"
46 : #include "gdal.h"
47 : #include "gdal_alg.h"
48 : #include "gdal_alg_priv.h"
49 : #include "gdal_priv.h"
50 : #include "gdalwarper.h"
51 : #include "ogr_api.h"
52 : #include "ogr_core.h"
53 : #include "ogr_geometry.h"
54 : #include "ogr_spatialref.h"
55 : #include "ogr_srs_api.h"
56 : #include "ogr_proj_p.h"
57 : #include "ogrct_priv.h"
58 : #include "ogrsf_frmts.h"
59 : #include "vrtdataset.h"
60 : #include "../frmts/gtiff/cogdriver.h"
61 :
62 : #if PROJ_VERSION_MAJOR > 6 || PROJ_VERSION_MINOR >= 3
63 : #define USE_PROJ_BASED_VERTICAL_SHIFT_METHOD
64 : #endif
65 :
66 : /************************************************************************/
67 : /* GDALWarpAppOptions */
68 : /************************************************************************/
69 :
70 : /** Options for use with GDALWarp(). GDALWarpAppOptions* must be allocated and
71 : * freed with GDALWarpAppOptionsNew() and GDALWarpAppOptionsFree() respectively.
72 : */
73 : struct GDALWarpAppOptions
74 : {
75 : /*! Raw program arguments */
76 : CPLStringList aosArgs{};
77 :
78 : /*! set georeferenced extents of output file to be created (in target SRS by
79 : default, or in the SRS specified with pszTE_SRS) */
80 : double dfMinX = 0;
81 : double dfMinY = 0;
82 : double dfMaxX = 0;
83 : double dfMaxY = 0;
84 :
85 : /*! the SRS in which to interpret the coordinates given in
86 : GDALWarpAppOptions::dfMinX, GDALWarpAppOptions::dfMinY,
87 : GDALWarpAppOptions::dfMaxX and GDALWarpAppOptions::dfMaxY. The SRS may be
88 : any of the usual GDAL/OGR forms, complete WKT, PROJ.4, EPSG:n or a file
89 : containing the WKT. It is a convenience e.g. when knowing the output
90 : coordinates in a geodetic long/lat SRS, but still wanting a result in a
91 : projected coordinate system. */
92 : std::string osTE_SRS{};
93 :
94 : /*! set output file resolution (in target georeferenced units) */
95 : double dfXRes = 0;
96 : double dfYRes = 0;
97 :
98 : /*! whether target pixels should have dfXRes == dfYRes */
99 : bool bSquarePixels = false;
100 :
101 : /*! align the coordinates of the extent of the output file to the values of
102 : the GDALWarpAppOptions::dfXRes and GDALWarpAppOptions::dfYRes, such that
103 : the aligned extent includes the minimum extent. */
104 : bool bTargetAlignedPixels = false;
105 :
106 : /*! set output file size in pixels and lines. If
107 : GDALWarpAppOptions::nForcePixels or GDALWarpAppOptions::nForceLines is
108 : set to 0, the other dimension will be guessed from the computed
109 : resolution. Note that GDALWarpAppOptions::nForcePixels and
110 : GDALWarpAppOptions::nForceLines cannot be used with
111 : GDALWarpAppOptions::dfXRes and GDALWarpAppOptions::dfYRes. */
112 : int nForcePixels = 0;
113 : int nForceLines = 0;
114 :
115 : /*! allow or suppress progress monitor and other non-error output */
116 : bool bQuiet = true;
117 :
118 : /*! the progress function to use */
119 : GDALProgressFunc pfnProgress = GDALDummyProgress;
120 :
121 : /*! pointer to the progress data variable */
122 : void *pProgressData = nullptr;
123 :
124 : /*! creates an output alpha band to identify nodata (unset/transparent)
125 : pixels when set to true */
126 : bool bEnableDstAlpha = false;
127 :
128 : /*! forces the last band of an input file to be considered as alpha band. */
129 : bool bEnableSrcAlpha = false;
130 :
131 : /*! Prevent a source alpha band from being considered as such */
132 : bool bDisableSrcAlpha = false;
133 :
134 : /*! output format. Use the short format name. */
135 : std::string osFormat{};
136 :
137 : bool bCreateOutput = false;
138 :
139 : /*! list of warp options. ("NAME1=VALUE1","NAME2=VALUE2",...). The
140 : GDALWarpOptions::aosWarpOptions docs show all options. */
141 : CPLStringList aosWarpOptions{};
142 :
143 : double dfErrorThreshold = -1;
144 :
145 : /*! the amount of memory (in megabytes) that the warp API is allowed
146 : to use for caching. */
147 : double dfWarpMemoryLimit = 0;
148 :
149 : /*! list of create options for the output format driver. See format
150 : specific documentation for legal creation options for each format. */
151 : CPLStringList aosCreateOptions{};
152 :
153 : /*! the data type of the output bands */
154 : GDALDataType eOutputType = GDT_Unknown;
155 :
156 : /*! working pixel data type. The data type of pixels in the source
157 : image and destination image buffers. */
158 : GDALDataType eWorkingType = GDT_Unknown;
159 :
160 : /*! the resampling method. Available methods are: near, bilinear,
161 : cubic, cubicspline, lanczos, average, mode, max, min, med,
162 : q1, q3, sum */
163 : GDALResampleAlg eResampleAlg = GRA_NearestNeighbour;
164 :
165 : /*! whether -r was specified */
166 : bool bResampleAlgSpecifiedByUser = false;
167 :
168 : /*! nodata masking values for input bands (different values can be supplied
169 : for each band). ("value1 value2 ..."). Masked values will not be used
170 : in interpolation. Use a value of "None" to ignore intrinsic nodata
171 : settings on the source dataset. */
172 : std::string osSrcNodata{};
173 :
174 : /*! nodata values for output bands (different values can be supplied for
175 : each band). ("value1 value2 ..."). New files will be initialized to
176 : this value and if possible the nodata value will be recorded in the
177 : output file. Use a value of "None" to ensure that nodata is not defined.
178 : If this argument is not used then nodata values will be copied from
179 : the source dataset. */
180 : std::string osDstNodata{};
181 :
182 : /*! use multithreaded warping implementation. Multiple threads will be used
183 : to process chunks of image and perform input/output operation
184 : simultaneously. */
185 : bool bMulti = false;
186 :
187 : /*! list of transformer options suitable to pass to
188 : GDALCreateGenImgProjTransformer2().
189 : ("NAME1=VALUE1","NAME2=VALUE2",...) */
190 : CPLStringList aosTransformerOptions{};
191 :
192 : /*! enable use of a blend cutline from a vector dataset name or a WKT
193 : * geometry
194 : */
195 : std::string osCutlineDSNameOrWKT{};
196 :
197 : /*! cutline SRS */
198 : std::string osCutlineSRS{};
199 :
200 : /*! the named layer to be selected from the cutline datasource */
201 : std::string osCLayer{};
202 :
203 : /*! restrict desired cutline features based on attribute query */
204 : std::string osCWHERE{};
205 :
206 : /*! SQL query to select the cutline features instead of from a layer
207 : with osCLayer */
208 : std::string osCSQL{};
209 :
210 : /*! crop the extent of the target dataset to the extent of the cutline */
211 : bool bCropToCutline = false;
212 :
213 : /*! copy dataset and band metadata will be copied from the first source
214 : dataset. Items that differ between source datasets will be set "*" (see
215 : GDALWarpAppOptions::pszMDConflictValue) */
216 : bool bCopyMetadata = true;
217 :
218 : /*! copy band information from the first source dataset */
219 : bool bCopyBandInfo = true;
220 :
221 : /*! value to set metadata items that conflict between source datasets
222 : (default is "*"). Use "" to remove conflicting items. */
223 : std::string osMDConflictValue = "*";
224 :
225 : /*! set the color interpretation of the bands of the target dataset from the
226 : * source dataset */
227 : bool bSetColorInterpretation = false;
228 :
229 : /*! overview level of source files to be used */
230 : int nOvLevel = OVR_LEVEL_AUTO;
231 :
232 : /*! Whether to enable vertical shift adjustment */
233 : bool bVShift = false;
234 :
235 : /*! Whether to disable vertical shift adjustment */
236 : bool bNoVShift = false;
237 :
238 : /*! Source bands */
239 : std::vector<int> anSrcBands{};
240 :
241 : /*! Destination bands */
242 : std::vector<int> anDstBands{};
243 :
244 : /*! Used when using a temporary TIFF file while warping */
245 : bool bDeleteOutputFileOnceCreated = false;
246 : };
247 :
248 : static CPLErr
249 : LoadCutline(const std::string &osCutlineDSNameOrWKT, const std::string &osSRS,
250 : const std::string &oszCLayer, const std::string &osCWHERE,
251 : const std::string &osCSQL, OGRGeometryH *phCutlineRet);
252 : static CPLErr TransformCutlineToSource(GDALDataset *poSrcDS,
253 : OGRGeometry *poCutline,
254 : char ***ppapszWarpOptions,
255 : CSLConstList papszTO);
256 :
257 : static GDALDatasetH GDALWarpCreateOutput(
258 : int nSrcCount, GDALDatasetH *pahSrcDS, const char *pszFilename,
259 : const char *pszFormat, char **papszTO, CSLConstList papszCreateOptions,
260 : GDALDataType eDT, GDALTransformerArgUniquePtr &hTransformArg,
261 : bool bSetColorInterpretation, GDALWarpAppOptions *psOptions,
262 : bool bUpdateTransformerWithDestGT);
263 :
264 : static void RemoveConflictingMetadata(GDALMajorObjectH hObj,
265 : CSLConstList papszMetadata,
266 : const char *pszValueConflict);
267 :
268 3 : static double GetAverageSegmentLength(const OGRGeometry *poGeom)
269 : {
270 3 : if (!poGeom)
271 0 : return 0;
272 3 : switch (wkbFlatten(poGeom->getGeometryType()))
273 : {
274 1 : case wkbLineString:
275 : {
276 1 : const auto *poLS = poGeom->toLineString();
277 1 : double dfSum = 0;
278 1 : const int nPoints = poLS->getNumPoints();
279 1 : if (nPoints == 0)
280 0 : return 0;
281 5 : for (int i = 0; i < nPoints - 1; i++)
282 : {
283 4 : double dfX1 = poLS->getX(i);
284 4 : double dfY1 = poLS->getY(i);
285 4 : double dfX2 = poLS->getX(i + 1);
286 4 : double dfY2 = poLS->getY(i + 1);
287 4 : double dfDX = dfX2 - dfX1;
288 4 : double dfDY = dfY2 - dfY1;
289 4 : dfSum += sqrt(dfDX * dfDX + dfDY * dfDY);
290 : }
291 1 : return dfSum / nPoints;
292 : }
293 :
294 1 : case wkbPolygon:
295 : {
296 1 : if (poGeom->IsEmpty())
297 0 : return 0;
298 1 : double dfSum = 0;
299 2 : for (const auto *poLS : poGeom->toPolygon())
300 : {
301 1 : dfSum += GetAverageSegmentLength(poLS);
302 : }
303 1 : return dfSum / (1 + poGeom->toPolygon()->getNumInteriorRings());
304 : }
305 :
306 1 : case wkbMultiPolygon:
307 : case wkbMultiLineString:
308 : case wkbGeometryCollection:
309 : {
310 1 : if (poGeom->IsEmpty())
311 0 : return 0;
312 1 : double dfSum = 0;
313 2 : for (const auto *poSubGeom : poGeom->toGeometryCollection())
314 : {
315 1 : dfSum += GetAverageSegmentLength(poSubGeom);
316 : }
317 1 : return dfSum / poGeom->toGeometryCollection()->getNumGeometries();
318 : }
319 :
320 0 : default:
321 0 : return 0;
322 : }
323 : }
324 :
325 : /************************************************************************/
326 : /* FetchSrcMethod() */
327 : /************************************************************************/
328 :
329 3083 : static const char *FetchSrcMethod(CSLConstList papszTO,
330 : const char *pszDefault = nullptr)
331 : {
332 3083 : const char *pszMethod = CSLFetchNameValue(papszTO, "SRC_METHOD");
333 3083 : if (!pszMethod)
334 3007 : pszMethod = CSLFetchNameValueDef(papszTO, "METHOD", pszDefault);
335 3083 : return pszMethod;
336 : }
337 :
338 1171 : static const char *FetchSrcMethod(const CPLStringList &aosTO,
339 : const char *pszDefault = nullptr)
340 : {
341 1171 : const char *pszMethod = aosTO.FetchNameValue("SRC_METHOD");
342 1171 : if (!pszMethod)
343 1137 : pszMethod = aosTO.FetchNameValueDef("METHOD", pszDefault);
344 1171 : return pszMethod;
345 : }
346 :
347 : /************************************************************************/
348 : /* GetSrcDSProjection() */
349 : /* */
350 : /* Takes into account SRC_SRS transformer option in priority, and then */
351 : /* dataset characteristics as well as the METHOD transformer */
352 : /* option to determine the source SRS. */
353 : /************************************************************************/
354 :
355 1299 : static CPLString GetSrcDSProjection(GDALDatasetH hDS, CSLConstList papszTO)
356 : {
357 1299 : const char *pszProjection = CSLFetchNameValue(papszTO, "SRC_SRS");
358 1299 : if (pszProjection != nullptr || hDS == nullptr)
359 : {
360 61 : return pszProjection ? pszProjection : "";
361 : }
362 :
363 1238 : const char *pszMethod = FetchSrcMethod(papszTO);
364 1238 : CSLConstList papszMD = nullptr;
365 1238 : const OGRSpatialReferenceH hSRS = GDALGetSpatialRef(hDS);
366 : const char *pszGeolocationDataset =
367 1238 : CSLFetchNameValueDef(papszTO, "SRC_GEOLOC_ARRAY",
368 : CSLFetchNameValue(papszTO, "GEOLOC_ARRAY"));
369 1238 : if (pszGeolocationDataset != nullptr &&
370 2 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")))
371 : {
372 : auto aosMD =
373 3 : GDALCreateGeolocationMetadata(hDS, pszGeolocationDataset, true);
374 3 : pszProjection = aosMD.FetchNameValue("SRS");
375 3 : if (pszProjection)
376 3 : return pszProjection; // return in this scope so that aosMD is
377 : // still valid
378 : }
379 1235 : else if (hSRS && (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")))
380 : {
381 961 : char *pszWKT = nullptr;
382 : {
383 1922 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
384 961 : if (OSRExportToWkt(hSRS, &pszWKT) != OGRERR_NONE)
385 : {
386 0 : CPLFree(pszWKT);
387 0 : pszWKT = nullptr;
388 0 : const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
389 0 : OSRExportToWktEx(hSRS, &pszWKT, apszOptions);
390 : }
391 : }
392 1922 : CPLString osWKT = pszWKT ? pszWKT : "";
393 961 : CPLFree(pszWKT);
394 961 : return osWKT;
395 : }
396 274 : else if (GDALGetGCPProjection(hDS) != nullptr &&
397 274 : strlen(GDALGetGCPProjection(hDS)) > 0 &&
398 553 : GDALGetGCPCount(hDS) > 1 &&
399 5 : (pszMethod == nullptr || STARTS_WITH_CI(pszMethod, "GCP_")))
400 : {
401 27 : pszProjection = GDALGetGCPProjection(hDS);
402 : }
403 250 : else if (GDALGetMetadata(hDS, "RPC") != nullptr &&
404 3 : (pszMethod == nullptr || EQUAL(pszMethod, "RPC")))
405 : {
406 14 : pszProjection = SRS_WKT_WGS84_LAT_LONG;
407 : }
408 246 : else if ((papszMD = GDALGetMetadata(hDS, "GEOLOCATION")) != nullptr &&
409 13 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")))
410 : {
411 20 : pszProjection = CSLFetchNameValue(papszMD, "SRS");
412 : }
413 274 : return pszProjection ? pszProjection : "";
414 : }
415 :
416 : /************************************************************************/
417 : /* CreateCTCutlineToSrc() */
418 : /************************************************************************/
419 :
420 52 : static std::unique_ptr<OGRCoordinateTransformation> CreateCTCutlineToSrc(
421 : const OGRSpatialReference *poRasterSRS, const OGRSpatialReference *poDstSRS,
422 : const OGRSpatialReference *poCutlineSRS, CSLConstList papszTO)
423 : {
424 52 : const OGRSpatialReference *poCutlineOrTargetSRS =
425 52 : poCutlineSRS ? poCutlineSRS : poDstSRS;
426 52 : std::unique_ptr<OGRCoordinateTransformation> poCTCutlineToSrc;
427 100 : if (poCutlineOrTargetSRS && poRasterSRS &&
428 48 : !poCutlineOrTargetSRS->IsSame(poRasterSRS))
429 : {
430 16 : OGRCoordinateTransformationOptions oOptions;
431 : // If the cutline SRS is the same as the target SRS and there is
432 : // an explicit -ct between the source SRS and the target SRS, then
433 : // use it in the reverse way to transform from the cutline SRS to
434 : // the source SRS.
435 8 : if (poDstSRS && poCutlineOrTargetSRS->IsSame(poDstSRS))
436 : {
437 : const char *pszCT =
438 2 : CSLFetchNameValue(papszTO, "COORDINATE_OPERATION");
439 2 : if (pszCT)
440 : {
441 1 : oOptions.SetCoordinateOperation(pszCT, /* bInverse = */ true);
442 : }
443 : }
444 8 : poCTCutlineToSrc.reset(OGRCreateCoordinateTransformation(
445 : poCutlineOrTargetSRS, poRasterSRS, oOptions));
446 : }
447 52 : return poCTCutlineToSrc;
448 : }
449 :
450 : /************************************************************************/
451 : /* CropToCutline() */
452 : /************************************************************************/
453 :
454 18 : static CPLErr CropToCutline(const OGRGeometry *poCutline, CSLConstList papszTO,
455 : CSLConstList papszWarpOptions, int nSrcCount,
456 : GDALDatasetH *pahSrcDS, double &dfMinX,
457 : double &dfMinY, double &dfMaxX, double &dfMaxY,
458 : const GDALWarpAppOptions *psOptions)
459 : {
460 : // We could possibly directly reproject from cutline SRS to target SRS,
461 : // but when applying the cutline, it is reprojected to source raster image
462 : // space using the source SRS. To be consistent, we reproject
463 : // the cutline from cutline SRS to source SRS and then from source SRS to
464 : // target SRS.
465 18 : const OGRSpatialReference *poCutlineSRS = poCutline->getSpatialReference();
466 18 : const char *pszThisTargetSRS = CSLFetchNameValue(papszTO, "DST_SRS");
467 18 : std::unique_ptr<OGRSpatialReference> poSrcSRS;
468 18 : std::unique_ptr<OGRSpatialReference> poDstSRS;
469 :
470 : const CPLString osThisSourceSRS =
471 36 : GetSrcDSProjection(nSrcCount > 0 ? pahSrcDS[0] : nullptr, papszTO);
472 18 : if (!osThisSourceSRS.empty())
473 : {
474 14 : poSrcSRS = std::make_unique<OGRSpatialReference>();
475 14 : poSrcSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
476 14 : if (poSrcSRS->SetFromUserInput(osThisSourceSRS) != OGRERR_NONE)
477 : {
478 1 : CPLError(CE_Failure, CPLE_AppDefined,
479 : "Cannot compute bounding box of cutline.");
480 1 : return CE_Failure;
481 : }
482 : }
483 4 : else if (!pszThisTargetSRS && !poCutlineSRS)
484 : {
485 1 : OGREnvelope sEnvelope;
486 1 : poCutline->getEnvelope(&sEnvelope);
487 :
488 1 : dfMinX = sEnvelope.MinX;
489 1 : dfMinY = sEnvelope.MinY;
490 1 : dfMaxX = sEnvelope.MaxX;
491 1 : dfMaxY = sEnvelope.MaxY;
492 :
493 1 : return CE_None;
494 : }
495 : else
496 : {
497 3 : CPLError(CE_Failure, CPLE_AppDefined,
498 : "Cannot compute bounding box of cutline. Cannot find "
499 : "source SRS");
500 3 : return CE_Failure;
501 : }
502 :
503 13 : if (pszThisTargetSRS)
504 : {
505 3 : poDstSRS = std::make_unique<OGRSpatialReference>();
506 3 : poDstSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
507 3 : if (poDstSRS->SetFromUserInput(pszThisTargetSRS) != OGRERR_NONE)
508 : {
509 1 : CPLError(CE_Failure, CPLE_AppDefined,
510 : "Cannot compute bounding box of cutline.");
511 1 : return CE_Failure;
512 : }
513 : }
514 : else
515 : {
516 10 : poDstSRS.reset(poSrcSRS->Clone());
517 : }
518 :
519 24 : auto poCutlineGeom = std::unique_ptr<OGRGeometry>(poCutline->clone());
520 12 : auto poCTCutlineToSrc = CreateCTCutlineToSrc(poSrcSRS.get(), poDstSRS.get(),
521 24 : poCutlineSRS, papszTO);
522 :
523 12 : std::unique_ptr<OGRCoordinateTransformation> poCTSrcToDst;
524 12 : if (!poSrcSRS->IsSame(poDstSRS.get()))
525 : {
526 1 : poCTSrcToDst.reset(
527 1 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get()));
528 : }
529 :
530 : // Reproject cutline to target SRS, by doing intermediate vertex
531 : // densification in source SRS.
532 12 : if (poCTSrcToDst || poCTCutlineToSrc)
533 : {
534 2 : OGREnvelope sLastEnvelope, sCurEnvelope;
535 0 : std::unique_ptr<OGRGeometry> poTransformedGeom;
536 : auto poGeomInSrcSRS =
537 2 : std::unique_ptr<OGRGeometry>(poCutlineGeom->clone());
538 2 : if (poCTCutlineToSrc)
539 : {
540 4 : poGeomInSrcSRS.reset(OGRGeometryFactory::transformWithOptions(
541 2 : poGeomInSrcSRS.get(), poCTCutlineToSrc.get(), nullptr));
542 2 : if (!poGeomInSrcSRS)
543 0 : return CE_Failure;
544 : }
545 :
546 : // Do not use a smaller epsilon, otherwise it could cause useless
547 : // segmentization (https://github.com/OSGeo/gdal/issues/4826)
548 2 : constexpr double epsilon = 1e-10;
549 3 : for (int nIter = 0; nIter < 10; nIter++)
550 : {
551 3 : poTransformedGeom.reset(poGeomInSrcSRS->clone());
552 3 : if (poCTSrcToDst)
553 : {
554 4 : poTransformedGeom.reset(
555 : OGRGeometryFactory::transformWithOptions(
556 2 : poTransformedGeom.get(), poCTSrcToDst.get(), nullptr));
557 2 : if (!poTransformedGeom)
558 0 : return CE_Failure;
559 : }
560 3 : poTransformedGeom->getEnvelope(&sCurEnvelope);
561 3 : if (nIter > 0 || !poCTSrcToDst)
562 : {
563 2 : if (std::abs(sCurEnvelope.MinX - sLastEnvelope.MinX) <=
564 2 : epsilon *
565 2 : std::abs(sCurEnvelope.MinX + sLastEnvelope.MinX) &&
566 2 : std::abs(sCurEnvelope.MinY - sLastEnvelope.MinY) <=
567 2 : epsilon *
568 2 : std::abs(sCurEnvelope.MinY + sLastEnvelope.MinY) &&
569 2 : std::abs(sCurEnvelope.MaxX - sLastEnvelope.MaxX) <=
570 2 : epsilon *
571 6 : std::abs(sCurEnvelope.MaxX + sLastEnvelope.MaxX) &&
572 2 : std::abs(sCurEnvelope.MaxY - sLastEnvelope.MaxY) <=
573 2 : epsilon *
574 2 : std::abs(sCurEnvelope.MaxY + sLastEnvelope.MaxY))
575 : {
576 2 : break;
577 : }
578 : }
579 : double dfAverageSegmentLength =
580 1 : GetAverageSegmentLength(poGeomInSrcSRS.get());
581 1 : poGeomInSrcSRS->segmentize(dfAverageSegmentLength / 4);
582 :
583 1 : sLastEnvelope = sCurEnvelope;
584 : }
585 :
586 2 : poCutlineGeom = std::move(poTransformedGeom);
587 : }
588 :
589 12 : OGREnvelope sEnvelope;
590 12 : poCutlineGeom->getEnvelope(&sEnvelope);
591 :
592 12 : dfMinX = sEnvelope.MinX;
593 12 : dfMinY = sEnvelope.MinY;
594 12 : dfMaxX = sEnvelope.MaxX;
595 12 : dfMaxY = sEnvelope.MaxY;
596 22 : if (!poCTSrcToDst && nSrcCount > 0 && psOptions->dfXRes == 0.0 &&
597 10 : psOptions->dfYRes == 0.0)
598 : {
599 : // No raster reprojection: stick on exact pixel boundaries of the source
600 : // to preserve resolution and avoid resampling
601 : double adfGT[6];
602 10 : if (GDALGetGeoTransform(pahSrcDS[0], adfGT) == CE_None)
603 : {
604 : // We allow for a relative error in coordinates up to 0.1% of the
605 : // pixel size for rounding purposes.
606 10 : constexpr double REL_EPS_PIXEL = 1e-3;
607 10 : if (CPLFetchBool(papszWarpOptions, "CUTLINE_ALL_TOUCHED", false))
608 : {
609 : // All touched ? Then make the extent a bit larger than the
610 : // cutline envelope
611 3 : dfMinX = adfGT[0] +
612 3 : floor((dfMinX - adfGT[0]) / adfGT[1] + REL_EPS_PIXEL) *
613 3 : adfGT[1];
614 3 : dfMinY = adfGT[3] +
615 3 : ceil((dfMinY - adfGT[3]) / adfGT[5] - REL_EPS_PIXEL) *
616 3 : adfGT[5];
617 3 : dfMaxX = adfGT[0] +
618 3 : ceil((dfMaxX - adfGT[0]) / adfGT[1] - REL_EPS_PIXEL) *
619 3 : adfGT[1];
620 3 : dfMaxY = adfGT[3] +
621 3 : floor((dfMaxY - adfGT[3]) / adfGT[5] + REL_EPS_PIXEL) *
622 3 : adfGT[5];
623 : }
624 : else
625 : {
626 : // Otherwise, make it a bit smaller
627 7 : dfMinX = adfGT[0] +
628 7 : ceil((dfMinX - adfGT[0]) / adfGT[1] - REL_EPS_PIXEL) *
629 7 : adfGT[1];
630 7 : dfMinY = adfGT[3] +
631 7 : floor((dfMinY - adfGT[3]) / adfGT[5] + REL_EPS_PIXEL) *
632 7 : adfGT[5];
633 7 : dfMaxX = adfGT[0] +
634 7 : floor((dfMaxX - adfGT[0]) / adfGT[1] + REL_EPS_PIXEL) *
635 7 : adfGT[1];
636 7 : dfMaxY = adfGT[3] +
637 7 : ceil((dfMaxY - adfGT[3]) / adfGT[5] - REL_EPS_PIXEL) *
638 7 : adfGT[5];
639 : }
640 : }
641 : }
642 :
643 12 : return CE_None;
644 : }
645 :
646 : #ifdef USE_PROJ_BASED_VERTICAL_SHIFT_METHOD
647 :
648 2286 : static bool MustApplyVerticalShift(GDALDatasetH hWrkSrcDS,
649 : const GDALWarpAppOptions *psOptions,
650 : OGRSpatialReference &oSRSSrc,
651 : OGRSpatialReference &oSRSDst,
652 : bool &bSrcHasVertAxis, bool &bDstHasVertAxis)
653 : {
654 2286 : bool bApplyVShift = psOptions->bVShift;
655 :
656 : // Check if we must do vertical shift grid transform
657 : const char *pszSrcWKT =
658 2286 : psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS");
659 2286 : if (pszSrcWKT)
660 58 : oSRSSrc.SetFromUserInput(pszSrcWKT);
661 : else
662 : {
663 2228 : auto hSRS = GDALGetSpatialRef(hWrkSrcDS);
664 2228 : if (hSRS)
665 1616 : oSRSSrc = *(OGRSpatialReference::FromHandle(hSRS));
666 : else
667 612 : return false;
668 : }
669 :
670 : const char *pszDstWKT =
671 1674 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
672 1674 : if (pszDstWKT)
673 531 : oSRSDst.SetFromUserInput(pszDstWKT);
674 : else
675 1143 : return false;
676 :
677 531 : if (oSRSSrc.IsSame(&oSRSDst))
678 20 : return false;
679 :
680 994 : bSrcHasVertAxis = oSRSSrc.IsCompound() ||
681 483 : ((oSRSSrc.IsProjected() || oSRSSrc.IsGeographic()) &&
682 483 : oSRSSrc.GetAxesCount() == 3);
683 :
684 1000 : bDstHasVertAxis = oSRSDst.IsCompound() ||
685 489 : ((oSRSDst.IsProjected() || oSRSDst.IsGeographic()) &&
686 488 : oSRSDst.GetAxesCount() == 3);
687 :
688 849 : if ((GDALGetRasterCount(hWrkSrcDS) == 1 || psOptions->bVShift) &&
689 338 : (bSrcHasVertAxis || bDstHasVertAxis))
690 : {
691 38 : bApplyVShift = true;
692 : }
693 511 : return bApplyVShift;
694 : }
695 :
696 : /************************************************************************/
697 : /* ApplyVerticalShift() */
698 : /************************************************************************/
699 :
700 1138 : static bool ApplyVerticalShift(GDALDatasetH hWrkSrcDS,
701 : const GDALWarpAppOptions *psOptions,
702 : GDALWarpOptions *psWO)
703 : {
704 1138 : if (psOptions->bVShift)
705 : {
706 0 : psWO->papszWarpOptions = CSLSetNameValue(psWO->papszWarpOptions,
707 : "APPLY_VERTICAL_SHIFT", "YES");
708 : }
709 :
710 2276 : OGRSpatialReference oSRSSrc;
711 1138 : OGRSpatialReference oSRSDst;
712 1138 : bool bSrcHasVertAxis = false;
713 1138 : bool bDstHasVertAxis = false;
714 : bool bApplyVShift =
715 1138 : MustApplyVerticalShift(hWrkSrcDS, psOptions, oSRSSrc, oSRSDst,
716 : bSrcHasVertAxis, bDstHasVertAxis);
717 :
718 1981 : if ((GDALGetRasterCount(hWrkSrcDS) == 1 || psOptions->bVShift) &&
719 843 : (bSrcHasVertAxis || bDstHasVertAxis))
720 : {
721 19 : bApplyVShift = true;
722 19 : psWO->papszWarpOptions = CSLSetNameValue(psWO->papszWarpOptions,
723 : "APPLY_VERTICAL_SHIFT", "YES");
724 :
725 19 : if (CSLFetchNameValue(psWO->papszWarpOptions,
726 19 : "MULT_FACTOR_VERTICAL_SHIFT") == nullptr)
727 : {
728 : // Select how to go from input dataset units to meters
729 19 : double dfToMeterSrc = 1.0;
730 : const char *pszUnit =
731 19 : GDALGetRasterUnitType(GDALGetRasterBand(hWrkSrcDS, 1));
732 :
733 19 : double dfToMeterSrcAxis = 1.0;
734 19 : if (bSrcHasVertAxis)
735 : {
736 15 : oSRSSrc.GetAxis(nullptr, 2, nullptr, &dfToMeterSrcAxis);
737 : }
738 :
739 19 : if (pszUnit && (EQUAL(pszUnit, "m") || EQUAL(pszUnit, "meter") ||
740 18 : EQUAL(pszUnit, "metre")))
741 : {
742 : }
743 18 : else if (pszUnit &&
744 18 : (EQUAL(pszUnit, "ft") || EQUAL(pszUnit, "foot")))
745 : {
746 1 : dfToMeterSrc = CPLAtof(SRS_UL_FOOT_CONV);
747 : }
748 17 : else if (pszUnit && (EQUAL(pszUnit, "US survey foot")))
749 : {
750 1 : dfToMeterSrc = CPLAtof(SRS_UL_US_FOOT_CONV);
751 : }
752 16 : else if (pszUnit && !EQUAL(pszUnit, ""))
753 : {
754 1 : if (bSrcHasVertAxis)
755 : {
756 1 : dfToMeterSrc = dfToMeterSrcAxis;
757 : }
758 : else
759 : {
760 0 : CPLError(CE_Warning, CPLE_AppDefined,
761 : "Unknown units=%s. Assuming metre.", pszUnit);
762 : }
763 : }
764 : else
765 : {
766 15 : if (bSrcHasVertAxis)
767 11 : oSRSSrc.GetAxis(nullptr, 2, nullptr, &dfToMeterSrc);
768 : }
769 :
770 19 : double dfToMeterDst = 1.0;
771 19 : if (bDstHasVertAxis)
772 19 : oSRSDst.GetAxis(nullptr, 2, nullptr, &dfToMeterDst);
773 :
774 19 : if (dfToMeterSrc > 0 && dfToMeterDst > 0)
775 : {
776 19 : const double dfMultFactorVerticalShift =
777 19 : dfToMeterSrc / dfToMeterDst;
778 19 : CPLDebug("WARP", "Applying MULT_FACTOR_VERTICAL_SHIFT=%.18g",
779 : dfMultFactorVerticalShift);
780 19 : psWO->papszWarpOptions = CSLSetNameValue(
781 : psWO->papszWarpOptions, "MULT_FACTOR_VERTICAL_SHIFT",
782 : CPLSPrintf("%.18g", dfMultFactorVerticalShift));
783 :
784 19 : const double dfMultFactorVerticalShiftPipeline =
785 19 : dfToMeterSrcAxis / dfToMeterDst;
786 19 : CPLDebug("WARP",
787 : "Applying MULT_FACTOR_VERTICAL_SHIFT_PIPELINE=%.18g",
788 : dfMultFactorVerticalShiftPipeline);
789 19 : psWO->papszWarpOptions = CSLSetNameValue(
790 : psWO->papszWarpOptions,
791 : "MULT_FACTOR_VERTICAL_SHIFT_PIPELINE",
792 : CPLSPrintf("%.18g", dfMultFactorVerticalShiftPipeline));
793 : }
794 : }
795 : }
796 :
797 2276 : return bApplyVShift;
798 : }
799 :
800 : #else
801 :
802 : /************************************************************************/
803 : /* ApplyVerticalShiftGrid() */
804 : /************************************************************************/
805 :
806 : static GDALDatasetH ApplyVerticalShiftGrid(GDALDatasetH hWrkSrcDS,
807 : const GDALWarpAppOptions *psOptions,
808 : GDALDatasetH hVRTDS,
809 : bool &bErrorOccurredOut)
810 : {
811 : bErrorOccurredOut = false;
812 : // Check if we must do vertical shift grid transform
813 : OGRSpatialReference oSRSSrc;
814 : OGRSpatialReference oSRSDst;
815 : const char *pszSrcWKT =
816 : psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS");
817 : if (pszSrcWKT)
818 : oSRSSrc.SetFromUserInput(pszSrcWKT);
819 : else
820 : {
821 : auto hSRS = GDALGetSpatialRef(hWrkSrcDS);
822 : if (hSRS)
823 : oSRSSrc = *(OGRSpatialReference::FromHandle(hSRS));
824 : }
825 :
826 : const char *pszDstWKT =
827 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
828 : if (pszDstWKT)
829 : oSRSDst.SetFromUserInput(pszDstWKT);
830 :
831 : double adfGT[6] = {};
832 : if (GDALGetRasterCount(hWrkSrcDS) == 1 &&
833 : GDALGetGeoTransform(hWrkSrcDS, adfGT) == CE_None &&
834 : !oSRSSrc.IsEmpty() && !oSRSDst.IsEmpty())
835 : {
836 : if ((oSRSSrc.IsCompound() ||
837 : (oSRSSrc.IsGeographic() && oSRSSrc.GetAxesCount() == 3)) ||
838 : (oSRSDst.IsCompound() ||
839 : (oSRSDst.IsGeographic() && oSRSDst.GetAxesCount() == 3)))
840 : {
841 : const char *pszSrcProj4Geoids =
842 : oSRSSrc.GetExtension("VERT_DATUM", "PROJ4_GRIDS");
843 : const char *pszDstProj4Geoids =
844 : oSRSDst.GetExtension("VERT_DATUM", "PROJ4_GRIDS");
845 :
846 : if (oSRSSrc.IsCompound() && pszSrcProj4Geoids == nullptr)
847 : {
848 : CPLDebug("GDALWARP", "Source SRS is a compound CRS but lacks "
849 : "+geoidgrids");
850 : }
851 :
852 : if (oSRSDst.IsCompound() && pszDstProj4Geoids == nullptr)
853 : {
854 : CPLDebug("GDALWARP", "Target SRS is a compound CRS but lacks "
855 : "+geoidgrids");
856 : }
857 :
858 : if (pszSrcProj4Geoids != nullptr && pszDstProj4Geoids != nullptr &&
859 : EQUAL(pszSrcProj4Geoids, pszDstProj4Geoids))
860 : {
861 : pszSrcProj4Geoids = nullptr;
862 : pszDstProj4Geoids = nullptr;
863 : }
864 :
865 : // Select how to go from input dataset units to meters
866 : const char *pszUnit =
867 : GDALGetRasterUnitType(GDALGetRasterBand(hWrkSrcDS, 1));
868 : double dfToMeterSrc = 1.0;
869 : if (pszUnit && (EQUAL(pszUnit, "m") || EQUAL(pszUnit, "meter") ||
870 : EQUAL(pszUnit, "metre")))
871 : {
872 : }
873 : else if (pszUnit &&
874 : (EQUAL(pszUnit, "ft") || EQUAL(pszUnit, "foot")))
875 : {
876 : dfToMeterSrc = CPLAtof(SRS_UL_FOOT_CONV);
877 : }
878 : else if (pszUnit && (EQUAL(pszUnit, "US survey foot")))
879 : {
880 : dfToMeterSrc = CPLAtof(SRS_UL_US_FOOT_CONV);
881 : }
882 : else
883 : {
884 : if (pszUnit && !EQUAL(pszUnit, ""))
885 : {
886 : CPLError(CE_Warning, CPLE_AppDefined, "Unknown units=%s",
887 : pszUnit);
888 : }
889 : if (oSRSSrc.IsCompound())
890 : {
891 : dfToMeterSrc = oSRSSrc.GetTargetLinearUnits("VERT_CS");
892 : }
893 : else if (oSRSSrc.IsProjected())
894 : {
895 : dfToMeterSrc = oSRSSrc.GetLinearUnits();
896 : }
897 : }
898 :
899 : double dfToMeterDst = 1.0;
900 : if (oSRSDst.IsCompound())
901 : {
902 : dfToMeterDst = oSRSDst.GetTargetLinearUnits("VERT_CS");
903 : }
904 : else if (oSRSDst.IsProjected())
905 : {
906 : dfToMeterDst = oSRSDst.GetLinearUnits();
907 : }
908 :
909 : char **papszOptions = nullptr;
910 : if (psOptions->eOutputType != GDT_Unknown)
911 : {
912 : papszOptions = CSLSetNameValue(
913 : papszOptions, "DATATYPE",
914 : GDALGetDataTypeName(psOptions->eOutputType));
915 : }
916 : papszOptions =
917 : CSLSetNameValue(papszOptions, "ERROR_ON_MISSING_VERT_SHIFT",
918 : psOptions->aosTransformerOptions.FetchNameValue(
919 : "ERROR_ON_MISSING_VERT_SHIFT"));
920 : papszOptions = CSLSetNameValue(papszOptions, "SRC_SRS", pszSrcWKT);
921 :
922 : if (pszSrcProj4Geoids != nullptr)
923 : {
924 : int bError = FALSE;
925 : GDALDatasetH hGridDataset =
926 : GDALOpenVerticalShiftGrid(pszSrcProj4Geoids, &bError);
927 : if (bError && hGridDataset == nullptr)
928 : {
929 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s.",
930 : pszSrcProj4Geoids);
931 : bErrorOccurredOut = true;
932 : CSLDestroy(papszOptions);
933 : return hWrkSrcDS;
934 : }
935 : else if (hGridDataset != nullptr)
936 : {
937 : // Transform from source vertical datum to WGS84
938 : GDALDatasetH hTmpDS = GDALApplyVerticalShiftGrid(
939 : hWrkSrcDS, hGridDataset, FALSE, dfToMeterSrc, 1.0,
940 : papszOptions);
941 : GDALReleaseDataset(hGridDataset);
942 : if (hTmpDS == nullptr)
943 : {
944 : bErrorOccurredOut = true;
945 : CSLDestroy(papszOptions);
946 : return hWrkSrcDS;
947 : }
948 : else
949 : {
950 : if (hVRTDS)
951 : {
952 : CPLError(
953 : CE_Failure, CPLE_NotSupported,
954 : "Warping to VRT with vertical transformation "
955 : "not supported with PROJ < 6.3");
956 : bErrorOccurredOut = true;
957 : CSLDestroy(papszOptions);
958 : return hWrkSrcDS;
959 : }
960 :
961 : CPLDebug("GDALWARP",
962 : "Adjusting source dataset "
963 : "with source vertical datum using %s",
964 : pszSrcProj4Geoids);
965 : GDALReleaseDataset(hWrkSrcDS);
966 : hWrkSrcDS = hTmpDS;
967 : dfToMeterSrc = 1.0;
968 : }
969 : }
970 : }
971 :
972 : if (pszDstProj4Geoids != nullptr)
973 : {
974 : int bError = FALSE;
975 : GDALDatasetH hGridDataset =
976 : GDALOpenVerticalShiftGrid(pszDstProj4Geoids, &bError);
977 : if (bError && hGridDataset == nullptr)
978 : {
979 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s.",
980 : pszDstProj4Geoids);
981 : bErrorOccurredOut = true;
982 : CSLDestroy(papszOptions);
983 : return hWrkSrcDS;
984 : }
985 : else if (hGridDataset != nullptr)
986 : {
987 : // Transform from WGS84 to target vertical datum
988 : GDALDatasetH hTmpDS = GDALApplyVerticalShiftGrid(
989 : hWrkSrcDS, hGridDataset, TRUE, dfToMeterSrc,
990 : dfToMeterDst, papszOptions);
991 : GDALReleaseDataset(hGridDataset);
992 : if (hTmpDS == nullptr)
993 : {
994 : bErrorOccurredOut = true;
995 : CSLDestroy(papszOptions);
996 : return hWrkSrcDS;
997 : }
998 : else
999 : {
1000 : if (hVRTDS)
1001 : {
1002 : CPLError(
1003 : CE_Failure, CPLE_NotSupported,
1004 : "Warping to VRT with vertical transformation "
1005 : "not supported with PROJ < 6.3");
1006 : bErrorOccurredOut = true;
1007 : CSLDestroy(papszOptions);
1008 : return hWrkSrcDS;
1009 : }
1010 :
1011 : CPLDebug("GDALWARP",
1012 : "Adjusting source dataset "
1013 : "with target vertical datum using %s",
1014 : pszDstProj4Geoids);
1015 : GDALReleaseDataset(hWrkSrcDS);
1016 : hWrkSrcDS = hTmpDS;
1017 : }
1018 : }
1019 : }
1020 :
1021 : CSLDestroy(papszOptions);
1022 : }
1023 : }
1024 : return hWrkSrcDS;
1025 : }
1026 :
1027 : #endif
1028 :
1029 : /************************************************************************/
1030 : /* CanUseBuildVRT() */
1031 : /************************************************************************/
1032 :
1033 5 : static bool CanUseBuildVRT(int nSrcCount, GDALDatasetH *pahSrcDS)
1034 : {
1035 :
1036 5 : bool bCanUseBuildVRT = true;
1037 10 : std::vector<std::array<double, 4>> aoExtents;
1038 5 : bool bSrcHasAlpha = false;
1039 5 : int nPrevBandCount = 0;
1040 5 : OGRSpatialReference oSRSPrev;
1041 5 : double dfLastResX = 0;
1042 5 : double dfLastResY = 0;
1043 14 : for (int i = 0; i < nSrcCount; i++)
1044 : {
1045 : double adfGT[6];
1046 9 : auto hSrcDS = pahSrcDS[i];
1047 9 : if (EQUAL(GDALGetDescription(hSrcDS), ""))
1048 : {
1049 0 : bCanUseBuildVRT = false;
1050 0 : break;
1051 : }
1052 18 : if (GDALGetGeoTransform(hSrcDS, adfGT) != CE_None || adfGT[2] != 0 ||
1053 18 : adfGT[4] != 0 || adfGT[5] > 0)
1054 : {
1055 0 : bCanUseBuildVRT = false;
1056 0 : break;
1057 : }
1058 9 : const double dfMinX = adfGT[0];
1059 9 : const double dfMinY = adfGT[3] + GDALGetRasterYSize(hSrcDS) * adfGT[5];
1060 9 : const double dfMaxX = adfGT[0] + GDALGetRasterXSize(hSrcDS) * adfGT[1];
1061 9 : const double dfMaxY = adfGT[3];
1062 9 : const int nBands = GDALGetRasterCount(hSrcDS);
1063 9 : if (nBands > 1 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
1064 : hSrcDS, nBands)) == GCI_AlphaBand)
1065 : {
1066 4 : bSrcHasAlpha = true;
1067 : }
1068 : aoExtents.emplace_back(
1069 9 : std::array<double, 4>{{dfMinX, dfMinY, dfMaxX, dfMaxY}});
1070 9 : const auto poSRS = GDALDataset::FromHandle(hSrcDS)->GetSpatialRef();
1071 9 : if (i == 0)
1072 : {
1073 5 : nPrevBandCount = nBands;
1074 5 : if (poSRS)
1075 5 : oSRSPrev = *poSRS;
1076 5 : dfLastResX = adfGT[1];
1077 5 : dfLastResY = adfGT[5];
1078 : }
1079 : else
1080 : {
1081 4 : if (nPrevBandCount != nBands)
1082 : {
1083 0 : bCanUseBuildVRT = false;
1084 0 : break;
1085 : }
1086 4 : if (poSRS == nullptr && !oSRSPrev.IsEmpty())
1087 : {
1088 0 : bCanUseBuildVRT = false;
1089 0 : break;
1090 : }
1091 8 : if (poSRS != nullptr &&
1092 4 : (oSRSPrev.IsEmpty() || !poSRS->IsSame(&oSRSPrev)))
1093 : {
1094 0 : bCanUseBuildVRT = false;
1095 0 : break;
1096 : }
1097 4 : if (dfLastResX != adfGT[1] || dfLastResY != adfGT[5])
1098 : {
1099 0 : bCanUseBuildVRT = false;
1100 0 : break;
1101 : }
1102 : }
1103 : }
1104 5 : if (bSrcHasAlpha && bCanUseBuildVRT)
1105 : {
1106 : // Quadratic performance loop. If that happens to be an issue,
1107 : // we might need to build a quad tree
1108 2 : for (size_t i = 0; i < aoExtents.size(); i++)
1109 : {
1110 2 : const double dfMinX = aoExtents[i][0];
1111 2 : const double dfMinY = aoExtents[i][1];
1112 2 : const double dfMaxX = aoExtents[i][2];
1113 2 : const double dfMaxY = aoExtents[i][3];
1114 2 : for (size_t j = i + 1; j < aoExtents.size(); j++)
1115 : {
1116 2 : const double dfOtherMinX = aoExtents[j][0];
1117 2 : const double dfOtherMinY = aoExtents[j][1];
1118 2 : const double dfOtherMaxX = aoExtents[j][2];
1119 2 : const double dfOtherMaxY = aoExtents[j][3];
1120 2 : if (dfMinX < dfOtherMaxX && dfOtherMinX < dfMaxX &&
1121 2 : dfMinY < dfOtherMaxY && dfOtherMinY < dfMaxY)
1122 : {
1123 2 : bCanUseBuildVRT = false;
1124 2 : break;
1125 : }
1126 : }
1127 2 : if (!bCanUseBuildVRT)
1128 2 : break;
1129 : }
1130 : }
1131 10 : return bCanUseBuildVRT;
1132 : }
1133 :
1134 : #ifdef HAVE_TIFF
1135 :
1136 : /************************************************************************/
1137 : /* DealWithCOGOptions() */
1138 : /************************************************************************/
1139 :
1140 8 : static bool DealWithCOGOptions(CPLStringList &aosCreateOptions, int nSrcCount,
1141 : GDALDatasetH *pahSrcDS,
1142 : GDALWarpAppOptions *psOptions,
1143 : GDALTransformerArgUniquePtr &hUniqueTransformArg)
1144 : {
1145 8 : const auto SetDstSRS = [psOptions](const std::string &osTargetSRS)
1146 : {
1147 : const char *pszExistingDstSRS =
1148 5 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
1149 5 : if (pszExistingDstSRS)
1150 : {
1151 2 : OGRSpatialReference oSRS1;
1152 2 : oSRS1.SetFromUserInput(pszExistingDstSRS);
1153 2 : OGRSpatialReference oSRS2;
1154 2 : oSRS2.SetFromUserInput(osTargetSRS.c_str());
1155 2 : if (!oSRS1.IsSame(&oSRS2))
1156 : {
1157 2 : CPLError(CE_Failure, CPLE_AppDefined,
1158 : "Target SRS implied by COG creation options is not "
1159 : "the same as the one specified by -t_srs");
1160 2 : return false;
1161 : }
1162 : }
1163 : psOptions->aosTransformerOptions.SetNameValue("DST_SRS",
1164 3 : osTargetSRS.c_str());
1165 3 : return true;
1166 8 : };
1167 :
1168 16 : CPLString osTargetSRS;
1169 8 : if (COGGetTargetSRS(aosCreateOptions.List(), osTargetSRS))
1170 : {
1171 5 : if (!SetDstSRS(osTargetSRS))
1172 2 : return false;
1173 : }
1174 :
1175 6 : if (!(psOptions->dfMinX == 0 && psOptions->dfMinY == 0 &&
1176 5 : psOptions->dfMaxX == 0 && psOptions->dfMaxY == 0 &&
1177 5 : psOptions->dfXRes == 0 && psOptions->dfYRes == 0 &&
1178 5 : psOptions->nForcePixels == 0 && psOptions->nForceLines == 0))
1179 : {
1180 2 : if (!psOptions->bResampleAlgSpecifiedByUser && nSrcCount > 0)
1181 : {
1182 : try
1183 : {
1184 1 : GDALGetWarpResampleAlg(
1185 2 : COGGetResampling(GDALDataset::FromHandle(pahSrcDS[0]),
1186 1 : aosCreateOptions.List())
1187 : .c_str(),
1188 1 : psOptions->eResampleAlg);
1189 : }
1190 0 : catch (const std::invalid_argument &)
1191 : {
1192 : // Cannot happen actually. Coverity Scan false positive...
1193 0 : CPLAssert(false);
1194 : }
1195 : }
1196 2 : return true;
1197 : }
1198 :
1199 8 : GDALWarpAppOptions oClonedOptions(*psOptions);
1200 4 : oClonedOptions.bQuiet = true;
1201 : const CPLString osTmpFilename(
1202 8 : VSIMemGenerateHiddenFilename("gdalwarp_tmp.tif"));
1203 8 : CPLStringList aosTmpGTiffCreateOptions;
1204 4 : aosTmpGTiffCreateOptions.SetNameValue("SPARSE_OK", "YES");
1205 4 : aosTmpGTiffCreateOptions.SetNameValue("TILED", "YES");
1206 4 : aosTmpGTiffCreateOptions.SetNameValue("BLOCKXSIZE", "4096");
1207 4 : aosTmpGTiffCreateOptions.SetNameValue("BLOCKYSIZE", "4096");
1208 4 : auto hTmpDS = GDALWarpCreateOutput(
1209 : nSrcCount, pahSrcDS, osTmpFilename, "GTiff",
1210 : oClonedOptions.aosTransformerOptions.List(),
1211 4 : aosTmpGTiffCreateOptions.List(), oClonedOptions.eOutputType,
1212 : hUniqueTransformArg, false, &oClonedOptions,
1213 : /* bUpdateTransformerWithDestGT = */ false);
1214 4 : if (hTmpDS == nullptr)
1215 : {
1216 0 : return false;
1217 : }
1218 :
1219 4 : CPLString osResampling;
1220 4 : int nXSize = 0;
1221 4 : int nYSize = 0;
1222 4 : double dfMinX = 0;
1223 4 : double dfMinY = 0;
1224 4 : double dfMaxX = 0;
1225 4 : double dfMaxY = 0;
1226 4 : bool bRet = true;
1227 4 : if (COGGetWarpingCharacteristics(GDALDataset::FromHandle(hTmpDS),
1228 4 : aosCreateOptions.List(), osResampling,
1229 : osTargetSRS, nXSize, nYSize, dfMinX,
1230 : dfMinY, dfMaxX, dfMaxY))
1231 : {
1232 2 : if (!psOptions->bResampleAlgSpecifiedByUser)
1233 : {
1234 : try
1235 : {
1236 2 : GDALGetWarpResampleAlg(osResampling, psOptions->eResampleAlg);
1237 : }
1238 0 : catch (const std::invalid_argument &)
1239 : {
1240 : // Cannot happen actually. Coverity Scan false positive...
1241 0 : CPLAssert(false);
1242 : }
1243 : }
1244 :
1245 2 : psOptions->dfMinX = dfMinX;
1246 2 : psOptions->dfMinY = dfMinY;
1247 2 : psOptions->dfMaxX = dfMaxX;
1248 2 : psOptions->dfMaxY = dfMaxY;
1249 2 : psOptions->nForcePixels = nXSize;
1250 2 : psOptions->nForceLines = nYSize;
1251 2 : COGRemoveWarpingOptions(aosCreateOptions);
1252 : }
1253 4 : GDALClose(hTmpDS);
1254 4 : VSIUnlink(osTmpFilename);
1255 4 : return bRet;
1256 : }
1257 :
1258 : #endif
1259 :
1260 : /************************************************************************/
1261 : /* GDALWarpIndirect() */
1262 : /************************************************************************/
1263 :
1264 : static GDALDatasetH
1265 : GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1266 : GDALDatasetH *pahSrcDS,
1267 : GDALTransformerArgUniquePtr hUniqueTransformArg,
1268 : GDALWarpAppOptions *psOptions, int *pbUsageError);
1269 :
1270 1028 : static int CPL_STDCALL myScaledProgress(double dfProgress, const char *,
1271 : void *pProgressData)
1272 : {
1273 1028 : return GDALScaledProgress(dfProgress, nullptr, pProgressData);
1274 : }
1275 :
1276 17 : static GDALDatasetH GDALWarpIndirect(const char *pszDest, GDALDriverH hDriver,
1277 : int nSrcCount, GDALDatasetH *pahSrcDS,
1278 : GDALWarpAppOptions *psOptions,
1279 : int *pbUsageError)
1280 : {
1281 34 : CPLStringList aosCreateOptions(psOptions->aosCreateOptions);
1282 17 : psOptions->aosCreateOptions.Clear();
1283 :
1284 : // Do not use a warped VRT input for COG output, because that would cause
1285 : // warping to be done both during overview computation and creation of
1286 : // full resolution image. Better materialize a temporary GTiff a bit later
1287 : // in that method.
1288 17 : if (nSrcCount == 1 && !EQUAL(psOptions->osFormat.c_str(), "COG"))
1289 : {
1290 6 : psOptions->osFormat = "VRT";
1291 6 : auto pfnProgress = psOptions->pfnProgress;
1292 6 : psOptions->pfnProgress = GDALDummyProgress;
1293 6 : auto pProgressData = psOptions->pProgressData;
1294 6 : psOptions->pProgressData = nullptr;
1295 :
1296 6 : auto hTmpDS = GDALWarpDirect("", nullptr, nSrcCount, pahSrcDS, nullptr,
1297 : psOptions, pbUsageError);
1298 6 : if (hTmpDS)
1299 : {
1300 6 : auto hRet = GDALCreateCopy(hDriver, pszDest, hTmpDS, FALSE,
1301 6 : aosCreateOptions.List(), pfnProgress,
1302 : pProgressData);
1303 6 : GDALClose(hTmpDS);
1304 6 : return hRet;
1305 : }
1306 0 : return nullptr;
1307 : }
1308 :
1309 : // Detect a pure mosaicing situation where a BuildVRT approach is
1310 : // sufficient.
1311 11 : GDALDatasetH hTmpDS = nullptr;
1312 11 : if (psOptions->aosTransformerOptions.empty() &&
1313 5 : psOptions->eOutputType == GDT_Unknown && psOptions->dfMinX == 0 &&
1314 5 : psOptions->dfMinY == 0 && psOptions->dfMaxX == 0 &&
1315 5 : psOptions->dfMaxY == 0 && psOptions->dfXRes == 0 &&
1316 5 : psOptions->dfYRes == 0 && psOptions->nForcePixels == 0 &&
1317 10 : psOptions->nForceLines == 0 &&
1318 21 : psOptions->osCutlineDSNameOrWKT.empty() &&
1319 5 : CanUseBuildVRT(nSrcCount, pahSrcDS))
1320 : {
1321 6 : CPLStringList aosArgv;
1322 3 : const int nBands = GDALGetRasterCount(pahSrcDS[0]);
1323 0 : if ((nBands == 1 ||
1324 0 : (nBands > 1 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
1325 6 : pahSrcDS[0], nBands)) != GCI_AlphaBand)) &&
1326 3 : (psOptions->bEnableDstAlpha
1327 : #ifdef HAVE_TIFF
1328 3 : || (EQUAL(psOptions->osFormat.c_str(), "COG") &&
1329 3 : COGHasWarpingOptions(aosCreateOptions.List()) &&
1330 2 : CPLTestBool(
1331 : aosCreateOptions.FetchNameValueDef("ADD_ALPHA", "YES")))
1332 : #endif
1333 : ))
1334 : {
1335 2 : aosArgv.AddString("-addalpha");
1336 : }
1337 : auto psBuildVRTOptions =
1338 3 : GDALBuildVRTOptionsNew(aosArgv.List(), nullptr);
1339 3 : hTmpDS = GDALBuildVRT("", nSrcCount, pahSrcDS, nullptr,
1340 : psBuildVRTOptions, nullptr);
1341 3 : GDALBuildVRTOptionsFree(psBuildVRTOptions);
1342 : }
1343 11 : auto pfnProgress = psOptions->pfnProgress;
1344 11 : auto pProgressData = psOptions->pProgressData;
1345 22 : CPLString osTmpFilename;
1346 11 : double dfStartPctCreateCopy = 0.0;
1347 11 : if (hTmpDS == nullptr)
1348 : {
1349 0 : GDALTransformerArgUniquePtr hUniqueTransformArg;
1350 : #ifdef HAVE_TIFF
1351 : // Special processing for COG output. As some of its options do
1352 : // on-the-fly reprojection, take them into account now, and remove them
1353 : // from the COG creation stage.
1354 16 : if (EQUAL(psOptions->osFormat.c_str(), "COG") &&
1355 8 : !DealWithCOGOptions(aosCreateOptions, nSrcCount, pahSrcDS,
1356 : psOptions, hUniqueTransformArg))
1357 : {
1358 2 : return nullptr;
1359 : }
1360 : #endif
1361 :
1362 : // Materialize a temporary GeoTIFF with the result of the warp
1363 6 : psOptions->osFormat = "GTiff";
1364 6 : psOptions->aosCreateOptions.AddString("SPARSE_OK=YES");
1365 6 : psOptions->aosCreateOptions.AddString("COMPRESS=LZW");
1366 6 : psOptions->aosCreateOptions.AddString("TILED=YES");
1367 6 : psOptions->aosCreateOptions.AddString("BIGTIFF=YES");
1368 6 : psOptions->pfnProgress = myScaledProgress;
1369 6 : dfStartPctCreateCopy = 2. / 3;
1370 6 : psOptions->pProgressData = GDALCreateScaledProgress(
1371 : 0, dfStartPctCreateCopy, pfnProgress, pProgressData);
1372 6 : psOptions->bDeleteOutputFileOnceCreated = true;
1373 6 : osTmpFilename = CPLGenerateTempFilenameSafe(CPLGetFilename(pszDest));
1374 6 : hTmpDS = GDALWarpDirect(osTmpFilename, nullptr, nSrcCount, pahSrcDS,
1375 6 : std::move(hUniqueTransformArg), psOptions,
1376 : pbUsageError);
1377 6 : GDALDestroyScaledProgress(psOptions->pProgressData);
1378 6 : psOptions->pfnProgress = nullptr;
1379 6 : psOptions->pProgressData = nullptr;
1380 : }
1381 9 : if (hTmpDS)
1382 : {
1383 9 : auto pScaledProgressData = GDALCreateScaledProgress(
1384 : dfStartPctCreateCopy, 1.0, pfnProgress, pProgressData);
1385 9 : auto hRet = GDALCreateCopy(hDriver, pszDest, hTmpDS, FALSE,
1386 9 : aosCreateOptions.List(), myScaledProgress,
1387 : pScaledProgressData);
1388 9 : GDALDestroyScaledProgress(pScaledProgressData);
1389 9 : GDALClose(hTmpDS);
1390 : VSIStatBufL sStat;
1391 15 : if (!osTmpFilename.empty() &&
1392 6 : VSIStatL(osTmpFilename.c_str(), &sStat) == 0)
1393 : {
1394 6 : GDALDeleteDataset(GDALGetDriverByName("GTiff"), osTmpFilename);
1395 : }
1396 9 : return hRet;
1397 : }
1398 0 : return nullptr;
1399 : }
1400 :
1401 : /************************************************************************/
1402 : /* GDALWarp() */
1403 : /************************************************************************/
1404 :
1405 : /**
1406 : * Image reprojection and warping function.
1407 : *
1408 : * This is the equivalent of the <a href="/programs/gdalwarp.html">gdalwarp</a>
1409 : * utility.
1410 : *
1411 : * GDALWarpAppOptions* must be allocated and freed with GDALWarpAppOptionsNew()
1412 : * and GDALWarpAppOptionsFree() respectively.
1413 : * pszDest and hDstDS cannot be used at the same time.
1414 : *
1415 : * @param pszDest the destination dataset path or NULL.
1416 : * @param hDstDS the destination dataset or NULL.
1417 : * @param nSrcCount the number of input datasets.
1418 : * @param pahSrcDS the list of input datasets. For practical purposes, the type
1419 : * of this argument should be considered as "const GDALDatasetH* const*", that
1420 : * is neither the array nor its values are mutated by this function.
1421 : * @param psOptionsIn the options struct returned by GDALWarpAppOptionsNew() or
1422 : * NULL.
1423 : * @param pbUsageError pointer to a integer output variable to store if any
1424 : * usage error has occurred, or NULL.
1425 : * @return the output dataset (new dataset that must be closed using
1426 : * GDALClose(), or hDstDS if not NULL) or NULL in case of error. If the output
1427 : * format is a VRT dataset, then the returned VRT dataset has a reference to
1428 : * pahSrcDS[0]. Hence pahSrcDS[0] should be closed after the returned dataset
1429 : * if using GDALClose().
1430 : * A safer alternative is to use GDALReleaseDataset() instead of using
1431 : * GDALClose(), in which case you can close datasets in any order.
1432 : *
1433 : * @since GDAL 2.1
1434 : */
1435 :
1436 1160 : GDALDatasetH GDALWarp(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1437 : GDALDatasetH *pahSrcDS,
1438 : const GDALWarpAppOptions *psOptionsIn, int *pbUsageError)
1439 : {
1440 1160 : CPLErrorReset();
1441 :
1442 2343 : for (int i = 0; i < nSrcCount; i++)
1443 : {
1444 1183 : if (!pahSrcDS[i])
1445 0 : return nullptr;
1446 : }
1447 :
1448 2320 : GDALWarpAppOptions oOptionsTmp;
1449 1160 : if (psOptionsIn)
1450 1158 : oOptionsTmp = *psOptionsIn;
1451 1160 : GDALWarpAppOptions *psOptions = &oOptionsTmp;
1452 :
1453 1160 : if (hDstDS == nullptr)
1454 : {
1455 1067 : if (psOptions->osFormat.empty())
1456 : {
1457 412 : psOptions->osFormat = GetOutputDriverForRaster(pszDest);
1458 412 : if (psOptions->osFormat.empty())
1459 : {
1460 0 : return nullptr;
1461 : }
1462 : }
1463 :
1464 1067 : auto hDriver = GDALGetDriverByName(psOptions->osFormat.c_str());
1465 2134 : if (hDriver != nullptr &&
1466 1067 : (EQUAL(psOptions->osFormat.c_str(), "COG") ||
1467 1056 : (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) ==
1468 6 : nullptr &&
1469 6 : GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
1470 : nullptr)))
1471 : {
1472 17 : auto ret = GDALWarpIndirect(pszDest, hDriver, nSrcCount, pahSrcDS,
1473 : psOptions, pbUsageError);
1474 17 : return ret;
1475 : }
1476 : }
1477 :
1478 1143 : auto ret = GDALWarpDirect(pszDest, hDstDS, nSrcCount, pahSrcDS, nullptr,
1479 : psOptions, pbUsageError);
1480 :
1481 1143 : return ret;
1482 : }
1483 :
1484 : /************************************************************************/
1485 : /* UseTEAndTSAndTRConsistently() */
1486 : /************************************************************************/
1487 :
1488 1067 : static bool UseTEAndTSAndTRConsistently(const GDALWarpAppOptions *psOptions)
1489 : {
1490 : // We normally don't allow -te, -ts and -tr together, unless they are all
1491 : // consistent. The interest of this is to use the -tr values to produce
1492 : // exact pixel size, rather than inferring it from -te and -ts
1493 :
1494 : // Constant and logic to be kept in sync with cogdriver.cpp
1495 1067 : constexpr double RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP = 1e-8;
1496 199 : return psOptions->nForcePixels != 0 && psOptions->nForceLines != 0 &&
1497 194 : psOptions->dfXRes != 0 && psOptions->dfYRes != 0 &&
1498 52 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1499 0 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0) &&
1500 52 : fabs((psOptions->dfMaxX - psOptions->dfMinX) / psOptions->dfXRes -
1501 52 : psOptions->nForcePixels) <=
1502 1266 : RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP &&
1503 52 : fabs((psOptions->dfMaxY - psOptions->dfMinY) / psOptions->dfYRes -
1504 52 : psOptions->nForceLines) <=
1505 1067 : RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP;
1506 : }
1507 :
1508 : /************************************************************************/
1509 : /* CheckOptions() */
1510 : /************************************************************************/
1511 :
1512 1155 : static bool CheckOptions(const char *pszDest, GDALDatasetH hDstDS,
1513 : int nSrcCount, GDALDatasetH *pahSrcDS,
1514 : GDALWarpAppOptions *psOptions, bool &bVRT,
1515 : int *pbUsageError)
1516 : {
1517 :
1518 1155 : if (hDstDS)
1519 : {
1520 93 : if (psOptions->bCreateOutput == true)
1521 : {
1522 0 : CPLError(CE_Warning, CPLE_AppDefined,
1523 : "All options related to creation ignored in update mode");
1524 0 : psOptions->bCreateOutput = false;
1525 : }
1526 : }
1527 :
1528 2496 : if ((psOptions->osFormat.empty() &&
1529 2310 : EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "VRT")) ||
1530 1155 : (EQUAL(psOptions->osFormat.c_str(), "VRT")))
1531 : {
1532 175 : if (hDstDS != nullptr)
1533 : {
1534 0 : CPLError(CE_Warning, CPLE_NotSupported,
1535 : "VRT output not compatible with existing dataset.");
1536 0 : return false;
1537 : }
1538 :
1539 175 : bVRT = true;
1540 :
1541 175 : if (nSrcCount > 1)
1542 : {
1543 0 : CPLError(CE_Warning, CPLE_AppDefined,
1544 : "gdalwarp -of VRT just takes into account "
1545 : "the first source dataset.\nIf all source datasets "
1546 : "are in the same projection, try making a mosaic of\n"
1547 : "them with gdalbuildvrt, and use the resulting "
1548 : "VRT file as the input of\ngdalwarp -of VRT.");
1549 : }
1550 : }
1551 :
1552 : /* -------------------------------------------------------------------- */
1553 : /* Check that incompatible options are not used */
1554 : /* -------------------------------------------------------------------- */
1555 :
1556 982 : if ((psOptions->nForcePixels != 0 || psOptions->nForceLines != 0) &&
1557 2163 : (psOptions->dfXRes != 0 && psOptions->dfYRes != 0) &&
1558 26 : !UseTEAndTSAndTRConsistently(psOptions))
1559 : {
1560 0 : CPLError(CE_Failure, CPLE_IllegalArg,
1561 : "-tr and -ts options cannot be used at the same time.");
1562 0 : if (pbUsageError)
1563 0 : *pbUsageError = TRUE;
1564 0 : return false;
1565 : }
1566 :
1567 1155 : if (psOptions->bTargetAlignedPixels && psOptions->dfXRes == 0 &&
1568 1 : psOptions->dfYRes == 0)
1569 : {
1570 1 : CPLError(CE_Failure, CPLE_IllegalArg,
1571 : "-tap option cannot be used without using -tr.");
1572 1 : if (pbUsageError)
1573 1 : *pbUsageError = TRUE;
1574 1 : return false;
1575 : }
1576 :
1577 1154 : if (!psOptions->bQuiet &&
1578 81 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1579 64 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0))
1580 : {
1581 17 : if (psOptions->dfMinX >= psOptions->dfMaxX)
1582 0 : CPLError(CE_Warning, CPLE_AppDefined,
1583 : "-te values have minx >= maxx. This will result in a "
1584 : "horizontally flipped image.");
1585 17 : if (psOptions->dfMinY >= psOptions->dfMaxY)
1586 0 : CPLError(CE_Warning, CPLE_AppDefined,
1587 : "-te values have miny >= maxy. This will result in a "
1588 : "vertically flipped image.");
1589 : }
1590 :
1591 1154 : if (psOptions->dfErrorThreshold < 0)
1592 : {
1593 : // By default, use approximate transformer unless RPC_DEM is specified
1594 1141 : if (psOptions->aosTransformerOptions.FetchNameValue("RPC_DEM") !=
1595 : nullptr)
1596 4 : psOptions->dfErrorThreshold = 0.0;
1597 : else
1598 1137 : psOptions->dfErrorThreshold = 0.125;
1599 : }
1600 :
1601 : /* -------------------------------------------------------------------- */
1602 : /* -te_srs option */
1603 : /* -------------------------------------------------------------------- */
1604 1154 : if (!psOptions->osTE_SRS.empty())
1605 : {
1606 6 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1607 0 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
1608 : {
1609 0 : CPLError(CE_Warning, CPLE_AppDefined,
1610 : "-te_srs ignored since -te is not specified.");
1611 : }
1612 : else
1613 : {
1614 6 : OGRSpatialReference oSRSIn;
1615 6 : oSRSIn.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1616 6 : oSRSIn.SetFromUserInput(psOptions->osTE_SRS.c_str());
1617 6 : OGRSpatialReference oSRSDS;
1618 6 : oSRSDS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1619 6 : bool bOK = false;
1620 6 : if (psOptions->aosTransformerOptions.FetchNameValue("DST_SRS") !=
1621 : nullptr)
1622 : {
1623 3 : oSRSDS.SetFromUserInput(
1624 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS"));
1625 3 : bOK = true;
1626 : }
1627 3 : else if (psOptions->aosTransformerOptions.FetchNameValue(
1628 3 : "SRC_SRS") != nullptr)
1629 : {
1630 0 : oSRSDS.SetFromUserInput(
1631 : psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS"));
1632 0 : bOK = true;
1633 : }
1634 3 : else if (nSrcCount)
1635 : {
1636 : const auto poSrcSRS =
1637 3 : GDALDataset::FromHandle(pahSrcDS[0])->GetSpatialRef();
1638 3 : if (poSrcSRS)
1639 : {
1640 3 : oSRSDS = *poSrcSRS;
1641 3 : bOK = true;
1642 : }
1643 : }
1644 6 : if (!bOK)
1645 : {
1646 0 : CPLError(CE_Failure, CPLE_AppDefined,
1647 : "-te_srs ignored since none of -t_srs, -s_srs is "
1648 : "specified or the input dataset has no projection.");
1649 0 : return false;
1650 : }
1651 6 : if (!oSRSIn.IsSame(&oSRSDS))
1652 : {
1653 5 : double dfWestLongitudeDeg = 0.0;
1654 5 : double dfSouthLatitudeDeg = 0.0;
1655 5 : double dfEastLongitudeDeg = 0.0;
1656 5 : double dfNorthLatitudeDeg = 0.0;
1657 :
1658 5 : OGRCoordinateTransformationOptions options;
1659 5 : if (GDALComputeAreaOfInterest(
1660 : &oSRSIn, psOptions->dfMinX, psOptions->dfMinY,
1661 : psOptions->dfMaxX, psOptions->dfMaxY,
1662 : dfWestLongitudeDeg, dfSouthLatitudeDeg,
1663 : dfEastLongitudeDeg, dfNorthLatitudeDeg))
1664 : {
1665 5 : options.SetAreaOfInterest(
1666 : dfWestLongitudeDeg, dfSouthLatitudeDeg,
1667 : dfEastLongitudeDeg, dfNorthLatitudeDeg);
1668 : }
1669 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1670 : OGRCreateCoordinateTransformation(&oSRSIn, &oSRSDS,
1671 5 : options));
1672 5 : constexpr int DENSIFY_PTS = 21;
1673 10 : if (!(poCT && poCT->TransformBounds(
1674 : psOptions->dfMinX, psOptions->dfMinY,
1675 : psOptions->dfMaxX, psOptions->dfMaxY,
1676 : &(psOptions->dfMinX), &(psOptions->dfMinY),
1677 : &(psOptions->dfMaxX), &(psOptions->dfMaxY),
1678 10 : DENSIFY_PTS)))
1679 : {
1680 0 : CPLError(CE_Failure, CPLE_AppDefined,
1681 : "-te_srs ignored since coordinate transformation "
1682 : "failed.");
1683 0 : return false;
1684 : }
1685 : }
1686 : }
1687 : }
1688 1154 : return true;
1689 : }
1690 :
1691 : /************************************************************************/
1692 : /* ProcessCutlineOptions() */
1693 : /************************************************************************/
1694 :
1695 1154 : static bool ProcessCutlineOptions(int nSrcCount, GDALDatasetH *pahSrcDS,
1696 : GDALWarpAppOptions *psOptions,
1697 : std::unique_ptr<OGRGeometry> &poCutline)
1698 : {
1699 1154 : if (!psOptions->osCutlineDSNameOrWKT.empty())
1700 : {
1701 : CPLErr eError;
1702 52 : OGRGeometryH hCutline = nullptr;
1703 104 : eError = LoadCutline(psOptions->osCutlineDSNameOrWKT,
1704 52 : psOptions->osCutlineSRS, psOptions->osCLayer,
1705 52 : psOptions->osCWHERE, psOptions->osCSQL, &hCutline);
1706 52 : poCutline.reset(OGRGeometry::FromHandle(hCutline));
1707 52 : if (eError == CE_Failure)
1708 : {
1709 6 : return false;
1710 : }
1711 : }
1712 :
1713 1148 : if (psOptions->bCropToCutline && poCutline)
1714 : {
1715 : CPLErr eError;
1716 18 : eError = CropToCutline(poCutline.get(),
1717 18 : psOptions->aosTransformerOptions.List(),
1718 18 : psOptions->aosWarpOptions.List(), nSrcCount,
1719 18 : pahSrcDS, psOptions->dfMinX, psOptions->dfMinY,
1720 18 : psOptions->dfMaxX, psOptions->dfMaxY, psOptions);
1721 18 : if (eError == CE_Failure)
1722 : {
1723 5 : return false;
1724 : }
1725 : }
1726 :
1727 : const char *pszWarpThreads =
1728 1143 : psOptions->aosWarpOptions.FetchNameValue("NUM_THREADS");
1729 1143 : if (pszWarpThreads != nullptr)
1730 : {
1731 : /* Used by TPS transformer to parallelize direct and inverse matrix
1732 : * computation */
1733 : psOptions->aosTransformerOptions.SetNameValue("NUM_THREADS",
1734 22 : pszWarpThreads);
1735 : }
1736 :
1737 1143 : return true;
1738 : }
1739 :
1740 : /************************************************************************/
1741 : /* CreateOutput() */
1742 : /************************************************************************/
1743 :
1744 : static GDALDatasetH
1745 1050 : CreateOutput(const char *pszDest, int nSrcCount, GDALDatasetH *pahSrcDS,
1746 : GDALWarpAppOptions *psOptions, const bool bInitDestSetByUser,
1747 : GDALTransformerArgUniquePtr &hUniqueTransformArg)
1748 : {
1749 1050 : if (nSrcCount == 1 && !psOptions->bDisableSrcAlpha)
1750 : {
1751 2038 : if (GDALGetRasterCount(pahSrcDS[0]) > 0 &&
1752 1019 : GDALGetRasterColorInterpretation(GDALGetRasterBand(
1753 : pahSrcDS[0], GDALGetRasterCount(pahSrcDS[0]))) == GCI_AlphaBand)
1754 : {
1755 39 : psOptions->bEnableSrcAlpha = true;
1756 39 : psOptions->bEnableDstAlpha = true;
1757 39 : if (!psOptions->bQuiet)
1758 0 : printf("Using band %d of source image as alpha.\n",
1759 : GDALGetRasterCount(pahSrcDS[0]));
1760 : }
1761 : }
1762 :
1763 1050 : auto hDstDS = GDALWarpCreateOutput(
1764 : nSrcCount, pahSrcDS, pszDest, psOptions->osFormat.c_str(),
1765 : psOptions->aosTransformerOptions.List(),
1766 1050 : psOptions->aosCreateOptions.List(), psOptions->eOutputType,
1767 1050 : hUniqueTransformArg, psOptions->bSetColorInterpretation, psOptions,
1768 : /* bUpdateTransformerWithDestGT = */ true);
1769 1050 : if (hDstDS == nullptr)
1770 : {
1771 19 : return nullptr;
1772 : }
1773 1031 : psOptions->bCreateOutput = true;
1774 :
1775 1031 : if (!bInitDestSetByUser)
1776 : {
1777 1011 : if (psOptions->osDstNodata.empty())
1778 : {
1779 879 : psOptions->aosWarpOptions.SetNameValue("INIT_DEST", "0");
1780 : }
1781 : else
1782 : {
1783 132 : psOptions->aosWarpOptions.SetNameValue("INIT_DEST", "NO_DATA");
1784 : }
1785 : }
1786 :
1787 1031 : return hDstDS;
1788 : }
1789 :
1790 : /************************************************************************/
1791 : /* EditISIS3ForMetadataChanges() */
1792 : /************************************************************************/
1793 :
1794 : static std::string
1795 2 : EditISIS3ForMetadataChanges(const char *pszJSON,
1796 : const GDALWarpAppOptions *psOptions)
1797 : {
1798 4 : CPLJSONDocument oJSONDocument;
1799 2 : if (!oJSONDocument.LoadMemory(pszJSON))
1800 : {
1801 0 : return std::string();
1802 : }
1803 :
1804 4 : auto oRoot = oJSONDocument.GetRoot();
1805 2 : if (!oRoot.IsValid())
1806 : {
1807 0 : return std::string();
1808 : }
1809 :
1810 6 : auto oGDALHistory = oRoot.GetObj("GDALHistory");
1811 2 : if (!oGDALHistory.IsValid())
1812 : {
1813 2 : oGDALHistory = CPLJSONObject();
1814 2 : oRoot.Add("GDALHistory", oGDALHistory);
1815 : }
1816 2 : oGDALHistory["_type"] = "object";
1817 :
1818 2 : char szFullFilename[2048] = {0};
1819 2 : if (!CPLGetExecPath(szFullFilename, sizeof(szFullFilename) - 1))
1820 0 : strcpy(szFullFilename, "unknown_program");
1821 4 : const CPLString osProgram(CPLGetBasenameSafe(szFullFilename));
1822 4 : const CPLString osPath(CPLGetPathSafe(szFullFilename));
1823 :
1824 2 : oGDALHistory["GdalVersion"] = GDALVersionInfo("RELEASE_NAME");
1825 2 : oGDALHistory["Program"] = osProgram;
1826 2 : if (osPath != ".")
1827 2 : oGDALHistory["ProgramPath"] = osPath;
1828 :
1829 4 : std::string osArgs;
1830 6 : for (const char *pszArg : psOptions->aosArgs)
1831 : {
1832 4 : if (!osArgs.empty())
1833 2 : osArgs += ' ';
1834 4 : osArgs += pszArg;
1835 : }
1836 2 : oGDALHistory["ProgramArguments"] = osArgs;
1837 :
1838 2 : oGDALHistory.Add(
1839 : "Comment",
1840 : "Part of that metadata might be invalid due to a reprojection "
1841 : "operation having been performed by GDAL tools");
1842 :
1843 2 : return oRoot.Format(CPLJSONObject::PrettyFormat::Pretty);
1844 : }
1845 :
1846 : /************************************************************************/
1847 : /* ProcessMetadata() */
1848 : /************************************************************************/
1849 :
1850 1145 : static void ProcessMetadata(int iSrc, GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
1851 : const GDALWarpAppOptions *psOptions,
1852 : const bool bEnableDstAlpha)
1853 : {
1854 1145 : if (psOptions->bCopyMetadata)
1855 : {
1856 1145 : const char *pszSrcInfo = nullptr;
1857 1145 : GDALRasterBandH hSrcBand = nullptr;
1858 1145 : GDALRasterBandH hDstBand = nullptr;
1859 :
1860 : /* copy metadata from first dataset */
1861 1145 : if (iSrc == 0)
1862 : {
1863 1121 : CPLDebug(
1864 : "WARP",
1865 : "Copying metadata from first source to destination dataset");
1866 : /* copy dataset-level metadata */
1867 1121 : CSLConstList papszMetadata = GDALGetMetadata(hSrcDS, nullptr);
1868 :
1869 1121 : char **papszMetadataNew = nullptr;
1870 2917 : for (int i = 0;
1871 2917 : papszMetadata != nullptr && papszMetadata[i] != nullptr; i++)
1872 : {
1873 : // Do not preserve NODATA_VALUES when the output includes an
1874 : // alpha band
1875 1796 : if (bEnableDstAlpha &&
1876 198 : STARTS_WITH_CI(papszMetadata[i], "NODATA_VALUES="))
1877 : {
1878 1 : continue;
1879 : }
1880 : // Do not preserve the CACHE_PATH from the WMS driver
1881 1795 : if (STARTS_WITH_CI(papszMetadata[i], "CACHE_PATH="))
1882 : {
1883 0 : continue;
1884 : }
1885 :
1886 : papszMetadataNew =
1887 1795 : CSLAddString(papszMetadataNew, papszMetadata[i]);
1888 : }
1889 :
1890 1121 : if (CSLCount(papszMetadataNew) > 0)
1891 : {
1892 746 : if (GDALSetMetadata(hDstDS, papszMetadataNew, nullptr) !=
1893 : CE_None)
1894 0 : CPLError(CE_Warning, CPLE_AppDefined,
1895 : "error copying metadata to destination dataset.");
1896 : }
1897 :
1898 1121 : CSLDestroy(papszMetadataNew);
1899 :
1900 : /* ISIS3 -> ISIS3 special case */
1901 1121 : if (EQUAL(psOptions->osFormat.c_str(), "ISIS3") ||
1902 1120 : EQUAL(psOptions->osFormat.c_str(), "PDS4") ||
1903 2915 : EQUAL(psOptions->osFormat.c_str(), "GTIFF") ||
1904 674 : EQUAL(psOptions->osFormat.c_str(), "COG"))
1905 : {
1906 : CSLConstList papszMD_ISIS3 =
1907 447 : GDALGetMetadata(hSrcDS, "json:ISIS3");
1908 447 : if (papszMD_ISIS3 != nullptr && papszMD_ISIS3[0])
1909 : {
1910 4 : std::string osJSON = papszMD_ISIS3[0];
1911 : osJSON =
1912 2 : EditISIS3ForMetadataChanges(osJSON.c_str(), psOptions);
1913 2 : if (!osJSON.empty())
1914 : {
1915 2 : char *apszMD[] = {osJSON.data(), nullptr};
1916 2 : GDALSetMetadata(hDstDS, apszMD, "json:ISIS3");
1917 : }
1918 : }
1919 : }
1920 674 : else if (EQUAL(psOptions->osFormat.c_str(), "PDS4"))
1921 : {
1922 0 : CSLConstList papszMD_PDS4 = GDALGetMetadata(hSrcDS, "xml:PDS4");
1923 0 : if (papszMD_PDS4 != nullptr)
1924 0 : GDALSetMetadata(hDstDS, papszMD_PDS4, "xml:PDS4");
1925 : }
1926 674 : else if (EQUAL(psOptions->osFormat.c_str(), "VICAR"))
1927 : {
1928 : CSLConstList papszMD_VICAR =
1929 0 : GDALGetMetadata(hSrcDS, "json:VICAR");
1930 0 : if (papszMD_VICAR != nullptr)
1931 0 : GDALSetMetadata(hDstDS, papszMD_VICAR, "json:VICAR");
1932 : }
1933 :
1934 : /* copy band-level metadata and other info */
1935 1121 : if (GDALGetRasterCount(hSrcDS) == GDALGetRasterCount(hDstDS))
1936 : {
1937 2430 : for (int iBand = 0; iBand < GDALGetRasterCount(hSrcDS); iBand++)
1938 : {
1939 1392 : hSrcBand = GDALGetRasterBand(hSrcDS, iBand + 1);
1940 1392 : hDstBand = GDALGetRasterBand(hDstDS, iBand + 1);
1941 : /* copy metadata, except stats (#5319) */
1942 1392 : papszMetadata = GDALGetMetadata(hSrcBand, nullptr);
1943 1392 : if (CSLCount(papszMetadata) > 0)
1944 : {
1945 : // GDALSetMetadata( hDstBand, papszMetadata, NULL );
1946 22 : papszMetadataNew = nullptr;
1947 120 : for (int i = 0; papszMetadata != nullptr &&
1948 120 : papszMetadata[i] != nullptr;
1949 : i++)
1950 : {
1951 98 : if (!STARTS_WITH(papszMetadata[i], "STATISTICS_"))
1952 83 : papszMetadataNew = CSLAddString(
1953 83 : papszMetadataNew, papszMetadata[i]);
1954 : }
1955 22 : GDALSetMetadata(hDstBand, papszMetadataNew, nullptr);
1956 22 : CSLDestroy(papszMetadataNew);
1957 : }
1958 : /* copy other info (Description, Unit Type) - what else? */
1959 1392 : if (psOptions->bCopyBandInfo)
1960 : {
1961 1392 : pszSrcInfo = GDALGetDescription(hSrcBand);
1962 1392 : if (pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0)
1963 3 : GDALSetDescription(hDstBand, pszSrcInfo);
1964 1392 : pszSrcInfo = GDALGetRasterUnitType(hSrcBand);
1965 1392 : if (pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0)
1966 38 : GDALSetRasterUnitType(hDstBand, pszSrcInfo);
1967 : }
1968 : }
1969 : }
1970 : }
1971 : /* remove metadata that conflicts between datasets */
1972 : else
1973 : {
1974 24 : CPLDebug("WARP",
1975 : "Removing conflicting metadata from destination dataset "
1976 : "(source #%d)",
1977 : iSrc);
1978 : /* remove conflicting dataset-level metadata */
1979 24 : RemoveConflictingMetadata(hDstDS, GDALGetMetadata(hSrcDS, nullptr),
1980 : psOptions->osMDConflictValue.c_str());
1981 :
1982 : /* remove conflicting copy band-level metadata and other info */
1983 24 : if (GDALGetRasterCount(hSrcDS) == GDALGetRasterCount(hDstDS))
1984 : {
1985 44 : for (int iBand = 0; iBand < GDALGetRasterCount(hSrcDS); iBand++)
1986 : {
1987 23 : hSrcBand = GDALGetRasterBand(hSrcDS, iBand + 1);
1988 23 : hDstBand = GDALGetRasterBand(hDstDS, iBand + 1);
1989 : /* remove conflicting metadata */
1990 23 : RemoveConflictingMetadata(
1991 : hDstBand, GDALGetMetadata(hSrcBand, nullptr),
1992 : psOptions->osMDConflictValue.c_str());
1993 : /* remove conflicting info */
1994 23 : if (psOptions->bCopyBandInfo)
1995 : {
1996 23 : pszSrcInfo = GDALGetDescription(hSrcBand);
1997 23 : const char *pszDstInfo = GDALGetDescription(hDstBand);
1998 23 : if (!(pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0 &&
1999 0 : pszDstInfo != nullptr && strlen(pszDstInfo) > 0 &&
2000 0 : EQUAL(pszSrcInfo, pszDstInfo)))
2001 23 : GDALSetDescription(hDstBand, "");
2002 23 : pszSrcInfo = GDALGetRasterUnitType(hSrcBand);
2003 23 : pszDstInfo = GDALGetRasterUnitType(hDstBand);
2004 23 : if (!(pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0 &&
2005 0 : pszDstInfo != nullptr && strlen(pszDstInfo) > 0 &&
2006 0 : EQUAL(pszSrcInfo, pszDstInfo)))
2007 23 : GDALSetRasterUnitType(hDstBand, "");
2008 : }
2009 : }
2010 : }
2011 : }
2012 : }
2013 1145 : }
2014 :
2015 : /************************************************************************/
2016 : /* SetupNoData() */
2017 : /************************************************************************/
2018 :
2019 1142 : static CPLErr SetupNoData(const char *pszDest, int iSrc, GDALDatasetH hSrcDS,
2020 : GDALDatasetH hWrkSrcDS, GDALDatasetH hDstDS,
2021 : GDALWarpOptions *psWO, GDALWarpAppOptions *psOptions,
2022 : const bool bEnableDstAlpha,
2023 : const bool bInitDestSetByUser)
2024 : {
2025 1192 : if (!psOptions->osSrcNodata.empty() &&
2026 50 : !EQUAL(psOptions->osSrcNodata.c_str(), "none"))
2027 : {
2028 : CPLStringList aosTokens(
2029 46 : CSLTokenizeString(psOptions->osSrcNodata.c_str()));
2030 46 : const int nTokenCount = aosTokens.Count();
2031 :
2032 46 : psWO->padfSrcNoDataReal =
2033 46 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
2034 46 : psWO->padfSrcNoDataImag = nullptr;
2035 :
2036 123 : for (int i = 0; i < psWO->nBandCount; i++)
2037 : {
2038 78 : if (i < nTokenCount)
2039 : {
2040 : double dfNoDataReal;
2041 : double dfNoDataImag;
2042 :
2043 48 : if (CPLStringToComplex(aosTokens[i], &dfNoDataReal,
2044 48 : &dfNoDataImag) != CE_None)
2045 : {
2046 1 : CPLError(CE_Failure, CPLE_AppDefined,
2047 : "Error parsing srcnodata for band %d", i + 1);
2048 1 : return CE_Failure;
2049 : }
2050 :
2051 94 : psWO->padfSrcNoDataReal[i] =
2052 47 : GDALAdjustNoDataCloseToFloatMax(dfNoDataReal);
2053 :
2054 47 : if (strchr(aosTokens[i], 'i') != nullptr)
2055 : {
2056 1 : if (psWO->padfSrcNoDataImag == nullptr)
2057 : {
2058 1 : psWO->padfSrcNoDataImag = static_cast<double *>(
2059 1 : CPLCalloc(psWO->nBandCount, sizeof(double)));
2060 : }
2061 2 : psWO->padfSrcNoDataImag[i] =
2062 1 : GDALAdjustNoDataCloseToFloatMax(dfNoDataImag);
2063 : }
2064 : }
2065 : else
2066 : {
2067 30 : psWO->padfSrcNoDataReal[i] = psWO->padfSrcNoDataReal[i - 1];
2068 30 : if (psWO->padfSrcNoDataImag != nullptr)
2069 : {
2070 0 : psWO->padfSrcNoDataImag[i] = psWO->padfSrcNoDataImag[i - 1];
2071 : }
2072 : }
2073 : }
2074 :
2075 71 : if (psWO->nBandCount > 1 &&
2076 26 : CSLFetchNameValue(psWO->papszWarpOptions, "UNIFIED_SRC_NODATA") ==
2077 : nullptr)
2078 : {
2079 6 : CPLDebug("WARP", "Set UNIFIED_SRC_NODATA=YES");
2080 6 : psWO->papszWarpOptions = CSLSetNameValue(
2081 : psWO->papszWarpOptions, "UNIFIED_SRC_NODATA", "YES");
2082 : }
2083 : }
2084 :
2085 : /* -------------------------------------------------------------------- */
2086 : /* If -srcnodata was not specified, but the data has nodata */
2087 : /* values, use them. */
2088 : /* -------------------------------------------------------------------- */
2089 1141 : if (psOptions->osSrcNodata.empty())
2090 : {
2091 1092 : int bHaveNodata = FALSE;
2092 1092 : double dfReal = 0.0;
2093 :
2094 2495 : for (int i = 0; !bHaveNodata && i < psWO->nBandCount; i++)
2095 : {
2096 : GDALRasterBandH hBand =
2097 1403 : GDALGetRasterBand(hWrkSrcDS, psWO->panSrcBands[i]);
2098 1403 : dfReal = GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2099 : }
2100 :
2101 1092 : if (bHaveNodata)
2102 : {
2103 90 : if (!psOptions->bQuiet)
2104 : {
2105 10 : if (std::isnan(dfReal))
2106 0 : printf("Using internal nodata values (e.g. nan) for image "
2107 : "%s.\n",
2108 : GDALGetDescription(hSrcDS));
2109 : else
2110 10 : printf("Using internal nodata values (e.g. %g) for image "
2111 : "%s.\n",
2112 : dfReal, GDALGetDescription(hSrcDS));
2113 : }
2114 90 : psWO->padfSrcNoDataReal = static_cast<double *>(
2115 90 : CPLMalloc(psWO->nBandCount * sizeof(double)));
2116 :
2117 217 : for (int i = 0; i < psWO->nBandCount; i++)
2118 : {
2119 : GDALRasterBandH hBand =
2120 127 : GDALGetRasterBand(hWrkSrcDS, psWO->panSrcBands[i]);
2121 :
2122 127 : dfReal = GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2123 :
2124 127 : if (bHaveNodata)
2125 : {
2126 127 : psWO->padfSrcNoDataReal[i] = dfReal;
2127 : }
2128 : else
2129 : {
2130 0 : psWO->padfSrcNoDataReal[i] = -123456.789;
2131 : }
2132 : }
2133 : }
2134 : }
2135 :
2136 : /* -------------------------------------------------------------------- */
2137 : /* If the output dataset was created, and we have a destination */
2138 : /* nodata value, go through marking the bands with the information.*/
2139 : /* -------------------------------------------------------------------- */
2140 1285 : if (!psOptions->osDstNodata.empty() &&
2141 144 : !EQUAL(psOptions->osDstNodata.c_str(), "none"))
2142 : {
2143 : CPLStringList aosTokens(
2144 144 : CSLTokenizeString(psOptions->osDstNodata.c_str()));
2145 144 : const int nTokenCount = aosTokens.Count();
2146 144 : bool bDstNoDataNone = true;
2147 :
2148 144 : psWO->padfDstNoDataReal =
2149 144 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
2150 144 : psWO->padfDstNoDataImag =
2151 144 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
2152 :
2153 370 : for (int i = 0; i < psWO->nBandCount; i++)
2154 : {
2155 227 : psWO->padfDstNoDataReal[i] = -1.1e20;
2156 227 : psWO->padfDstNoDataImag[i] = 0.0;
2157 :
2158 227 : if (i < nTokenCount)
2159 : {
2160 145 : if (aosTokens[i] != nullptr && EQUAL(aosTokens[i], "none"))
2161 : {
2162 0 : CPLDebug("WARP", "dstnodata of band %d not set", i);
2163 0 : bDstNoDataNone = true;
2164 0 : continue;
2165 : }
2166 145 : else if (aosTokens[i] ==
2167 : nullptr) // this should not happen, but just in case
2168 : {
2169 0 : CPLError(CE_Failure, CPLE_AppDefined,
2170 : "Error parsing dstnodata arg #%d", i);
2171 0 : bDstNoDataNone = true;
2172 0 : continue;
2173 : }
2174 :
2175 145 : if (CPLStringToComplex(aosTokens[i],
2176 145 : psWO->padfDstNoDataReal + i,
2177 290 : psWO->padfDstNoDataImag + i) != CE_None)
2178 : {
2179 :
2180 1 : CPLError(CE_Failure, CPLE_AppDefined,
2181 : "Error parsing dstnodata for band %d", i + 1);
2182 1 : return CE_Failure;
2183 : }
2184 :
2185 288 : psWO->padfDstNoDataReal[i] =
2186 144 : GDALAdjustNoDataCloseToFloatMax(psWO->padfDstNoDataReal[i]);
2187 288 : psWO->padfDstNoDataImag[i] =
2188 144 : GDALAdjustNoDataCloseToFloatMax(psWO->padfDstNoDataImag[i]);
2189 144 : bDstNoDataNone = false;
2190 144 : CPLDebug("WARP", "dstnodata of band %d set to %f", i,
2191 144 : psWO->padfDstNoDataReal[i]);
2192 : }
2193 : else
2194 : {
2195 82 : if (!bDstNoDataNone)
2196 : {
2197 82 : psWO->padfDstNoDataReal[i] = psWO->padfDstNoDataReal[i - 1];
2198 82 : psWO->padfDstNoDataImag[i] = psWO->padfDstNoDataImag[i - 1];
2199 82 : CPLDebug("WARP",
2200 : "dstnodata of band %d set from previous band", i);
2201 : }
2202 : else
2203 : {
2204 0 : CPLDebug("WARP", "dstnodata value of band %d not set", i);
2205 0 : continue;
2206 : }
2207 : }
2208 :
2209 : GDALRasterBandH hBand =
2210 226 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2211 226 : int bClamped = FALSE;
2212 226 : int bRounded = FALSE;
2213 226 : psWO->padfDstNoDataReal[i] = GDALAdjustValueToDataType(
2214 226 : GDALGetRasterDataType(hBand), psWO->padfDstNoDataReal[i],
2215 : &bClamped, &bRounded);
2216 :
2217 226 : if (bClamped)
2218 : {
2219 0 : CPLError(
2220 : CE_Warning, CPLE_AppDefined,
2221 : "for band %d, destination nodata value has been clamped "
2222 : "to %.0f, the original value being out of range.",
2223 0 : psWO->panDstBands[i], psWO->padfDstNoDataReal[i]);
2224 : }
2225 226 : else if (bRounded)
2226 : {
2227 0 : CPLError(
2228 : CE_Warning, CPLE_AppDefined,
2229 : "for band %d, destination nodata value has been rounded "
2230 : "to %.0f, %s being an integer datatype.",
2231 0 : psWO->panDstBands[i], psWO->padfDstNoDataReal[i],
2232 : GDALGetDataTypeName(GDALGetRasterDataType(hBand)));
2233 : }
2234 :
2235 226 : if (psOptions->bCreateOutput && iSrc == 0)
2236 : {
2237 222 : GDALSetRasterNoDataValue(
2238 222 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]),
2239 222 : psWO->padfDstNoDataReal[i]);
2240 : }
2241 : }
2242 : }
2243 :
2244 : /* check if the output dataset has already nodata */
2245 1140 : if (psOptions->osDstNodata.empty())
2246 : {
2247 997 : int bHaveNodataAll = TRUE;
2248 2299 : for (int i = 0; i < psWO->nBandCount; i++)
2249 : {
2250 : GDALRasterBandH hBand =
2251 1302 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2252 1302 : int bHaveNodata = FALSE;
2253 1302 : GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2254 1302 : bHaveNodataAll &= bHaveNodata;
2255 : }
2256 997 : if (bHaveNodataAll)
2257 : {
2258 6 : psWO->padfDstNoDataReal = static_cast<double *>(
2259 6 : CPLMalloc(psWO->nBandCount * sizeof(double)));
2260 13 : for (int i = 0; i < psWO->nBandCount; i++)
2261 : {
2262 : GDALRasterBandH hBand =
2263 7 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2264 7 : int bHaveNodata = FALSE;
2265 14 : psWO->padfDstNoDataReal[i] =
2266 7 : GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2267 7 : CPLDebug("WARP", "band=%d dstNoData=%f", i,
2268 7 : psWO->padfDstNoDataReal[i]);
2269 : }
2270 : }
2271 : }
2272 :
2273 : // If creating a new file that has default nodata value,
2274 : // try to override the default output nodata values with the source ones.
2275 2137 : if (psOptions->osDstNodata.empty() && psWO->padfSrcNoDataReal != nullptr &&
2276 93 : psWO->padfDstNoDataReal != nullptr && psOptions->bCreateOutput &&
2277 2137 : iSrc == 0 && !bEnableDstAlpha)
2278 : {
2279 5 : for (int i = 0; i < psWO->nBandCount; i++)
2280 : {
2281 : GDALRasterBandH hBand =
2282 3 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2283 3 : int bHaveNodata = FALSE;
2284 3 : CPLPushErrorHandler(CPLQuietErrorHandler);
2285 : bool bRedefinedOK =
2286 3 : (GDALSetRasterNoDataValue(hBand, psWO->padfSrcNoDataReal[i]) ==
2287 3 : CE_None &&
2288 3 : GDALGetRasterNoDataValue(hBand, &bHaveNodata) ==
2289 9 : psWO->padfSrcNoDataReal[i] &&
2290 3 : bHaveNodata);
2291 3 : CPLPopErrorHandler();
2292 3 : if (bRedefinedOK)
2293 : {
2294 3 : if (i == 0 && !psOptions->bQuiet)
2295 0 : printf("Copying nodata values from source %s "
2296 : "to destination %s.\n",
2297 : GDALGetDescription(hSrcDS), pszDest);
2298 3 : psWO->padfDstNoDataReal[i] = psWO->padfSrcNoDataReal[i];
2299 :
2300 3 : if (i == 0 && !bInitDestSetByUser)
2301 : {
2302 : /* As we didn't know at the beginning if there was source
2303 : * nodata */
2304 : /* we have initialized INIT_DEST=0. Override this with
2305 : * NO_DATA now */
2306 2 : psWO->papszWarpOptions = CSLSetNameValue(
2307 : psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
2308 : }
2309 : }
2310 : else
2311 : {
2312 0 : break;
2313 : }
2314 : }
2315 : }
2316 :
2317 : /* else try to fill dstNoData from source bands, unless -dstalpha is
2318 : * specified */
2319 1138 : else if (psOptions->osDstNodata.empty() &&
2320 995 : psWO->padfSrcNoDataReal != nullptr &&
2321 2133 : psWO->padfDstNoDataReal == nullptr && !bEnableDstAlpha)
2322 : {
2323 76 : psWO->padfDstNoDataReal =
2324 76 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
2325 :
2326 76 : if (psWO->padfSrcNoDataImag != nullptr)
2327 : {
2328 1 : psWO->padfDstNoDataImag = static_cast<double *>(
2329 1 : CPLMalloc(psWO->nBandCount * sizeof(double)));
2330 : }
2331 :
2332 76 : if (!psOptions->bQuiet)
2333 3 : printf("Copying nodata values from source %s to destination %s.\n",
2334 : GDALGetDescription(hSrcDS), pszDest);
2335 :
2336 171 : for (int i = 0; i < psWO->nBandCount; i++)
2337 : {
2338 95 : psWO->padfDstNoDataReal[i] = psWO->padfSrcNoDataReal[i];
2339 95 : if (psWO->padfSrcNoDataImag != nullptr)
2340 : {
2341 1 : psWO->padfDstNoDataImag[i] = psWO->padfSrcNoDataImag[i];
2342 : }
2343 95 : CPLDebug("WARP", "srcNoData=%f dstNoData=%f",
2344 95 : psWO->padfSrcNoDataReal[i], psWO->padfDstNoDataReal[i]);
2345 :
2346 95 : if (psOptions->bCreateOutput && iSrc == 0)
2347 : {
2348 95 : CPLDebug("WARP",
2349 : "calling GDALSetRasterNoDataValue() for band#%d", i);
2350 95 : GDALSetRasterNoDataValue(
2351 95 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]),
2352 95 : psWO->padfDstNoDataReal[i]);
2353 : }
2354 : }
2355 :
2356 76 : if (psOptions->bCreateOutput && !bInitDestSetByUser && iSrc == 0)
2357 : {
2358 : /* As we didn't know at the beginning if there was source nodata */
2359 : /* we have initialized INIT_DEST=0. Override this with NO_DATA now
2360 : */
2361 73 : psWO->papszWarpOptions =
2362 73 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
2363 : }
2364 : }
2365 :
2366 1140 : return CE_None;
2367 : }
2368 :
2369 : /************************************************************************/
2370 : /* SetupSkipNoSource() */
2371 : /************************************************************************/
2372 :
2373 1140 : static void SetupSkipNoSource(int iSrc, GDALDatasetH hDstDS,
2374 : GDALWarpOptions *psWO,
2375 : GDALWarpAppOptions *psOptions)
2376 : {
2377 1049 : if (psOptions->bCreateOutput && iSrc == 0 &&
2378 1027 : CSLFetchNameValue(psWO->papszWarpOptions, "SKIP_NOSOURCE") == nullptr &&
2379 1022 : CSLFetchNameValue(psWO->papszWarpOptions, "STREAMABLE_OUTPUT") ==
2380 2189 : nullptr &&
2381 : // This white list of drivers could potentially be extended.
2382 1021 : (EQUAL(psOptions->osFormat.c_str(), "MEM") ||
2383 618 : EQUAL(psOptions->osFormat.c_str(), "GTiff") ||
2384 178 : EQUAL(psOptions->osFormat.c_str(), "GPKG")))
2385 : {
2386 : // We can enable the optimization only if the user didn't specify
2387 : // a INIT_DEST value that would contradict the destination nodata.
2388 :
2389 843 : bool bOKRegardingInitDest = false;
2390 : const char *pszInitDest =
2391 843 : CSLFetchNameValue(psWO->papszWarpOptions, "INIT_DEST");
2392 843 : if (pszInitDest == nullptr || EQUAL(pszInitDest, "NO_DATA"))
2393 : {
2394 175 : bOKRegardingInitDest = true;
2395 :
2396 : // The MEM driver will return non-initialized blocks at 0
2397 : // so make sure that the nodata value is 0.
2398 175 : if (EQUAL(psOptions->osFormat.c_str(), "MEM"))
2399 : {
2400 346 : for (int i = 0; i < GDALGetRasterCount(hDstDS); i++)
2401 : {
2402 234 : int bHasNoData = false;
2403 234 : double dfDstNoDataVal = GDALGetRasterNoDataValue(
2404 : GDALGetRasterBand(hDstDS, i + 1), &bHasNoData);
2405 234 : if (bHasNoData && dfDstNoDataVal != 0.0)
2406 : {
2407 38 : bOKRegardingInitDest = false;
2408 38 : break;
2409 : }
2410 : }
2411 175 : }
2412 : }
2413 : else
2414 : {
2415 668 : char **papszTokensInitDest = CSLTokenizeString(pszInitDest);
2416 668 : const int nTokenCountInitDest = CSLCount(papszTokensInitDest);
2417 668 : if (nTokenCountInitDest == 1 ||
2418 0 : nTokenCountInitDest == GDALGetRasterCount(hDstDS))
2419 : {
2420 668 : bOKRegardingInitDest = true;
2421 1555 : for (int i = 0; i < GDALGetRasterCount(hDstDS); i++)
2422 : {
2423 895 : double dfInitVal = GDALAdjustNoDataCloseToFloatMax(
2424 895 : CPLAtofM(papszTokensInitDest[std::min(
2425 895 : i, nTokenCountInitDest - 1)]));
2426 895 : int bHasNoData = false;
2427 895 : double dfDstNoDataVal = GDALGetRasterNoDataValue(
2428 : GDALGetRasterBand(hDstDS, i + 1), &bHasNoData);
2429 895 : if (!((bHasNoData && dfInitVal == dfDstNoDataVal) ||
2430 893 : (!bHasNoData && dfInitVal == 0.0)))
2431 : {
2432 7 : bOKRegardingInitDest = false;
2433 8 : break;
2434 : }
2435 888 : if (EQUAL(psOptions->osFormat.c_str(), "MEM") &&
2436 888 : bHasNoData && dfDstNoDataVal != 0.0)
2437 : {
2438 1 : bOKRegardingInitDest = false;
2439 1 : break;
2440 : }
2441 : }
2442 : }
2443 668 : CSLDestroy(papszTokensInitDest);
2444 : }
2445 :
2446 843 : if (bOKRegardingInitDest)
2447 : {
2448 797 : CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2449 797 : psWO->papszWarpOptions =
2450 797 : CSLSetNameValue(psWO->papszWarpOptions, "SKIP_NOSOURCE", "YES");
2451 : }
2452 : }
2453 1140 : }
2454 :
2455 : /************************************************************************/
2456 : /* AdjustOutputExtentForRPC() */
2457 : /************************************************************************/
2458 :
2459 : /** Returns false if there's no intersection between source extent defined
2460 : * by RPC and target extent.
2461 : */
2462 1140 : static bool AdjustOutputExtentForRPC(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
2463 : GDALTransformerFunc pfnTransformer,
2464 : void *hTransformArg, GDALWarpOptions *psWO,
2465 : GDALWarpAppOptions *psOptions,
2466 : int &nWarpDstXOff, int &nWarpDstYOff,
2467 : int &nWarpDstXSize, int &nWarpDstYSize)
2468 : {
2469 1140 : if (CPLTestBool(CSLFetchNameValueDef(psWO->papszWarpOptions,
2470 913 : "SKIP_NOSOURCE", "NO")) &&
2471 913 : GDALGetMetadata(hSrcDS, "RPC") != nullptr &&
2472 2065 : EQUAL(FetchSrcMethod(psOptions->aosTransformerOptions, "RPC"), "RPC") &&
2473 12 : CPLTestBool(
2474 : CPLGetConfigOption("RESTRICT_OUTPUT_DATASET_UPDATE", "YES")))
2475 : {
2476 : double adfSuggestedGeoTransform[6];
2477 : double adfExtent[4];
2478 : int nPixels, nLines;
2479 10 : if (GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, hTransformArg,
2480 : adfSuggestedGeoTransform, &nPixels,
2481 10 : &nLines, adfExtent, 0) == CE_None)
2482 : {
2483 6 : const double dfMinX = adfExtent[0];
2484 6 : const double dfMinY = adfExtent[1];
2485 6 : const double dfMaxX = adfExtent[2];
2486 6 : const double dfMaxY = adfExtent[3];
2487 6 : const double dfThreshold = static_cast<double>(INT_MAX) / 2;
2488 6 : if (std::fabs(dfMinX) < dfThreshold &&
2489 6 : std::fabs(dfMinY) < dfThreshold &&
2490 6 : std::fabs(dfMaxX) < dfThreshold &&
2491 6 : std::fabs(dfMaxY) < dfThreshold)
2492 : {
2493 6 : const int nPadding = 5;
2494 6 : nWarpDstXOff =
2495 6 : std::max(nWarpDstXOff,
2496 6 : static_cast<int>(std::floor(dfMinX)) - nPadding);
2497 6 : nWarpDstYOff =
2498 6 : std::max(nWarpDstYOff,
2499 6 : static_cast<int>(std::floor(dfMinY)) - nPadding);
2500 12 : nWarpDstXSize = std::min(nWarpDstXSize - nWarpDstXOff,
2501 6 : static_cast<int>(std::ceil(dfMaxX)) +
2502 6 : nPadding - nWarpDstXOff);
2503 12 : nWarpDstYSize = std::min(nWarpDstYSize - nWarpDstYOff,
2504 6 : static_cast<int>(std::ceil(dfMaxY)) +
2505 6 : nPadding - nWarpDstYOff);
2506 6 : if (nWarpDstXSize <= 0 || nWarpDstYSize <= 0)
2507 : {
2508 1 : CPLDebug("WARP",
2509 : "No intersection between source extent defined "
2510 : "by RPC and target extent");
2511 1 : return false;
2512 : }
2513 2 : if (nWarpDstXOff != 0 || nWarpDstYOff != 0 ||
2514 9 : nWarpDstXSize != GDALGetRasterXSize(hDstDS) ||
2515 2 : nWarpDstYSize != GDALGetRasterYSize(hDstDS))
2516 : {
2517 3 : CPLDebug("WARP",
2518 : "Restricting warping to output dataset window "
2519 : "%d,%d,%dx%d",
2520 : nWarpDstXOff, nWarpDstYOff, nWarpDstXSize,
2521 : nWarpDstYSize);
2522 : }
2523 : }
2524 : }
2525 : }
2526 1139 : return true;
2527 : }
2528 :
2529 : /************************************************************************/
2530 : /* GDALWarpDirect() */
2531 : /************************************************************************/
2532 :
2533 : static GDALDatasetH
2534 1155 : GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
2535 : GDALDatasetH *pahSrcDS,
2536 : GDALTransformerArgUniquePtr hUniqueTransformArg,
2537 : GDALWarpAppOptions *psOptions, int *pbUsageError)
2538 : {
2539 1155 : CPLErrorReset();
2540 1155 : if (pszDest == nullptr && hDstDS == nullptr)
2541 : {
2542 0 : CPLError(CE_Failure, CPLE_AppDefined,
2543 : "pszDest == NULL && hDstDS == NULL");
2544 :
2545 0 : if (pbUsageError)
2546 0 : *pbUsageError = TRUE;
2547 0 : return nullptr;
2548 : }
2549 1155 : if (pszDest == nullptr)
2550 90 : pszDest = GDALGetDescription(hDstDS);
2551 :
2552 : #ifdef DEBUG
2553 1155 : GDALDataset *poDstDS = GDALDataset::FromHandle(hDstDS);
2554 : const int nExpectedRefCountAtEnd =
2555 1155 : (poDstDS != nullptr) ? poDstDS->GetRefCount() : 1;
2556 : (void)nExpectedRefCountAtEnd;
2557 : #endif
2558 1155 : const bool bDropDstDSRef = (hDstDS != nullptr);
2559 1155 : if (hDstDS != nullptr)
2560 93 : GDALReferenceDataset(hDstDS);
2561 :
2562 : #if defined(USE_PROJ_BASED_VERTICAL_SHIFT_METHOD)
2563 1155 : if (psOptions->bNoVShift)
2564 : {
2565 1 : psOptions->aosTransformerOptions.SetNameValue("@STRIP_VERT_CS", "YES");
2566 : }
2567 1154 : else if (nSrcCount)
2568 : {
2569 1148 : bool bSrcHasVertAxis = false;
2570 1148 : bool bDstHasVertAxis = false;
2571 2296 : OGRSpatialReference oSRSSrc;
2572 2296 : OGRSpatialReference oSRSDst;
2573 :
2574 1148 : if (MustApplyVerticalShift(pahSrcDS[0], psOptions, oSRSSrc, oSRSDst,
2575 : bSrcHasVertAxis, bDstHasVertAxis))
2576 : {
2577 : psOptions->aosTransformerOptions.SetNameValue("PROMOTE_TO_3D",
2578 19 : "YES");
2579 : }
2580 : }
2581 : #else
2582 : psOptions->aosTransformerOptions.SetNameValue("@STRIP_VERT_CS", "YES");
2583 : #endif
2584 :
2585 1155 : bool bVRT = false;
2586 1155 : if (!CheckOptions(pszDest, hDstDS, nSrcCount, pahSrcDS, psOptions, bVRT,
2587 : pbUsageError))
2588 : {
2589 1 : return nullptr;
2590 : }
2591 :
2592 : /* -------------------------------------------------------------------- */
2593 : /* If we have a cutline datasource read it and attach it in the */
2594 : /* warp options. */
2595 : /* -------------------------------------------------------------------- */
2596 1154 : std::unique_ptr<OGRGeometry> poCutline;
2597 1154 : if (!ProcessCutlineOptions(nSrcCount, pahSrcDS, psOptions, poCutline))
2598 : {
2599 11 : return nullptr;
2600 : }
2601 :
2602 : /* -------------------------------------------------------------------- */
2603 : /* If the target dataset does not exist, we need to create it. */
2604 : /* -------------------------------------------------------------------- */
2605 : const bool bInitDestSetByUser =
2606 1143 : (psOptions->aosWarpOptions.FetchNameValue("INIT_DEST") != nullptr);
2607 :
2608 1143 : const bool bFigureoutCorrespondingWindow =
2609 2193 : (hDstDS != nullptr) ||
2610 1050 : (((psOptions->nForcePixels != 0 && psOptions->nForceLines != 0) ||
2611 882 : (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)) &&
2612 254 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
2613 132 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0));
2614 :
2615 1143 : const char *pszMethod = FetchSrcMethod(psOptions->aosTransformerOptions);
2616 40 : if (pszMethod && EQUAL(pszMethod, "GCP_TPS") &&
2617 1188 : psOptions->dfErrorThreshold > 0 &&
2618 5 : !psOptions->aosTransformerOptions.FetchNameValue(
2619 : "SRC_APPROX_ERROR_IN_PIXEL"))
2620 : {
2621 : psOptions->aosTransformerOptions.SetNameValue(
2622 : "SRC_APPROX_ERROR_IN_PIXEL",
2623 5 : CPLSPrintf("%g", psOptions->dfErrorThreshold));
2624 : }
2625 :
2626 1143 : if (hDstDS == nullptr)
2627 : {
2628 1050 : hDstDS = CreateOutput(pszDest, nSrcCount, pahSrcDS, psOptions,
2629 : bInitDestSetByUser, hUniqueTransformArg);
2630 1050 : if (!hDstDS)
2631 : {
2632 19 : return nullptr;
2633 : }
2634 : #ifdef DEBUG
2635 : // Do not remove this if the #ifdef DEBUG before is still there !
2636 1031 : poDstDS = GDALDataset::FromHandle(hDstDS);
2637 1031 : CPL_IGNORE_RET_VAL(poDstDS);
2638 : #endif
2639 : }
2640 : else
2641 : {
2642 93 : if (psOptions->aosWarpOptions.FetchNameValue("SKIP_NOSOURCE") ==
2643 : nullptr)
2644 : {
2645 92 : CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2646 92 : psOptions->aosWarpOptions.SetNameValue("SKIP_NOSOURCE", "YES");
2647 : }
2648 : }
2649 :
2650 : /* -------------------------------------------------------------------- */
2651 : /* Detect if output has alpha channel. */
2652 : /* -------------------------------------------------------------------- */
2653 1124 : bool bEnableDstAlpha = psOptions->bEnableDstAlpha;
2654 1025 : if (!bEnableDstAlpha && GDALGetRasterCount(hDstDS) &&
2655 1024 : GDALGetRasterColorInterpretation(GDALGetRasterBand(
2656 2149 : hDstDS, GDALGetRasterCount(hDstDS))) == GCI_AlphaBand &&
2657 34 : !psOptions->bDisableSrcAlpha)
2658 : {
2659 33 : if (!psOptions->bQuiet)
2660 1 : printf("Using band %d of destination image as alpha.\n",
2661 : GDALGetRasterCount(hDstDS));
2662 :
2663 33 : bEnableDstAlpha = true;
2664 : }
2665 :
2666 : /* -------------------------------------------------------------------- */
2667 : /* Create global progress function. */
2668 : /* -------------------------------------------------------------------- */
2669 : struct Progress
2670 : {
2671 : GDALProgressFunc pfnExternalProgress;
2672 : void *pExternalProgressData;
2673 : int iSrc;
2674 : int nSrcCount;
2675 : GDALDatasetH *pahSrcDS;
2676 :
2677 18912 : int Do(double dfComplete)
2678 : {
2679 37824 : CPLString osMsg;
2680 : osMsg.Printf("Processing %s [%d/%d]",
2681 18912 : CPLGetFilename(GDALGetDescription(pahSrcDS[iSrc])),
2682 18912 : iSrc + 1, nSrcCount);
2683 18912 : return pfnExternalProgress((iSrc + dfComplete) / nSrcCount,
2684 37824 : osMsg.c_str(), pExternalProgressData);
2685 : }
2686 :
2687 17603 : static int CPL_STDCALL ProgressFunc(double dfComplete, const char *,
2688 : void *pThis)
2689 : {
2690 17603 : return static_cast<Progress *>(pThis)->Do(dfComplete);
2691 : }
2692 : };
2693 :
2694 : Progress oProgress;
2695 1124 : oProgress.pfnExternalProgress = psOptions->pfnProgress;
2696 1124 : oProgress.pExternalProgressData = psOptions->pProgressData;
2697 1124 : oProgress.nSrcCount = nSrcCount;
2698 1124 : oProgress.pahSrcDS = pahSrcDS;
2699 :
2700 : /* -------------------------------------------------------------------- */
2701 : /* Loop over all source files, processing each in turn. */
2702 : /* -------------------------------------------------------------------- */
2703 1124 : bool bHasGotErr = false;
2704 2092 : for (int iSrc = 0; iSrc < nSrcCount; iSrc++)
2705 : {
2706 : GDALDatasetH hSrcDS;
2707 :
2708 : /* --------------------------------------------------------------------
2709 : */
2710 : /* Open this file. */
2711 : /* --------------------------------------------------------------------
2712 : */
2713 1146 : hSrcDS = pahSrcDS[iSrc];
2714 1146 : oProgress.iSrc = iSrc;
2715 :
2716 : /* --------------------------------------------------------------------
2717 : */
2718 : /* Check that there's at least one raster band */
2719 : /* --------------------------------------------------------------------
2720 : */
2721 1146 : if (GDALGetRasterCount(hSrcDS) == 0)
2722 : {
2723 1 : CPLError(CE_Failure, CPLE_AppDefined,
2724 : "Input file %s has no raster bands.",
2725 : GDALGetDescription(hSrcDS));
2726 1 : GDALReleaseDataset(hDstDS);
2727 178 : return nullptr;
2728 : }
2729 :
2730 : /* --------------------------------------------------------------------
2731 : */
2732 : /* Do we have a source alpha band? */
2733 : /* --------------------------------------------------------------------
2734 : */
2735 1145 : bool bEnableSrcAlpha = psOptions->bEnableSrcAlpha;
2736 1145 : if (GDALGetRasterColorInterpretation(GDALGetRasterBand(
2737 73 : hSrcDS, GDALGetRasterCount(hSrcDS))) == GCI_AlphaBand &&
2738 1145 : !bEnableSrcAlpha && !psOptions->bDisableSrcAlpha)
2739 : {
2740 31 : bEnableSrcAlpha = true;
2741 31 : if (!psOptions->bQuiet)
2742 0 : printf("Using band %d of source image as alpha.\n",
2743 : GDALGetRasterCount(hSrcDS));
2744 : }
2745 :
2746 : /* --------------------------------------------------------------------
2747 : */
2748 : /* Get the metadata of the first source DS and copy it to the */
2749 : /* destination DS. Copy Band-level metadata and other info, only */
2750 : /* if source and destination band count are equal. Any values that
2751 : */
2752 : /* conflict between source datasets are set to pszMDConflictValue.
2753 : */
2754 : /* --------------------------------------------------------------------
2755 : */
2756 1145 : ProcessMetadata(iSrc, hSrcDS, hDstDS, psOptions, bEnableDstAlpha);
2757 :
2758 : /* --------------------------------------------------------------------
2759 : */
2760 : /* Warns if the file has a color table and something more */
2761 : /* complicated than nearest neighbour resampling is asked */
2762 : /* --------------------------------------------------------------------
2763 : */
2764 :
2765 2817 : if (psOptions->eResampleAlg != GRA_NearestNeighbour &&
2766 1631 : psOptions->eResampleAlg != GRA_Mode &&
2767 486 : GDALGetRasterColorTable(GDALGetRasterBand(hSrcDS, 1)) != nullptr)
2768 : {
2769 0 : if (!psOptions->bQuiet)
2770 0 : CPLError(
2771 : CE_Warning, CPLE_AppDefined,
2772 : "Input file %s has a color table, which will likely lead "
2773 : "to "
2774 : "bad results when using a resampling method other than "
2775 : "nearest neighbour or mode. Converting the dataset prior "
2776 : "to 24/32 bit "
2777 : "is advised.",
2778 : GDALGetDescription(hSrcDS));
2779 : }
2780 :
2781 : // For RPC warping add a few extra source pixels by default
2782 : // (probably mostly needed in the RPC DEM case)
2783 1148 : if (iSrc == 0 && (GDALGetMetadata(hSrcDS, "RPC") != nullptr &&
2784 3 : (pszMethod == nullptr || EQUAL(pszMethod, "RPC"))))
2785 : {
2786 13 : if (!psOptions->aosWarpOptions.FetchNameValue("SOURCE_EXTRA"))
2787 : {
2788 13 : CPLDebug(
2789 : "WARP",
2790 : "Set SOURCE_EXTRA=5 warping options due to RPC warping");
2791 13 : psOptions->aosWarpOptions.SetNameValue("SOURCE_EXTRA", "5");
2792 : }
2793 :
2794 13 : if (!psOptions->aosWarpOptions.FetchNameValue("SAMPLE_STEPS") &&
2795 26 : !psOptions->aosWarpOptions.FetchNameValue("SAMPLE_GRID") &&
2796 13 : psOptions->aosTransformerOptions.FetchNameValue("RPC_DEM"))
2797 : {
2798 10 : CPLDebug("WARP", "Set SAMPLE_STEPS=ALL warping options due to "
2799 : "RPC DEM warping");
2800 10 : psOptions->aosWarpOptions.SetNameValue("SAMPLE_STEPS", "ALL");
2801 : }
2802 : }
2803 : // Also do the same for GCP TPS warping, e.g. to solve use case of
2804 : // https://github.com/OSGeo/gdal/issues/12736
2805 2240 : else if (iSrc == 0 &&
2806 1108 : (GDALGetGCPCount(hSrcDS) > 0 &&
2807 6 : (pszMethod == nullptr || EQUAL(pszMethod, "TPS"))))
2808 : {
2809 21 : if (!psOptions->aosWarpOptions.FetchNameValue("SOURCE_EXTRA"))
2810 : {
2811 21 : CPLDebug(
2812 : "WARP",
2813 : "Set SOURCE_EXTRA=5 warping options due to TPS warping");
2814 : }
2815 : }
2816 :
2817 1145 : if (iSrc > 0)
2818 : psOptions->aosWarpOptions.SetNameValue("RESET_DEST_PIXELS",
2819 24 : nullptr);
2820 :
2821 : /* --------------------------------------------------------------------
2822 : */
2823 : /* Create a transformation object from the source to */
2824 : /* destination coordinate system. */
2825 : /* --------------------------------------------------------------------
2826 : */
2827 0 : GDALTransformerArgUniquePtr hTransformArg;
2828 1145 : if (hUniqueTransformArg)
2829 1009 : hTransformArg = std::move(hUniqueTransformArg);
2830 : else
2831 : {
2832 136 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
2833 136 : hSrcDS, hDstDS, psOptions->aosTransformerOptions.List()));
2834 136 : if (hTransformArg == nullptr)
2835 : {
2836 0 : GDALReleaseDataset(hDstDS);
2837 0 : return nullptr;
2838 : }
2839 : }
2840 :
2841 1145 : GDALTransformerFunc pfnTransformer = GDALGenImgProjTransform;
2842 :
2843 : // Check if transformation is inversible
2844 : {
2845 1145 : double dfX = GDALGetRasterXSize(hDstDS) / 2.0;
2846 1145 : double dfY = GDALGetRasterYSize(hDstDS) / 2.0;
2847 1145 : double dfZ = 0;
2848 1145 : int bSuccess = false;
2849 1145 : const auto nErrorCounterBefore = CPLGetErrorCounter();
2850 1145 : pfnTransformer(hTransformArg.get(), TRUE, 1, &dfX, &dfY, &dfZ,
2851 : &bSuccess);
2852 1145 : if (!bSuccess && CPLGetErrorCounter() > nErrorCounterBefore &&
2853 0 : strstr(CPLGetLastErrorMsg(), "No inverse operation"))
2854 : {
2855 0 : GDALReleaseDataset(hDstDS);
2856 0 : return nullptr;
2857 : }
2858 : }
2859 :
2860 : /* --------------------------------------------------------------------
2861 : */
2862 : /* Determine if we must work with the full-resolution source */
2863 : /* dataset, or one of its overview level. */
2864 : /* --------------------------------------------------------------------
2865 : */
2866 1145 : GDALDataset *poSrcDS = static_cast<GDALDataset *>(hSrcDS);
2867 1145 : GDALDataset *poSrcOvrDS = nullptr;
2868 1145 : int nOvCount = poSrcDS->GetRasterBand(1)->GetOverviewCount();
2869 1145 : if (psOptions->nOvLevel <= OVR_LEVEL_AUTO && nOvCount > 0)
2870 : {
2871 22 : double dfTargetRatio = 0;
2872 22 : double dfTargetRatioX = 0;
2873 22 : double dfTargetRatioY = 0;
2874 :
2875 22 : if (bFigureoutCorrespondingWindow)
2876 : {
2877 : // If the user has explicitly set the target bounds and
2878 : // resolution, or we're updating an existing file, then figure
2879 : // out which source window corresponds to the target raster.
2880 4 : constexpr int nPointsOneDim = 10;
2881 4 : constexpr int nPoints = nPointsOneDim * nPointsOneDim;
2882 8 : std::vector<double> adfX(nPoints);
2883 8 : std::vector<double> adfY(nPoints);
2884 8 : std::vector<double> adfZ(nPoints);
2885 4 : const int nDstXSize = GDALGetRasterXSize(hDstDS);
2886 4 : const int nDstYSize = GDALGetRasterYSize(hDstDS);
2887 4 : int iPoint = 0;
2888 44 : for (int iX = 0; iX < nPointsOneDim; ++iX)
2889 : {
2890 440 : for (int iY = 0; iY < nPointsOneDim; ++iY)
2891 : {
2892 400 : adfX[iPoint] = nDstXSize * static_cast<double>(iX) /
2893 : (nPointsOneDim - 1);
2894 400 : adfY[iPoint] = nDstYSize * static_cast<double>(iY) /
2895 : (nPointsOneDim - 1);
2896 400 : iPoint++;
2897 : }
2898 : }
2899 4 : std::vector<int> abSuccess(nPoints);
2900 4 : pfnTransformer(hTransformArg.get(), TRUE, nPoints, &adfX[0],
2901 4 : &adfY[0], &adfZ[0], &abSuccess[0]);
2902 :
2903 4 : double dfMinSrcX = std::numeric_limits<double>::infinity();
2904 4 : double dfMaxSrcX = -std::numeric_limits<double>::infinity();
2905 4 : double dfMinSrcY = std::numeric_limits<double>::infinity();
2906 4 : double dfMaxSrcY = -std::numeric_limits<double>::infinity();
2907 404 : for (int i = 0; i < nPoints; i++)
2908 : {
2909 400 : if (abSuccess[i])
2910 : {
2911 400 : dfMinSrcX = std::min(dfMinSrcX, adfX[i]);
2912 400 : dfMaxSrcX = std::max(dfMaxSrcX, adfX[i]);
2913 400 : dfMinSrcY = std::min(dfMinSrcY, adfY[i]);
2914 400 : dfMaxSrcY = std::max(dfMaxSrcY, adfY[i]);
2915 : }
2916 : }
2917 4 : if (dfMaxSrcX > dfMinSrcX)
2918 : {
2919 4 : dfTargetRatioX =
2920 4 : (dfMaxSrcX - dfMinSrcX) / GDALGetRasterXSize(hDstDS);
2921 : }
2922 4 : if (dfMaxSrcY > dfMinSrcY)
2923 : {
2924 4 : dfTargetRatioY =
2925 4 : (dfMaxSrcY - dfMinSrcY) / GDALGetRasterYSize(hDstDS);
2926 : }
2927 : // take the minimum of these ratios #7019
2928 4 : dfTargetRatio = std::min(dfTargetRatioX, dfTargetRatioY);
2929 : }
2930 : else
2931 : {
2932 : /* Compute what the "natural" output resolution (in pixels)
2933 : * would be for this */
2934 : /* input dataset */
2935 : double adfSuggestedGeoTransform[6];
2936 : int nPixels, nLines;
2937 18 : if (GDALSuggestedWarpOutput(
2938 : hSrcDS, pfnTransformer, hTransformArg.get(),
2939 18 : adfSuggestedGeoTransform, &nPixels, &nLines) == CE_None)
2940 : {
2941 18 : dfTargetRatio = 1.0 / adfSuggestedGeoTransform[1];
2942 : }
2943 : }
2944 :
2945 22 : if (dfTargetRatio > 1.0)
2946 : {
2947 : // Note: keep this logic for overview selection in sync between
2948 : // gdalwarp_lib.cpp and rasterio.cpp
2949 15 : const char *pszOversampligThreshold = CPLGetConfigOption(
2950 : "GDALWARP_OVERSAMPLING_THRESHOLD", nullptr);
2951 : const double dfOversamplingThreshold =
2952 15 : pszOversampligThreshold ? CPLAtof(pszOversampligThreshold)
2953 15 : : 1.0;
2954 :
2955 15 : int iBestOvr = -1;
2956 15 : double dfBestRatio = 0;
2957 35 : for (int iOvr = -1; iOvr < nOvCount; iOvr++)
2958 : {
2959 : const double dfOvrRatio =
2960 : iOvr < 0
2961 31 : ? 1.0
2962 16 : : static_cast<double>(poSrcDS->GetRasterXSize()) /
2963 16 : poSrcDS->GetRasterBand(1)
2964 16 : ->GetOverview(iOvr)
2965 16 : ->GetXSize();
2966 :
2967 : // Is it nearly the requested factor and better (lower) than
2968 : // the current best factor?
2969 : // Use an epsilon because of numerical instability.
2970 31 : constexpr double EPSILON = 1e-1;
2971 35 : if (dfOvrRatio >=
2972 31 : dfTargetRatio * dfOversamplingThreshold + EPSILON ||
2973 : dfOvrRatio <= dfBestRatio)
2974 : {
2975 4 : continue;
2976 : }
2977 :
2978 27 : iBestOvr = iOvr;
2979 27 : dfBestRatio = dfOvrRatio;
2980 27 : if (std::abs(dfTargetRatio - dfOvrRatio) < EPSILON)
2981 : {
2982 11 : break;
2983 : }
2984 : }
2985 15 : const int iOvr =
2986 15 : iBestOvr + (psOptions->nOvLevel - OVR_LEVEL_AUTO);
2987 15 : if (iOvr >= 0)
2988 : {
2989 9 : CPLDebug("WARP", "Selecting overview level %d for %s", iOvr,
2990 : GDALGetDescription(hSrcDS));
2991 : poSrcOvrDS =
2992 9 : GDALCreateOverviewDataset(poSrcDS, iOvr,
2993 : /* bThisLevelOnly = */ false);
2994 : }
2995 22 : }
2996 : }
2997 1123 : else if (psOptions->nOvLevel >= 0)
2998 : {
2999 6 : poSrcOvrDS = GDALCreateOverviewDataset(poSrcDS, psOptions->nOvLevel,
3000 : /* bThisLevelOnly = */ true);
3001 6 : if (poSrcOvrDS == nullptr)
3002 : {
3003 1 : if (!psOptions->bQuiet)
3004 : {
3005 1 : CPLError(CE_Warning, CPLE_AppDefined,
3006 : "cannot get overview level %d for "
3007 : "dataset %s. Defaulting to level %d",
3008 : psOptions->nOvLevel, GDALGetDescription(hSrcDS),
3009 : nOvCount - 1);
3010 : }
3011 1 : if (nOvCount > 0)
3012 : poSrcOvrDS =
3013 1 : GDALCreateOverviewDataset(poSrcDS, nOvCount - 1,
3014 : /* bThisLevelOnly = */ false);
3015 : }
3016 : else
3017 : {
3018 5 : CPLDebug("WARP", "Selecting overview level %d for %s",
3019 : psOptions->nOvLevel, GDALGetDescription(hSrcDS));
3020 : }
3021 : }
3022 :
3023 1145 : if (poSrcOvrDS == nullptr)
3024 1130 : GDALReferenceDataset(hSrcDS);
3025 :
3026 1145 : GDALDatasetH hWrkSrcDS =
3027 1145 : poSrcOvrDS ? static_cast<GDALDatasetH>(poSrcOvrDS) : hSrcDS;
3028 :
3029 : #if !defined(USE_PROJ_BASED_VERTICAL_SHIFT_METHOD)
3030 : if (!psOptions->bNoVShift)
3031 : {
3032 : bool bErrorOccurred = false;
3033 : hWrkSrcDS = ApplyVerticalShiftGrid(
3034 : hWrkSrcDS, psOptions, bVRT ? hDstDS : nullptr, bErrorOccurred);
3035 : if (bErrorOccurred)
3036 : {
3037 : GDALReleaseDataset(hWrkSrcDS);
3038 : GDALReleaseDataset(hDstDS);
3039 : return nullptr;
3040 : }
3041 : }
3042 : #endif
3043 :
3044 : /* --------------------------------------------------------------------
3045 : */
3046 : /* Clear temporary INIT_DEST settings after the first image. */
3047 : /* --------------------------------------------------------------------
3048 : */
3049 1145 : if (psOptions->bCreateOutput && iSrc == 1)
3050 22 : psOptions->aosWarpOptions.SetNameValue("INIT_DEST", nullptr);
3051 :
3052 : /* --------------------------------------------------------------------
3053 : */
3054 : /* Define SKIP_NOSOURCE after the first image (since
3055 : * initialization*/
3056 : /* has already be done). */
3057 : /* --------------------------------------------------------------------
3058 : */
3059 1145 : if (iSrc == 1 && psOptions->aosWarpOptions.FetchNameValue(
3060 : "SKIP_NOSOURCE") == nullptr)
3061 : {
3062 22 : CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
3063 22 : psOptions->aosWarpOptions.SetNameValue("SKIP_NOSOURCE", "YES");
3064 : }
3065 :
3066 : /* --------------------------------------------------------------------
3067 : */
3068 : /* Setup warp options. */
3069 : /* --------------------------------------------------------------------
3070 : */
3071 : std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>
3072 1145 : psWO(GDALCreateWarpOptions(), GDALDestroyWarpOptions);
3073 :
3074 1145 : psWO->papszWarpOptions = CSLDuplicate(psOptions->aosWarpOptions.List());
3075 1145 : psWO->eWorkingDataType = psOptions->eWorkingType;
3076 :
3077 1145 : psWO->eResampleAlg = psOptions->eResampleAlg;
3078 :
3079 1145 : psWO->hSrcDS = hWrkSrcDS;
3080 1145 : psWO->hDstDS = hDstDS;
3081 :
3082 1145 : if (!bVRT)
3083 : {
3084 974 : if (psOptions->pfnProgress == GDALDummyProgress)
3085 : {
3086 856 : psWO->pfnProgress = GDALDummyProgress;
3087 856 : psWO->pProgressArg = nullptr;
3088 : }
3089 : else
3090 : {
3091 118 : psWO->pfnProgress = Progress::ProgressFunc;
3092 118 : psWO->pProgressArg = &oProgress;
3093 : }
3094 : }
3095 :
3096 1145 : if (psOptions->dfWarpMemoryLimit != 0.0)
3097 35 : psWO->dfWarpMemoryLimit = psOptions->dfWarpMemoryLimit;
3098 :
3099 : /* --------------------------------------------------------------------
3100 : */
3101 : /* Setup band mapping. */
3102 : /* --------------------------------------------------------------------
3103 : */
3104 1145 : if (psOptions->anSrcBands.empty())
3105 : {
3106 1118 : if (bEnableSrcAlpha)
3107 62 : psWO->nBandCount = GDALGetRasterCount(hWrkSrcDS) - 1;
3108 : else
3109 1056 : psWO->nBandCount = GDALGetRasterCount(hWrkSrcDS);
3110 : }
3111 : else
3112 : {
3113 27 : psWO->nBandCount = static_cast<int>(psOptions->anSrcBands.size());
3114 : }
3115 :
3116 : const int nNeededDstBands =
3117 1145 : psWO->nBandCount + (bEnableDstAlpha ? 1 : 0);
3118 1145 : if (nNeededDstBands > GDALGetRasterCount(hDstDS))
3119 : {
3120 1 : CPLError(CE_Failure, CPLE_AppDefined,
3121 : "Destination dataset has %d bands, but at least %d "
3122 : "are needed",
3123 : GDALGetRasterCount(hDstDS), nNeededDstBands);
3124 1 : GDALReleaseDataset(hWrkSrcDS);
3125 1 : GDALReleaseDataset(hDstDS);
3126 1 : return nullptr;
3127 : }
3128 :
3129 2288 : psWO->panSrcBands =
3130 1144 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
3131 2288 : psWO->panDstBands =
3132 1144 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
3133 1144 : if (psOptions->anSrcBands.empty())
3134 : {
3135 2606 : for (int i = 0; i < psWO->nBandCount; i++)
3136 : {
3137 1489 : psWO->panSrcBands[i] = i + 1;
3138 1489 : psWO->panDstBands[i] = i + 1;
3139 : }
3140 : }
3141 : else
3142 : {
3143 68 : for (int i = 0; i < psWO->nBandCount; i++)
3144 : {
3145 86 : if (psOptions->anSrcBands[i] <= 0 ||
3146 43 : psOptions->anSrcBands[i] > GDALGetRasterCount(hSrcDS))
3147 : {
3148 1 : CPLError(CE_Failure, CPLE_AppDefined,
3149 : "-srcband[%d] = %d is invalid", i,
3150 1 : psOptions->anSrcBands[i]);
3151 1 : GDALReleaseDataset(hWrkSrcDS);
3152 1 : GDALReleaseDataset(hDstDS);
3153 1 : return nullptr;
3154 : }
3155 84 : if (psOptions->anDstBands[i] <= 0 ||
3156 42 : psOptions->anDstBands[i] > GDALGetRasterCount(hDstDS))
3157 : {
3158 1 : CPLError(CE_Failure, CPLE_AppDefined,
3159 : "-dstband[%d] = %d is invalid", i,
3160 1 : psOptions->anDstBands[i]);
3161 1 : GDALReleaseDataset(hWrkSrcDS);
3162 1 : GDALReleaseDataset(hDstDS);
3163 1 : return nullptr;
3164 : }
3165 41 : psWO->panSrcBands[i] = psOptions->anSrcBands[i];
3166 41 : psWO->panDstBands[i] = psOptions->anDstBands[i];
3167 : }
3168 : }
3169 :
3170 : /* --------------------------------------------------------------------
3171 : */
3172 : /* Setup alpha bands used if any. */
3173 : /* --------------------------------------------------------------------
3174 : */
3175 1142 : if (bEnableSrcAlpha)
3176 70 : psWO->nSrcAlphaBand = GDALGetRasterCount(hWrkSrcDS);
3177 :
3178 1142 : if (bEnableDstAlpha)
3179 : {
3180 137 : if (psOptions->anSrcBands.empty())
3181 128 : psWO->nDstAlphaBand = GDALGetRasterCount(hDstDS);
3182 : else
3183 9 : psWO->nDstAlphaBand =
3184 9 : static_cast<int>(psOptions->anDstBands.size()) + 1;
3185 : }
3186 :
3187 : /* ------------------------------------------------------------------ */
3188 : /* Setup NODATA options. */
3189 : /* ------------------------------------------------------------------ */
3190 1142 : if (SetupNoData(pszDest, iSrc, hSrcDS, hWrkSrcDS, hDstDS, psWO.get(),
3191 : psOptions, bEnableDstAlpha,
3192 1142 : bInitDestSetByUser) != CE_None)
3193 : {
3194 2 : GDALReleaseDataset(hWrkSrcDS);
3195 2 : GDALReleaseDataset(hDstDS);
3196 2 : return nullptr;
3197 : }
3198 :
3199 1140 : oProgress.Do(0);
3200 :
3201 : /* --------------------------------------------------------------------
3202 : */
3203 : /* For the first source image of a newly created dataset, decide */
3204 : /* if we can safely enable SKIP_NOSOURCE optimization. */
3205 : /* --------------------------------------------------------------------
3206 : */
3207 1140 : SetupSkipNoSource(iSrc, hDstDS, psWO.get(), psOptions);
3208 :
3209 : /* --------------------------------------------------------------------
3210 : */
3211 : /* In some cases, RPC evaluation can find valid input pixel for */
3212 : /* output pixels that are outside the footprint of the source */
3213 : /* dataset, so limit the area we update in the target dataset from
3214 : */
3215 : /* the suggested warp output (only in cases where
3216 : * SKIP_NOSOURCE=YES) */
3217 : /* --------------------------------------------------------------------
3218 : */
3219 1140 : int nWarpDstXOff = 0;
3220 1140 : int nWarpDstYOff = 0;
3221 1140 : int nWarpDstXSize = GDALGetRasterXSize(hDstDS);
3222 1140 : int nWarpDstYSize = GDALGetRasterYSize(hDstDS);
3223 :
3224 1140 : if (!AdjustOutputExtentForRPC(hSrcDS, hDstDS, pfnTransformer,
3225 : hTransformArg.get(), psWO.get(),
3226 : psOptions, nWarpDstXOff, nWarpDstYOff,
3227 : nWarpDstXSize, nWarpDstYSize))
3228 : {
3229 1 : GDALReleaseDataset(hWrkSrcDS);
3230 1 : continue;
3231 : }
3232 :
3233 : /* We need to recreate the transform when operating on an overview */
3234 1139 : if (poSrcOvrDS != nullptr)
3235 : {
3236 15 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
3237 15 : hWrkSrcDS, hDstDS, psOptions->aosTransformerOptions.List()));
3238 : }
3239 :
3240 1139 : bool bUseApproxTransformer = psOptions->dfErrorThreshold != 0.0;
3241 : #ifdef USE_PROJ_BASED_VERTICAL_SHIFT_METHOD
3242 1139 : if (!psOptions->bNoVShift)
3243 : {
3244 : // Can modify psWO->papszWarpOptions
3245 1138 : if (ApplyVerticalShift(hWrkSrcDS, psOptions, psWO.get()))
3246 : {
3247 19 : bUseApproxTransformer = false;
3248 : }
3249 : }
3250 : #endif
3251 :
3252 : /* --------------------------------------------------------------------
3253 : */
3254 : /* Warp the transformer with a linear approximator unless the */
3255 : /* acceptable error is zero. */
3256 : /* --------------------------------------------------------------------
3257 : */
3258 1139 : if (bUseApproxTransformer)
3259 : {
3260 1106 : hTransformArg.reset(GDALCreateApproxTransformer(
3261 : GDALGenImgProjTransform, hTransformArg.release(),
3262 : psOptions->dfErrorThreshold));
3263 1106 : pfnTransformer = GDALApproxTransform;
3264 1106 : GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
3265 : }
3266 :
3267 : /* --------------------------------------------------------------------
3268 : */
3269 : /* If we have a cutline, transform it into the source */
3270 : /* pixel/line coordinate system and insert into warp options. */
3271 : /* --------------------------------------------------------------------
3272 : */
3273 1139 : if (poCutline)
3274 : {
3275 : CPLErr eError;
3276 40 : eError = TransformCutlineToSource(
3277 : GDALDataset::FromHandle(hWrkSrcDS), poCutline.get(),
3278 40 : &(psWO->papszWarpOptions),
3279 40 : psOptions->aosTransformerOptions.List());
3280 40 : if (eError == CE_Failure)
3281 : {
3282 1 : GDALReleaseDataset(hWrkSrcDS);
3283 1 : GDALReleaseDataset(hDstDS);
3284 1 : return nullptr;
3285 : }
3286 : }
3287 :
3288 : /* --------------------------------------------------------------------
3289 : */
3290 : /* If we are producing VRT output, then just initialize it with */
3291 : /* the warp options and write out now rather than proceeding */
3292 : /* with the operations. */
3293 : /* --------------------------------------------------------------------
3294 : */
3295 1138 : if (bVRT)
3296 : {
3297 171 : GDALSetMetadataItem(hDstDS, "SrcOvrLevel",
3298 : CPLSPrintf("%d", psOptions->nOvLevel), nullptr);
3299 :
3300 : // In case of success, hDstDS has become the owner of hTransformArg
3301 : // so we need to release it
3302 171 : psWO->pfnTransformer = pfnTransformer;
3303 171 : psWO->pTransformerArg = hTransformArg.release();
3304 171 : CPLErr eErr = GDALInitializeWarpedVRT(hDstDS, psWO.get());
3305 171 : if (eErr != CE_None)
3306 : {
3307 : // In case of error, reacquire psWO->pTransformerArg
3308 1 : hTransformArg.reset(psWO->pTransformerArg);
3309 : }
3310 171 : GDALReleaseDataset(hWrkSrcDS);
3311 171 : if (eErr != CE_None)
3312 : {
3313 1 : GDALReleaseDataset(hDstDS);
3314 1 : return nullptr;
3315 : }
3316 :
3317 170 : if (!EQUAL(pszDest, ""))
3318 : {
3319 : const bool bWasFailureBefore =
3320 20 : (CPLGetLastErrorType() == CE_Failure);
3321 20 : GDALFlushCache(hDstDS);
3322 20 : if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
3323 : {
3324 1 : GDALReleaseDataset(hDstDS);
3325 1 : hDstDS = nullptr;
3326 : }
3327 : }
3328 :
3329 170 : if (hDstDS)
3330 169 : oProgress.Do(1);
3331 :
3332 170 : return hDstDS;
3333 : }
3334 :
3335 : /* --------------------------------------------------------------------
3336 : */
3337 : /* Initialize and execute the warp. */
3338 : /* --------------------------------------------------------------------
3339 : */
3340 1934 : GDALWarpOperation oWO;
3341 :
3342 1934 : if (oWO.Initialize(psWO.get(), pfnTransformer,
3343 1934 : std::move(hTransformArg)) == CE_None)
3344 : {
3345 : CPLErr eErr;
3346 964 : if (psOptions->bMulti)
3347 6 : eErr = oWO.ChunkAndWarpMulti(nWarpDstXOff, nWarpDstYOff,
3348 : nWarpDstXSize, nWarpDstYSize);
3349 : else
3350 958 : eErr = oWO.ChunkAndWarpImage(nWarpDstXOff, nWarpDstYOff,
3351 : nWarpDstXSize, nWarpDstYSize);
3352 964 : if (eErr != CE_None)
3353 5 : bHasGotErr = true;
3354 : }
3355 : else
3356 : {
3357 3 : bHasGotErr = true;
3358 : }
3359 :
3360 : /* --------------------------------------------------------------------
3361 : */
3362 : /* Cleanup */
3363 : /* --------------------------------------------------------------------
3364 : */
3365 967 : GDALReleaseDataset(hWrkSrcDS);
3366 : }
3367 :
3368 : /* -------------------------------------------------------------------- */
3369 : /* Final Cleanup. */
3370 : /* -------------------------------------------------------------------- */
3371 946 : const bool bWasFailureBefore = (CPLGetLastErrorType() == CE_Failure);
3372 946 : GDALFlushCache(hDstDS);
3373 946 : if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
3374 : {
3375 1 : bHasGotErr = true;
3376 : }
3377 :
3378 946 : if (bHasGotErr || bDropDstDSRef)
3379 100 : GDALReleaseDataset(hDstDS);
3380 :
3381 : #ifdef DEBUG
3382 946 : if (!bHasGotErr || bDropDstDSRef)
3383 : {
3384 937 : CPLAssert(poDstDS->GetRefCount() == nExpectedRefCountAtEnd);
3385 : }
3386 : #endif
3387 :
3388 946 : return bHasGotErr ? nullptr : hDstDS;
3389 : }
3390 :
3391 : /************************************************************************/
3392 : /* ValidateCutline() */
3393 : /* Same as OGR_G_IsValid() except that it processes polygon per polygon*/
3394 : /* without paying attention to MultiPolygon specific validity rules. */
3395 : /************************************************************************/
3396 :
3397 216 : static bool ValidateCutline(const OGRGeometry *poGeom, bool bVerbose)
3398 : {
3399 216 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3400 216 : if (eType == wkbMultiPolygon)
3401 : {
3402 151 : for (const auto *poSubGeom : *(poGeom->toMultiPolygon()))
3403 : {
3404 81 : if (!ValidateCutline(poSubGeom, bVerbose))
3405 5 : return false;
3406 : }
3407 : }
3408 141 : else if (eType == wkbPolygon)
3409 : {
3410 140 : std::string osReason;
3411 140 : if (OGRGeometryFactory::haveGEOS() && !poGeom->IsValid(&osReason))
3412 : {
3413 6 : if (!bVerbose)
3414 6 : return false;
3415 :
3416 2 : char *pszWKT = nullptr;
3417 2 : poGeom->exportToWkt(&pszWKT);
3418 2 : CPLDebug("GDALWARP", "WKT = \"%s\"", pszWKT ? pszWKT : "(null)");
3419 : const char *pszFile =
3420 2 : CPLGetConfigOption("GDALWARP_DUMP_WKT_TO_FILE", nullptr);
3421 2 : if (pszFile && pszWKT)
3422 : {
3423 : FILE *f =
3424 0 : EQUAL(pszFile, "stderr") ? stderr : fopen(pszFile, "wb");
3425 0 : if (f)
3426 : {
3427 0 : fprintf(f, "id,WKT\n");
3428 0 : fprintf(f, "1,\"%s\"\n", pszWKT);
3429 0 : if (!EQUAL(pszFile, "stderr"))
3430 0 : fclose(f);
3431 : }
3432 : }
3433 2 : CPLFree(pszWKT);
3434 :
3435 2 : if (CPLTestBool(
3436 : CPLGetConfigOption("GDALWARP_IGNORE_BAD_CUTLINE", "NO")))
3437 : {
3438 0 : CPLError(CE_Warning, CPLE_AppDefined,
3439 : "Cutline polygon is invalid: %s.", osReason.c_str());
3440 : }
3441 : else
3442 : {
3443 2 : CPLError(CE_Failure, CPLE_AppDefined,
3444 : "Cutline polygon is invalid: %s.", osReason.c_str());
3445 2 : return false;
3446 : }
3447 : }
3448 : }
3449 : else
3450 : {
3451 1 : if (bVerbose)
3452 : {
3453 1 : CPLError(CE_Failure, CPLE_AppDefined,
3454 : "Cutline not of polygon type.");
3455 : }
3456 1 : return false;
3457 : }
3458 :
3459 204 : return true;
3460 : }
3461 :
3462 : /************************************************************************/
3463 : /* LoadCutline() */
3464 : /* */
3465 : /* Load blend cutline from OGR datasource. */
3466 : /************************************************************************/
3467 :
3468 52 : static CPLErr LoadCutline(const std::string &osCutlineDSNameOrWKT,
3469 : const std::string &osSRS, const std::string &osCLayer,
3470 : const std::string &osCWHERE,
3471 : const std::string &osCSQL, OGRGeometryH *phCutlineRet)
3472 :
3473 : {
3474 52 : if (STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "POLYGON(") ||
3475 52 : STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "POLYGON (") ||
3476 148 : STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "MULTIPOLYGON(") ||
3477 44 : STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "MULTIPOLYGON ("))
3478 : {
3479 16 : OGRSpatialReferenceRefCountedPtr poSRS;
3480 8 : if (!osSRS.empty())
3481 : {
3482 2 : poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
3483 2 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3484 2 : poSRS->SetFromUserInput(osSRS.c_str());
3485 : }
3486 :
3487 8 : auto [poGeom, _] = OGRGeometryFactory::createFromWkt(
3488 8 : osCutlineDSNameOrWKT.c_str(), poSRS.get());
3489 8 : *phCutlineRet = OGRGeometry::ToHandle(poGeom.release());
3490 8 : return *phCutlineRet ? CE_None : CE_Failure;
3491 : }
3492 :
3493 : /* -------------------------------------------------------------------- */
3494 : /* Open source vector dataset. */
3495 : /* -------------------------------------------------------------------- */
3496 : auto poDS = std::unique_ptr<GDALDataset>(
3497 88 : GDALDataset::Open(osCutlineDSNameOrWKT.c_str(), GDAL_OF_VECTOR));
3498 44 : if (poDS == nullptr)
3499 : {
3500 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s.",
3501 : osCutlineDSNameOrWKT.c_str());
3502 1 : return CE_Failure;
3503 : }
3504 :
3505 : /* -------------------------------------------------------------------- */
3506 : /* Get the source layer */
3507 : /* -------------------------------------------------------------------- */
3508 43 : OGRLayer *poLayer = nullptr;
3509 :
3510 43 : if (!osCSQL.empty())
3511 2 : poLayer = poDS->ExecuteSQL(osCSQL.c_str(), nullptr, nullptr);
3512 41 : else if (!osCLayer.empty())
3513 15 : poLayer = poDS->GetLayerByName(osCLayer.c_str());
3514 : else
3515 26 : poLayer = poDS->GetLayer(0);
3516 :
3517 43 : if (poLayer == nullptr)
3518 : {
3519 1 : CPLError(CE_Failure, CPLE_AppDefined,
3520 : "Failed to identify source layer from datasource.");
3521 1 : return CE_Failure;
3522 : }
3523 :
3524 : /* -------------------------------------------------------------------- */
3525 : /* Apply WHERE clause if there is one. */
3526 : /* -------------------------------------------------------------------- */
3527 42 : if (!osCWHERE.empty())
3528 1 : poLayer->SetAttributeFilter(osCWHERE.c_str());
3529 :
3530 : /* -------------------------------------------------------------------- */
3531 : /* Collect the geometries from this layer, and build list of */
3532 : /* burn values. */
3533 : /* -------------------------------------------------------------------- */
3534 84 : auto poMultiPolygon = std::make_unique<OGRMultiPolygon>();
3535 :
3536 81 : for (auto &&poFeature : poLayer)
3537 : {
3538 42 : auto poGeom = std::unique_ptr<OGRGeometry>(poFeature->StealGeometry());
3539 42 : if (poGeom == nullptr)
3540 : {
3541 1 : CPLError(CE_Failure, CPLE_AppDefined,
3542 : "Cutline feature without a geometry.");
3543 1 : goto error;
3544 : }
3545 :
3546 41 : if (!ValidateCutline(poGeom.get(), true))
3547 : {
3548 2 : goto error;
3549 : }
3550 :
3551 39 : OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3552 :
3553 39 : if (eType == wkbPolygon)
3554 36 : poMultiPolygon->addGeometry(std::move(poGeom));
3555 3 : else if (eType == wkbMultiPolygon)
3556 : {
3557 7 : for (const auto *poSubGeom : poGeom->toMultiPolygon())
3558 : {
3559 4 : poMultiPolygon->addGeometry(poSubGeom);
3560 : }
3561 : }
3562 : }
3563 :
3564 39 : if (poMultiPolygon->IsEmpty())
3565 : {
3566 1 : CPLError(CE_Failure, CPLE_AppDefined,
3567 : "Did not get any cutline features.");
3568 1 : goto error;
3569 : }
3570 :
3571 : /* -------------------------------------------------------------------- */
3572 : /* Ensure the coordinate system gets set on the geometry. */
3573 : /* -------------------------------------------------------------------- */
3574 38 : if (!osSRS.empty())
3575 : {
3576 2 : auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
3577 1 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3578 1 : poSRS->SetFromUserInput(osSRS.c_str());
3579 1 : poMultiPolygon->assignSpatialReference(poSRS.get());
3580 : }
3581 : else
3582 : {
3583 37 : poMultiPolygon->assignSpatialReference(poLayer->GetSpatialRef());
3584 : }
3585 :
3586 38 : *phCutlineRet = OGRGeometry::ToHandle(poMultiPolygon.release());
3587 :
3588 : /* -------------------------------------------------------------------- */
3589 : /* Cleanup */
3590 : /* -------------------------------------------------------------------- */
3591 38 : if (!osCSQL.empty())
3592 1 : poDS->ReleaseResultSet(poLayer);
3593 :
3594 38 : return CE_None;
3595 :
3596 4 : error:
3597 4 : if (!osCSQL.empty())
3598 1 : poDS->ReleaseResultSet(poLayer);
3599 :
3600 4 : return CE_Failure;
3601 : }
3602 :
3603 : /************************************************************************/
3604 : /* GDALWarpCreateOutput() */
3605 : /* */
3606 : /* Create the output file based on various command line options, */
3607 : /* and the input file. */
3608 : /* If there's just one source file, then hUniqueTransformArg will */
3609 : /* be set in order them to be reused by main function. This saves */
3610 : /* transform recomputation, which can be expensive in the -tps case*/
3611 : /************************************************************************/
3612 :
3613 1054 : static GDALDatasetH GDALWarpCreateOutput(
3614 : int nSrcCount, GDALDatasetH *pahSrcDS, const char *pszFilename,
3615 : const char *pszFormat, char **papszTO, CSLConstList papszCreateOptions,
3616 : GDALDataType eDT, GDALTransformerArgUniquePtr &hUniqueTransformArg,
3617 : bool bSetColorInterpretation, GDALWarpAppOptions *psOptions,
3618 : bool bUpdateTransformerWithDestGT)
3619 :
3620 : {
3621 : GDALDriverH hDriver;
3622 : GDALDatasetH hDstDS;
3623 1054 : GDALRasterAttributeTableH hRAT = nullptr;
3624 1054 : double dfWrkMinX = 0, dfWrkMaxX = 0, dfWrkMinY = 0, dfWrkMaxY = 0;
3625 1054 : double dfWrkResX = 0, dfWrkResY = 0;
3626 1054 : int nDstBandCount = 0;
3627 2108 : std::vector<GDALColorInterp> apeColorInterpretations;
3628 1054 : bool bVRT = false;
3629 :
3630 1054 : if (EQUAL(pszFormat, "VRT"))
3631 175 : bVRT = true;
3632 :
3633 : // Special case for geographic to Mercator (typically EPSG:4326 to EPSG:3857)
3634 : // where latitudes close to 90 go to infinity
3635 : // We clamp latitudes between ~ -85 and ~ 85 degrees.
3636 1054 : const char *pszDstSRS = CSLFetchNameValue(papszTO, "DST_SRS");
3637 1054 : if (nSrcCount == 1 && pszDstSRS && psOptions->dfMinX == 0.0 &&
3638 164 : psOptions->dfMinY == 0.0 && psOptions->dfMaxX == 0.0 &&
3639 164 : psOptions->dfMaxY == 0.0)
3640 : {
3641 164 : auto hSrcDS = pahSrcDS[0];
3642 328 : const auto osSrcSRS = GetSrcDSProjection(pahSrcDS[0], papszTO);
3643 328 : OGRSpatialReference oSrcSRS;
3644 164 : oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3645 164 : oSrcSRS.SetFromUserInput(osSrcSRS.c_str());
3646 328 : OGRSpatialReference oDstSRS;
3647 164 : oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3648 164 : oDstSRS.SetFromUserInput(pszDstSRS);
3649 164 : const char *pszProjection = oDstSRS.GetAttrValue("PROJECTION");
3650 164 : const char *pszMethod = FetchSrcMethod(papszTO);
3651 164 : double adfSrcGT[6] = {0};
3652 : // This MAX_LAT values is equivalent to the semi_major_axis * PI
3653 : // easting/northing value only for EPSG:3857, but it is also quite
3654 : // reasonable for other Mercator projections
3655 164 : constexpr double MAX_LAT = 85.0511287798066;
3656 164 : constexpr double EPS = 1e-3;
3657 5 : const auto GetMinLon = [&adfSrcGT]() { return adfSrcGT[0]; };
3658 5 : const auto GetMaxLon = [&adfSrcGT, hSrcDS]()
3659 5 : { return adfSrcGT[0] + adfSrcGT[1] * GDALGetRasterXSize(hSrcDS); };
3660 5 : const auto GetMinLat = [&adfSrcGT, hSrcDS]()
3661 5 : { return adfSrcGT[3] + adfSrcGT[5] * GDALGetRasterYSize(hSrcDS); };
3662 6 : const auto GetMaxLat = [&adfSrcGT]() { return adfSrcGT[3]; };
3663 230 : if (oSrcSRS.IsGeographic() && !oSrcSRS.IsDerivedGeographic() &&
3664 39 : pszProjection && EQUAL(pszProjection, SRS_PT_MERCATOR_1SP) &&
3665 3 : oDstSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0) == 0 &&
3666 0 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
3667 3 : CSLFetchNameValue(papszTO, "COORDINATE_OPERATION") == nullptr &&
3668 3 : CSLFetchNameValue(papszTO, "SRC_METHOD") == nullptr &&
3669 3 : CSLFetchNameValue(papszTO, "DST_METHOD") == nullptr &&
3670 3 : GDALGetGeoTransform(hSrcDS, adfSrcGT) == CE_None &&
3671 6 : adfSrcGT[2] == 0 && adfSrcGT[4] == 0 && adfSrcGT[5] < 0 &&
3672 9 : GetMinLon() >= -180 - EPS && GetMaxLon() <= 180 + EPS &&
3673 6 : ((GetMaxLat() > MAX_LAT && GetMinLat() < MAX_LAT) ||
3674 2 : (GetMaxLat() > -MAX_LAT && GetMinLat() < -MAX_LAT)) &&
3675 232 : GDALGetMetadata(hSrcDS, "GEOLOC_ARRAY") == nullptr &&
3676 2 : GDALGetMetadata(hSrcDS, "RPC") == nullptr)
3677 : {
3678 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
3679 4 : OGRCreateCoordinateTransformation(&oSrcSRS, &oDstSRS));
3680 2 : if (poCT)
3681 : {
3682 2 : double xLL = std::max(GetMinLon(), -180.0);
3683 2 : double yLL = std::max(GetMinLat(), -MAX_LAT);
3684 2 : double xUR = std::min(GetMaxLon(), 180.0);
3685 2 : double yUR = std::min(GetMaxLat(), MAX_LAT);
3686 4 : if (poCT->Transform(1, &xLL, &yLL) &&
3687 2 : poCT->Transform(1, &xUR, &yUR))
3688 : {
3689 2 : psOptions->dfMinX = xLL;
3690 2 : psOptions->dfMinY = yLL;
3691 2 : psOptions->dfMaxX = xUR;
3692 2 : psOptions->dfMaxY = yUR;
3693 2 : CPLError(CE_Warning, CPLE_AppDefined,
3694 : "Clamping output bounds to (%f,%f) -> (%f, %f)",
3695 : psOptions->dfMinX, psOptions->dfMinY,
3696 : psOptions->dfMaxX, psOptions->dfMaxY);
3697 : }
3698 : }
3699 : }
3700 : }
3701 :
3702 : /* If (-ts and -te) or (-tr and -te) are specified, we don't need to compute
3703 : * the suggested output extent */
3704 1054 : const bool bNeedsSuggestedWarpOutput =
3705 2108 : !(((psOptions->nForcePixels != 0 && psOptions->nForceLines != 0) ||
3706 886 : (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)) &&
3707 254 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
3708 132 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0));
3709 :
3710 : // If -te is specified, not not -tr and -ts
3711 1054 : const bool bKnownTargetExtentButNotResolution =
3712 860 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
3713 855 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0) &&
3714 201 : psOptions->nForcePixels == 0 && psOptions->nForceLines == 0 &&
3715 2108 : psOptions->dfXRes == 0 && psOptions->dfYRes == 0;
3716 :
3717 : /* -------------------------------------------------------------------- */
3718 : /* Find the output driver. */
3719 : /* -------------------------------------------------------------------- */
3720 1054 : hDriver = GDALGetDriverByName(pszFormat);
3721 2108 : if (hDriver == nullptr ||
3722 1054 : (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) == nullptr &&
3723 0 : GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) ==
3724 : nullptr))
3725 : {
3726 : auto poMissingDriver =
3727 0 : GetGDALDriverManager()->GetHiddenDriverByName(pszFormat);
3728 0 : if (poMissingDriver)
3729 : {
3730 : const std::string msg =
3731 0 : GDALGetMessageAboutMissingPluginDriver(poMissingDriver);
3732 0 : printf("Output driver `%s' not found but is known. However plugin "
3733 : "%s\n",
3734 : pszFormat, msg.c_str());
3735 0 : return nullptr;
3736 : }
3737 :
3738 0 : printf("Output driver `%s' not recognised or does not support\n",
3739 : pszFormat);
3740 0 : printf("direct output file creation or CreateCopy. "
3741 : "The following format drivers are eligible for warp output:\n");
3742 :
3743 0 : for (int iDr = 0; iDr < GDALGetDriverCount(); iDr++)
3744 : {
3745 0 : hDriver = GDALGetDriver(iDr);
3746 :
3747 0 : if (GDALGetMetadataItem(hDriver, GDAL_DCAP_RASTER, nullptr) !=
3748 0 : nullptr &&
3749 0 : (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) !=
3750 0 : nullptr ||
3751 0 : GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
3752 : nullptr))
3753 : {
3754 0 : printf(" %s: %s\n", GDALGetDriverShortName(hDriver),
3755 : GDALGetDriverLongName(hDriver));
3756 : }
3757 : }
3758 0 : printf("\n");
3759 0 : return nullptr;
3760 : }
3761 :
3762 : /* -------------------------------------------------------------------- */
3763 : /* For virtual output files, we have to set a special subclass */
3764 : /* of dataset to create. */
3765 : /* -------------------------------------------------------------------- */
3766 2108 : CPLStringList aosCreateOptions(CSLDuplicate(papszCreateOptions));
3767 1054 : if (bVRT)
3768 175 : aosCreateOptions.SetNameValue("SUBCLASS", "VRTWarpedDataset");
3769 :
3770 : /* -------------------------------------------------------------------- */
3771 : /* Loop over all input files to collect extents. */
3772 : /* -------------------------------------------------------------------- */
3773 2108 : CPLString osThisTargetSRS;
3774 : {
3775 1054 : const char *pszThisTargetSRS = CSLFetchNameValue(papszTO, "DST_SRS");
3776 1054 : if (pszThisTargetSRS != nullptr)
3777 278 : osThisTargetSRS = pszThisTargetSRS;
3778 : }
3779 :
3780 2108 : CPLStringList aoTOList(papszTO, FALSE);
3781 :
3782 1054 : double dfResFromSourceAndTargetExtent =
3783 : std::numeric_limits<double>::infinity();
3784 :
3785 : /* -------------------------------------------------------------------- */
3786 : /* Establish list of files of output dataset if it already exists. */
3787 : /* -------------------------------------------------------------------- */
3788 2108 : std::set<std::string> oSetExistingDestFiles;
3789 : {
3790 1054 : CPLPushErrorHandler(CPLQuietErrorHandler);
3791 1054 : const char *const apszAllowedDrivers[] = {pszFormat, nullptr};
3792 : auto poExistingOutputDS = std::unique_ptr<GDALDataset>(
3793 2108 : GDALDataset::Open(pszFilename, GDAL_OF_RASTER, apszAllowedDrivers));
3794 1054 : if (poExistingOutputDS)
3795 : {
3796 74 : for (const char *pszFilenameInList :
3797 70 : CPLStringList(poExistingOutputDS->GetFileList()))
3798 : {
3799 : oSetExistingDestFiles.insert(
3800 37 : CPLString(pszFilenameInList).replaceAll('\\', '/'));
3801 : }
3802 : }
3803 1054 : CPLPopErrorHandler();
3804 : }
3805 2108 : std::set<std::string> oSetExistingDestFilesFoundInSource;
3806 1054 : std::unique_ptr<GDALColorTable> poCT;
3807 :
3808 2122 : for (int iSrc = 0; iSrc < nSrcCount; iSrc++)
3809 : {
3810 : /* --------------------------------------------------------------------
3811 : */
3812 : /* Check that there's at least one raster band */
3813 : /* --------------------------------------------------------------------
3814 : */
3815 1078 : GDALDatasetH hSrcDS = pahSrcDS[iSrc];
3816 1078 : if (GDALGetRasterCount(hSrcDS) == 0)
3817 : {
3818 1 : CPLError(CE_Failure, CPLE_AppDefined,
3819 : "Input file %s has no raster bands.",
3820 : GDALGetDescription(hSrcDS));
3821 10 : return nullptr;
3822 : }
3823 :
3824 : // Examine desired overview level and retrieve the corresponding dataset
3825 : // if it exists.
3826 0 : std::unique_ptr<GDALDataset> oDstDSOverview;
3827 1077 : if (psOptions->nOvLevel >= 0)
3828 : {
3829 5 : oDstDSOverview.reset(GDALCreateOverviewDataset(
3830 : GDALDataset::FromHandle(hSrcDS), psOptions->nOvLevel,
3831 : /* bThisLevelOnly = */ true));
3832 5 : if (oDstDSOverview)
3833 4 : hSrcDS = oDstDSOverview.get();
3834 : }
3835 :
3836 : /* --------------------------------------------------------------------
3837 : */
3838 : /* Check if the source dataset shares some files with the dest
3839 : * one.*/
3840 : /* --------------------------------------------------------------------
3841 : */
3842 1077 : if (!oSetExistingDestFiles.empty())
3843 : {
3844 : // We need to reopen in a temporary dataset for the particular
3845 : // case of overwritten a .tif.ovr file from a .tif
3846 : // If we probe the file list of the .tif, it will then open the
3847 : // .tif.ovr !
3848 36 : auto poSrcDS = GDALDataset::FromHandle(hSrcDS);
3849 36 : const char *const apszAllowedDrivers[] = {
3850 36 : poSrcDS->GetDriver() ? poSrcDS->GetDriver()->GetDescription()
3851 : : nullptr,
3852 36 : nullptr};
3853 : auto poSrcDSTmp = std::unique_ptr<GDALDataset>(GDALDataset::Open(
3854 72 : poSrcDS->GetDescription(), GDAL_OF_RASTER, apszAllowedDrivers));
3855 36 : if (poSrcDSTmp)
3856 : {
3857 38 : for (const char *pszFilenameInList :
3858 74 : CPLStringList(poSrcDSTmp->GetFileList()))
3859 : {
3860 : std::string osFilename =
3861 76 : CPLString(pszFilenameInList).replaceAll('\\', '/');
3862 38 : if (oSetExistingDestFiles.find(osFilename) !=
3863 76 : oSetExistingDestFiles.end())
3864 : {
3865 : oSetExistingDestFilesFoundInSource.insert(
3866 5 : std::move(osFilename));
3867 : }
3868 : }
3869 : }
3870 : }
3871 :
3872 1077 : if (eDT == GDT_Unknown)
3873 1016 : eDT = GDALGetRasterDataType(GDALGetRasterBand(hSrcDS, 1));
3874 :
3875 : /* --------------------------------------------------------------------
3876 : */
3877 : /* If we are processing the first file, and it has a raster */
3878 : /* attribute table, then we will copy it to the destination file.
3879 : */
3880 : /* --------------------------------------------------------------------
3881 : */
3882 1077 : if (iSrc == 0)
3883 : {
3884 1051 : hRAT = GDALGetDefaultRAT(GDALGetRasterBand(hSrcDS, 1));
3885 1051 : if (hRAT != nullptr)
3886 : {
3887 0 : if (psOptions->eResampleAlg != GRA_NearestNeighbour &&
3888 0 : psOptions->eResampleAlg != GRA_Mode &&
3889 0 : GDALRATGetTableType(hRAT) == GRTT_THEMATIC)
3890 : {
3891 0 : if (!psOptions->bQuiet)
3892 : {
3893 0 : CPLError(CE_Warning, CPLE_AppDefined,
3894 : "Warning: Input file %s has a thematic RAT, "
3895 : "which will likely lead "
3896 : "to bad results when using a resampling "
3897 : "method other than nearest neighbour "
3898 : "or mode so we are discarding it.\n",
3899 : GDALGetDescription(hSrcDS));
3900 : }
3901 0 : hRAT = nullptr;
3902 : }
3903 : else
3904 : {
3905 0 : if (!psOptions->bQuiet)
3906 0 : printf("Copying raster attribute table from %s to new "
3907 : "file.\n",
3908 : GDALGetDescription(hSrcDS));
3909 : }
3910 : }
3911 : }
3912 :
3913 : /* --------------------------------------------------------------------
3914 : */
3915 : /* If we are processing the first file, and it has a color */
3916 : /* table, then we will copy it to the destination file. */
3917 : /* --------------------------------------------------------------------
3918 : */
3919 1077 : if (iSrc == 0)
3920 : {
3921 1051 : auto hCT = GDALGetRasterColorTable(GDALGetRasterBand(hSrcDS, 1));
3922 1051 : if (hCT != nullptr)
3923 : {
3924 9 : poCT.reset(
3925 : GDALColorTable::FromHandle(GDALCloneColorTable(hCT)));
3926 9 : if (!psOptions->bQuiet)
3927 0 : printf("Copying color table from %s to new file.\n",
3928 : GDALGetDescription(hSrcDS));
3929 : }
3930 :
3931 1051 : if (psOptions->anDstBands.empty())
3932 : {
3933 1025 : nDstBandCount = GDALGetRasterCount(hSrcDS);
3934 2446 : for (int iBand = 0; iBand < nDstBandCount; iBand++)
3935 : {
3936 1421 : if (psOptions->anDstBands.empty())
3937 : {
3938 : GDALColorInterp eInterp =
3939 1421 : GDALGetRasterColorInterpretation(
3940 1421 : GDALGetRasterBand(hSrcDS, iBand + 1));
3941 1421 : apeColorInterpretations.push_back(eInterp);
3942 : }
3943 : }
3944 :
3945 : // Do we want to generate an alpha band in the output file?
3946 1025 : if (psOptions->bEnableSrcAlpha)
3947 31 : nDstBandCount--;
3948 :
3949 1025 : if (psOptions->bEnableDstAlpha)
3950 90 : nDstBandCount++;
3951 : }
3952 : else
3953 : {
3954 68 : for (int nSrcBand : psOptions->anSrcBands)
3955 : {
3956 42 : auto hBand = GDALGetRasterBand(hSrcDS, nSrcBand);
3957 : GDALColorInterp eInterp =
3958 42 : hBand ? GDALGetRasterColorInterpretation(hBand)
3959 42 : : GCI_Undefined;
3960 42 : apeColorInterpretations.push_back(eInterp);
3961 : }
3962 26 : nDstBandCount = static_cast<int>(psOptions->anDstBands.size());
3963 26 : if (psOptions->bEnableDstAlpha)
3964 : {
3965 9 : nDstBandCount++;
3966 9 : apeColorInterpretations.push_back(GCI_AlphaBand);
3967 : }
3968 17 : else if (GDALGetRasterCount(hSrcDS) &&
3969 17 : GDALGetRasterColorInterpretation(GDALGetRasterBand(
3970 : hSrcDS, GDALGetRasterCount(hSrcDS))) ==
3971 34 : GCI_AlphaBand &&
3972 1 : !psOptions->bDisableSrcAlpha)
3973 : {
3974 0 : nDstBandCount++;
3975 0 : apeColorInterpretations.push_back(GCI_AlphaBand);
3976 : }
3977 : }
3978 : }
3979 :
3980 : /* --------------------------------------------------------------------
3981 : */
3982 : /* If we are processing the first file, get the source srs from */
3983 : /* dataset, if not set already. */
3984 : /* --------------------------------------------------------------------
3985 : */
3986 1077 : const auto osThisSourceSRS = GetSrcDSProjection(hSrcDS, papszTO);
3987 1077 : if (iSrc == 0 && osThisTargetSRS.empty())
3988 : {
3989 773 : if (!osThisSourceSRS.empty())
3990 : {
3991 571 : osThisTargetSRS = osThisSourceSRS;
3992 571 : aoTOList.SetNameValue("DST_SRS", osThisSourceSRS);
3993 : }
3994 : }
3995 :
3996 : /* --------------------------------------------------------------------
3997 : */
3998 : /* Create a transformation object from the source to */
3999 : /* destination coordinate system. */
4000 : /* --------------------------------------------------------------------
4001 : */
4002 0 : GDALTransformerArgUniquePtr hTransformArg;
4003 1077 : if (hUniqueTransformArg)
4004 2 : hTransformArg = std::move(hUniqueTransformArg);
4005 : else
4006 : {
4007 1075 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
4008 1075 : hSrcDS, nullptr, aoTOList.List()));
4009 1075 : if (hTransformArg == nullptr)
4010 : {
4011 9 : return nullptr;
4012 : }
4013 : }
4014 :
4015 : GDALTransformerInfo *psInfo =
4016 1068 : static_cast<GDALTransformerInfo *>(hTransformArg.get());
4017 :
4018 : /* --------------------------------------------------------------------
4019 : */
4020 : /* Get approximate output resolution */
4021 : /* --------------------------------------------------------------------
4022 : */
4023 :
4024 1068 : if (bKnownTargetExtentButNotResolution)
4025 : {
4026 : // Sample points along a grid in target CRS
4027 84 : constexpr int nPointsX = 10;
4028 84 : constexpr int nPointsY = 10;
4029 84 : constexpr int nPoints = 3 * nPointsX * nPointsY;
4030 168 : std::vector<double> padfX;
4031 168 : std::vector<double> padfY;
4032 168 : std::vector<double> padfZ(nPoints);
4033 168 : std::vector<int> pabSuccess(nPoints);
4034 : const double dfEps =
4035 168 : std::min(psOptions->dfMaxX - psOptions->dfMinX,
4036 84 : std::abs(psOptions->dfMaxY - psOptions->dfMinY)) /
4037 84 : 1000;
4038 924 : for (int iY = 0; iY < nPointsY; iY++)
4039 : {
4040 9240 : for (int iX = 0; iX < nPointsX; iX++)
4041 : {
4042 8400 : const double dfX =
4043 8400 : psOptions->dfMinX +
4044 8400 : static_cast<double>(iX) *
4045 8400 : (psOptions->dfMaxX - psOptions->dfMinX) /
4046 : (nPointsX - 1);
4047 8400 : const double dfY =
4048 8400 : psOptions->dfMinY +
4049 8400 : static_cast<double>(iY) *
4050 8400 : (psOptions->dfMaxY - psOptions->dfMinY) /
4051 : (nPointsY - 1);
4052 :
4053 : // Reproject each destination sample point and its
4054 : // neighbours at (x+1,y) and (x,y+1), so as to get the local
4055 : // scale.
4056 8400 : padfX.push_back(dfX);
4057 8400 : padfY.push_back(dfY);
4058 :
4059 15960 : padfX.push_back((iX == nPointsX - 1) ? dfX - dfEps
4060 7560 : : dfX + dfEps);
4061 8400 : padfY.push_back(dfY);
4062 :
4063 8400 : padfX.push_back(dfX);
4064 15960 : padfY.push_back((iY == nPointsY - 1) ? dfY - dfEps
4065 7560 : : dfY + dfEps);
4066 : }
4067 : }
4068 :
4069 84 : bool transformedToSrcCRS{false};
4070 :
4071 : GDALGenImgProjTransformInfo *psTransformInfo{
4072 : static_cast<GDALGenImgProjTransformInfo *>(
4073 84 : hTransformArg.get())};
4074 :
4075 : // If a transformer is available, use an extent that covers the
4076 : // target extent instead of the real source image extent, but also
4077 : // check for target extent compatibility with source CRS extent
4078 84 : if (psTransformInfo && psTransformInfo->pReprojectArg &&
4079 64 : psTransformInfo->sSrcParams.pTransformer == nullptr)
4080 : {
4081 64 : const GDALReprojectionTransformInfo *psRTI =
4082 : static_cast<const GDALReprojectionTransformInfo *>(
4083 : psTransformInfo->pReprojectArg);
4084 64 : if (psRTI && psRTI->poReverseTransform)
4085 : {
4086 :
4087 : // Compute new geotransform from transformed target extent
4088 : double adfGeoTransform[6];
4089 64 : if (GDALGetGeoTransform(hSrcDS, adfGeoTransform) ==
4090 64 : CE_None &&
4091 64 : adfGeoTransform[2] == 0 && adfGeoTransform[4] == 0)
4092 : {
4093 :
4094 : // Transform target extent to source CRS
4095 64 : double dfMinX = psOptions->dfMinX;
4096 64 : double dfMinY = psOptions->dfMinY;
4097 :
4098 : // Need this to check if the target extent is compatible with the source extent
4099 64 : double dfMaxX = psOptions->dfMaxX;
4100 64 : double dfMaxY = psOptions->dfMaxY;
4101 :
4102 : // Clone of psRTI->poReverseTransform with CHECK_WITH_INVERT_PROJ set to TRUE
4103 : // to detect out of source CRS bounds destination extent and fall back to original
4104 : // algorithm if needed
4105 : CPLConfigOptionSetter oSetter("CHECK_WITH_INVERT_PROJ",
4106 128 : "TRUE", false);
4107 128 : OGRCoordinateTransformationOptions options;
4108 : auto poReverseTransform =
4109 : std::unique_ptr<OGRCoordinateTransformation>(
4110 : OGRCreateCoordinateTransformation(
4111 64 : psRTI->poReverseTransform->GetSourceCS(),
4112 64 : psRTI->poReverseTransform->GetTargetCS(),
4113 128 : options));
4114 :
4115 64 : if (poReverseTransform)
4116 : {
4117 :
4118 128 : poReverseTransform->Transform(
4119 64 : 1, &dfMinX, &dfMinY, nullptr, &pabSuccess[0]);
4120 :
4121 64 : if (pabSuccess[0])
4122 : {
4123 60 : adfGeoTransform[0] = dfMinX;
4124 60 : adfGeoTransform[3] = dfMinY;
4125 :
4126 120 : poReverseTransform->Transform(1, &dfMaxX,
4127 : &dfMaxY, nullptr,
4128 60 : &pabSuccess[0]);
4129 :
4130 60 : if (pabSuccess[0])
4131 : {
4132 :
4133 : // Reproject to source image CRS
4134 59 : psRTI->poReverseTransform->Transform(
4135 59 : nPoints, &padfX[0], &padfY[0],
4136 59 : &padfZ[0], &pabSuccess[0]);
4137 :
4138 : // Transform back to source image coordinate space using geotransform
4139 17759 : for (size_t i = 0; i < padfX.size(); i++)
4140 : {
4141 35400 : padfX[i] =
4142 17700 : (padfX[i] - adfGeoTransform[0]) /
4143 17700 : adfGeoTransform[1];
4144 17700 : padfY[i] = std::abs(
4145 17700 : (padfY[i] - adfGeoTransform[3]) /
4146 17700 : adfGeoTransform[5]);
4147 : }
4148 :
4149 59 : transformedToSrcCRS = true;
4150 : }
4151 : }
4152 : }
4153 : }
4154 : }
4155 : }
4156 :
4157 84 : if (!transformedToSrcCRS)
4158 : {
4159 : // Transform to source image coordinate space
4160 25 : psInfo->pfnTransform(hTransformArg.get(), TRUE, nPoints,
4161 25 : &padfX[0], &padfY[0], &padfZ[0],
4162 25 : &pabSuccess[0]);
4163 : }
4164 :
4165 : // Compute the resolution at sampling points
4166 168 : std::vector<std::pair<double, double>> aoResPairs;
4167 :
4168 15062 : const auto Distance = [](double x, double y)
4169 15062 : { return sqrt(x * x + y * y); };
4170 :
4171 84 : const int nSrcXSize = GDALGetRasterXSize(hSrcDS);
4172 84 : const int nSrcYSize = GDALGetRasterYSize(hSrcDS);
4173 :
4174 8484 : for (int i = 0; i < nPoints; i += 3)
4175 : {
4176 16800 : if (pabSuccess[i] && pabSuccess[i + 1] && pabSuccess[i + 2] &&
4177 18925 : padfX[i] >= 0 && padfY[i] >= 0 &&
4178 2125 : (transformedToSrcCRS ||
4179 2125 : (padfX[i] <= nSrcXSize && padfY[i] <= nSrcYSize)))
4180 : {
4181 : const double dfRes1 =
4182 15062 : std::abs(dfEps) / Distance(padfX[i + 1] - padfX[i],
4183 7531 : padfY[i + 1] - padfY[i]);
4184 : const double dfRes2 =
4185 15062 : std::abs(dfEps) / Distance(padfX[i + 2] - padfX[i],
4186 7531 : padfY[i + 2] - padfY[i]);
4187 7531 : if (std::isfinite(dfRes1) && std::isfinite(dfRes2))
4188 : {
4189 7511 : aoResPairs.push_back(
4190 15022 : std::pair<double, double>(dfRes1, dfRes2));
4191 : }
4192 : }
4193 : }
4194 :
4195 : // Find the minimum resolution that is at least 10 times greater
4196 : // than the median, to remove outliers.
4197 : // Start first by doing that on dfRes1, then dfRes2 and then their
4198 : // average.
4199 84 : std::sort(aoResPairs.begin(), aoResPairs.end(),
4200 38467 : [](const std::pair<double, double> &oPair1,
4201 : const std::pair<double, double> &oPair2)
4202 38467 : { return oPair1.first < oPair2.first; });
4203 :
4204 84 : if (!aoResPairs.empty())
4205 : {
4206 168 : std::vector<std::pair<double, double>> aoResPairsNew;
4207 : const double dfMedian1 =
4208 84 : aoResPairs[aoResPairs.size() / 2].first;
4209 7595 : for (const auto &oPair : aoResPairs)
4210 : {
4211 7511 : if (oPair.first > dfMedian1 / 10)
4212 : {
4213 7481 : aoResPairsNew.push_back(oPair);
4214 : }
4215 : }
4216 :
4217 84 : aoResPairs = std::move(aoResPairsNew);
4218 84 : std::sort(aoResPairs.begin(), aoResPairs.end(),
4219 40580 : [](const std::pair<double, double> &oPair1,
4220 : const std::pair<double, double> &oPair2)
4221 40580 : { return oPair1.second < oPair2.second; });
4222 84 : if (!aoResPairs.empty())
4223 : {
4224 168 : std::vector<double> adfRes;
4225 : const double dfMedian2 =
4226 84 : aoResPairs[aoResPairs.size() / 2].second;
4227 7565 : for (const auto &oPair : aoResPairs)
4228 : {
4229 7481 : if (oPair.second > dfMedian2 / 10)
4230 : {
4231 7471 : adfRes.push_back((oPair.first + oPair.second) / 2);
4232 : }
4233 : }
4234 :
4235 84 : std::sort(adfRes.begin(), adfRes.end());
4236 84 : if (!adfRes.empty())
4237 : {
4238 84 : const double dfMedian = adfRes[adfRes.size() / 2];
4239 84 : for (const double dfRes : adfRes)
4240 : {
4241 84 : if (dfRes > dfMedian / 10)
4242 : {
4243 84 : dfResFromSourceAndTargetExtent = std::min(
4244 84 : dfResFromSourceAndTargetExtent, dfRes);
4245 84 : break;
4246 : }
4247 : }
4248 : }
4249 : }
4250 : }
4251 : }
4252 :
4253 : /* --------------------------------------------------------------------
4254 : */
4255 : /* Get approximate output definition. */
4256 : /* --------------------------------------------------------------------
4257 : */
4258 : double adfThisGeoTransform[6];
4259 : double adfExtent[4];
4260 1068 : if (bNeedsSuggestedWarpOutput)
4261 : {
4262 : int nThisPixels, nThisLines;
4263 :
4264 : // For sum, round-up dimension, to be sure that the output extent
4265 : // includes all source pixels, to have the sum preserving property.
4266 1888 : int nOptions = (psOptions->eResampleAlg == GRA_Sum)
4267 944 : ? GDAL_SWO_ROUND_UP_SIZE
4268 : : 0;
4269 944 : if (psOptions->bSquarePixels)
4270 : {
4271 1 : nOptions |= GDAL_SWO_FORCE_SQUARE_PIXEL;
4272 : }
4273 :
4274 944 : if (GDALSuggestedWarpOutput2(
4275 : hSrcDS, psInfo->pfnTransform, hTransformArg.get(),
4276 : adfThisGeoTransform, &nThisPixels, &nThisLines, adfExtent,
4277 944 : nOptions) != CE_None)
4278 : {
4279 0 : return nullptr;
4280 : }
4281 :
4282 944 : if (CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", nullptr) ==
4283 : nullptr)
4284 : {
4285 944 : double MinX = adfExtent[0];
4286 944 : double MaxX = adfExtent[2];
4287 944 : double MaxY = adfExtent[3];
4288 944 : double MinY = adfExtent[1];
4289 944 : int bSuccess = TRUE;
4290 :
4291 : // +/-180 deg in longitude do not roundtrip sometimes
4292 944 : if (MinX == -180)
4293 51 : MinX += 1e-6;
4294 944 : if (MaxX == 180)
4295 50 : MaxX -= 1e-6;
4296 :
4297 : // +/-90 deg in latitude do not roundtrip sometimes
4298 944 : if (MinY == -90)
4299 52 : MinY += 1e-6;
4300 944 : if (MaxY == 90)
4301 54 : MaxY -= 1e-6;
4302 :
4303 : /* Check that the edges of the target image are in the validity
4304 : * area */
4305 : /* of the target projection */
4306 944 : const int N_STEPS = 20;
4307 20183 : for (int i = 0; i <= N_STEPS && bSuccess; i++)
4308 : {
4309 422762 : for (int j = 0; j <= N_STEPS && bSuccess; j++)
4310 : {
4311 403523 : const double dfRatioI = i * 1.0 / N_STEPS;
4312 403523 : const double dfRatioJ = j * 1.0 / N_STEPS;
4313 403523 : const double expected_x =
4314 403523 : (1 - dfRatioI) * MinX + dfRatioI * MaxX;
4315 403523 : const double expected_y =
4316 403523 : (1 - dfRatioJ) * MinY + dfRatioJ * MaxY;
4317 403523 : double x = expected_x;
4318 403523 : double y = expected_y;
4319 403523 : double z = 0;
4320 : /* Target SRS coordinates to source image pixel
4321 : * coordinates */
4322 403523 : if (!psInfo->pfnTransform(hTransformArg.get(), TRUE, 1,
4323 807027 : &x, &y, &z, &bSuccess) ||
4324 403504 : !bSuccess)
4325 19 : bSuccess = FALSE;
4326 : /* Source image pixel coordinates to target SRS
4327 : * coordinates */
4328 403523 : if (!psInfo->pfnTransform(hTransformArg.get(), FALSE, 1,
4329 807025 : &x, &y, &z, &bSuccess) ||
4330 403502 : !bSuccess)
4331 21 : bSuccess = FALSE;
4332 403523 : if (fabs(x - expected_x) >
4333 403523 : (MaxX - MinX) / nThisPixels ||
4334 403493 : fabs(y - expected_y) > (MaxY - MinY) / nThisLines)
4335 30 : bSuccess = FALSE;
4336 : }
4337 : }
4338 :
4339 : /* If not, retry with CHECK_WITH_INVERT_PROJ=TRUE that forces
4340 : * ogrct.cpp */
4341 : /* to check the consistency of each requested projection result
4342 : * with the */
4343 : /* invert projection */
4344 944 : if (!bSuccess)
4345 : {
4346 30 : CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ",
4347 : "TRUE");
4348 30 : CPLDebug("WARP", "Recompute out extent with "
4349 : "CHECK_WITH_INVERT_PROJ=TRUE");
4350 :
4351 30 : const CPLErr eErr = GDALSuggestedWarpOutput2(
4352 : hSrcDS, psInfo->pfnTransform, hTransformArg.get(),
4353 : adfThisGeoTransform, &nThisPixels, &nThisLines,
4354 : adfExtent, 0);
4355 30 : CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ",
4356 : nullptr);
4357 30 : if (eErr != CE_None)
4358 : {
4359 0 : return nullptr;
4360 : }
4361 : }
4362 : }
4363 : }
4364 :
4365 : // If no reprojection or geometry change is involved, and that the
4366 : // source image is north-up, preserve source resolution instead of
4367 : // forcing square pixels.
4368 1068 : const char *pszMethod = FetchSrcMethod(papszTO);
4369 : double adfThisGeoTransformTmp[6];
4370 1067 : if (!psOptions->bSquarePixels && bNeedsSuggestedWarpOutput &&
4371 943 : psOptions->dfXRes == 0 && psOptions->dfYRes == 0 &&
4372 916 : psOptions->nForcePixels == 0 && psOptions->nForceLines == 0 &&
4373 32 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
4374 773 : CSLFetchNameValue(papszTO, "COORDINATE_OPERATION") == nullptr &&
4375 769 : CSLFetchNameValue(papszTO, "SRC_METHOD") == nullptr &&
4376 769 : CSLFetchNameValue(papszTO, "DST_METHOD") == nullptr &&
4377 766 : GDALGetGeoTransform(hSrcDS, adfThisGeoTransformTmp) == CE_None &&
4378 746 : adfThisGeoTransformTmp[2] == 0 && adfThisGeoTransformTmp[4] == 0 &&
4379 744 : adfThisGeoTransformTmp[5] < 0 &&
4380 2768 : GDALGetMetadata(hSrcDS, "GEOLOC_ARRAY") == nullptr &&
4381 633 : GDALGetMetadata(hSrcDS, "RPC") == nullptr)
4382 : {
4383 633 : bool bIsSameHorizontal = osThisSourceSRS == osThisTargetSRS;
4384 633 : if (!bIsSameHorizontal)
4385 : {
4386 314 : OGRSpatialReference oSrcSRS;
4387 314 : OGRSpatialReference oDstSRS;
4388 314 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4389 : // DemoteTo2D requires PROJ >= 6.3
4390 157 : if (oSrcSRS.SetFromUserInput(osThisSourceSRS.c_str()) ==
4391 157 : OGRERR_NONE &&
4392 157 : oDstSRS.SetFromUserInput(osThisTargetSRS.c_str()) ==
4393 157 : OGRERR_NONE &&
4394 157 : (oSrcSRS.GetAxesCount() == 3 ||
4395 140 : oDstSRS.GetAxesCount() == 3) &&
4396 335 : oSrcSRS.DemoteTo2D(nullptr) == OGRERR_NONE &&
4397 21 : oDstSRS.DemoteTo2D(nullptr) == OGRERR_NONE)
4398 : {
4399 21 : bIsSameHorizontal = CPL_TO_BOOL(oSrcSRS.IsSame(&oDstSRS));
4400 : }
4401 : }
4402 633 : if (bIsSameHorizontal)
4403 : {
4404 482 : memcpy(adfThisGeoTransform, adfThisGeoTransformTmp,
4405 : 6 * sizeof(double));
4406 482 : adfExtent[0] = adfThisGeoTransform[0];
4407 482 : adfExtent[1] =
4408 964 : adfThisGeoTransform[3] +
4409 482 : GDALGetRasterYSize(hSrcDS) * adfThisGeoTransform[5];
4410 482 : adfExtent[2] =
4411 964 : adfThisGeoTransform[0] +
4412 482 : GDALGetRasterXSize(hSrcDS) * adfThisGeoTransform[1];
4413 482 : adfExtent[3] = adfThisGeoTransform[3];
4414 482 : dfResFromSourceAndTargetExtent =
4415 482 : std::numeric_limits<double>::infinity();
4416 : }
4417 : }
4418 :
4419 1068 : if (bNeedsSuggestedWarpOutput)
4420 : {
4421 : /* --------------------------------------------------------------------
4422 : */
4423 : /* Expand the working bounds to include this region, ensure the
4424 : */
4425 : /* working resolution is no more than this resolution. */
4426 : /* --------------------------------------------------------------------
4427 : */
4428 944 : if (dfWrkMaxX == 0.0 && dfWrkMinX == 0.0)
4429 : {
4430 920 : dfWrkMinX = adfExtent[0];
4431 920 : dfWrkMaxX = adfExtent[2];
4432 920 : dfWrkMaxY = adfExtent[3];
4433 920 : dfWrkMinY = adfExtent[1];
4434 920 : dfWrkResX = adfThisGeoTransform[1];
4435 920 : dfWrkResY = std::abs(adfThisGeoTransform[5]);
4436 : }
4437 : else
4438 : {
4439 24 : dfWrkMinX = std::min(dfWrkMinX, adfExtent[0]);
4440 24 : dfWrkMaxX = std::max(dfWrkMaxX, adfExtent[2]);
4441 24 : dfWrkMaxY = std::max(dfWrkMaxY, adfExtent[3]);
4442 24 : dfWrkMinY = std::min(dfWrkMinY, adfExtent[1]);
4443 24 : dfWrkResX = std::min(dfWrkResX, adfThisGeoTransform[1]);
4444 24 : dfWrkResY =
4445 24 : std::min(dfWrkResY, std::abs(adfThisGeoTransform[5]));
4446 : }
4447 : }
4448 :
4449 1068 : if (nSrcCount == 1)
4450 : {
4451 1016 : hUniqueTransformArg = std::move(hTransformArg);
4452 : }
4453 : }
4454 :
4455 : // If the source file(s) and the dest one share some files in common,
4456 : // only remove the files that are *not* in common
4457 1044 : if (!oSetExistingDestFilesFoundInSource.empty())
4458 : {
4459 14 : for (const std::string &osFilename : oSetExistingDestFiles)
4460 : {
4461 9 : if (oSetExistingDestFilesFoundInSource.find(osFilename) ==
4462 18 : oSetExistingDestFilesFoundInSource.end())
4463 : {
4464 4 : VSIUnlink(osFilename.c_str());
4465 : }
4466 : }
4467 : }
4468 :
4469 1044 : if (std::isfinite(dfResFromSourceAndTargetExtent))
4470 : {
4471 26 : dfWrkResX = dfResFromSourceAndTargetExtent;
4472 26 : dfWrkResY = dfResFromSourceAndTargetExtent;
4473 : }
4474 :
4475 : /* -------------------------------------------------------------------- */
4476 : /* Did we have any usable sources? */
4477 : /* -------------------------------------------------------------------- */
4478 1044 : if (nDstBandCount == 0)
4479 : {
4480 3 : CPLError(CE_Failure, CPLE_AppDefined, "No usable source images.");
4481 3 : return nullptr;
4482 : }
4483 :
4484 : /* -------------------------------------------------------------------- */
4485 : /* Turn the suggested region into a geotransform and suggested */
4486 : /* number of pixels and lines. */
4487 : /* -------------------------------------------------------------------- */
4488 1041 : double adfDstGeoTransform[6] = {0, 0, 0, 0, 0, 0};
4489 1041 : int nPixels = 0;
4490 1041 : int nLines = 0;
4491 :
4492 195 : const auto ComputePixelsFromResAndExtent = [psOptions]()
4493 : {
4494 390 : return std::max(1.0,
4495 390 : std::round((psOptions->dfMaxX - psOptions->dfMinX) /
4496 195 : psOptions->dfXRes));
4497 1041 : };
4498 :
4499 197 : const auto ComputeLinesFromResAndExtent = [psOptions]()
4500 : {
4501 : return std::max(
4502 394 : 1.0, std::round(std::fabs(psOptions->dfMaxY - psOptions->dfMinY) /
4503 197 : psOptions->dfYRes));
4504 1041 : };
4505 :
4506 1041 : if (bNeedsSuggestedWarpOutput)
4507 : {
4508 918 : adfDstGeoTransform[0] = dfWrkMinX;
4509 918 : adfDstGeoTransform[1] = dfWrkResX;
4510 918 : adfDstGeoTransform[2] = 0.0;
4511 918 : adfDstGeoTransform[3] = dfWrkMaxY;
4512 918 : adfDstGeoTransform[4] = 0.0;
4513 918 : adfDstGeoTransform[5] = -1 * dfWrkResY;
4514 :
4515 918 : const double dfPixels = (dfWrkMaxX - dfWrkMinX) / dfWrkResX;
4516 918 : const double dfLines = (dfWrkMaxY - dfWrkMinY) / dfWrkResY;
4517 : // guaranteed by GDALSuggestedWarpOutput2() behavior
4518 918 : CPLAssert(std::round(dfPixels) <= INT_MAX);
4519 918 : CPLAssert(std::round(dfLines) <= INT_MAX);
4520 918 : nPixels =
4521 918 : static_cast<int>(std::min<double>(std::round(dfPixels), INT_MAX));
4522 918 : nLines =
4523 918 : static_cast<int>(std::min<double>(std::round(dfLines), INT_MAX));
4524 : }
4525 :
4526 : /* -------------------------------------------------------------------- */
4527 : /* Did the user override some parameters? */
4528 : /* -------------------------------------------------------------------- */
4529 1041 : if (UseTEAndTSAndTRConsistently(psOptions))
4530 : {
4531 26 : adfDstGeoTransform[0] = psOptions->dfMinX;
4532 26 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4533 26 : adfDstGeoTransform[1] = psOptions->dfXRes;
4534 26 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4535 :
4536 26 : nPixels = psOptions->nForcePixels;
4537 26 : nLines = psOptions->nForceLines;
4538 : }
4539 1015 : else if (psOptions->dfXRes != 0.0 && psOptions->dfYRes != 0.0)
4540 : {
4541 86 : bool bDetectBlankBorders = false;
4542 :
4543 86 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4544 28 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4545 : {
4546 27 : bDetectBlankBorders = bNeedsSuggestedWarpOutput;
4547 :
4548 27 : psOptions->dfMinX = adfDstGeoTransform[0];
4549 27 : psOptions->dfMaxX =
4550 27 : adfDstGeoTransform[0] + adfDstGeoTransform[1] * nPixels;
4551 27 : psOptions->dfMaxY = adfDstGeoTransform[3];
4552 27 : psOptions->dfMinY =
4553 27 : adfDstGeoTransform[3] + adfDstGeoTransform[5] * nLines;
4554 : }
4555 :
4556 168 : if (psOptions->bTargetAlignedPixels ||
4557 82 : (psOptions->bCropToCutline &&
4558 1 : psOptions->aosWarpOptions.FetchBool("CUTLINE_ALL_TOUCHED", false)))
4559 : {
4560 4 : if ((psOptions->bTargetAlignedPixels &&
4561 10 : bNeedsSuggestedWarpOutput) ||
4562 2 : (psOptions->bCropToCutline &&
4563 1 : psOptions->aosWarpOptions.FetchBool("CUTLINE_ALL_TOUCHED",
4564 : false)))
4565 : {
4566 4 : bDetectBlankBorders = true;
4567 : }
4568 5 : constexpr double EPS = 1e-8;
4569 5 : psOptions->dfMinX =
4570 5 : floor(psOptions->dfMinX / psOptions->dfXRes + EPS) *
4571 5 : psOptions->dfXRes;
4572 5 : psOptions->dfMaxX =
4573 5 : ceil(psOptions->dfMaxX / psOptions->dfXRes - EPS) *
4574 5 : psOptions->dfXRes;
4575 5 : psOptions->dfMinY =
4576 5 : floor(psOptions->dfMinY / psOptions->dfYRes + EPS) *
4577 5 : psOptions->dfYRes;
4578 5 : psOptions->dfMaxY =
4579 5 : ceil(psOptions->dfMaxY / psOptions->dfYRes - EPS) *
4580 5 : psOptions->dfYRes;
4581 : }
4582 :
4583 115 : const auto UpdateGeoTransformandAndPixelLines = [&]()
4584 : {
4585 115 : const double dfPixels = ComputePixelsFromResAndExtent();
4586 115 : const double dfLines = ComputeLinesFromResAndExtent();
4587 115 : if (dfPixels > INT_MAX || dfLines > INT_MAX)
4588 : {
4589 2 : CPLError(CE_Failure, CPLE_AppDefined,
4590 : "Too large output raster size: %f x %f", dfPixels,
4591 : dfLines);
4592 2 : return false;
4593 : }
4594 :
4595 113 : nPixels = static_cast<int>(dfPixels);
4596 113 : nLines = static_cast<int>(dfLines);
4597 339 : adfDstGeoTransform[0] = psOptions->dfMinX;
4598 113 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4599 113 : adfDstGeoTransform[1] = psOptions->dfXRes;
4600 226 : adfDstGeoTransform[5] = (psOptions->dfMaxY > psOptions->dfMinY)
4601 113 : ? -psOptions->dfYRes
4602 2 : : psOptions->dfYRes;
4603 113 : return true;
4604 86 : };
4605 :
4606 114 : if (bDetectBlankBorders && nSrcCount == 1 && hUniqueTransformArg &&
4607 : // to avoid too large memory allocations
4608 28 : std::max(nPixels, nLines) < 100 * 1000 * 1000)
4609 : {
4610 : // Try to detect if the edge of the raster would be blank
4611 : // Cf https://github.com/OSGeo/gdal/issues/7905
4612 32 : while (nPixels > 1 || nLines > 1)
4613 : {
4614 29 : if (!UpdateGeoTransformandAndPixelLines())
4615 0 : return nullptr;
4616 :
4617 29 : GDALSetGenImgProjTransformerDstGeoTransform(
4618 : hUniqueTransformArg.get(), adfDstGeoTransform);
4619 :
4620 29 : std::vector<double> adfX(std::max(nPixels, nLines));
4621 29 : std::vector<double> adfY(adfX.size());
4622 29 : std::vector<double> adfZ(adfX.size());
4623 29 : std::vector<int> abSuccess(adfX.size());
4624 :
4625 : const auto DetectBlankBorder =
4626 116 : [&](int nValues,
4627 : std::function<bool(double, double)> funcIsOK)
4628 : {
4629 116 : if (nValues > 3)
4630 : {
4631 : // First try with just a subsample of 3 points
4632 60 : double adf3X[3] = {adfX[0], adfX[nValues / 2],
4633 60 : adfX[nValues - 1]};
4634 60 : double adf3Y[3] = {adfY[0], adfY[nValues / 2],
4635 60 : adfY[nValues - 1]};
4636 60 : double adf3Z[3] = {0};
4637 60 : if (GDALGenImgProjTransform(
4638 60 : hUniqueTransformArg.get(), TRUE, 3, &adf3X[0],
4639 120 : &adf3Y[0], &adf3Z[0], &abSuccess[0]))
4640 : {
4641 66 : for (int i = 0; i < 3; ++i)
4642 : {
4643 126 : if (abSuccess[i] &&
4644 63 : funcIsOK(adf3X[i], adf3Y[i]))
4645 : {
4646 51 : return false;
4647 : }
4648 : }
4649 : }
4650 : }
4651 :
4652 : // Do on full border to confirm
4653 65 : if (GDALGenImgProjTransform(hUniqueTransformArg.get(), TRUE,
4654 65 : nValues, &adfX[0], &adfY[0],
4655 130 : &adfZ[0], &abSuccess[0]))
4656 : {
4657 125 : for (int i = 0; i < nValues; ++i)
4658 : {
4659 122 : if (abSuccess[i] && funcIsOK(adfX[i], adfY[i]))
4660 : {
4661 56 : return false;
4662 : }
4663 : }
4664 : }
4665 :
4666 9 : return true;
4667 29 : };
4668 :
4669 9886 : for (int i = 0; i < nPixels; ++i)
4670 : {
4671 9857 : adfX[i] = i + 0.5;
4672 9857 : adfY[i] = 0.5;
4673 9857 : adfZ[i] = 0;
4674 : }
4675 29 : const bool bTopBlankLine = DetectBlankBorder(
4676 44 : nPixels, [](double, double y) { return y >= 0; });
4677 :
4678 9886 : for (int i = 0; i < nPixels; ++i)
4679 : {
4680 9857 : adfX[i] = i + 0.5;
4681 9857 : adfY[i] = nLines - 0.5;
4682 9857 : adfZ[i] = 0;
4683 : }
4684 29 : const int nSrcLines = GDALGetRasterYSize(pahSrcDS[0]);
4685 : const bool bBottomBlankLine =
4686 29 : DetectBlankBorder(nPixels, [nSrcLines](double, double y)
4687 26 : { return y <= nSrcLines; });
4688 :
4689 5419 : for (int i = 0; i < nLines; ++i)
4690 : {
4691 5390 : adfX[i] = 0.5;
4692 5390 : adfY[i] = i + 0.5;
4693 5390 : adfZ[i] = 0;
4694 : }
4695 29 : const bool bLeftBlankCol = DetectBlankBorder(
4696 61 : nLines, [](double x, double) { return x >= 0; });
4697 :
4698 5419 : for (int i = 0; i < nLines; ++i)
4699 : {
4700 5390 : adfX[i] = nPixels - 0.5;
4701 5390 : adfY[i] = i + 0.5;
4702 5390 : adfZ[i] = 0;
4703 : }
4704 29 : const int nSrcCols = GDALGetRasterXSize(pahSrcDS[0]);
4705 : const bool bRightBlankCol =
4706 29 : DetectBlankBorder(nLines, [nSrcCols](double x, double)
4707 54 : { return x <= nSrcCols; });
4708 :
4709 29 : if (!bTopBlankLine && !bBottomBlankLine && !bLeftBlankCol &&
4710 24 : !bRightBlankCol)
4711 24 : break;
4712 :
4713 5 : if (bTopBlankLine)
4714 : {
4715 1 : if (psOptions->dfMaxY - psOptions->dfMinY <=
4716 1 : 2 * psOptions->dfYRes)
4717 0 : break;
4718 1 : psOptions->dfMaxY -= psOptions->dfYRes;
4719 : }
4720 5 : if (bBottomBlankLine)
4721 : {
4722 3 : if (psOptions->dfMaxY - psOptions->dfMinY <=
4723 3 : 2 * psOptions->dfYRes)
4724 0 : break;
4725 3 : psOptions->dfMinY += psOptions->dfYRes;
4726 : }
4727 5 : if (bLeftBlankCol)
4728 : {
4729 2 : if (psOptions->dfMaxX - psOptions->dfMinX <=
4730 2 : 2 * psOptions->dfXRes)
4731 0 : break;
4732 2 : psOptions->dfMinX += psOptions->dfXRes;
4733 : }
4734 5 : if (bRightBlankCol)
4735 : {
4736 3 : if (psOptions->dfMaxX - psOptions->dfMinX <=
4737 3 : 2 * psOptions->dfXRes)
4738 0 : break;
4739 3 : psOptions->dfMaxX -= psOptions->dfXRes;
4740 : }
4741 : }
4742 : }
4743 :
4744 86 : if (!UpdateGeoTransformandAndPixelLines())
4745 86 : return nullptr;
4746 : }
4747 :
4748 929 : else if (psOptions->nForcePixels != 0 && psOptions->nForceLines != 0)
4749 : {
4750 142 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4751 104 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4752 : {
4753 104 : psOptions->dfMinX = dfWrkMinX;
4754 104 : psOptions->dfMaxX = dfWrkMaxX;
4755 104 : psOptions->dfMaxY = dfWrkMaxY;
4756 104 : psOptions->dfMinY = dfWrkMinY;
4757 : }
4758 :
4759 142 : psOptions->dfXRes =
4760 142 : (psOptions->dfMaxX - psOptions->dfMinX) / psOptions->nForcePixels;
4761 142 : psOptions->dfYRes =
4762 142 : (psOptions->dfMaxY - psOptions->dfMinY) / psOptions->nForceLines;
4763 :
4764 142 : adfDstGeoTransform[0] = psOptions->dfMinX;
4765 142 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4766 142 : adfDstGeoTransform[1] = psOptions->dfXRes;
4767 142 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4768 :
4769 142 : nPixels = psOptions->nForcePixels;
4770 142 : nLines = psOptions->nForceLines;
4771 : }
4772 :
4773 787 : else if (psOptions->nForcePixels != 0)
4774 : {
4775 5 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4776 4 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4777 : {
4778 4 : psOptions->dfMinX = dfWrkMinX;
4779 4 : psOptions->dfMaxX = dfWrkMaxX;
4780 4 : psOptions->dfMaxY = dfWrkMaxY;
4781 4 : psOptions->dfMinY = dfWrkMinY;
4782 : }
4783 :
4784 5 : psOptions->dfXRes =
4785 5 : (psOptions->dfMaxX - psOptions->dfMinX) / psOptions->nForcePixels;
4786 5 : psOptions->dfYRes = psOptions->dfXRes;
4787 :
4788 5 : adfDstGeoTransform[0] = psOptions->dfMinX;
4789 5 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4790 5 : adfDstGeoTransform[1] = psOptions->dfXRes;
4791 10 : adfDstGeoTransform[5] = (psOptions->dfMaxY > psOptions->dfMinY)
4792 5 : ? -psOptions->dfYRes
4793 0 : : psOptions->dfYRes;
4794 :
4795 5 : nPixels = psOptions->nForcePixels;
4796 5 : const double dfLines = ComputeLinesFromResAndExtent();
4797 5 : if (dfLines > INT_MAX)
4798 : {
4799 1 : CPLError(CE_Failure, CPLE_AppDefined,
4800 : "Too large output raster size: %d x %f", nPixels, dfLines);
4801 1 : return nullptr;
4802 : }
4803 4 : nLines = static_cast<int>(dfLines);
4804 : }
4805 :
4806 782 : else if (psOptions->nForceLines != 0)
4807 : {
4808 3 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4809 3 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4810 : {
4811 3 : psOptions->dfMinX = dfWrkMinX;
4812 3 : psOptions->dfMaxX = dfWrkMaxX;
4813 3 : psOptions->dfMaxY = dfWrkMaxY;
4814 3 : psOptions->dfMinY = dfWrkMinY;
4815 : }
4816 :
4817 3 : psOptions->dfYRes =
4818 3 : (psOptions->dfMaxY - psOptions->dfMinY) / psOptions->nForceLines;
4819 3 : psOptions->dfXRes = std::fabs(psOptions->dfYRes);
4820 :
4821 3 : adfDstGeoTransform[0] = psOptions->dfMinX;
4822 3 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4823 3 : adfDstGeoTransform[1] = psOptions->dfXRes;
4824 3 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4825 :
4826 3 : const double dfPixels = ComputePixelsFromResAndExtent();
4827 3 : nLines = psOptions->nForceLines;
4828 3 : if (dfPixels > INT_MAX)
4829 : {
4830 1 : CPLError(CE_Failure, CPLE_AppDefined,
4831 : "Too large output raster size: %f x %d", dfPixels, nLines);
4832 1 : return nullptr;
4833 : }
4834 2 : nPixels = static_cast<int>(dfPixels);
4835 : }
4836 :
4837 779 : else if (psOptions->dfMinX != 0.0 || psOptions->dfMinY != 0.0 ||
4838 703 : psOptions->dfMaxX != 0.0 || psOptions->dfMaxY != 0.0)
4839 : {
4840 77 : psOptions->dfXRes = adfDstGeoTransform[1];
4841 77 : psOptions->dfYRes = fabs(adfDstGeoTransform[5]);
4842 :
4843 77 : const double dfPixels = ComputePixelsFromResAndExtent();
4844 77 : const double dfLines = ComputeLinesFromResAndExtent();
4845 77 : if (dfPixels > INT_MAX || dfLines > INT_MAX)
4846 : {
4847 1 : CPLError(CE_Failure, CPLE_AppDefined,
4848 : "Too large output raster size: %f x %f", dfPixels,
4849 : dfLines);
4850 1 : return nullptr;
4851 : }
4852 :
4853 76 : nPixels = static_cast<int>(dfPixels);
4854 76 : nLines = static_cast<int>(dfLines);
4855 :
4856 76 : psOptions->dfXRes = (psOptions->dfMaxX - psOptions->dfMinX) / nPixels;
4857 76 : psOptions->dfYRes = (psOptions->dfMaxY - psOptions->dfMinY) / nLines;
4858 :
4859 76 : adfDstGeoTransform[0] = psOptions->dfMinX;
4860 76 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4861 76 : adfDstGeoTransform[1] = psOptions->dfXRes;
4862 76 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4863 : }
4864 :
4865 1036 : if (EQUAL(pszFormat, "GTiff"))
4866 : {
4867 :
4868 : /* --------------------------------------------------------------------
4869 : */
4870 : /* Automatically set PHOTOMETRIC=RGB for GTiff when appropriate */
4871 : /* --------------------------------------------------------------------
4872 : */
4873 451 : if (apeColorInterpretations.size() >= 3 &&
4874 35 : apeColorInterpretations[0] == GCI_RedBand &&
4875 35 : apeColorInterpretations[1] == GCI_GreenBand &&
4876 521 : apeColorInterpretations[2] == GCI_BlueBand &&
4877 35 : aosCreateOptions.FetchNameValue("PHOTOMETRIC") == nullptr)
4878 : {
4879 35 : aosCreateOptions.SetNameValue("PHOTOMETRIC", "RGB");
4880 :
4881 : // Preserve potential ALPHA=PREMULTIPLIED from source alpha band
4882 70 : if (aosCreateOptions.FetchNameValue("ALPHA") == nullptr &&
4883 35 : apeColorInterpretations.size() == 4 &&
4884 75 : apeColorInterpretations[3] == GCI_AlphaBand &&
4885 5 : GDALGetRasterCount(pahSrcDS[0]) == 4)
4886 : {
4887 : const char *pszAlpha =
4888 5 : GDALGetMetadataItem(GDALGetRasterBand(pahSrcDS[0], 4),
4889 : "ALPHA", "IMAGE_STRUCTURE");
4890 5 : if (pszAlpha)
4891 : {
4892 1 : aosCreateOptions.SetNameValue("ALPHA", pszAlpha);
4893 : }
4894 : }
4895 : }
4896 :
4897 : /* The GTiff driver now supports writing band color interpretation */
4898 : /* in the TIFF_GDAL_METADATA tag */
4899 451 : bSetColorInterpretation = true;
4900 : }
4901 :
4902 : /* -------------------------------------------------------------------- */
4903 : /* Create the output file. */
4904 : /* -------------------------------------------------------------------- */
4905 1036 : if (!psOptions->bQuiet)
4906 78 : printf("Creating output file that is %dP x %dL.\n", nPixels, nLines);
4907 :
4908 1036 : hDstDS = GDALCreate(hDriver, pszFilename, nPixels, nLines, nDstBandCount,
4909 1036 : eDT, aosCreateOptions.List());
4910 :
4911 1036 : if (hDstDS == nullptr)
4912 : {
4913 1 : return nullptr;
4914 : }
4915 :
4916 1035 : if (psOptions->bDeleteOutputFileOnceCreated)
4917 : {
4918 12 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4919 6 : GDALDeleteDataset(hDriver, pszFilename);
4920 : }
4921 :
4922 : /* -------------------------------------------------------------------- */
4923 : /* Write out the projection definition. */
4924 : /* -------------------------------------------------------------------- */
4925 1035 : const char *pszDstMethod = CSLFetchNameValue(papszTO, "DST_METHOD");
4926 1035 : if (pszDstMethod == nullptr || !EQUAL(pszDstMethod, "NO_GEOTRANSFORM"))
4927 : {
4928 1033 : OGRSpatialReference oTargetSRS;
4929 1033 : oTargetSRS.SetFromUserInput(osThisTargetSRS);
4930 1033 : oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4931 :
4932 1033 : if (oTargetSRS.IsDynamic())
4933 : {
4934 613 : double dfCoordEpoch = CPLAtof(CSLFetchNameValueDef(
4935 : papszTO, "DST_COORDINATE_EPOCH",
4936 : CSLFetchNameValueDef(papszTO, "COORDINATE_EPOCH", "0")));
4937 613 : if (dfCoordEpoch == 0)
4938 : {
4939 : const OGRSpatialReferenceH hSrcSRS =
4940 613 : GDALGetSpatialRef(pahSrcDS[0]);
4941 613 : const char *pszMethod = FetchSrcMethod(papszTO);
4942 613 : if (hSrcSRS &&
4943 7 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")))
4944 : {
4945 571 : dfCoordEpoch = OSRGetCoordinateEpoch(hSrcSRS);
4946 : }
4947 : }
4948 613 : if (dfCoordEpoch > 0)
4949 1 : oTargetSRS.SetCoordinateEpoch(dfCoordEpoch);
4950 : }
4951 :
4952 1033 : if (GDALSetSpatialRef(hDstDS, OGRSpatialReference::ToHandle(
4953 2066 : &oTargetSRS)) == CE_Failure ||
4954 1033 : GDALSetGeoTransform(hDstDS, adfDstGeoTransform) == CE_Failure)
4955 : {
4956 0 : GDALClose(hDstDS);
4957 0 : return nullptr;
4958 1033 : }
4959 : }
4960 : else
4961 : {
4962 2 : adfDstGeoTransform[3] += adfDstGeoTransform[5] * nLines;
4963 2 : adfDstGeoTransform[5] = fabs(adfDstGeoTransform[5]);
4964 : }
4965 :
4966 1035 : if (hUniqueTransformArg && bUpdateTransformerWithDestGT)
4967 : {
4968 1009 : GDALSetGenImgProjTransformerDstGeoTransform(hUniqueTransformArg.get(),
4969 : adfDstGeoTransform);
4970 :
4971 1009 : void *pTransformerArg = hUniqueTransformArg.get();
4972 1009 : if (GDALIsTransformer(pTransformerArg,
4973 : GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4974 : {
4975 : // Detect if there is a change of coordinate operation in the area of
4976 : // interest. The underlying proj_trans_get_last_used_operation() is
4977 : // quite costly due to using proj_clone() internally, so only do that
4978 : // on a restricted set of points.
4979 1009 : GDALGenImgProjTransformInfo *psTransformInfo{
4980 : static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg)};
4981 1009 : GDALTransformerInfo *psInfo = &psTransformInfo->sTI;
4982 :
4983 1009 : void *pReprojectArg = psTransformInfo->pReprojectArg;
4984 1009 : if (GDALIsTransformer(pReprojectArg,
4985 : GDAL_APPROX_TRANSFORMER_CLASS_NAME))
4986 : {
4987 1 : const auto *pApproxInfo =
4988 : static_cast<const GDALApproxTransformInfo *>(pReprojectArg);
4989 1 : pReprojectArg = pApproxInfo->pBaseCBData;
4990 : }
4991 :
4992 1009 : if (GDALIsTransformer(pReprojectArg,
4993 : GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME))
4994 : {
4995 611 : const GDALReprojectionTransformInfo *psRTI =
4996 : static_cast<const GDALReprojectionTransformInfo *>(
4997 : pReprojectArg);
4998 611 : if (psRTI->poReverseTransform)
4999 : {
5000 1222 : std::vector<double> adfX, adfY, adfZ;
5001 1222 : std::vector<int> abSuccess;
5002 :
5003 611 : GDALDatasetH hSrcDS = pahSrcDS[0];
5004 :
5005 : // Sample points on a N x N grid in the source raster
5006 611 : constexpr int N = 10;
5007 611 : const int nSrcXSize = GDALGetRasterXSize(hSrcDS);
5008 611 : const int nSrcYSize = GDALGetRasterYSize(hSrcDS);
5009 7332 : for (int j = 0; j <= N; ++j)
5010 : {
5011 80652 : for (int i = 0; i <= N; ++i)
5012 : {
5013 73931 : adfX.push_back(static_cast<double>(i) / N *
5014 : nSrcXSize);
5015 73931 : adfY.push_back(static_cast<double>(j) / N *
5016 : nSrcYSize);
5017 73931 : adfZ.push_back(0);
5018 73931 : abSuccess.push_back(0);
5019 : }
5020 : }
5021 :
5022 : {
5023 1222 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
5024 :
5025 : // Transform from source raster coordinates to target raster
5026 : // coordinates
5027 611 : psInfo->pfnTransform(hUniqueTransformArg.get(), FALSE,
5028 611 : static_cast<int>(adfX.size()),
5029 611 : &adfX[0], &adfY[0], &adfZ[0],
5030 611 : &abSuccess[0]);
5031 :
5032 611 : const int nDstXSize = nPixels;
5033 611 : const int nDstYSize = nLines;
5034 :
5035 : // Clamp target raster coordinates
5036 74542 : for (size_t i = 0; i < adfX.size(); ++i)
5037 : {
5038 73931 : if (adfX[i] < 0)
5039 1106 : adfX[i] = 0;
5040 73931 : if (adfX[i] > nDstXSize)
5041 2981 : adfX[i] = nDstXSize;
5042 73931 : if (adfY[i] < 0)
5043 1413 : adfY[i] = 0;
5044 73931 : if (adfY[i] > nDstYSize)
5045 3519 : adfY[i] = nDstYSize;
5046 : }
5047 :
5048 : // Start recording if different coordinate operations are
5049 : // going to be used
5050 611 : OGRProjCTDifferentOperationsStart(
5051 611 : psRTI->poReverseTransform);
5052 :
5053 : // Transform back to source raster coordinates.
5054 611 : psInfo->pfnTransform(hUniqueTransformArg.get(), TRUE,
5055 611 : static_cast<int>(adfX.size()),
5056 611 : &adfX[0], &adfY[0], &adfZ[0],
5057 611 : &abSuccess[0]);
5058 : }
5059 :
5060 611 : if (OGRProjCTDifferentOperationsUsed(
5061 611 : psRTI->poReverseTransform))
5062 : {
5063 0 : CPLError(
5064 : CE_Warning, CPLE_AppDefined,
5065 : "Several coordinate operations are going to be "
5066 : "used. Artifacts may appear. You may consider "
5067 : "using the -to ALLOW_BALLPARK=NO and/or "
5068 : "-to ONLY_BEST=YES transform options, or specify "
5069 : "a particular coordinate operation with -ct");
5070 : }
5071 :
5072 : // Stop recording
5073 611 : OGRProjCTDifferentOperationsStop(psRTI->poReverseTransform);
5074 : }
5075 : }
5076 : }
5077 : }
5078 :
5079 : /* -------------------------------------------------------------------- */
5080 : /* Try to set color interpretation of source bands to target */
5081 : /* dataset. */
5082 : /* FIXME? We should likely do that for other drivers than VRT & */
5083 : /* GTiff but it might create spurious .aux.xml files (at least */
5084 : /* with HFA, and netCDF) */
5085 : /* -------------------------------------------------------------------- */
5086 1035 : if (bVRT || bSetColorInterpretation)
5087 : {
5088 622 : int nBandsToCopy = static_cast<int>(apeColorInterpretations.size());
5089 622 : if (psOptions->bEnableSrcAlpha)
5090 30 : nBandsToCopy--;
5091 1427 : for (int iBand = 0; iBand < nBandsToCopy; iBand++)
5092 : {
5093 805 : GDALSetRasterColorInterpretation(
5094 : GDALGetRasterBand(hDstDS, iBand + 1),
5095 805 : apeColorInterpretations[iBand]);
5096 : }
5097 : }
5098 :
5099 : /* -------------------------------------------------------------------- */
5100 : /* Try to set color interpretation of output file alpha band. */
5101 : /* -------------------------------------------------------------------- */
5102 1035 : if (psOptions->bEnableDstAlpha)
5103 : {
5104 99 : GDALSetRasterColorInterpretation(
5105 : GDALGetRasterBand(hDstDS, nDstBandCount), GCI_AlphaBand);
5106 : }
5107 :
5108 : /* -------------------------------------------------------------------- */
5109 : /* Copy the raster attribute table, if required. */
5110 : /* -------------------------------------------------------------------- */
5111 1035 : if (hRAT != nullptr)
5112 : {
5113 0 : GDALSetDefaultRAT(GDALGetRasterBand(hDstDS, 1), hRAT);
5114 : }
5115 :
5116 : /* -------------------------------------------------------------------- */
5117 : /* Copy the color table, if required. */
5118 : /* -------------------------------------------------------------------- */
5119 1035 : if (poCT)
5120 : {
5121 6 : GDALSetRasterColorTable(GDALGetRasterBand(hDstDS, 1),
5122 : GDALColorTable::ToHandle(poCT.get()));
5123 : }
5124 :
5125 : /* -------------------------------------------------------------------- */
5126 : /* Copy scale/offset if found on source */
5127 : /* -------------------------------------------------------------------- */
5128 1035 : if (nSrcCount == 1)
5129 : {
5130 1011 : GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
5131 1011 : GDALDataset *poDstDS = GDALDataset::FromHandle(hDstDS);
5132 :
5133 1011 : int nBandsToCopy = nDstBandCount;
5134 1011 : if (psOptions->bEnableDstAlpha)
5135 96 : nBandsToCopy--;
5136 1011 : nBandsToCopy = std::min(nBandsToCopy, poSrcDS->GetRasterCount());
5137 :
5138 2391 : for (int i = 0; i < nBandsToCopy; i++)
5139 : {
5140 1420 : auto poSrcBand = poSrcDS->GetRasterBand(
5141 1380 : psOptions->anSrcBands.empty() ? i + 1
5142 40 : : psOptions->anSrcBands[i]);
5143 1420 : auto poDstBand = poDstDS->GetRasterBand(
5144 1380 : psOptions->anDstBands.empty() ? i + 1
5145 40 : : psOptions->anDstBands[i]);
5146 1380 : if (poSrcBand && poDstBand)
5147 : {
5148 1378 : int bHasScale = FALSE;
5149 1378 : const double dfScale = poSrcBand->GetScale(&bHasScale);
5150 1378 : if (bHasScale)
5151 41 : poDstBand->SetScale(dfScale);
5152 :
5153 1378 : int bHasOffset = FALSE;
5154 1378 : const double dfOffset = poSrcBand->GetOffset(&bHasOffset);
5155 1378 : if (bHasOffset)
5156 41 : poDstBand->SetOffset(dfOffset);
5157 : }
5158 : }
5159 : }
5160 :
5161 1035 : return hDstDS;
5162 : }
5163 :
5164 : /************************************************************************/
5165 : /* GeoTransform_Transformer() */
5166 : /* */
5167 : /* Convert points from georef coordinates to pixel/line based */
5168 : /* on a geotransform. */
5169 : /************************************************************************/
5170 :
5171 : class CutlineTransformer : public OGRCoordinateTransformation
5172 : {
5173 : CPL_DISALLOW_COPY_ASSIGN(CutlineTransformer)
5174 :
5175 : public:
5176 : void *hSrcImageTransformer = nullptr;
5177 :
5178 40 : explicit CutlineTransformer(void *hTransformArg)
5179 40 : : hSrcImageTransformer(hTransformArg)
5180 : {
5181 40 : }
5182 :
5183 0 : const OGRSpatialReference *GetSourceCS() const override
5184 : {
5185 0 : return nullptr;
5186 : }
5187 :
5188 161 : const OGRSpatialReference *GetTargetCS() const override
5189 : {
5190 161 : return nullptr;
5191 : }
5192 :
5193 : ~CutlineTransformer() override;
5194 :
5195 60 : virtual int Transform(size_t nCount, double *x, double *y, double *z,
5196 : double * /* t */, int *pabSuccess) override
5197 : {
5198 60 : CPLAssert(nCount <=
5199 : static_cast<size_t>(std::numeric_limits<int>::max()));
5200 60 : return GDALGenImgProjTransform(hSrcImageTransformer, TRUE,
5201 : static_cast<int>(nCount), x, y, z,
5202 60 : pabSuccess);
5203 : }
5204 :
5205 0 : OGRCoordinateTransformation *Clone() const override
5206 : {
5207 : return new CutlineTransformer(
5208 0 : GDALCloneTransformer(hSrcImageTransformer));
5209 : }
5210 :
5211 0 : OGRCoordinateTransformation *GetInverse() const override
5212 : {
5213 0 : return nullptr;
5214 : }
5215 : };
5216 :
5217 40 : CutlineTransformer::~CutlineTransformer()
5218 : {
5219 40 : GDALDestroyTransformer(hSrcImageTransformer);
5220 40 : }
5221 :
5222 276 : static double GetMaximumSegmentLength(OGRGeometry *poGeom)
5223 : {
5224 276 : switch (wkbFlatten(poGeom->getGeometryType()))
5225 : {
5226 102 : case wkbLineString:
5227 : {
5228 102 : OGRLineString *poLS = static_cast<OGRLineString *>(poGeom);
5229 102 : double dfMaxSquaredLength = 0.0;
5230 13245 : for (int i = 0; i < poLS->getNumPoints() - 1; i++)
5231 : {
5232 13143 : double dfDeltaX = poLS->getX(i + 1) - poLS->getX(i);
5233 13143 : double dfDeltaY = poLS->getY(i + 1) - poLS->getY(i);
5234 13143 : double dfSquaredLength =
5235 13143 : dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY;
5236 13143 : dfMaxSquaredLength =
5237 13143 : std::max(dfMaxSquaredLength, dfSquaredLength);
5238 : }
5239 102 : return sqrt(dfMaxSquaredLength);
5240 : }
5241 :
5242 100 : case wkbPolygon:
5243 : {
5244 100 : OGRPolygon *poPoly = static_cast<OGRPolygon *>(poGeom);
5245 : double dfMaxLength =
5246 100 : GetMaximumSegmentLength(poPoly->getExteriorRing());
5247 102 : for (int i = 0; i < poPoly->getNumInteriorRings(); i++)
5248 : {
5249 2 : dfMaxLength = std::max(
5250 : dfMaxLength,
5251 2 : GetMaximumSegmentLength(poPoly->getInteriorRing(i)));
5252 : }
5253 100 : return dfMaxLength;
5254 : }
5255 :
5256 74 : case wkbMultiPolygon:
5257 : {
5258 74 : OGRMultiPolygon *poMP = static_cast<OGRMultiPolygon *>(poGeom);
5259 74 : double dfMaxLength = 0.0;
5260 152 : for (int i = 0; i < poMP->getNumGeometries(); i++)
5261 : {
5262 78 : dfMaxLength =
5263 78 : std::max(dfMaxLength,
5264 78 : GetMaximumSegmentLength(poMP->getGeometryRef(i)));
5265 : }
5266 74 : return dfMaxLength;
5267 : }
5268 :
5269 0 : default:
5270 0 : CPLAssert(false);
5271 : return 0.0;
5272 : }
5273 : }
5274 :
5275 : /************************************************************************/
5276 : /* RemoveZeroWidthSlivers() */
5277 : /* */
5278 : /* Such slivers can cause issues after reprojection. */
5279 : /************************************************************************/
5280 :
5281 118 : static void RemoveZeroWidthSlivers(OGRGeometry *poGeom)
5282 : {
5283 118 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
5284 118 : if (eType == wkbMultiPolygon)
5285 : {
5286 32 : auto poMP = poGeom->toMultiPolygon();
5287 32 : int nNumGeometries = poMP->getNumGeometries();
5288 66 : for (int i = 0; i < nNumGeometries; /* incremented in loop */)
5289 : {
5290 34 : auto poPoly = poMP->getGeometryRef(i);
5291 34 : RemoveZeroWidthSlivers(poPoly);
5292 34 : if (poPoly->IsEmpty())
5293 : {
5294 1 : CPLDebug("WARP",
5295 : "RemoveZeroWidthSlivers: removing empty polygon");
5296 1 : poMP->removeGeometry(i, /* bDelete = */ true);
5297 1 : --nNumGeometries;
5298 : }
5299 : else
5300 : {
5301 33 : ++i;
5302 : }
5303 : }
5304 : }
5305 86 : else if (eType == wkbPolygon)
5306 : {
5307 42 : auto poPoly = poGeom->toPolygon();
5308 42 : if (auto poExteriorRing = poPoly->getExteriorRing())
5309 : {
5310 42 : RemoveZeroWidthSlivers(poExteriorRing);
5311 42 : if (poExteriorRing->getNumPoints() < 4)
5312 : {
5313 1 : poPoly->empty();
5314 1 : return;
5315 : }
5316 : }
5317 41 : int nNumInteriorRings = poPoly->getNumInteriorRings();
5318 43 : for (int i = 0; i < nNumInteriorRings; /* incremented in loop */)
5319 : {
5320 2 : auto poRing = poPoly->getInteriorRing(i);
5321 2 : RemoveZeroWidthSlivers(poRing);
5322 2 : if (poRing->getNumPoints() < 4)
5323 : {
5324 1 : CPLDebug(
5325 : "WARP",
5326 : "RemoveZeroWidthSlivers: removing empty interior ring");
5327 1 : constexpr int OFFSET_EXTERIOR_RING = 1;
5328 1 : poPoly->removeRing(i + OFFSET_EXTERIOR_RING,
5329 : /* bDelete = */ true);
5330 1 : --nNumInteriorRings;
5331 : }
5332 : else
5333 : {
5334 1 : ++i;
5335 : }
5336 : }
5337 : }
5338 44 : else if (eType == wkbLineString)
5339 : {
5340 44 : OGRLineString *poLS = poGeom->toLineString();
5341 44 : int numPoints = poLS->getNumPoints();
5342 210 : for (int i = 1; i < numPoints - 1;)
5343 : {
5344 166 : const double x1 = poLS->getX(i - 1);
5345 166 : const double y1 = poLS->getY(i - 1);
5346 166 : const double x2 = poLS->getX(i);
5347 166 : const double y2 = poLS->getY(i);
5348 166 : const double x3 = poLS->getX(i + 1);
5349 166 : const double y3 = poLS->getY(i + 1);
5350 166 : const double dx1 = x2 - x1;
5351 166 : const double dy1 = y2 - y1;
5352 166 : const double dx2 = x3 - x2;
5353 166 : const double dy2 = y3 - y2;
5354 166 : const double scalar_product = dx1 * dx2 + dy1 * dy2;
5355 166 : const double square_scalar_product =
5356 : scalar_product * scalar_product;
5357 166 : const double square_norm1 = dx1 * dx1 + dy1 * dy1;
5358 166 : const double square_norm2 = dx2 * dx2 + dy2 * dy2;
5359 166 : const double square_norm1_mult_norm2 = square_norm1 * square_norm2;
5360 166 : if (scalar_product < 0 &&
5361 25 : fabs(square_scalar_product - square_norm1_mult_norm2) <=
5362 25 : 1e-15 * square_norm1_mult_norm2)
5363 : {
5364 5 : CPLDebug("WARP",
5365 : "RemoveZeroWidthSlivers: removing point %.10g %.10g",
5366 : x2, y2);
5367 5 : poLS->removePoint(i);
5368 5 : numPoints--;
5369 : }
5370 : else
5371 : {
5372 161 : ++i;
5373 : }
5374 : }
5375 : }
5376 : }
5377 :
5378 : /************************************************************************/
5379 : /* TransformCutlineToSource() */
5380 : /* */
5381 : /* Transform cutline from its SRS to source pixel/line coordinates.*/
5382 : /************************************************************************/
5383 40 : static CPLErr TransformCutlineToSource(GDALDataset *poSrcDS,
5384 : OGRGeometry *poCutline,
5385 : char ***ppapszWarpOptions,
5386 : CSLConstList papszTO_In)
5387 :
5388 : {
5389 40 : RemoveZeroWidthSlivers(poCutline);
5390 :
5391 80 : auto poMultiPolygon = std::unique_ptr<OGRGeometry>(poCutline->clone());
5392 :
5393 : /* -------------------------------------------------------------------- */
5394 : /* Checkout that if there's a cutline SRS, there's also a raster */
5395 : /* one. */
5396 : /* -------------------------------------------------------------------- */
5397 40 : std::unique_ptr<OGRSpatialReference> poRasterSRS;
5398 : const CPLString osProjection =
5399 80 : GetSrcDSProjection(GDALDataset::ToHandle(poSrcDS), papszTO_In);
5400 40 : if (!osProjection.empty())
5401 : {
5402 36 : poRasterSRS = std::make_unique<OGRSpatialReference>();
5403 36 : poRasterSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5404 36 : if (poRasterSRS->SetFromUserInput(osProjection) != OGRERR_NONE)
5405 : {
5406 0 : poRasterSRS.reset();
5407 : }
5408 : }
5409 :
5410 40 : std::unique_ptr<OGRSpatialReference> poDstSRS;
5411 40 : const char *pszThisTargetSRS = CSLFetchNameValue(papszTO_In, "DST_SRS");
5412 40 : if (pszThisTargetSRS)
5413 : {
5414 8 : poDstSRS = std::make_unique<OGRSpatialReference>();
5415 8 : poDstSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5416 8 : if (poDstSRS->SetFromUserInput(pszThisTargetSRS) != OGRERR_NONE)
5417 : {
5418 0 : return CE_Failure;
5419 : }
5420 : }
5421 32 : else if (poRasterSRS)
5422 : {
5423 28 : poDstSRS.reset(poRasterSRS->Clone());
5424 : }
5425 :
5426 : /* -------------------------------------------------------------------- */
5427 : /* Extract the cutline SRS. */
5428 : /* -------------------------------------------------------------------- */
5429 : const OGRSpatialReference *poCutlineSRS =
5430 40 : poMultiPolygon->getSpatialReference();
5431 :
5432 : /* -------------------------------------------------------------------- */
5433 : /* Detect if there's no transform at all involved, in which case */
5434 : /* we can avoid densification. */
5435 : /* -------------------------------------------------------------------- */
5436 40 : bool bMayNeedDensify = true;
5437 76 : if (poRasterSRS && poCutlineSRS && poRasterSRS->IsSame(poCutlineSRS) &&
5438 26 : poSrcDS->GetGCPCount() == 0 && !poSrcDS->GetMetadata("RPC") &&
5439 22 : !poSrcDS->GetMetadata("GEOLOCATION") &&
5440 98 : !CSLFetchNameValue(papszTO_In, "GEOLOC_ARRAY") &&
5441 22 : !CSLFetchNameValue(papszTO_In, "SRC_GEOLOC_ARRAY"))
5442 : {
5443 44 : CPLStringList aosTOTmp(papszTO_In);
5444 22 : aosTOTmp.SetNameValue("SRC_SRS", nullptr);
5445 22 : aosTOTmp.SetNameValue("DST_SRS", nullptr);
5446 22 : if (aosTOTmp.size() == 0)
5447 : {
5448 22 : bMayNeedDensify = false;
5449 : }
5450 : }
5451 :
5452 : /* -------------------------------------------------------------------- */
5453 : /* Compare source raster SRS and cutline SRS */
5454 : /* -------------------------------------------------------------------- */
5455 40 : if (poRasterSRS && poCutlineSRS)
5456 : {
5457 : /* OK, we will reproject */
5458 : }
5459 8 : else if (poRasterSRS && !poCutlineSRS)
5460 : {
5461 4 : CPLError(
5462 : CE_Warning, CPLE_AppDefined,
5463 : "the source raster dataset has a SRS, but the cutline features\n"
5464 : "not. We assume that the cutline coordinates are expressed in the "
5465 : "destination SRS.\n"
5466 : "If not, cutline results may be incorrect.");
5467 : }
5468 4 : else if (!poRasterSRS && poCutlineSRS)
5469 : {
5470 0 : CPLError(CE_Warning, CPLE_AppDefined,
5471 : "the input vector layer has a SRS, but the source raster "
5472 : "dataset does not.\n"
5473 : "Cutline results may be incorrect.");
5474 : }
5475 :
5476 : auto poCTCutlineToSrc = CreateCTCutlineToSrc(
5477 80 : poRasterSRS.get(), poDstSRS.get(), poCutlineSRS, papszTO_In);
5478 :
5479 80 : CPLStringList aosTO(papszTO_In);
5480 :
5481 40 : if (pszThisTargetSRS && !osProjection.empty())
5482 : {
5483 : // Avoid any reprojection when using the GenImgProjTransformer
5484 8 : aosTO.SetNameValue("DST_SRS", osProjection.c_str());
5485 : }
5486 40 : aosTO.SetNameValue("SRC_COORDINATE_EPOCH", nullptr);
5487 40 : aosTO.SetNameValue("DST_COORDINATE_EPOCH", nullptr);
5488 40 : aosTO.SetNameValue("COORDINATE_OPERATION", nullptr);
5489 :
5490 : /* -------------------------------------------------------------------- */
5491 : /* It may be unwise to let the mask geometry be re-wrapped by */
5492 : /* the CENTER_LONG machinery as this can easily screw up world */
5493 : /* spanning masks and invert the mask topology. */
5494 : /* -------------------------------------------------------------------- */
5495 40 : aosTO.SetNameValue("INSERT_CENTER_LONG", "FALSE");
5496 :
5497 : /* -------------------------------------------------------------------- */
5498 : /* Transform the geometry to pixel/line coordinates. */
5499 : /* -------------------------------------------------------------------- */
5500 : /* The cutline transformer will *invert* the hSrcImageTransformer */
5501 : /* so it will convert from the source SRS to the source pixel/line */
5502 : /* coordinates */
5503 : CutlineTransformer oTransformer(GDALCreateGenImgProjTransformer2(
5504 80 : GDALDataset::ToHandle(poSrcDS), nullptr, aosTO.List()));
5505 :
5506 40 : if (oTransformer.hSrcImageTransformer == nullptr)
5507 : {
5508 0 : return CE_Failure;
5509 : }
5510 :
5511 : // Some transforms like RPC can transform a valid geometry into an invalid
5512 : // one if the node density of the input geometry isn't sufficient before
5513 : // reprojection. So after an initial reprojection, we check that the
5514 : // maximum length of a segment is no longer than 1 pixel, and if not,
5515 : // we densify the input geometry before doing a new reprojection
5516 : const double dfMaxLengthInSpatUnits =
5517 40 : GetMaximumSegmentLength(poMultiPolygon.get());
5518 40 : OGRErr eErr = OGRERR_NONE;
5519 40 : if (poCTCutlineToSrc)
5520 : {
5521 12 : poMultiPolygon.reset(OGRGeometryFactory::transformWithOptions(
5522 6 : poMultiPolygon.get(), poCTCutlineToSrc.get(), nullptr));
5523 6 : if (!poMultiPolygon)
5524 : {
5525 0 : eErr = OGRERR_FAILURE;
5526 0 : poMultiPolygon.reset(poCutline->clone());
5527 0 : poMultiPolygon->transform(poCTCutlineToSrc.get());
5528 : }
5529 : }
5530 40 : if (poMultiPolygon->transform(&oTransformer) != OGRERR_NONE)
5531 : {
5532 0 : CPLError(CE_Failure, CPLE_AppDefined,
5533 : "poMultiPolygon->transform(&oTransformer) failed at line %d",
5534 : __LINE__);
5535 0 : eErr = OGRERR_FAILURE;
5536 : }
5537 : const double dfInitialMaxLengthInPixels =
5538 40 : GetMaximumSegmentLength(poMultiPolygon.get());
5539 :
5540 40 : CPLPushErrorHandler(CPLQuietErrorHandler);
5541 : const bool bWasValidInitially =
5542 40 : ValidateCutline(poMultiPolygon.get(), false);
5543 40 : CPLPopErrorHandler();
5544 40 : if (!bWasValidInitially)
5545 : {
5546 3 : CPLDebug("WARP", "Cutline is not valid after initial reprojection");
5547 3 : char *pszWKT = nullptr;
5548 3 : poMultiPolygon->exportToWkt(&pszWKT);
5549 3 : CPLDebug("GDALWARP", "WKT = \"%s\"", pszWKT ? pszWKT : "(null)");
5550 3 : CPLFree(pszWKT);
5551 : }
5552 :
5553 40 : bool bDensify = false;
5554 40 : if (bMayNeedDensify && eErr == OGRERR_NONE &&
5555 : dfInitialMaxLengthInPixels > 1.0)
5556 : {
5557 : const char *pszDensifyCutline =
5558 16 : CPLGetConfigOption("GDALWARP_DENSIFY_CUTLINE", "YES");
5559 16 : if (EQUAL(pszDensifyCutline, "ONLY_IF_INVALID"))
5560 : {
5561 1 : bDensify = (OGRGeometryFactory::haveGEOS() && !bWasValidInitially);
5562 : }
5563 15 : else if (CSLFetchNameValue(*ppapszWarpOptions, "CUTLINE_BLEND_DIST") !=
5564 15 : nullptr &&
5565 0 : CPLGetConfigOption("GDALWARP_DENSIFY_CUTLINE", nullptr) ==
5566 : nullptr)
5567 : {
5568 : // TODO: we should only emit this message if a
5569 : // transform/reprojection will be actually done
5570 0 : CPLDebug("WARP",
5571 : "Densification of cutline could perhaps be useful but as "
5572 : "CUTLINE_BLEND_DIST is used, this could be very slow. So "
5573 : "disabled "
5574 : "unless GDALWARP_DENSIFY_CUTLINE=YES is explicitly "
5575 : "specified as configuration option");
5576 : }
5577 : else
5578 : {
5579 15 : bDensify = CPLTestBool(pszDensifyCutline);
5580 : }
5581 : }
5582 40 : if (bDensify)
5583 : {
5584 15 : CPLDebug("WARP",
5585 : "Cutline maximum segment size was %.0f pixel after "
5586 : "reprojection to source coordinates.",
5587 : dfInitialMaxLengthInPixels);
5588 :
5589 : // Densify and reproject with the aim of having a 1 pixel density
5590 15 : double dfSegmentSize =
5591 : dfMaxLengthInSpatUnits / dfInitialMaxLengthInPixels;
5592 15 : const int MAX_ITERATIONS = 10;
5593 16 : for (int i = 0; i < MAX_ITERATIONS; i++)
5594 : {
5595 16 : poMultiPolygon.reset(poCutline->clone());
5596 16 : poMultiPolygon->segmentize(dfSegmentSize);
5597 16 : if (i == MAX_ITERATIONS - 1)
5598 : {
5599 0 : char *pszWKT = nullptr;
5600 0 : poMultiPolygon->exportToWkt(&pszWKT);
5601 0 : CPLDebug("WARP",
5602 : "WKT of polygon after densification with segment size "
5603 : "= %f: %s",
5604 : dfSegmentSize, pszWKT);
5605 0 : CPLFree(pszWKT);
5606 : }
5607 16 : eErr = OGRERR_NONE;
5608 16 : if (poCTCutlineToSrc)
5609 : {
5610 12 : poMultiPolygon.reset(OGRGeometryFactory::transformWithOptions(
5611 6 : poMultiPolygon.get(), poCTCutlineToSrc.get(), nullptr));
5612 6 : if (!poMultiPolygon)
5613 : {
5614 0 : eErr = OGRERR_FAILURE;
5615 0 : break;
5616 : }
5617 : }
5618 16 : if (poMultiPolygon->transform(&oTransformer) != OGRERR_NONE)
5619 0 : eErr = OGRERR_FAILURE;
5620 16 : if (eErr == OGRERR_NONE)
5621 : {
5622 : const double dfMaxLengthInPixels =
5623 16 : GetMaximumSegmentLength(poMultiPolygon.get());
5624 16 : if (bWasValidInitially)
5625 : {
5626 : // In some cases, the densification itself results in a
5627 : // reprojected invalid polygon due to the non-linearity of
5628 : // RPC DEM transformation, so in those cases, try a less
5629 : // dense cutline
5630 14 : CPLPushErrorHandler(CPLQuietErrorHandler);
5631 : const bool bIsValid =
5632 14 : ValidateCutline(poMultiPolygon.get(), false);
5633 14 : CPLPopErrorHandler();
5634 14 : if (!bIsValid)
5635 : {
5636 1 : if (i == MAX_ITERATIONS - 1)
5637 : {
5638 0 : char *pszWKT = nullptr;
5639 0 : poMultiPolygon->exportToWkt(&pszWKT);
5640 0 : CPLDebug("WARP",
5641 : "After densification, cutline maximum "
5642 : "segment size is now %.0f pixel, "
5643 : "but cutline is invalid. %s",
5644 : dfMaxLengthInPixels, pszWKT);
5645 0 : CPLFree(pszWKT);
5646 0 : break;
5647 : }
5648 1 : CPLDebug("WARP",
5649 : "After densification, cutline maximum segment "
5650 : "size is now %.0f pixel, "
5651 : "but cutline is invalid. So trying a less "
5652 : "dense cutline.",
5653 : dfMaxLengthInPixels);
5654 1 : dfSegmentSize *= 2;
5655 1 : continue;
5656 : }
5657 : }
5658 15 : CPLDebug("WARP",
5659 : "After densification, cutline maximum segment size is "
5660 : "now %.0f pixel.",
5661 : dfMaxLengthInPixels);
5662 : }
5663 15 : break;
5664 : }
5665 : }
5666 :
5667 40 : if (eErr == OGRERR_FAILURE)
5668 : {
5669 0 : if (CPLTestBool(
5670 : CPLGetConfigOption("GDALWARP_IGNORE_BAD_CUTLINE", "NO")))
5671 0 : CPLError(CE_Warning, CPLE_AppDefined,
5672 : "Cutline transformation failed");
5673 : else
5674 : {
5675 0 : CPLError(CE_Failure, CPLE_AppDefined,
5676 : "Cutline transformation failed");
5677 0 : return CE_Failure;
5678 : }
5679 : }
5680 40 : else if (!ValidateCutline(poMultiPolygon.get(), true))
5681 : {
5682 1 : return CE_Failure;
5683 : }
5684 :
5685 : // Optimization: if the cutline contains the footprint of the source
5686 : // dataset, no need to use the cutline.
5687 39 : if (OGRGeometryFactory::haveGEOS()
5688 : #ifdef DEBUG
5689 : // Env var just for debugging purposes
5690 39 : && !CPLTestBool(CPLGetConfigOption(
5691 : "GDALWARP_SKIP_CUTLINE_CONTAINMENT_TEST", "NO"))
5692 : #endif
5693 : )
5694 : {
5695 38 : const double dfCutlineBlendDist = CPLAtof(CSLFetchNameValueDef(
5696 : *ppapszWarpOptions, "CUTLINE_BLEND_DIST", "0"));
5697 38 : auto poRing = std::make_unique<OGRLinearRing>();
5698 38 : poRing->addPoint(-dfCutlineBlendDist, -dfCutlineBlendDist);
5699 38 : poRing->addPoint(-dfCutlineBlendDist,
5700 38 : dfCutlineBlendDist + poSrcDS->GetRasterYSize());
5701 76 : poRing->addPoint(dfCutlineBlendDist + poSrcDS->GetRasterXSize(),
5702 38 : dfCutlineBlendDist + poSrcDS->GetRasterYSize());
5703 38 : poRing->addPoint(dfCutlineBlendDist + poSrcDS->GetRasterXSize(),
5704 : -dfCutlineBlendDist);
5705 38 : poRing->addPoint(-dfCutlineBlendDist, -dfCutlineBlendDist);
5706 38 : OGRPolygon oSrcDSFootprint;
5707 38 : oSrcDSFootprint.addRing(std::move(poRing));
5708 38 : OGREnvelope sSrcDSEnvelope;
5709 38 : oSrcDSFootprint.getEnvelope(&sSrcDSEnvelope);
5710 38 : OGREnvelope sCutlineEnvelope;
5711 38 : poMultiPolygon->getEnvelope(&sCutlineEnvelope);
5712 43 : if (sCutlineEnvelope.Contains(sSrcDSEnvelope) &&
5713 5 : poMultiPolygon->Contains(&oSrcDSFootprint))
5714 : {
5715 2 : CPLDebug("WARP", "Source dataset fully contained within cutline.");
5716 2 : return CE_None;
5717 : }
5718 : }
5719 :
5720 : /* -------------------------------------------------------------------- */
5721 : /* Convert aggregate geometry into WKT. */
5722 : /* -------------------------------------------------------------------- */
5723 37 : char *pszWKT = nullptr;
5724 37 : poMultiPolygon->exportToWkt(&pszWKT);
5725 : // fprintf(stderr, "WKT = \"%s\"\n", pszWKT ? pszWKT : "(null)");
5726 :
5727 37 : *ppapszWarpOptions = CSLSetNameValue(*ppapszWarpOptions, "CUTLINE", pszWKT);
5728 37 : CPLFree(pszWKT);
5729 37 : return CE_None;
5730 : }
5731 :
5732 47 : static void RemoveConflictingMetadata(GDALMajorObjectH hObj,
5733 : CSLConstList papszSrcMetadata,
5734 : const char *pszValueConflict)
5735 : {
5736 47 : if (hObj == nullptr)
5737 0 : return;
5738 :
5739 38 : for (const auto &[pszKey, pszValue] :
5740 85 : cpl::IterateNameValue(papszSrcMetadata))
5741 : {
5742 19 : const char *pszValueComp = GDALGetMetadataItem(hObj, pszKey, nullptr);
5743 19 : if (pszValueComp == nullptr || (!EQUAL(pszValue, pszValueComp) &&
5744 2 : !EQUAL(pszValueComp, pszValueConflict)))
5745 : {
5746 3 : if (STARTS_WITH(pszKey, "STATISTICS_"))
5747 1 : GDALSetMetadataItem(hObj, pszKey, nullptr, nullptr);
5748 : else
5749 2 : GDALSetMetadataItem(hObj, pszKey, pszValueConflict, nullptr);
5750 : }
5751 : }
5752 : }
5753 :
5754 : /************************************************************************/
5755 : /* IsValidSRS */
5756 : /************************************************************************/
5757 :
5758 315 : static bool IsValidSRS(const char *pszUserInput)
5759 :
5760 : {
5761 : OGRSpatialReferenceH hSRS;
5762 315 : bool bRes = true;
5763 :
5764 315 : hSRS = OSRNewSpatialReference(nullptr);
5765 315 : if (OSRSetFromUserInput(hSRS, pszUserInput) != OGRERR_NONE)
5766 : {
5767 0 : bRes = false;
5768 : }
5769 :
5770 315 : OSRDestroySpatialReference(hSRS);
5771 :
5772 315 : return bRes;
5773 : }
5774 :
5775 : /************************************************************************/
5776 : /* GDALWarpAppOptionsGetParser() */
5777 : /************************************************************************/
5778 :
5779 : static std::unique_ptr<GDALArgumentParser>
5780 1168 : GDALWarpAppOptionsGetParser(GDALWarpAppOptions *psOptions,
5781 : GDALWarpAppOptionsForBinary *psOptionsForBinary)
5782 : {
5783 : auto argParser = std::make_unique<GDALArgumentParser>(
5784 1168 : "gdalwarp", /* bForBinary=*/psOptionsForBinary != nullptr);
5785 :
5786 1168 : argParser->add_description(_("Image reprojection and warping utility."));
5787 :
5788 1168 : argParser->add_epilog(
5789 1168 : _("For more details, consult https://gdal.org/programs/gdalwarp.html"));
5790 :
5791 : argParser->add_quiet_argument(
5792 1168 : psOptionsForBinary ? &psOptionsForBinary->bQuiet : nullptr);
5793 :
5794 1168 : argParser->add_argument("-overwrite")
5795 1168 : .flag()
5796 : .action(
5797 90 : [psOptionsForBinary](const std::string &)
5798 : {
5799 49 : if (psOptionsForBinary)
5800 41 : psOptionsForBinary->bOverwrite = true;
5801 1168 : })
5802 1168 : .help(_("Overwrite the target dataset if it already exists."));
5803 :
5804 1168 : argParser->add_output_format_argument(psOptions->osFormat);
5805 :
5806 1168 : argParser->add_argument("-co")
5807 2336 : .metavar("<NAME>=<VALUE>")
5808 1168 : .append()
5809 : .action(
5810 297 : [psOptions, psOptionsForBinary](const std::string &s)
5811 : {
5812 145 : psOptions->aosCreateOptions.AddString(s.c_str());
5813 145 : psOptions->bCreateOutput = true;
5814 :
5815 145 : if (psOptionsForBinary)
5816 7 : psOptionsForBinary->aosCreateOptions.AddString(s.c_str());
5817 1168 : })
5818 1168 : .help(_("Creation option(s)."));
5819 :
5820 1168 : argParser->add_argument("-s_srs")
5821 2336 : .metavar("<srs_def>")
5822 : .action(
5823 60 : [psOptions](const std::string &s)
5824 : {
5825 30 : if (!IsValidSRS(s.c_str()))
5826 : {
5827 0 : throw std::invalid_argument("Invalid SRS for -s_srs");
5828 : }
5829 : psOptions->aosTransformerOptions.SetNameValue("SRC_SRS",
5830 30 : s.c_str());
5831 1198 : })
5832 1168 : .help(_("Set source spatial reference."));
5833 :
5834 1168 : argParser->add_argument("-t_srs")
5835 2336 : .metavar("<srs_def>")
5836 : .action(
5837 552 : [psOptions](const std::string &s)
5838 : {
5839 276 : if (!IsValidSRS(s.c_str()))
5840 : {
5841 0 : throw std::invalid_argument("Invalid SRS for -t_srs");
5842 : }
5843 : psOptions->aosTransformerOptions.SetNameValue("DST_SRS",
5844 276 : s.c_str());
5845 1444 : })
5846 1168 : .help(_("Set target spatial reference."));
5847 :
5848 : {
5849 1168 : auto &group = argParser->add_mutually_exclusive_group();
5850 1168 : group.add_argument("-srcalpha")
5851 1168 : .flag()
5852 1168 : .store_into(psOptions->bEnableSrcAlpha)
5853 : .help(_("Force the last band of a source image to be considered as "
5854 1168 : "a source alpha band."));
5855 1168 : group.add_argument("-nosrcalpha")
5856 1168 : .flag()
5857 1168 : .store_into(psOptions->bDisableSrcAlpha)
5858 : .help(_("Prevent the alpha band of a source image to be considered "
5859 1168 : "as such."));
5860 : }
5861 :
5862 1168 : argParser->add_argument("-dstalpha")
5863 1168 : .flag()
5864 1168 : .store_into(psOptions->bEnableDstAlpha)
5865 : .help(_("Create an output alpha band to identify nodata "
5866 1168 : "(unset/transparent) pixels."));
5867 :
5868 : // Parsing of that option is done in a preprocessing stage
5869 1168 : argParser->add_argument("-tr")
5870 2336 : .metavar("<xres> <yres>|square")
5871 1168 : .help(_("Target resolution."));
5872 :
5873 1168 : argParser->add_argument("-ts")
5874 2336 : .metavar("<width> <height>")
5875 1168 : .nargs(2)
5876 1168 : .scan<'i', int>()
5877 1168 : .help(_("Set output file size in pixels and lines."));
5878 :
5879 1168 : argParser->add_argument("-te")
5880 2336 : .metavar("<xmin> <ymin> <xmax> <ymax>")
5881 1168 : .nargs(4)
5882 1168 : .scan<'g', double>()
5883 1168 : .help(_("Set georeferenced extents of output file to be created."));
5884 :
5885 1168 : argParser->add_argument("-te_srs")
5886 2336 : .metavar("<srs_def>")
5887 : .action(
5888 18 : [psOptions](const std::string &s)
5889 : {
5890 6 : if (!IsValidSRS(s.c_str()))
5891 : {
5892 0 : throw std::invalid_argument("Invalid SRS for -te_srs");
5893 : }
5894 6 : psOptions->osTE_SRS = s;
5895 6 : psOptions->bCreateOutput = true;
5896 1174 : })
5897 1168 : .help(_("Set source spatial reference."));
5898 :
5899 1168 : argParser->add_argument("-r")
5900 : .metavar("near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|"
5901 2336 : "max|med|q1|q3|sum")
5902 : .action(
5903 1387 : [psOptions](const std::string &s)
5904 : {
5905 694 : GDALGetWarpResampleAlg(s.c_str(), psOptions->eResampleAlg,
5906 : /*bThrow=*/true);
5907 693 : psOptions->bResampleAlgSpecifiedByUser = true;
5908 1168 : })
5909 1168 : .help(_("Resampling method to use."));
5910 :
5911 1168 : argParser->add_output_type_argument(psOptions->eOutputType);
5912 :
5913 : ///////////////////////////////////////////////////////////////////////
5914 1168 : argParser->add_group("Advanced options");
5915 :
5916 32 : const auto CheckSingleMethod = [psOptions]()
5917 : {
5918 : const char *pszMethod =
5919 16 : FetchSrcMethod(psOptions->aosTransformerOptions);
5920 16 : if (pszMethod)
5921 0 : CPLError(CE_Warning, CPLE_IllegalArg,
5922 : "Warning: only one METHOD can be used. Method %s is "
5923 : "already defined.",
5924 : pszMethod);
5925 : const char *pszMAX_GCP_ORDER =
5926 16 : psOptions->aosTransformerOptions.FetchNameValue("MAX_GCP_ORDER");
5927 16 : if (pszMAX_GCP_ORDER)
5928 0 : CPLError(CE_Warning, CPLE_IllegalArg,
5929 : "Warning: only one METHOD can be used. -order %s "
5930 : "option was specified, so it is likely that "
5931 : "GCP_POLYNOMIAL was implied.",
5932 : pszMAX_GCP_ORDER);
5933 1184 : };
5934 :
5935 1168 : argParser->add_argument("-wo")
5936 2336 : .metavar("<NAME>=<VALUE>")
5937 1168 : .append()
5938 217 : .action([psOptions](const std::string &s)
5939 1385 : { psOptions->aosWarpOptions.AddString(s.c_str()); })
5940 1168 : .help(_("Warping option(s)."));
5941 :
5942 1168 : argParser->add_argument("-multi")
5943 1168 : .flag()
5944 1168 : .store_into(psOptions->bMulti)
5945 1168 : .help(_("Multithreaded input/output."));
5946 :
5947 1168 : argParser->add_argument("-s_coord_epoch")
5948 2336 : .metavar("<epoch>")
5949 : .action(
5950 0 : [psOptions](const std::string &s)
5951 : {
5952 : psOptions->aosTransformerOptions.SetNameValue(
5953 0 : "SRC_COORDINATE_EPOCH", s.c_str());
5954 1168 : })
5955 : .help(_("Assign a coordinate epoch, linked with the source SRS when "
5956 1168 : "-s_srs is used."));
5957 :
5958 1168 : argParser->add_argument("-t_coord_epoch")
5959 2336 : .metavar("<epoch>")
5960 : .action(
5961 0 : [psOptions](const std::string &s)
5962 : {
5963 : psOptions->aosTransformerOptions.SetNameValue(
5964 0 : "DST_COORDINATE_EPOCH", s.c_str());
5965 1168 : })
5966 : .help(_("Assign a coordinate epoch, linked with the output SRS when "
5967 1168 : "-t_srs is used."));
5968 :
5969 1168 : argParser->add_argument("-ct")
5970 2336 : .metavar("<string>")
5971 : .action(
5972 4 : [psOptions](const std::string &s)
5973 : {
5974 : psOptions->aosTransformerOptions.SetNameValue(
5975 4 : "COORDINATE_OPERATION", s.c_str());
5976 1168 : })
5977 1168 : .help(_("Set a coordinate transformation."));
5978 :
5979 : {
5980 1168 : auto &group = argParser->add_mutually_exclusive_group();
5981 1168 : group.add_argument("-tps")
5982 1168 : .flag()
5983 : .action(
5984 10 : [psOptions, CheckSingleMethod](const std::string &)
5985 : {
5986 5 : CheckSingleMethod();
5987 : psOptions->aosTransformerOptions.SetNameValue("SRC_METHOD",
5988 5 : "GCP_TPS");
5989 1168 : })
5990 : .help(_("Force use of thin plate spline transformer based on "
5991 1168 : "available GCPs."));
5992 :
5993 1168 : group.add_argument("-rpc")
5994 1168 : .flag()
5995 : .action(
5996 4 : [psOptions, CheckSingleMethod](const std::string &)
5997 : {
5998 2 : CheckSingleMethod();
5999 : psOptions->aosTransformerOptions.SetNameValue("SRC_METHOD",
6000 2 : "RPC");
6001 1168 : })
6002 1168 : .help(_("Force use of RPCs."));
6003 :
6004 1168 : group.add_argument("-geoloc")
6005 1168 : .flag()
6006 : .action(
6007 18 : [psOptions, CheckSingleMethod](const std::string &)
6008 : {
6009 9 : CheckSingleMethod();
6010 : psOptions->aosTransformerOptions.SetNameValue(
6011 9 : "SRC_METHOD", "GEOLOC_ARRAY");
6012 1168 : })
6013 1168 : .help(_("Force use of Geolocation Arrays."));
6014 : }
6015 :
6016 1168 : argParser->add_argument("-order")
6017 2336 : .metavar("<1|2|3>")
6018 1168 : .choices("1", "2", "3")
6019 : .action(
6020 0 : [psOptions](const std::string &s)
6021 : {
6022 : const char *pszMethod =
6023 0 : FetchSrcMethod(psOptions->aosTransformerOptions);
6024 0 : if (pszMethod)
6025 0 : CPLError(
6026 : CE_Warning, CPLE_IllegalArg,
6027 : "Warning: only one METHOD can be used. Method %s is "
6028 : "already defined",
6029 : pszMethod);
6030 : psOptions->aosTransformerOptions.SetNameValue("MAX_GCP_ORDER",
6031 0 : s.c_str());
6032 1168 : })
6033 1168 : .help(_("Order of polynomial used for GCP warping."));
6034 :
6035 : // Parsing of that option is done in a preprocessing stage
6036 1168 : argParser->add_argument("-refine_gcps")
6037 2336 : .metavar("<tolerance> [<minimum_gcps>]")
6038 1168 : .help(_("Refines the GCPs by automatically eliminating outliers."));
6039 :
6040 1168 : argParser->add_argument("-to")
6041 2336 : .metavar("<NAME>=<VALUE>")
6042 1168 : .append()
6043 64 : .action([psOptions](const std::string &s)
6044 1232 : { psOptions->aosTransformerOptions.AddString(s.c_str()); })
6045 1168 : .help(_("Transform option(s)."));
6046 :
6047 1168 : argParser->add_argument("-et")
6048 2336 : .metavar("<err_threshold>")
6049 1168 : .store_into(psOptions->dfErrorThreshold)
6050 : .action(
6051 26 : [psOptions](const std::string &)
6052 : {
6053 13 : if (psOptions->dfErrorThreshold < 0)
6054 : {
6055 : throw std::invalid_argument(
6056 0 : "Invalid value for error threshold");
6057 : }
6058 : psOptions->aosWarpOptions.AddString(CPLSPrintf(
6059 13 : "ERROR_THRESHOLD=%.16g", psOptions->dfErrorThreshold));
6060 1181 : })
6061 1168 : .help(_("Error threshold."));
6062 :
6063 1168 : argParser->add_argument("-wm")
6064 2336 : .metavar("<memory_in_mb>")
6065 : .action(
6066 74 : [psOptions](const std::string &s)
6067 : {
6068 38 : bool bUnitSpecified = false;
6069 : GIntBig nBytes;
6070 38 : if (CPLParseMemorySize(s.c_str(), &nBytes, &bUnitSpecified) ==
6071 : CE_None)
6072 : {
6073 36 : if (!bUnitSpecified && nBytes < 10000)
6074 : {
6075 7 : nBytes *= (1024 * 1024);
6076 : }
6077 36 : psOptions->dfWarpMemoryLimit = static_cast<double>(nBytes);
6078 : }
6079 : else
6080 : {
6081 2 : throw std::invalid_argument("Failed to parse value of -wm");
6082 : }
6083 1204 : })
6084 1168 : .help(_("Set max warp memory."));
6085 :
6086 1168 : argParser->add_argument("-srcnodata")
6087 2336 : .metavar("\"<value>[ <value>]...\"")
6088 1168 : .store_into(psOptions->osSrcNodata)
6089 1168 : .help(_("Nodata masking values for input bands."));
6090 :
6091 1168 : argParser->add_argument("-dstnodata")
6092 2336 : .metavar("\"<value>[ <value>]...\"")
6093 1168 : .store_into(psOptions->osDstNodata)
6094 1168 : .help(_("Nodata masking values for output bands."));
6095 :
6096 1168 : argParser->add_argument("-tap")
6097 1168 : .flag()
6098 1168 : .store_into(psOptions->bTargetAlignedPixels)
6099 1168 : .help(_("Force target aligned pixels."));
6100 :
6101 1168 : argParser->add_argument("-wt")
6102 2336 : .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
6103 : .action(
6104 0 : [psOptions](const std::string &s)
6105 : {
6106 0 : psOptions->eWorkingType = GDALGetDataTypeByName(s.c_str());
6107 0 : if (psOptions->eWorkingType == GDT_Unknown)
6108 : {
6109 : throw std::invalid_argument(
6110 0 : std::string("Unknown output pixel type: ").append(s));
6111 : }
6112 1168 : })
6113 1168 : .help(_("Working data type."));
6114 :
6115 : // Non-documented alias of -r nearest
6116 1168 : argParser->add_argument("-rn")
6117 1168 : .flag()
6118 1168 : .hidden()
6119 1 : .action([psOptions](const std::string &)
6120 1168 : { psOptions->eResampleAlg = GRA_NearestNeighbour; })
6121 1168 : .help(_("Nearest neighbour resampling."));
6122 :
6123 : // Non-documented alias of -r bilinear
6124 1168 : argParser->add_argument("-rb")
6125 1168 : .flag()
6126 1168 : .hidden()
6127 2 : .action([psOptions](const std::string &)
6128 1168 : { psOptions->eResampleAlg = GRA_Bilinear; })
6129 1168 : .help(_("Bilinear resampling."));
6130 :
6131 : // Non-documented alias of -r cubic
6132 1168 : argParser->add_argument("-rc")
6133 1168 : .flag()
6134 1168 : .hidden()
6135 1 : .action([psOptions](const std::string &)
6136 1168 : { psOptions->eResampleAlg = GRA_Cubic; })
6137 1168 : .help(_("Cubic resampling."));
6138 :
6139 : // Non-documented alias of -r cubicspline
6140 1168 : argParser->add_argument("-rcs")
6141 1168 : .flag()
6142 1168 : .hidden()
6143 1 : .action([psOptions](const std::string &)
6144 1168 : { psOptions->eResampleAlg = GRA_CubicSpline; })
6145 1168 : .help(_("Cubic spline resampling."));
6146 :
6147 : // Non-documented alias of -r lanczos
6148 1168 : argParser->add_argument("-rl")
6149 1168 : .flag()
6150 1168 : .hidden()
6151 0 : .action([psOptions](const std::string &)
6152 1168 : { psOptions->eResampleAlg = GRA_Lanczos; })
6153 1168 : .help(_("Lanczos resampling."));
6154 :
6155 : // Non-documented alias of -r average
6156 1168 : argParser->add_argument("-ra")
6157 1168 : .flag()
6158 1168 : .hidden()
6159 0 : .action([psOptions](const std::string &)
6160 1168 : { psOptions->eResampleAlg = GRA_Average; })
6161 1168 : .help(_("Average resampling."));
6162 :
6163 : // Non-documented alias of -r rms
6164 1168 : argParser->add_argument("-rrms")
6165 1168 : .flag()
6166 1168 : .hidden()
6167 0 : .action([psOptions](const std::string &)
6168 1168 : { psOptions->eResampleAlg = GRA_RMS; })
6169 1168 : .help(_("RMS resampling."));
6170 :
6171 : // Non-documented alias of -r mode
6172 1168 : argParser->add_argument("-rm")
6173 1168 : .flag()
6174 1168 : .hidden()
6175 0 : .action([psOptions](const std::string &)
6176 1168 : { psOptions->eResampleAlg = GRA_Mode; })
6177 1168 : .help(_("Mode resampling."));
6178 :
6179 1168 : argParser->add_argument("-cutline")
6180 2336 : .metavar("<datasource>|<WKT>")
6181 1168 : .store_into(psOptions->osCutlineDSNameOrWKT)
6182 : .help(_("Enable use of a blend cutline from the name of a vector "
6183 1168 : "dataset or a WKT geometry."));
6184 :
6185 1168 : argParser->add_argument("-cutline_srs")
6186 2336 : .metavar("<srs_def>")
6187 : .action(
6188 6 : [psOptions](const std::string &s)
6189 : {
6190 3 : if (!IsValidSRS(s.c_str()))
6191 : {
6192 0 : throw std::invalid_argument("Invalid SRS for -cutline_srs");
6193 : }
6194 3 : psOptions->osCutlineSRS = s;
6195 1171 : })
6196 1168 : .help(_("Sets/overrides cutline SRS."));
6197 :
6198 1168 : argParser->add_argument("-cwhere")
6199 2336 : .metavar("<expression>")
6200 1168 : .store_into(psOptions->osCWHERE)
6201 1168 : .help(_("Restrict desired cutline features based on attribute query."));
6202 :
6203 : {
6204 1168 : auto &group = argParser->add_mutually_exclusive_group();
6205 1168 : group.add_argument("-cl")
6206 2336 : .metavar("<layername>")
6207 1168 : .store_into(psOptions->osCLayer)
6208 1168 : .help(_("Select the named layer from the cutline datasource."));
6209 :
6210 1168 : group.add_argument("-csql")
6211 2336 : .metavar("<query>")
6212 1168 : .store_into(psOptions->osCSQL)
6213 1168 : .help(_("Select cutline features using an SQL query."));
6214 : }
6215 :
6216 1168 : argParser->add_argument("-cblend")
6217 2336 : .metavar("<distance>")
6218 : .action(
6219 0 : [psOptions](const std::string &s)
6220 : {
6221 : psOptions->aosWarpOptions.SetNameValue("CUTLINE_BLEND_DIST",
6222 0 : s.c_str());
6223 1168 : })
6224 : .help(_(
6225 1168 : "Set a blend distance to use to blend over cutlines (in pixels)."));
6226 :
6227 1168 : argParser->add_argument("-crop_to_cutline")
6228 1168 : .flag()
6229 : .action(
6230 18 : [psOptions](const std::string &)
6231 : {
6232 18 : psOptions->bCropToCutline = true;
6233 18 : psOptions->bCreateOutput = true;
6234 1168 : })
6235 : .help(_("Crop the extent of the target dataset to the extent of the "
6236 1168 : "cutline."));
6237 :
6238 1168 : argParser->add_argument("-nomd")
6239 1168 : .flag()
6240 : .action(
6241 0 : [psOptions](const std::string &)
6242 : {
6243 0 : psOptions->bCopyMetadata = false;
6244 0 : psOptions->bCopyBandInfo = false;
6245 1168 : })
6246 1168 : .help(_("Do not copy metadata."));
6247 :
6248 1168 : argParser->add_argument("-cvmd")
6249 2336 : .metavar("<meta_conflict_value>")
6250 1168 : .store_into(psOptions->osMDConflictValue)
6251 : .help(_("Value to set metadata items that conflict between source "
6252 1168 : "datasets."));
6253 :
6254 1168 : argParser->add_argument("-setci")
6255 1168 : .flag()
6256 1168 : .store_into(psOptions->bSetColorInterpretation)
6257 : .help(_("Set the color interpretation of the bands of the target "
6258 1168 : "dataset from the source dataset."));
6259 :
6260 : argParser->add_open_options_argument(
6261 1168 : psOptionsForBinary ? &(psOptionsForBinary->aosOpenOptions) : nullptr);
6262 :
6263 1168 : argParser->add_argument("-doo")
6264 2336 : .metavar("<NAME>=<VALUE>")
6265 1168 : .append()
6266 : .action(
6267 0 : [psOptionsForBinary](const std::string &s)
6268 : {
6269 0 : if (psOptionsForBinary)
6270 0 : psOptionsForBinary->aosDestOpenOptions.AddString(s.c_str());
6271 1168 : })
6272 1168 : .help(_("Open option(s) for output dataset."));
6273 :
6274 1168 : argParser->add_argument("-ovr")
6275 2336 : .metavar("<level>|AUTO|AUTO-<n>|NONE")
6276 : .action(
6277 24 : [psOptions](const std::string &s)
6278 : {
6279 12 : const char *pszOvLevel = s.c_str();
6280 12 : if (EQUAL(pszOvLevel, "AUTO"))
6281 1 : psOptions->nOvLevel = OVR_LEVEL_AUTO;
6282 11 : else if (STARTS_WITH_CI(pszOvLevel, "AUTO-"))
6283 1 : psOptions->nOvLevel =
6284 1 : OVR_LEVEL_AUTO - atoi(pszOvLevel + strlen("AUTO-"));
6285 10 : else if (EQUAL(pszOvLevel, "NONE"))
6286 5 : psOptions->nOvLevel = OVR_LEVEL_NONE;
6287 5 : else if (CPLGetValueType(pszOvLevel) == CPL_VALUE_INTEGER)
6288 5 : psOptions->nOvLevel = atoi(pszOvLevel);
6289 : else
6290 : {
6291 : throw std::invalid_argument(CPLSPrintf(
6292 0 : "Invalid value '%s' for -ov option", pszOvLevel));
6293 : }
6294 1180 : })
6295 1168 : .help(_("Specify which overview level of source files must be used."));
6296 :
6297 : {
6298 1168 : auto &group = argParser->add_mutually_exclusive_group();
6299 1168 : group.add_argument("-vshift")
6300 1168 : .flag()
6301 1168 : .store_into(psOptions->bVShift)
6302 1168 : .help(_("Force the use of vertical shift."));
6303 1168 : group.add_argument("-novshift", "-novshiftgrid")
6304 1168 : .flag()
6305 1168 : .store_into(psOptions->bNoVShift)
6306 1168 : .help(_("Disable the use of vertical shift."));
6307 : }
6308 :
6309 : argParser->add_input_format_argument(
6310 : psOptionsForBinary ? &psOptionsForBinary->aosAllowedInputDrivers
6311 1168 : : nullptr);
6312 :
6313 1168 : argParser->add_argument("-b", "-srcband")
6314 2336 : .metavar("<band>")
6315 1168 : .append()
6316 1168 : .store_into(psOptions->anSrcBands)
6317 1168 : .help(_("Specify input band(s) number to warp."));
6318 :
6319 1168 : argParser->add_argument("-dstband")
6320 2336 : .metavar("<band>")
6321 1168 : .append()
6322 1168 : .store_into(psOptions->anDstBands)
6323 1168 : .help(_("Specify the output band number in which to warp."));
6324 :
6325 1168 : if (psOptionsForBinary)
6326 : {
6327 92 : argParser->add_argument("src_dataset_name")
6328 184 : .metavar("<src_dataset_name>")
6329 92 : .nargs(argparse::nargs_pattern::at_least_one)
6330 93 : .action([psOptionsForBinary](const std::string &s)
6331 185 : { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
6332 92 : .help(_("Input dataset(s)."));
6333 :
6334 92 : argParser->add_argument("dst_dataset_name")
6335 184 : .metavar("<dst_dataset_name>")
6336 92 : .store_into(psOptionsForBinary->osDstFilename)
6337 92 : .help(_("Output dataset."));
6338 : }
6339 :
6340 2336 : return argParser;
6341 : }
6342 :
6343 : /************************************************************************/
6344 : /* GDALWarpAppGetParserUsage() */
6345 : /************************************************************************/
6346 :
6347 2 : std::string GDALWarpAppGetParserUsage()
6348 : {
6349 : try
6350 : {
6351 4 : GDALWarpAppOptions sOptions;
6352 4 : GDALWarpAppOptionsForBinary sOptionsForBinary;
6353 : auto argParser =
6354 4 : GDALWarpAppOptionsGetParser(&sOptions, &sOptionsForBinary);
6355 2 : return argParser->usage();
6356 : }
6357 0 : catch (const std::exception &err)
6358 : {
6359 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
6360 0 : err.what());
6361 0 : return std::string();
6362 : }
6363 : }
6364 :
6365 : /************************************************************************/
6366 : /* GDALWarpAppOptionsNew() */
6367 : /************************************************************************/
6368 :
6369 : #ifndef CheckHasEnoughAdditionalArgs_defined
6370 : #define CheckHasEnoughAdditionalArgs_defined
6371 :
6372 113 : static bool CheckHasEnoughAdditionalArgs(CSLConstList papszArgv, int i,
6373 : int nExtraArg, int nArgc)
6374 : {
6375 113 : if (i + nExtraArg >= nArgc)
6376 : {
6377 0 : CPLError(CE_Failure, CPLE_IllegalArg,
6378 0 : "%s option requires %d argument%s", papszArgv[i], nExtraArg,
6379 : nExtraArg == 1 ? "" : "s");
6380 0 : return false;
6381 : }
6382 113 : return true;
6383 : }
6384 : #endif
6385 :
6386 : #define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \
6387 : if (!CheckHasEnoughAdditionalArgs(papszArgv, i, nExtraArg, nArgc)) \
6388 : { \
6389 : return nullptr; \
6390 : }
6391 :
6392 : /**
6393 : * Allocates a GDALWarpAppOptions struct.
6394 : *
6395 : * @param papszArgv NULL terminated list of options (potentially including
6396 : * filename and open options too), or NULL. The accepted options are the ones of
6397 : * the <a href="/programs/gdalwarp.html">gdalwarp</a> utility.
6398 : * @param psOptionsForBinary (output) may be NULL (and should generally be
6399 : * NULL), otherwise (gdal_translate_bin.cpp use case) must be allocated with
6400 : * GDALWarpAppOptionsForBinaryNew() prior to this
6401 : * function. Will be filled with potentially present filename, open options,...
6402 : * @return pointer to the allocated GDALWarpAppOptions struct. Must be freed
6403 : * with GDALWarpAppOptionsFree().
6404 : *
6405 : * @since GDAL 2.1
6406 : */
6407 :
6408 : GDALWarpAppOptions *
6409 1166 : GDALWarpAppOptionsNew(char **papszArgv,
6410 : GDALWarpAppOptionsForBinary *psOptionsForBinary)
6411 : {
6412 2332 : auto psOptions = std::make_unique<GDALWarpAppOptions>();
6413 :
6414 1166 : psOptions->aosArgs.Assign(CSLDuplicate(papszArgv), true);
6415 :
6416 : /* -------------------------------------------------------------------- */
6417 : /* Pre-processing for custom syntax that ArgumentParser does not */
6418 : /* support. */
6419 : /* -------------------------------------------------------------------- */
6420 :
6421 2332 : CPLStringList aosArgv;
6422 1166 : const int nArgc = CSLCount(papszArgv);
6423 7938 : for (int i = 0;
6424 7938 : i < nArgc && papszArgv != nullptr && papszArgv[i] != nullptr; i++)
6425 : {
6426 6772 : if (EQUAL(papszArgv[i], "-refine_gcps"))
6427 : {
6428 0 : CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
6429 0 : psOptions->aosTransformerOptions.SetNameValue("REFINE_TOLERANCE",
6430 0 : papszArgv[++i]);
6431 0 : if (CPLAtof(papszArgv[i]) < 0)
6432 : {
6433 0 : CPLError(CE_Failure, CPLE_IllegalArg,
6434 : "The tolerance for -refine_gcps may not be negative.");
6435 0 : return nullptr;
6436 : }
6437 0 : if (i < nArgc - 1 && atoi(papszArgv[i + 1]) >= 0 &&
6438 0 : isdigit(static_cast<unsigned char>(papszArgv[i + 1][0])))
6439 : {
6440 0 : psOptions->aosTransformerOptions.SetNameValue(
6441 0 : "REFINE_MINIMUM_GCPS", papszArgv[++i]);
6442 : }
6443 : else
6444 : {
6445 0 : psOptions->aosTransformerOptions.SetNameValue(
6446 0 : "REFINE_MINIMUM_GCPS", "-1");
6447 : }
6448 : }
6449 6772 : else if (EQUAL(papszArgv[i], "-tr") && i + 1 < nArgc &&
6450 114 : EQUAL(papszArgv[i + 1], "square"))
6451 : {
6452 1 : ++i;
6453 1 : psOptions->bSquarePixels = true;
6454 1 : psOptions->bCreateOutput = true;
6455 : }
6456 6771 : else if (EQUAL(papszArgv[i], "-tr"))
6457 : {
6458 113 : CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(2);
6459 113 : psOptions->dfXRes = CPLAtofM(papszArgv[++i]);
6460 113 : psOptions->dfYRes = fabs(CPLAtofM(papszArgv[++i]));
6461 113 : if (psOptions->dfXRes == 0 || psOptions->dfYRes == 0)
6462 : {
6463 0 : CPLError(CE_Failure, CPLE_IllegalArg,
6464 : "Wrong value for -tr parameters.");
6465 0 : return nullptr;
6466 : }
6467 113 : psOptions->bCreateOutput = true;
6468 : }
6469 : // argparser will be confused if the value of a string argument
6470 : // starts with a negative sign.
6471 6658 : else if (EQUAL(papszArgv[i], "-srcnodata") && i + 1 < nArgc)
6472 : {
6473 48 : ++i;
6474 48 : psOptions->osSrcNodata = papszArgv[i];
6475 : }
6476 : // argparser will be confused if the value of a string argument
6477 : // starts with a negative sign.
6478 6610 : else if (EQUAL(papszArgv[i], "-dstnodata") && i + 1 < nArgc)
6479 : {
6480 140 : ++i;
6481 140 : psOptions->osDstNodata = papszArgv[i];
6482 : }
6483 : else
6484 : {
6485 6470 : aosArgv.AddString(papszArgv[i]);
6486 : }
6487 : }
6488 :
6489 : try
6490 : {
6491 : auto argParser =
6492 2332 : GDALWarpAppOptionsGetParser(psOptions.get(), psOptionsForBinary);
6493 :
6494 1166 : argParser->parse_args_without_binary_name(aosArgv.List());
6495 :
6496 1336 : if (auto oTS = argParser->present<std::vector<int>>("-ts"))
6497 : {
6498 174 : psOptions->nForcePixels = (*oTS)[0];
6499 174 : psOptions->nForceLines = (*oTS)[1];
6500 174 : psOptions->bCreateOutput = true;
6501 : }
6502 :
6503 1347 : if (auto oTE = argParser->present<std::vector<double>>("-te"))
6504 : {
6505 185 : psOptions->dfMinX = (*oTE)[0];
6506 185 : psOptions->dfMinY = (*oTE)[1];
6507 185 : psOptions->dfMaxX = (*oTE)[2];
6508 185 : psOptions->dfMaxY = (*oTE)[3];
6509 185 : psOptions->aosTransformerOptions.SetNameValue(
6510 : "TARGET_EXTENT",
6511 185 : CPLSPrintf("%.17g,%.17g,%.17g,%.17g", psOptions->dfMinX,
6512 370 : psOptions->dfMinY, psOptions->dfMaxX,
6513 185 : psOptions->dfMaxY));
6514 185 : psOptions->bCreateOutput = true;
6515 : }
6516 :
6517 1167 : if (!psOptions->anDstBands.empty() &&
6518 5 : psOptions->anSrcBands.size() != psOptions->anDstBands.size())
6519 : {
6520 1 : CPLError(
6521 : CE_Failure, CPLE_IllegalArg,
6522 : "-srcband should be specified as many times as -dstband is");
6523 1 : return nullptr;
6524 : }
6525 1188 : else if (!psOptions->anSrcBands.empty() &&
6526 27 : psOptions->anDstBands.empty())
6527 : {
6528 60 : for (int i = 0; i < static_cast<int>(psOptions->anSrcBands.size());
6529 : ++i)
6530 : {
6531 37 : psOptions->anDstBands.push_back(i + 1);
6532 : }
6533 : }
6534 :
6535 1667 : if (!psOptions->osFormat.empty() ||
6536 506 : psOptions->eOutputType != GDT_Unknown)
6537 : {
6538 661 : psOptions->bCreateOutput = true;
6539 : }
6540 :
6541 1161 : if (psOptionsForBinary)
6542 88 : psOptionsForBinary->bCreateOutput = psOptions->bCreateOutput;
6543 :
6544 1161 : return psOptions.release();
6545 : }
6546 4 : catch (const std::exception &err)
6547 : {
6548 4 : CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
6549 4 : return nullptr;
6550 : }
6551 : }
6552 :
6553 : /************************************************************************/
6554 : /* GDALWarpAppOptionsFree() */
6555 : /************************************************************************/
6556 :
6557 : /**
6558 : * Frees the GDALWarpAppOptions struct.
6559 : *
6560 : * @param psOptions the options struct for GDALWarp().
6561 : *
6562 : * @since GDAL 2.1
6563 : */
6564 :
6565 1161 : void GDALWarpAppOptionsFree(GDALWarpAppOptions *psOptions)
6566 : {
6567 1161 : delete psOptions;
6568 1161 : }
6569 :
6570 : /************************************************************************/
6571 : /* GDALWarpAppOptionsSetProgress() */
6572 : /************************************************************************/
6573 :
6574 : /**
6575 : * Set a progress function.
6576 : *
6577 : * @param psOptions the options struct for GDALWarp().
6578 : * @param pfnProgress the progress callback.
6579 : * @param pProgressData the user data for the progress callback.
6580 : *
6581 : * @since GDAL 2.1
6582 : */
6583 :
6584 130 : void GDALWarpAppOptionsSetProgress(GDALWarpAppOptions *psOptions,
6585 : GDALProgressFunc pfnProgress,
6586 : void *pProgressData)
6587 : {
6588 130 : psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
6589 130 : psOptions->pProgressData = pProgressData;
6590 130 : if (pfnProgress == GDALTermProgress)
6591 0 : psOptions->bQuiet = false;
6592 130 : }
6593 :
6594 : /************************************************************************/
6595 : /* GDALWarpAppOptionsSetQuiet() */
6596 : /************************************************************************/
6597 :
6598 : /**
6599 : * Set a progress function.
6600 : *
6601 : * @param psOptions the options struct for GDALWarp().
6602 : * @param bQuiet whether GDALWarp() should emit messages on stdout.
6603 : *
6604 : * @since GDAL 2.3
6605 : */
6606 :
6607 81 : void GDALWarpAppOptionsSetQuiet(GDALWarpAppOptions *psOptions, int bQuiet)
6608 : {
6609 81 : psOptions->bQuiet = CPL_TO_BOOL(bQuiet);
6610 81 : }
6611 :
6612 : /************************************************************************/
6613 : /* GDALWarpAppOptionsSetWarpOption() */
6614 : /************************************************************************/
6615 :
6616 : /**
6617 : * Set a warp option
6618 : *
6619 : * @param psOptions the options struct for GDALWarp().
6620 : * @param pszKey key.
6621 : * @param pszValue value.
6622 : *
6623 : * @since GDAL 2.1
6624 : */
6625 :
6626 0 : void GDALWarpAppOptionsSetWarpOption(GDALWarpAppOptions *psOptions,
6627 : const char *pszKey, const char *pszValue)
6628 : {
6629 0 : psOptions->aosWarpOptions.SetNameValue(pszKey, pszValue);
6630 0 : }
6631 :
6632 : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS
|