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