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