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