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