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