Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: OpenGIS Simple Features Reference Implementation |
4 | | * Purpose: The OGRSCoordinateTransformation class. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2000, Frank Warmerdam |
9 | | * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_port.h" |
15 | | #include "ogr_spatialref.h" |
16 | | |
17 | | #include <algorithm> |
18 | | #include <cassert> |
19 | | #include <cmath> |
20 | | #include <cstring> |
21 | | #include <limits> |
22 | | #include <list> |
23 | | #include <mutex> |
24 | | |
25 | | #include "cpl_conv.h" |
26 | | #include "cpl_error.h" |
27 | | #include "cpl_mem_cache.h" |
28 | | #include "cpl_string.h" |
29 | | #include "ogr_core.h" |
30 | | #include "ogr_srs_api.h" |
31 | | #include "ogr_proj_p.h" |
32 | | #include "ogrct_priv.h" |
33 | | |
34 | | #include "proj.h" |
35 | | #include "proj_experimental.h" |
36 | | |
37 | | #ifdef DEBUG_PERF |
38 | | static double g_dfTotalTimeCRStoCRS = 0; |
39 | | static double g_dfTotalTimeReprojection = 0; |
40 | | |
41 | | /************************************************************************/ |
42 | | /* CPLGettimeofday() */ |
43 | | /************************************************************************/ |
44 | | |
45 | | #if defined(_WIN32) && !defined(__CYGWIN__) |
46 | | #include <sys/timeb.h> |
47 | | |
48 | | namespace |
49 | | { |
50 | | struct CPLTimeVal |
51 | | { |
52 | | time_t tv_sec; /* seconds */ |
53 | | long tv_usec; /* and microseconds */ |
54 | | }; |
55 | | } // namespace |
56 | | |
57 | | static void CPLGettimeofday(struct CPLTimeVal *tp, void * /* timezonep*/) |
58 | | { |
59 | | struct _timeb theTime; |
60 | | |
61 | | _ftime(&theTime); |
62 | | tp->tv_sec = static_cast<time_t>(theTime.time); |
63 | | tp->tv_usec = theTime.millitm * 1000; |
64 | | } |
65 | | #else |
66 | | #include <sys/time.h> /* for gettimeofday() */ |
67 | | #define CPLTimeVal timeval |
68 | | #define CPLGettimeofday(t, u) gettimeofday(t, u) |
69 | | #endif |
70 | | |
71 | | #endif // DEBUG_PERF |
72 | | |
73 | | // Cache of OGRProjCT objects |
74 | | static std::mutex g_oCTCacheMutex; |
75 | | class OGRProjCT; |
76 | | typedef std::string CTCacheKey; |
77 | | typedef std::unique_ptr<OGRProjCT> CTCacheValue; |
78 | | static lru11::Cache<CTCacheKey, CTCacheValue> *g_poCTCache = nullptr; |
79 | | |
80 | | /************************************************************************/ |
81 | | /* OGRCoordinateTransformationOptions::Private */ |
82 | | /************************************************************************/ |
83 | | |
84 | | struct OGRCoordinateTransformationOptions::Private |
85 | | { |
86 | | bool bHasAreaOfInterest = false; |
87 | | double dfWestLongitudeDeg = 0.0; |
88 | | double dfSouthLatitudeDeg = 0.0; |
89 | | double dfEastLongitudeDeg = 0.0; |
90 | | double dfNorthLatitudeDeg = 0.0; |
91 | | |
92 | | CPLString osCoordOperation{}; |
93 | | bool bReverseCO = false; |
94 | | |
95 | | bool bAllowBallpark = true; |
96 | | double dfAccuracy = -1; // no constraint |
97 | | |
98 | | bool bOnlyBest = false; |
99 | | bool bOnlyBestOptionSet = false; |
100 | | |
101 | | bool bHasSourceCenterLong = false; |
102 | | double dfSourceCenterLong = 0.0; |
103 | | |
104 | | bool bHasTargetCenterLong = false; |
105 | | double dfTargetCenterLong = 0.0; |
106 | | |
107 | | bool bCheckWithInvertProj = false; |
108 | | |
109 | | Private(); |
110 | 0 | Private(const Private &) = default; |
111 | | Private(Private &&) = default; |
112 | 0 | Private &operator=(const Private &) = default; |
113 | | Private &operator=(Private &&) = default; |
114 | | |
115 | | std::string GetKey() const; |
116 | | void RefreshCheckWithInvertProj(); |
117 | | }; |
118 | | |
119 | | /************************************************************************/ |
120 | | /* Private() */ |
121 | | /************************************************************************/ |
122 | | |
123 | | OGRCoordinateTransformationOptions::Private::Private() |
124 | 0 | { |
125 | 0 | RefreshCheckWithInvertProj(); |
126 | 0 | } |
127 | | |
128 | | /************************************************************************/ |
129 | | /* GetKey() */ |
130 | | /************************************************************************/ |
131 | | |
132 | | std::string OGRCoordinateTransformationOptions::Private::GetKey() const |
133 | 0 | { |
134 | 0 | std::string ret; |
135 | 0 | ret += std::to_string(static_cast<int>(bHasAreaOfInterest)); |
136 | 0 | ret += std::to_string(dfWestLongitudeDeg); |
137 | 0 | ret += std::to_string(dfSouthLatitudeDeg); |
138 | 0 | ret += std::to_string(dfEastLongitudeDeg); |
139 | 0 | ret += std::to_string(dfNorthLatitudeDeg); |
140 | 0 | ret += osCoordOperation; |
141 | 0 | ret += std::to_string(static_cast<int>(bReverseCO)); |
142 | 0 | ret += std::to_string(static_cast<int>(bAllowBallpark)); |
143 | 0 | ret += std::to_string(dfAccuracy); |
144 | 0 | ret += std::to_string(static_cast<int>(bOnlyBestOptionSet)); |
145 | 0 | ret += std::to_string(static_cast<int>(bOnlyBest)); |
146 | 0 | ret += std::to_string(static_cast<int>(bHasSourceCenterLong)); |
147 | 0 | ret += std::to_string(dfSourceCenterLong); |
148 | 0 | ret += std::to_string(static_cast<int>(bHasTargetCenterLong)); |
149 | 0 | ret += std::to_string(dfTargetCenterLong); |
150 | 0 | ret += std::to_string(static_cast<int>(bCheckWithInvertProj)); |
151 | 0 | return ret; |
152 | 0 | } |
153 | | |
154 | | /************************************************************************/ |
155 | | /* RefreshCheckWithInvertProj() */ |
156 | | /************************************************************************/ |
157 | | |
158 | | void OGRCoordinateTransformationOptions::Private::RefreshCheckWithInvertProj() |
159 | 0 | { |
160 | 0 | bCheckWithInvertProj = |
161 | 0 | CPLTestBool(CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", "NO")); |
162 | 0 | } |
163 | | |
164 | | /************************************************************************/ |
165 | | /* GetAsAProjRecognizableString() */ |
166 | | /************************************************************************/ |
167 | | |
168 | | static char *GetAsAProjRecognizableString(const OGRSpatialReference *poSRS) |
169 | 0 | { |
170 | 0 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
171 | | // If there's a PROJ4 EXTENSION node in WKT1, then use |
172 | | // it. For example when dealing with "+proj=longlat +lon_wrap=180" |
173 | 0 | char *pszText = nullptr; |
174 | 0 | if (poSRS->GetExtension(nullptr, "PROJ4", nullptr)) |
175 | 0 | { |
176 | 0 | poSRS->exportToProj4(&pszText); |
177 | 0 | if (strstr(pszText, " +type=crs") == nullptr) |
178 | 0 | { |
179 | 0 | auto tmpText = std::string(pszText) + " +type=crs"; |
180 | 0 | CPLFree(pszText); |
181 | 0 | pszText = CPLStrdup(tmpText.c_str()); |
182 | 0 | } |
183 | 0 | } |
184 | 0 | else if (poSRS->IsEmpty()) |
185 | 0 | { |
186 | 0 | pszText = CPLStrdup(""); |
187 | 0 | } |
188 | 0 | else |
189 | 0 | { |
190 | | // We export to PROJJSON rather than WKT2:2019 because PROJJSON |
191 | | // is a bit more verbose, which helps in situations like |
192 | | // https://github.com/OSGeo/gdal/issues/9732 / |
193 | | // https://github.com/OSGeo/PROJ/pull/4124 where we want to export |
194 | | // a DerivedProjectedCRS whose base ProjectedCRS has non-metre axis. |
195 | 0 | poSRS->exportToPROJJSON(&pszText, nullptr); |
196 | 0 | } |
197 | |
|
198 | 0 | return pszText; |
199 | 0 | } |
200 | | |
201 | | /************************************************************************/ |
202 | | /* GetTextRepresentation() */ |
203 | | /************************************************************************/ |
204 | | |
205 | | static char *GetTextRepresentation(const OGRSpatialReference *poSRS) |
206 | 0 | { |
207 | 0 | const auto CanUseAuthorityDef = [](const OGRSpatialReference *poSRS1, |
208 | 0 | OGRSpatialReference *poSRSFromAuth, |
209 | 0 | const char *pszAuth) |
210 | 0 | { |
211 | 0 | if (EQUAL(pszAuth, "EPSG") && |
212 | 0 | CPLTestBool( |
213 | 0 | CPLGetConfigOption("OSR_CT_USE_DEFAULT_EPSG_TOWGS84", "NO"))) |
214 | 0 | { |
215 | | // We don't want by default to honour 'default' TOWGS84 terms that |
216 | | // come with the EPSG code because there might be a better |
217 | | // transformation from that Typical case if EPSG:31468 "DHDN / |
218 | | // 3-degree Gauss-Kruger zone 4" where the DHDN->TOWGS84 |
219 | | // transformation can use the BETA2007.gsb grid instead of |
220 | | // TOWGS84[598.1,73.7,418.2,0.202,0.045,-2.455,6.7] But if the user |
221 | | // really wants it, it can set the OSR_CT_USE_DEFAULT_EPSG_TOWGS84 |
222 | | // configuration option to YES |
223 | 0 | double adfTOWGS84_1[7]; |
224 | 0 | double adfTOWGS84_2[7]; |
225 | |
|
226 | 0 | poSRSFromAuth->AddGuessedTOWGS84(); |
227 | |
|
228 | 0 | if (poSRS1->GetTOWGS84(adfTOWGS84_1) == OGRERR_NONE && |
229 | 0 | poSRSFromAuth->GetTOWGS84(adfTOWGS84_2) == OGRERR_NONE && |
230 | 0 | memcmp(adfTOWGS84_1, adfTOWGS84_2, sizeof(adfTOWGS84_1)) == 0) |
231 | 0 | { |
232 | 0 | return false; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | return true; |
236 | 0 | }; |
237 | |
|
238 | 0 | char *pszText = nullptr; |
239 | | // If we have a AUTH:CODE attached, use it to retrieve the full |
240 | | // definition in case a trip to WKT1 has lost the area of use. |
241 | | // unless OGR_CT_PREFER_OFFICIAL_SRS_DEF=NO (see |
242 | | // https://github.com/OSGeo/PROJ/issues/2955) |
243 | 0 | const char *pszAuth = poSRS->GetAuthorityName(nullptr); |
244 | 0 | const char *pszCode = poSRS->GetAuthorityCode(nullptr); |
245 | 0 | if (pszAuth && pszCode && |
246 | 0 | CPLTestBool( |
247 | 0 | CPLGetConfigOption("OGR_CT_PREFER_OFFICIAL_SRS_DEF", "YES"))) |
248 | 0 | { |
249 | 0 | CPLString osAuthCode(pszAuth); |
250 | 0 | osAuthCode += ':'; |
251 | 0 | osAuthCode += pszCode; |
252 | 0 | OGRSpatialReference oTmpSRS; |
253 | 0 | oTmpSRS.SetFromUserInput(osAuthCode); |
254 | 0 | oTmpSRS.SetDataAxisToSRSAxisMapping( |
255 | 0 | poSRS->GetDataAxisToSRSAxisMapping()); |
256 | 0 | const char *const apszOptionsIsSame[] = {"CRITERION=EQUIVALENT", |
257 | 0 | nullptr}; |
258 | 0 | if (oTmpSRS.IsSame(poSRS, apszOptionsIsSame)) |
259 | 0 | { |
260 | 0 | if (CanUseAuthorityDef(poSRS, &oTmpSRS, pszAuth)) |
261 | 0 | { |
262 | 0 | pszText = CPLStrdup(osAuthCode); |
263 | 0 | } |
264 | 0 | } |
265 | 0 | } |
266 | 0 | if (pszText == nullptr) |
267 | 0 | { |
268 | 0 | pszText = GetAsAProjRecognizableString(poSRS); |
269 | 0 | } |
270 | 0 | return pszText; |
271 | 0 | } |
272 | | |
273 | | /************************************************************************/ |
274 | | /* OGRCoordinateTransformationOptions() */ |
275 | | /************************************************************************/ |
276 | | |
277 | | /** \brief Constructs a new OGRCoordinateTransformationOptions. |
278 | | * |
279 | | * @since GDAL 3.0 |
280 | | */ |
281 | | OGRCoordinateTransformationOptions::OGRCoordinateTransformationOptions() |
282 | 0 | : d(new Private()) |
283 | 0 | { |
284 | 0 | } |
285 | | |
286 | | /************************************************************************/ |
287 | | /* OGRCoordinateTransformationOptions() */ |
288 | | /************************************************************************/ |
289 | | |
290 | | /** \brief Copy constructor |
291 | | * |
292 | | * @since GDAL 3.1 |
293 | | */ |
294 | | OGRCoordinateTransformationOptions::OGRCoordinateTransformationOptions( |
295 | | const OGRCoordinateTransformationOptions &other) |
296 | 0 | : d(new Private(*(other.d))) |
297 | 0 | { |
298 | 0 | } |
299 | | |
300 | | /************************************************************************/ |
301 | | /* operator =() */ |
302 | | /************************************************************************/ |
303 | | |
304 | | /** \brief Assignment operator |
305 | | * |
306 | | * @since GDAL 3.1 |
307 | | */ |
308 | | OGRCoordinateTransformationOptions & |
309 | | OGRCoordinateTransformationOptions::operator=( |
310 | | const OGRCoordinateTransformationOptions &other) |
311 | 0 | { |
312 | 0 | if (this != &other) |
313 | 0 | { |
314 | 0 | *d = *(other.d); |
315 | 0 | } |
316 | 0 | return *this; |
317 | 0 | } |
318 | | |
319 | | /************************************************************************/ |
320 | | /* OGRCoordinateTransformationOptions() */ |
321 | | /************************************************************************/ |
322 | | |
323 | | /** \brief Destroys a OGRCoordinateTransformationOptions. |
324 | | * |
325 | | * @since GDAL 3.0 |
326 | | */ |
327 | | OGRCoordinateTransformationOptions::~OGRCoordinateTransformationOptions() |
328 | 0 | { |
329 | 0 | } |
330 | | |
331 | | /************************************************************************/ |
332 | | /* OCTNewCoordinateTransformationOptions() */ |
333 | | /************************************************************************/ |
334 | | |
335 | | /** \brief Create coordinate transformation options. |
336 | | * |
337 | | * To be freed with OCTDestroyCoordinateTransformationOptions() |
338 | | * |
339 | | * @since GDAL 3.0 |
340 | | */ |
341 | | OGRCoordinateTransformationOptionsH OCTNewCoordinateTransformationOptions(void) |
342 | 0 | { |
343 | 0 | return new OGRCoordinateTransformationOptions(); |
344 | 0 | } |
345 | | |
346 | | /************************************************************************/ |
347 | | /* OCTDestroyCoordinateTransformationOptions() */ |
348 | | /************************************************************************/ |
349 | | |
350 | | /** \brief Destroy coordinate transformation options. |
351 | | * |
352 | | * @since GDAL 3.0 |
353 | | */ |
354 | | void OCTDestroyCoordinateTransformationOptions( |
355 | | OGRCoordinateTransformationOptionsH hOptions) |
356 | 0 | { |
357 | 0 | delete hOptions; |
358 | 0 | } |
359 | | |
360 | | /************************************************************************/ |
361 | | /* SetAreaOfInterest() */ |
362 | | /************************************************************************/ |
363 | | |
364 | | /** \brief Sets an area of interest. |
365 | | * |
366 | | * The west longitude is generally lower than the east longitude, except for |
367 | | * areas of interest that go across the anti-meridian. |
368 | | * |
369 | | * @param dfWestLongitudeDeg West longitude (in degree). Must be in [-180,180] |
370 | | * @param dfSouthLatitudeDeg South latitude (in degree). Must be in [-90,90] |
371 | | * @param dfEastLongitudeDeg East longitude (in degree). Must be in [-180,180] |
372 | | * @param dfNorthLatitudeDeg North latitude (in degree). Must be in [-90,90] |
373 | | * @return true in case of success. |
374 | | * |
375 | | * @since GDAL 3.0 |
376 | | */ |
377 | | bool OGRCoordinateTransformationOptions::SetAreaOfInterest( |
378 | | double dfWestLongitudeDeg, double dfSouthLatitudeDeg, |
379 | | double dfEastLongitudeDeg, double dfNorthLatitudeDeg) |
380 | 0 | { |
381 | 0 | if (std::fabs(dfWestLongitudeDeg) > 180) |
382 | 0 | { |
383 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfWestLongitudeDeg"); |
384 | 0 | return false; |
385 | 0 | } |
386 | 0 | if (std::fabs(dfSouthLatitudeDeg) > 90) |
387 | 0 | { |
388 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfSouthLatitudeDeg"); |
389 | 0 | return false; |
390 | 0 | } |
391 | 0 | if (std::fabs(dfEastLongitudeDeg) > 180) |
392 | 0 | { |
393 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfEastLongitudeDeg"); |
394 | 0 | return false; |
395 | 0 | } |
396 | 0 | if (std::fabs(dfNorthLatitudeDeg) > 90) |
397 | 0 | { |
398 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Invalid dfNorthLatitudeDeg"); |
399 | 0 | return false; |
400 | 0 | } |
401 | 0 | if (dfSouthLatitudeDeg > dfNorthLatitudeDeg) |
402 | 0 | { |
403 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
404 | 0 | "dfSouthLatitudeDeg should be lower than dfNorthLatitudeDeg"); |
405 | 0 | return false; |
406 | 0 | } |
407 | 0 | d->bHasAreaOfInterest = true; |
408 | 0 | d->dfWestLongitudeDeg = dfWestLongitudeDeg; |
409 | 0 | d->dfSouthLatitudeDeg = dfSouthLatitudeDeg; |
410 | 0 | d->dfEastLongitudeDeg = dfEastLongitudeDeg; |
411 | 0 | d->dfNorthLatitudeDeg = dfNorthLatitudeDeg; |
412 | 0 | return true; |
413 | 0 | } |
414 | | |
415 | | /************************************************************************/ |
416 | | /* OCTCoordinateTransformationOptionsSetAreaOfInterest() */ |
417 | | /************************************************************************/ |
418 | | |
419 | | /** \brief Sets an area of interest. |
420 | | * |
421 | | * See OGRCoordinateTransformationOptions::SetAreaOfInterest() |
422 | | * |
423 | | * @since GDAL 3.0 |
424 | | */ |
425 | | int OCTCoordinateTransformationOptionsSetAreaOfInterest( |
426 | | OGRCoordinateTransformationOptionsH hOptions, double dfWestLongitudeDeg, |
427 | | double dfSouthLatitudeDeg, double dfEastLongitudeDeg, |
428 | | double dfNorthLatitudeDeg) |
429 | 0 | { |
430 | 0 | return hOptions->SetAreaOfInterest(dfWestLongitudeDeg, dfSouthLatitudeDeg, |
431 | 0 | dfEastLongitudeDeg, dfNorthLatitudeDeg); |
432 | 0 | } |
433 | | |
434 | | /************************************************************************/ |
435 | | /* SetCoordinateOperation() */ |
436 | | /************************************************************************/ |
437 | | |
438 | | /** \brief Sets a coordinate operation. |
439 | | * |
440 | | * This is a user override to be used instead of the normally computed pipeline. |
441 | | * |
442 | | * The pipeline must take into account the axis order of the source and target |
443 | | * SRS. |
444 | | * |
445 | | * The pipeline may be provided as a PROJ string (single step operation or |
446 | | * multiple step string starting with +proj=pipeline), a WKT2 string describing |
447 | | * a CoordinateOperation, or a "urn:ogc:def:coordinateOperation:EPSG::XXXX" URN |
448 | | * |
449 | | * @param pszCO PROJ or WKT string describing a coordinate operation |
450 | | * @param bReverseCO Whether the PROJ or WKT string should be evaluated in the |
451 | | * reverse path |
452 | | * @return true in case of success. |
453 | | * |
454 | | * @since GDAL 3.0 |
455 | | */ |
456 | | bool OGRCoordinateTransformationOptions::SetCoordinateOperation( |
457 | | const char *pszCO, bool bReverseCO) |
458 | 0 | { |
459 | 0 | d->osCoordOperation = pszCO ? pszCO : ""; |
460 | 0 | d->bReverseCO = bReverseCO; |
461 | 0 | return true; |
462 | 0 | } |
463 | | |
464 | | /************************************************************************/ |
465 | | /* SetSourceCenterLong() */ |
466 | | /************************************************************************/ |
467 | | |
468 | | /*! @cond Doxygen_Suppress */ |
469 | | void OGRCoordinateTransformationOptions::SetSourceCenterLong( |
470 | | double dfCenterLong) |
471 | 0 | { |
472 | 0 | d->dfSourceCenterLong = dfCenterLong; |
473 | 0 | d->bHasSourceCenterLong = true; |
474 | 0 | } |
475 | | |
476 | | /*! @endcond */ |
477 | | |
478 | | /************************************************************************/ |
479 | | /* SetTargetCenterLong() */ |
480 | | /************************************************************************/ |
481 | | |
482 | | /*! @cond Doxygen_Suppress */ |
483 | | void OGRCoordinateTransformationOptions::SetTargetCenterLong( |
484 | | double dfCenterLong) |
485 | 0 | { |
486 | 0 | d->dfTargetCenterLong = dfCenterLong; |
487 | 0 | d->bHasTargetCenterLong = true; |
488 | 0 | } |
489 | | |
490 | | /*! @endcond */ |
491 | | |
492 | | /************************************************************************/ |
493 | | /* OCTCoordinateTransformationOptionsSetOperation() */ |
494 | | /************************************************************************/ |
495 | | |
496 | | /** \brief Sets a coordinate operation. |
497 | | * |
498 | | * See OGRCoordinateTransformationOptions::SetCoordinateTransformation() |
499 | | * |
500 | | * @since GDAL 3.0 |
501 | | */ |
502 | | int OCTCoordinateTransformationOptionsSetOperation( |
503 | | OGRCoordinateTransformationOptionsH hOptions, const char *pszCO, |
504 | | int bReverseCO) |
505 | 0 | { |
506 | | // cppcheck-suppress knownConditionTrueFalse |
507 | 0 | return hOptions->SetCoordinateOperation(pszCO, CPL_TO_BOOL(bReverseCO)); |
508 | 0 | } |
509 | | |
510 | | /************************************************************************/ |
511 | | /* SetDesiredAccuracy() */ |
512 | | /************************************************************************/ |
513 | | |
514 | | /** \brief Sets the desired accuracy for coordinate operations. |
515 | | * |
516 | | * Only coordinate operations that offer an accuracy of at least the one |
517 | | * specified will be considered. |
518 | | * |
519 | | * An accuracy of 0 is valid and means a coordinate operation made only of one |
520 | | * or several conversions (map projections, unit conversion, etc.) Operations |
521 | | * involving ballpark transformations have a unknown accuracy, and will be |
522 | | * filtered out by any dfAccuracy >= 0 value. |
523 | | * |
524 | | * If this option is specified with PROJ < 8, the OGR_CT_OP_SELECTION |
525 | | * configuration option will default to BEST_ACCURACY. |
526 | | * |
527 | | * @param dfAccuracy accuracy in meters (or a negative value to disable this |
528 | | * filter) |
529 | | * |
530 | | * @since GDAL 3.3 |
531 | | */ |
532 | | bool OGRCoordinateTransformationOptions::SetDesiredAccuracy(double dfAccuracy) |
533 | 0 | { |
534 | 0 | d->dfAccuracy = dfAccuracy; |
535 | 0 | return true; |
536 | 0 | } |
537 | | |
538 | | /************************************************************************/ |
539 | | /* OCTCoordinateTransformationOptionsSetDesiredAccuracy() */ |
540 | | /************************************************************************/ |
541 | | |
542 | | /** \brief Sets the desired accuracy for coordinate operations. |
543 | | * |
544 | | * See OGRCoordinateTransformationOptions::SetDesiredAccuracy() |
545 | | * |
546 | | * @since GDAL 3.3 |
547 | | */ |
548 | | int OCTCoordinateTransformationOptionsSetDesiredAccuracy( |
549 | | OGRCoordinateTransformationOptionsH hOptions, double dfAccuracy) |
550 | 0 | { |
551 | | // cppcheck-suppress knownConditionTrueFalse |
552 | 0 | return hOptions->SetDesiredAccuracy(dfAccuracy); |
553 | 0 | } |
554 | | |
555 | | /************************************************************************/ |
556 | | /* SetBallparkAllowed() */ |
557 | | /************************************************************************/ |
558 | | |
559 | | /** \brief Sets whether ballpark transformations are allowed. |
560 | | * |
561 | | * By default, PROJ may generate "ballpark transformations" (see |
562 | | * https://proj.org/glossary.html) when precise datum transformations are |
563 | | * missing. For high accuracy use cases, such transformations might not be |
564 | | * allowed. |
565 | | * |
566 | | * If this option is specified with PROJ < 8, the OGR_CT_OP_SELECTION |
567 | | * configuration option will default to BEST_ACCURACY. |
568 | | * |
569 | | * @param bAllowBallpark false to disable the user of ballpark transformations |
570 | | * |
571 | | * @since GDAL 3.3 |
572 | | */ |
573 | | bool OGRCoordinateTransformationOptions::SetBallparkAllowed(bool bAllowBallpark) |
574 | 0 | { |
575 | 0 | d->bAllowBallpark = bAllowBallpark; |
576 | 0 | return true; |
577 | 0 | } |
578 | | |
579 | | /************************************************************************/ |
580 | | /* OCTCoordinateTransformationOptionsSetBallparkAllowed() */ |
581 | | /************************************************************************/ |
582 | | |
583 | | /** \brief Sets whether ballpark transformations are allowed. |
584 | | * |
585 | | * See OGRCoordinateTransformationOptions::SetDesiredAccuracy() |
586 | | * |
587 | | * @since GDAL 3.3 and PROJ 8 |
588 | | */ |
589 | | int OCTCoordinateTransformationOptionsSetBallparkAllowed( |
590 | | OGRCoordinateTransformationOptionsH hOptions, int bAllowBallpark) |
591 | 0 | { |
592 | | // cppcheck-suppress knownConditionTrueFalse |
593 | 0 | return hOptions->SetBallparkAllowed(CPL_TO_BOOL(bAllowBallpark)); |
594 | 0 | } |
595 | | |
596 | | /************************************************************************/ |
597 | | /* SetOnlyBest() */ |
598 | | /************************************************************************/ |
599 | | |
600 | | /** \brief Sets whether only the "best" operation should be used. |
601 | | * |
602 | | * By default (at least in the PROJ 9.x series), PROJ may use coordinate |
603 | | * operations that are not the "best" if resources (typically grids) needed |
604 | | * to use them are missing. It will then fallback to other coordinate operations |
605 | | * that have a lesser accuracy, for example using Helmert transformations, |
606 | | * or in the absence of such operations, to ones with potential very rough |
607 | | * accuracy, using "ballpark" transformations |
608 | | * (see https://proj.org/glossary.html). |
609 | | * |
610 | | * When calling this method with bOnlyBest = true, PROJ will only consider the |
611 | | * "best" operation, and error out (at Transform() time) if they cannot be |
612 | | * used. |
613 | | * This method may be used together with SetBallparkAllowed(false) to |
614 | | * only allow best operations that have a known accuracy. |
615 | | * |
616 | | * Note that this method has no effect on PROJ versions before 9.2. |
617 | | * |
618 | | * The default value for this option can be also set with the |
619 | | * PROJ_ONLY_BEST_DEFAULT environment variable, or with the "only_best_default" |
620 | | * setting of proj.ini. Calling SetOnlyBest() overrides such default value. |
621 | | * |
622 | | * @param bOnlyBest set to true to ask PROJ to use only the best operation(s) |
623 | | * |
624 | | * @since GDAL 3.8 and PROJ 9.2 |
625 | | */ |
626 | | bool OGRCoordinateTransformationOptions::SetOnlyBest(bool bOnlyBest) |
627 | 0 | { |
628 | 0 | d->bOnlyBest = bOnlyBest; |
629 | 0 | d->bOnlyBestOptionSet = true; |
630 | 0 | return true; |
631 | 0 | } |
632 | | |
633 | | /************************************************************************/ |
634 | | /* OCTCoordinateTransformationOptionsSetOnlyBest() */ |
635 | | /************************************************************************/ |
636 | | |
637 | | /** \brief Sets whether only the "best" operation(s) should be used. |
638 | | * |
639 | | * See OGRCoordinateTransformationOptions::SetOnlyBest() |
640 | | * |
641 | | * @since GDAL 3.8 and PROJ 9.2 |
642 | | */ |
643 | | int OCTCoordinateTransformationOptionsSetOnlyBest( |
644 | | OGRCoordinateTransformationOptionsH hOptions, bool bOnlyBest) |
645 | 0 | { |
646 | | // cppcheck-suppress knownConditionTrueFalse |
647 | 0 | return hOptions->SetOnlyBest(bOnlyBest); |
648 | 0 | } |
649 | | |
650 | | /************************************************************************/ |
651 | | /* OGRProjCT */ |
652 | | /************************************************************************/ |
653 | | |
654 | | //! @cond Doxygen_Suppress |
655 | | class OGRProjCT : public OGRCoordinateTransformation |
656 | | { |
657 | | friend void |
658 | | OGRProjCTDifferentOperationsStart(OGRCoordinateTransformation *poCT); |
659 | | friend void |
660 | | OGRProjCTDifferentOperationsStop(OGRCoordinateTransformation *poCT); |
661 | | friend bool |
662 | | OGRProjCTDifferentOperationsUsed(OGRCoordinateTransformation *poCT); |
663 | | |
664 | | class PjPtr |
665 | | { |
666 | | PJ *m_pj = nullptr; |
667 | | |
668 | | void reset() |
669 | 0 | { |
670 | 0 | if (m_pj) |
671 | 0 | { |
672 | 0 | proj_assign_context(m_pj, OSRGetProjTLSContext()); |
673 | 0 | proj_destroy(m_pj); |
674 | 0 | } |
675 | 0 | } |
676 | | |
677 | | public: |
678 | 0 | PjPtr() : m_pj(nullptr) |
679 | 0 | { |
680 | 0 | } |
681 | | |
682 | 0 | explicit PjPtr(PJ *pjIn) : m_pj(pjIn) |
683 | 0 | { |
684 | 0 | } |
685 | | |
686 | | ~PjPtr() |
687 | 0 | { |
688 | 0 | reset(); |
689 | 0 | } |
690 | | |
691 | | PjPtr(const PjPtr &other) |
692 | 0 | : m_pj((other.m_pj != nullptr) |
693 | 0 | ? (proj_clone(OSRGetProjTLSContext(), other.m_pj)) |
694 | 0 | : (nullptr)) |
695 | 0 | { |
696 | 0 | } |
697 | | |
698 | | PjPtr(PjPtr &&other) : m_pj(other.m_pj) |
699 | 0 | { |
700 | 0 | other.m_pj = nullptr; |
701 | 0 | } |
702 | | |
703 | | PjPtr &operator=(const PjPtr &other) |
704 | 0 | { |
705 | 0 | if (this != &other) |
706 | 0 | { |
707 | 0 | reset(); |
708 | 0 | m_pj = (other.m_pj != nullptr) |
709 | 0 | ? (proj_clone(OSRGetProjTLSContext(), other.m_pj)) |
710 | 0 | : (nullptr); |
711 | 0 | } |
712 | 0 | return *this; |
713 | 0 | } |
714 | | |
715 | | PjPtr &operator=(PJ *pjIn) |
716 | 0 | { |
717 | 0 | if (m_pj != pjIn) |
718 | 0 | { |
719 | 0 | reset(); |
720 | 0 | m_pj = pjIn; |
721 | 0 | } |
722 | 0 | return *this; |
723 | 0 | } |
724 | | |
725 | | operator PJ *() |
726 | 0 | { |
727 | 0 | return m_pj; |
728 | 0 | } |
729 | | |
730 | | operator const PJ *() const |
731 | 0 | { |
732 | 0 | return m_pj; |
733 | 0 | } |
734 | | }; |
735 | | |
736 | | OGRSpatialReference *poSRSSource = nullptr; |
737 | | OGRAxisOrientation m_eSourceFirstAxisOrient = OAO_Other; |
738 | | bool bSourceLatLong = false; |
739 | | bool bSourceWrap = false; |
740 | | double dfSourceWrapLong = 0.0; |
741 | | bool bSourceIsDynamicCRS = false; |
742 | | double dfSourceCoordinateEpoch = 0.0; |
743 | | std::string m_osSrcSRS{}; // WKT, PROJ4 or AUTH:CODE |
744 | | |
745 | | OGRSpatialReference *poSRSTarget = nullptr; |
746 | | OGRAxisOrientation m_eTargetFirstAxisOrient = OAO_Other; |
747 | | bool bTargetLatLong = false; |
748 | | bool bTargetWrap = false; |
749 | | double dfTargetWrapLong = 0.0; |
750 | | bool bTargetIsDynamicCRS = false; |
751 | | double dfTargetCoordinateEpoch = 0.0; |
752 | | std::string m_osTargetSRS{}; // WKT, PROJ4 or AUTH:CODE |
753 | | |
754 | | bool bWebMercatorToWGS84LongLat = false; |
755 | | |
756 | | size_t nErrorCount = 0; |
757 | | |
758 | | double dfThreshold = 0.0; |
759 | | |
760 | | PjPtr m_pj{}; |
761 | | bool m_bReversePj = false; |
762 | | |
763 | | bool m_bEmitErrors = true; |
764 | | |
765 | | bool bNoTransform = false; |
766 | | |
767 | | enum class Strategy |
768 | | { |
769 | | PROJ, |
770 | | BEST_ACCURACY, |
771 | | FIRST_MATCHING |
772 | | }; |
773 | | Strategy m_eStrategy = Strategy::PROJ; |
774 | | |
775 | | bool |
776 | | ListCoordinateOperations(const char *pszSrcSRS, const char *pszTargetSRS, |
777 | | const OGRCoordinateTransformationOptions &options); |
778 | | |
779 | | struct Transformation |
780 | | { |
781 | | double minx = 0.0; |
782 | | double miny = 0.0; |
783 | | double maxx = 0.0; |
784 | | double maxy = 0.0; |
785 | | PjPtr pj{}; |
786 | | CPLString osName{}; |
787 | | CPLString osProjString{}; |
788 | | double accuracy = 0.0; |
789 | | |
790 | | Transformation(double minxIn, double minyIn, double maxxIn, |
791 | | double maxyIn, PJ *pjIn, const CPLString &osNameIn, |
792 | | const CPLString &osProjStringIn, double accuracyIn) |
793 | 0 | : minx(minxIn), miny(minyIn), maxx(maxxIn), maxy(maxyIn), pj(pjIn), |
794 | 0 | osName(osNameIn), osProjString(osProjStringIn), |
795 | 0 | accuracy(accuracyIn) |
796 | 0 | { |
797 | 0 | } |
798 | | }; |
799 | | |
800 | | std::vector<Transformation> m_oTransformations{}; |
801 | | int m_iCurTransformation = -1; |
802 | | OGRCoordinateTransformationOptions m_options{}; |
803 | | |
804 | | bool m_recordDifferentOperationsUsed = false; |
805 | | std::string m_lastPjUsedPROJString{}; |
806 | | bool m_differentOperationsUsed = false; |
807 | | |
808 | | void ComputeThreshold(); |
809 | | void DetectWebMercatorToWGS84(); |
810 | | |
811 | | OGRProjCT(const OGRProjCT &other); |
812 | | OGRProjCT &operator=(const OGRProjCT &) = delete; |
813 | | |
814 | | static CTCacheKey |
815 | | MakeCacheKey(const OGRSpatialReference *poSRS1, const char *pszSrcSRS, |
816 | | const OGRSpatialReference *poSRS2, const char *pszTargetSRS, |
817 | | const OGRCoordinateTransformationOptions &options); |
818 | | bool ContainsNorthPole(const double xmin, const double ymin, |
819 | | const double xmax, const double ymax, |
820 | | bool lon_lat_order); |
821 | | bool ContainsSouthPole(const double xmin, const double ymin, |
822 | | const double xmax, const double ymax, |
823 | | bool lon_lat_order); |
824 | | |
825 | | public: |
826 | | OGRProjCT(); |
827 | | ~OGRProjCT() override; |
828 | | |
829 | | int Initialize(const OGRSpatialReference *poSource, const char *pszSrcSRS, |
830 | | const OGRSpatialReference *poTarget, |
831 | | const char *pszTargetSRS, |
832 | | const OGRCoordinateTransformationOptions &options); |
833 | | |
834 | | const OGRSpatialReference *GetSourceCS() const override; |
835 | | const OGRSpatialReference *GetTargetCS() const override; |
836 | | |
837 | | int Transform(size_t nCount, double *x, double *y, double *z, double *t, |
838 | | int *pabSuccess) override; |
839 | | |
840 | | int TransformWithErrorCodes(size_t nCount, double *x, double *y, double *z, |
841 | | double *t, int *panErrorCodes) override; |
842 | | |
843 | | int TransformBounds(const double xmin, const double ymin, const double xmax, |
844 | | const double ymax, double *out_xmin, double *out_ymin, |
845 | | double *out_xmax, double *out_ymax, |
846 | | const int densify_pts) override; |
847 | | |
848 | | bool GetEmitErrors() const override |
849 | 0 | { |
850 | 0 | return m_bEmitErrors; |
851 | 0 | } |
852 | | |
853 | | void SetEmitErrors(bool bEmitErrors) override |
854 | 0 | { |
855 | 0 | m_bEmitErrors = bEmitErrors; |
856 | 0 | } |
857 | | |
858 | | OGRCoordinateTransformation *Clone() const override; |
859 | | |
860 | | OGRCoordinateTransformation *GetInverse() const override; |
861 | | |
862 | | static void InsertIntoCache(OGRProjCT *poCT); |
863 | | |
864 | | static OGRProjCT * |
865 | | FindFromCache(const OGRSpatialReference *poSource, const char *pszSrcSRS, |
866 | | const OGRSpatialReference *poTarget, const char *pszTargetSRS, |
867 | | const OGRCoordinateTransformationOptions &options); |
868 | | }; |
869 | | |
870 | | /************************************************************************/ |
871 | | /* OGRProjCTDifferentOperationsStart() */ |
872 | | /************************************************************************/ |
873 | | |
874 | | void OGRProjCTDifferentOperationsStart(OGRCoordinateTransformation *poCT) |
875 | 0 | { |
876 | 0 | auto poOGRCT = dynamic_cast<OGRProjCT *>(poCT); |
877 | 0 | if (poOGRCT) |
878 | 0 | { |
879 | 0 | poOGRCT->m_recordDifferentOperationsUsed = true; |
880 | 0 | poOGRCT->m_differentOperationsUsed = false; |
881 | 0 | poOGRCT->m_lastPjUsedPROJString.clear(); |
882 | 0 | } |
883 | 0 | else |
884 | 0 | { |
885 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
886 | 0 | "OGRProjCTDifferentOperationsStart() called with a non " |
887 | 0 | "OGRProjCT instance"); |
888 | 0 | } |
889 | 0 | } |
890 | | |
891 | | /************************************************************************/ |
892 | | /* OGRProjCTDifferentOperationsStop() */ |
893 | | /************************************************************************/ |
894 | | |
895 | | void OGRProjCTDifferentOperationsStop(OGRCoordinateTransformation *poCT) |
896 | 0 | { |
897 | 0 | auto poOGRCT = dynamic_cast<OGRProjCT *>(poCT); |
898 | 0 | if (poOGRCT) |
899 | 0 | { |
900 | 0 | poOGRCT->m_recordDifferentOperationsUsed = false; |
901 | 0 | } |
902 | 0 | else |
903 | 0 | { |
904 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
905 | 0 | "OGRProjCTDifferentOperationsStop() called with a non " |
906 | 0 | "OGRProjCT instance"); |
907 | 0 | } |
908 | 0 | } |
909 | | |
910 | | /************************************************************************/ |
911 | | /* OGRProjCTDifferentOperationsUsed() */ |
912 | | /************************************************************************/ |
913 | | |
914 | | bool OGRProjCTDifferentOperationsUsed(OGRCoordinateTransformation *poCT) |
915 | 0 | { |
916 | 0 | auto poOGRCT = dynamic_cast<OGRProjCT *>(poCT); |
917 | 0 | if (poOGRCT) |
918 | 0 | { |
919 | 0 | return poOGRCT->m_differentOperationsUsed; |
920 | 0 | } |
921 | 0 | else |
922 | 0 | { |
923 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
924 | 0 | "OGRProjCTDifferentOperationsReset() called with a non " |
925 | 0 | "OGRProjCT instance"); |
926 | 0 | return false; |
927 | 0 | } |
928 | 0 | } |
929 | | |
930 | | //! @endcond |
931 | | |
932 | | /************************************************************************/ |
933 | | /* ~OGRCoordinateTransformation() */ |
934 | | /************************************************************************/ |
935 | | |
936 | 0 | OGRCoordinateTransformation::~OGRCoordinateTransformation() = default; |
937 | | |
938 | | /************************************************************************/ |
939 | | /* OCTDestroyCoordinateTransformation() */ |
940 | | /************************************************************************/ |
941 | | |
942 | | /** |
943 | | * \brief OGRCoordinateTransformation destructor. |
944 | | * |
945 | | * This function is the same as OGRCoordinateTransformation::DestroyCT() |
946 | | * |
947 | | * @param hCT the object to delete |
948 | | */ |
949 | | |
950 | | void CPL_STDCALL |
951 | | OCTDestroyCoordinateTransformation(OGRCoordinateTransformationH hCT) |
952 | | |
953 | 0 | { |
954 | 0 | OGRCoordinateTransformation::DestroyCT( |
955 | 0 | OGRCoordinateTransformation::FromHandle(hCT)); |
956 | 0 | } |
957 | | |
958 | | /************************************************************************/ |
959 | | /* DestroyCT() */ |
960 | | /************************************************************************/ |
961 | | |
962 | | /** |
963 | | * \brief OGRCoordinateTransformation destructor. |
964 | | * |
965 | | * This function is the same as |
966 | | * OGRCoordinateTransformation::~OGRCoordinateTransformation() |
967 | | * and OCTDestroyCoordinateTransformation() |
968 | | * |
969 | | * This static method will destroy a OGRCoordinateTransformation. It is |
970 | | * equivalent to calling delete on the object, but it ensures that the |
971 | | * deallocation is properly executed within the OGR libraries heap on |
972 | | * platforms where this can matter (win32). |
973 | | * |
974 | | * @param poCT the object to delete |
975 | | * |
976 | | * @since GDAL 1.7.0 |
977 | | */ |
978 | | |
979 | | void OGRCoordinateTransformation::DestroyCT(OGRCoordinateTransformation *poCT) |
980 | 0 | { |
981 | 0 | auto poProjCT = dynamic_cast<OGRProjCT *>(poCT); |
982 | 0 | if (poProjCT) |
983 | 0 | { |
984 | 0 | OGRProjCT::InsertIntoCache(poProjCT); |
985 | 0 | } |
986 | 0 | else |
987 | 0 | { |
988 | 0 | delete poCT; |
989 | 0 | } |
990 | 0 | } |
991 | | |
992 | | /************************************************************************/ |
993 | | /* OGRCreateCoordinateTransformation() */ |
994 | | /************************************************************************/ |
995 | | |
996 | | /** |
997 | | * Create transformation object. |
998 | | * |
999 | | * This is the same as the C function OCTNewCoordinateTransformation(). |
1000 | | * |
1001 | | * Input spatial reference system objects are assigned |
1002 | | * by copy (calling clone() method) and no ownership transfer occurs. |
1003 | | * |
1004 | | * The delete operator, or OCTDestroyCoordinateTransformation() should |
1005 | | * be used to destroy transformation objects. |
1006 | | * |
1007 | | * This will honour the axis order advertized by the source and target SRS, |
1008 | | * as well as their "data axis to SRS axis mapping". |
1009 | | * To have a behavior similar to GDAL < 3.0, the |
1010 | | * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES. |
1011 | | * |
1012 | | * @param poSource source spatial reference system. |
1013 | | * @param poTarget target spatial reference system. |
1014 | | * @return NULL on failure or a ready to use transformation object. |
1015 | | */ |
1016 | | |
1017 | | OGRCoordinateTransformation * |
1018 | | OGRCreateCoordinateTransformation(const OGRSpatialReference *poSource, |
1019 | | const OGRSpatialReference *poTarget) |
1020 | | |
1021 | 0 | { |
1022 | 0 | return OGRCreateCoordinateTransformation( |
1023 | 0 | poSource, poTarget, OGRCoordinateTransformationOptions()); |
1024 | 0 | } |
1025 | | |
1026 | | /** |
1027 | | * Create transformation object. |
1028 | | * |
1029 | | * This is the same as the C function OCTNewCoordinateTransformationEx(). |
1030 | | * |
1031 | | * Input spatial reference system objects are assigned |
1032 | | * by copy (calling clone() method) and no ownership transfer occurs. |
1033 | | * |
1034 | | * The delete operator, or OCTDestroyCoordinateTransformation() should |
1035 | | * be used to destroy transformation objects. |
1036 | | * |
1037 | | * This will honour the axis order advertized by the source and target SRS, |
1038 | | * as well as their "data axis to SRS axis mapping". |
1039 | | * To have a behavior similar to GDAL < 3.0, the |
1040 | | * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES. |
1041 | | * |
1042 | | * The source SRS and target SRS should generally not be NULL. This is only |
1043 | | * allowed if a custom coordinate operation is set through the hOptions |
1044 | | * argument. |
1045 | | * |
1046 | | * Starting with GDAL 3.0.3, the OGR_CT_OP_SELECTION configuration option can be |
1047 | | * set to PROJ (default if PROJ >= 6.3), BEST_ACCURACY or FIRST_MATCHING to |
1048 | | * decide of the strategy to select the operation to use among candidates, whose |
1049 | | * area of use is compatible with the points to transform. It is only taken into |
1050 | | * account if no user defined coordinate transformation pipeline has been |
1051 | | * specified. <ul> <li>PROJ means the default behavior used by PROJ |
1052 | | * proj_create_crs_to_crs(). In particular the operation to use among several |
1053 | | * initial candidates is evaluated for each point to transform.</li> |
1054 | | * <li>BEST_ACCURACY means the operation whose accuracy is best. It should be |
1055 | | * close to PROJ behavior, except that the operation to select is decided |
1056 | | * for the average point of the coordinates passed in a single Transform() |
1057 | | * call. Note: if the OGRCoordinateTransformationOptions::SetDesiredAccuracy() |
1058 | | * or OGRCoordinateTransformationOptions::SetBallparkAllowed() methods are |
1059 | | * called with PROJ < 8, this strategy will be selected instead of PROJ. |
1060 | | * </li> |
1061 | | * <li>FIRST_MATCHING is the operation ordered first in the list of candidates: |
1062 | | * it will not necessarily have the best accuracy, but generally a larger |
1063 | | * area of use. It is evaluated for the average point of the coordinates passed |
1064 | | * in a single Transform() call. This was the default behavior for GDAL 3.0.0 to |
1065 | | * 3.0.2</li> |
1066 | | * </ul> |
1067 | | * |
1068 | | * By default, if the source or target SRS definition refers to an official |
1069 | | * CRS through a code, GDAL will use the official definition if the official |
1070 | | * definition and the source/target SRS definition are equivalent. Note that |
1071 | | * TOWGS84[] clauses are ignored when checking equivalence. Starting with |
1072 | | * GDAL 3.4.1, if you set the OGR_CT_PREFER_OFFICIAL_SRS_DEF configuration |
1073 | | * option to NO, the source or target SRS definition will be always used. |
1074 | | * |
1075 | | * If options contains a user defined coordinate transformation pipeline, it |
1076 | | * will be unconditionally used. |
1077 | | * If options has an area of interest defined, it will be used to research the |
1078 | | * best fitting coordinate transformation (which will be used for all coordinate |
1079 | | * transformations, even if they don't fall into the declared area of interest) |
1080 | | * If no options are set, then a list of candidate coordinate operations will be |
1081 | | * researched, and at each call to Transform(), the best of those candidate |
1082 | | * regarding the centroid of the coordinate set will be dynamically selected. |
1083 | | * |
1084 | | * @param poSource source spatial reference system. |
1085 | | * @param poTarget target spatial reference system. |
1086 | | * @param options Coordinate transformation options. |
1087 | | * @return NULL on failure or a ready to use transformation object. |
1088 | | * @since GDAL 3.0 |
1089 | | */ |
1090 | | |
1091 | | OGRCoordinateTransformation *OGRCreateCoordinateTransformation( |
1092 | | const OGRSpatialReference *poSource, const OGRSpatialReference *poTarget, |
1093 | | const OGRCoordinateTransformationOptions &options) |
1094 | | |
1095 | 0 | { |
1096 | 0 | char *pszSrcSRS = poSource ? GetTextRepresentation(poSource) : nullptr; |
1097 | 0 | char *pszTargetSRS = poTarget ? GetTextRepresentation(poTarget) : nullptr; |
1098 | | // Try to find if we have a match in the case |
1099 | 0 | OGRProjCT *poCT = OGRProjCT::FindFromCache(poSource, pszSrcSRS, poTarget, |
1100 | 0 | pszTargetSRS, options); |
1101 | 0 | if (poCT == nullptr) |
1102 | 0 | { |
1103 | 0 | poCT = new OGRProjCT(); |
1104 | 0 | if (!poCT->Initialize(poSource, pszSrcSRS, poTarget, pszTargetSRS, |
1105 | 0 | options)) |
1106 | 0 | { |
1107 | 0 | delete poCT; |
1108 | 0 | poCT = nullptr; |
1109 | 0 | } |
1110 | 0 | } |
1111 | 0 | CPLFree(pszSrcSRS); |
1112 | 0 | CPLFree(pszTargetSRS); |
1113 | |
|
1114 | 0 | return poCT; |
1115 | 0 | } |
1116 | | |
1117 | | /************************************************************************/ |
1118 | | /* OCTNewCoordinateTransformation() */ |
1119 | | /************************************************************************/ |
1120 | | |
1121 | | /** |
1122 | | * Create transformation object. |
1123 | | * |
1124 | | * This is the same as the C++ function OGRCreateCoordinateTransformation(const |
1125 | | * OGRSpatialReference *, const OGRSpatialReference *) |
1126 | | * |
1127 | | * Input spatial reference system objects are assigned |
1128 | | * by copy (calling clone() method) and no ownership transfer occurs. |
1129 | | * |
1130 | | * OCTDestroyCoordinateTransformation() should |
1131 | | * be used to destroy transformation objects. |
1132 | | * |
1133 | | * This will honour the axis order advertized by the source and target SRS, |
1134 | | * as well as their "data axis to SRS axis mapping". |
1135 | | * To have a behavior similar to GDAL < 3.0, the |
1136 | | * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES. |
1137 | | * |
1138 | | * @param hSourceSRS source spatial reference system. |
1139 | | * @param hTargetSRS target spatial reference system. |
1140 | | * @return NULL on failure or a ready to use transformation object. |
1141 | | */ |
1142 | | |
1143 | | OGRCoordinateTransformationH CPL_STDCALL OCTNewCoordinateTransformation( |
1144 | | OGRSpatialReferenceH hSourceSRS, OGRSpatialReferenceH hTargetSRS) |
1145 | | |
1146 | 0 | { |
1147 | 0 | return reinterpret_cast<OGRCoordinateTransformationH>( |
1148 | 0 | OGRCreateCoordinateTransformation( |
1149 | 0 | reinterpret_cast<OGRSpatialReference *>(hSourceSRS), |
1150 | 0 | reinterpret_cast<OGRSpatialReference *>(hTargetSRS))); |
1151 | 0 | } |
1152 | | |
1153 | | /************************************************************************/ |
1154 | | /* OCTNewCoordinateTransformationEx() */ |
1155 | | /************************************************************************/ |
1156 | | |
1157 | | /** |
1158 | | * Create transformation object. |
1159 | | * |
1160 | | * This is the same as the C++ function OGRCreateCoordinateTransformation(const |
1161 | | * OGRSpatialReference *, const OGRSpatialReference *, const |
1162 | | * OGRCoordinateTransformationOptions& ) |
1163 | | * |
1164 | | * Input spatial reference system objects are assigned |
1165 | | * by copy (calling clone() method) and no ownership transfer occurs. |
1166 | | * |
1167 | | * OCTDestroyCoordinateTransformation() should |
1168 | | * be used to destroy transformation objects. |
1169 | | * |
1170 | | * The source SRS and target SRS should generally not be NULL. This is only |
1171 | | * allowed if a custom coordinate operation is set through the hOptions |
1172 | | * argument. |
1173 | | * |
1174 | | * This will honour the axis order advertized by the source and target SRS, |
1175 | | * as well as their "data axis to SRS axis mapping". |
1176 | | * To have a behavior similar to GDAL < 3.0, the |
1177 | | * OGR_CT_FORCE_TRADITIONAL_GIS_ORDER configuration option can be set to YES. |
1178 | | * |
1179 | | * If options contains a user defined coordinate transformation pipeline, it |
1180 | | * will be unconditionally used. |
1181 | | * If options has an area of interest defined, it will be used to research the |
1182 | | * best fitting coordinate transformation (which will be used for all coordinate |
1183 | | * transformations, even if they don't fall into the declared area of interest) |
1184 | | * If no options are set, then a list of candidate coordinate operations will be |
1185 | | * researched, and at each call to Transform(), the best of those candidate |
1186 | | * regarding the centroid of the coordinate set will be dynamically selected. |
1187 | | * |
1188 | | * @param hSourceSRS source spatial reference system. |
1189 | | * @param hTargetSRS target spatial reference system. |
1190 | | * @param hOptions Coordinate transformation options. |
1191 | | * @return NULL on failure or a ready to use transformation object. |
1192 | | * @since GDAL 3.0 |
1193 | | */ |
1194 | | |
1195 | | OGRCoordinateTransformationH |
1196 | | OCTNewCoordinateTransformationEx(OGRSpatialReferenceH hSourceSRS, |
1197 | | OGRSpatialReferenceH hTargetSRS, |
1198 | | OGRCoordinateTransformationOptionsH hOptions) |
1199 | | |
1200 | 0 | { |
1201 | 0 | return reinterpret_cast<OGRCoordinateTransformationH>( |
1202 | 0 | OGRCreateCoordinateTransformation( |
1203 | 0 | reinterpret_cast<OGRSpatialReference *>(hSourceSRS), |
1204 | 0 | reinterpret_cast<OGRSpatialReference *>(hTargetSRS), |
1205 | 0 | hOptions ? *hOptions : OGRCoordinateTransformationOptions())); |
1206 | 0 | } |
1207 | | |
1208 | | /************************************************************************/ |
1209 | | /* OCTClone() */ |
1210 | | /************************************************************************/ |
1211 | | |
1212 | | /** |
1213 | | * Clone transformation object. |
1214 | | * |
1215 | | * This is the same as the C++ function OGRCreateCoordinateTransformation::Clone |
1216 | | * |
1217 | | * @return handle to transformation's clone or NULL on error, |
1218 | | * must be freed with OCTDestroyCoordinateTransformation |
1219 | | * |
1220 | | * @since GDAL 3.4 |
1221 | | */ |
1222 | | |
1223 | | OGRCoordinateTransformationH OCTClone(OGRCoordinateTransformationH hTransform) |
1224 | | |
1225 | 0 | { |
1226 | 0 | VALIDATE_POINTER1(hTransform, "OCTClone", nullptr); |
1227 | 0 | return OGRCoordinateTransformation::ToHandle( |
1228 | 0 | OGRCoordinateTransformation::FromHandle(hTransform)->Clone()); |
1229 | 0 | } |
1230 | | |
1231 | | /************************************************************************/ |
1232 | | /* OCTGetSourceCS() */ |
1233 | | /************************************************************************/ |
1234 | | |
1235 | | /** |
1236 | | * Transformation's source coordinate system reference. |
1237 | | * |
1238 | | * This is the same as the C++ function |
1239 | | * OGRCreateCoordinateTransformation::GetSourceCS |
1240 | | * |
1241 | | * @return handle to transformation's source coordinate system or NULL if not |
1242 | | * present. |
1243 | | * |
1244 | | * The ownership of the returned SRS belongs to the transformation object, and |
1245 | | * the returned SRS should not be modified. |
1246 | | * |
1247 | | * @since GDAL 3.4 |
1248 | | */ |
1249 | | |
1250 | | OGRSpatialReferenceH OCTGetSourceCS(OGRCoordinateTransformationH hTransform) |
1251 | | |
1252 | 0 | { |
1253 | 0 | VALIDATE_POINTER1(hTransform, "OCTGetSourceCS", nullptr); |
1254 | 0 | return OGRSpatialReference::ToHandle(const_cast<OGRSpatialReference *>( |
1255 | 0 | OGRCoordinateTransformation::FromHandle(hTransform)->GetSourceCS())); |
1256 | 0 | } |
1257 | | |
1258 | | /************************************************************************/ |
1259 | | /* OCTGetTargetCS() */ |
1260 | | /************************************************************************/ |
1261 | | |
1262 | | /** |
1263 | | * Transformation's target coordinate system reference. |
1264 | | * |
1265 | | * This is the same as the C++ function |
1266 | | * OGRCreateCoordinateTransformation::GetTargetCS |
1267 | | * |
1268 | | * @return handle to transformation's target coordinate system or NULL if not |
1269 | | * present. |
1270 | | * |
1271 | | * The ownership of the returned SRS belongs to the transformation object, and |
1272 | | * the returned SRS should not be modified. |
1273 | | * |
1274 | | * @since GDAL 3.4 |
1275 | | */ |
1276 | | |
1277 | | OGRSpatialReferenceH OCTGetTargetCS(OGRCoordinateTransformationH hTransform) |
1278 | | |
1279 | 0 | { |
1280 | 0 | VALIDATE_POINTER1(hTransform, "OCTGetTargetCS", nullptr); |
1281 | 0 | return OGRSpatialReference::ToHandle(const_cast<OGRSpatialReference *>( |
1282 | 0 | OGRCoordinateTransformation::FromHandle(hTransform)->GetTargetCS())); |
1283 | 0 | } |
1284 | | |
1285 | | /************************************************************************/ |
1286 | | /* OCTGetInverse() */ |
1287 | | /************************************************************************/ |
1288 | | |
1289 | | /** |
1290 | | * Inverse transformation object. |
1291 | | * |
1292 | | * This is the same as the C++ function |
1293 | | * OGRCreateCoordinateTransformation::GetInverse |
1294 | | * |
1295 | | * @return handle to inverse transformation or NULL on error, |
1296 | | * must be freed with OCTDestroyCoordinateTransformation |
1297 | | * |
1298 | | * @since GDAL 3.4 |
1299 | | */ |
1300 | | |
1301 | | OGRCoordinateTransformationH CPL_DLL |
1302 | | OCTGetInverse(OGRCoordinateTransformationH hTransform) |
1303 | | |
1304 | 0 | { |
1305 | 0 | VALIDATE_POINTER1(hTransform, "OCTGetInverse", nullptr); |
1306 | 0 | return OGRCoordinateTransformation::ToHandle( |
1307 | 0 | OGRCoordinateTransformation::FromHandle(hTransform)->GetInverse()); |
1308 | 0 | } |
1309 | | |
1310 | | /************************************************************************/ |
1311 | | /* OGRProjCT() */ |
1312 | | /************************************************************************/ |
1313 | | |
1314 | | //! @cond Doxygen_Suppress |
1315 | | OGRProjCT::OGRProjCT() |
1316 | 0 | { |
1317 | 0 | } |
1318 | | |
1319 | | /************************************************************************/ |
1320 | | /* OGRProjCT(const OGRProjCT& other) */ |
1321 | | /************************************************************************/ |
1322 | | |
1323 | | OGRProjCT::OGRProjCT(const OGRProjCT &other) |
1324 | 0 | : poSRSSource((other.poSRSSource != nullptr) ? (other.poSRSSource->Clone()) |
1325 | 0 | : (nullptr)), |
1326 | 0 | m_eSourceFirstAxisOrient(other.m_eSourceFirstAxisOrient), |
1327 | 0 | bSourceLatLong(other.bSourceLatLong), bSourceWrap(other.bSourceWrap), |
1328 | 0 | dfSourceWrapLong(other.dfSourceWrapLong), |
1329 | 0 | bSourceIsDynamicCRS(other.bSourceIsDynamicCRS), |
1330 | 0 | dfSourceCoordinateEpoch(other.dfSourceCoordinateEpoch), |
1331 | 0 | m_osSrcSRS(other.m_osSrcSRS), |
1332 | 0 | poSRSTarget((other.poSRSTarget != nullptr) ? (other.poSRSTarget->Clone()) |
1333 | 0 | : (nullptr)), |
1334 | 0 | m_eTargetFirstAxisOrient(other.m_eTargetFirstAxisOrient), |
1335 | 0 | bTargetLatLong(other.bTargetLatLong), bTargetWrap(other.bTargetWrap), |
1336 | 0 | dfTargetWrapLong(other.dfTargetWrapLong), |
1337 | 0 | bTargetIsDynamicCRS(other.bTargetIsDynamicCRS), |
1338 | 0 | dfTargetCoordinateEpoch(other.dfTargetCoordinateEpoch), |
1339 | 0 | m_osTargetSRS(other.m_osTargetSRS), |
1340 | 0 | bWebMercatorToWGS84LongLat(other.bWebMercatorToWGS84LongLat), |
1341 | 0 | nErrorCount(other.nErrorCount), dfThreshold(other.dfThreshold), |
1342 | 0 | m_pj(other.m_pj), m_bReversePj(other.m_bReversePj), |
1343 | 0 | m_bEmitErrors(other.m_bEmitErrors), bNoTransform(other.bNoTransform), |
1344 | 0 | m_eStrategy(other.m_eStrategy), |
1345 | 0 | m_oTransformations(other.m_oTransformations), |
1346 | 0 | m_iCurTransformation(other.m_iCurTransformation), |
1347 | 0 | m_options(other.m_options), m_recordDifferentOperationsUsed(false), |
1348 | 0 | m_lastPjUsedPROJString(std::string()), m_differentOperationsUsed(false) |
1349 | 0 | { |
1350 | 0 | } |
1351 | | |
1352 | | /************************************************************************/ |
1353 | | /* ~OGRProjCT() */ |
1354 | | /************************************************************************/ |
1355 | | |
1356 | | OGRProjCT::~OGRProjCT() |
1357 | | |
1358 | 0 | { |
1359 | 0 | if (poSRSSource != nullptr) |
1360 | 0 | { |
1361 | 0 | poSRSSource->Release(); |
1362 | 0 | } |
1363 | |
|
1364 | 0 | if (poSRSTarget != nullptr) |
1365 | 0 | { |
1366 | 0 | poSRSTarget->Release(); |
1367 | 0 | } |
1368 | 0 | } |
1369 | | |
1370 | | /************************************************************************/ |
1371 | | /* ComputeThreshold() */ |
1372 | | /************************************************************************/ |
1373 | | |
1374 | | void OGRProjCT::ComputeThreshold() |
1375 | 0 | { |
1376 | | // The threshold is experimental. Works well with the cases of ticket #2305. |
1377 | 0 | if (bSourceLatLong) |
1378 | 0 | { |
1379 | | // coverity[tainted_data] |
1380 | 0 | dfThreshold = CPLAtof(CPLGetConfigOption("THRESHOLD", ".1")); |
1381 | 0 | } |
1382 | 0 | else |
1383 | 0 | { |
1384 | | // 1 works well for most projections, except for +proj=aeqd that |
1385 | | // requires a tolerance of 10000. |
1386 | | // coverity[tainted_data] |
1387 | 0 | dfThreshold = CPLAtof(CPLGetConfigOption("THRESHOLD", "10000")); |
1388 | 0 | } |
1389 | 0 | } |
1390 | | |
1391 | | /************************************************************************/ |
1392 | | /* DetectWebMercatorToWGS84() */ |
1393 | | /************************************************************************/ |
1394 | | |
1395 | | void OGRProjCT::DetectWebMercatorToWGS84() |
1396 | 0 | { |
1397 | | // Detect webmercator to WGS84 |
1398 | 0 | if (m_options.d->osCoordOperation.empty() && poSRSSource && poSRSTarget && |
1399 | 0 | poSRSSource->IsProjected() && poSRSTarget->IsGeographic() && |
1400 | 0 | ((m_eTargetFirstAxisOrient == OAO_North && |
1401 | 0 | poSRSTarget->GetDataAxisToSRSAxisMapping() == |
1402 | 0 | std::vector<int>{2, 1}) || |
1403 | 0 | (m_eTargetFirstAxisOrient == OAO_East && |
1404 | 0 | poSRSTarget->GetDataAxisToSRSAxisMapping() == |
1405 | 0 | std::vector<int>{1, 2}))) |
1406 | 0 | { |
1407 | | // Examine SRS ID before going to Proj4 string for faster execution |
1408 | | // This assumes that the SRS definition is "not lying", that is, it |
1409 | | // is equivalent to the resolution of the official EPSG code. |
1410 | 0 | const char *pszSourceAuth = poSRSSource->GetAuthorityName(nullptr); |
1411 | 0 | const char *pszSourceCode = poSRSSource->GetAuthorityCode(nullptr); |
1412 | 0 | const char *pszTargetAuth = poSRSTarget->GetAuthorityName(nullptr); |
1413 | 0 | const char *pszTargetCode = poSRSTarget->GetAuthorityCode(nullptr); |
1414 | 0 | if (pszSourceAuth && pszSourceCode && pszTargetAuth && pszTargetCode && |
1415 | 0 | EQUAL(pszSourceAuth, "EPSG") && EQUAL(pszTargetAuth, "EPSG")) |
1416 | 0 | { |
1417 | 0 | bWebMercatorToWGS84LongLat = |
1418 | 0 | (EQUAL(pszSourceCode, "3857") || |
1419 | 0 | EQUAL(pszSourceCode, "3785") || // deprecated |
1420 | 0 | EQUAL(pszSourceCode, "900913")) && // deprecated |
1421 | 0 | EQUAL(pszTargetCode, "4326"); |
1422 | 0 | } |
1423 | 0 | else |
1424 | 0 | { |
1425 | 0 | CPLPushErrorHandler(CPLQuietErrorHandler); |
1426 | 0 | char *pszSrcProj4Defn = nullptr; |
1427 | 0 | poSRSSource->exportToProj4(&pszSrcProj4Defn); |
1428 | |
|
1429 | 0 | char *pszDstProj4Defn = nullptr; |
1430 | 0 | poSRSTarget->exportToProj4(&pszDstProj4Defn); |
1431 | 0 | CPLPopErrorHandler(); |
1432 | |
|
1433 | 0 | if (pszSrcProj4Defn && pszDstProj4Defn) |
1434 | 0 | { |
1435 | 0 | if (pszSrcProj4Defn[0] != '\0' && |
1436 | 0 | pszSrcProj4Defn[strlen(pszSrcProj4Defn) - 1] == ' ') |
1437 | 0 | pszSrcProj4Defn[strlen(pszSrcProj4Defn) - 1] = 0; |
1438 | 0 | if (pszDstProj4Defn[0] != '\0' && |
1439 | 0 | pszDstProj4Defn[strlen(pszDstProj4Defn) - 1] == ' ') |
1440 | 0 | pszDstProj4Defn[strlen(pszDstProj4Defn) - 1] = 0; |
1441 | 0 | char *pszNeedle = strstr(pszSrcProj4Defn, " "); |
1442 | 0 | if (pszNeedle) |
1443 | 0 | memmove(pszNeedle, pszNeedle + 1, |
1444 | 0 | strlen(pszNeedle + 1) + 1); |
1445 | 0 | pszNeedle = strstr(pszDstProj4Defn, " "); |
1446 | 0 | if (pszNeedle) |
1447 | 0 | memmove(pszNeedle, pszNeedle + 1, |
1448 | 0 | strlen(pszNeedle + 1) + 1); |
1449 | |
|
1450 | 0 | if ((strstr(pszDstProj4Defn, "+datum=WGS84") != nullptr || |
1451 | 0 | strstr(pszDstProj4Defn, |
1452 | 0 | "+ellps=WGS84 +towgs84=0,0,0,0,0,0,0 ") != |
1453 | 0 | nullptr) && |
1454 | 0 | strstr(pszSrcProj4Defn, "+nadgrids=@null ") != nullptr && |
1455 | 0 | strstr(pszSrcProj4Defn, "+towgs84") == nullptr) |
1456 | 0 | { |
1457 | 0 | char *pszDst = |
1458 | 0 | strstr(pszDstProj4Defn, "+towgs84=0,0,0,0,0,0,0 "); |
1459 | 0 | if (pszDst != nullptr) |
1460 | 0 | { |
1461 | 0 | char *pszSrc = |
1462 | 0 | pszDst + strlen("+towgs84=0,0,0,0,0,0,0 "); |
1463 | 0 | memmove(pszDst, pszSrc, strlen(pszSrc) + 1); |
1464 | 0 | } |
1465 | 0 | else |
1466 | 0 | { |
1467 | 0 | memcpy(strstr(pszDstProj4Defn, "+datum=WGS84"), |
1468 | 0 | "+ellps", 6); |
1469 | 0 | } |
1470 | |
|
1471 | 0 | pszDst = strstr(pszSrcProj4Defn, "+nadgrids=@null "); |
1472 | 0 | char *pszSrc = pszDst + strlen("+nadgrids=@null "); |
1473 | 0 | memmove(pszDst, pszSrc, strlen(pszSrc) + 1); |
1474 | |
|
1475 | 0 | pszDst = strstr(pszSrcProj4Defn, "+wktext "); |
1476 | 0 | if (pszDst) |
1477 | 0 | { |
1478 | 0 | pszSrc = pszDst + strlen("+wktext "); |
1479 | 0 | memmove(pszDst, pszSrc, strlen(pszSrc) + 1); |
1480 | 0 | } |
1481 | 0 | bWebMercatorToWGS84LongLat = |
1482 | 0 | strcmp(pszDstProj4Defn, |
1483 | 0 | "+proj=longlat +ellps=WGS84 +no_defs") == 0 && |
1484 | 0 | (strcmp(pszSrcProj4Defn, |
1485 | 0 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 " |
1486 | 0 | "+lon_0=0.0 " |
1487 | 0 | "+x_0=0.0 +y_0=0 +k=1.0 +units=m +no_defs") == |
1488 | 0 | 0 || |
1489 | 0 | strcmp(pszSrcProj4Defn, |
1490 | 0 | "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 " |
1491 | 0 | "+lon_0=0 " |
1492 | 0 | "+x_0=0 +y_0=0 +k=1 +units=m +no_defs") == 0); |
1493 | 0 | } |
1494 | 0 | } |
1495 | |
|
1496 | 0 | CPLFree(pszSrcProj4Defn); |
1497 | 0 | CPLFree(pszDstProj4Defn); |
1498 | 0 | } |
1499 | |
|
1500 | 0 | if (bWebMercatorToWGS84LongLat) |
1501 | 0 | { |
1502 | 0 | CPLDebug("OGRCT", "Using WebMercator to WGS84 optimization"); |
1503 | 0 | } |
1504 | 0 | } |
1505 | 0 | } |
1506 | | |
1507 | | /************************************************************************/ |
1508 | | /* Initialize() */ |
1509 | | /************************************************************************/ |
1510 | | |
1511 | | int OGRProjCT::Initialize(const OGRSpatialReference *poSourceIn, |
1512 | | const char *pszSrcSRS, |
1513 | | const OGRSpatialReference *poTargetIn, |
1514 | | const char *pszTargetSRS, |
1515 | | const OGRCoordinateTransformationOptions &options) |
1516 | | |
1517 | 0 | { |
1518 | 0 | m_options = options; |
1519 | |
|
1520 | 0 | if (poSourceIn == nullptr || poTargetIn == nullptr) |
1521 | 0 | { |
1522 | 0 | if (options.d->osCoordOperation.empty()) |
1523 | 0 | { |
1524 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1525 | 0 | "OGRProjCT::Initialize(): if source and/or target CRS " |
1526 | 0 | "are null, a coordinate operation must be specified"); |
1527 | 0 | return FALSE; |
1528 | 0 | } |
1529 | 0 | } |
1530 | | |
1531 | 0 | if (poSourceIn) |
1532 | 0 | { |
1533 | 0 | poSRSSource = poSourceIn->Clone(); |
1534 | 0 | m_osSrcSRS = pszSrcSRS; |
1535 | 0 | } |
1536 | 0 | if (poTargetIn) |
1537 | 0 | { |
1538 | 0 | poSRSTarget = poTargetIn->Clone(); |
1539 | 0 | m_osTargetSRS = pszTargetSRS; |
1540 | 0 | } |
1541 | | |
1542 | | // To easy quick&dirty compatibility with GDAL < 3.0 |
1543 | 0 | if (CPLTestBool( |
1544 | 0 | CPLGetConfigOption("OGR_CT_FORCE_TRADITIONAL_GIS_ORDER", "NO"))) |
1545 | 0 | { |
1546 | 0 | if (poSRSSource) |
1547 | 0 | poSRSSource->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1548 | 0 | if (poSRSTarget) |
1549 | 0 | poSRSTarget->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
1550 | 0 | } |
1551 | |
|
1552 | 0 | if (poSRSSource) |
1553 | 0 | { |
1554 | 0 | bSourceLatLong = CPL_TO_BOOL(poSRSSource->IsGeographic()); |
1555 | 0 | bSourceIsDynamicCRS = poSRSSource->IsDynamic(); |
1556 | 0 | dfSourceCoordinateEpoch = poSRSSource->GetCoordinateEpoch(); |
1557 | 0 | if (!bSourceIsDynamicCRS && dfSourceCoordinateEpoch > 0) |
1558 | 0 | { |
1559 | 0 | bSourceIsDynamicCRS = poSRSSource->HasPointMotionOperation(); |
1560 | 0 | } |
1561 | 0 | poSRSSource->GetAxis(nullptr, 0, &m_eSourceFirstAxisOrient); |
1562 | 0 | } |
1563 | 0 | if (poSRSTarget) |
1564 | 0 | { |
1565 | 0 | bTargetLatLong = CPL_TO_BOOL(poSRSTarget->IsGeographic()); |
1566 | 0 | bTargetIsDynamicCRS = poSRSTarget->IsDynamic(); |
1567 | 0 | dfTargetCoordinateEpoch = poSRSTarget->GetCoordinateEpoch(); |
1568 | 0 | if (!bTargetIsDynamicCRS && dfTargetCoordinateEpoch > 0) |
1569 | 0 | { |
1570 | 0 | bTargetIsDynamicCRS = poSRSTarget->HasPointMotionOperation(); |
1571 | 0 | } |
1572 | 0 | poSRSTarget->GetAxis(nullptr, 0, &m_eTargetFirstAxisOrient); |
1573 | 0 | } |
1574 | |
|
1575 | | #if PROJ_VERSION_MAJOR < 9 || \ |
1576 | | (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR < 4) |
1577 | | if (bSourceIsDynamicCRS && bTargetIsDynamicCRS && |
1578 | | dfSourceCoordinateEpoch > 0 && dfTargetCoordinateEpoch > 0 && |
1579 | | dfSourceCoordinateEpoch != dfTargetCoordinateEpoch) |
1580 | | { |
1581 | | CPLError(CE_Warning, CPLE_AppDefined, |
1582 | | "Coordinate transformation between different epochs only" |
1583 | | "supported since PROJ 9.4"); |
1584 | | } |
1585 | | #endif |
1586 | | |
1587 | | /* -------------------------------------------------------------------- */ |
1588 | | /* Setup source and target translations to radians for lat/long */ |
1589 | | /* systems. */ |
1590 | | /* -------------------------------------------------------------------- */ |
1591 | 0 | bSourceWrap = false; |
1592 | 0 | dfSourceWrapLong = 0.0; |
1593 | |
|
1594 | 0 | bTargetWrap = false; |
1595 | 0 | dfTargetWrapLong = 0.0; |
1596 | | |
1597 | | /* -------------------------------------------------------------------- */ |
1598 | | /* Preliminary logic to setup wrapping. */ |
1599 | | /* -------------------------------------------------------------------- */ |
1600 | 0 | if (CPLGetConfigOption("CENTER_LONG", nullptr) != nullptr) |
1601 | 0 | { |
1602 | 0 | bSourceWrap = true; |
1603 | 0 | bTargetWrap = true; |
1604 | | // coverity[tainted_data] |
1605 | 0 | dfSourceWrapLong = dfTargetWrapLong = |
1606 | 0 | CPLAtof(CPLGetConfigOption("CENTER_LONG", "")); |
1607 | 0 | CPLDebug("OGRCT", "Wrap at %g.", dfSourceWrapLong); |
1608 | 0 | } |
1609 | |
|
1610 | 0 | const char *pszCENTER_LONG; |
1611 | 0 | { |
1612 | 0 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
1613 | 0 | pszCENTER_LONG = |
1614 | 0 | poSRSSource ? poSRSSource->GetExtension("GEOGCS", "CENTER_LONG") |
1615 | 0 | : nullptr; |
1616 | 0 | } |
1617 | 0 | if (pszCENTER_LONG != nullptr) |
1618 | 0 | { |
1619 | 0 | dfSourceWrapLong = CPLAtof(pszCENTER_LONG); |
1620 | 0 | bSourceWrap = true; |
1621 | 0 | CPLDebug("OGRCT", "Wrap source at %g.", dfSourceWrapLong); |
1622 | 0 | } |
1623 | 0 | else if (bSourceLatLong && options.d->bHasSourceCenterLong) |
1624 | 0 | { |
1625 | 0 | dfSourceWrapLong = options.d->dfSourceCenterLong; |
1626 | 0 | bSourceWrap = true; |
1627 | 0 | CPLDebug("OGRCT", "Wrap source at %g.", dfSourceWrapLong); |
1628 | 0 | } |
1629 | |
|
1630 | 0 | { |
1631 | 0 | CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler); |
1632 | 0 | pszCENTER_LONG = |
1633 | 0 | poSRSTarget ? poSRSTarget->GetExtension("GEOGCS", "CENTER_LONG") |
1634 | 0 | : nullptr; |
1635 | 0 | } |
1636 | 0 | if (pszCENTER_LONG != nullptr) |
1637 | 0 | { |
1638 | 0 | dfTargetWrapLong = CPLAtof(pszCENTER_LONG); |
1639 | 0 | bTargetWrap = true; |
1640 | 0 | CPLDebug("OGRCT", "Wrap target at %g.", dfTargetWrapLong); |
1641 | 0 | } |
1642 | 0 | else if (bTargetLatLong && options.d->bHasTargetCenterLong) |
1643 | 0 | { |
1644 | 0 | dfTargetWrapLong = options.d->dfTargetCenterLong; |
1645 | 0 | bTargetWrap = true; |
1646 | 0 | CPLDebug("OGRCT", "Wrap target at %g.", dfTargetWrapLong); |
1647 | 0 | } |
1648 | |
|
1649 | 0 | ComputeThreshold(); |
1650 | |
|
1651 | 0 | DetectWebMercatorToWGS84(); |
1652 | |
|
1653 | 0 | const char *pszCTOpSelection = |
1654 | 0 | CPLGetConfigOption("OGR_CT_OP_SELECTION", nullptr); |
1655 | 0 | if (pszCTOpSelection) |
1656 | 0 | { |
1657 | 0 | if (EQUAL(pszCTOpSelection, "PROJ")) |
1658 | 0 | m_eStrategy = Strategy::PROJ; |
1659 | 0 | else if (EQUAL(pszCTOpSelection, "BEST_ACCURACY")) |
1660 | 0 | m_eStrategy = Strategy::BEST_ACCURACY; |
1661 | 0 | else if (EQUAL(pszCTOpSelection, "FIRST_MATCHING")) |
1662 | 0 | m_eStrategy = Strategy::FIRST_MATCHING; |
1663 | 0 | else |
1664 | 0 | CPLError(CE_Warning, CPLE_NotSupported, |
1665 | 0 | "OGR_CT_OP_SELECTION=%s not supported", pszCTOpSelection); |
1666 | 0 | } |
1667 | | #if PROJ_VERSION_MAJOR < 8 |
1668 | | else |
1669 | | { |
1670 | | if (options.d->dfAccuracy >= 0 || !options.d->bAllowBallpark) |
1671 | | { |
1672 | | m_eStrategy = Strategy::BEST_ACCURACY; |
1673 | | } |
1674 | | } |
1675 | | #endif |
1676 | 0 | if (m_eStrategy == Strategy::PROJ) |
1677 | 0 | { |
1678 | 0 | const char *pszUseApproxTMERC = |
1679 | 0 | CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr); |
1680 | 0 | if (pszUseApproxTMERC && CPLTestBool(pszUseApproxTMERC)) |
1681 | 0 | { |
1682 | 0 | CPLDebug("OSRCT", "Using OGR_CT_OP_SELECTION=BEST_ACCURACY as " |
1683 | 0 | "OSR_USE_APPROX_TMERC is set"); |
1684 | 0 | m_eStrategy = Strategy::BEST_ACCURACY; |
1685 | 0 | } |
1686 | 0 | } |
1687 | |
|
1688 | 0 | if (!options.d->osCoordOperation.empty()) |
1689 | 0 | { |
1690 | 0 | auto ctx = OSRGetProjTLSContext(); |
1691 | 0 | m_pj = proj_create(ctx, options.d->osCoordOperation); |
1692 | 0 | if (!m_pj) |
1693 | 0 | { |
1694 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
1695 | 0 | "Cannot instantiate pipeline %s", |
1696 | 0 | options.d->osCoordOperation.c_str()); |
1697 | 0 | return FALSE; |
1698 | 0 | } |
1699 | 0 | m_bReversePj = options.d->bReverseCO; |
1700 | 0 | #ifdef DEBUG |
1701 | 0 | auto info = proj_pj_info(m_pj); |
1702 | 0 | CPLDebug("OGRCT", "%s %s(user set)", info.definition, |
1703 | 0 | m_bReversePj ? "(reversed) " : ""); |
1704 | 0 | #endif |
1705 | 0 | } |
1706 | 0 | else if (!bWebMercatorToWGS84LongLat && poSRSSource && poSRSTarget) |
1707 | 0 | { |
1708 | | #ifdef DEBUG_PERF |
1709 | | struct CPLTimeVal tvStart; |
1710 | | CPLGettimeofday(&tvStart, nullptr); |
1711 | | CPLDebug("OGR_CT", "Before proj_create_crs_to_crs()"); |
1712 | | #endif |
1713 | 0 | #ifdef DEBUG |
1714 | 0 | CPLDebug("OGR_CT", "Source CRS: '%s'", pszSrcSRS); |
1715 | 0 | if (dfSourceCoordinateEpoch > 0) |
1716 | 0 | CPLDebug("OGR_CT", "Source coordinate epoch: %.3f", |
1717 | 0 | dfSourceCoordinateEpoch); |
1718 | 0 | CPLDebug("OGR_CT", "Target CRS: '%s'", pszTargetSRS); |
1719 | 0 | if (dfTargetCoordinateEpoch > 0) |
1720 | 0 | CPLDebug("OGR_CT", "Target coordinate epoch: %.3f", |
1721 | 0 | dfTargetCoordinateEpoch); |
1722 | 0 | #endif |
1723 | |
|
1724 | 0 | if (m_eStrategy == Strategy::PROJ) |
1725 | 0 | { |
1726 | 0 | PJ_AREA *area = nullptr; |
1727 | 0 | if (options.d->bHasAreaOfInterest) |
1728 | 0 | { |
1729 | 0 | area = proj_area_create(); |
1730 | 0 | proj_area_set_bbox(area, options.d->dfWestLongitudeDeg, |
1731 | 0 | options.d->dfSouthLatitudeDeg, |
1732 | 0 | options.d->dfEastLongitudeDeg, |
1733 | 0 | options.d->dfNorthLatitudeDeg); |
1734 | 0 | } |
1735 | 0 | auto ctx = OSRGetProjTLSContext(); |
1736 | 0 | #if PROJ_VERSION_MAJOR >= 8 |
1737 | 0 | auto srcCRS = proj_create(ctx, pszSrcSRS); |
1738 | 0 | auto targetCRS = proj_create(ctx, pszTargetSRS); |
1739 | 0 | if (srcCRS == nullptr || targetCRS == nullptr) |
1740 | 0 | { |
1741 | 0 | proj_destroy(srcCRS); |
1742 | 0 | proj_destroy(targetCRS); |
1743 | 0 | if (area) |
1744 | 0 | proj_area_destroy(area); |
1745 | 0 | return FALSE; |
1746 | 0 | } |
1747 | 0 | CPLStringList aosOptions; |
1748 | 0 | if (options.d->dfAccuracy >= 0) |
1749 | 0 | aosOptions.SetNameValue( |
1750 | 0 | "ACCURACY", CPLSPrintf("%.17g", options.d->dfAccuracy)); |
1751 | 0 | if (!options.d->bAllowBallpark) |
1752 | 0 | aosOptions.SetNameValue("ALLOW_BALLPARK", "NO"); |
1753 | 0 | #if PROJ_VERSION_MAJOR > 9 || \ |
1754 | 0 | (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 2) |
1755 | 0 | if (options.d->bOnlyBestOptionSet) |
1756 | 0 | { |
1757 | 0 | aosOptions.SetNameValue("ONLY_BEST", |
1758 | 0 | options.d->bOnlyBest ? "YES" : "NO"); |
1759 | 0 | } |
1760 | 0 | #endif |
1761 | |
|
1762 | 0 | #if PROJ_VERSION_MAJOR > 9 || \ |
1763 | 0 | (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 4) |
1764 | 0 | if (bSourceIsDynamicCRS && dfSourceCoordinateEpoch > 0 && |
1765 | 0 | bTargetIsDynamicCRS && dfTargetCoordinateEpoch > 0) |
1766 | 0 | { |
1767 | 0 | auto srcCM = proj_coordinate_metadata_create( |
1768 | 0 | ctx, srcCRS, dfSourceCoordinateEpoch); |
1769 | 0 | proj_destroy(srcCRS); |
1770 | 0 | srcCRS = srcCM; |
1771 | |
|
1772 | 0 | auto targetCM = proj_coordinate_metadata_create( |
1773 | 0 | ctx, targetCRS, dfTargetCoordinateEpoch); |
1774 | 0 | proj_destroy(targetCRS); |
1775 | 0 | targetCRS = targetCM; |
1776 | 0 | } |
1777 | 0 | #endif |
1778 | |
|
1779 | 0 | m_pj = proj_create_crs_to_crs_from_pj(ctx, srcCRS, targetCRS, area, |
1780 | 0 | aosOptions.List()); |
1781 | 0 | proj_destroy(srcCRS); |
1782 | 0 | proj_destroy(targetCRS); |
1783 | | #else |
1784 | | m_pj = proj_create_crs_to_crs(ctx, pszSrcSRS, pszTargetSRS, area); |
1785 | | #endif |
1786 | 0 | if (area) |
1787 | 0 | proj_area_destroy(area); |
1788 | 0 | if (m_pj == nullptr) |
1789 | 0 | { |
1790 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
1791 | 0 | "Cannot find coordinate operations from `%s' to `%s'", |
1792 | 0 | pszSrcSRS, pszTargetSRS); |
1793 | 0 | return FALSE; |
1794 | 0 | } |
1795 | 0 | } |
1796 | 0 | else if (!ListCoordinateOperations(pszSrcSRS, pszTargetSRS, options)) |
1797 | 0 | { |
1798 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
1799 | 0 | "Cannot find coordinate operations from `%s' to `%s'", |
1800 | 0 | pszSrcSRS, pszTargetSRS); |
1801 | 0 | return FALSE; |
1802 | 0 | } |
1803 | | #ifdef DEBUG_PERF |
1804 | | struct CPLTimeVal tvEnd; |
1805 | | CPLGettimeofday(&tvEnd, nullptr); |
1806 | | const double delay = (tvEnd.tv_sec + tvEnd.tv_usec * 1e-6) - |
1807 | | (tvStart.tv_sec + tvStart.tv_usec * 1e-6); |
1808 | | g_dfTotalTimeCRStoCRS += delay; |
1809 | | CPLDebug("OGR_CT", "After proj_create_crs_to_crs(): %d ms", |
1810 | | static_cast<int>(delay * 1000)); |
1811 | | #endif |
1812 | 0 | } |
1813 | | |
1814 | 0 | if (options.d->osCoordOperation.empty() && poSRSSource && poSRSTarget && |
1815 | 0 | (dfSourceCoordinateEpoch == 0 || dfTargetCoordinateEpoch == 0 || |
1816 | 0 | dfSourceCoordinateEpoch == dfTargetCoordinateEpoch)) |
1817 | 0 | { |
1818 | | // Determine if we can skip the transformation completely. |
1819 | 0 | const char *const apszOptionsIsSame[] = {"CRITERION=EQUIVALENT", |
1820 | 0 | nullptr}; |
1821 | 0 | bNoTransform = |
1822 | 0 | !bSourceWrap && !bTargetWrap && |
1823 | 0 | CPL_TO_BOOL(poSRSSource->IsSame(poSRSTarget, apszOptionsIsSame)); |
1824 | 0 | } |
1825 | |
|
1826 | 0 | return TRUE; |
1827 | 0 | } |
1828 | | |
1829 | | /************************************************************************/ |
1830 | | /* op_to_pj() */ |
1831 | | /************************************************************************/ |
1832 | | |
1833 | | static PJ *op_to_pj(PJ_CONTEXT *ctx, PJ *op, |
1834 | | CPLString *osOutProjString = nullptr) |
1835 | 0 | { |
1836 | | // OSR_USE_ETMERC is here just for legacy |
1837 | 0 | bool bForceApproxTMerc = false; |
1838 | 0 | const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr); |
1839 | 0 | if (pszUseETMERC && pszUseETMERC[0]) |
1840 | 0 | { |
1841 | 0 | CPLErrorOnce(CE_Warning, CPLE_AppDefined, |
1842 | 0 | "OSR_USE_ETMERC is a legacy configuration option, which " |
1843 | 0 | "now has only effect when set to NO (YES is the default). " |
1844 | 0 | "Use OSR_USE_APPROX_TMERC=YES instead"); |
1845 | 0 | bForceApproxTMerc = !CPLTestBool(pszUseETMERC); |
1846 | 0 | } |
1847 | 0 | else |
1848 | 0 | { |
1849 | 0 | const char *pszUseApproxTMERC = |
1850 | 0 | CPLGetConfigOption("OSR_USE_APPROX_TMERC", nullptr); |
1851 | 0 | if (pszUseApproxTMERC && pszUseApproxTMERC[0]) |
1852 | 0 | { |
1853 | 0 | bForceApproxTMerc = CPLTestBool(pszUseApproxTMERC); |
1854 | 0 | } |
1855 | 0 | } |
1856 | 0 | const char *options[] = { |
1857 | 0 | bForceApproxTMerc ? "USE_APPROX_TMERC=YES" : nullptr, nullptr}; |
1858 | 0 | auto proj_string = proj_as_proj_string(ctx, op, PJ_PROJ_5, options); |
1859 | 0 | if (!proj_string) |
1860 | 0 | { |
1861 | 0 | return nullptr; |
1862 | 0 | } |
1863 | 0 | if (osOutProjString) |
1864 | 0 | *osOutProjString = proj_string; |
1865 | |
|
1866 | 0 | if (proj_string[0] == '\0') |
1867 | 0 | { |
1868 | | /* Null transform ? */ |
1869 | 0 | return proj_create(ctx, "proj=affine"); |
1870 | 0 | } |
1871 | 0 | else |
1872 | 0 | { |
1873 | 0 | return proj_create(ctx, proj_string); |
1874 | 0 | } |
1875 | 0 | } |
1876 | | |
1877 | | /************************************************************************/ |
1878 | | /* ListCoordinateOperations() */ |
1879 | | /************************************************************************/ |
1880 | | |
1881 | | bool OGRProjCT::ListCoordinateOperations( |
1882 | | const char *pszSrcSRS, const char *pszTargetSRS, |
1883 | | const OGRCoordinateTransformationOptions &options) |
1884 | 0 | { |
1885 | 0 | auto ctx = OSRGetProjTLSContext(); |
1886 | |
|
1887 | 0 | auto src = proj_create(ctx, pszSrcSRS); |
1888 | 0 | if (!src) |
1889 | 0 | { |
1890 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot instantiate source_crs"); |
1891 | 0 | return false; |
1892 | 0 | } |
1893 | | |
1894 | 0 | auto dst = proj_create(ctx, pszTargetSRS); |
1895 | 0 | if (!dst) |
1896 | 0 | { |
1897 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Cannot instantiate target_crs"); |
1898 | 0 | proj_destroy(src); |
1899 | 0 | return false; |
1900 | 0 | } |
1901 | | |
1902 | 0 | auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr); |
1903 | 0 | if (!operation_ctx) |
1904 | 0 | { |
1905 | 0 | proj_destroy(src); |
1906 | 0 | proj_destroy(dst); |
1907 | 0 | return false; |
1908 | 0 | } |
1909 | | |
1910 | 0 | proj_operation_factory_context_set_spatial_criterion( |
1911 | 0 | ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); |
1912 | 0 | proj_operation_factory_context_set_grid_availability_use( |
1913 | 0 | ctx, operation_ctx, |
1914 | 0 | #if PROJ_VERSION_MAJOR >= 7 |
1915 | 0 | proj_context_is_network_enabled(ctx) |
1916 | 0 | ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE |
1917 | 0 | : |
1918 | 0 | #endif |
1919 | 0 | PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); |
1920 | |
|
1921 | 0 | if (options.d->bHasAreaOfInterest) |
1922 | 0 | { |
1923 | 0 | proj_operation_factory_context_set_area_of_interest( |
1924 | 0 | ctx, operation_ctx, options.d->dfWestLongitudeDeg, |
1925 | 0 | options.d->dfSouthLatitudeDeg, options.d->dfEastLongitudeDeg, |
1926 | 0 | options.d->dfNorthLatitudeDeg); |
1927 | 0 | } |
1928 | |
|
1929 | 0 | if (options.d->dfAccuracy >= 0) |
1930 | 0 | proj_operation_factory_context_set_desired_accuracy( |
1931 | 0 | ctx, operation_ctx, options.d->dfAccuracy); |
1932 | 0 | if (!options.d->bAllowBallpark) |
1933 | 0 | { |
1934 | 0 | #if PROJ_VERSION_MAJOR > 7 || \ |
1935 | 0 | (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 1) |
1936 | 0 | proj_operation_factory_context_set_allow_ballpark_transformations( |
1937 | 0 | ctx, operation_ctx, FALSE); |
1938 | | #else |
1939 | | if (options.d->dfAccuracy < 0) |
1940 | | { |
1941 | | proj_operation_factory_context_set_desired_accuracy( |
1942 | | ctx, operation_ctx, HUGE_VAL); |
1943 | | } |
1944 | | #endif |
1945 | 0 | } |
1946 | |
|
1947 | 0 | auto op_list = proj_create_operations(ctx, src, dst, operation_ctx); |
1948 | |
|
1949 | 0 | if (!op_list) |
1950 | 0 | { |
1951 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
1952 | 0 | proj_destroy(src); |
1953 | 0 | proj_destroy(dst); |
1954 | 0 | return false; |
1955 | 0 | } |
1956 | | |
1957 | 0 | auto op_count = proj_list_get_count(op_list); |
1958 | 0 | if (op_count == 0) |
1959 | 0 | { |
1960 | 0 | proj_list_destroy(op_list); |
1961 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
1962 | 0 | proj_destroy(src); |
1963 | 0 | proj_destroy(dst); |
1964 | 0 | CPLDebug("OGRCT", "No operation found matching criteria"); |
1965 | 0 | return false; |
1966 | 0 | } |
1967 | | |
1968 | 0 | if (op_count == 1 || options.d->bHasAreaOfInterest || |
1969 | 0 | proj_get_type(src) == PJ_TYPE_GEOCENTRIC_CRS || |
1970 | 0 | proj_get_type(dst) == PJ_TYPE_GEOCENTRIC_CRS) |
1971 | 0 | { |
1972 | 0 | auto op = proj_list_get(ctx, op_list, 0); |
1973 | 0 | CPLAssert(op); |
1974 | 0 | m_pj = op_to_pj(ctx, op); |
1975 | 0 | CPLString osName; |
1976 | 0 | auto name = proj_get_name(op); |
1977 | 0 | if (name) |
1978 | 0 | osName = name; |
1979 | 0 | proj_destroy(op); |
1980 | 0 | proj_list_destroy(op_list); |
1981 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
1982 | 0 | proj_destroy(src); |
1983 | 0 | proj_destroy(dst); |
1984 | 0 | if (!m_pj) |
1985 | 0 | return false; |
1986 | 0 | #ifdef DEBUG |
1987 | 0 | auto info = proj_pj_info(m_pj); |
1988 | 0 | CPLDebug("OGRCT", "%s (%s)", info.definition, osName.c_str()); |
1989 | 0 | #endif |
1990 | 0 | return true; |
1991 | 0 | } |
1992 | | |
1993 | | // Create a geographic 2D long-lat degrees CRS that is related to the |
1994 | | // source CRS |
1995 | 0 | auto geodetic_crs = proj_crs_get_geodetic_crs(ctx, src); |
1996 | 0 | if (!geodetic_crs) |
1997 | 0 | { |
1998 | 0 | proj_list_destroy(op_list); |
1999 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
2000 | 0 | proj_destroy(src); |
2001 | 0 | proj_destroy(dst); |
2002 | 0 | CPLDebug("OGRCT", "Cannot find geodetic CRS matching source CRS"); |
2003 | 0 | return false; |
2004 | 0 | } |
2005 | 0 | auto geodetic_crs_type = proj_get_type(geodetic_crs); |
2006 | 0 | if (geodetic_crs_type == PJ_TYPE_GEOCENTRIC_CRS || |
2007 | 0 | geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_2D_CRS || |
2008 | 0 | geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_3D_CRS) |
2009 | 0 | { |
2010 | 0 | auto datum = proj_crs_get_datum(ctx, geodetic_crs); |
2011 | 0 | #if PROJ_VERSION_MAJOR > 7 || \ |
2012 | 0 | (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2) |
2013 | 0 | if (datum == nullptr) |
2014 | 0 | { |
2015 | 0 | datum = proj_crs_get_datum_forced(ctx, geodetic_crs); |
2016 | 0 | } |
2017 | 0 | #endif |
2018 | 0 | if (datum) |
2019 | 0 | { |
2020 | 0 | auto ellps = proj_get_ellipsoid(ctx, datum); |
2021 | 0 | proj_destroy(datum); |
2022 | 0 | double semi_major_metre = 0; |
2023 | 0 | double inv_flattening = 0; |
2024 | 0 | proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, |
2025 | 0 | nullptr, nullptr, &inv_flattening); |
2026 | 0 | auto cs = proj_create_ellipsoidal_2D_cs( |
2027 | 0 | ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); |
2028 | | // It is critical to set the prime meridian to 0 |
2029 | 0 | auto temp = proj_create_geographic_crs( |
2030 | 0 | ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps), |
2031 | 0 | semi_major_metre, inv_flattening, "Reference prime meridian", 0, |
2032 | 0 | nullptr, 0, cs); |
2033 | 0 | proj_destroy(ellps); |
2034 | 0 | proj_destroy(cs); |
2035 | 0 | proj_destroy(geodetic_crs); |
2036 | 0 | geodetic_crs = temp; |
2037 | 0 | geodetic_crs_type = proj_get_type(geodetic_crs); |
2038 | 0 | } |
2039 | 0 | } |
2040 | 0 | if (geodetic_crs_type != PJ_TYPE_GEOGRAPHIC_2D_CRS) |
2041 | 0 | { |
2042 | | // Shouldn't happen |
2043 | 0 | proj_list_destroy(op_list); |
2044 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
2045 | 0 | proj_destroy(src); |
2046 | 0 | proj_destroy(dst); |
2047 | 0 | proj_destroy(geodetic_crs); |
2048 | 0 | CPLDebug("OGRCT", "Cannot find geographic CRS matching source CRS"); |
2049 | 0 | return false; |
2050 | 0 | } |
2051 | | |
2052 | | // Create the transformation from this geographic 2D CRS to the source CRS |
2053 | 0 | auto op_list_to_geodetic = |
2054 | 0 | proj_create_operations(ctx, geodetic_crs, src, operation_ctx); |
2055 | 0 | proj_destroy(geodetic_crs); |
2056 | |
|
2057 | 0 | if (op_list_to_geodetic == nullptr || |
2058 | 0 | proj_list_get_count(op_list_to_geodetic) == 0) |
2059 | 0 | { |
2060 | 0 | CPLDebug( |
2061 | 0 | "OGRCT", |
2062 | 0 | "Cannot compute transformation from geographic CRS to source CRS"); |
2063 | 0 | proj_list_destroy(op_list); |
2064 | 0 | proj_list_destroy(op_list_to_geodetic); |
2065 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
2066 | 0 | proj_destroy(src); |
2067 | 0 | proj_destroy(dst); |
2068 | 0 | return false; |
2069 | 0 | } |
2070 | 0 | auto opGeogToSrc = proj_list_get(ctx, op_list_to_geodetic, 0); |
2071 | 0 | CPLAssert(opGeogToSrc); |
2072 | 0 | proj_list_destroy(op_list_to_geodetic); |
2073 | 0 | auto pjGeogToSrc = op_to_pj(ctx, opGeogToSrc); |
2074 | 0 | proj_destroy(opGeogToSrc); |
2075 | 0 | if (!pjGeogToSrc) |
2076 | 0 | { |
2077 | 0 | proj_list_destroy(op_list); |
2078 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
2079 | 0 | proj_destroy(src); |
2080 | 0 | proj_destroy(dst); |
2081 | 0 | return false; |
2082 | 0 | } |
2083 | | |
2084 | 0 | const auto addTransformation = |
2085 | 0 | [this, &pjGeogToSrc, &ctx](PJ *op, double west_lon, double south_lat, |
2086 | 0 | double east_lon, double north_lat) |
2087 | 0 | { |
2088 | 0 | double minx = -std::numeric_limits<double>::max(); |
2089 | 0 | double miny = -std::numeric_limits<double>::max(); |
2090 | 0 | double maxx = std::numeric_limits<double>::max(); |
2091 | 0 | double maxy = std::numeric_limits<double>::max(); |
2092 | |
|
2093 | 0 | if (!(west_lon == -180.0 && east_lon == 180.0 && south_lat == -90.0 && |
2094 | 0 | north_lat == 90.0)) |
2095 | 0 | { |
2096 | 0 | minx = -minx; |
2097 | 0 | miny = -miny; |
2098 | 0 | maxx = -maxx; |
2099 | 0 | maxy = -maxy; |
2100 | |
|
2101 | 0 | double x[21 * 4], y[21 * 4]; |
2102 | 0 | for (int j = 0; j <= 20; j++) |
2103 | 0 | { |
2104 | 0 | x[j] = west_lon + j * (east_lon - west_lon) / 20; |
2105 | 0 | y[j] = south_lat; |
2106 | 0 | x[21 + j] = west_lon + j * (east_lon - west_lon) / 20; |
2107 | 0 | y[21 + j] = north_lat; |
2108 | 0 | x[21 * 2 + j] = west_lon; |
2109 | 0 | y[21 * 2 + j] = south_lat + j * (north_lat - south_lat) / 20; |
2110 | 0 | x[21 * 3 + j] = east_lon; |
2111 | 0 | y[21 * 3 + j] = south_lat + j * (north_lat - south_lat) / 20; |
2112 | 0 | } |
2113 | 0 | proj_trans_generic(pjGeogToSrc, PJ_FWD, x, sizeof(double), 21 * 4, |
2114 | 0 | y, sizeof(double), 21 * 4, nullptr, 0, 0, |
2115 | 0 | nullptr, 0, 0); |
2116 | 0 | for (int j = 0; j < 21 * 4; j++) |
2117 | 0 | { |
2118 | 0 | if (x[j] != HUGE_VAL && y[j] != HUGE_VAL) |
2119 | 0 | { |
2120 | 0 | minx = std::min(minx, x[j]); |
2121 | 0 | miny = std::min(miny, y[j]); |
2122 | 0 | maxx = std::max(maxx, x[j]); |
2123 | 0 | maxy = std::max(maxy, y[j]); |
2124 | 0 | } |
2125 | 0 | } |
2126 | 0 | } |
2127 | |
|
2128 | 0 | if (minx <= maxx) |
2129 | 0 | { |
2130 | 0 | CPLString osProjString; |
2131 | 0 | const double accuracy = proj_coordoperation_get_accuracy(ctx, op); |
2132 | 0 | auto pj = op_to_pj(ctx, op, &osProjString); |
2133 | 0 | CPLString osName; |
2134 | 0 | auto name = proj_get_name(op); |
2135 | 0 | if (name) |
2136 | 0 | osName = name; |
2137 | 0 | proj_destroy(op); |
2138 | 0 | op = nullptr; |
2139 | 0 | if (pj) |
2140 | 0 | { |
2141 | 0 | m_oTransformations.emplace_back(minx, miny, maxx, maxy, pj, |
2142 | 0 | osName, osProjString, accuracy); |
2143 | 0 | } |
2144 | 0 | } |
2145 | 0 | return op; |
2146 | 0 | }; |
2147 | | |
2148 | | // Iterate over source->target candidate transformations and reproject |
2149 | | // their long-lat bounding box into the source CRS. |
2150 | 0 | bool foundWorldTransformation = false; |
2151 | 0 | for (int i = 0; i < op_count; i++) |
2152 | 0 | { |
2153 | 0 | auto op = proj_list_get(ctx, op_list, i); |
2154 | 0 | CPLAssert(op); |
2155 | 0 | double west_lon = 0.0; |
2156 | 0 | double south_lat = 0.0; |
2157 | 0 | double east_lon = 0.0; |
2158 | 0 | double north_lat = 0.0; |
2159 | 0 | if (proj_get_area_of_use(ctx, op, &west_lon, &south_lat, &east_lon, |
2160 | 0 | &north_lat, nullptr)) |
2161 | 0 | { |
2162 | 0 | if (west_lon <= east_lon) |
2163 | 0 | { |
2164 | 0 | if (west_lon == -180 && east_lon == 180 && south_lat == -90 && |
2165 | 0 | north_lat == 90) |
2166 | 0 | { |
2167 | 0 | foundWorldTransformation = true; |
2168 | 0 | } |
2169 | 0 | op = addTransformation(op, west_lon, south_lat, east_lon, |
2170 | 0 | north_lat); |
2171 | 0 | } |
2172 | 0 | else |
2173 | 0 | { |
2174 | 0 | auto op_clone = proj_clone(ctx, op); |
2175 | |
|
2176 | 0 | op = addTransformation(op, west_lon, south_lat, 180, north_lat); |
2177 | 0 | op_clone = addTransformation(op_clone, -180, south_lat, |
2178 | 0 | east_lon, north_lat); |
2179 | 0 | proj_destroy(op_clone); |
2180 | 0 | } |
2181 | 0 | } |
2182 | |
|
2183 | 0 | proj_destroy(op); |
2184 | 0 | } |
2185 | | |
2186 | 0 | proj_list_destroy(op_list); |
2187 | | |
2188 | | // Sometimes the user will operate even outside the area of use of the |
2189 | | // source and target CRS, so if no global transformation has been returned |
2190 | | // previously, trigger the computation of one. |
2191 | 0 | if (!foundWorldTransformation) |
2192 | 0 | { |
2193 | 0 | proj_operation_factory_context_set_area_of_interest(ctx, operation_ctx, |
2194 | 0 | -180, -90, 180, 90); |
2195 | 0 | proj_operation_factory_context_set_spatial_criterion( |
2196 | 0 | ctx, operation_ctx, PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT); |
2197 | 0 | op_list = proj_create_operations(ctx, src, dst, operation_ctx); |
2198 | 0 | if (op_list) |
2199 | 0 | { |
2200 | 0 | op_count = proj_list_get_count(op_list); |
2201 | 0 | for (int i = 0; i < op_count; i++) |
2202 | 0 | { |
2203 | 0 | auto op = proj_list_get(ctx, op_list, i); |
2204 | 0 | CPLAssert(op); |
2205 | 0 | double west_lon = 0.0; |
2206 | 0 | double south_lat = 0.0; |
2207 | 0 | double east_lon = 0.0; |
2208 | 0 | double north_lat = 0.0; |
2209 | 0 | if (proj_get_area_of_use(ctx, op, &west_lon, &south_lat, |
2210 | 0 | &east_lon, &north_lat, nullptr) && |
2211 | 0 | west_lon == -180 && east_lon == 180 && south_lat == -90 && |
2212 | 0 | north_lat == 90) |
2213 | 0 | { |
2214 | 0 | op = addTransformation(op, west_lon, south_lat, east_lon, |
2215 | 0 | north_lat); |
2216 | 0 | } |
2217 | 0 | proj_destroy(op); |
2218 | 0 | } |
2219 | 0 | } |
2220 | 0 | proj_list_destroy(op_list); |
2221 | 0 | } |
2222 | | |
2223 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
2224 | 0 | proj_destroy(src); |
2225 | 0 | proj_destroy(dst); |
2226 | 0 | proj_destroy(pjGeogToSrc); |
2227 | 0 | return !m_oTransformations.empty(); |
2228 | 0 | } |
2229 | | |
2230 | | /************************************************************************/ |
2231 | | /* GetSourceCS() */ |
2232 | | /************************************************************************/ |
2233 | | |
2234 | | const OGRSpatialReference *OGRProjCT::GetSourceCS() const |
2235 | | |
2236 | 0 | { |
2237 | 0 | return poSRSSource; |
2238 | 0 | } |
2239 | | |
2240 | | /************************************************************************/ |
2241 | | /* GetTargetCS() */ |
2242 | | /************************************************************************/ |
2243 | | |
2244 | | const OGRSpatialReference *OGRProjCT::GetTargetCS() const |
2245 | | |
2246 | 0 | { |
2247 | 0 | return poSRSTarget; |
2248 | 0 | } |
2249 | | |
2250 | | /************************************************************************/ |
2251 | | /* Transform() */ |
2252 | | /************************************************************************/ |
2253 | | |
2254 | | int OGRCoordinateTransformation::Transform(size_t nCount, double *x, double *y, |
2255 | | double *z, int *pabSuccessIn) |
2256 | | |
2257 | 0 | { |
2258 | 0 | int *pabSuccess = |
2259 | 0 | pabSuccessIn |
2260 | 0 | ? pabSuccessIn |
2261 | 0 | : static_cast<int *>(VSI_MALLOC2_VERBOSE(sizeof(int), nCount)); |
2262 | 0 | if (!pabSuccess) |
2263 | 0 | return FALSE; |
2264 | | |
2265 | 0 | const int bRet = Transform(nCount, x, y, z, nullptr, pabSuccess); |
2266 | |
|
2267 | 0 | if (pabSuccess != pabSuccessIn) |
2268 | 0 | CPLFree(pabSuccess); |
2269 | |
|
2270 | 0 | return bRet; |
2271 | 0 | } |
2272 | | |
2273 | | /************************************************************************/ |
2274 | | /* TransformWithErrorCodes() */ |
2275 | | /************************************************************************/ |
2276 | | |
2277 | | int OGRCoordinateTransformation::TransformWithErrorCodes(size_t nCount, |
2278 | | double *x, double *y, |
2279 | | double *z, double *t, |
2280 | | int *panErrorCodes) |
2281 | | |
2282 | 0 | { |
2283 | 0 | if (nCount == 1) |
2284 | 0 | { |
2285 | 0 | int nSuccess = 0; |
2286 | 0 | const int bRet = Transform(nCount, x, y, z, t, &nSuccess); |
2287 | 0 | if (panErrorCodes) |
2288 | 0 | { |
2289 | 0 | panErrorCodes[0] = nSuccess ? 0 : -1; |
2290 | 0 | } |
2291 | 0 | return bRet; |
2292 | 0 | } |
2293 | | |
2294 | 0 | std::vector<int> abSuccess; |
2295 | 0 | try |
2296 | 0 | { |
2297 | 0 | abSuccess.resize(nCount); |
2298 | 0 | } |
2299 | 0 | catch (const std::bad_alloc &) |
2300 | 0 | { |
2301 | 0 | CPLError(CE_Failure, CPLE_OutOfMemory, |
2302 | 0 | "Cannot allocate abSuccess[] temporary array"); |
2303 | 0 | return FALSE; |
2304 | 0 | } |
2305 | | |
2306 | 0 | const int bRet = Transform(nCount, x, y, z, t, abSuccess.data()); |
2307 | |
|
2308 | 0 | if (panErrorCodes) |
2309 | 0 | { |
2310 | 0 | for (size_t i = 0; i < nCount; i++) |
2311 | 0 | { |
2312 | 0 | panErrorCodes[i] = abSuccess[i] ? 0 : -1; |
2313 | 0 | } |
2314 | 0 | } |
2315 | |
|
2316 | 0 | return bRet; |
2317 | 0 | } |
2318 | | |
2319 | | /************************************************************************/ |
2320 | | /* Transform() */ |
2321 | | /************************************************************************/ |
2322 | | |
2323 | | int OGRProjCT::Transform(size_t nCount, double *x, double *y, double *z, |
2324 | | double *t, int *pabSuccess) |
2325 | | |
2326 | 0 | { |
2327 | 0 | const int bRet = TransformWithErrorCodes(nCount, x, y, z, t, pabSuccess); |
2328 | |
|
2329 | 0 | if (pabSuccess) |
2330 | 0 | { |
2331 | 0 | for (size_t i = 0; i < nCount; i++) |
2332 | 0 | { |
2333 | 0 | pabSuccess[i] = (pabSuccess[i] == 0); |
2334 | 0 | } |
2335 | 0 | } |
2336 | |
|
2337 | 0 | return bRet; |
2338 | 0 | } |
2339 | | |
2340 | | /************************************************************************/ |
2341 | | /* TransformWithErrorCodes() */ |
2342 | | /************************************************************************/ |
2343 | | |
2344 | | #ifndef PROJ_ERR_COORD_TRANSFM_INVALID_COORD |
2345 | | #define PROJ_ERR_COORD_TRANSFM_INVALID_COORD 2049 |
2346 | | #define PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN 2050 |
2347 | | #define PROJ_ERR_COORD_TRANSFM_NO_OPERATION 2051 |
2348 | | #endif |
2349 | | |
2350 | | int OGRProjCT::TransformWithErrorCodes(size_t nCount, double *x, double *y, |
2351 | | double *z, double *t, int *panErrorCodes) |
2352 | | |
2353 | 0 | { |
2354 | 0 | if (nCount == 0) |
2355 | 0 | return TRUE; |
2356 | | |
2357 | | // Prevent any coordinate modification when possible |
2358 | 0 | if (bNoTransform) |
2359 | 0 | { |
2360 | 0 | if (panErrorCodes) |
2361 | 0 | { |
2362 | 0 | for (size_t i = 0; i < nCount; i++) |
2363 | 0 | { |
2364 | 0 | panErrorCodes[i] = 0; |
2365 | 0 | } |
2366 | 0 | } |
2367 | 0 | return TRUE; |
2368 | 0 | } |
2369 | | |
2370 | | #ifdef DEBUG_VERBOSE |
2371 | | bool bDebugCT = CPLTestBool(CPLGetConfigOption("OGR_CT_DEBUG", "NO")); |
2372 | | if (bDebugCT) |
2373 | | { |
2374 | | CPLDebug("OGRCT", "count = %d", nCount); |
2375 | | for (size_t i = 0; i < nCount; ++i) |
2376 | | { |
2377 | | CPLDebug("OGRCT", " x[%d] = %.16g y[%d] = %.16g", int(i), x[i], |
2378 | | int(i), y[i]); |
2379 | | } |
2380 | | } |
2381 | | #endif |
2382 | | #ifdef DEBUG_PERF |
2383 | | // CPLDebug("OGR_CT", "Begin TransformWithErrorCodes()"); |
2384 | | struct CPLTimeVal tvStart; |
2385 | | CPLGettimeofday(&tvStart, nullptr); |
2386 | | #endif |
2387 | | |
2388 | | /* -------------------------------------------------------------------- */ |
2389 | | /* Apply data axis to source CRS mapping. */ |
2390 | | /* -------------------------------------------------------------------- */ |
2391 | | |
2392 | | // Since we may swap the x and y pointers, but cannot tell the caller about this swap, |
2393 | | // we save the original pointer. The same axis swap code is executed for poSRSTarget. |
2394 | | // If this nullifies, we save the swap of both axes |
2395 | 0 | const auto xOriginal = x; |
2396 | |
|
2397 | 0 | if (poSRSSource) |
2398 | 0 | { |
2399 | 0 | const auto &mapping = poSRSSource->GetDataAxisToSRSAxisMapping(); |
2400 | 0 | if (mapping.size() >= 2) |
2401 | 0 | { |
2402 | 0 | if (std::abs(mapping[0]) == 2 && std::abs(mapping[1]) == 1) |
2403 | 0 | { |
2404 | 0 | std::swap(x, y); |
2405 | 0 | } |
2406 | 0 | const bool bNegateX = mapping[0] < 0; |
2407 | 0 | if (bNegateX) |
2408 | 0 | { |
2409 | 0 | for (size_t i = 0; i < nCount; i++) |
2410 | 0 | { |
2411 | 0 | x[i] = -x[i]; |
2412 | 0 | } |
2413 | 0 | } |
2414 | 0 | const bool bNegateY = mapping[1] < 0; |
2415 | 0 | if (bNegateY) |
2416 | 0 | { |
2417 | 0 | for (size_t i = 0; i < nCount; i++) |
2418 | 0 | { |
2419 | 0 | y[i] = -y[i]; |
2420 | 0 | } |
2421 | 0 | } |
2422 | 0 | if (z && mapping.size() >= 3 && mapping[2] == -3) |
2423 | 0 | { |
2424 | 0 | for (size_t i = 0; i < nCount; i++) |
2425 | 0 | { |
2426 | 0 | z[i] = -z[i]; |
2427 | 0 | } |
2428 | 0 | } |
2429 | 0 | } |
2430 | 0 | } |
2431 | | |
2432 | | /* -------------------------------------------------------------------- */ |
2433 | | /* Potentially do longitude wrapping. */ |
2434 | | /* -------------------------------------------------------------------- */ |
2435 | 0 | if (bSourceLatLong && bSourceWrap) |
2436 | 0 | { |
2437 | 0 | if (m_eSourceFirstAxisOrient == OAO_East) |
2438 | 0 | { |
2439 | 0 | for (size_t i = 0; i < nCount; i++) |
2440 | 0 | { |
2441 | 0 | if (x[i] != HUGE_VAL && y[i] != HUGE_VAL) |
2442 | 0 | { |
2443 | 0 | if (x[i] < dfSourceWrapLong - 180.0) |
2444 | 0 | x[i] += 360.0; |
2445 | 0 | else if (x[i] > dfSourceWrapLong + 180) |
2446 | 0 | x[i] -= 360.0; |
2447 | 0 | } |
2448 | 0 | } |
2449 | 0 | } |
2450 | 0 | else |
2451 | 0 | { |
2452 | 0 | for (size_t i = 0; i < nCount; i++) |
2453 | 0 | { |
2454 | 0 | if (x[i] != HUGE_VAL && y[i] != HUGE_VAL) |
2455 | 0 | { |
2456 | 0 | if (y[i] < dfSourceWrapLong - 180.0) |
2457 | 0 | y[i] += 360.0; |
2458 | 0 | else if (y[i] > dfSourceWrapLong + 180) |
2459 | 0 | y[i] -= 360.0; |
2460 | 0 | } |
2461 | 0 | } |
2462 | 0 | } |
2463 | 0 | } |
2464 | | |
2465 | | /* -------------------------------------------------------------------- */ |
2466 | | /* Optimized transform from WebMercator to WGS84 */ |
2467 | | /* -------------------------------------------------------------------- */ |
2468 | 0 | bool bTransformDone = false; |
2469 | 0 | int bRet = TRUE; |
2470 | 0 | if (bWebMercatorToWGS84LongLat) |
2471 | 0 | { |
2472 | 0 | constexpr double REVERSE_SPHERE_RADIUS = 1.0 / 6378137.0; |
2473 | |
|
2474 | 0 | if (m_eSourceFirstAxisOrient != OAO_East) |
2475 | 0 | { |
2476 | 0 | std::swap(x, y); |
2477 | 0 | } |
2478 | |
|
2479 | 0 | double y0 = y[0]; |
2480 | 0 | for (size_t i = 0; i < nCount; i++) |
2481 | 0 | { |
2482 | 0 | if (x[i] == HUGE_VAL) |
2483 | 0 | { |
2484 | 0 | bRet = FALSE; |
2485 | 0 | } |
2486 | 0 | else |
2487 | 0 | { |
2488 | 0 | x[i] = x[i] * REVERSE_SPHERE_RADIUS; |
2489 | 0 | if (x[i] > M_PI) |
2490 | 0 | { |
2491 | 0 | if (x[i] < M_PI + 1e-14) |
2492 | 0 | { |
2493 | 0 | x[i] = M_PI; |
2494 | 0 | } |
2495 | 0 | else if (m_options.d->bCheckWithInvertProj) |
2496 | 0 | { |
2497 | 0 | x[i] = HUGE_VAL; |
2498 | 0 | y[i] = HUGE_VAL; |
2499 | 0 | y0 = HUGE_VAL; |
2500 | 0 | continue; |
2501 | 0 | } |
2502 | 0 | else |
2503 | 0 | { |
2504 | 0 | do |
2505 | 0 | { |
2506 | 0 | x[i] -= 2 * M_PI; |
2507 | 0 | } while (x[i] > M_PI); |
2508 | 0 | } |
2509 | 0 | } |
2510 | 0 | else if (x[i] < -M_PI) |
2511 | 0 | { |
2512 | 0 | if (x[i] > -M_PI - 1e-14) |
2513 | 0 | { |
2514 | 0 | x[i] = -M_PI; |
2515 | 0 | } |
2516 | 0 | else if (m_options.d->bCheckWithInvertProj) |
2517 | 0 | { |
2518 | 0 | x[i] = HUGE_VAL; |
2519 | 0 | y[i] = HUGE_VAL; |
2520 | 0 | y0 = HUGE_VAL; |
2521 | 0 | continue; |
2522 | 0 | } |
2523 | 0 | else |
2524 | 0 | { |
2525 | 0 | do |
2526 | 0 | { |
2527 | 0 | x[i] += 2 * M_PI; |
2528 | 0 | } while (x[i] < -M_PI); |
2529 | 0 | } |
2530 | 0 | } |
2531 | 0 | constexpr double RAD_TO_DEG = 180. / M_PI; |
2532 | 0 | x[i] *= RAD_TO_DEG; |
2533 | | |
2534 | | // Optimization for the case where we are provided a whole line |
2535 | | // of same northing. |
2536 | 0 | if (i > 0 && y[i] == y0) |
2537 | 0 | y[i] = y[0]; |
2538 | 0 | else |
2539 | 0 | { |
2540 | 0 | y[i] = M_PI / 2.0 - |
2541 | 0 | 2.0 * atan(exp(-y[i] * REVERSE_SPHERE_RADIUS)); |
2542 | 0 | y[i] *= RAD_TO_DEG; |
2543 | 0 | } |
2544 | 0 | } |
2545 | 0 | } |
2546 | |
|
2547 | 0 | if (panErrorCodes) |
2548 | 0 | { |
2549 | 0 | for (size_t i = 0; i < nCount; i++) |
2550 | 0 | { |
2551 | 0 | if (x[i] != HUGE_VAL) |
2552 | 0 | panErrorCodes[i] = 0; |
2553 | 0 | else |
2554 | 0 | panErrorCodes[i] = |
2555 | 0 | PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN; |
2556 | 0 | } |
2557 | 0 | } |
2558 | |
|
2559 | 0 | if (m_eTargetFirstAxisOrient != OAO_East) |
2560 | 0 | { |
2561 | 0 | std::swap(x, y); |
2562 | 0 | } |
2563 | |
|
2564 | 0 | bTransformDone = true; |
2565 | 0 | } |
2566 | | |
2567 | | // Determine the default coordinate epoch, if not provided in the point to |
2568 | | // transform. |
2569 | | // For time-dependent transformations, PROJ can currently only do |
2570 | | // staticCRS -> dynamicCRS or dynamicCRS -> staticCRS transformations, and |
2571 | | // in either case, the coordinate epoch of the dynamicCRS must be provided |
2572 | | // as the input time. |
2573 | 0 | double dfDefaultTime = HUGE_VAL; |
2574 | 0 | if (bSourceIsDynamicCRS && dfSourceCoordinateEpoch > 0 && |
2575 | 0 | !bTargetIsDynamicCRS && |
2576 | 0 | CPLTestBool( |
2577 | 0 | CPLGetConfigOption("OGR_CT_USE_SRS_COORDINATE_EPOCH", "YES"))) |
2578 | 0 | { |
2579 | 0 | dfDefaultTime = dfSourceCoordinateEpoch; |
2580 | 0 | CPLDebug("OGR_CT", "Using coordinate epoch %f from source CRS", |
2581 | 0 | dfDefaultTime); |
2582 | 0 | } |
2583 | 0 | else if (bTargetIsDynamicCRS && dfTargetCoordinateEpoch > 0 && |
2584 | 0 | !bSourceIsDynamicCRS && |
2585 | 0 | CPLTestBool( |
2586 | 0 | CPLGetConfigOption("OGR_CT_USE_SRS_COORDINATE_EPOCH", "YES"))) |
2587 | 0 | { |
2588 | 0 | dfDefaultTime = dfTargetCoordinateEpoch; |
2589 | 0 | CPLDebug("OGR_CT", "Using coordinate epoch %f from target CRS", |
2590 | 0 | dfDefaultTime); |
2591 | 0 | } |
2592 | | |
2593 | | /* -------------------------------------------------------------------- */ |
2594 | | /* Select dynamically the best transformation for the data, if */ |
2595 | | /* needed. */ |
2596 | | /* -------------------------------------------------------------------- */ |
2597 | 0 | auto ctx = OSRGetProjTLSContext(); |
2598 | |
|
2599 | 0 | PJ *pj = m_pj; |
2600 | 0 | if (!bTransformDone && !pj) |
2601 | 0 | { |
2602 | 0 | double avgX = 0.0; |
2603 | 0 | double avgY = 0.0; |
2604 | 0 | size_t nCountValid = 0; |
2605 | 0 | for (size_t i = 0; i < nCount; i++) |
2606 | 0 | { |
2607 | 0 | if (x[i] != HUGE_VAL && y[i] != HUGE_VAL) |
2608 | 0 | { |
2609 | 0 | avgX += x[i]; |
2610 | 0 | avgY += y[i]; |
2611 | 0 | nCountValid++; |
2612 | 0 | } |
2613 | 0 | } |
2614 | 0 | if (nCountValid != 0) |
2615 | 0 | { |
2616 | 0 | avgX /= static_cast<double>(nCountValid); |
2617 | 0 | avgY /= static_cast<double>(nCountValid); |
2618 | 0 | } |
2619 | |
|
2620 | 0 | constexpr int N_MAX_RETRY = 2; |
2621 | 0 | int iExcluded[N_MAX_RETRY] = {-1, -1}; |
2622 | |
|
2623 | 0 | const int nOperations = static_cast<int>(m_oTransformations.size()); |
2624 | 0 | PJ_COORD coord; |
2625 | 0 | coord.xyzt.x = avgX; |
2626 | 0 | coord.xyzt.y = avgY; |
2627 | 0 | coord.xyzt.z = z ? z[0] : 0; |
2628 | 0 | coord.xyzt.t = t ? t[0] : dfDefaultTime; |
2629 | | |
2630 | | // We may need several attempts. For example the point at |
2631 | | // lon=-111.5 lat=45.26 falls into the bounding box of the Canadian |
2632 | | // ntv2_0.gsb grid, except that it is not in any of the subgrids, being |
2633 | | // in the US. We thus need another retry that will select the conus |
2634 | | // grid. |
2635 | 0 | for (int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++) |
2636 | 0 | { |
2637 | 0 | int iBestTransf = -1; |
2638 | | // Select transform whose BBOX match our data and has the best |
2639 | | // accuracy if m_eStrategy == BEST_ACCURACY. Or just the first BBOX |
2640 | | // matching one, if |
2641 | | // m_eStrategy == FIRST_MATCHING |
2642 | 0 | double dfBestAccuracy = std::numeric_limits<double>::infinity(); |
2643 | 0 | for (int i = 0; i < nOperations; i++) |
2644 | 0 | { |
2645 | 0 | if (i == iExcluded[0] || i == iExcluded[1]) |
2646 | 0 | { |
2647 | 0 | continue; |
2648 | 0 | } |
2649 | 0 | const auto &transf = m_oTransformations[i]; |
2650 | 0 | if (avgX >= transf.minx && avgX <= transf.maxx && |
2651 | 0 | avgY >= transf.miny && avgY <= transf.maxy && |
2652 | 0 | (iBestTransf < 0 || (transf.accuracy >= 0 && |
2653 | 0 | transf.accuracy < dfBestAccuracy))) |
2654 | 0 | { |
2655 | 0 | iBestTransf = i; |
2656 | 0 | dfBestAccuracy = transf.accuracy; |
2657 | 0 | if (m_eStrategy == Strategy::FIRST_MATCHING) |
2658 | 0 | break; |
2659 | 0 | } |
2660 | 0 | } |
2661 | 0 | if (iBestTransf < 0) |
2662 | 0 | { |
2663 | 0 | break; |
2664 | 0 | } |
2665 | 0 | auto &transf = m_oTransformations[iBestTransf]; |
2666 | 0 | pj = transf.pj; |
2667 | 0 | proj_assign_context(pj, ctx); |
2668 | 0 | if (iBestTransf != m_iCurTransformation) |
2669 | 0 | { |
2670 | 0 | CPLDebug("OGRCT", "Selecting transformation %s (%s)", |
2671 | 0 | transf.osProjString.c_str(), transf.osName.c_str()); |
2672 | 0 | m_iCurTransformation = iBestTransf; |
2673 | 0 | } |
2674 | |
|
2675 | 0 | auto res = proj_trans(pj, m_bReversePj ? PJ_INV : PJ_FWD, coord); |
2676 | 0 | if (res.xyzt.x != HUGE_VAL) |
2677 | 0 | { |
2678 | 0 | break; |
2679 | 0 | } |
2680 | 0 | pj = nullptr; |
2681 | 0 | CPLDebug("OGRCT", "Did not result in valid result. " |
2682 | 0 | "Attempting a retry with another operation."); |
2683 | 0 | if (iRetry == N_MAX_RETRY) |
2684 | 0 | { |
2685 | 0 | break; |
2686 | 0 | } |
2687 | 0 | iExcluded[iRetry] = iBestTransf; |
2688 | 0 | } |
2689 | |
|
2690 | 0 | if (!pj) |
2691 | 0 | { |
2692 | | // In case we did not find an operation whose area of use is |
2693 | | // compatible with the input coordinate, then goes through again the |
2694 | | // list, and use the first operation that does not require grids. |
2695 | 0 | for (int i = 0; i < nOperations; i++) |
2696 | 0 | { |
2697 | 0 | auto &transf = m_oTransformations[i]; |
2698 | 0 | if (proj_coordoperation_get_grid_used_count(ctx, transf.pj) == |
2699 | 0 | 0) |
2700 | 0 | { |
2701 | 0 | pj = transf.pj; |
2702 | 0 | proj_assign_context(pj, ctx); |
2703 | 0 | if (i != m_iCurTransformation) |
2704 | 0 | { |
2705 | 0 | CPLDebug("OGRCT", "Selecting transformation %s (%s)", |
2706 | 0 | transf.osProjString.c_str(), |
2707 | 0 | transf.osName.c_str()); |
2708 | 0 | m_iCurTransformation = i; |
2709 | 0 | } |
2710 | 0 | break; |
2711 | 0 | } |
2712 | 0 | } |
2713 | 0 | } |
2714 | |
|
2715 | 0 | if (!pj) |
2716 | 0 | { |
2717 | 0 | if (m_bEmitErrors && ++nErrorCount < 20) |
2718 | 0 | { |
2719 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2720 | 0 | "Cannot find transformation for provided coordinates"); |
2721 | 0 | } |
2722 | 0 | else if (nErrorCount == 20) |
2723 | 0 | { |
2724 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2725 | 0 | "Reprojection failed, further errors will be " |
2726 | 0 | "suppressed on the transform object."); |
2727 | 0 | } |
2728 | |
|
2729 | 0 | for (size_t i = 0; i < nCount; i++) |
2730 | 0 | { |
2731 | 0 | x[i] = HUGE_VAL; |
2732 | 0 | y[i] = HUGE_VAL; |
2733 | 0 | if (panErrorCodes) |
2734 | 0 | panErrorCodes[i] = PROJ_ERR_COORD_TRANSFM_NO_OPERATION; |
2735 | 0 | } |
2736 | 0 | return FALSE; |
2737 | 0 | } |
2738 | 0 | } |
2739 | 0 | if (pj) |
2740 | 0 | { |
2741 | 0 | proj_assign_context(pj, ctx); |
2742 | 0 | } |
2743 | | |
2744 | | /* -------------------------------------------------------------------- */ |
2745 | | /* Do the transformation (or not...) using PROJ */ |
2746 | | /* -------------------------------------------------------------------- */ |
2747 | |
|
2748 | 0 | if (!bTransformDone) |
2749 | 0 | { |
2750 | 0 | const auto nLastErrorCounter = CPLGetErrorCounter(); |
2751 | |
|
2752 | 0 | for (size_t i = 0; i < nCount; i++) |
2753 | 0 | { |
2754 | 0 | PJ_COORD coord; |
2755 | 0 | const double xIn = x[i]; |
2756 | 0 | const double yIn = y[i]; |
2757 | 0 | if (!std::isfinite(xIn)) |
2758 | 0 | { |
2759 | 0 | bRet = FALSE; |
2760 | 0 | x[i] = HUGE_VAL; |
2761 | 0 | y[i] = HUGE_VAL; |
2762 | 0 | if (panErrorCodes) |
2763 | 0 | panErrorCodes[i] = PROJ_ERR_COORD_TRANSFM_INVALID_COORD; |
2764 | 0 | continue; |
2765 | 0 | } |
2766 | 0 | coord.xyzt.x = x[i]; |
2767 | 0 | coord.xyzt.y = y[i]; |
2768 | 0 | coord.xyzt.z = z ? z[i] : 0; |
2769 | 0 | coord.xyzt.t = t ? t[i] : dfDefaultTime; |
2770 | 0 | proj_errno_reset(pj); |
2771 | 0 | coord = proj_trans(pj, m_bReversePj ? PJ_INV : PJ_FWD, coord); |
2772 | | #if 0 |
2773 | | CPLDebug("OGRCT", |
2774 | | "Transforming (x=%f,y=%f,z=%f,time=%f) to " |
2775 | | "(x=%f,y=%f,z=%f,time=%f)", |
2776 | | x[i], y[i], z ? z[i] : 0, t ? t[i] : dfDefaultTime, |
2777 | | coord.xyzt.x, coord.xyzt.y, coord.xyzt.z, coord.xyzt.t); |
2778 | | #endif |
2779 | 0 | x[i] = coord.xyzt.x; |
2780 | 0 | y[i] = coord.xyzt.y; |
2781 | 0 | if (z) |
2782 | 0 | z[i] = coord.xyzt.z; |
2783 | 0 | if (t) |
2784 | 0 | t[i] = coord.xyzt.t; |
2785 | 0 | int err = 0; |
2786 | 0 | if (std::isnan(coord.xyzt.x)) |
2787 | 0 | { |
2788 | 0 | bRet = FALSE; |
2789 | | // This shouldn't normally happen if PROJ projections behave |
2790 | | // correctly, but e.g inverse laea before PROJ 8.1.1 could |
2791 | | // do that for points out of domain. |
2792 | | // See https://github.com/OSGeo/PROJ/pull/2800 |
2793 | 0 | x[i] = HUGE_VAL; |
2794 | 0 | y[i] = HUGE_VAL; |
2795 | 0 | err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN; |
2796 | |
|
2797 | 0 | #ifdef DEBUG |
2798 | 0 | CPLErrorOnce(CE_Warning, CPLE_AppDefined, |
2799 | 0 | "PROJ returned a NaN value. It should be fixed"); |
2800 | | #else |
2801 | | CPLDebugOnce("OGR_CT", |
2802 | | "PROJ returned a NaN value. It should be fixed"); |
2803 | | #endif |
2804 | 0 | } |
2805 | 0 | else if (coord.xyzt.x == HUGE_VAL) |
2806 | 0 | { |
2807 | 0 | bRet = FALSE; |
2808 | 0 | err = proj_errno(pj); |
2809 | | // PROJ should normally emit an error, but in case it does not |
2810 | | // (e.g PROJ 6.3 with the +ortho projection), synthetize one |
2811 | 0 | if (err == 0) |
2812 | 0 | err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN; |
2813 | 0 | } |
2814 | 0 | else |
2815 | 0 | { |
2816 | 0 | if (m_recordDifferentOperationsUsed && |
2817 | 0 | !m_differentOperationsUsed) |
2818 | 0 | { |
2819 | 0 | #if PROJ_VERSION_MAJOR > 9 || \ |
2820 | 0 | (PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 1) |
2821 | |
|
2822 | 0 | PJ *lastOp = proj_trans_get_last_used_operation(pj); |
2823 | 0 | if (lastOp) |
2824 | 0 | { |
2825 | 0 | const char *projString = proj_as_proj_string( |
2826 | 0 | ctx, lastOp, PJ_PROJ_5, nullptr); |
2827 | 0 | if (projString) |
2828 | 0 | { |
2829 | 0 | if (m_lastPjUsedPROJString.empty()) |
2830 | 0 | { |
2831 | 0 | m_lastPjUsedPROJString = projString; |
2832 | 0 | } |
2833 | 0 | else if (m_lastPjUsedPROJString != projString) |
2834 | 0 | { |
2835 | 0 | m_differentOperationsUsed = true; |
2836 | 0 | } |
2837 | 0 | } |
2838 | 0 | proj_destroy(lastOp); |
2839 | 0 | } |
2840 | 0 | #endif |
2841 | 0 | } |
2842 | |
|
2843 | 0 | if (m_options.d->bCheckWithInvertProj) |
2844 | 0 | { |
2845 | | // For some projections, we cannot detect if we are trying to |
2846 | | // reproject coordinates outside the validity area of the |
2847 | | // projection. So let's do the reverse reprojection and compare |
2848 | | // with the source coordinates. |
2849 | 0 | coord = |
2850 | 0 | proj_trans(pj, m_bReversePj ? PJ_FWD : PJ_INV, coord); |
2851 | 0 | if (fabs(coord.xyzt.x - xIn) > dfThreshold || |
2852 | 0 | fabs(coord.xyzt.y - yIn) > dfThreshold) |
2853 | 0 | { |
2854 | 0 | bRet = FALSE; |
2855 | 0 | err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN; |
2856 | 0 | x[i] = HUGE_VAL; |
2857 | 0 | y[i] = HUGE_VAL; |
2858 | 0 | } |
2859 | 0 | } |
2860 | 0 | } |
2861 | |
|
2862 | 0 | if (panErrorCodes) |
2863 | 0 | panErrorCodes[i] = err; |
2864 | | |
2865 | | /* -------------------------------------------------------------------- |
2866 | | */ |
2867 | | /* Try to report an error through CPL. Get proj error string |
2868 | | */ |
2869 | | /* if possible. Try to avoid reporting thousands of errors. */ |
2870 | | /* Suppress further error reporting on this OGRProjCT if we */ |
2871 | | /* have already reported 20 errors. */ |
2872 | | /* -------------------------------------------------------------------- |
2873 | | */ |
2874 | 0 | if (err != 0) |
2875 | 0 | { |
2876 | 0 | if (++nErrorCount < 20) |
2877 | 0 | { |
2878 | 0 | #if PROJ_VERSION_MAJOR >= 8 |
2879 | 0 | const char *pszError = proj_context_errno_string(ctx, err); |
2880 | | #else |
2881 | | const char *pszError = proj_errno_string(err); |
2882 | | #endif |
2883 | 0 | if (m_bEmitErrors |
2884 | 0 | #ifdef PROJ_ERR_OTHER_NO_INVERSE_OP |
2885 | 0 | || (i == 0 && err == PROJ_ERR_OTHER_NO_INVERSE_OP) |
2886 | 0 | #endif |
2887 | 0 | ) |
2888 | 0 | { |
2889 | 0 | if (nLastErrorCounter != CPLGetErrorCounter() && |
2890 | 0 | CPLGetLastErrorType() == CE_Failure && |
2891 | 0 | strstr(CPLGetLastErrorMsg(), "PROJ:")) |
2892 | 0 | { |
2893 | | // do nothing |
2894 | 0 | } |
2895 | 0 | else if (pszError == nullptr) |
2896 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2897 | 0 | "Reprojection failed, err = %d", err); |
2898 | 0 | else |
2899 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s", |
2900 | 0 | pszError); |
2901 | 0 | } |
2902 | 0 | else |
2903 | 0 | { |
2904 | 0 | if (pszError == nullptr) |
2905 | 0 | CPLDebug("OGRCT", "Reprojection failed, err = %d", |
2906 | 0 | err); |
2907 | 0 | else |
2908 | 0 | CPLDebug("OGRCT", "%s", pszError); |
2909 | 0 | } |
2910 | 0 | } |
2911 | 0 | else if (nErrorCount == 20) |
2912 | 0 | { |
2913 | 0 | if (m_bEmitErrors) |
2914 | 0 | { |
2915 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
2916 | 0 | "Reprojection failed, err = %d, further " |
2917 | 0 | "errors will be " |
2918 | 0 | "suppressed on the transform object.", |
2919 | 0 | err); |
2920 | 0 | } |
2921 | 0 | else |
2922 | 0 | { |
2923 | 0 | CPLDebug("OGRCT", |
2924 | 0 | "Reprojection failed, err = %d, further " |
2925 | 0 | "errors will be " |
2926 | 0 | "suppressed on the transform object.", |
2927 | 0 | err); |
2928 | 0 | } |
2929 | 0 | } |
2930 | 0 | } |
2931 | 0 | } |
2932 | 0 | } |
2933 | | |
2934 | | /* -------------------------------------------------------------------- */ |
2935 | | /* Potentially do longitude wrapping. */ |
2936 | | /* -------------------------------------------------------------------- */ |
2937 | 0 | if (bTargetLatLong && bTargetWrap) |
2938 | 0 | { |
2939 | 0 | if (m_eTargetFirstAxisOrient == OAO_East) |
2940 | 0 | { |
2941 | 0 | for (size_t i = 0; i < nCount; i++) |
2942 | 0 | { |
2943 | 0 | if (x[i] != HUGE_VAL && y[i] != HUGE_VAL) |
2944 | 0 | { |
2945 | 0 | if (x[i] < dfTargetWrapLong - 180.0) |
2946 | 0 | x[i] += 360.0; |
2947 | 0 | else if (x[i] > dfTargetWrapLong + 180) |
2948 | 0 | x[i] -= 360.0; |
2949 | 0 | } |
2950 | 0 | } |
2951 | 0 | } |
2952 | 0 | else |
2953 | 0 | { |
2954 | 0 | for (size_t i = 0; i < nCount; i++) |
2955 | 0 | { |
2956 | 0 | if (x[i] != HUGE_VAL && y[i] != HUGE_VAL) |
2957 | 0 | { |
2958 | 0 | if (y[i] < dfTargetWrapLong - 180.0) |
2959 | 0 | y[i] += 360.0; |
2960 | 0 | else if (y[i] > dfTargetWrapLong + 180) |
2961 | 0 | y[i] -= 360.0; |
2962 | 0 | } |
2963 | 0 | } |
2964 | 0 | } |
2965 | 0 | } |
2966 | | |
2967 | | /* -------------------------------------------------------------------- */ |
2968 | | /* Apply data axis to target CRS mapping. */ |
2969 | | /* -------------------------------------------------------------------- */ |
2970 | 0 | if (poSRSTarget) |
2971 | 0 | { |
2972 | 0 | const auto &mapping = poSRSTarget->GetDataAxisToSRSAxisMapping(); |
2973 | 0 | if (mapping.size() >= 2) |
2974 | 0 | { |
2975 | 0 | const bool bNegateX = mapping[0] < 0; |
2976 | 0 | if (bNegateX) |
2977 | 0 | { |
2978 | 0 | for (size_t i = 0; i < nCount; i++) |
2979 | 0 | { |
2980 | 0 | x[i] = -x[i]; |
2981 | 0 | } |
2982 | 0 | } |
2983 | 0 | const bool bNegateY = mapping[1] < 0; |
2984 | 0 | if (bNegateY) |
2985 | 0 | { |
2986 | 0 | for (size_t i = 0; i < nCount; i++) |
2987 | 0 | { |
2988 | 0 | y[i] = -y[i]; |
2989 | 0 | } |
2990 | 0 | } |
2991 | 0 | if (z && mapping.size() >= 3 && mapping[2] == -3) |
2992 | 0 | { |
2993 | 0 | for (size_t i = 0; i < nCount; i++) |
2994 | 0 | { |
2995 | 0 | z[i] = -z[i]; |
2996 | 0 | } |
2997 | 0 | } |
2998 | |
|
2999 | 0 | if (std::abs(mapping[0]) == 2 && std::abs(mapping[1]) == 1) |
3000 | 0 | { |
3001 | 0 | std::swap(x, y); |
3002 | 0 | } |
3003 | 0 | } |
3004 | 0 | } |
3005 | | |
3006 | | // Check whether final "genuine" axis swap is really necessary |
3007 | 0 | if (x != xOriginal) |
3008 | 0 | { |
3009 | 0 | std::swap_ranges(x, x + nCount, y); |
3010 | 0 | } |
3011 | |
|
3012 | | #ifdef DEBUG_VERBOSE |
3013 | | if (bDebugCT) |
3014 | | { |
3015 | | CPLDebug("OGRCT", "Out:"); |
3016 | | for (size_t i = 0; i < nCount; ++i) |
3017 | | { |
3018 | | CPLDebug("OGRCT", " x[%d] = %.16g y[%d] = %.16g", int(i), x[i], |
3019 | | int(i), y[i]); |
3020 | | } |
3021 | | } |
3022 | | #endif |
3023 | | #ifdef DEBUG_PERF |
3024 | | struct CPLTimeVal tvEnd; |
3025 | | CPLGettimeofday(&tvEnd, nullptr); |
3026 | | const double delay = (tvEnd.tv_sec + tvEnd.tv_usec * 1e-6) - |
3027 | | (tvStart.tv_sec + tvStart.tv_usec * 1e-6); |
3028 | | g_dfTotalTimeReprojection += delay; |
3029 | | // CPLDebug("OGR_CT", "End TransformWithErrorCodes(): %d ms", |
3030 | | // static_cast<int>(delay * 1000)); |
3031 | | #endif |
3032 | |
|
3033 | 0 | return bRet; |
3034 | 0 | } |
3035 | | |
3036 | | /************************************************************************/ |
3037 | | /* TransformBounds() */ |
3038 | | /************************************************************************/ |
3039 | | |
3040 | | // --------------------------------------------------------------------------- |
3041 | | static double simple_min(const double *data, const int *panErrorCodes, |
3042 | | const int arr_len) |
3043 | 0 | { |
3044 | 0 | double min_value = HUGE_VAL; |
3045 | 0 | for (int iii = 0; iii < arr_len; iii++) |
3046 | 0 | { |
3047 | 0 | if ((data[iii] < min_value || min_value == HUGE_VAL) && |
3048 | 0 | panErrorCodes[iii] == 0) |
3049 | 0 | min_value = data[iii]; |
3050 | 0 | } |
3051 | 0 | return min_value; |
3052 | 0 | } |
3053 | | |
3054 | | // --------------------------------------------------------------------------- |
3055 | | static double simple_max(const double *data, const int *panErrorCodes, |
3056 | | const int arr_len) |
3057 | 0 | { |
3058 | 0 | double max_value = HUGE_VAL; |
3059 | 0 | for (int iii = 0; iii < arr_len; iii++) |
3060 | 0 | { |
3061 | 0 | if ((data[iii] > max_value || max_value == HUGE_VAL) && |
3062 | 0 | panErrorCodes[iii] == 0) |
3063 | 0 | max_value = data[iii]; |
3064 | 0 | } |
3065 | 0 | return max_value; |
3066 | 0 | } |
3067 | | |
3068 | | // --------------------------------------------------------------------------- |
3069 | | static int _find_previous_index(const int iii, const int *panErrorCodes, |
3070 | | const int arr_len) |
3071 | 0 | { |
3072 | | // find index of nearest valid previous value if exists |
3073 | 0 | int prev_iii = iii - 1; |
3074 | 0 | if (prev_iii == -1) // handle wraparound |
3075 | 0 | prev_iii = arr_len - 1; |
3076 | 0 | while (panErrorCodes[prev_iii] != 0 && prev_iii != iii) |
3077 | 0 | { |
3078 | 0 | prev_iii--; |
3079 | 0 | if (prev_iii == -1) // handle wraparound |
3080 | 0 | prev_iii = arr_len - 1; |
3081 | 0 | } |
3082 | 0 | return prev_iii; |
3083 | 0 | } |
3084 | | |
3085 | | // --------------------------------------------------------------------------- |
3086 | | /****************************************************************************** |
3087 | | Handles the case when longitude values cross the antimeridian |
3088 | | when calculating the minimum. |
3089 | | Note: The data array must be in a linear ring. |
3090 | | Note: This requires a densified ring with at least 2 additional |
3091 | | points per edge to correctly handle global extents. |
3092 | | If only 1 additional point: |
3093 | | | | |
3094 | | |RL--x0--|RL-- |
3095 | | | | |
3096 | | -180 180|-180 |
3097 | | If they are evenly spaced and it crosses the antimeridian: |
3098 | | x0 - L = 180 |
3099 | | R - x0 = -180 |
3100 | | For example: |
3101 | | Let R = -179.9, x0 = 0.1, L = -179.89 |
3102 | | x0 - L = 0.1 - -179.9 = 180 |
3103 | | R - x0 = -179.89 - 0.1 ~= -180 |
3104 | | This is the same in the case when it didn't cross the antimeridian. |
3105 | | If you have 2 additional points: |
3106 | | | | |
3107 | | |RL--x0--x1--|RL-- |
3108 | | | | |
3109 | | -180 180|-180 |
3110 | | If they are evenly spaced and it crosses the antimeridian: |
3111 | | x0 - L = 120 |
3112 | | x1 - x0 = 120 |
3113 | | R - x1 = -240 |
3114 | | For example: |
3115 | | Let R = -179.9, x0 = -59.9, x1 = 60.1 L = -179.89 |
3116 | | x0 - L = 59.9 - -179.9 = 120 |
3117 | | x1 - x0 = 60.1 - 59.9 = 120 |
3118 | | R - x1 = -179.89 - 60.1 ~= -240 |
3119 | | However, if they are evenly spaced and it didn't cross the antimeridian: |
3120 | | x0 - L = 120 |
3121 | | x1 - x0 = 120 |
3122 | | R - x1 = 120 |
3123 | | From this, we have a delta that is guaranteed to be significantly |
3124 | | large enough to tell the difference reguarless of the direction |
3125 | | the antimeridian was crossed. |
3126 | | However, even though the spacing was even in the source projection, it isn't |
3127 | | guaranteed in the target geographic projection. So, instead of 240, 200 is used |
3128 | | as it significantly larger than 120 to be sure that the antimeridian was crossed |
3129 | | but smalller than 240 to account for possible irregularities in distances |
3130 | | when re-projecting. Also, 200 ensures latitudes are ignored for axis order |
3131 | | handling. |
3132 | | ******************************************************************************/ |
3133 | | static double antimeridian_min(const double *data, const int *panErrorCodes, |
3134 | | const int arr_len) |
3135 | 0 | { |
3136 | 0 | double positive_min = HUGE_VAL; |
3137 | 0 | double min_value = HUGE_VAL; |
3138 | 0 | int crossed_meridian_count = 0; |
3139 | 0 | bool positive_meridian = false; |
3140 | |
|
3141 | 0 | for (int iii = 0; iii < arr_len; iii++) |
3142 | 0 | { |
3143 | 0 | if (panErrorCodes[iii]) |
3144 | 0 | continue; |
3145 | 0 | int prev_iii = _find_previous_index(iii, panErrorCodes, arr_len); |
3146 | | // check if crossed meridian |
3147 | 0 | double delta = data[prev_iii] - data[iii]; |
3148 | | // 180 -> -180 |
3149 | 0 | if (delta >= 200 && delta != HUGE_VAL) |
3150 | 0 | { |
3151 | 0 | if (crossed_meridian_count == 0) |
3152 | 0 | positive_min = min_value; |
3153 | 0 | crossed_meridian_count++; |
3154 | 0 | positive_meridian = false; |
3155 | | // -180 -> 180 |
3156 | 0 | } |
3157 | 0 | else if (delta <= -200 && delta != HUGE_VAL) |
3158 | 0 | { |
3159 | 0 | if (crossed_meridian_count == 0) |
3160 | 0 | positive_min = data[iii]; |
3161 | 0 | crossed_meridian_count++; |
3162 | 0 | positive_meridian = true; |
3163 | 0 | } |
3164 | | // positive meridian side min |
3165 | 0 | if (positive_meridian && data[iii] < positive_min) |
3166 | 0 | positive_min = data[iii]; |
3167 | | // track general min value |
3168 | 0 | if (data[iii] < min_value) |
3169 | 0 | min_value = data[iii]; |
3170 | 0 | } |
3171 | |
|
3172 | 0 | if (crossed_meridian_count == 2) |
3173 | 0 | return positive_min; |
3174 | 0 | else if (crossed_meridian_count == 4) |
3175 | | // bounds extends beyond -180/180 |
3176 | 0 | return -180; |
3177 | 0 | return min_value; |
3178 | 0 | } |
3179 | | |
3180 | | // --------------------------------------------------------------------------- |
3181 | | // Handles the case when longitude values cross the antimeridian |
3182 | | // when calculating the minimum. |
3183 | | // Note: The data array must be in a linear ring. |
3184 | | // Note: This requires a densified ring with at least 2 additional |
3185 | | // points per edge to correctly handle global extents. |
3186 | | // See antimeridian_min docstring for reasoning. |
3187 | | static double antimeridian_max(const double *data, const int *panErrorCodes, |
3188 | | const int arr_len) |
3189 | 0 | { |
3190 | 0 | double negative_max = -HUGE_VAL; |
3191 | 0 | double max_value = -HUGE_VAL; |
3192 | 0 | bool negative_meridian = false; |
3193 | 0 | int crossed_meridian_count = 0; |
3194 | |
|
3195 | 0 | for (int iii = 0; iii < arr_len; iii++) |
3196 | 0 | { |
3197 | 0 | if (panErrorCodes[iii]) |
3198 | 0 | continue; |
3199 | 0 | int prev_iii = _find_previous_index(iii, panErrorCodes, arr_len); |
3200 | | // check if crossed meridian |
3201 | 0 | double delta = data[prev_iii] - data[iii]; |
3202 | | // 180 -> -180 |
3203 | 0 | if (delta >= 200 && delta != HUGE_VAL) |
3204 | 0 | { |
3205 | 0 | if (crossed_meridian_count == 0) |
3206 | 0 | negative_max = data[iii]; |
3207 | 0 | crossed_meridian_count++; |
3208 | 0 | negative_meridian = true; |
3209 | | // -180 -> 180 |
3210 | 0 | } |
3211 | 0 | else if (delta <= -200 && delta != HUGE_VAL) |
3212 | 0 | { |
3213 | 0 | if (crossed_meridian_count == 0) |
3214 | 0 | negative_max = max_value; |
3215 | 0 | negative_meridian = false; |
3216 | 0 | crossed_meridian_count++; |
3217 | 0 | } |
3218 | | // negative meridian side max |
3219 | 0 | if (negative_meridian && |
3220 | 0 | (data[iii] > negative_max || negative_max == HUGE_VAL) && |
3221 | 0 | panErrorCodes[iii] == 0) |
3222 | 0 | negative_max = data[iii]; |
3223 | | // track general max value |
3224 | 0 | if ((data[iii] > max_value || max_value == HUGE_VAL) && |
3225 | 0 | panErrorCodes[iii] == 0) |
3226 | 0 | max_value = data[iii]; |
3227 | 0 | } |
3228 | 0 | if (crossed_meridian_count == 2) |
3229 | 0 | return negative_max; |
3230 | 0 | else if (crossed_meridian_count == 4) |
3231 | | // bounds extends beyond -180/180 |
3232 | 0 | return 180; |
3233 | 0 | return max_value; |
3234 | 0 | } |
3235 | | |
3236 | | // --------------------------------------------------------------------------- |
3237 | | // Check if the original projected bounds contains |
3238 | | // the north pole. |
3239 | | // This assumes that the destination CRS is geographic. |
3240 | | bool OGRProjCT::ContainsNorthPole(const double xmin, const double ymin, |
3241 | | const double xmax, const double ymax, |
3242 | | bool lon_lat_order) |
3243 | 0 | { |
3244 | 0 | double pole_y = 90; |
3245 | 0 | double pole_x = 0; |
3246 | 0 | if (!lon_lat_order) |
3247 | 0 | { |
3248 | 0 | pole_y = 0; |
3249 | 0 | pole_x = 90; |
3250 | 0 | } |
3251 | 0 | auto inverseCT = std::unique_ptr<OGRCoordinateTransformation>(GetInverse()); |
3252 | 0 | if (!inverseCT) |
3253 | 0 | return false; |
3254 | 0 | CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); |
3255 | 0 | const bool success = inverseCT->TransformWithErrorCodes( |
3256 | 0 | 1, &pole_x, &pole_y, nullptr, nullptr, nullptr); |
3257 | 0 | return success && xmin < pole_x && pole_x < xmax && ymax > pole_y && |
3258 | 0 | pole_y > ymin; |
3259 | 0 | } |
3260 | | |
3261 | | // --------------------------------------------------------------------------- |
3262 | | // Check if the original projected bounds contains |
3263 | | // the south pole. |
3264 | | // This assumes that the destination CRS is geographic. |
3265 | | bool OGRProjCT::ContainsSouthPole(const double xmin, const double ymin, |
3266 | | const double xmax, const double ymax, |
3267 | | bool lon_lat_order) |
3268 | 0 | { |
3269 | 0 | double pole_y = -90; |
3270 | 0 | double pole_x = 0; |
3271 | 0 | if (!lon_lat_order) |
3272 | 0 | { |
3273 | 0 | pole_y = 0; |
3274 | 0 | pole_x = -90; |
3275 | 0 | } |
3276 | 0 | auto inverseCT = std::unique_ptr<OGRCoordinateTransformation>(GetInverse()); |
3277 | 0 | if (!inverseCT) |
3278 | 0 | return false; |
3279 | 0 | CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); |
3280 | 0 | const bool success = inverseCT->TransformWithErrorCodes( |
3281 | 0 | 1, &pole_x, &pole_y, nullptr, nullptr, nullptr); |
3282 | 0 | return success && xmin < pole_x && pole_x < xmax && ymax > pole_y && |
3283 | 0 | pole_y > ymin; |
3284 | 0 | } |
3285 | | |
3286 | | int OGRProjCT::TransformBounds(const double xmin, const double ymin, |
3287 | | const double xmax, const double ymax, |
3288 | | double *out_xmin, double *out_ymin, |
3289 | | double *out_xmax, double *out_ymax, |
3290 | | const int densify_pts) |
3291 | 0 | { |
3292 | 0 | if (bNoTransform) |
3293 | 0 | { |
3294 | 0 | *out_xmin = xmin; |
3295 | 0 | *out_ymin = ymin; |
3296 | 0 | *out_xmax = xmax; |
3297 | 0 | *out_ymax = ymax; |
3298 | 0 | return true; |
3299 | 0 | } |
3300 | | |
3301 | 0 | *out_xmin = HUGE_VAL; |
3302 | 0 | *out_ymin = HUGE_VAL; |
3303 | 0 | *out_xmax = HUGE_VAL; |
3304 | 0 | *out_ymax = HUGE_VAL; |
3305 | |
|
3306 | 0 | if (densify_pts < 0 || densify_pts > 10000) |
3307 | 0 | { |
3308 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3309 | 0 | "densify_pts must be between 0-10000."); |
3310 | 0 | return false; |
3311 | 0 | } |
3312 | 0 | if (!poSRSSource) |
3313 | 0 | { |
3314 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "missing source SRS."); |
3315 | 0 | return false; |
3316 | 0 | } |
3317 | 0 | if (!poSRSTarget) |
3318 | 0 | { |
3319 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "missing target SRS."); |
3320 | 0 | return false; |
3321 | 0 | } |
3322 | | |
3323 | 0 | bool degree_input = false; |
3324 | 0 | bool degree_output = false; |
3325 | 0 | bool input_lon_lat_order = false; |
3326 | 0 | bool output_lon_lat_order = false; |
3327 | |
|
3328 | 0 | if (bSourceLatLong) |
3329 | 0 | { |
3330 | 0 | degree_input = fabs(poSRSSource->GetAngularUnits(nullptr) - |
3331 | 0 | CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8; |
3332 | 0 | const auto &mapping = poSRSSource->GetDataAxisToSRSAxisMapping(); |
3333 | 0 | if ((mapping[0] == 1 && m_eSourceFirstAxisOrient == OAO_East) || |
3334 | 0 | (mapping[0] == 2 && m_eSourceFirstAxisOrient != OAO_East)) |
3335 | 0 | { |
3336 | 0 | input_lon_lat_order = true; |
3337 | 0 | } |
3338 | 0 | } |
3339 | 0 | if (bTargetLatLong) |
3340 | 0 | { |
3341 | 0 | degree_output = fabs(poSRSTarget->GetAngularUnits(nullptr) - |
3342 | 0 | CPLAtof(SRS_UA_DEGREE_CONV)) < 1e-8; |
3343 | 0 | const auto &mapping = poSRSTarget->GetDataAxisToSRSAxisMapping(); |
3344 | 0 | if ((mapping[0] == 1 && m_eTargetFirstAxisOrient == OAO_East) || |
3345 | 0 | (mapping[0] == 2 && m_eTargetFirstAxisOrient != OAO_East)) |
3346 | 0 | { |
3347 | 0 | output_lon_lat_order = true; |
3348 | 0 | } |
3349 | 0 | } |
3350 | |
|
3351 | 0 | if (degree_output && densify_pts < 2) |
3352 | 0 | { |
3353 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3354 | 0 | "densify_pts must be at least 2 if the output is geograpic."); |
3355 | 0 | return false; |
3356 | 0 | } |
3357 | | |
3358 | 0 | int side_pts = densify_pts + 1; // add one because we are densifying |
3359 | 0 | const int boundary_len = side_pts * 4; |
3360 | 0 | std::vector<double> x_boundary_array; |
3361 | 0 | std::vector<double> y_boundary_array; |
3362 | 0 | std::vector<int> anErrorCodes; |
3363 | 0 | try |
3364 | 0 | { |
3365 | 0 | x_boundary_array.resize(boundary_len); |
3366 | 0 | y_boundary_array.resize(boundary_len); |
3367 | 0 | anErrorCodes.resize(boundary_len); |
3368 | 0 | } |
3369 | 0 | catch (const std::exception &e) // memory allocation failure |
3370 | 0 | { |
3371 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what()); |
3372 | 0 | return false; |
3373 | 0 | } |
3374 | 0 | double delta_x = 0; |
3375 | 0 | double delta_y = 0; |
3376 | 0 | bool north_pole_in_bounds = false; |
3377 | 0 | bool south_pole_in_bounds = false; |
3378 | 0 | if (degree_output) |
3379 | 0 | { |
3380 | 0 | north_pole_in_bounds = |
3381 | 0 | ContainsNorthPole(xmin, ymin, xmax, ymax, output_lon_lat_order); |
3382 | 0 | south_pole_in_bounds = |
3383 | 0 | ContainsSouthPole(xmin, ymin, xmax, ymax, output_lon_lat_order); |
3384 | 0 | } |
3385 | |
|
3386 | 0 | if (degree_input && xmax < xmin) |
3387 | 0 | { |
3388 | 0 | if (!input_lon_lat_order) |
3389 | 0 | { |
3390 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3391 | 0 | "latitude max < latitude min."); |
3392 | 0 | return false; |
3393 | 0 | } |
3394 | | // handle antimeridian |
3395 | 0 | delta_x = (xmax - xmin + 360.0) / side_pts; |
3396 | 0 | } |
3397 | 0 | else |
3398 | 0 | { |
3399 | 0 | delta_x = (xmax - xmin) / side_pts; |
3400 | 0 | } |
3401 | 0 | if (degree_input && ymax < ymin) |
3402 | 0 | { |
3403 | 0 | if (input_lon_lat_order) |
3404 | 0 | { |
3405 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
3406 | 0 | "latitude max < latitude min."); |
3407 | 0 | return false; |
3408 | 0 | } |
3409 | | // handle antimeridian |
3410 | 0 | delta_y = (ymax - ymin + 360.0) / side_pts; |
3411 | 0 | } |
3412 | 0 | else |
3413 | 0 | { |
3414 | 0 | delta_y = (ymax - ymin) / side_pts; |
3415 | 0 | } |
3416 | | |
3417 | | // build densified bounding box |
3418 | | // Note: must be a linear ring for antimeridian logic |
3419 | 0 | for (int iii = 0; iii < side_pts; iii++) |
3420 | 0 | { |
3421 | | // xmin boundary |
3422 | 0 | y_boundary_array[iii] = ymax - iii * delta_y; |
3423 | 0 | x_boundary_array[iii] = xmin; |
3424 | | // ymin boundary |
3425 | 0 | y_boundary_array[iii + side_pts] = ymin; |
3426 | 0 | x_boundary_array[iii + side_pts] = xmin + iii * delta_x; |
3427 | | // xmax boundary |
3428 | 0 | y_boundary_array[iii + side_pts * 2] = ymin + iii * delta_y; |
3429 | 0 | x_boundary_array[iii + side_pts * 2] = xmax; |
3430 | | // ymax boundary |
3431 | 0 | y_boundary_array[iii + side_pts * 3] = ymax; |
3432 | 0 | x_boundary_array[iii + side_pts * 3] = xmax - iii * delta_x; |
3433 | 0 | } |
3434 | |
|
3435 | 0 | { |
3436 | 0 | CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); |
3437 | 0 | bool success = TransformWithErrorCodes( |
3438 | 0 | boundary_len, &x_boundary_array[0], &y_boundary_array[0], nullptr, |
3439 | 0 | nullptr, anErrorCodes.data()); |
3440 | 0 | if (!success) |
3441 | 0 | { |
3442 | 0 | for (int i = 0; i < boundary_len; ++i) |
3443 | 0 | { |
3444 | 0 | if (anErrorCodes[i] == 0) |
3445 | 0 | { |
3446 | 0 | success = true; |
3447 | 0 | break; |
3448 | 0 | } |
3449 | 0 | } |
3450 | 0 | if (!success) |
3451 | 0 | return false; |
3452 | 0 | } |
3453 | 0 | } |
3454 | | |
3455 | 0 | if (!degree_output) |
3456 | 0 | { |
3457 | 0 | *out_xmin = |
3458 | 0 | simple_min(&x_boundary_array[0], anErrorCodes.data(), boundary_len); |
3459 | 0 | *out_xmax = |
3460 | 0 | simple_max(&x_boundary_array[0], anErrorCodes.data(), boundary_len); |
3461 | 0 | *out_ymin = |
3462 | 0 | simple_min(&y_boundary_array[0], anErrorCodes.data(), boundary_len); |
3463 | 0 | *out_ymax = |
3464 | 0 | simple_max(&y_boundary_array[0], anErrorCodes.data(), boundary_len); |
3465 | |
|
3466 | 0 | if (poSRSTarget->IsProjected()) |
3467 | 0 | { |
3468 | 0 | CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler); |
3469 | |
|
3470 | 0 | auto poBaseTarget = std::unique_ptr<OGRSpatialReference>( |
3471 | 0 | poSRSTarget->CloneGeogCS()); |
3472 | 0 | poBaseTarget->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
3473 | |
|
3474 | 0 | auto poCTBaseTargetToSrc = |
3475 | 0 | std::unique_ptr<OGRCoordinateTransformation>( |
3476 | 0 | OGRCreateCoordinateTransformation(poBaseTarget.get(), |
3477 | 0 | poSRSSource)); |
3478 | 0 | if (poCTBaseTargetToSrc) |
3479 | 0 | { |
3480 | 0 | const double dfLon0 = |
3481 | 0 | poSRSTarget->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0); |
3482 | |
|
3483 | 0 | double dfSignedPoleLat = 0; |
3484 | 0 | double dfAbsPoleLat = 90; |
3485 | 0 | bool bIncludesPole = false; |
3486 | 0 | const char *pszProjection = |
3487 | 0 | poSRSTarget->GetAttrValue("PROJECTION"); |
3488 | 0 | if (pszProjection && |
3489 | 0 | EQUAL(pszProjection, SRS_PT_MERCATOR_1SP) && dfLon0 == 0) |
3490 | 0 | { |
3491 | | // This MAX_LAT_MERCATOR values is equivalent to the |
3492 | | // semi_major_axis * PI easting/northing value only |
3493 | | // for EPSG:3857, but it is also quite |
3494 | | // reasonable for other Mercator projections |
3495 | 0 | constexpr double MAX_LAT_MERCATOR = 85.0511287798066; |
3496 | 0 | dfAbsPoleLat = MAX_LAT_MERCATOR; |
3497 | 0 | } |
3498 | | |
3499 | | // Detect if a point at long = central_meridian and |
3500 | | // lat = +/- 90deg is included in the extent. |
3501 | | // Helps for example for EPSG:4326 to ESRI:53037 |
3502 | 0 | for (int iSign = -1; iSign <= 1; iSign += 2) |
3503 | 0 | { |
3504 | 0 | double dfX = dfLon0; |
3505 | 0 | constexpr double EPS = 1e-8; |
3506 | 0 | double dfY = iSign * (dfAbsPoleLat - EPS); |
3507 | |
|
3508 | 0 | if (poCTBaseTargetToSrc->TransformWithErrorCodes( |
3509 | 0 | 1, &dfX, &dfY, nullptr, nullptr, nullptr) && |
3510 | 0 | dfX >= xmin && dfY >= ymin && dfX <= xmax && |
3511 | 0 | dfY <= ymax && |
3512 | 0 | TransformWithErrorCodes(1, &dfX, &dfY, nullptr, nullptr, |
3513 | 0 | nullptr)) |
3514 | 0 | { |
3515 | 0 | dfSignedPoleLat = iSign * dfAbsPoleLat; |
3516 | 0 | bIncludesPole = true; |
3517 | 0 | *out_xmin = std::min(*out_xmin, dfX); |
3518 | 0 | *out_ymin = std::min(*out_ymin, dfY); |
3519 | 0 | *out_xmax = std::max(*out_xmax, dfX); |
3520 | 0 | *out_ymax = std::max(*out_ymax, dfY); |
3521 | 0 | } |
3522 | 0 | } |
3523 | |
|
3524 | 0 | const auto TryAtPlusMinus180 = |
3525 | 0 | [this, dfLon0, xmin, ymin, xmax, ymax, out_xmin, out_ymin, |
3526 | 0 | out_xmax, out_ymax, &poCTBaseTargetToSrc](double dfLat) |
3527 | 0 | { |
3528 | 0 | for (int iSign = -1; iSign <= 1; iSign += 2) |
3529 | 0 | { |
3530 | 0 | constexpr double EPS = 1e-8; |
3531 | 0 | double dfX = |
3532 | 0 | fmod(dfLon0 + iSign * (180 - EPS) + 180, 360) - 180; |
3533 | 0 | double dfY = dfLat; |
3534 | |
|
3535 | 0 | if (poCTBaseTargetToSrc->TransformWithErrorCodes( |
3536 | 0 | 1, &dfX, &dfY, nullptr, nullptr, nullptr) && |
3537 | 0 | dfX >= xmin && dfY >= ymin && dfX <= xmax && |
3538 | 0 | dfY <= ymax && |
3539 | 0 | TransformWithErrorCodes(1, &dfX, &dfY, nullptr, |
3540 | 0 | nullptr, nullptr)) |
3541 | 0 | { |
3542 | 0 | *out_xmin = std::min(*out_xmin, dfX); |
3543 | 0 | *out_ymin = std::min(*out_ymin, dfY); |
3544 | 0 | *out_xmax = std::max(*out_xmax, dfX); |
3545 | 0 | *out_ymax = std::max(*out_ymax, dfY); |
3546 | 0 | } |
3547 | 0 | } |
3548 | 0 | }; |
3549 | | |
3550 | | // For a projected CRS with a central meridian != 0, try to |
3551 | | // reproject the points with long = +/- 180deg of the central |
3552 | | // meridian and at lat = latitude_of_origin. |
3553 | 0 | const double dfLat0 = poSRSTarget->GetNormProjParm( |
3554 | 0 | SRS_PP_LATITUDE_OF_ORIGIN, 0.0); |
3555 | 0 | if (dfLon0 != 0) |
3556 | 0 | { |
3557 | 0 | TryAtPlusMinus180(dfLat0); |
3558 | 0 | } |
3559 | |
|
3560 | 0 | if (bIncludesPole && dfLat0 != dfSignedPoleLat) |
3561 | 0 | { |
3562 | 0 | TryAtPlusMinus180(dfSignedPoleLat); |
3563 | 0 | } |
3564 | 0 | } |
3565 | 0 | } |
3566 | 0 | } |
3567 | 0 | else if (north_pole_in_bounds && output_lon_lat_order) |
3568 | 0 | { |
3569 | 0 | *out_xmin = -180; |
3570 | 0 | *out_ymin = |
3571 | 0 | simple_min(&y_boundary_array[0], anErrorCodes.data(), boundary_len); |
3572 | 0 | *out_xmax = 180; |
3573 | 0 | *out_ymax = 90; |
3574 | 0 | } |
3575 | 0 | else if (north_pole_in_bounds) |
3576 | 0 | { |
3577 | 0 | *out_xmin = |
3578 | 0 | simple_min(&x_boundary_array[0], anErrorCodes.data(), boundary_len); |
3579 | 0 | *out_ymin = -180; |
3580 | 0 | *out_xmax = 90; |
3581 | 0 | *out_ymax = 180; |
3582 | 0 | } |
3583 | 0 | else if (south_pole_in_bounds && output_lon_lat_order) |
3584 | 0 | { |
3585 | 0 | *out_xmin = -180; |
3586 | 0 | *out_ymin = -90; |
3587 | 0 | *out_xmax = 180; |
3588 | 0 | *out_ymax = |
3589 | 0 | simple_max(&y_boundary_array[0], anErrorCodes.data(), boundary_len); |
3590 | 0 | } |
3591 | 0 | else if (south_pole_in_bounds) |
3592 | 0 | { |
3593 | 0 | *out_xmin = -90; |
3594 | 0 | *out_ymin = -180; |
3595 | 0 | *out_xmax = |
3596 | 0 | simple_max(&x_boundary_array[0], anErrorCodes.data(), boundary_len); |
3597 | 0 | *out_ymax = 180; |
3598 | 0 | } |
3599 | 0 | else if (output_lon_lat_order) |
3600 | 0 | { |
3601 | 0 | *out_xmin = antimeridian_min(&x_boundary_array[0], anErrorCodes.data(), |
3602 | 0 | boundary_len); |
3603 | 0 | *out_xmax = antimeridian_max(&x_boundary_array[0], anErrorCodes.data(), |
3604 | 0 | boundary_len); |
3605 | 0 | *out_ymin = |
3606 | 0 | simple_min(&y_boundary_array[0], anErrorCodes.data(), boundary_len); |
3607 | 0 | *out_ymax = |
3608 | 0 | simple_max(&y_boundary_array[0], anErrorCodes.data(), boundary_len); |
3609 | 0 | } |
3610 | 0 | else |
3611 | 0 | { |
3612 | 0 | *out_xmin = |
3613 | 0 | simple_min(&x_boundary_array[0], anErrorCodes.data(), boundary_len); |
3614 | 0 | *out_xmax = |
3615 | 0 | simple_max(&x_boundary_array[0], anErrorCodes.data(), boundary_len); |
3616 | 0 | *out_ymin = antimeridian_min(&y_boundary_array[0], anErrorCodes.data(), |
3617 | 0 | boundary_len); |
3618 | 0 | *out_ymax = antimeridian_max(&y_boundary_array[0], anErrorCodes.data(), |
3619 | 0 | boundary_len); |
3620 | 0 | } |
3621 | |
|
3622 | 0 | return *out_xmin != HUGE_VAL && *out_ymin != HUGE_VAL && |
3623 | 0 | *out_xmax != HUGE_VAL && *out_ymax != HUGE_VAL; |
3624 | 0 | } |
3625 | | |
3626 | | /************************************************************************/ |
3627 | | /* Clone() */ |
3628 | | /************************************************************************/ |
3629 | | |
3630 | | OGRCoordinateTransformation *OGRProjCT::Clone() const |
3631 | 0 | { |
3632 | 0 | std::unique_ptr<OGRProjCT> poNewCT(new OGRProjCT(*this)); |
3633 | | #if (PROJ_VERSION_MAJOR * 10000 + PROJ_VERSION_MINOR * 100 + \ |
3634 | | PROJ_VERSION_PATCH) < 80001 |
3635 | | // See https://github.com/OSGeo/PROJ/pull/2582 |
3636 | | // This may fail before PROJ 8.0.1 if the m_pj object is a "meta" |
3637 | | // operation being a set of real operations |
3638 | | bool bCloneDone = ((m_pj == nullptr) == (poNewCT->m_pj == nullptr)); |
3639 | | if (!bCloneDone) |
3640 | | { |
3641 | | poNewCT.reset(new OGRProjCT()); |
3642 | | auto ret = |
3643 | | poNewCT->Initialize(poSRSSource, m_osSrcSRS.c_str(), poSRSTarget, |
3644 | | m_osTargetSRS.c_str(), m_options); |
3645 | | if (!ret) |
3646 | | { |
3647 | | return nullptr; |
3648 | | } |
3649 | | } |
3650 | | #endif // PROJ_VERSION |
3651 | 0 | return poNewCT.release(); |
3652 | 0 | } |
3653 | | |
3654 | | /************************************************************************/ |
3655 | | /* GetInverse() */ |
3656 | | /************************************************************************/ |
3657 | | |
3658 | | OGRCoordinateTransformation *OGRProjCT::GetInverse() const |
3659 | 0 | { |
3660 | 0 | PJ *new_pj = nullptr; |
3661 | | // m_pj can be nullptr if using m_eStrategy != PROJ |
3662 | 0 | if (m_pj && !bWebMercatorToWGS84LongLat && !bNoTransform) |
3663 | 0 | { |
3664 | | // See https://github.com/OSGeo/PROJ/pull/2582 |
3665 | | // This may fail before PROJ 8.0.1 if the m_pj object is a "meta" |
3666 | | // operation being a set of real operations |
3667 | 0 | new_pj = proj_clone(OSRGetProjTLSContext(), m_pj); |
3668 | 0 | } |
3669 | |
|
3670 | 0 | OGRCoordinateTransformationOptions newOptions(m_options); |
3671 | 0 | std::swap(newOptions.d->bHasSourceCenterLong, |
3672 | 0 | newOptions.d->bHasTargetCenterLong); |
3673 | 0 | std::swap(newOptions.d->dfSourceCenterLong, |
3674 | 0 | newOptions.d->dfTargetCenterLong); |
3675 | 0 | newOptions.d->bReverseCO = !newOptions.d->bReverseCO; |
3676 | 0 | newOptions.d->RefreshCheckWithInvertProj(); |
3677 | |
|
3678 | 0 | if (new_pj == nullptr && !bNoTransform) |
3679 | 0 | { |
3680 | 0 | return OGRCreateCoordinateTransformation(poSRSTarget, poSRSSource, |
3681 | 0 | newOptions); |
3682 | 0 | } |
3683 | | |
3684 | 0 | auto poNewCT = new OGRProjCT(); |
3685 | |
|
3686 | 0 | if (poSRSTarget) |
3687 | 0 | poNewCT->poSRSSource = poSRSTarget->Clone(); |
3688 | 0 | poNewCT->m_eSourceFirstAxisOrient = m_eTargetFirstAxisOrient; |
3689 | 0 | poNewCT->bSourceLatLong = bTargetLatLong; |
3690 | 0 | poNewCT->bSourceWrap = bTargetWrap; |
3691 | 0 | poNewCT->dfSourceWrapLong = dfTargetWrapLong; |
3692 | 0 | poNewCT->bSourceIsDynamicCRS = bTargetIsDynamicCRS; |
3693 | 0 | poNewCT->dfSourceCoordinateEpoch = dfTargetCoordinateEpoch; |
3694 | 0 | poNewCT->m_osSrcSRS = m_osTargetSRS; |
3695 | |
|
3696 | 0 | if (poSRSSource) |
3697 | 0 | poNewCT->poSRSTarget = poSRSSource->Clone(); |
3698 | 0 | poNewCT->m_eTargetFirstAxisOrient = m_eSourceFirstAxisOrient; |
3699 | 0 | poNewCT->bTargetLatLong = bSourceLatLong; |
3700 | 0 | poNewCT->bTargetWrap = bSourceWrap; |
3701 | 0 | poNewCT->dfTargetWrapLong = dfSourceWrapLong; |
3702 | 0 | poNewCT->bTargetIsDynamicCRS = bSourceIsDynamicCRS; |
3703 | 0 | poNewCT->dfTargetCoordinateEpoch = dfSourceCoordinateEpoch; |
3704 | 0 | poNewCT->m_osTargetSRS = m_osSrcSRS; |
3705 | |
|
3706 | 0 | poNewCT->ComputeThreshold(); |
3707 | |
|
3708 | 0 | poNewCT->m_pj = new_pj; |
3709 | 0 | poNewCT->m_bReversePj = !m_bReversePj; |
3710 | 0 | poNewCT->bNoTransform = bNoTransform; |
3711 | 0 | poNewCT->m_eStrategy = m_eStrategy; |
3712 | 0 | poNewCT->m_options = newOptions; |
3713 | |
|
3714 | 0 | poNewCT->DetectWebMercatorToWGS84(); |
3715 | |
|
3716 | 0 | return poNewCT; |
3717 | 0 | } |
3718 | | |
3719 | | /************************************************************************/ |
3720 | | /* OSRCTCleanCache() */ |
3721 | | /************************************************************************/ |
3722 | | |
3723 | | void OSRCTCleanCache() |
3724 | 0 | { |
3725 | 0 | std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex); |
3726 | 0 | delete g_poCTCache; |
3727 | 0 | g_poCTCache = nullptr; |
3728 | 0 | } |
3729 | | |
3730 | | /************************************************************************/ |
3731 | | /* MakeCacheKey() */ |
3732 | | /************************************************************************/ |
3733 | | |
3734 | | CTCacheKey OGRProjCT::MakeCacheKey( |
3735 | | const OGRSpatialReference *poSRS1, const char *pszSrcSRS, |
3736 | | const OGRSpatialReference *poSRS2, const char *pszTargetSRS, |
3737 | | const OGRCoordinateTransformationOptions &options) |
3738 | 0 | { |
3739 | 0 | const auto GetKeyForSRS = |
3740 | 0 | [](const OGRSpatialReference *poSRS, const char *pszText) |
3741 | 0 | { |
3742 | 0 | if (poSRS) |
3743 | 0 | { |
3744 | 0 | std::string ret(pszText); |
3745 | 0 | const auto &mapping = poSRS->GetDataAxisToSRSAxisMapping(); |
3746 | 0 | for (const auto &axis : mapping) |
3747 | 0 | { |
3748 | 0 | ret += std::to_string(axis); |
3749 | 0 | } |
3750 | 0 | return ret; |
3751 | 0 | } |
3752 | 0 | else |
3753 | 0 | { |
3754 | 0 | return std::string("null"); |
3755 | 0 | } |
3756 | 0 | }; |
3757 | |
|
3758 | 0 | std::string ret(GetKeyForSRS(poSRS1, pszSrcSRS)); |
3759 | 0 | ret += GetKeyForSRS(poSRS2, pszTargetSRS); |
3760 | 0 | ret += options.d->GetKey(); |
3761 | 0 | return ret; |
3762 | 0 | } |
3763 | | |
3764 | | /************************************************************************/ |
3765 | | /* InsertIntoCache() */ |
3766 | | /************************************************************************/ |
3767 | | |
3768 | | void OGRProjCT::InsertIntoCache(OGRProjCT *poCT) |
3769 | 0 | { |
3770 | 0 | { |
3771 | 0 | std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex); |
3772 | 0 | if (g_poCTCache == nullptr) |
3773 | 0 | { |
3774 | 0 | g_poCTCache = new lru11::Cache<CTCacheKey, CTCacheValue>(); |
3775 | 0 | } |
3776 | 0 | } |
3777 | 0 | const auto key = MakeCacheKey(poCT->poSRSSource, poCT->m_osSrcSRS.c_str(), |
3778 | 0 | poCT->poSRSTarget, |
3779 | 0 | poCT->m_osTargetSRS.c_str(), poCT->m_options); |
3780 | |
|
3781 | 0 | std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex); |
3782 | 0 | if (g_poCTCache->contains(key)) |
3783 | 0 | { |
3784 | 0 | delete poCT; |
3785 | 0 | return; |
3786 | 0 | } |
3787 | 0 | g_poCTCache->insert(key, std::unique_ptr<OGRProjCT>(poCT)); |
3788 | 0 | } |
3789 | | |
3790 | | /************************************************************************/ |
3791 | | /* FindFromCache() */ |
3792 | | /************************************************************************/ |
3793 | | |
3794 | | OGRProjCT *OGRProjCT::FindFromCache( |
3795 | | const OGRSpatialReference *poSource, const char *pszSrcSRS, |
3796 | | const OGRSpatialReference *poTarget, const char *pszTargetSRS, |
3797 | | const OGRCoordinateTransformationOptions &options) |
3798 | 0 | { |
3799 | 0 | { |
3800 | 0 | std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex); |
3801 | 0 | if (g_poCTCache == nullptr || g_poCTCache->empty()) |
3802 | 0 | return nullptr; |
3803 | 0 | } |
3804 | | |
3805 | 0 | const auto key = |
3806 | 0 | MakeCacheKey(poSource, pszSrcSRS, poTarget, pszTargetSRS, options); |
3807 | | // Get value from cache and remove it |
3808 | 0 | std::lock_guard<std::mutex> oGuard(g_oCTCacheMutex); |
3809 | 0 | CTCacheValue *cachedValue = g_poCTCache->getPtr(key); |
3810 | 0 | if (cachedValue) |
3811 | 0 | { |
3812 | 0 | auto poCT = cachedValue->release(); |
3813 | 0 | g_poCTCache->remove(key); |
3814 | 0 | return poCT; |
3815 | 0 | } |
3816 | 0 | return nullptr; |
3817 | 0 | } |
3818 | | |
3819 | | //! @endcond |
3820 | | |
3821 | | /************************************************************************/ |
3822 | | /* OCTTransform() */ |
3823 | | /************************************************************************/ |
3824 | | |
3825 | | /** Transform an array of points |
3826 | | * |
3827 | | * @param hTransform Transformation object |
3828 | | * @param nCount Number of points |
3829 | | * @param x Array of nCount x values. |
3830 | | * @param y Array of nCount y values. |
3831 | | * @param z Array of nCount z values. |
3832 | | * @return TRUE if a transformation could be found (but not all points may |
3833 | | * have necessarily succeed to transform), otherwise FALSE. |
3834 | | */ |
3835 | | int CPL_STDCALL OCTTransform(OGRCoordinateTransformationH hTransform, |
3836 | | int nCount, double *x, double *y, double *z) |
3837 | | |
3838 | 0 | { |
3839 | 0 | VALIDATE_POINTER1(hTransform, "OCTTransform", FALSE); |
3840 | | |
3841 | 0 | return OGRCoordinateTransformation::FromHandle(hTransform) |
3842 | 0 | ->Transform(nCount, x, y, z); |
3843 | 0 | } |
3844 | | |
3845 | | /************************************************************************/ |
3846 | | /* OCTTransformEx() */ |
3847 | | /************************************************************************/ |
3848 | | |
3849 | | /** Transform an array of points |
3850 | | * |
3851 | | * @param hTransform Transformation object |
3852 | | * @param nCount Number of points |
3853 | | * @param x Array of nCount x values. |
3854 | | * @param y Array of nCount y values. |
3855 | | * @param z Array of nCount z values. |
3856 | | * @param pabSuccess Output array of nCount value that will be set to TRUE/FALSE |
3857 | | * @return TRUE if a transformation could be found (but not all points may |
3858 | | * have necessarily succeed to transform), otherwise FALSE. |
3859 | | */ |
3860 | | int CPL_STDCALL OCTTransformEx(OGRCoordinateTransformationH hTransform, |
3861 | | int nCount, double *x, double *y, double *z, |
3862 | | int *pabSuccess) |
3863 | | |
3864 | 0 | { |
3865 | 0 | VALIDATE_POINTER1(hTransform, "OCTTransformEx", FALSE); |
3866 | | |
3867 | 0 | return OGRCoordinateTransformation::FromHandle(hTransform) |
3868 | 0 | ->Transform(nCount, x, y, z, pabSuccess); |
3869 | 0 | } |
3870 | | |
3871 | | /************************************************************************/ |
3872 | | /* OCTTransform4D() */ |
3873 | | /************************************************************************/ |
3874 | | |
3875 | | /** Transform an array of points |
3876 | | * |
3877 | | * @param hTransform Transformation object |
3878 | | * @param nCount Number of points |
3879 | | * @param x Array of nCount x values. Should not be NULL |
3880 | | * @param y Array of nCount y values. Should not be NULL |
3881 | | * @param z Array of nCount z values. Might be NULL |
3882 | | * @param t Array of nCount time values. Might be NULL |
3883 | | * @param pabSuccess Output array of nCount value that will be set to |
3884 | | * TRUE/FALSE. Might be NULL. |
3885 | | * @since GDAL 3.0 |
3886 | | * @return TRUE if a transformation could be found (but not all points may |
3887 | | * have necessarily succeed to transform), otherwise FALSE. |
3888 | | */ |
3889 | | int OCTTransform4D(OGRCoordinateTransformationH hTransform, int nCount, |
3890 | | double *x, double *y, double *z, double *t, int *pabSuccess) |
3891 | | |
3892 | 0 | { |
3893 | 0 | VALIDATE_POINTER1(hTransform, "OCTTransform4D", FALSE); |
3894 | | |
3895 | 0 | return OGRCoordinateTransformation::FromHandle(hTransform) |
3896 | 0 | ->Transform(nCount, x, y, z, t, pabSuccess); |
3897 | 0 | } |
3898 | | |
3899 | | /************************************************************************/ |
3900 | | /* OCTTransform4DWithErrorCodes() */ |
3901 | | /************************************************************************/ |
3902 | | |
3903 | | /** Transform an array of points |
3904 | | * |
3905 | | * @param hTransform Transformation object |
3906 | | * @param nCount Number of points |
3907 | | * @param x Array of nCount x values. Should not be NULL |
3908 | | * @param y Array of nCount y values. Should not be NULL |
3909 | | * @param z Array of nCount z values. Might be NULL |
3910 | | * @param t Array of nCount time values. Might be NULL |
3911 | | * @param panErrorCodes Output array of nCount value that will be set to 0 for |
3912 | | * success, or a non-zero value for failure. Refer to |
3913 | | * PROJ 8 public error codes. Might be NULL |
3914 | | * @since GDAL 3.3, and PROJ 8 to be able to use PROJ public error codes |
3915 | | * @return TRUE if a transformation could be found (but not all points may |
3916 | | * have necessarily succeed to transform), otherwise FALSE. |
3917 | | */ |
3918 | | int OCTTransform4DWithErrorCodes(OGRCoordinateTransformationH hTransform, |
3919 | | int nCount, double *x, double *y, double *z, |
3920 | | double *t, int *panErrorCodes) |
3921 | | |
3922 | 0 | { |
3923 | 0 | VALIDATE_POINTER1(hTransform, "OCTTransform4DWithErrorCodes", FALSE); |
3924 | | |
3925 | 0 | return OGRCoordinateTransformation::FromHandle(hTransform) |
3926 | 0 | ->TransformWithErrorCodes(nCount, x, y, z, t, panErrorCodes); |
3927 | 0 | } |
3928 | | |
3929 | | /************************************************************************/ |
3930 | | /* OCTTransformBounds() */ |
3931 | | /************************************************************************/ |
3932 | | /** \brief Transform boundary. |
3933 | | * |
3934 | | * Transform boundary densifying the edges to account for nonlinear |
3935 | | * transformations along these edges and extracting the outermost bounds. |
3936 | | * |
3937 | | * If the destination CRS is geographic, the first axis is longitude, |
3938 | | * and *out_xmax < *out_xmin then the bounds crossed the antimeridian. |
3939 | | * In this scenario there are two polygons, one on each side of the |
3940 | | * antimeridian. The first polygon should be constructed with |
3941 | | * (*out_xmin, *out_ymin, 180, ymax) and the second with |
3942 | | * (-180, *out_ymin, *out_xmax, *out_ymax). |
3943 | | * |
3944 | | * If the destination CRS is geographic, the first axis is latitude, |
3945 | | * and *out_ymax < *out_ymin then the bounds crossed the antimeridian. |
3946 | | * In this scenario there are two polygons, one on each side of the |
3947 | | * antimeridian. The first polygon should be constructed with |
3948 | | * (*out_ymin, *out_xmin, *out_ymax, 180) and the second with |
3949 | | * (*out_ymin, -180, *out_ymax, *out_xmax). |
3950 | | * |
3951 | | * @param hTransform Transformation object |
3952 | | * @param xmin Minimum bounding coordinate of the first axis in source CRS. |
3953 | | * @param ymin Minimum bounding coordinate of the second axis in source CRS. |
3954 | | * @param xmax Maximum bounding coordinate of the first axis in source CRS. |
3955 | | * @param ymax Maximum bounding coordinate of the second axis in source CRS. |
3956 | | * @param out_xmin Minimum bounding coordinate of the first axis in target CRS |
3957 | | * @param out_ymin Minimum bounding coordinate of the second axis in target CRS. |
3958 | | * @param out_xmax Maximum bounding coordinate of the first axis in target CRS. |
3959 | | * @param out_ymax Maximum bounding coordinate of the second axis in target CRS. |
3960 | | * @param densify_pts Recommended to use 21. This is the number of points |
3961 | | * to use to densify the bounding polygon in the transformation. |
3962 | | * @return TRUE if successful. FALSE if failures encountered. |
3963 | | * @since 3.4 |
3964 | | */ |
3965 | | int CPL_STDCALL OCTTransformBounds(OGRCoordinateTransformationH hTransform, |
3966 | | const double xmin, const double ymin, |
3967 | | const double xmax, const double ymax, |
3968 | | double *out_xmin, double *out_ymin, |
3969 | | double *out_xmax, double *out_ymax, |
3970 | | int densify_pts) |
3971 | | |
3972 | 0 | { |
3973 | 0 | VALIDATE_POINTER1(hTransform, "TransformBounds", FALSE); |
3974 | | |
3975 | 0 | return OGRProjCT::FromHandle(hTransform) |
3976 | 0 | ->TransformBounds(xmin, ymin, xmax, ymax, out_xmin, out_ymin, out_xmax, |
3977 | 0 | out_ymax, densify_pts); |
3978 | 0 | } |
3979 | | |
3980 | | /************************************************************************/ |
3981 | | /* OGRCTDumpStatistics() */ |
3982 | | /************************************************************************/ |
3983 | | |
3984 | | void OGRCTDumpStatistics() |
3985 | 0 | { |
3986 | | #ifdef DEBUG_PERF |
3987 | | CPLDebug("OGR_CT", "Total time in proj_create_crs_to_crs(): %d ms", |
3988 | | static_cast<int>(g_dfTotalTimeCRStoCRS * 1000)); |
3989 | | CPLDebug("OGR_CT", "Total time in coordinate transformation: %d ms", |
3990 | | static_cast<int>(g_dfTotalTimeReprojection * 1000)); |
3991 | | #endif |
3992 | 0 | } |