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