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