1 /******************************************************************************
2  *
3  * Project:  PROJ
4  * Purpose:  Test ISO19111:2019 implementation
5  * Author:   Even Rouault <even dot rouault at spatialys dot com>
6  *
7  ******************************************************************************
8  * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "gtest_include.h"
30 
31 #include <cstdio>
32 #include <limits>
33 
34 #include "proj.h"
35 #include "proj_constants.h"
36 #include "proj_experimental.h"
37 
38 #include "proj/common.hpp"
39 #include "proj/coordinateoperation.hpp"
40 #include "proj/coordinatesystem.hpp"
41 #include "proj/crs.hpp"
42 #include "proj/datum.hpp"
43 #include "proj/io.hpp"
44 #include "proj/metadata.hpp"
45 #include "proj/util.hpp"
46 
47 #include <sqlite3.h>
48 
49 using namespace osgeo::proj::common;
50 using namespace osgeo::proj::crs;
51 using namespace osgeo::proj::cs;
52 using namespace osgeo::proj::datum;
53 using namespace osgeo::proj::io;
54 using namespace osgeo::proj::metadata;
55 using namespace osgeo::proj::operation;
56 using namespace osgeo::proj::util;
57 
58 namespace {
59 
60 class CApi : public ::testing::Test {
61 
DummyLogFunction(void *,int,const char *)62     static void DummyLogFunction(void *, int, const char *) {}
63 
64   protected:
SetUp()65     void SetUp() override {
66         m_ctxt = proj_context_create();
67         proj_log_func(m_ctxt, nullptr, DummyLogFunction);
68     }
69 
TearDown()70     void TearDown() override {
71         if (m_ctxt)
72             proj_context_destroy(m_ctxt);
73     }
74 
createBoundCRS()75     static BoundCRSNNPtr createBoundCRS() {
76         return BoundCRS::create(
77             GeographicCRS::EPSG_4807, GeographicCRS::EPSG_4326,
78             Transformation::create(
79                 PropertyMap(), GeographicCRS::EPSG_4807,
80                 GeographicCRS::EPSG_4326, nullptr, PropertyMap(),
81                 {OperationParameter::create(
82                     PropertyMap().set(IdentifiedObject::NAME_KEY, "foo"))},
83                 {ParameterValue::create(
84                     Measure(1.0, UnitOfMeasure::SCALE_UNITY))},
85                 {}));
86     }
87 
createProjectedCRS()88     static ProjectedCRSNNPtr createProjectedCRS() {
89         PropertyMap propertiesCRS;
90         propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
91             .set(Identifier::CODE_KEY, 32631)
92             .set(IdentifiedObject::NAME_KEY, "WGS 84 / UTM zone 31N");
93         return ProjectedCRS::create(
94             propertiesCRS, GeographicCRS::EPSG_4326,
95             Conversion::createUTM(PropertyMap(), 31, true),
96             CartesianCS::createEastingNorthing(UnitOfMeasure::METRE));
97     }
98 
createVerticalCRS()99     static VerticalCRSNNPtr createVerticalCRS() {
100         PropertyMap propertiesVDatum;
101         propertiesVDatum.set(Identifier::CODESPACE_KEY, "EPSG")
102             .set(Identifier::CODE_KEY, 5101)
103             .set(IdentifiedObject::NAME_KEY, "Ordnance Datum Newlyn");
104         auto vdatum = VerticalReferenceFrame::create(propertiesVDatum);
105         PropertyMap propertiesCRS;
106         propertiesCRS.set(Identifier::CODESPACE_KEY, "EPSG")
107             .set(Identifier::CODE_KEY, 5701)
108             .set(IdentifiedObject::NAME_KEY, "ODN height");
109         return VerticalCRS::create(
110             propertiesCRS, vdatum,
111             VerticalCS::createGravityRelatedHeight(UnitOfMeasure::METRE));
112     }
113 
createCompoundCRS()114     static CompoundCRSNNPtr createCompoundCRS() {
115         PropertyMap properties;
116         properties.set(Identifier::CODESPACE_KEY, "codespace")
117             .set(Identifier::CODE_KEY, "code")
118             .set(IdentifiedObject::NAME_KEY, "horizontal + vertical");
119         return CompoundCRS::create(
120             properties,
121             std::vector<CRSNNPtr>{createProjectedCRS(), createVerticalCRS()});
122     }
123 
124     PJ_CONTEXT *m_ctxt = nullptr;
125 
126     struct ObjectKeeper {
127         PJ *m_obj = nullptr;
ObjectKeeper__anon29fdcccb0111::CApi::ObjectKeeper128         explicit ObjectKeeper(PJ *obj) : m_obj(obj) {}
~ObjectKeeper__anon29fdcccb0111::CApi::ObjectKeeper129         ~ObjectKeeper() { proj_destroy(m_obj); }
130 
131         ObjectKeeper(const ObjectKeeper &) = delete;
132         ObjectKeeper &operator=(const ObjectKeeper &) = delete;
133     };
134 
135     struct PjContextKeeper {
136         PJ_CONTEXT *m_ctxt = nullptr;
PjContextKeeper__anon29fdcccb0111::CApi::PjContextKeeper137         explicit PjContextKeeper(PJ_CONTEXT *ctxt) : m_ctxt(ctxt) {}
~PjContextKeeper__anon29fdcccb0111::CApi::PjContextKeeper138         ~PjContextKeeper() { proj_context_destroy(m_ctxt); }
139 
140         PjContextKeeper(const PjContextKeeper &) = delete;
141         PjContextKeeper &operator=(const PjContextKeeper &) = delete;
142     };
143 
144     struct ContextKeeper {
145         PJ_OPERATION_FACTORY_CONTEXT *m_op_ctxt = nullptr;
ContextKeeper__anon29fdcccb0111::CApi::ContextKeeper146         explicit ContextKeeper(PJ_OPERATION_FACTORY_CONTEXT *op_ctxt)
147             : m_op_ctxt(op_ctxt) {}
~ContextKeeper__anon29fdcccb0111::CApi::ContextKeeper148         ~ContextKeeper() { proj_operation_factory_context_destroy(m_op_ctxt); }
149 
150         ContextKeeper(const ContextKeeper &) = delete;
151         ContextKeeper &operator=(const ContextKeeper &) = delete;
152     };
153 
154     struct ObjListKeeper {
155         PJ_OBJ_LIST *m_res = nullptr;
ObjListKeeper__anon29fdcccb0111::CApi::ObjListKeeper156         explicit ObjListKeeper(PJ_OBJ_LIST *res) : m_res(res) {}
~ObjListKeeper__anon29fdcccb0111::CApi::ObjListKeeper157         ~ObjListKeeper() { proj_list_destroy(m_res); }
158 
159         ObjListKeeper(const ObjListKeeper &) = delete;
160         ObjListKeeper &operator=(const ObjListKeeper &) = delete;
161     };
162 };
163 
164 // ---------------------------------------------------------------------------
165 
TEST_F(CApi,proj_create)166 TEST_F(CApi, proj_create) {
167     proj_destroy(nullptr);
168     EXPECT_EQ(proj_create(m_ctxt, "invalid"), nullptr);
169     {
170         auto obj =
171             proj_create(m_ctxt, GeographicCRS::EPSG_4326
172                                     ->exportToWKT(WKTFormatter::create().get())
173                                     .c_str());
174         ObjectKeeper keeper(obj);
175         EXPECT_NE(obj, nullptr);
176 
177         // Check that functions that operate on 'non-C++' PJ don't crash
178         PJ_COORD coord;
179         coord.xyzt.x = 0;
180         coord.xyzt.y = 0;
181         coord.xyzt.z = 0;
182         coord.xyzt.t = 0;
183         EXPECT_EQ(proj_trans(obj, PJ_FWD, coord).xyzt.x,
184                   std::numeric_limits<double>::infinity());
185 
186         EXPECT_EQ(proj_geod(obj, coord, coord).xyzt.x,
187                   std::numeric_limits<double>::infinity());
188         EXPECT_EQ(proj_lp_dist(obj, coord, coord),
189                   std::numeric_limits<double>::infinity());
190 
191         auto info = proj_pj_info(obj);
192         EXPECT_EQ(info.id, nullptr);
193         ASSERT_NE(info.description, nullptr);
194         EXPECT_EQ(info.description, std::string("WGS 84"));
195         ASSERT_NE(info.definition, nullptr);
196         EXPECT_EQ(info.definition, std::string(""));
197     }
198     {
199         auto obj = proj_create(m_ctxt, "EPSG:4326");
200         ObjectKeeper keeper(obj);
201         EXPECT_NE(obj, nullptr);
202     }
203 }
204 
205 // ---------------------------------------------------------------------------
206 
TEST_F(CApi,proj_create_from_wkt)207 TEST_F(CApi, proj_create_from_wkt) {
208 
209     {
210         EXPECT_EQ(
211             proj_create_from_wkt(m_ctxt, "invalid", nullptr, nullptr, nullptr),
212             nullptr);
213     }
214     {
215         PROJ_STRING_LIST warningList = nullptr;
216         PROJ_STRING_LIST errorList = nullptr;
217         EXPECT_EQ(proj_create_from_wkt(m_ctxt, "invalid", nullptr, &warningList,
218                                        &errorList),
219                   nullptr);
220         EXPECT_EQ(warningList, nullptr);
221         proj_string_list_destroy(warningList);
222         EXPECT_NE(errorList, nullptr);
223         proj_string_list_destroy(errorList);
224     }
225     {
226         auto obj = proj_create_from_wkt(
227             m_ctxt,
228             GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
229                 .c_str(),
230             nullptr, nullptr, nullptr);
231         ObjectKeeper keeper(obj);
232         EXPECT_NE(obj, nullptr);
233     }
234     {
235         auto obj = proj_create_from_wkt(
236             m_ctxt,
237             "GEOGCS[\"WGS 84\",\n"
238             "    DATUM[\"WGS_1984\",\n"
239             "        SPHEROID[\"WGS 84\",6378137,298.257223563,\"unused\"]],\n"
240             "    PRIMEM[\"Greenwich\",0],\n"
241             "    UNIT[\"degree\",0.0174532925199433]]",
242             nullptr, nullptr, nullptr);
243         ObjectKeeper keeper(obj);
244         EXPECT_NE(obj, nullptr);
245     }
246     {
247         PROJ_STRING_LIST warningList = nullptr;
248         PROJ_STRING_LIST errorList = nullptr;
249         auto obj = proj_create_from_wkt(
250             m_ctxt,
251             "GEOGCS[\"WGS 84\",\n"
252             "    DATUM[\"WGS_1984\",\n"
253             "        SPHEROID[\"WGS 84\",6378137,298.257223563,\"unused\"]],\n"
254             "    PRIMEM[\"Greenwich\",0],\n"
255             "    UNIT[\"degree\",0.0174532925199433]]",
256             nullptr, &warningList, &errorList);
257         ObjectKeeper keeper(obj);
258         EXPECT_NE(obj, nullptr);
259         EXPECT_EQ(warningList, nullptr);
260         proj_string_list_destroy(warningList);
261         EXPECT_NE(errorList, nullptr);
262         proj_string_list_destroy(errorList);
263     }
264     {
265         PROJ_STRING_LIST warningList = nullptr;
266         PROJ_STRING_LIST errorList = nullptr;
267         const char *const options[] = {"STRICT=NO", nullptr};
268         auto obj = proj_create_from_wkt(
269             m_ctxt,
270             "GEOGCS[\"WGS 84\",\n"
271             "    DATUM[\"WGS_1984\",\n"
272             "        SPHEROID[\"WGS 84\",6378137,298.257223563,\"unused\"]],\n"
273             "    PRIMEM[\"Greenwich\",0],\n"
274             "    UNIT[\"degree\",0.0174532925199433]]",
275             options, &warningList, &errorList);
276         ObjectKeeper keeper(obj);
277         EXPECT_NE(obj, nullptr);
278         EXPECT_EQ(warningList, nullptr);
279         proj_string_list_destroy(warningList);
280         EXPECT_NE(errorList, nullptr);
281         proj_string_list_destroy(errorList);
282     }
283     {
284         PROJ_STRING_LIST warningList = nullptr;
285         PROJ_STRING_LIST errorList = nullptr;
286         auto obj = proj_create_from_wkt(
287             m_ctxt,
288             GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
289                 .c_str(),
290             nullptr, &warningList, &errorList);
291         ObjectKeeper keeper(obj);
292         EXPECT_NE(obj, nullptr);
293         EXPECT_EQ(warningList, nullptr);
294         EXPECT_EQ(errorList, nullptr);
295     }
296     // Warnings: missing projection parameters
297     {
298         PROJ_STRING_LIST warningList = nullptr;
299         PROJ_STRING_LIST errorList = nullptr;
300         auto obj = proj_create_from_wkt(
301             m_ctxt, "PROJCS[\"test\",\n"
302                     "  GEOGCS[\"WGS 84\",\n"
303                     "    DATUM[\"WGS_1984\",\n"
304                     "        SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
305                     "    PRIMEM[\"Greenwich\",0],\n"
306                     "    UNIT[\"degree\",0.0174532925199433]],\n"
307                     "  PROJECTION[\"Transverse_Mercator\"],\n"
308                     "  PARAMETER[\"latitude_of_origin\",31],\n"
309                     "  UNIT[\"metre\",1]]",
310             nullptr, &warningList, &errorList);
311         ObjectKeeper keeper(obj);
312         EXPECT_NE(obj, nullptr);
313         EXPECT_NE(warningList, nullptr);
314         proj_string_list_destroy(warningList);
315         EXPECT_EQ(errorList, nullptr);
316         proj_string_list_destroy(errorList);
317     }
318     {
319         auto obj = proj_create_from_wkt(
320             m_ctxt, "PROJCS[\"test\",\n"
321                     "  GEOGCS[\"WGS 84\",\n"
322                     "    DATUM[\"WGS_1984\",\n"
323                     "        SPHEROID[\"WGS 84\",6378137,298.257223563]],\n"
324                     "    PRIMEM[\"Greenwich\",0],\n"
325                     "    UNIT[\"degree\",0.0174532925199433]],\n"
326                     "  PROJECTION[\"Transverse_Mercator\"],\n"
327                     "  PARAMETER[\"latitude_of_origin\",31],\n"
328                     "  UNIT[\"metre\",1]]",
329             nullptr, nullptr, nullptr);
330         ObjectKeeper keeper(obj);
331         EXPECT_NE(obj, nullptr);
332     }
333 }
334 
335 // ---------------------------------------------------------------------------
336 
TEST_F(CApi,proj_as_wkt)337 TEST_F(CApi, proj_as_wkt) {
338     auto obj = proj_create_from_wkt(
339         m_ctxt,
340         GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
341             .c_str(),
342         nullptr, nullptr, nullptr);
343     ObjectKeeper keeper(obj);
344     ASSERT_NE(obj, nullptr);
345 
346     {
347         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2019, nullptr);
348         ASSERT_NE(wkt, nullptr);
349         EXPECT_TRUE(std::string(wkt).find("GEOGCRS[") == 0) << wkt;
350     }
351 
352     {
353         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2019_SIMPLIFIED, nullptr);
354         ASSERT_NE(wkt, nullptr);
355         EXPECT_TRUE(std::string(wkt).find("GEOGCRS[") == 0) << wkt;
356         EXPECT_TRUE(std::string(wkt).find("ANGULARUNIT[") == std::string::npos)
357             << wkt;
358     }
359 
360     {
361         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2015, nullptr);
362         ASSERT_NE(wkt, nullptr);
363         EXPECT_TRUE(std::string(wkt).find("GEODCRS[") == 0) << wkt;
364     }
365 
366     {
367         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2015_SIMPLIFIED, nullptr);
368         ASSERT_NE(wkt, nullptr);
369         EXPECT_TRUE(std::string(wkt).find("GEODCRS[") == 0) << wkt;
370         EXPECT_TRUE(std::string(wkt).find("ANGULARUNIT[") == std::string::npos)
371             << wkt;
372     }
373 
374     {
375         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, nullptr);
376         ASSERT_NE(wkt, nullptr);
377         EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"WGS 84\"") == 0) << wkt;
378     }
379 
380     {
381         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_ESRI, nullptr);
382         ASSERT_NE(wkt, nullptr);
383         EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"GCS_WGS_1984\"") == 0)
384             << wkt;
385     }
386 
387     // MULTILINE=NO
388     {
389         const char *const options[] = {"MULTILINE=NO", nullptr};
390         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options);
391         ASSERT_NE(wkt, nullptr);
392         EXPECT_TRUE(std::string(wkt).find("\n") == std::string::npos) << wkt;
393     }
394 
395     // INDENTATION_WIDTH=2
396     {
397         const char *const options[] = {"INDENTATION_WIDTH=2", nullptr};
398         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options);
399         ASSERT_NE(wkt, nullptr);
400         EXPECT_TRUE(std::string(wkt).find("\n  DATUM") != std::string::npos)
401             << wkt;
402     }
403 
404     // OUTPUT_AXIS=NO
405     {
406         const char *const options[] = {"OUTPUT_AXIS=NO", nullptr};
407         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options);
408         ASSERT_NE(wkt, nullptr);
409         EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt;
410     }
411 
412     // OUTPUT_AXIS=AUTO
413     {
414         const char *const options[] = {"OUTPUT_AXIS=AUTO", nullptr};
415         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options);
416         ASSERT_NE(wkt, nullptr);
417         EXPECT_TRUE(std::string(wkt).find("AXIS") == std::string::npos) << wkt;
418     }
419 
420     // OUTPUT_AXIS=YES
421     {
422         const char *const options[] = {"OUTPUT_AXIS=YES", nullptr};
423         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, options);
424         ASSERT_NE(wkt, nullptr);
425         EXPECT_TRUE(std::string(wkt).find("AXIS") != std::string::npos) << wkt;
426     }
427 
428     auto crs4979 = proj_create_from_wkt(
429         m_ctxt,
430         GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get())
431             .c_str(),
432         nullptr, nullptr, nullptr);
433     ObjectKeeper keeper_crs4979(crs4979);
434     ASSERT_NE(crs4979, nullptr);
435 
436     EXPECT_EQ(proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, nullptr), nullptr);
437 
438     // STRICT=NO
439     {
440         const char *const options[] = {"STRICT=NO", nullptr};
441         auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, options);
442         ASSERT_NE(wkt, nullptr);
443         EXPECT_TRUE(std::string(wkt).find("GEOGCS[\"WGS 84\"") == 0) << wkt;
444     }
445 
446     // ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES
447     {
448         const char *const options[] = {
449             "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS=YES", nullptr};
450         auto wkt = proj_as_wkt(m_ctxt, crs4979, PJ_WKT1_GDAL, options);
451         ASSERT_NE(wkt, nullptr);
452         EXPECT_TRUE(std::string(wkt).find(
453                         "COMPD_CS[\"WGS 84 + Ellipsoid (metre)\"") == 0)
454             << wkt;
455     }
456 
457     // unsupported option
458     {
459         const char *const options[] = {"unsupported=yes", nullptr};
460         auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT2_2019, options);
461         EXPECT_EQ(wkt, nullptr);
462     }
463 }
464 
465 // ---------------------------------------------------------------------------
466 
TEST_F(CApi,proj_as_wkt_check_db_use)467 TEST_F(CApi, proj_as_wkt_check_db_use) {
468     auto obj = proj_create_from_wkt(
469         m_ctxt, "GEOGCS[\"AGD66\",DATUM[\"Australian_Geodetic_Datum_1966\","
470                 "SPHEROID[\"Australian National Spheroid\",6378160,298.25]],"
471                 "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]]",
472         nullptr, nullptr, nullptr);
473     ObjectKeeper keeper(obj);
474     ASSERT_NE(obj, nullptr);
475 
476     auto wkt = proj_as_wkt(m_ctxt, obj, PJ_WKT1_ESRI, nullptr);
477     EXPECT_EQ(std::string(wkt),
478               "GEOGCS[\"GCS_Australian_1966\",DATUM[\"D_Australian_1966\","
479               "SPHEROID[\"Australian\",6378160.0,298.25]],"
480               "PRIMEM[\"Greenwich\",0.0],"
481               "UNIT[\"Degree\",0.0174532925199433]]");
482 }
483 
484 // ---------------------------------------------------------------------------
485 
TEST_F(CApi,proj_as_wkt_incompatible_WKT1)486 TEST_F(CApi, proj_as_wkt_incompatible_WKT1) {
487     auto wkt = createBoundCRS()->exportToWKT(WKTFormatter::create().get());
488     auto obj =
489         proj_create_from_wkt(m_ctxt, wkt.c_str(), nullptr, nullptr, nullptr);
490     ObjectKeeper keeper(obj);
491     ASSERT_NE(obj, nullptr) << wkt;
492 
493     auto wkt1_GDAL = proj_as_wkt(m_ctxt, obj, PJ_WKT1_GDAL, nullptr);
494     ASSERT_EQ(wkt1_GDAL, nullptr);
495 }
496 
497 // ---------------------------------------------------------------------------
498 
TEST_F(CApi,proj_as_proj_string)499 TEST_F(CApi, proj_as_proj_string) {
500     auto obj = proj_create_from_wkt(
501         m_ctxt,
502         GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
503             .c_str(),
504         nullptr, nullptr, nullptr);
505     ObjectKeeper keeper(obj);
506     ASSERT_NE(obj, nullptr);
507 
508     {
509         auto proj_5 = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_5, nullptr);
510         ASSERT_NE(proj_5, nullptr);
511         EXPECT_EQ(std::string(proj_5),
512                   "+proj=longlat +datum=WGS84 +no_defs +type=crs");
513     }
514     {
515         auto proj_4 = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_4, nullptr);
516         ASSERT_NE(proj_4, nullptr);
517         EXPECT_EQ(std::string(proj_4),
518                   "+proj=longlat +datum=WGS84 +no_defs +type=crs");
519     }
520 }
521 
522 // ---------------------------------------------------------------------------
523 
TEST_F(CApi,proj_as_proj_string_incompatible_WKT1)524 TEST_F(CApi, proj_as_proj_string_incompatible_WKT1) {
525     auto obj = proj_create_from_wkt(
526         m_ctxt,
527         createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
528         nullptr, nullptr, nullptr);
529     ObjectKeeper keeper(obj);
530     ASSERT_NE(obj, nullptr);
531 
532     auto str = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_5, nullptr);
533     ASSERT_EQ(str, nullptr);
534 }
535 
536 // ---------------------------------------------------------------------------
537 
TEST_F(CApi,proj_as_proj_string_approx_tmerc_option_yes)538 TEST_F(CApi, proj_as_proj_string_approx_tmerc_option_yes) {
539     auto obj = proj_create(m_ctxt, "+proj=tmerc +type=crs");
540     ObjectKeeper keeper(obj);
541     ASSERT_NE(obj, nullptr);
542 
543     const char *options[] = {"USE_APPROX_TMERC=YES", nullptr};
544     auto str = proj_as_proj_string(m_ctxt, obj, PJ_PROJ_4, options);
545     ASSERT_NE(str, nullptr);
546     EXPECT_EQ(str,
547               std::string("+proj=tmerc +approx +lat_0=0 +lon_0=0 +k=1 +x_0=0 "
548                           "+y_0=0 +datum=WGS84 +units=m +no_defs +type=crs"));
549 }
550 
551 // ---------------------------------------------------------------------------
552 
TEST_F(CApi,proj_crs_create_bound_crs_to_WGS84)553 TEST_F(CApi, proj_crs_create_bound_crs_to_WGS84) {
554     auto crs = proj_create_from_database(m_ctxt, "EPSG", "4807",
555                                          PJ_CATEGORY_CRS, false, nullptr);
556     ObjectKeeper keeper(crs);
557     ASSERT_NE(crs, nullptr);
558 
559     auto res = proj_crs_create_bound_crs_to_WGS84(m_ctxt, crs, nullptr);
560     ObjectKeeper keeper_res(res);
561     ASSERT_NE(res, nullptr);
562 
563     auto proj_4 = proj_as_proj_string(m_ctxt, res, PJ_PROJ_4, nullptr);
564     ASSERT_NE(proj_4, nullptr);
565     EXPECT_EQ(std::string(proj_4),
566               "+proj=longlat +ellps=clrk80ign +pm=paris "
567               "+towgs84=-168,-60,320,0,0,0,0 +no_defs +type=crs");
568 
569     auto base_crs = proj_get_source_crs(m_ctxt, res);
570     ObjectKeeper keeper_base_crs(base_crs);
571     ASSERT_NE(base_crs, nullptr);
572 
573     auto hub_crs = proj_get_target_crs(m_ctxt, res);
574     ObjectKeeper keeper_hub_crs(hub_crs);
575     ASSERT_NE(hub_crs, nullptr);
576 
577     auto transf = proj_crs_get_coordoperation(m_ctxt, res);
578     ObjectKeeper keeper_transf(transf);
579     ASSERT_NE(transf, nullptr);
580 
581     std::vector<double> values(7, 0);
582     EXPECT_TRUE(proj_coordoperation_get_towgs84_values(m_ctxt, transf,
583                                                        values.data(), 7, true));
584     auto expected = std::vector<double>{-168, -60, 320, 0, 0, 0, 0};
585     EXPECT_EQ(values, expected);
586 
587     auto res2 = proj_crs_create_bound_crs(m_ctxt, base_crs, hub_crs, transf);
588     ObjectKeeper keeper_res2(res2);
589     ASSERT_NE(res2, nullptr);
590 
591     EXPECT_TRUE(proj_is_equivalent_to(res, res2, PJ_COMP_STRICT));
592 }
593 
594 // ---------------------------------------------------------------------------
595 
TEST_F(CApi,proj_crs_create_bound_crs_to_WGS84_on_invalid_type)596 TEST_F(CApi, proj_crs_create_bound_crs_to_WGS84_on_invalid_type) {
597     auto wkt = createProjectedCRS()->derivingConversion()->exportToWKT(
598         WKTFormatter::create().get());
599     auto obj =
600         proj_create_from_wkt(m_ctxt, wkt.c_str(), nullptr, nullptr, nullptr);
601     ObjectKeeper keeper(obj);
602     ASSERT_NE(obj, nullptr) << wkt;
603 
604     auto res = proj_crs_create_bound_crs_to_WGS84(m_ctxt, obj, nullptr);
605     ASSERT_EQ(res, nullptr);
606 }
607 
608 // ---------------------------------------------------------------------------
609 
TEST_F(CApi,proj_get_name)610 TEST_F(CApi, proj_get_name) {
611     auto obj = proj_create_from_wkt(
612         m_ctxt,
613         GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
614             .c_str(),
615         nullptr, nullptr, nullptr);
616     ObjectKeeper keeper(obj);
617     ASSERT_NE(obj, nullptr);
618     auto name = proj_get_name(obj);
619     ASSERT_TRUE(name != nullptr);
620     EXPECT_EQ(name, std::string("WGS 84"));
621     EXPECT_EQ(name, proj_get_name(obj));
622 }
623 
624 // ---------------------------------------------------------------------------
625 
TEST_F(CApi,proj_get_id_auth_name)626 TEST_F(CApi, proj_get_id_auth_name) {
627     auto obj = proj_create_from_wkt(
628         m_ctxt,
629         GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
630             .c_str(),
631         nullptr, nullptr, nullptr);
632     ObjectKeeper keeper(obj);
633     ASSERT_NE(obj, nullptr);
634     auto auth = proj_get_id_auth_name(obj, 0);
635     ASSERT_TRUE(auth != nullptr);
636     EXPECT_EQ(auth, std::string("EPSG"));
637     EXPECT_EQ(auth, proj_get_id_auth_name(obj, 0));
638     EXPECT_EQ(proj_get_id_auth_name(obj, -1), nullptr);
639     EXPECT_EQ(proj_get_id_auth_name(obj, 1), nullptr);
640 }
641 
642 // ---------------------------------------------------------------------------
643 
TEST_F(CApi,proj_get_id_code)644 TEST_F(CApi, proj_get_id_code) {
645     auto obj = proj_create_from_wkt(
646         m_ctxt,
647         GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
648             .c_str(),
649         nullptr, nullptr, nullptr);
650     ObjectKeeper keeper(obj);
651     ASSERT_NE(obj, nullptr);
652     auto code = proj_get_id_code(obj, 0);
653     ASSERT_TRUE(code != nullptr);
654     EXPECT_EQ(code, std::string("4326"));
655     EXPECT_EQ(code, proj_get_id_code(obj, 0));
656     EXPECT_EQ(proj_get_id_code(obj, -1), nullptr);
657     EXPECT_EQ(proj_get_id_code(obj, 1), nullptr);
658 }
659 
660 // ---------------------------------------------------------------------------
661 
TEST_F(CApi,proj_get_type)662 TEST_F(CApi, proj_get_type) {
663     {
664         auto obj = proj_create_from_wkt(
665             m_ctxt,
666             GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
667                 .c_str(),
668             nullptr, nullptr, nullptr);
669         ObjectKeeper keeper(obj);
670         ASSERT_NE(obj, nullptr);
671         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEOGRAPHIC_2D_CRS);
672     }
673     {
674         auto obj = proj_create_from_wkt(
675             m_ctxt,
676             GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get())
677                 .c_str(),
678             nullptr, nullptr, nullptr);
679         ObjectKeeper keeper(obj);
680         ASSERT_NE(obj, nullptr);
681         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEOGRAPHIC_3D_CRS);
682     }
683     {
684         auto obj = proj_create_from_wkt(
685             m_ctxt,
686             GeographicCRS::EPSG_4978->exportToWKT(WKTFormatter::create().get())
687                 .c_str(),
688             nullptr, nullptr, nullptr);
689         ObjectKeeper keeper(obj);
690         ASSERT_NE(obj, nullptr);
691         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEOCENTRIC_CRS);
692     }
693     {
694         auto obj = proj_create_from_wkt(
695             m_ctxt, GeographicCRS::EPSG_4326->datum()
696                         ->exportToWKT(WKTFormatter::create().get())
697                         .c_str(),
698             nullptr, nullptr, nullptr);
699         ObjectKeeper keeper(obj);
700         ASSERT_NE(obj, nullptr);
701         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_GEODETIC_REFERENCE_FRAME);
702     }
703     {
704         auto obj = proj_create_from_wkt(
705             m_ctxt, GeographicCRS::EPSG_4326->ellipsoid()
706                         ->exportToWKT(WKTFormatter::create().get())
707                         .c_str(),
708             nullptr, nullptr, nullptr);
709         ObjectKeeper keeper(obj);
710         ASSERT_NE(obj, nullptr);
711         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_ELLIPSOID);
712     }
713     {
714         auto obj = proj_create_from_wkt(
715             m_ctxt, createProjectedCRS()
716                         ->exportToWKT(WKTFormatter::create().get())
717                         .c_str(),
718             nullptr, nullptr, nullptr);
719         ObjectKeeper keeper(obj);
720         ASSERT_NE(obj, nullptr);
721         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_PROJECTED_CRS);
722     }
723     {
724         auto obj = proj_create_from_wkt(
725             m_ctxt, createVerticalCRS()
726                         ->exportToWKT(WKTFormatter::create().get())
727                         .c_str(),
728             nullptr, nullptr, nullptr);
729         ObjectKeeper keeper(obj);
730         ASSERT_NE(obj, nullptr);
731         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_VERTICAL_CRS);
732     }
733     {
734         auto wkt = "TDATUM[\"Gregorian calendar\",\n"
735                    "     CALENDAR[\"proleptic Gregorian\"],\n"
736                    "     TIMEORIGIN[0000-01-01]]";
737 
738         auto datum =
739             proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr);
740         ObjectKeeper keeper(datum);
741         ASSERT_NE(datum, nullptr);
742         EXPECT_EQ(proj_get_type(datum), PJ_TYPE_TEMPORAL_DATUM);
743     }
744     {
745         auto wkt = "ENGINEERINGDATUM[\"Engineering datum\"]";
746         auto datum =
747             proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr);
748         ObjectKeeper keeper(datum);
749         EXPECT_EQ(proj_get_type(datum), PJ_TYPE_ENGINEERING_DATUM);
750     }
751     {
752         auto wkt = "PDATUM[\"Mean Sea Level\",ANCHOR[\"1013.25 hPa at 15°C\"]]";
753         auto datum =
754             proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr);
755         ObjectKeeper keeper(datum);
756         EXPECT_EQ(proj_get_type(datum), PJ_TYPE_PARAMETRIC_DATUM);
757     }
758     {
759         auto obj = proj_create_from_wkt(
760             m_ctxt, createVerticalCRS()
761                         ->datum()
762                         ->exportToWKT(WKTFormatter::create().get())
763                         .c_str(),
764             nullptr, nullptr, nullptr);
765         ObjectKeeper keeper(obj);
766         ASSERT_NE(obj, nullptr);
767         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_VERTICAL_REFERENCE_FRAME);
768     }
769     {
770         auto obj = proj_create_from_wkt(
771             m_ctxt, createProjectedCRS()
772                         ->derivingConversion()
773                         ->exportToWKT(WKTFormatter::create().get())
774                         .c_str(),
775             nullptr, nullptr, nullptr);
776         ObjectKeeper keeper(obj);
777         ASSERT_NE(obj, nullptr);
778         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_CONVERSION);
779     }
780     {
781         auto obj = proj_create_from_wkt(
782             m_ctxt,
783             createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
784             nullptr, nullptr, nullptr);
785         ObjectKeeper keeper(obj);
786         ASSERT_NE(obj, nullptr);
787         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_BOUND_CRS);
788     }
789     {
790         auto obj = proj_create_from_wkt(
791             m_ctxt, createBoundCRS()
792                         ->transformation()
793                         ->exportToWKT(WKTFormatter::create().get())
794                         .c_str(),
795             nullptr, nullptr, nullptr);
796         ObjectKeeper keeper(obj);
797         ASSERT_NE(obj, nullptr);
798         EXPECT_EQ(proj_get_type(obj), PJ_TYPE_TRANSFORMATION);
799     }
800     {
801         auto obj = proj_create_from_wkt(m_ctxt, "AUTHORITY[\"EPSG\", 4326]",
802                                         nullptr, nullptr, nullptr);
803         ObjectKeeper keeper(obj);
804         ASSERT_EQ(obj, nullptr);
805     }
806 }
807 
808 // ---------------------------------------------------------------------------
809 
TEST_F(CApi,proj_create_from_database)810 TEST_F(CApi, proj_create_from_database) {
811     {
812         auto crs = proj_create_from_database(m_ctxt, "EPSG", "-1",
813                                              PJ_CATEGORY_CRS, false, nullptr);
814         ASSERT_EQ(crs, nullptr);
815     }
816     {
817         auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
818                                              PJ_CATEGORY_CRS, false, nullptr);
819         ASSERT_NE(crs, nullptr);
820         ObjectKeeper keeper(crs);
821         EXPECT_TRUE(proj_is_crs(crs));
822         EXPECT_FALSE(proj_is_deprecated(crs));
823         EXPECT_EQ(proj_get_type(crs), PJ_TYPE_GEOGRAPHIC_2D_CRS);
824 
825         const char *code = proj_get_id_code(crs, 0);
826         ASSERT_NE(code, nullptr);
827         EXPECT_EQ(std::string(code), "4326");
828     }
829     {
830         auto crs = proj_create_from_database(m_ctxt, "EPSG", "6871",
831                                              PJ_CATEGORY_CRS, false, nullptr);
832         ASSERT_NE(crs, nullptr);
833         ObjectKeeper keeper(crs);
834         EXPECT_TRUE(proj_is_crs(crs));
835         EXPECT_EQ(proj_get_type(crs), PJ_TYPE_COMPOUND_CRS);
836     }
837     {
838         auto ellipsoid = proj_create_from_database(
839             m_ctxt, "EPSG", "7030", PJ_CATEGORY_ELLIPSOID, false, nullptr);
840         ASSERT_NE(ellipsoid, nullptr);
841         ObjectKeeper keeper(ellipsoid);
842         EXPECT_EQ(proj_get_type(ellipsoid), PJ_TYPE_ELLIPSOID);
843     }
844     {
845         auto pm = proj_create_from_database(
846             m_ctxt, "EPSG", "8903", PJ_CATEGORY_PRIME_MERIDIAN, false, nullptr);
847         ASSERT_NE(pm, nullptr);
848         ObjectKeeper keeper(pm);
849         EXPECT_EQ(proj_get_type(pm), PJ_TYPE_PRIME_MERIDIAN);
850     }
851     {
852         auto datum = proj_create_from_database(
853             m_ctxt, "EPSG", "6326", PJ_CATEGORY_DATUM, false, nullptr);
854         ASSERT_NE(datum, nullptr);
855         ObjectKeeper keeper(datum);
856         EXPECT_EQ(proj_get_type(datum), PJ_TYPE_GEODETIC_REFERENCE_FRAME);
857     }
858     {
859         // International Terrestrial Reference Frame 2008
860         auto datum = proj_create_from_database(
861             m_ctxt, "EPSG", "1061", PJ_CATEGORY_DATUM, false, nullptr);
862         ASSERT_NE(datum, nullptr);
863         ObjectKeeper keeper(datum);
864         EXPECT_EQ(proj_get_type(datum),
865                   PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME);
866         EXPECT_EQ(proj_dynamic_datum_get_frame_reference_epoch(m_ctxt, datum),
867                   2005.0);
868     }
869     {
870         // Norway Normal Null 2000
871         auto datum = proj_create_from_database(
872             m_ctxt, "EPSG", "1096", PJ_CATEGORY_DATUM, false, nullptr);
873         ASSERT_NE(datum, nullptr);
874         ObjectKeeper keeper(datum);
875         EXPECT_EQ(proj_get_type(datum),
876                   PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME);
877         EXPECT_EQ(proj_dynamic_datum_get_frame_reference_epoch(m_ctxt, datum),
878                   2000.0);
879     }
880     {
881         auto op = proj_create_from_database(m_ctxt, "EPSG", "16031",
882                                             PJ_CATEGORY_COORDINATE_OPERATION,
883                                             false, nullptr);
884         ASSERT_NE(op, nullptr);
885         ObjectKeeper keeper(op);
886         EXPECT_EQ(proj_get_type(op), PJ_TYPE_CONVERSION);
887 
888         auto info = proj_pj_info(op);
889         EXPECT_NE(info.id, nullptr);
890         EXPECT_EQ(info.id, std::string("utm"));
891         ASSERT_NE(info.description, nullptr);
892         EXPECT_EQ(info.description, std::string("UTM zone 31N"));
893         ASSERT_NE(info.definition, nullptr);
894         EXPECT_EQ(info.definition, std::string("proj=utm zone=31 ellps=GRS80"));
895         EXPECT_EQ(info.accuracy, 0);
896     }
897     {
898         auto op = proj_create_from_database(m_ctxt, "EPSG", "1024",
899                                             PJ_CATEGORY_COORDINATE_OPERATION,
900                                             false, nullptr);
901         ASSERT_NE(op, nullptr);
902         ObjectKeeper keeper(op);
903         EXPECT_EQ(proj_get_type(op), PJ_TYPE_TRANSFORMATION);
904 
905         auto info = proj_pj_info(op);
906         EXPECT_NE(info.id, nullptr);
907         EXPECT_EQ(info.id, std::string("pipeline"));
908         ASSERT_NE(info.description, nullptr);
909         EXPECT_EQ(info.description, std::string("MGI to ETRS89 (4)"));
910         ASSERT_NE(info.definition, nullptr);
911         EXPECT_EQ(
912             info.definition,
913             std::string("proj=pipeline step proj=axisswap "
914                         "order=2,1 step proj=unitconvert xy_in=deg xy_out=rad "
915                         "step proj=push v_3 "
916                         "step proj=cart ellps=bessel step proj=helmert "
917                         "x=601.705 y=84.263 z=485.227 rx=-4.7354 ry=-1.3145 "
918                         "rz=-5.393 s=-2.3887 convention=coordinate_frame "
919                         "step inv proj=cart ellps=GRS80 "
920                         "step proj=pop v_3 "
921                         "step proj=unitconvert xy_in=rad xy_out=deg "
922                         "step proj=axisswap order=2,1"));
923         EXPECT_EQ(info.accuracy, 1);
924     }
925 }
926 
927 // ---------------------------------------------------------------------------
928 
TEST_F(CApi,proj_crs)929 TEST_F(CApi, proj_crs) {
930     auto crs = proj_create_from_wkt(
931         m_ctxt,
932         createProjectedCRS()
933             ->exportToWKT(
934                 WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get())
935             .c_str(),
936         nullptr, nullptr, nullptr);
937     ASSERT_NE(crs, nullptr);
938     ObjectKeeper keeper(crs);
939     EXPECT_TRUE(proj_is_crs(crs));
940 
941     auto geodCRS = proj_crs_get_geodetic_crs(m_ctxt, crs);
942     ASSERT_NE(geodCRS, nullptr);
943     ObjectKeeper keeper_geogCRS(geodCRS);
944     EXPECT_TRUE(proj_is_crs(geodCRS));
945     auto geogCRS_name = proj_get_name(geodCRS);
946     ASSERT_TRUE(geogCRS_name != nullptr);
947     EXPECT_EQ(geogCRS_name, std::string("WGS 84"));
948 
949     auto h_datum = proj_crs_get_horizontal_datum(m_ctxt, crs);
950     ASSERT_NE(h_datum, nullptr);
951     ObjectKeeper keeper_h_datum(h_datum);
952 
953     auto datum = proj_crs_get_datum(m_ctxt, crs);
954     ASSERT_NE(datum, nullptr);
955     ObjectKeeper keeper_datum(datum);
956 
957     EXPECT_TRUE(proj_is_equivalent_to(h_datum, datum, PJ_COMP_STRICT));
958 
959     auto datum_name = proj_get_name(datum);
960     ASSERT_TRUE(datum_name != nullptr);
961     EXPECT_EQ(datum_name, std::string("World Geodetic System 1984"));
962 
963     auto ellipsoid = proj_get_ellipsoid(m_ctxt, crs);
964     ASSERT_NE(ellipsoid, nullptr);
965     ObjectKeeper keeper_ellipsoid(ellipsoid);
966     auto ellipsoid_name = proj_get_name(ellipsoid);
967     ASSERT_TRUE(ellipsoid_name != nullptr);
968     EXPECT_EQ(ellipsoid_name, std::string("WGS 84"));
969 
970     auto ellipsoid_from_datum = proj_get_ellipsoid(m_ctxt, datum);
971     ASSERT_NE(ellipsoid_from_datum, nullptr);
972     ObjectKeeper keeper_ellipsoid_from_datum(ellipsoid_from_datum);
973 
974     EXPECT_EQ(proj_get_ellipsoid(m_ctxt, ellipsoid), nullptr);
975     EXPECT_FALSE(proj_is_crs(ellipsoid));
976 
977     double a;
978     double b;
979     int b_is_computed;
980     double rf;
981     EXPECT_TRUE(proj_ellipsoid_get_parameters(m_ctxt, ellipsoid, nullptr,
982                                               nullptr, nullptr, nullptr));
983     EXPECT_TRUE(proj_ellipsoid_get_parameters(m_ctxt, ellipsoid, &a, &b,
984                                               &b_is_computed, &rf));
985     EXPECT_FALSE(proj_ellipsoid_get_parameters(m_ctxt, crs, &a, &b,
986                                                &b_is_computed, &rf));
987     EXPECT_EQ(a, 6378137);
988     EXPECT_NEAR(b, 6356752.31424518, 1e-9);
989     EXPECT_EQ(b_is_computed, 1);
990     EXPECT_EQ(rf, 298.257223563);
991     auto id = proj_get_id_code(ellipsoid, 0);
992     ASSERT_TRUE(id != nullptr);
993     EXPECT_EQ(id, std::string("7030"));
994 }
995 
996 // ---------------------------------------------------------------------------
997 
TEST_F(CApi,proj_get_prime_meridian)998 TEST_F(CApi, proj_get_prime_meridian) {
999     auto crs = proj_create_from_wkt(
1000         m_ctxt,
1001         createProjectedCRS()
1002             ->exportToWKT(
1003                 WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get())
1004             .c_str(),
1005         nullptr, nullptr, nullptr);
1006     ASSERT_NE(crs, nullptr);
1007     ObjectKeeper keeper(crs);
1008 
1009     auto pm = proj_get_prime_meridian(m_ctxt, crs);
1010     ASSERT_NE(pm, nullptr);
1011     ObjectKeeper keeper_pm(pm);
1012     auto pm_name = proj_get_name(pm);
1013     ASSERT_TRUE(pm_name != nullptr);
1014     EXPECT_EQ(pm_name, std::string("Greenwich"));
1015 
1016     EXPECT_EQ(proj_get_prime_meridian(m_ctxt, pm), nullptr);
1017 
1018     EXPECT_TRUE(proj_prime_meridian_get_parameters(m_ctxt, pm, nullptr, nullptr,
1019                                                    nullptr));
1020     double longitude = -1;
1021     double longitude_unit = 0;
1022     const char *longitude_unit_name = nullptr;
1023     EXPECT_TRUE(proj_prime_meridian_get_parameters(
1024         m_ctxt, pm, &longitude, &longitude_unit, &longitude_unit_name));
1025     EXPECT_EQ(longitude, 0);
1026     EXPECT_NEAR(longitude_unit, UnitOfMeasure::DEGREE.conversionToSI(), 1e-10);
1027     ASSERT_TRUE(longitude_unit_name != nullptr);
1028     EXPECT_EQ(longitude_unit_name, std::string("degree"));
1029 
1030     auto datum = proj_crs_get_horizontal_datum(m_ctxt, crs);
1031     ASSERT_NE(datum, nullptr);
1032     ObjectKeeper keeper_datum(datum);
1033     auto pm_from_datum = proj_get_prime_meridian(m_ctxt, datum);
1034     ASSERT_NE(pm_from_datum, nullptr);
1035     ObjectKeeper keeper_pm_from_datum(pm_from_datum);
1036 }
1037 
1038 // ---------------------------------------------------------------------------
1039 
TEST_F(CApi,proj_crs_compound)1040 TEST_F(CApi, proj_crs_compound) {
1041     auto crs = proj_create_from_wkt(
1042         m_ctxt,
1043         createCompoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
1044         nullptr, nullptr, nullptr);
1045     ASSERT_NE(crs, nullptr);
1046     ObjectKeeper keeper(crs);
1047     EXPECT_EQ(proj_get_type(crs), PJ_TYPE_COMPOUND_CRS);
1048 
1049     EXPECT_EQ(proj_crs_get_sub_crs(m_ctxt, crs, -1), nullptr);
1050     EXPECT_EQ(proj_crs_get_sub_crs(m_ctxt, crs, 2), nullptr);
1051 
1052     auto subcrs_horiz = proj_crs_get_sub_crs(m_ctxt, crs, 0);
1053     ASSERT_NE(subcrs_horiz, nullptr);
1054     ObjectKeeper keeper_subcrs_horiz(subcrs_horiz);
1055     EXPECT_EQ(proj_get_type(subcrs_horiz), PJ_TYPE_PROJECTED_CRS);
1056     EXPECT_EQ(proj_crs_get_sub_crs(m_ctxt, subcrs_horiz, 0), nullptr);
1057 
1058     auto subcrs_vertical = proj_crs_get_sub_crs(m_ctxt, crs, 1);
1059     ASSERT_NE(subcrs_vertical, nullptr);
1060     ObjectKeeper keeper_subcrs_vertical(subcrs_vertical);
1061     EXPECT_EQ(proj_get_type(subcrs_vertical), PJ_TYPE_VERTICAL_CRS);
1062 }
1063 
1064 // ---------------------------------------------------------------------------
1065 
TEST_F(CApi,proj_get_source_target_crs_bound_crs)1066 TEST_F(CApi, proj_get_source_target_crs_bound_crs) {
1067     auto crs = proj_create_from_wkt(
1068         m_ctxt,
1069         createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
1070         nullptr, nullptr, nullptr);
1071     ASSERT_NE(crs, nullptr);
1072     ObjectKeeper keeper(crs);
1073 
1074     auto sourceCRS = proj_get_source_crs(m_ctxt, crs);
1075     ASSERT_NE(sourceCRS, nullptr);
1076     ObjectKeeper keeper_sourceCRS(sourceCRS);
1077     EXPECT_EQ(std::string(proj_get_name(sourceCRS)), "NTF (Paris)");
1078 
1079     auto targetCRS = proj_get_target_crs(m_ctxt, crs);
1080     ASSERT_NE(targetCRS, nullptr);
1081     ObjectKeeper keeper_targetCRS(targetCRS);
1082     EXPECT_EQ(std::string(proj_get_name(targetCRS)), "WGS 84");
1083 }
1084 
1085 // ---------------------------------------------------------------------------
1086 
TEST_F(CApi,proj_get_source_target_crs_transformation)1087 TEST_F(CApi, proj_get_source_target_crs_transformation) {
1088     auto obj = proj_create_from_wkt(
1089         m_ctxt, createBoundCRS()
1090                     ->transformation()
1091                     ->exportToWKT(WKTFormatter::create().get())
1092                     .c_str(),
1093         nullptr, nullptr, nullptr);
1094     ASSERT_NE(obj, nullptr);
1095     ObjectKeeper keeper(obj);
1096 
1097     auto sourceCRS = proj_get_source_crs(m_ctxt, obj);
1098     ASSERT_NE(sourceCRS, nullptr);
1099     ObjectKeeper keeper_sourceCRS(sourceCRS);
1100     EXPECT_EQ(std::string(proj_get_name(sourceCRS)), "NTF (Paris)");
1101 
1102     auto targetCRS = proj_get_target_crs(m_ctxt, obj);
1103     ASSERT_NE(targetCRS, nullptr);
1104     ObjectKeeper keeper_targetCRS(targetCRS);
1105     EXPECT_EQ(std::string(proj_get_name(targetCRS)), "WGS 84");
1106 }
1107 
1108 // ---------------------------------------------------------------------------
1109 
TEST_F(CApi,proj_get_source_crs_of_projected_crs)1110 TEST_F(CApi, proj_get_source_crs_of_projected_crs) {
1111     auto crs = proj_create_from_wkt(
1112         m_ctxt,
1113         createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
1114         nullptr, nullptr, nullptr);
1115     ASSERT_NE(crs, nullptr);
1116     ObjectKeeper keeper(crs);
1117 
1118     auto sourceCRS = proj_get_source_crs(m_ctxt, crs);
1119     ASSERT_NE(sourceCRS, nullptr);
1120     ObjectKeeper keeper_sourceCRS(sourceCRS);
1121     EXPECT_EQ(std::string(proj_get_name(sourceCRS)), "WGS 84");
1122 }
1123 
1124 // ---------------------------------------------------------------------------
1125 
TEST_F(CApi,proj_get_source_target_crs_conversion_without_crs)1126 TEST_F(CApi, proj_get_source_target_crs_conversion_without_crs) {
1127     auto obj = proj_create_from_database(m_ctxt, "EPSG", "16031",
1128                                          PJ_CATEGORY_COORDINATE_OPERATION,
1129                                          false, nullptr);
1130     ASSERT_NE(obj, nullptr);
1131     ObjectKeeper keeper(obj);
1132 
1133     auto sourceCRS = proj_get_source_crs(m_ctxt, obj);
1134     ASSERT_EQ(sourceCRS, nullptr);
1135 
1136     auto targetCRS = proj_get_target_crs(m_ctxt, obj);
1137     ASSERT_EQ(targetCRS, nullptr);
1138 }
1139 
1140 // ---------------------------------------------------------------------------
1141 
TEST_F(CApi,proj_get_source_target_crs_invalid_object)1142 TEST_F(CApi, proj_get_source_target_crs_invalid_object) {
1143     auto obj = proj_create_from_wkt(
1144         m_ctxt, "ELLIPSOID[\"WGS 84\",6378137,298.257223563]", nullptr, nullptr,
1145         nullptr);
1146     ASSERT_NE(obj, nullptr);
1147     ObjectKeeper keeper(obj);
1148 
1149     auto sourceCRS = proj_get_source_crs(m_ctxt, obj);
1150     ASSERT_EQ(sourceCRS, nullptr);
1151 
1152     auto targetCRS = proj_get_target_crs(m_ctxt, obj);
1153     ASSERT_EQ(targetCRS, nullptr);
1154 }
1155 
1156 // ---------------------------------------------------------------------------
1157 
1158 struct ListFreer {
1159     PROJ_STRING_LIST list;
ListFreer__anon29fdcccb0111::ListFreer1160     ListFreer(PROJ_STRING_LIST ptrIn) : list(ptrIn) {}
~ListFreer__anon29fdcccb0111::ListFreer1161     ~ListFreer() { proj_string_list_destroy(list); }
1162     ListFreer(const ListFreer &) = delete;
1163     ListFreer &operator=(const ListFreer &) = delete;
1164 };
1165 
1166 // ---------------------------------------------------------------------------
1167 
TEST_F(CApi,proj_get_authorities_from_database)1168 TEST_F(CApi, proj_get_authorities_from_database) {
1169     auto list = proj_get_authorities_from_database(m_ctxt);
1170     ListFreer feer(list);
1171     ASSERT_NE(list, nullptr);
1172     ASSERT_TRUE(list[0] != nullptr);
1173     EXPECT_EQ(list[0], std::string("EPSG"));
1174     ASSERT_TRUE(list[1] != nullptr);
1175     EXPECT_EQ(list[1], std::string("ESRI"));
1176     ASSERT_TRUE(list[2] != nullptr);
1177     EXPECT_EQ(list[2], std::string("IGNF"));
1178     ASSERT_TRUE(list[3] != nullptr);
1179     EXPECT_EQ(list[3], std::string("NKG"));
1180     ASSERT_TRUE(list[4] != nullptr);
1181     EXPECT_EQ(list[4], std::string("OGC"));
1182     ASSERT_TRUE(list[5] != nullptr);
1183     EXPECT_EQ(list[5], std::string("PROJ"));
1184     EXPECT_EQ(list[6], nullptr);
1185 }
1186 
1187 // ---------------------------------------------------------------------------
1188 
TEST_F(CApi,proj_get_codes_from_database)1189 TEST_F(CApi, proj_get_codes_from_database) {
1190 
1191     auto listTypes =
1192         std::vector<PJ_TYPE>{PJ_TYPE_ELLIPSOID,
1193 
1194                              PJ_TYPE_PRIME_MERIDIAN,
1195 
1196                              PJ_TYPE_GEODETIC_REFERENCE_FRAME,
1197                              PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME,
1198                              PJ_TYPE_VERTICAL_REFERENCE_FRAME,
1199                              PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME,
1200                              PJ_TYPE_DATUM_ENSEMBLE,
1201                              PJ_TYPE_TEMPORAL_DATUM,
1202                              PJ_TYPE_ENGINEERING_DATUM,
1203                              PJ_TYPE_PARAMETRIC_DATUM,
1204 
1205                              PJ_TYPE_CRS,
1206                              PJ_TYPE_GEODETIC_CRS,
1207                              PJ_TYPE_GEOCENTRIC_CRS,
1208                              PJ_TYPE_GEOGRAPHIC_CRS,
1209                              PJ_TYPE_GEOGRAPHIC_2D_CRS,
1210                              PJ_TYPE_GEOGRAPHIC_3D_CRS,
1211                              PJ_TYPE_VERTICAL_CRS,
1212                              PJ_TYPE_PROJECTED_CRS,
1213                              PJ_TYPE_COMPOUND_CRS,
1214                              PJ_TYPE_TEMPORAL_CRS,
1215                              PJ_TYPE_BOUND_CRS,
1216                              PJ_TYPE_OTHER_CRS,
1217 
1218                              PJ_TYPE_CONVERSION,
1219                              PJ_TYPE_TRANSFORMATION,
1220                              PJ_TYPE_CONCATENATED_OPERATION,
1221                              PJ_TYPE_OTHER_COORDINATE_OPERATION,
1222 
1223                              PJ_TYPE_UNKNOWN};
1224     for (const auto &type : listTypes) {
1225         auto list = proj_get_codes_from_database(m_ctxt, "EPSG", type, true);
1226         ListFreer feer(list);
1227         if (type == PJ_TYPE_TEMPORAL_CRS || type == PJ_TYPE_BOUND_CRS ||
1228             type == PJ_TYPE_UNKNOWN || type == PJ_TYPE_TEMPORAL_DATUM ||
1229             type == PJ_TYPE_ENGINEERING_DATUM ||
1230             type == PJ_TYPE_PARAMETRIC_DATUM) {
1231             EXPECT_EQ(list, nullptr) << type;
1232         } else {
1233             ASSERT_NE(list, nullptr) << type;
1234             ASSERT_NE(list[0], nullptr) << type;
1235             if (type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
1236                 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME) {
1237                 auto obj = proj_create_from_database(
1238                     m_ctxt, "EPSG", list[0], PJ_CATEGORY_DATUM, false, nullptr);
1239                 ASSERT_NE(obj, nullptr);
1240                 ObjectKeeper keeper(obj);
1241                 EXPECT_EQ(proj_get_type(obj), type) << type << " " << list[0];
1242             }
1243         }
1244     }
1245 }
1246 
1247 // ---------------------------------------------------------------------------
1248 
TEST_F(CApi,conversion)1249 TEST_F(CApi, conversion) {
1250     auto crs = proj_create_from_database(m_ctxt, "EPSG", "32631",
1251                                          PJ_CATEGORY_CRS, false, nullptr);
1252     ASSERT_NE(crs, nullptr);
1253     ObjectKeeper keeper(crs);
1254 
1255     // invalid object type
1256     EXPECT_FALSE(proj_coordoperation_get_method_info(m_ctxt, crs, nullptr,
1257                                                      nullptr, nullptr));
1258 
1259     {
1260         auto conv = proj_crs_get_coordoperation(m_ctxt, crs);
1261         ASSERT_NE(conv, nullptr);
1262         ObjectKeeper keeper_conv(conv);
1263 
1264         ASSERT_EQ(proj_crs_get_coordoperation(m_ctxt, conv), nullptr);
1265     }
1266 
1267     auto conv = proj_crs_get_coordoperation(m_ctxt, crs);
1268     ASSERT_NE(conv, nullptr);
1269     ObjectKeeper keeper_conv(conv);
1270 
1271     EXPECT_TRUE(proj_coordoperation_get_method_info(m_ctxt, conv, nullptr,
1272                                                     nullptr, nullptr));
1273 
1274     const char *methodName = nullptr;
1275     const char *methodAuthorityName = nullptr;
1276     const char *methodCode = nullptr;
1277     EXPECT_TRUE(proj_coordoperation_get_method_info(
1278         m_ctxt, conv, &methodName, &methodAuthorityName, &methodCode));
1279 
1280     ASSERT_NE(methodName, nullptr);
1281     ASSERT_NE(methodAuthorityName, nullptr);
1282     ASSERT_NE(methodCode, nullptr);
1283     EXPECT_EQ(methodName, std::string("Transverse Mercator"));
1284     EXPECT_EQ(methodAuthorityName, std::string("EPSG"));
1285     EXPECT_EQ(methodCode, std::string("9807"));
1286 
1287     EXPECT_EQ(proj_coordoperation_get_param_count(m_ctxt, conv), 5);
1288     EXPECT_EQ(proj_coordoperation_get_param_index(m_ctxt, conv, "foo"), -1);
1289     EXPECT_EQ(
1290         proj_coordoperation_get_param_index(m_ctxt, conv, "False easting"), 3);
1291 
1292     EXPECT_FALSE(proj_coordoperation_get_param(
1293         m_ctxt, conv, -1, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
1294         nullptr, nullptr, nullptr, nullptr));
1295     EXPECT_FALSE(proj_coordoperation_get_param(
1296         m_ctxt, conv, 5, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
1297         nullptr, nullptr, nullptr, nullptr));
1298 
1299     const char *name = nullptr;
1300     const char *nameAuthorityName = nullptr;
1301     const char *nameCode = nullptr;
1302     double value = 0;
1303     const char *valueString = nullptr;
1304     double valueUnitConvFactor = 0;
1305     const char *valueUnitName = nullptr;
1306     const char *unitAuthName = nullptr;
1307     const char *unitCode = nullptr;
1308     const char *unitCategory = nullptr;
1309     EXPECT_TRUE(proj_coordoperation_get_param(
1310         m_ctxt, conv, 3, &name, &nameAuthorityName, &nameCode, &value,
1311         &valueString, &valueUnitConvFactor, &valueUnitName, &unitAuthName,
1312         &unitCode, &unitCategory));
1313     ASSERT_NE(name, nullptr);
1314     ASSERT_NE(nameAuthorityName, nullptr);
1315     ASSERT_NE(nameCode, nullptr);
1316     EXPECT_EQ(valueString, nullptr);
1317     ASSERT_NE(valueUnitName, nullptr);
1318     ASSERT_NE(unitAuthName, nullptr);
1319     ASSERT_NE(unitCategory, nullptr);
1320     ASSERT_NE(unitCategory, nullptr);
1321     EXPECT_EQ(name, std::string("False easting"));
1322     EXPECT_EQ(nameAuthorityName, std::string("EPSG"));
1323     EXPECT_EQ(nameCode, std::string("8806"));
1324     EXPECT_EQ(value, 500000.0);
1325     EXPECT_EQ(valueUnitConvFactor, 1.0);
1326     EXPECT_EQ(valueUnitName, std::string("metre"));
1327     EXPECT_EQ(unitAuthName, std::string("EPSG"));
1328     EXPECT_EQ(unitCode, std::string("9001"));
1329     EXPECT_EQ(unitCategory, std::string("linear"));
1330 }
1331 
1332 // ---------------------------------------------------------------------------
1333 
TEST_F(CApi,transformation_from_boundCRS)1334 TEST_F(CApi, transformation_from_boundCRS) {
1335     auto crs = proj_create_from_wkt(
1336         m_ctxt,
1337         createBoundCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
1338         nullptr, nullptr, nullptr);
1339     ASSERT_NE(crs, nullptr);
1340     ObjectKeeper keeper(crs);
1341 
1342     auto transf = proj_crs_get_coordoperation(m_ctxt, crs);
1343     ASSERT_NE(transf, nullptr);
1344     ObjectKeeper keeper_transf(transf);
1345 }
1346 
1347 // ---------------------------------------------------------------------------
1348 
TEST_F(CApi,proj_coordoperation_get_grid_used)1349 TEST_F(CApi, proj_coordoperation_get_grid_used) {
1350     auto op = proj_create_from_database(m_ctxt, "EPSG", "1312",
1351                                         PJ_CATEGORY_COORDINATE_OPERATION, true,
1352                                         nullptr);
1353     ASSERT_NE(op, nullptr);
1354     ObjectKeeper keeper(op);
1355 
1356     EXPECT_EQ(proj_coordoperation_get_grid_used_count(m_ctxt, op), 1);
1357     const char *shortName = nullptr;
1358     const char *fullName = nullptr;
1359     const char *packageName = nullptr;
1360     const char *url = nullptr;
1361     int directDownload = 0;
1362     int openLicense = 0;
1363     int available = 0;
1364     EXPECT_EQ(proj_coordoperation_get_grid_used(m_ctxt, op, -1, nullptr,
1365                                                 nullptr, nullptr, nullptr,
1366                                                 nullptr, nullptr, nullptr),
1367               0);
1368     EXPECT_EQ(proj_coordoperation_get_grid_used(m_ctxt, op, 1, nullptr, nullptr,
1369                                                 nullptr, nullptr, nullptr,
1370                                                 nullptr, nullptr),
1371               0);
1372     EXPECT_EQ(proj_coordoperation_get_grid_used(
1373                   m_ctxt, op, 0, &shortName, &fullName, &packageName, &url,
1374                   &directDownload, &openLicense, &available),
1375               1);
1376     ASSERT_NE(shortName, nullptr);
1377     ASSERT_NE(fullName, nullptr);
1378     ASSERT_NE(packageName, nullptr);
1379     ASSERT_NE(url, nullptr);
1380     EXPECT_EQ(shortName, std::string("ca_nrc_ntv1_can.tif"));
1381     // EXPECT_EQ(fullName, std::string(""));
1382     EXPECT_EQ(packageName, std::string(""));
1383     EXPECT_EQ(std::string(url), "https://cdn.proj.org/ca_nrc_ntv1_can.tif");
1384     EXPECT_EQ(directDownload, 1);
1385     EXPECT_EQ(openLicense, 1);
1386 }
1387 
1388 // ---------------------------------------------------------------------------
1389 
TEST_F(CApi,proj_coordoperation_is_instantiable)1390 TEST_F(CApi, proj_coordoperation_is_instantiable) {
1391     auto op = proj_create_from_database(m_ctxt, "EPSG", "1671",
1392                                         PJ_CATEGORY_COORDINATE_OPERATION, true,
1393                                         nullptr);
1394     ASSERT_NE(op, nullptr);
1395     ObjectKeeper keeper(op);
1396     EXPECT_EQ(proj_coordoperation_is_instantiable(m_ctxt, op), 1);
1397 }
1398 
1399 // ---------------------------------------------------------------------------
1400 
TEST_F(CApi,proj_create_operations)1401 TEST_F(CApi, proj_create_operations) {
1402     auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1403     ASSERT_NE(ctxt, nullptr);
1404     ContextKeeper keeper_ctxt(ctxt);
1405 
1406     auto source_crs = proj_create_from_database(
1407         m_ctxt, "EPSG", "4267", PJ_CATEGORY_CRS, false, nullptr); // NAD27
1408     ASSERT_NE(source_crs, nullptr);
1409     ObjectKeeper keeper_source_crs(source_crs);
1410 
1411     auto target_crs = proj_create_from_database(
1412         m_ctxt, "EPSG", "4269", PJ_CATEGORY_CRS, false, nullptr); // NAD83
1413     ASSERT_NE(target_crs, nullptr);
1414     ObjectKeeper keeper_target_crs(target_crs);
1415 
1416     proj_operation_factory_context_set_spatial_criterion(
1417         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1418 
1419     proj_operation_factory_context_set_grid_availability_use(
1420         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1421 
1422     auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1423     ASSERT_NE(res, nullptr);
1424     ObjListKeeper keeper_res(res);
1425 
1426     EXPECT_EQ(proj_list_get_count(res), 10);
1427 
1428     EXPECT_EQ(proj_list_get(m_ctxt, res, -1), nullptr);
1429     EXPECT_EQ(proj_list_get(m_ctxt, res, proj_list_get_count(res)), nullptr);
1430     {
1431         auto op = proj_list_get(m_ctxt, res, 0);
1432         ASSERT_NE(op, nullptr);
1433         ObjectKeeper keeper_op(op);
1434         EXPECT_FALSE(
1435             proj_coordoperation_has_ballpark_transformation(m_ctxt, op));
1436         EXPECT_EQ(proj_get_name(op), std::string("NAD27 to NAD83 (3)"));
1437     }
1438 
1439     {
1440         PJ_COORD coord;
1441         coord.xy.x = 40;
1442         coord.xy.y = -100;
1443         int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord);
1444         ASSERT_GE(idx, 0);
1445         ASSERT_LT(idx, proj_list_get_count(res));
1446         auto op = proj_list_get(m_ctxt, res, idx);
1447         ASSERT_NE(op, nullptr);
1448         ObjectKeeper keeper_op(op);
1449         // Transformation for USA
1450         EXPECT_EQ(proj_get_name(op), std::string("NAD27 to NAD83 (1)"));
1451     }
1452 
1453     {
1454         PJ_COORD coord;
1455         coord.xy.x = 40;
1456         coord.xy.y = 10;
1457         int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord);
1458         EXPECT_GE(idx, -1);
1459     }
1460 }
1461 
1462 // ---------------------------------------------------------------------------
1463 
TEST_F(CApi,proj_get_suggested_operation_with_operations_without_area_of_use)1464 TEST_F(CApi, proj_get_suggested_operation_with_operations_without_area_of_use) {
1465     auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1466     ASSERT_NE(ctxt, nullptr);
1467     ContextKeeper keeper_ctxt(ctxt);
1468 
1469     // NAD83(2011) geocentric
1470     auto source_crs = proj_create_from_database(
1471         m_ctxt, "EPSG", "6317", PJ_CATEGORY_CRS, false, nullptr);
1472     ASSERT_NE(source_crs, nullptr);
1473     ObjectKeeper keeper_source_crs(source_crs);
1474 
1475     // NAD83(2011) 2D
1476     auto target_crs = proj_create_from_database(
1477         m_ctxt, "EPSG", "6318", PJ_CATEGORY_CRS, false, nullptr);
1478     ASSERT_NE(target_crs, nullptr);
1479     ObjectKeeper keeper_target_crs(target_crs);
1480 
1481     proj_operation_factory_context_set_spatial_criterion(
1482         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1483 
1484     proj_operation_factory_context_set_grid_availability_use(
1485         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1486 
1487     auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1488     ASSERT_NE(res, nullptr);
1489     ObjListKeeper keeper_res(res);
1490 
1491     PJ_COORD coord;
1492     coord.xyz.x = -463930;
1493     coord.xyz.y = -4414006;
1494     coord.xyz.z = 4562247;
1495     int idx = proj_get_suggested_operation(m_ctxt, res, PJ_FWD, coord);
1496     EXPECT_GE(idx, 0);
1497 }
1498 
1499 // ---------------------------------------------------------------------------
1500 
TEST_F(CApi,proj_create_operations_discard_superseded)1501 TEST_F(CApi, proj_create_operations_discard_superseded) {
1502     auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1503     ASSERT_NE(ctxt, nullptr);
1504     ContextKeeper keeper_ctxt(ctxt);
1505 
1506     auto source_crs = proj_create_from_database(
1507         m_ctxt, "EPSG", "4203", PJ_CATEGORY_CRS, false, nullptr); // AGD84
1508     ASSERT_NE(source_crs, nullptr);
1509     ObjectKeeper keeper_source_crs(source_crs);
1510 
1511     auto target_crs = proj_create_from_database(
1512         m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84
1513     ASSERT_NE(target_crs, nullptr);
1514     ObjectKeeper keeper_target_crs(target_crs);
1515 
1516     proj_operation_factory_context_set_spatial_criterion(
1517         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1518 
1519     proj_operation_factory_context_set_grid_availability_use(
1520         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1521 
1522     proj_operation_factory_context_set_discard_superseded(m_ctxt, ctxt, true);
1523 
1524     auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1525     ASSERT_NE(res, nullptr);
1526     ObjListKeeper keeper_res(res);
1527 
1528     EXPECT_EQ(proj_list_get_count(res), 4);
1529 }
1530 
1531 // ---------------------------------------------------------------------------
1532 
TEST_F(CApi,proj_create_operations_dont_discard_superseded)1533 TEST_F(CApi, proj_create_operations_dont_discard_superseded) {
1534     auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1535     ASSERT_NE(ctxt, nullptr);
1536     ContextKeeper keeper_ctxt(ctxt);
1537 
1538     auto source_crs = proj_create_from_database(
1539         m_ctxt, "EPSG", "4203", PJ_CATEGORY_CRS, false, nullptr); // AGD84
1540     ASSERT_NE(source_crs, nullptr);
1541     ObjectKeeper keeper_source_crs(source_crs);
1542 
1543     auto target_crs = proj_create_from_database(
1544         m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84
1545     ASSERT_NE(target_crs, nullptr);
1546     ObjectKeeper keeper_target_crs(target_crs);
1547 
1548     proj_operation_factory_context_set_spatial_criterion(
1549         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1550 
1551     proj_operation_factory_context_set_grid_availability_use(
1552         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1553 
1554     proj_operation_factory_context_set_discard_superseded(m_ctxt, ctxt, false);
1555 
1556     auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1557     ASSERT_NE(res, nullptr);
1558     ObjListKeeper keeper_res(res);
1559 
1560     EXPECT_EQ(proj_list_get_count(res), 5);
1561 }
1562 
1563 // ---------------------------------------------------------------------------
1564 
TEST_F(CApi,proj_create_operations_with_pivot)1565 TEST_F(CApi, proj_create_operations_with_pivot) {
1566 
1567     auto source_crs = proj_create_from_database(
1568         m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr); // WGS84
1569     ASSERT_NE(source_crs, nullptr);
1570     ObjectKeeper keeper_source_crs(source_crs);
1571 
1572     auto target_crs = proj_create_from_database(
1573         m_ctxt, "EPSG", "6668", PJ_CATEGORY_CRS, false, nullptr); // JGD2011
1574     ASSERT_NE(target_crs, nullptr);
1575     ObjectKeeper keeper_target_crs(target_crs);
1576 
1577     // There is no direct transformations between both
1578 
1579     // Default behavior: allow any pivot
1580     {
1581         auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1582         ASSERT_NE(ctxt, nullptr);
1583         ContextKeeper keeper_ctxt(ctxt);
1584 
1585         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1586         ASSERT_NE(res, nullptr);
1587         ObjListKeeper keeper_res(res);
1588         EXPECT_EQ(proj_list_get_count(res), 1);
1589         auto op = proj_list_get(m_ctxt, res, 0);
1590         ASSERT_NE(op, nullptr);
1591         ObjectKeeper keeper_op(op);
1592 
1593         EXPECT_EQ(
1594             proj_get_name(op),
1595             std::string(
1596                 "Inverse of JGD2000 to WGS 84 (1) + JGD2000 to JGD2011 (2)"));
1597     }
1598 
1599     // Disallow pivots
1600     {
1601         auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1602         ASSERT_NE(ctxt, nullptr);
1603         ContextKeeper keeper_ctxt(ctxt);
1604         proj_operation_factory_context_set_allow_use_intermediate_crs(
1605             m_ctxt, ctxt, PROJ_INTERMEDIATE_CRS_USE_NEVER);
1606 
1607         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1608         ASSERT_NE(res, nullptr);
1609         ObjListKeeper keeper_res(res);
1610         EXPECT_EQ(proj_list_get_count(res), 1);
1611         auto op = proj_list_get(m_ctxt, res, 0);
1612         ASSERT_NE(op, nullptr);
1613         ObjectKeeper keeper_op(op);
1614 
1615         EXPECT_EQ(
1616             proj_get_name(op),
1617             std::string("Ballpark geographic offset from WGS 84 to JGD2011"));
1618     }
1619 
1620     // Restrict pivot to Tokyo CRS
1621     {
1622         auto ctxt = proj_create_operation_factory_context(m_ctxt, "EPSG");
1623         ASSERT_NE(ctxt, nullptr);
1624         ContextKeeper keeper_ctxt(ctxt);
1625 
1626         const char *pivots[] = {"EPSG", "4301", nullptr};
1627         proj_operation_factory_context_set_allowed_intermediate_crs(
1628             m_ctxt, ctxt, pivots);
1629         proj_operation_factory_context_set_spatial_criterion(
1630             m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1631         proj_operation_factory_context_set_grid_availability_use(
1632             m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1633 
1634         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1635         ASSERT_NE(res, nullptr);
1636         ObjListKeeper keeper_res(res);
1637         EXPECT_EQ(proj_list_get_count(res), 8);
1638         auto op = proj_list_get(m_ctxt, res, 0);
1639         ASSERT_NE(op, nullptr);
1640         ObjectKeeper keeper_op(op);
1641 
1642         EXPECT_EQ(
1643             proj_get_name(op),
1644             std::string(
1645                 "Inverse of Tokyo to WGS 84 (108) + Tokyo to JGD2011 (2)"));
1646     }
1647 
1648     // Restrict pivot to JGD2000
1649     {
1650         auto ctxt = proj_create_operation_factory_context(m_ctxt, "any");
1651         ASSERT_NE(ctxt, nullptr);
1652         ContextKeeper keeper_ctxt(ctxt);
1653 
1654         const char *pivots[] = {"EPSG", "4612", nullptr};
1655         proj_operation_factory_context_set_allowed_intermediate_crs(
1656             m_ctxt, ctxt, pivots);
1657         proj_operation_factory_context_set_spatial_criterion(
1658             m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1659         proj_operation_factory_context_set_grid_availability_use(
1660             m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1661         proj_operation_factory_context_set_allow_use_intermediate_crs(
1662             m_ctxt, ctxt, PROJ_INTERMEDIATE_CRS_USE_ALWAYS);
1663 
1664         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1665         ASSERT_NE(res, nullptr);
1666         ObjListKeeper keeper_res(res);
1667         // includes results from ESRI
1668         EXPECT_EQ(proj_list_get_count(res), 4);
1669         auto op = proj_list_get(m_ctxt, res, 0);
1670         ASSERT_NE(op, nullptr);
1671         ObjectKeeper keeper_op(op);
1672 
1673         EXPECT_EQ(
1674             proj_get_name(op),
1675             std::string(
1676                 "Inverse of JGD2000 to WGS 84 (1) + JGD2000 to JGD2011 (2)"));
1677     }
1678 }
1679 
1680 // ---------------------------------------------------------------------------
1681 
TEST_F(CApi,proj_create_operations_allow_ballpark_transformations)1682 TEST_F(CApi, proj_create_operations_allow_ballpark_transformations) {
1683     auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
1684     ASSERT_NE(ctxt, nullptr);
1685     ContextKeeper keeper_ctxt(ctxt);
1686 
1687     auto source_crs = proj_create_from_database(
1688         m_ctxt, "EPSG", "4267", PJ_CATEGORY_CRS, false, nullptr); // NAD27
1689     ASSERT_NE(source_crs, nullptr);
1690     ObjectKeeper keeper_source_crs(source_crs);
1691 
1692     auto target_crs = proj_create_from_database(
1693         m_ctxt, "EPSG", "4258", PJ_CATEGORY_CRS, false, nullptr); // ETRS89
1694     ASSERT_NE(target_crs, nullptr);
1695     ObjectKeeper keeper_target_crs(target_crs);
1696 
1697     proj_operation_factory_context_set_spatial_criterion(
1698         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
1699 
1700     proj_operation_factory_context_set_grid_availability_use(
1701         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
1702 
1703     // Default: allowed implicitly
1704     {
1705         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1706         ASSERT_NE(res, nullptr);
1707         ObjListKeeper keeper_res(res);
1708 
1709         EXPECT_EQ(proj_list_get_count(res), 1);
1710     }
1711 
1712     // Allow explicitly
1713     {
1714         proj_operation_factory_context_set_allow_ballpark_transformations(
1715             m_ctxt, ctxt, true);
1716 
1717         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1718         ASSERT_NE(res, nullptr);
1719         ObjListKeeper keeper_res(res);
1720 
1721         EXPECT_EQ(proj_list_get_count(res), 1);
1722     }
1723 
1724     // Disallow
1725     {
1726         proj_operation_factory_context_set_allow_ballpark_transformations(
1727             m_ctxt, ctxt, false);
1728 
1729         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
1730         ASSERT_NE(res, nullptr);
1731         ObjListKeeper keeper_res(res);
1732 
1733         EXPECT_EQ(proj_list_get_count(res), 0);
1734     }
1735 }
1736 
1737 // ---------------------------------------------------------------------------
1738 
TEST_F(CApi,proj_context_set_database_path_null)1739 TEST_F(CApi, proj_context_set_database_path_null) {
1740 
1741     EXPECT_TRUE(
1742         proj_context_set_database_path(m_ctxt, nullptr, nullptr, nullptr));
1743     auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
1744                                                 PJ_CATEGORY_CRS, false,
1745                                                 nullptr); // WGS84
1746     ASSERT_NE(source_crs, nullptr);
1747     ObjectKeeper keeper_source_crs(source_crs);
1748 }
1749 
1750 // ---------------------------------------------------------------------------
1751 
TEST_F(CApi,proj_context_set_database_path_main_memory_one_aux)1752 TEST_F(CApi, proj_context_set_database_path_main_memory_one_aux) {
1753 
1754     auto c_path = proj_context_get_database_path(m_ctxt);
1755     ASSERT_TRUE(c_path != nullptr);
1756     std::string path(c_path);
1757     const char *aux_db_list[] = {path.c_str(), nullptr};
1758 
1759     // This is super exotic and a miracle that it works. :memory: as the
1760     // main DB is empty. The real stuff is in the aux_db_list. No view
1761     // is created in the ':memory:' internal DB, but as there's only one
1762     // aux DB its tables and views can be directly queried...
1763     // If that breaks at some point, that wouldn't be a big issue.
1764     // Keeping that one as I had a hard time figuring out why it worked !
1765     // The real thing is tested by the C++
1766     // factory::attachExtraDatabases_auxiliary
1767     EXPECT_TRUE(proj_context_set_database_path(m_ctxt, ":memory:", aux_db_list,
1768                                                nullptr));
1769 
1770     auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
1771                                                 PJ_CATEGORY_CRS, false,
1772                                                 nullptr); // WGS84
1773     ASSERT_NE(source_crs, nullptr);
1774     ObjectKeeper keeper_source_crs(source_crs);
1775 }
1776 
1777 // ---------------------------------------------------------------------------
1778 
TEST_F(CApi,proj_context_set_database_path_error_1)1779 TEST_F(CApi, proj_context_set_database_path_error_1) {
1780 
1781     EXPECT_FALSE(proj_context_set_database_path(m_ctxt, "i_do_not_exist.db",
1782                                                 nullptr, nullptr));
1783 
1784     // We will eventually re-open on the default DB
1785     auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
1786                                                 PJ_CATEGORY_CRS, false,
1787                                                 nullptr); // WGS84
1788     ASSERT_NE(source_crs, nullptr);
1789     ObjectKeeper keeper_source_crs(source_crs);
1790 }
1791 
1792 // ---------------------------------------------------------------------------
1793 
TEST_F(CApi,proj_context_set_database_path_error_2)1794 TEST_F(CApi, proj_context_set_database_path_error_2) {
1795 
1796     const char *aux_db_list[] = {"i_do_not_exist.db", nullptr};
1797     EXPECT_FALSE(
1798         proj_context_set_database_path(m_ctxt, nullptr, aux_db_list, nullptr));
1799 
1800     // We will eventually re-open on the default DB
1801     auto source_crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
1802                                                 PJ_CATEGORY_CRS, false,
1803                                                 nullptr); // WGS84
1804     ASSERT_NE(source_crs, nullptr);
1805     ObjectKeeper keeper_source_crs(source_crs);
1806 }
1807 
1808 // ---------------------------------------------------------------------------
1809 
TEST_F(CApi,proj_context_guess_wkt_dialect)1810 TEST_F(CApi, proj_context_guess_wkt_dialect) {
1811 
1812     EXPECT_EQ(proj_context_guess_wkt_dialect(nullptr, "LOCAL_CS[\"foo\"]"),
1813               PJ_GUESSED_WKT1_GDAL);
1814 
1815     EXPECT_EQ(proj_context_guess_wkt_dialect(
1816                   nullptr,
1817                   "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_"
1818                   "1984\",6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],"
1819                   "UNIT[\"Degree\",0.0174532925199433]]"),
1820               PJ_GUESSED_WKT1_ESRI);
1821 
1822     EXPECT_EQ(proj_context_guess_wkt_dialect(
1823                   nullptr,
1824                   "GEOGCRS[\"WGS 84\",\n"
1825                   "    DATUM[\"World Geodetic System 1984\",\n"
1826                   "        ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
1827                   "    CS[ellipsoidal,2],\n"
1828                   "        AXIS[\"geodetic latitude (Lat)\",north],\n"
1829                   "        AXIS[\"geodetic longitude (Lon)\",east],\n"
1830                   "        UNIT[\"degree\",0.0174532925199433]]"),
1831               PJ_GUESSED_WKT2_2019);
1832 
1833     EXPECT_EQ(proj_context_guess_wkt_dialect(
1834                   nullptr,
1835                   "GEODCRS[\"WGS 84\",\n"
1836                   "    DATUM[\"World Geodetic System 1984\",\n"
1837                   "        ELLIPSOID[\"WGS 84\",6378137,298.257223563]],\n"
1838                   "    CS[ellipsoidal,2],\n"
1839                   "        AXIS[\"geodetic latitude (Lat)\",north],\n"
1840                   "        AXIS[\"geodetic longitude (Lon)\",east],\n"
1841                   "        UNIT[\"degree\",0.0174532925199433]]"),
1842               PJ_GUESSED_WKT2_2015);
1843 
1844     EXPECT_EQ(proj_context_guess_wkt_dialect(nullptr, "foo"),
1845               PJ_GUESSED_NOT_WKT);
1846 }
1847 
1848 // ---------------------------------------------------------------------------
1849 
TEST_F(CApi,proj_create_from_name)1850 TEST_F(CApi, proj_create_from_name) {
1851     /*
1852         PJ_OBJ_LIST PROJ_DLL *proj_create_from_name(
1853             PJ_CONTEXT *ctx,
1854             const char *auth_name,
1855             const char *searchedName,
1856             const PJ_TYPE* types,
1857             size_t typesCount,
1858             int approximateMatch,
1859             size_t limitResultCount,
1860             const char* const *options); */
1861     {
1862         auto res = proj_create_from_name(m_ctxt, nullptr, "WGS 84", nullptr, 0,
1863                                          false, 0, nullptr);
1864         ASSERT_NE(res, nullptr);
1865         ObjListKeeper keeper_res(res);
1866         EXPECT_EQ(proj_list_get_count(res), 5);
1867     }
1868     {
1869         auto res = proj_create_from_name(m_ctxt, "xx", "WGS 84", nullptr, 0,
1870                                          false, 0, nullptr);
1871         ASSERT_NE(res, nullptr);
1872         ObjListKeeper keeper_res(res);
1873         EXPECT_EQ(proj_list_get_count(res), 0);
1874     }
1875     {
1876         const PJ_TYPE types[] = {PJ_TYPE_GEODETIC_CRS, PJ_TYPE_PROJECTED_CRS};
1877         auto res = proj_create_from_name(m_ctxt, nullptr, "WGS 84", types, 2,
1878                                          true, 10, nullptr);
1879         ASSERT_NE(res, nullptr);
1880         ObjListKeeper keeper_res(res);
1881         EXPECT_EQ(proj_list_get_count(res), 10);
1882     }
1883 }
1884 
1885 // ---------------------------------------------------------------------------
1886 
TEST_F(CApi,proj_identify)1887 TEST_F(CApi, proj_identify) {
1888     auto obj = proj_create_from_wkt(
1889         m_ctxt,
1890         GeographicCRS::EPSG_4807->exportToWKT(WKTFormatter::create().get())
1891             .c_str(),
1892         nullptr, nullptr, nullptr);
1893     ObjectKeeper keeper(obj);
1894     ASSERT_NE(obj, nullptr);
1895     {
1896         auto res = proj_identify(m_ctxt, obj, nullptr, nullptr, nullptr);
1897         ObjListKeeper keeper_res(res);
1898         EXPECT_EQ(proj_list_get_count(res), 1);
1899     }
1900     {
1901         int *confidence = nullptr;
1902         auto res = proj_identify(m_ctxt, obj, "EPSG", nullptr, &confidence);
1903         ObjListKeeper keeper_res(res);
1904         EXPECT_EQ(proj_list_get_count(res), 1);
1905         EXPECT_EQ(confidence[0], 100);
1906         proj_int_list_destroy(confidence);
1907     }
1908     {
1909         auto objEllps = proj_create_from_wkt(
1910             m_ctxt,
1911             Ellipsoid::GRS1980->exportToWKT(WKTFormatter::create().get())
1912                 .c_str(),
1913             nullptr, nullptr, nullptr);
1914         ObjectKeeper keeperEllps(objEllps);
1915         ASSERT_NE(objEllps, nullptr);
1916         auto res = proj_identify(m_ctxt, objEllps, nullptr, nullptr, nullptr);
1917         ObjListKeeper keeper_res(res);
1918         EXPECT_EQ(res, nullptr);
1919     }
1920     {
1921         auto obj2 = proj_create(
1922             m_ctxt, "+proj=longlat +datum=WGS84 +no_defs +type=crs");
1923         ObjectKeeper keeper2(obj2);
1924         ASSERT_NE(obj2, nullptr);
1925         int *confidence = nullptr;
1926         auto res = proj_identify(m_ctxt, obj2, nullptr, nullptr, &confidence);
1927         ObjListKeeper keeper_res(res);
1928         EXPECT_EQ(proj_list_get_count(res), 4);
1929         proj_int_list_destroy(confidence);
1930     }
1931     {
1932         auto obj2 = proj_create_from_database(m_ctxt, "IGNF", "ETRS89UTM28",
1933                                               PJ_CATEGORY_CRS, false, nullptr);
1934         ObjectKeeper keeper2(obj2);
1935         ASSERT_NE(obj2, nullptr);
1936         int *confidence = nullptr;
1937         auto res = proj_identify(m_ctxt, obj2, "EPSG", nullptr, &confidence);
1938         ObjListKeeper keeper_res(res);
1939         EXPECT_EQ(proj_list_get_count(res), 1);
1940         auto gotCRS = proj_list_get(m_ctxt, res, 0);
1941         ASSERT_NE(gotCRS, nullptr);
1942         ObjectKeeper keeper_gotCRS(gotCRS);
1943         auto auth = proj_get_id_auth_name(gotCRS, 0);
1944         ASSERT_TRUE(auth != nullptr);
1945         EXPECT_EQ(auth, std::string("EPSG"));
1946         auto code = proj_get_id_code(gotCRS, 0);
1947         ASSERT_TRUE(code != nullptr);
1948         EXPECT_EQ(code, std::string("25828"));
1949         EXPECT_EQ(confidence[0], 70);
1950         proj_int_list_destroy(confidence);
1951     }
1952 }
1953 
1954 // ---------------------------------------------------------------------------
1955 
TEST_F(CApi,proj_get_area_of_use)1956 TEST_F(CApi, proj_get_area_of_use) {
1957     {
1958         auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
1959                                              PJ_CATEGORY_CRS, false, nullptr);
1960         ASSERT_NE(crs, nullptr);
1961         ObjectKeeper keeper(crs);
1962         EXPECT_TRUE(proj_get_area_of_use(m_ctxt, crs, nullptr, nullptr, nullptr,
1963                                          nullptr, nullptr));
1964         const char *name = nullptr;
1965         double w;
1966         double s;
1967         double e;
1968         double n;
1969         EXPECT_TRUE(proj_get_area_of_use(m_ctxt, crs, &w, &s, &e, &n, &name));
1970         EXPECT_EQ(w, -180);
1971         EXPECT_EQ(s, -90);
1972         EXPECT_EQ(e, 180);
1973         EXPECT_EQ(n, 90);
1974         ASSERT_TRUE(name != nullptr);
1975         EXPECT_EQ(std::string(name), "World.");
1976     }
1977     {
1978         auto obj = proj_create(m_ctxt, "+proj=longlat +type=crs");
1979         ObjectKeeper keeper(obj);
1980         ASSERT_NE(obj, nullptr);
1981         EXPECT_FALSE(proj_get_area_of_use(m_ctxt, obj, nullptr, nullptr,
1982                                           nullptr, nullptr, nullptr));
1983     }
1984 }
1985 
1986 // ---------------------------------------------------------------------------
1987 
TEST_F(CApi,proj_coordoperation_get_accuracy)1988 TEST_F(CApi, proj_coordoperation_get_accuracy) {
1989     {
1990         auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
1991                                              PJ_CATEGORY_CRS, false, nullptr);
1992         ASSERT_NE(crs, nullptr);
1993         ObjectKeeper keeper(crs);
1994         EXPECT_EQ(proj_coordoperation_get_accuracy(m_ctxt, crs), -1.0);
1995     }
1996     {
1997         auto obj = proj_create_from_database(m_ctxt, "EPSG", "1170",
1998                                              PJ_CATEGORY_COORDINATE_OPERATION,
1999                                              false, nullptr);
2000         ASSERT_NE(obj, nullptr);
2001         ObjectKeeper keeper(obj);
2002         EXPECT_EQ(proj_coordoperation_get_accuracy(m_ctxt, obj), 16.0);
2003     }
2004     {
2005         auto obj = proj_create(m_ctxt, "+proj=helmert");
2006         ObjectKeeper keeper(obj);
2007         ASSERT_NE(obj, nullptr);
2008         EXPECT_EQ(proj_coordoperation_get_accuracy(m_ctxt, obj), -1.0);
2009     }
2010 }
2011 
2012 // ---------------------------------------------------------------------------
2013 
TEST_F(CApi,proj_create_geographic_crs)2014 TEST_F(CApi, proj_create_geographic_crs) {
2015 
2016     auto cs = proj_create_ellipsoidal_2D_cs(
2017         m_ctxt, PJ_ELLPS2D_LATITUDE_LONGITUDE, nullptr, 0);
2018     ObjectKeeper keeper_cs(cs);
2019     ASSERT_NE(cs, nullptr);
2020 
2021     {
2022         auto obj = proj_create_geographic_crs(
2023             m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
2024             298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs);
2025         ObjectKeeper keeper(obj);
2026         ASSERT_NE(obj, nullptr);
2027 
2028         auto objRef =
2029             proj_create(m_ctxt, GeographicCRS::EPSG_4326
2030                                     ->exportToWKT(WKTFormatter::create().get())
2031                                     .c_str());
2032         ObjectKeeper keeperobjRef(objRef);
2033         EXPECT_NE(objRef, nullptr);
2034 
2035         EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT));
2036 
2037         auto datum = proj_crs_get_datum(m_ctxt, obj);
2038         ObjectKeeper keeper_datum(datum);
2039         ASSERT_NE(datum, nullptr);
2040 
2041         auto obj2 =
2042             proj_create_geographic_crs_from_datum(m_ctxt, "WGS 84", datum, cs);
2043         ObjectKeeper keeperObj(obj2);
2044         ASSERT_NE(obj2, nullptr);
2045 
2046         EXPECT_TRUE(proj_is_equivalent_to(obj, obj2, PJ_COMP_STRICT));
2047     }
2048     {
2049         auto obj =
2050             proj_create_geographic_crs(m_ctxt, nullptr, nullptr, nullptr, 1.0,
2051                                        0.0, nullptr, 0.0, nullptr, 0.0, cs);
2052         ObjectKeeper keeper(obj);
2053         ASSERT_NE(obj, nullptr);
2054     }
2055 
2056     // Datum with GDAL_WKT1 spelling: special case of WGS_1984
2057     {
2058         auto obj = proj_create_geographic_crs(
2059             m_ctxt, "WGS 84", "WGS_1984", "WGS 84", 6378137, 298.257223563,
2060             "Greenwich", 0.0, "Degree", 0.0174532925199433, cs);
2061         ObjectKeeper keeper(obj);
2062         ASSERT_NE(obj, nullptr);
2063 
2064         auto objRef =
2065             proj_create(m_ctxt, GeographicCRS::EPSG_4326
2066                                     ->exportToWKT(WKTFormatter::create().get())
2067                                     .c_str());
2068         ObjectKeeper keeperobjRef(objRef);
2069         EXPECT_NE(objRef, nullptr);
2070 
2071         EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT));
2072     }
2073 
2074     // Datum with GDAL_WKT1 spelling: database query
2075     {
2076         auto obj = proj_create_geographic_crs(
2077             m_ctxt, "NAD83", "North_American_Datum_1983", "GRS 1980", 6378137,
2078             298.257222101, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs);
2079         ObjectKeeper keeper(obj);
2080         ASSERT_NE(obj, nullptr);
2081 
2082         auto objRef =
2083             proj_create(m_ctxt, GeographicCRS::EPSG_4269
2084                                     ->exportToWKT(WKTFormatter::create().get())
2085                                     .c_str());
2086         ObjectKeeper keeperobjRef(objRef);
2087         EXPECT_NE(objRef, nullptr);
2088 
2089         EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT));
2090     }
2091 
2092     // Datum with GDAL_WKT1 spelling: database query in alias_name table
2093     {
2094         auto crs = proj_create_geographic_crs(
2095             m_ctxt, "S-JTSK (Ferro)",
2096             "System_Jednotne_Trigonometricke_Site_Katastralni_Ferro",
2097             "Bessel 1841", 6377397.155, 299.1528128, "Ferro",
2098             -17.66666666666667, "Degree", 0.0174532925199433, cs);
2099         ObjectKeeper keeper(crs);
2100         ASSERT_NE(crs, nullptr);
2101 
2102         auto datum = proj_crs_get_datum(m_ctxt, crs);
2103         ASSERT_NE(datum, nullptr);
2104         ObjectKeeper keeper_datum(datum);
2105 
2106         auto datum_name = proj_get_name(datum);
2107         ASSERT_TRUE(datum_name != nullptr);
2108         EXPECT_EQ(datum_name,
2109                   std::string("System of the Unified Trigonometrical Cadastral "
2110                               "Network (Ferro)"));
2111     }
2112 
2113     // WKT1 with (deprecated)
2114     {
2115         auto crs = proj_create_geographic_crs(
2116             m_ctxt, "SAD69 (deprecated)", "South_American_Datum_1969",
2117             "GRS 1967", 6378160, 298.247167427, "Greenwich", 0, "Degree",
2118             0.0174532925199433, cs);
2119         ObjectKeeper keeper(crs);
2120         ASSERT_NE(crs, nullptr);
2121 
2122         auto name = proj_get_name(crs);
2123         ASSERT_TRUE(name != nullptr);
2124         EXPECT_EQ(name, std::string("SAD69"));
2125         EXPECT_TRUE(proj_is_deprecated(crs));
2126     }
2127 }
2128 
2129 // ---------------------------------------------------------------------------
2130 
TEST_F(CApi,proj_create_geocentric_crs)2131 TEST_F(CApi, proj_create_geocentric_crs) {
2132     {
2133         auto obj = proj_create_geocentric_crs(
2134             m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
2135             298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433,
2136             "Metre", 1.0);
2137         ObjectKeeper keeper(obj);
2138         ASSERT_NE(obj, nullptr);
2139 
2140         auto objRef =
2141             proj_create(m_ctxt, GeographicCRS::EPSG_4978
2142                                     ->exportToWKT(WKTFormatter::create().get())
2143                                     .c_str());
2144         ObjectKeeper keeperobjRef(objRef);
2145         EXPECT_NE(objRef, nullptr);
2146 
2147         EXPECT_TRUE(proj_is_equivalent_to(obj, objRef, PJ_COMP_EQUIVALENT));
2148 
2149         auto datum = proj_crs_get_datum(m_ctxt, obj);
2150         ObjectKeeper keeper_datum(datum);
2151         ASSERT_NE(datum, nullptr);
2152 
2153         auto obj2 = proj_create_geocentric_crs_from_datum(m_ctxt, "WGS 84",
2154                                                           datum, "Metre", 1.0);
2155         ObjectKeeper keeperObj(obj2);
2156         ASSERT_NE(obj2, nullptr);
2157 
2158         EXPECT_TRUE(proj_is_equivalent_to(obj, obj2, PJ_COMP_STRICT));
2159     }
2160     {
2161         auto obj = proj_create_geocentric_crs(m_ctxt, nullptr, nullptr, nullptr,
2162                                               1.0, 0.0, nullptr, 0.0, nullptr,
2163                                               0.0, nullptr, 0.0);
2164         ObjectKeeper keeper(obj);
2165         ASSERT_NE(obj, nullptr);
2166     }
2167 }
2168 
2169 // ---------------------------------------------------------------------------
2170 
TEST_F(CApi,check_coord_op_obj_can_be_used_with_proj_trans)2171 TEST_F(CApi, check_coord_op_obj_can_be_used_with_proj_trans) {
2172 
2173     {
2174         auto projCRS = proj_create_conversion_utm(m_ctxt, 31, true);
2175         ObjectKeeper keeper_projCRS(projCRS);
2176         ASSERT_NE(projCRS, nullptr);
2177 
2178         PJ_COORD coord;
2179         coord.xyzt.x = proj_torad(3.0);
2180         coord.xyzt.y = 0;
2181         coord.xyzt.z = 0;
2182         coord.xyzt.t = 0;
2183         EXPECT_NEAR(proj_trans(projCRS, PJ_FWD, coord).xyzt.x, 500000.0, 1e-9);
2184     }
2185 }
2186 
2187 // ---------------------------------------------------------------------------
2188 
TEST_F(CApi,proj_create_projections)2189 TEST_F(CApi, proj_create_projections) {
2190 
2191     /* BEGIN: Generated by scripts/create_c_api_projections.py*/
2192     {
2193         auto projCRS = proj_create_conversion_utm(m_ctxt, 0, 0);
2194         ObjectKeeper keeper_projCRS(projCRS);
2195         ASSERT_NE(projCRS, nullptr);
2196     }
2197     {
2198         auto projCRS = proj_create_conversion_transverse_mercator(
2199             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2200         ObjectKeeper keeper_projCRS(projCRS);
2201         ASSERT_NE(projCRS, nullptr);
2202     }
2203     {
2204         auto projCRS =
2205             proj_create_conversion_gauss_schreiber_transverse_mercator(
2206                 m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2207                 1.0);
2208         ObjectKeeper keeper_projCRS(projCRS);
2209         ASSERT_NE(projCRS, nullptr);
2210     }
2211     {
2212         auto projCRS =
2213             proj_create_conversion_transverse_mercator_south_oriented(
2214                 m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2215                 1.0);
2216         ObjectKeeper keeper_projCRS(projCRS);
2217         ASSERT_NE(projCRS, nullptr);
2218     }
2219     {
2220         auto projCRS = proj_create_conversion_two_point_equidistant(
2221             m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2222             1.0);
2223         ObjectKeeper keeper_projCRS(projCRS);
2224         ASSERT_NE(projCRS, nullptr);
2225     }
2226     {
2227         auto projCRS = proj_create_conversion_tunisia_mapping_grid(
2228             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2229         ObjectKeeper keeper_projCRS(projCRS);
2230         ASSERT_NE(projCRS, nullptr);
2231     }
2232     {
2233         auto projCRS = proj_create_conversion_albers_equal_area(
2234             m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2235             1.0);
2236         ObjectKeeper keeper_projCRS(projCRS);
2237         ASSERT_NE(projCRS, nullptr);
2238     }
2239     {
2240         auto projCRS = proj_create_conversion_lambert_conic_conformal_1sp(
2241             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2242         ObjectKeeper keeper_projCRS(projCRS);
2243         ASSERT_NE(projCRS, nullptr);
2244     }
2245     {
2246         auto projCRS = proj_create_conversion_lambert_conic_conformal_2sp(
2247             m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2248             1.0);
2249         ObjectKeeper keeper_projCRS(projCRS);
2250         ASSERT_NE(projCRS, nullptr);
2251     }
2252     {
2253         auto projCRS =
2254             proj_create_conversion_lambert_conic_conformal_2sp_michigan(
2255                 m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
2256                 "Metre", 1.0);
2257         ObjectKeeper keeper_projCRS(projCRS);
2258         ASSERT_NE(projCRS, nullptr);
2259     }
2260     {
2261         auto projCRS =
2262             proj_create_conversion_lambert_conic_conformal_2sp_belgium(
2263                 m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2264                 1.0);
2265         ObjectKeeper keeper_projCRS(projCRS);
2266         ASSERT_NE(projCRS, nullptr);
2267     }
2268     {
2269         auto projCRS = proj_create_conversion_azimuthal_equidistant(
2270             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2271         ObjectKeeper keeper_projCRS(projCRS);
2272         ASSERT_NE(projCRS, nullptr);
2273     }
2274     {
2275         auto projCRS = proj_create_conversion_guam_projection(
2276             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2277         ObjectKeeper keeper_projCRS(projCRS);
2278         ASSERT_NE(projCRS, nullptr);
2279     }
2280     {
2281         auto projCRS = proj_create_conversion_bonne(
2282             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2283         ObjectKeeper keeper_projCRS(projCRS);
2284         ASSERT_NE(projCRS, nullptr);
2285     }
2286     {
2287         auto projCRS =
2288             proj_create_conversion_lambert_cylindrical_equal_area_spherical(
2289                 m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2290         ObjectKeeper keeper_projCRS(projCRS);
2291         ASSERT_NE(projCRS, nullptr);
2292     }
2293     {
2294         auto projCRS = proj_create_conversion_lambert_cylindrical_equal_area(
2295             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2296         ObjectKeeper keeper_projCRS(projCRS);
2297         ASSERT_NE(projCRS, nullptr);
2298     }
2299     {
2300         auto projCRS = proj_create_conversion_cassini_soldner(
2301             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2302         ObjectKeeper keeper_projCRS(projCRS);
2303         ASSERT_NE(projCRS, nullptr);
2304     }
2305     {
2306         auto projCRS = proj_create_conversion_equidistant_conic(
2307             m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2308             1.0);
2309         ObjectKeeper keeper_projCRS(projCRS);
2310         ASSERT_NE(projCRS, nullptr);
2311     }
2312     {
2313         auto projCRS = proj_create_conversion_eckert_i(
2314             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2315         ObjectKeeper keeper_projCRS(projCRS);
2316         ASSERT_NE(projCRS, nullptr);
2317     }
2318     {
2319         auto projCRS = proj_create_conversion_eckert_ii(
2320             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2321         ObjectKeeper keeper_projCRS(projCRS);
2322         ASSERT_NE(projCRS, nullptr);
2323     }
2324     {
2325         auto projCRS = proj_create_conversion_eckert_iii(
2326             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2327         ObjectKeeper keeper_projCRS(projCRS);
2328         ASSERT_NE(projCRS, nullptr);
2329     }
2330     {
2331         auto projCRS = proj_create_conversion_eckert_iv(
2332             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2333         ObjectKeeper keeper_projCRS(projCRS);
2334         ASSERT_NE(projCRS, nullptr);
2335     }
2336     {
2337         auto projCRS = proj_create_conversion_eckert_v(
2338             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2339         ObjectKeeper keeper_projCRS(projCRS);
2340         ASSERT_NE(projCRS, nullptr);
2341     }
2342     {
2343         auto projCRS = proj_create_conversion_eckert_vi(
2344             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2345         ObjectKeeper keeper_projCRS(projCRS);
2346         ASSERT_NE(projCRS, nullptr);
2347     }
2348     {
2349         auto projCRS = proj_create_conversion_equidistant_cylindrical(
2350             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2351         ObjectKeeper keeper_projCRS(projCRS);
2352         ASSERT_NE(projCRS, nullptr);
2353     }
2354     {
2355         auto projCRS = proj_create_conversion_equidistant_cylindrical_spherical(
2356             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2357         ObjectKeeper keeper_projCRS(projCRS);
2358         ASSERT_NE(projCRS, nullptr);
2359     }
2360     {
2361         auto projCRS = proj_create_conversion_gall(
2362             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2363         ObjectKeeper keeper_projCRS(projCRS);
2364         ASSERT_NE(projCRS, nullptr);
2365     }
2366     {
2367         auto projCRS = proj_create_conversion_goode_homolosine(
2368             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2369         ObjectKeeper keeper_projCRS(projCRS);
2370         ASSERT_NE(projCRS, nullptr);
2371     }
2372     {
2373         auto projCRS = proj_create_conversion_interrupted_goode_homolosine(
2374             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2375         ObjectKeeper keeper_projCRS(projCRS);
2376         ASSERT_NE(projCRS, nullptr);
2377     }
2378     {
2379         auto projCRS = proj_create_conversion_geostationary_satellite_sweep_x(
2380             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2381         ObjectKeeper keeper_projCRS(projCRS);
2382         ASSERT_NE(projCRS, nullptr);
2383     }
2384     {
2385         auto projCRS = proj_create_conversion_geostationary_satellite_sweep_y(
2386             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2387         ObjectKeeper keeper_projCRS(projCRS);
2388         ASSERT_NE(projCRS, nullptr);
2389     }
2390     {
2391         auto projCRS = proj_create_conversion_gnomonic(
2392             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2393         ObjectKeeper keeper_projCRS(projCRS);
2394         ASSERT_NE(projCRS, nullptr);
2395     }
2396     {
2397         auto projCRS = proj_create_conversion_hotine_oblique_mercator_variant_a(
2398             m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2399             1.0);
2400         ObjectKeeper keeper_projCRS(projCRS);
2401         ASSERT_NE(projCRS, nullptr);
2402     }
2403     {
2404         auto projCRS = proj_create_conversion_hotine_oblique_mercator_variant_b(
2405             m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2406             1.0);
2407         ObjectKeeper keeper_projCRS(projCRS);
2408         ASSERT_NE(projCRS, nullptr);
2409     }
2410     {
2411         auto projCRS =
2412             proj_create_conversion_hotine_oblique_mercator_two_point_natural_origin(
2413                 m_ctxt, 0, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433,
2414                 "Metre", 1.0);
2415         ObjectKeeper keeper_projCRS(projCRS);
2416         ASSERT_NE(projCRS, nullptr);
2417     }
2418     {
2419         auto projCRS = proj_create_conversion_laborde_oblique_mercator(
2420             m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2421             1.0);
2422         ObjectKeeper keeper_projCRS(projCRS);
2423         ASSERT_NE(projCRS, nullptr);
2424     }
2425     {
2426         auto projCRS = proj_create_conversion_international_map_world_polyconic(
2427             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2428         ObjectKeeper keeper_projCRS(projCRS);
2429         ASSERT_NE(projCRS, nullptr);
2430     }
2431     {
2432         auto projCRS = proj_create_conversion_krovak_north_oriented(
2433             m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2434             1.0);
2435         ObjectKeeper keeper_projCRS(projCRS);
2436         ASSERT_NE(projCRS, nullptr);
2437     }
2438     {
2439         auto projCRS =
2440             proj_create_conversion_krovak(m_ctxt, 0, 0, 0, 0, 0, 0, 0, "Degree",
2441                                           0.0174532925199433, "Metre", 1.0);
2442         ObjectKeeper keeper_projCRS(projCRS);
2443         ASSERT_NE(projCRS, nullptr);
2444     }
2445     {
2446         auto projCRS = proj_create_conversion_lambert_azimuthal_equal_area(
2447             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2448         ObjectKeeper keeper_projCRS(projCRS);
2449         ASSERT_NE(projCRS, nullptr);
2450     }
2451     {
2452         auto projCRS = proj_create_conversion_miller_cylindrical(
2453             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2454         ObjectKeeper keeper_projCRS(projCRS);
2455         ASSERT_NE(projCRS, nullptr);
2456     }
2457     {
2458         auto projCRS = proj_create_conversion_mercator_variant_a(
2459             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2460         ObjectKeeper keeper_projCRS(projCRS);
2461         ASSERT_NE(projCRS, nullptr);
2462     }
2463     {
2464         auto projCRS = proj_create_conversion_mercator_variant_b(
2465             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2466         ObjectKeeper keeper_projCRS(projCRS);
2467         ASSERT_NE(projCRS, nullptr);
2468     }
2469     {
2470         auto projCRS =
2471             proj_create_conversion_popular_visualisation_pseudo_mercator(
2472                 m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2473         ObjectKeeper keeper_projCRS(projCRS);
2474         ASSERT_NE(projCRS, nullptr);
2475     }
2476     {
2477         auto projCRS = proj_create_conversion_mollweide(
2478             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2479         ObjectKeeper keeper_projCRS(projCRS);
2480         ASSERT_NE(projCRS, nullptr);
2481     }
2482     {
2483         auto projCRS = proj_create_conversion_new_zealand_mapping_grid(
2484             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2485         ObjectKeeper keeper_projCRS(projCRS);
2486         ASSERT_NE(projCRS, nullptr);
2487     }
2488     {
2489         auto projCRS = proj_create_conversion_oblique_stereographic(
2490             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2491         ObjectKeeper keeper_projCRS(projCRS);
2492         ASSERT_NE(projCRS, nullptr);
2493     }
2494     {
2495         auto projCRS = proj_create_conversion_orthographic(
2496             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2497         ObjectKeeper keeper_projCRS(projCRS);
2498         ASSERT_NE(projCRS, nullptr);
2499     }
2500     {
2501         auto projCRS = proj_create_conversion_american_polyconic(
2502             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2503         ObjectKeeper keeper_projCRS(projCRS);
2504         ASSERT_NE(projCRS, nullptr);
2505     }
2506     {
2507         auto projCRS = proj_create_conversion_polar_stereographic_variant_a(
2508             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2509         ObjectKeeper keeper_projCRS(projCRS);
2510         ASSERT_NE(projCRS, nullptr);
2511     }
2512     {
2513         auto projCRS = proj_create_conversion_polar_stereographic_variant_b(
2514             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2515         ObjectKeeper keeper_projCRS(projCRS);
2516         ASSERT_NE(projCRS, nullptr);
2517     }
2518     {
2519         auto projCRS = proj_create_conversion_robinson(
2520             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2521         ObjectKeeper keeper_projCRS(projCRS);
2522         ASSERT_NE(projCRS, nullptr);
2523     }
2524     {
2525         auto projCRS = proj_create_conversion_sinusoidal(
2526             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2527         ObjectKeeper keeper_projCRS(projCRS);
2528         ASSERT_NE(projCRS, nullptr);
2529     }
2530     {
2531         auto projCRS = proj_create_conversion_stereographic(
2532             m_ctxt, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2533         ObjectKeeper keeper_projCRS(projCRS);
2534         ASSERT_NE(projCRS, nullptr);
2535     }
2536     {
2537         auto projCRS = proj_create_conversion_van_der_grinten(
2538             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2539         ObjectKeeper keeper_projCRS(projCRS);
2540         ASSERT_NE(projCRS, nullptr);
2541     }
2542     {
2543         auto projCRS = proj_create_conversion_wagner_i(
2544             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2545         ObjectKeeper keeper_projCRS(projCRS);
2546         ASSERT_NE(projCRS, nullptr);
2547     }
2548     {
2549         auto projCRS = proj_create_conversion_wagner_ii(
2550             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2551         ObjectKeeper keeper_projCRS(projCRS);
2552         ASSERT_NE(projCRS, nullptr);
2553     }
2554     {
2555         auto projCRS = proj_create_conversion_wagner_iii(
2556             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2557         ObjectKeeper keeper_projCRS(projCRS);
2558         ASSERT_NE(projCRS, nullptr);
2559     }
2560     {
2561         auto projCRS = proj_create_conversion_wagner_iv(
2562             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2563         ObjectKeeper keeper_projCRS(projCRS);
2564         ASSERT_NE(projCRS, nullptr);
2565     }
2566     {
2567         auto projCRS = proj_create_conversion_wagner_v(
2568             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2569         ObjectKeeper keeper_projCRS(projCRS);
2570         ASSERT_NE(projCRS, nullptr);
2571     }
2572     {
2573         auto projCRS = proj_create_conversion_wagner_vi(
2574             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2575         ObjectKeeper keeper_projCRS(projCRS);
2576         ASSERT_NE(projCRS, nullptr);
2577     }
2578     {
2579         auto projCRS = proj_create_conversion_wagner_vii(
2580             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2581         ObjectKeeper keeper_projCRS(projCRS);
2582         ASSERT_NE(projCRS, nullptr);
2583     }
2584     {
2585         auto projCRS = proj_create_conversion_quadrilateralized_spherical_cube(
2586             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2587         ObjectKeeper keeper_projCRS(projCRS);
2588         ASSERT_NE(projCRS, nullptr);
2589     }
2590     {
2591         auto projCRS = proj_create_conversion_spherical_cross_track_height(
2592             m_ctxt, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2593         ObjectKeeper keeper_projCRS(projCRS);
2594         ASSERT_NE(projCRS, nullptr);
2595     }
2596     {
2597         auto projCRS = proj_create_conversion_equal_earth(
2598             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433, "Metre", 1.0);
2599         ObjectKeeper keeper_projCRS(projCRS);
2600         ASSERT_NE(projCRS, nullptr);
2601     }
2602     {
2603         auto projCRS = proj_create_conversion_vertical_perspective(
2604             m_ctxt, 0, 0, 0, 0, 0, 0, "Degree", 0.0174532925199433, "Metre",
2605             1.0);
2606         ObjectKeeper keeper_projCRS(projCRS);
2607         ASSERT_NE(projCRS, nullptr);
2608     }
2609     {
2610         auto projCRS = proj_create_conversion_pole_rotation_grib_convention(
2611             m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433);
2612         ObjectKeeper keeper_projCRS(projCRS);
2613         ASSERT_NE(projCRS, nullptr);
2614     }
2615 
2616     /* END: Generated by scripts/create_c_api_projections.py*/
2617 }
2618 
2619 // ---------------------------------------------------------------------------
2620 
TEST_F(CApi,proj_cs_get_axis_info)2621 TEST_F(CApi, proj_cs_get_axis_info) {
2622     {
2623         auto crs = proj_create_from_database(m_ctxt, "EPSG", "4326",
2624                                              PJ_CATEGORY_CRS, false, nullptr);
2625         ASSERT_NE(crs, nullptr);
2626         ObjectKeeper keeper(crs);
2627 
2628         auto cs = proj_crs_get_coordinate_system(m_ctxt, crs);
2629         ASSERT_NE(cs, nullptr);
2630         ObjectKeeper keeperCs(cs);
2631 
2632         EXPECT_EQ(proj_cs_get_type(m_ctxt, cs), PJ_CS_TYPE_ELLIPSOIDAL);
2633 
2634         EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 2);
2635 
2636         EXPECT_FALSE(proj_cs_get_axis_info(m_ctxt, cs, -1, nullptr, nullptr,
2637                                            nullptr, nullptr, nullptr, nullptr,
2638                                            nullptr));
2639 
2640         EXPECT_FALSE(proj_cs_get_axis_info(m_ctxt, cs, 2, nullptr, nullptr,
2641                                            nullptr, nullptr, nullptr, nullptr,
2642                                            nullptr));
2643 
2644         EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr,
2645                                           nullptr, nullptr, nullptr, nullptr,
2646                                           nullptr));
2647 
2648         const char *name = nullptr;
2649         const char *abbrev = nullptr;
2650         const char *direction = nullptr;
2651         double unitConvFactor = 0.0;
2652         const char *unitName = nullptr;
2653         const char *unitAuthority = nullptr;
2654         const char *unitCode = nullptr;
2655 
2656         EXPECT_TRUE(proj_cs_get_axis_info(
2657             m_ctxt, cs, 0, &name, &abbrev, &direction, &unitConvFactor,
2658             &unitName, &unitAuthority, &unitCode));
2659         ASSERT_NE(name, nullptr);
2660         ASSERT_NE(abbrev, nullptr);
2661         ASSERT_NE(direction, nullptr);
2662         ASSERT_NE(unitName, nullptr);
2663         ASSERT_NE(unitAuthority, nullptr);
2664         ASSERT_NE(unitCode, nullptr);
2665         EXPECT_EQ(std::string(name), "Geodetic latitude");
2666         EXPECT_EQ(std::string(abbrev), "Lat");
2667         EXPECT_EQ(std::string(direction), "north");
2668         EXPECT_EQ(unitConvFactor, 0.017453292519943295) << unitConvFactor;
2669         EXPECT_EQ(std::string(unitName), "degree");
2670         EXPECT_EQ(std::string(unitAuthority), "EPSG");
2671         EXPECT_EQ(std::string(unitCode), "9122");
2672     }
2673 
2674     // Non CRS object
2675     {
2676         auto obj = proj_create_from_database(m_ctxt, "EPSG", "1170",
2677                                              PJ_CATEGORY_COORDINATE_OPERATION,
2678                                              false, nullptr);
2679         ASSERT_NE(obj, nullptr);
2680         ObjectKeeper keeper(obj);
2681         EXPECT_EQ(proj_crs_get_coordinate_system(m_ctxt, obj), nullptr);
2682 
2683         EXPECT_EQ(proj_cs_get_type(m_ctxt, obj), PJ_CS_TYPE_UNKNOWN);
2684 
2685         EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, obj), -1);
2686 
2687         EXPECT_FALSE(proj_cs_get_axis_info(m_ctxt, obj, 0, nullptr, nullptr,
2688                                            nullptr, nullptr, nullptr, nullptr,
2689                                            nullptr));
2690     }
2691 }
2692 
2693 // ---------------------------------------------------------------------------
2694 
TEST_F(CApi,proj_context_get_database_metadata)2695 TEST_F(CApi, proj_context_get_database_metadata) {
2696     EXPECT_TRUE(proj_context_get_database_metadata(m_ctxt, "IGNF.VERSION") !=
2697                 nullptr);
2698 
2699     EXPECT_TRUE(proj_context_get_database_metadata(m_ctxt, "FOO") == nullptr);
2700 }
2701 
2702 // ---------------------------------------------------------------------------
2703 
TEST_F(CApi,proj_clone)2704 TEST_F(CApi, proj_clone) {
2705     auto obj = proj_create(m_ctxt, "+proj=longlat");
2706     ObjectKeeper keeper(obj);
2707     ASSERT_NE(obj, nullptr);
2708 
2709     auto clone = proj_clone(m_ctxt, obj);
2710     ObjectKeeper keeperClone(clone);
2711     ASSERT_NE(clone, nullptr);
2712 
2713     EXPECT_TRUE(proj_is_equivalent_to(obj, clone, PJ_COMP_STRICT));
2714 }
2715 
2716 // ---------------------------------------------------------------------------
2717 
TEST_F(CApi,proj_crs_alter_geodetic_crs)2718 TEST_F(CApi, proj_crs_alter_geodetic_crs) {
2719     auto projCRS = proj_create_from_wkt(
2720         m_ctxt,
2721         createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
2722         nullptr, nullptr, nullptr);
2723     ObjectKeeper keeper(projCRS);
2724     ASSERT_NE(projCRS, nullptr);
2725 
2726     auto newGeodCRS = proj_create(m_ctxt, "+proj=longlat +type=crs");
2727     ObjectKeeper keeper_newGeodCRS(newGeodCRS);
2728     ASSERT_NE(newGeodCRS, nullptr);
2729 
2730     auto geodCRS = proj_crs_get_geodetic_crs(m_ctxt, projCRS);
2731     ObjectKeeper keeper_geodCRS(geodCRS);
2732     ASSERT_NE(geodCRS, nullptr);
2733 
2734     auto geodCRSAltered =
2735         proj_crs_alter_geodetic_crs(m_ctxt, geodCRS, newGeodCRS);
2736     ObjectKeeper keeper_geodCRSAltered(geodCRSAltered);
2737     ASSERT_NE(geodCRSAltered, nullptr);
2738     EXPECT_TRUE(
2739         proj_is_equivalent_to(geodCRSAltered, newGeodCRS, PJ_COMP_STRICT));
2740 
2741     {
2742         auto projCRSAltered =
2743             proj_crs_alter_geodetic_crs(m_ctxt, projCRS, newGeodCRS);
2744         ObjectKeeper keeper_projCRSAltered(projCRSAltered);
2745         ASSERT_NE(projCRSAltered, nullptr);
2746 
2747         EXPECT_EQ(proj_get_type(projCRSAltered), PJ_TYPE_PROJECTED_CRS);
2748 
2749         auto projCRSAltered_geodCRS =
2750             proj_crs_get_geodetic_crs(m_ctxt, projCRSAltered);
2751         ObjectKeeper keeper_projCRSAltered_geodCRS(projCRSAltered_geodCRS);
2752         ASSERT_NE(projCRSAltered_geodCRS, nullptr);
2753 
2754         EXPECT_TRUE(proj_is_equivalent_to(projCRSAltered_geodCRS, newGeodCRS,
2755                                           PJ_COMP_STRICT));
2756     }
2757 
2758     // Check that proj_crs_alter_geodetic_crs preserves deprecation flag
2759     {
2760         auto projCRSDeprecated =
2761             proj_alter_name(m_ctxt, projCRS, "new name (deprecated)");
2762         ObjectKeeper keeper_projCRSDeprecated(projCRSDeprecated);
2763         ASSERT_NE(projCRSDeprecated, nullptr);
2764 
2765         auto projCRSAltered =
2766             proj_crs_alter_geodetic_crs(m_ctxt, projCRSDeprecated, newGeodCRS);
2767         ObjectKeeper keeper_projCRSAltered(projCRSAltered);
2768         ASSERT_NE(projCRSAltered, nullptr);
2769 
2770         EXPECT_EQ(proj_get_name(projCRSAltered), std::string("new name"));
2771         EXPECT_TRUE(proj_is_deprecated(projCRSAltered));
2772     }
2773 }
2774 
2775 // ---------------------------------------------------------------------------
2776 
TEST_F(CApi,proj_crs_alter_cs_angular_unit)2777 TEST_F(CApi, proj_crs_alter_cs_angular_unit) {
2778     auto crs = proj_create_from_wkt(
2779         m_ctxt,
2780         GeographicCRS::EPSG_4326->exportToWKT(WKTFormatter::create().get())
2781             .c_str(),
2782         nullptr, nullptr, nullptr);
2783     ObjectKeeper keeper(crs);
2784     ASSERT_NE(crs, nullptr);
2785 
2786     {
2787         auto alteredCRS = proj_crs_alter_cs_angular_unit(m_ctxt, crs, "my unit",
2788                                                          2, nullptr, nullptr);
2789         ObjectKeeper keeper_alteredCRS(alteredCRS);
2790         ASSERT_NE(alteredCRS, nullptr);
2791 
2792         auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS);
2793         ASSERT_NE(cs, nullptr);
2794         ObjectKeeper keeperCs(cs);
2795         double unitConvFactor = 0.0;
2796         const char *unitName = nullptr;
2797 
2798         EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr,
2799                                           nullptr, &unitConvFactor, &unitName,
2800                                           nullptr, nullptr));
2801         ASSERT_NE(unitName, nullptr);
2802         EXPECT_EQ(unitConvFactor, 2) << unitConvFactor;
2803         EXPECT_EQ(std::string(unitName), "my unit");
2804     }
2805 
2806     {
2807         auto alteredCRS = proj_crs_alter_cs_angular_unit(
2808             m_ctxt, crs, "my unit", 2, "my auth", "my code");
2809         ObjectKeeper keeper_alteredCRS(alteredCRS);
2810         ASSERT_NE(alteredCRS, nullptr);
2811 
2812         auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS);
2813         ASSERT_NE(cs, nullptr);
2814         ObjectKeeper keeperCs(cs);
2815         double unitConvFactor = 0.0;
2816         const char *unitName = nullptr;
2817         const char *unitAuthName = nullptr;
2818         const char *unitCode = nullptr;
2819 
2820         EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr,
2821                                           nullptr, &unitConvFactor, &unitName,
2822                                           &unitAuthName, &unitCode));
2823         ASSERT_NE(unitName, nullptr);
2824         EXPECT_EQ(unitConvFactor, 2) << unitConvFactor;
2825         EXPECT_EQ(std::string(unitName), "my unit");
2826         ASSERT_NE(unitAuthName, nullptr);
2827         EXPECT_EQ(std::string(unitAuthName), "my auth");
2828         ASSERT_NE(unitCode, nullptr);
2829         EXPECT_EQ(std::string(unitCode), "my code");
2830     }
2831 }
2832 
2833 // ---------------------------------------------------------------------------
2834 
TEST_F(CApi,proj_crs_alter_cs_linear_unit)2835 TEST_F(CApi, proj_crs_alter_cs_linear_unit) {
2836     auto crs = proj_create_from_wkt(
2837         m_ctxt,
2838         createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
2839         nullptr, nullptr, nullptr);
2840     ObjectKeeper keeper(crs);
2841     ASSERT_NE(crs, nullptr);
2842 
2843     {
2844         auto alteredCRS = proj_crs_alter_cs_linear_unit(m_ctxt, crs, "my unit",
2845                                                         2, nullptr, nullptr);
2846         ObjectKeeper keeper_alteredCRS(alteredCRS);
2847         ASSERT_NE(alteredCRS, nullptr);
2848 
2849         auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS);
2850         ASSERT_NE(cs, nullptr);
2851         ObjectKeeper keeperCs(cs);
2852         double unitConvFactor = 0.0;
2853         const char *unitName = nullptr;
2854 
2855         EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr,
2856                                           nullptr, &unitConvFactor, &unitName,
2857                                           nullptr, nullptr));
2858         ASSERT_NE(unitName, nullptr);
2859         EXPECT_EQ(unitConvFactor, 2) << unitConvFactor;
2860         EXPECT_EQ(std::string(unitName), "my unit");
2861     }
2862 
2863     {
2864         auto alteredCRS = proj_crs_alter_cs_linear_unit(
2865             m_ctxt, crs, "my unit", 2, "my auth", "my code");
2866         ObjectKeeper keeper_alteredCRS(alteredCRS);
2867         ASSERT_NE(alteredCRS, nullptr);
2868 
2869         auto cs = proj_crs_get_coordinate_system(m_ctxt, alteredCRS);
2870         ASSERT_NE(cs, nullptr);
2871         ObjectKeeper keeperCs(cs);
2872         double unitConvFactor = 0.0;
2873         const char *unitName = nullptr;
2874         const char *unitAuthName = nullptr;
2875         const char *unitCode = nullptr;
2876 
2877         EXPECT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, nullptr, nullptr,
2878                                           nullptr, &unitConvFactor, &unitName,
2879                                           &unitAuthName, &unitCode));
2880         ASSERT_NE(unitName, nullptr);
2881         EXPECT_EQ(unitConvFactor, 2) << unitConvFactor;
2882         EXPECT_EQ(std::string(unitName), "my unit");
2883         ASSERT_NE(unitAuthName, nullptr);
2884         EXPECT_EQ(std::string(unitAuthName), "my auth");
2885         ASSERT_NE(unitCode, nullptr);
2886         EXPECT_EQ(std::string(unitCode), "my code");
2887     }
2888 }
2889 
2890 // ---------------------------------------------------------------------------
2891 
TEST_F(CApi,proj_crs_alter_parameters_linear_unit)2892 TEST_F(CApi, proj_crs_alter_parameters_linear_unit) {
2893     auto crs = proj_create_from_wkt(
2894         m_ctxt,
2895         createProjectedCRS()->exportToWKT(WKTFormatter::create().get()).c_str(),
2896         nullptr, nullptr, nullptr);
2897     ObjectKeeper keeper(crs);
2898     ASSERT_NE(crs, nullptr);
2899 
2900     {
2901         auto alteredCRS = proj_crs_alter_parameters_linear_unit(
2902             m_ctxt, crs, "my unit", 2, nullptr, nullptr, false);
2903         ObjectKeeper keeper_alteredCRS(alteredCRS);
2904         ASSERT_NE(alteredCRS, nullptr);
2905 
2906         auto wkt = proj_as_wkt(m_ctxt, alteredCRS, PJ_WKT2_2019, nullptr);
2907         ASSERT_NE(wkt, nullptr);
2908         EXPECT_TRUE(std::string(wkt).find("500000") != std::string::npos)
2909             << wkt;
2910         EXPECT_TRUE(std::string(wkt).find("\"my unit\",2") != std::string::npos)
2911             << wkt;
2912     }
2913 
2914     {
2915         auto alteredCRS = proj_crs_alter_parameters_linear_unit(
2916             m_ctxt, crs, "my unit", 2, nullptr, nullptr, true);
2917         ObjectKeeper keeper_alteredCRS(alteredCRS);
2918         ASSERT_NE(alteredCRS, nullptr);
2919 
2920         auto wkt = proj_as_wkt(m_ctxt, alteredCRS, PJ_WKT2_2019, nullptr);
2921         ASSERT_NE(wkt, nullptr);
2922         EXPECT_TRUE(std::string(wkt).find("250000") != std::string::npos)
2923             << wkt;
2924         EXPECT_TRUE(std::string(wkt).find("\"my unit\",2") != std::string::npos)
2925             << wkt;
2926     }
2927 }
2928 
2929 // ---------------------------------------------------------------------------
2930 
TEST_F(CApi,proj_create_engineering_crs)2931 TEST_F(CApi, proj_create_engineering_crs) {
2932 
2933     auto crs = proj_create_engineering_crs(m_ctxt, "name");
2934     ObjectKeeper keeper(crs);
2935     ASSERT_NE(crs, nullptr);
2936     auto wkt = proj_as_wkt(m_ctxt, crs, PJ_WKT1_GDAL, nullptr);
2937     ASSERT_NE(wkt, nullptr);
2938     EXPECT_EQ(std::string(wkt), "LOCAL_CS[\"name\",\n"
2939                                 "    UNIT[\"metre\",1,\n"
2940                                 "        AUTHORITY[\"EPSG\",\"9001\"]],\n"
2941                                 "    AXIS[\"Easting\",EAST],\n"
2942                                 "    AXIS[\"Northing\",NORTH]]")
2943         << wkt;
2944 }
2945 
2946 // ---------------------------------------------------------------------------
2947 
TEST_F(CApi,proj_alter_name)2948 TEST_F(CApi, proj_alter_name) {
2949 
2950     auto cs = proj_create_ellipsoidal_2D_cs(
2951         m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
2952     ObjectKeeper keeper_cs(cs);
2953     ASSERT_NE(cs, nullptr);
2954 
2955     auto obj = proj_create_geographic_crs(
2956         m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
2957         298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs);
2958     ObjectKeeper keeper(obj);
2959     ASSERT_NE(obj, nullptr);
2960 
2961     {
2962         auto alteredObj = proj_alter_name(m_ctxt, obj, "new name");
2963         ObjectKeeper keeper_alteredObj(alteredObj);
2964         ASSERT_NE(alteredObj, nullptr);
2965 
2966         EXPECT_EQ(std::string(proj_get_name(alteredObj)), "new name");
2967         EXPECT_FALSE(proj_is_deprecated(alteredObj));
2968     }
2969 
2970     {
2971         auto alteredObj = proj_alter_name(m_ctxt, obj, "new name (deprecated)");
2972         ObjectKeeper keeper_alteredObj(alteredObj);
2973         ASSERT_NE(alteredObj, nullptr);
2974 
2975         EXPECT_EQ(std::string(proj_get_name(alteredObj)), "new name");
2976         EXPECT_TRUE(proj_is_deprecated(alteredObj));
2977     }
2978 }
2979 
2980 // ---------------------------------------------------------------------------
2981 
TEST_F(CApi,proj_alter_id)2982 TEST_F(CApi, proj_alter_id) {
2983 
2984     auto cs = proj_create_ellipsoidal_2D_cs(
2985         m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
2986     ObjectKeeper keeper_cs(cs);
2987     ASSERT_NE(cs, nullptr);
2988 
2989     auto obj = proj_create_geographic_crs(
2990         m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
2991         298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, cs);
2992     ObjectKeeper keeper(obj);
2993     ASSERT_NE(obj, nullptr);
2994 
2995     auto alteredObj = proj_alter_id(m_ctxt, obj, "auth", "code");
2996     ObjectKeeper keeper_alteredObj(alteredObj);
2997     ASSERT_NE(alteredObj, nullptr);
2998 
2999     EXPECT_EQ(std::string(proj_get_id_auth_name(alteredObj, 0)), "auth");
3000     EXPECT_EQ(std::string(proj_get_id_code(alteredObj, 0)), "code");
3001 }
3002 
3003 // ---------------------------------------------------------------------------
3004 
TEST_F(CApi,proj_create_projected_crs)3005 TEST_F(CApi, proj_create_projected_crs) {
3006 
3007     PJ_PARAM_DESCRIPTION param;
3008     param.name = "param name";
3009     param.auth_name = nullptr;
3010     param.code = nullptr;
3011     param.value = 0.99;
3012     param.unit_name = nullptr;
3013     param.unit_conv_factor = 1.0;
3014     param.unit_type = PJ_UT_SCALE;
3015 
3016     auto conv = proj_create_conversion(m_ctxt, "conv", "conv auth", "conv code",
3017                                        "method", "method auth", "method code",
3018                                        1, &param);
3019     ObjectKeeper keeper_conv(conv);
3020     ASSERT_NE(conv, nullptr);
3021 
3022     auto geog_cs = proj_create_ellipsoidal_2D_cs(
3023         m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
3024     ObjectKeeper keeper_geog_cs(geog_cs);
3025     ASSERT_NE(geog_cs, nullptr);
3026 
3027     auto geogCRS = proj_create_geographic_crs(
3028         m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
3029         298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs);
3030     ObjectKeeper keeper_geogCRS(geogCRS);
3031     ASSERT_NE(geogCRS, nullptr);
3032 
3033     auto cs = proj_create_cartesian_2D_cs(m_ctxt, PJ_CART2D_EASTING_NORTHING,
3034                                           nullptr, 0);
3035     ObjectKeeper keeper_cs(cs);
3036     ASSERT_NE(cs, nullptr);
3037 
3038     auto projCRS =
3039         proj_create_projected_crs(m_ctxt, "my CRS", geogCRS, conv, cs);
3040     ObjectKeeper keeper_projCRS(projCRS);
3041     ASSERT_NE(projCRS, nullptr);
3042 }
3043 
3044 // ---------------------------------------------------------------------------
3045 
TEST_F(CApi,proj_create_transformation)3046 TEST_F(CApi, proj_create_transformation) {
3047 
3048     PJ_PARAM_DESCRIPTION param;
3049     param.name = "param name";
3050     param.auth_name = nullptr;
3051     param.code = nullptr;
3052     param.value = 0.99;
3053     param.unit_name = nullptr;
3054     param.unit_conv_factor = 1.0;
3055     param.unit_type = PJ_UT_SCALE;
3056 
3057     auto geog_cs = proj_create_ellipsoidal_2D_cs(
3058         m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
3059     ObjectKeeper keeper_geog_cs(geog_cs);
3060     ASSERT_NE(geog_cs, nullptr);
3061 
3062     auto source_crs = proj_create_geographic_crs(
3063         m_ctxt, "Source CRS", "World Geodetic System 1984", "WGS 84", 6378137,
3064         298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs);
3065     ObjectKeeper keeper_source_crs(source_crs);
3066     ASSERT_NE(source_crs, nullptr);
3067 
3068     auto target_crs = proj_create_geographic_crs(
3069         m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
3070         298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433, geog_cs);
3071     ObjectKeeper keeper_target_crs(target_crs);
3072     ASSERT_NE(target_crs, nullptr);
3073 
3074     auto interp_crs = proj_create_geographic_crs(
3075         m_ctxt, "Interpolation CRS", "World Geodetic System 1984", "WGS 84",
3076         6378137, 298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433,
3077         geog_cs);
3078     ObjectKeeper keeper_interp_crs(interp_crs);
3079     ASSERT_NE(interp_crs, nullptr);
3080 
3081     {
3082         auto transf = proj_create_transformation(
3083             m_ctxt, "transf", "transf auth", "transf code", source_crs,
3084             target_crs, interp_crs, "method", "method auth", "method code", 1,
3085             &param, 0);
3086         ObjectKeeper keeper_transf(transf);
3087         ASSERT_NE(transf, nullptr);
3088 
3089         EXPECT_EQ(proj_coordoperation_get_param_count(m_ctxt, transf), 1);
3090 
3091         auto got_source_crs = proj_get_source_crs(m_ctxt, transf);
3092         ObjectKeeper keeper_got_source_crs(got_source_crs);
3093         ASSERT_NE(got_source_crs, nullptr);
3094         EXPECT_TRUE(
3095             proj_is_equivalent_to(source_crs, got_source_crs, PJ_COMP_STRICT));
3096 
3097         auto got_target_crs = proj_get_target_crs(m_ctxt, transf);
3098         ObjectKeeper keeper_got_target_crs(got_target_crs);
3099         ASSERT_NE(got_target_crs, nullptr);
3100         EXPECT_TRUE(
3101             proj_is_equivalent_to(target_crs, got_target_crs, PJ_COMP_STRICT));
3102     }
3103 
3104     {
3105         auto transf = proj_create_transformation(
3106             m_ctxt, "transf", "transf auth", "transf code", source_crs,
3107             target_crs, nullptr, "method", "method auth", "method code", 1,
3108             &param, -1);
3109         ObjectKeeper keeper_transf(transf);
3110         ASSERT_NE(transf, nullptr);
3111     }
3112 }
3113 
3114 // ---------------------------------------------------------------------------
3115 
TEST_F(CApi,proj_create_compound_crs)3116 TEST_F(CApi, proj_create_compound_crs) {
3117 
3118     auto horiz_cs = proj_create_ellipsoidal_2D_cs(
3119         m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
3120     ObjectKeeper keeper_horiz_cs(horiz_cs);
3121     ASSERT_NE(horiz_cs, nullptr);
3122 
3123     auto horiz_crs = proj_create_geographic_crs(
3124         m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
3125         298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433,
3126         horiz_cs);
3127     ObjectKeeper keeper_horiz_crs(horiz_crs);
3128     ASSERT_NE(horiz_crs, nullptr);
3129 
3130     auto vert_crs = proj_create_vertical_crs(m_ctxt, "myVertCRS", "myVertDatum",
3131                                              nullptr, 0.0);
3132     ObjectKeeper keeper_vert_crs(vert_crs);
3133     ASSERT_NE(vert_crs, nullptr);
3134 
3135     EXPECT_EQ(proj_get_name(vert_crs), std::string("myVertCRS"));
3136 
3137     auto compound_crs =
3138         proj_create_compound_crs(m_ctxt, "myCompoundCRS", horiz_crs, vert_crs);
3139     ObjectKeeper keeper_compound_crss(compound_crs);
3140     ASSERT_NE(compound_crs, nullptr);
3141 
3142     EXPECT_EQ(proj_get_name(compound_crs), std::string("myCompoundCRS"));
3143 
3144     auto subcrs_horiz = proj_crs_get_sub_crs(m_ctxt, compound_crs, 0);
3145     ASSERT_NE(subcrs_horiz, nullptr);
3146     ObjectKeeper keeper_subcrs_horiz(subcrs_horiz);
3147     EXPECT_TRUE(proj_is_equivalent_to(subcrs_horiz, horiz_crs, PJ_COMP_STRICT));
3148 
3149     auto subcrs_vert = proj_crs_get_sub_crs(m_ctxt, compound_crs, 1);
3150     ASSERT_NE(subcrs_vert, nullptr);
3151     ObjectKeeper keeper_subcrs_vert(subcrs_vert);
3152     EXPECT_TRUE(proj_is_equivalent_to(subcrs_vert, vert_crs, PJ_COMP_STRICT));
3153 }
3154 
3155 // ---------------------------------------------------------------------------
3156 
TEST_F(CApi,proj_convert_conversion_to_other_method)3157 TEST_F(CApi, proj_convert_conversion_to_other_method) {
3158     {
3159         auto geog_cs = proj_create_ellipsoidal_2D_cs(
3160             m_ctxt, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0);
3161         ObjectKeeper keeper_geog_cs(geog_cs);
3162         ASSERT_NE(geog_cs, nullptr);
3163 
3164         auto geogCRS = proj_create_geographic_crs(
3165             m_ctxt, "WGS 84", "World Geodetic System 1984", "WGS 84", 6378137,
3166             298.257223563, "Greenwich", 0.0, "Degree", 0.0174532925199433,
3167             geog_cs);
3168         ObjectKeeper keeper_geogCRS(geogCRS);
3169         ASSERT_NE(geogCRS, nullptr);
3170 
3171         auto cs = proj_create_cartesian_2D_cs(
3172             m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
3173         ObjectKeeper keeper_cs(cs);
3174         ASSERT_NE(cs, nullptr);
3175 
3176         auto conv = proj_create_conversion_mercator_variant_a(
3177             m_ctxt, 0, 1, 0.99, 2, 3, "Degree", 0.0174532925199433, "Metre",
3178             1.0);
3179         ObjectKeeper keeper_conv(conv);
3180         ASSERT_NE(conv, nullptr);
3181 
3182         auto projCRS =
3183             proj_create_projected_crs(m_ctxt, "my CRS", geogCRS, conv, cs);
3184         ObjectKeeper keeper_projCRS(projCRS);
3185         ASSERT_NE(projCRS, nullptr);
3186 
3187         // Wrong object type
3188         EXPECT_EQ(
3189             proj_convert_conversion_to_other_method(
3190                 m_ctxt, projCRS, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, nullptr),
3191             nullptr);
3192 
3193         auto conv_in_proj = proj_crs_get_coordoperation(m_ctxt, projCRS);
3194         ObjectKeeper keeper_conv_in_proj(conv_in_proj);
3195         ASSERT_NE(conv_in_proj, nullptr);
3196 
3197         // 3rd and 4th argument both 0/null
3198         EXPECT_EQ(proj_convert_conversion_to_other_method(m_ctxt, conv_in_proj,
3199                                                           0, nullptr),
3200                   nullptr);
3201 
3202         auto new_conv = proj_convert_conversion_to_other_method(
3203             m_ctxt, conv_in_proj, EPSG_CODE_METHOD_MERCATOR_VARIANT_B, nullptr);
3204         ObjectKeeper keeper_new_conv(new_conv);
3205         ASSERT_NE(new_conv, nullptr);
3206 
3207         EXPECT_FALSE(
3208             proj_is_equivalent_to(new_conv, conv_in_proj, PJ_COMP_STRICT));
3209         EXPECT_TRUE(
3210             proj_is_equivalent_to(new_conv, conv_in_proj, PJ_COMP_EQUIVALENT));
3211 
3212         auto new_conv_from_name = proj_convert_conversion_to_other_method(
3213             m_ctxt, conv_in_proj, 0, EPSG_NAME_METHOD_MERCATOR_VARIANT_B);
3214         ObjectKeeper keeper_new_conv_from_name(new_conv_from_name);
3215         ASSERT_NE(new_conv_from_name, nullptr);
3216 
3217         EXPECT_TRUE(proj_is_equivalent_to(new_conv, new_conv_from_name,
3218                                           PJ_COMP_STRICT));
3219 
3220         auto new_conv_back = proj_convert_conversion_to_other_method(
3221             m_ctxt, conv_in_proj, 0, EPSG_NAME_METHOD_MERCATOR_VARIANT_A);
3222         ObjectKeeper keeper_new_conv_back(new_conv_back);
3223         ASSERT_NE(new_conv_back, nullptr);
3224 
3225         EXPECT_TRUE(
3226             proj_is_equivalent_to(conv_in_proj, new_conv_back, PJ_COMP_STRICT));
3227     }
3228 }
3229 
3230 // ---------------------------------------------------------------------------
3231 
TEST_F(CApi,proj_get_non_deprecated)3232 TEST_F(CApi, proj_get_non_deprecated) {
3233     auto crs = proj_create_from_database(m_ctxt, "EPSG", "4226",
3234                                          PJ_CATEGORY_CRS, false, nullptr);
3235     ObjectKeeper keeper(crs);
3236     ASSERT_NE(crs, nullptr);
3237 
3238     auto list = proj_get_non_deprecated(m_ctxt, crs);
3239     ASSERT_NE(list, nullptr);
3240     ObjListKeeper keeper_list(list);
3241     EXPECT_EQ(proj_list_get_count(list), 2);
3242 }
3243 
3244 // ---------------------------------------------------------------------------
3245 
TEST_F(CApi,proj_query_geodetic_crs_from_datum)3246 TEST_F(CApi, proj_query_geodetic_crs_from_datum) {
3247     {
3248         auto list = proj_query_geodetic_crs_from_datum(m_ctxt, nullptr, "EPSG",
3249                                                        "6326", nullptr);
3250         ASSERT_NE(list, nullptr);
3251         ObjListKeeper keeper_list(list);
3252         EXPECT_GE(proj_list_get_count(list), 3);
3253     }
3254     {
3255         auto list = proj_query_geodetic_crs_from_datum(m_ctxt, "EPSG", "EPSG",
3256                                                        "6326", "geographic 2D");
3257         ASSERT_NE(list, nullptr);
3258         ObjListKeeper keeper_list(list);
3259         EXPECT_EQ(proj_list_get_count(list), 1);
3260     }
3261 }
3262 
3263 // ---------------------------------------------------------------------------
3264 
TEST_F(CApi,proj_uom_get_info_from_database)3265 TEST_F(CApi, proj_uom_get_info_from_database) {
3266     {
3267         EXPECT_FALSE(proj_uom_get_info_from_database(
3268             m_ctxt, "auth", "code", nullptr, nullptr, nullptr));
3269     }
3270     {
3271         EXPECT_TRUE(proj_uom_get_info_from_database(m_ctxt, "EPSG", "9001",
3272                                                     nullptr, nullptr, nullptr));
3273     }
3274     {
3275         const char *name = nullptr;
3276         double conv_factor = 0.0;
3277         const char *category = nullptr;
3278         EXPECT_TRUE(proj_uom_get_info_from_database(
3279             m_ctxt, "EPSG", "9001", &name, &conv_factor, &category));
3280         ASSERT_NE(name, nullptr);
3281         ASSERT_NE(category, nullptr);
3282         EXPECT_EQ(std::string(name), "metre");
3283         EXPECT_EQ(conv_factor, 1.0);
3284         EXPECT_EQ(std::string(category), "linear");
3285     }
3286     {
3287         const char *name = nullptr;
3288         double conv_factor = 0.0;
3289         const char *category = nullptr;
3290         EXPECT_TRUE(proj_uom_get_info_from_database(
3291             m_ctxt, "EPSG", "9102", &name, &conv_factor, &category));
3292         ASSERT_NE(name, nullptr);
3293         ASSERT_NE(category, nullptr);
3294         EXPECT_EQ(std::string(name), "degree");
3295         EXPECT_NEAR(conv_factor, UnitOfMeasure::DEGREE.conversionToSI(), 1e-10);
3296         EXPECT_EQ(std::string(category), "angular");
3297     }
3298 }
3299 
3300 // ---------------------------------------------------------------------------
3301 
TEST_F(CApi,proj_grid_get_info_from_database)3302 TEST_F(CApi, proj_grid_get_info_from_database) {
3303     {
3304         EXPECT_FALSE(proj_grid_get_info_from_database(m_ctxt, "xxx", nullptr,
3305                                                       nullptr, nullptr, nullptr,
3306                                                       nullptr, nullptr));
3307     }
3308     {
3309         EXPECT_TRUE(proj_grid_get_info_from_database(
3310             m_ctxt, "au_icsm_GDA94_GDA2020_conformal.tif", nullptr, nullptr,
3311             nullptr, nullptr, nullptr, nullptr));
3312     }
3313     {
3314         const char *full_name = nullptr;
3315         const char *package_name = nullptr;
3316         const char *url = nullptr;
3317         int direct_download = 0;
3318         int open_license = 0;
3319         int available = 0;
3320         EXPECT_TRUE(proj_grid_get_info_from_database(
3321             m_ctxt, "au_icsm_GDA94_GDA2020_conformal.tif", &full_name,
3322             &package_name, &url, &direct_download, &open_license, &available));
3323         ASSERT_NE(full_name, nullptr);
3324         // empty string expected as the file is not in test data
3325         EXPECT_TRUE(full_name[0] == 0);
3326         ASSERT_NE(package_name, nullptr);
3327         EXPECT_TRUE(package_name[0] == 0); // empty string expected
3328         ASSERT_NE(url, nullptr);
3329         EXPECT_EQ(std::string(url),
3330                   "https://cdn.proj.org/au_icsm_GDA94_GDA2020_conformal.tif");
3331         EXPECT_EQ(direct_download, 1);
3332         EXPECT_EQ(open_license, 1);
3333     }
3334     // Same test as above, but with PROJ 6 grid name
3335     {
3336         const char *full_name = nullptr;
3337         const char *package_name = nullptr;
3338         const char *url = nullptr;
3339         int direct_download = 0;
3340         int open_license = 0;
3341         int available = 0;
3342         EXPECT_TRUE(proj_grid_get_info_from_database(
3343             m_ctxt, "GDA94_GDA2020_conformal.gsb", &full_name, &package_name,
3344             &url, &direct_download, &open_license, &available));
3345         ASSERT_NE(full_name, nullptr);
3346         // empty string expected as the file is not in test data
3347         EXPECT_TRUE(full_name[0] == 0);
3348         ASSERT_NE(package_name, nullptr);
3349         EXPECT_TRUE(package_name[0] == 0); // empty string expected
3350         ASSERT_NE(url, nullptr);
3351         EXPECT_EQ(std::string(url),
3352                   "https://cdn.proj.org/au_icsm_GDA94_GDA2020_conformal.tif");
3353         EXPECT_EQ(direct_download, 1);
3354         EXPECT_EQ(open_license, 1);
3355     }
3356 }
3357 
3358 // ---------------------------------------------------------------------------
3359 
TEST_F(CApi,proj_create_cartesian_2D_cs)3360 TEST_F(CApi, proj_create_cartesian_2D_cs) {
3361     {
3362         auto cs = proj_create_cartesian_2D_cs(
3363             m_ctxt, PJ_CART2D_EASTING_NORTHING, nullptr, 0);
3364         ObjectKeeper keeper_cs(cs);
3365         ASSERT_NE(cs, nullptr);
3366     }
3367     {
3368         auto cs = proj_create_cartesian_2D_cs(
3369             m_ctxt, PJ_CART2D_NORTHING_EASTING, nullptr, 0);
3370         ObjectKeeper keeper_cs(cs);
3371         ASSERT_NE(cs, nullptr);
3372     }
3373     {
3374         auto cs = proj_create_cartesian_2D_cs(
3375             m_ctxt, PJ_CART2D_NORTH_POLE_EASTING_SOUTH_NORTHING_SOUTH, nullptr,
3376             0);
3377         ObjectKeeper keeper_cs(cs);
3378         ASSERT_NE(cs, nullptr);
3379     }
3380     {
3381         auto cs = proj_create_cartesian_2D_cs(
3382             m_ctxt, PJ_CART2D_SOUTH_POLE_EASTING_NORTH_NORTHING_NORTH, nullptr,
3383             0);
3384         ObjectKeeper keeper_cs(cs);
3385         ASSERT_NE(cs, nullptr);
3386     }
3387     {
3388         auto cs = proj_create_cartesian_2D_cs(
3389             m_ctxt, PJ_CART2D_WESTING_SOUTHING, nullptr, 0);
3390         ObjectKeeper keeper_cs(cs);
3391         ASSERT_NE(cs, nullptr);
3392     }
3393 }
3394 
3395 // ---------------------------------------------------------------------------
3396 
TEST_F(CApi,proj_get_crs_info_list_from_database)3397 TEST_F(CApi, proj_get_crs_info_list_from_database) {
3398     { proj_crs_info_list_destroy(nullptr); }
3399 
3400     { proj_get_crs_list_parameters_destroy(nullptr); }
3401 
3402     // All null parameters
3403     {
3404         auto list = proj_get_crs_info_list_from_database(nullptr, nullptr,
3405                                                          nullptr, nullptr);
3406         ASSERT_NE(list, nullptr);
3407         ASSERT_NE(list[0], nullptr);
3408         EXPECT_NE(list[0]->auth_name, nullptr);
3409         EXPECT_NE(list[0]->code, nullptr);
3410         EXPECT_NE(list[0]->name, nullptr);
3411         proj_crs_info_list_destroy(list);
3412     }
3413 
3414     // Default parameters
3415     {
3416         int result_count = 0;
3417         auto params = proj_get_crs_list_parameters_create();
3418         auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params,
3419                                                          &result_count);
3420         proj_get_crs_list_parameters_destroy(params);
3421         ASSERT_NE(list, nullptr);
3422         EXPECT_GT(result_count, 1);
3423         EXPECT_EQ(list[result_count], nullptr);
3424         bool found4326 = false;
3425         bool found4978 = false;
3426         bool found4979 = false;
3427         bool found32631 = false;
3428         bool found3855 = false;
3429         bool found3901 = false;
3430         for (int i = 0; i < result_count; i++) {
3431             auto code = std::string(list[i]->code);
3432             if (code == "4326") {
3433                 found4326 = true;
3434                 EXPECT_EQ(std::string(list[i]->auth_name), "EPSG");
3435                 EXPECT_EQ(std::string(list[i]->name), "WGS 84");
3436                 EXPECT_EQ(list[i]->type, PJ_TYPE_GEOGRAPHIC_2D_CRS);
3437                 EXPECT_EQ(list[i]->deprecated, 0);
3438                 EXPECT_EQ(list[i]->bbox_valid, 1);
3439                 EXPECT_EQ(list[i]->west_lon_degree, -180.0);
3440                 EXPECT_EQ(list[i]->south_lat_degree, -90.0);
3441                 EXPECT_EQ(list[i]->east_lon_degree, 180.0);
3442                 EXPECT_EQ(list[i]->north_lat_degree, 90.0);
3443                 EXPECT_EQ(std::string(list[i]->area_name), "World.");
3444                 EXPECT_EQ(list[i]->projection_method_name, nullptr);
3445             } else if (code == "4978") {
3446                 found4978 = true;
3447                 EXPECT_EQ(list[i]->type, PJ_TYPE_GEOCENTRIC_CRS);
3448             } else if (code == "4979") {
3449                 found4979 = true;
3450                 EXPECT_EQ(list[i]->type, PJ_TYPE_GEOGRAPHIC_3D_CRS);
3451             } else if (code == "32631") {
3452                 found32631 = true;
3453                 EXPECT_EQ(list[i]->type, PJ_TYPE_PROJECTED_CRS);
3454                 EXPECT_EQ(std::string(list[i]->projection_method_name),
3455                           "Transverse Mercator");
3456             } else if (code == "3855") {
3457                 found3855 = true;
3458                 EXPECT_EQ(list[i]->type, PJ_TYPE_VERTICAL_CRS);
3459             } else if (code == "3901") {
3460                 found3901 = true;
3461                 EXPECT_EQ(list[i]->type, PJ_TYPE_COMPOUND_CRS);
3462             }
3463             EXPECT_EQ(list[i]->deprecated, 0);
3464         }
3465         EXPECT_TRUE(found4326);
3466         EXPECT_TRUE(found4978);
3467         EXPECT_TRUE(found4979);
3468         EXPECT_TRUE(found32631);
3469         EXPECT_TRUE(found3855);
3470         EXPECT_TRUE(found3901);
3471         proj_crs_info_list_destroy(list);
3472     }
3473 
3474     // Filter on only geodetic crs
3475     {
3476         int result_count = 0;
3477         auto params = proj_get_crs_list_parameters_create();
3478         params->typesCount = 1;
3479         auto type = PJ_TYPE_GEODETIC_CRS;
3480         params->types = &type;
3481         auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params,
3482                                                          &result_count);
3483         bool foundGeog2D = false;
3484         bool foundGeog3D = false;
3485         bool foundGeocentric = false;
3486         for (int i = 0; i < result_count; i++) {
3487             foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS;
3488             foundGeog3D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
3489             foundGeocentric |= list[i]->type == PJ_TYPE_GEOCENTRIC_CRS;
3490             EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
3491                         list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS ||
3492                         list[i]->type == PJ_TYPE_GEOCENTRIC_CRS);
3493         }
3494         EXPECT_TRUE(foundGeog2D);
3495         EXPECT_TRUE(foundGeog3D);
3496         EXPECT_TRUE(foundGeocentric);
3497         proj_get_crs_list_parameters_destroy(params);
3498         proj_crs_info_list_destroy(list);
3499     }
3500 
3501     // Filter on only geographic crs
3502     {
3503         int result_count = 0;
3504         auto params = proj_get_crs_list_parameters_create();
3505         params->typesCount = 1;
3506         auto type = PJ_TYPE_GEOGRAPHIC_CRS;
3507         params->types = &type;
3508         auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params,
3509                                                          &result_count);
3510         bool foundGeog2D = false;
3511         bool foundGeog3D = false;
3512         for (int i = 0; i < result_count; i++) {
3513             foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS;
3514             foundGeog3D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS;
3515             EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
3516                         list[i]->type == PJ_TYPE_GEOGRAPHIC_3D_CRS);
3517         }
3518         EXPECT_TRUE(foundGeog2D);
3519         EXPECT_TRUE(foundGeog3D);
3520         proj_get_crs_list_parameters_destroy(params);
3521         proj_crs_info_list_destroy(list);
3522     }
3523 
3524     // Filter on only geographic 2D crs and projected CRS
3525     {
3526         int result_count = 0;
3527         auto params = proj_get_crs_list_parameters_create();
3528         params->typesCount = 2;
3529         const PJ_TYPE types[] = {PJ_TYPE_GEOGRAPHIC_2D_CRS,
3530                                  PJ_TYPE_PROJECTED_CRS};
3531         params->types = types;
3532         auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params,
3533                                                          &result_count);
3534         bool foundGeog2D = false;
3535         bool foundProjected = false;
3536         for (int i = 0; i < result_count; i++) {
3537             foundGeog2D |= list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS;
3538             foundProjected |= list[i]->type == PJ_TYPE_PROJECTED_CRS;
3539             EXPECT_TRUE(list[i]->type == PJ_TYPE_GEOGRAPHIC_2D_CRS ||
3540                         list[i]->type == PJ_TYPE_PROJECTED_CRS);
3541         }
3542         EXPECT_TRUE(foundGeog2D);
3543         EXPECT_TRUE(foundProjected);
3544         proj_get_crs_list_parameters_destroy(params);
3545         proj_crs_info_list_destroy(list);
3546     }
3547 
3548     // Filter on bbox (inclusion)
3549     {
3550         int result_count = 0;
3551         auto params = proj_get_crs_list_parameters_create();
3552         params->bbox_valid = 1;
3553         params->west_lon_degree = 2;
3554         params->south_lat_degree = 49;
3555         params->east_lon_degree = 2.1;
3556         params->north_lat_degree = 49.1;
3557         params->typesCount = 1;
3558         auto type = PJ_TYPE_PROJECTED_CRS;
3559         params->types = &type;
3560         auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params,
3561                                                          &result_count);
3562         ASSERT_NE(list, nullptr);
3563         EXPECT_GT(result_count, 1);
3564         for (int i = 0; i < result_count; i++) {
3565             if (list[i]->west_lon_degree < list[i]->east_lon_degree) {
3566                 EXPECT_LE(list[i]->west_lon_degree, params->west_lon_degree);
3567                 EXPECT_GE(list[i]->east_lon_degree, params->east_lon_degree);
3568             }
3569             EXPECT_LE(list[i]->south_lat_degree, params->south_lat_degree);
3570             EXPECT_GE(list[i]->north_lat_degree, params->north_lat_degree);
3571         }
3572         proj_get_crs_list_parameters_destroy(params);
3573         proj_crs_info_list_destroy(list);
3574     }
3575 
3576     // Filter on bbox (intersection)
3577     {
3578         int result_count = 0;
3579         auto params = proj_get_crs_list_parameters_create();
3580         params->bbox_valid = 1;
3581         params->west_lon_degree = 2;
3582         params->south_lat_degree = 49;
3583         params->east_lon_degree = 2.1;
3584         params->north_lat_degree = 49.1;
3585         params->crs_area_of_use_contains_bbox = 0;
3586         params->typesCount = 1;
3587         auto type = PJ_TYPE_PROJECTED_CRS;
3588         params->types = &type;
3589         auto list = proj_get_crs_info_list_from_database(m_ctxt, "EPSG", params,
3590                                                          &result_count);
3591         ASSERT_NE(list, nullptr);
3592         EXPECT_GT(result_count, 1);
3593         for (int i = 0; i < result_count; i++) {
3594             if (list[i]->west_lon_degree < list[i]->east_lon_degree) {
3595                 EXPECT_LE(list[i]->west_lon_degree, params->west_lon_degree);
3596                 EXPECT_GE(list[i]->east_lon_degree, params->east_lon_degree);
3597             }
3598             EXPECT_LE(list[i]->south_lat_degree, params->north_lat_degree);
3599             EXPECT_GE(list[i]->north_lat_degree, params->south_lat_degree);
3600         }
3601         proj_get_crs_list_parameters_destroy(params);
3602         proj_crs_info_list_destroy(list);
3603     }
3604 }
3605 
3606 // ---------------------------------------------------------------------------
3607 
TEST_F(CApi,proj_get_units_from_database)3608 TEST_F(CApi, proj_get_units_from_database) {
3609     { proj_unit_list_destroy(nullptr); }
3610 
3611     {
3612         auto list = proj_get_units_from_database(nullptr, nullptr, nullptr,
3613                                                  true, nullptr);
3614         ASSERT_NE(list, nullptr);
3615         ASSERT_NE(list[0], nullptr);
3616         ASSERT_NE(list[0]->auth_name, nullptr);
3617         ASSERT_NE(list[0]->code, nullptr);
3618         ASSERT_NE(list[0]->name, nullptr);
3619         proj_unit_list_destroy(list);
3620     }
3621 
3622     {
3623         int result_count = 0;
3624         auto list = proj_get_units_from_database(nullptr, "EPSG", "linear",
3625                                                  false, &result_count);
3626         ASSERT_NE(list, nullptr);
3627         EXPECT_GT(result_count, 1);
3628         EXPECT_EQ(list[result_count], nullptr);
3629         bool found9001 = false;
3630         for (int i = 0; i < result_count; i++) {
3631             EXPECT_EQ(std::string(list[i]->auth_name), "EPSG");
3632             if (std::string(list[i]->code) == "9001") {
3633                 EXPECT_EQ(std::string(list[i]->name), "metre");
3634                 EXPECT_EQ(std::string(list[i]->category), "linear");
3635                 EXPECT_EQ(list[i]->conv_factor, 1.0);
3636                 ASSERT_NE(list[i]->proj_short_name, nullptr);
3637                 EXPECT_EQ(std::string(list[i]->proj_short_name), "m");
3638                 EXPECT_EQ(list[i]->deprecated, 0);
3639                 found9001 = true;
3640             }
3641             EXPECT_EQ(list[i]->deprecated, 0);
3642         }
3643         EXPECT_TRUE(found9001);
3644         proj_unit_list_destroy(list);
3645     }
3646 }
3647 
3648 // ---------------------------------------------------------------------------
3649 
TEST_F(CApi,proj_normalize_for_visualization)3650 TEST_F(CApi, proj_normalize_for_visualization) {
3651 
3652     {
3653         auto P = proj_create(m_ctxt, "+proj=utm +zone=31 +ellps=WGS84");
3654         ObjectKeeper keeper_P(P);
3655         ASSERT_NE(P, nullptr);
3656         auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
3657         ObjectKeeper keeper_Pnormalized(Pnormalized);
3658         EXPECT_EQ(Pnormalized, nullptr);
3659     }
3660 
3661     auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "EPSG:32631", nullptr);
3662     ObjectKeeper keeper_P(P);
3663     ASSERT_NE(P, nullptr);
3664     auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
3665     ObjectKeeper keeper_Pnormalized(Pnormalized);
3666     ASSERT_NE(Pnormalized, nullptr);
3667     auto projstr = proj_as_proj_string(m_ctxt, Pnormalized, PJ_PROJ_5, nullptr);
3668     ASSERT_NE(projstr, nullptr);
3669     EXPECT_EQ(std::string(projstr),
3670               "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
3671               "+step +proj=utm +zone=31 +ellps=WGS84");
3672 }
3673 
3674 // ---------------------------------------------------------------------------
3675 
TEST_F(CApi,proj_normalize_for_visualization_with_alternatives)3676 TEST_F(CApi, proj_normalize_for_visualization_with_alternatives) {
3677 
3678     auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:4326", "EPSG:3003", nullptr);
3679     ObjectKeeper keeper_P(P);
3680     ASSERT_NE(P, nullptr);
3681     auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
3682     ObjectKeeper keeper_Pnormalized(Pnormalized);
3683     ASSERT_NE(Pnormalized, nullptr);
3684 
3685     {
3686         PJ_COORD c;
3687         // Approximately Roma
3688         c.lpz.lam = 12.5;
3689         c.lpz.phi = 42;
3690         c.lpz.z = 0;
3691         c = proj_trans(Pnormalized, PJ_FWD, c);
3692         EXPECT_NEAR(c.xy.x, 1789912.46264783037, 1e-8);
3693         EXPECT_NEAR(c.xy.y, 4655716.25402576849, 1e-8);
3694         auto projstr = proj_pj_info(Pnormalized).definition;
3695         ASSERT_NE(projstr, nullptr);
3696         EXPECT_EQ(std::string(projstr),
3697                   "proj=pipeline step proj=unitconvert xy_in=deg xy_out=rad "
3698                   "step proj=push v_3 step proj=cart ellps=WGS84 "
3699                   "step inv proj=helmert x=-104.1 y=-49.1 z=-9.9 rx=0.971 "
3700                   "ry=-2.917 rz=0.714 s=-11.68 convention=position_vector "
3701                   "step inv proj=cart ellps=intl step proj=pop v_3 "
3702                   "step proj=tmerc lat_0=0 lon_0=9 k=0.9996 x_0=1500000 "
3703                   "y_0=0 ellps=intl");
3704     }
3705 
3706     {
3707         PJ_COORD c;
3708         // Approximately Roma
3709         c.xyz.x = 1789912.46264783037;
3710         c.xyz.y = 4655716.25402576849;
3711         c.xyz.z = 0;
3712         c = proj_trans(Pnormalized, PJ_INV, c);
3713         EXPECT_NEAR(c.lp.lam, 12.5, 1e-8);
3714         EXPECT_NEAR(c.lp.phi, 42, 1e-8);
3715     }
3716 }
3717 
3718 // ---------------------------------------------------------------------------
3719 
TEST_F(CApi,proj_normalize_for_visualization_with_alternatives_reverse)3720 TEST_F(CApi, proj_normalize_for_visualization_with_alternatives_reverse) {
3721 
3722     auto P = proj_create_crs_to_crs(m_ctxt, "EPSG:3003", "EPSG:4326", nullptr);
3723     ObjectKeeper keeper_P(P);
3724     ASSERT_NE(P, nullptr);
3725     auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
3726     ObjectKeeper keeper_Pnormalized(Pnormalized);
3727     ASSERT_NE(Pnormalized, nullptr);
3728 
3729     PJ_COORD c;
3730     // Approximately Roma
3731     c.xyz.x = 1789912.46264783037;
3732     c.xyz.y = 4655716.25402576849;
3733     c.xyz.z = 0;
3734     c = proj_trans(Pnormalized, PJ_FWD, c);
3735     EXPECT_NEAR(c.lp.lam, 12.5, 1e-8);
3736     EXPECT_NEAR(c.lp.phi, 42, 1e-8);
3737 }
3738 
3739 // ---------------------------------------------------------------------------
3740 
TEST_F(CApi,proj_normalize_for_visualization_on_crs)3741 TEST_F(CApi, proj_normalize_for_visualization_on_crs) {
3742 
3743     auto P = proj_create(m_ctxt, "EPSG:4326");
3744     ObjectKeeper keeper_P(P);
3745     ASSERT_NE(P, nullptr);
3746     auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
3747     ObjectKeeper keeper_Pnormalized(Pnormalized);
3748     ASSERT_NE(Pnormalized, nullptr);
3749     EXPECT_EQ(proj_get_id_code(Pnormalized, 0), nullptr);
3750 
3751     auto cs = proj_crs_get_coordinate_system(m_ctxt, Pnormalized);
3752     ASSERT_NE(cs, nullptr);
3753     ObjectKeeper keeperCs(cs);
3754 
3755     const char *name = nullptr;
3756     ASSERT_TRUE(proj_cs_get_axis_info(m_ctxt, cs, 0, &name, nullptr, nullptr,
3757                                       nullptr, nullptr, nullptr, nullptr));
3758     ASSERT_NE(name, nullptr);
3759     EXPECT_EQ(std::string(name), "Geodetic longitude");
3760 }
3761 
3762 // ---------------------------------------------------------------------------
3763 
TEST_F(CApi,proj_coordoperation_create_inverse)3764 TEST_F(CApi, proj_coordoperation_create_inverse) {
3765 
3766     auto P = proj_create(
3767         m_ctxt, "+proj=pipeline +step +proj=axisswap +order=2,1 +step "
3768                 "+proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push "
3769                 "+v_3 +step +proj=cart +ellps=evrst30 +step +proj=helmert "
3770                 "+x=293 +y=836 +z=318 +rx=0.5 +ry=1.6 +rz=-2.8 +s=2.1 "
3771                 "+convention=position_vector +step +inv +proj=cart "
3772                 "+ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert "
3773                 "+xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1");
3774     ObjectKeeper keeper_P(P);
3775     ASSERT_NE(P, nullptr);
3776     auto Pinversed = proj_coordoperation_create_inverse(m_ctxt, P);
3777     ObjectKeeper keeper_Pinversed(Pinversed);
3778     ASSERT_NE(Pinversed, nullptr);
3779 
3780     const char *options[] = {"MULTILINE=YES", "INDENTATION_WIDTH=4",
3781                              "MAX_LINE_LENGTH=40", nullptr};
3782     auto projstr = proj_as_proj_string(m_ctxt, Pinversed, PJ_PROJ_5, options);
3783     ASSERT_NE(projstr, nullptr);
3784     const char *expected_projstr = "+proj=pipeline\n"
3785                                    "    +step +proj=axisswap +order=2,1\n"
3786                                    "    +step +proj=unitconvert +xy_in=deg\n"
3787                                    "          +xy_out=rad\n"
3788                                    "    +step +proj=push +v_3\n"
3789                                    "    +step +proj=cart +ellps=WGS84\n"
3790                                    "    +step +inv +proj=helmert +x=293\n"
3791                                    "          +y=836 +z=318 +rx=0.5 +ry=1.6\n"
3792                                    "          +rz=-2.8 +s=2.1\n"
3793                                    "          +convention=position_vector\n"
3794                                    "    +step +inv +proj=cart +ellps=evrst30\n"
3795                                    "    +step +proj=pop +v_3\n"
3796                                    "    +step +proj=unitconvert +xy_in=rad\n"
3797                                    "          +xy_out=deg\n"
3798                                    "    +step +proj=axisswap +order=2,1";
3799     EXPECT_EQ(std::string(projstr), expected_projstr);
3800 }
3801 
3802 // ---------------------------------------------------------------------------
3803 
TEST_F(CApi,proj_get_remarks)3804 TEST_F(CApi, proj_get_remarks) {
3805     // Transformation
3806     {
3807         auto co = proj_create_from_database(m_ctxt, "EPSG", "8048",
3808                                             PJ_CATEGORY_COORDINATE_OPERATION,
3809                                             false, nullptr);
3810         ObjectKeeper keeper(co);
3811         ASSERT_NE(co, nullptr);
3812 
3813         auto remarks = proj_get_remarks(co);
3814         ASSERT_NE(remarks, nullptr);
3815         EXPECT_TRUE(std::string(remarks).find(
3816                         "Scale difference in ppb where 1/billion = 1E-9.") == 0)
3817             << remarks;
3818     }
3819 
3820     // Conversion
3821     {
3822         auto co = proj_create_from_database(m_ctxt, "EPSG", "3811",
3823                                             PJ_CATEGORY_COORDINATE_OPERATION,
3824                                             false, nullptr);
3825         ObjectKeeper keeper(co);
3826         ASSERT_NE(co, nullptr);
3827 
3828         auto remarks = proj_get_remarks(co);
3829         ASSERT_NE(remarks, nullptr);
3830         EXPECT_EQ(remarks, std::string("Replaces Lambert 2005."));
3831     }
3832 }
3833 
3834 // ---------------------------------------------------------------------------
3835 
TEST_F(CApi,proj_get_scope)3836 TEST_F(CApi, proj_get_scope) {
3837     // Transformation
3838     {
3839         auto co = proj_create_from_database(m_ctxt, "EPSG", "8048",
3840                                             PJ_CATEGORY_COORDINATE_OPERATION,
3841                                             false, nullptr);
3842         ObjectKeeper keeper(co);
3843         ASSERT_NE(co, nullptr);
3844 
3845         auto scope = proj_get_scope(co);
3846         ASSERT_NE(scope, nullptr);
3847         EXPECT_EQ(scope,
3848                   std::string("Transformation of GDA94 coordinates that have "
3849                               "been derived through GNSS CORS."));
3850     }
3851 
3852     // Conversion
3853     {
3854         auto co = proj_create_from_database(m_ctxt, "EPSG", "3811",
3855                                             PJ_CATEGORY_COORDINATE_OPERATION,
3856                                             false, nullptr);
3857         ObjectKeeper keeper(co);
3858         ASSERT_NE(co, nullptr);
3859 
3860         auto scope = proj_get_scope(co);
3861         ASSERT_NE(scope, nullptr);
3862         EXPECT_EQ(scope,
3863                   std::string("Engineering survey, topographic mapping."));
3864     }
3865 
3866     {
3867         auto P = proj_create(m_ctxt, "+proj=noop");
3868         ObjectKeeper keeper(P);
3869         ASSERT_NE(P, nullptr);
3870         auto scope = proj_get_scope(P);
3871         ASSERT_EQ(scope, nullptr);
3872     }
3873 }
3874 
3875 // ---------------------------------------------------------------------------
3876 
TEST_F(CApi,proj_concatoperation_get_step)3877 TEST_F(CApi, proj_concatoperation_get_step) {
3878     // Test on a non concatenated operation
3879     {
3880         auto co = proj_create_from_database(m_ctxt, "EPSG", "8048",
3881                                             PJ_CATEGORY_COORDINATE_OPERATION,
3882                                             false, nullptr);
3883         ObjectKeeper keeper(co);
3884         ASSERT_NE(co, nullptr);
3885         ASSERT_NE(proj_get_type(co), PJ_TYPE_CONCATENATED_OPERATION);
3886 
3887         ASSERT_EQ(proj_concatoperation_get_step_count(m_ctxt, co), 0);
3888         ASSERT_EQ(proj_concatoperation_get_step(m_ctxt, co, 0), nullptr);
3889     }
3890     // Test on a concatenated operation
3891     {
3892         auto ctxt = proj_create_operation_factory_context(m_ctxt, nullptr);
3893         ASSERT_NE(ctxt, nullptr);
3894         ContextKeeper keeper_ctxt(ctxt);
3895 
3896         // GDA94 / MGA zone 56
3897         auto source_crs = proj_create_from_database(
3898             m_ctxt, "EPSG", "28356", PJ_CATEGORY_CRS, false, nullptr);
3899         ASSERT_NE(source_crs, nullptr);
3900         ObjectKeeper keeper_source_crs(source_crs);
3901 
3902         // GDA2020 / MGA zone 56
3903         auto target_crs = proj_create_from_database(
3904             m_ctxt, "EPSG", "7856", PJ_CATEGORY_CRS, false, nullptr);
3905         ASSERT_NE(target_crs, nullptr);
3906         ObjectKeeper keeper_target_crs(target_crs);
3907 
3908         proj_operation_factory_context_set_spatial_criterion(
3909             m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
3910 
3911         proj_operation_factory_context_set_grid_availability_use(
3912             m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
3913 
3914         auto res = proj_create_operations(m_ctxt, source_crs, target_crs, ctxt);
3915         ASSERT_NE(res, nullptr);
3916         ObjListKeeper keeper_res(res);
3917 
3918         ASSERT_GT(proj_list_get_count(res), 0);
3919 
3920         auto op = proj_list_get(m_ctxt, res, 0);
3921         ASSERT_NE(op, nullptr);
3922         ObjectKeeper keeper_op(op);
3923 
3924         ASSERT_EQ(proj_get_type(op), PJ_TYPE_CONCATENATED_OPERATION);
3925         ASSERT_EQ(proj_concatoperation_get_step_count(m_ctxt, op), 3);
3926 
3927         EXPECT_EQ(proj_concatoperation_get_step(m_ctxt, op, -1), nullptr);
3928         EXPECT_EQ(proj_concatoperation_get_step(m_ctxt, op, 3), nullptr);
3929 
3930         auto step = proj_concatoperation_get_step(m_ctxt, op, 1);
3931         ASSERT_NE(step, nullptr);
3932         ObjectKeeper keeper_step(step);
3933 
3934         const char *scope = proj_get_scope(step);
3935         EXPECT_NE(scope, nullptr);
3936         EXPECT_NE(std::string(scope), std::string());
3937 
3938         const char *remarks = proj_get_remarks(step);
3939         EXPECT_NE(remarks, nullptr);
3940         EXPECT_NE(std::string(remarks), std::string());
3941     }
3942 }
3943 
3944 // ---------------------------------------------------------------------------
3945 
TEST_F(CApi,proj_as_projjson)3946 TEST_F(CApi, proj_as_projjson) {
3947     auto obj = proj_create(
3948         m_ctxt,
3949         Ellipsoid::WGS84->exportToJSON(JSONFormatter::create().get()).c_str());
3950     ObjectKeeper keeper(obj);
3951     ASSERT_NE(obj, nullptr);
3952 
3953     {
3954         auto projjson = proj_as_projjson(m_ctxt, obj, nullptr);
3955         ASSERT_NE(projjson, nullptr);
3956         EXPECT_EQ(std::string(projjson),
3957                   "{\n"
3958                   "  \"$schema\": "
3959                   "\"https://proj.org/schemas/v0.2/projjson.schema.json\",\n"
3960                   "  \"type\": \"Ellipsoid\",\n"
3961                   "  \"name\": \"WGS 84\",\n"
3962                   "  \"semi_major_axis\": 6378137,\n"
3963                   "  \"inverse_flattening\": 298.257223563,\n"
3964                   "  \"id\": {\n"
3965                   "    \"authority\": \"EPSG\",\n"
3966                   "    \"code\": 7030\n"
3967                   "  }\n"
3968                   "}");
3969     }
3970     {
3971         const char *const options[] = {"INDENTATION_WIDTH=4", "SCHEMA=",
3972                                        nullptr};
3973         auto projjson = proj_as_projjson(m_ctxt, obj, options);
3974         ASSERT_NE(projjson, nullptr);
3975         EXPECT_EQ(std::string(projjson),
3976                   "{\n"
3977                   "    \"type\": \"Ellipsoid\",\n"
3978                   "    \"name\": \"WGS 84\",\n"
3979                   "    \"semi_major_axis\": 6378137,\n"
3980                   "    \"inverse_flattening\": 298.257223563,\n"
3981                   "    \"id\": {\n"
3982                   "        \"authority\": \"EPSG\",\n"
3983                   "        \"code\": 7030\n"
3984                   "    }\n"
3985                   "}");
3986     }
3987     {
3988         const char *const options[] = {"MULTILINE=NO", "SCHEMA=", nullptr};
3989         auto projjson = proj_as_projjson(m_ctxt, obj, options);
3990         ASSERT_NE(projjson, nullptr);
3991         EXPECT_EQ(std::string(projjson),
3992                   "{\"type\":\"Ellipsoid\",\"name\":\"WGS 84\","
3993                   "\"semi_major_axis\":6378137,"
3994                   "\"inverse_flattening\":298.257223563,"
3995                   "\"id\":{\"authority\":\"EPSG\",\"code\":7030}}");
3996     }
3997 }
3998 
3999 // ---------------------------------------------------------------------------
4000 
4001 struct Fixture_proj_context_set_autoclose_database : public CApi {
test__anon29fdcccb0111::Fixture_proj_context_set_autoclose_database4002     void test(bool autoclose) {
4003         proj_context_set_autoclose_database(m_ctxt, autoclose);
4004 
4005         auto c_path = proj_context_get_database_path(m_ctxt);
4006         ASSERT_TRUE(c_path != nullptr);
4007         std::string path(c_path);
4008 
4009         FILE *f = fopen(path.c_str(), "rb");
4010         ASSERT_NE(f, nullptr);
4011         fseek(f, 0, SEEK_END);
4012         auto length = ftell(f);
4013         std::string content;
4014         content.resize(static_cast<size_t>(length));
4015         fseek(f, 0, SEEK_SET);
4016         auto read_bytes = fread(&content[0], 1, content.size(), f);
4017         ASSERT_EQ(read_bytes, content.size());
4018         fclose(f);
4019         const char *tempdir = getenv("TEMP");
4020         if (!tempdir) {
4021             tempdir = getenv("TMP");
4022         }
4023         if (!tempdir) {
4024             tempdir = "/tmp";
4025         }
4026         std::string tmp_filename(
4027             std::string(tempdir) +
4028             "/test_proj_context_set_autoclose_database.db");
4029         f = fopen(tmp_filename.c_str(), "wb");
4030         if (!f) {
4031             std::cerr << "Cannot create " << tmp_filename << std::endl;
4032             return;
4033         }
4034         fwrite(content.data(), 1, content.size(), f);
4035         fclose(f);
4036 
4037         {
4038             sqlite3 *db = nullptr;
4039             sqlite3_open_v2(tmp_filename.c_str(), &db, SQLITE_OPEN_READWRITE,
4040                             nullptr);
4041             ASSERT_NE(db, nullptr);
4042             ASSERT_TRUE(sqlite3_exec(db, "UPDATE geodetic_crs SET name = 'foo' "
4043                                          "WHERE auth_name = 'EPSG' and code = "
4044                                          "'4326'",
4045                                      nullptr, nullptr, nullptr) == SQLITE_OK);
4046             sqlite3_close(db);
4047         }
4048 
4049         EXPECT_TRUE(proj_context_set_database_path(m_ctxt, tmp_filename.c_str(),
4050                                                    nullptr, nullptr));
4051         {
4052             auto crs = proj_create_from_database(
4053                 m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr);
4054             ObjectKeeper keeper(crs);
4055             ASSERT_NE(crs, nullptr);
4056             EXPECT_EQ(proj_get_name(crs), std::string("foo"));
4057         }
4058 
4059         {
4060             sqlite3 *db = nullptr;
4061             sqlite3_open_v2(tmp_filename.c_str(), &db, SQLITE_OPEN_READWRITE,
4062                             nullptr);
4063             ASSERT_NE(db, nullptr);
4064             ASSERT_TRUE(sqlite3_exec(db, "UPDATE geodetic_crs SET name = 'bar' "
4065                                          "WHERE auth_name = 'EPSG' and code = "
4066                                          "'4326'",
4067                                      nullptr, nullptr, nullptr) == SQLITE_OK);
4068             sqlite3_close(db);
4069         }
4070         {
4071             auto crs = proj_create_from_database(
4072                 m_ctxt, "EPSG", "4326", PJ_CATEGORY_CRS, false, nullptr);
4073             ObjectKeeper keeper(crs);
4074             ASSERT_NE(crs, nullptr);
4075             EXPECT_EQ(proj_get_name(crs),
4076                       std::string(autoclose ? "bar" : "foo"));
4077         }
4078 
4079         if (!autoclose) {
4080             proj_context_destroy(m_ctxt);
4081             m_ctxt = nullptr;
4082         }
4083         std::remove(tmp_filename.c_str());
4084     }
4085 };
4086 
TEST_F(Fixture_proj_context_set_autoclose_database,proj_context_set_autoclose_database_true)4087 TEST_F(Fixture_proj_context_set_autoclose_database,
4088        proj_context_set_autoclose_database_true) {
4089     test(true);
4090 }
4091 
TEST_F(Fixture_proj_context_set_autoclose_database,proj_context_set_autoclose_database_false)4092 TEST_F(Fixture_proj_context_set_autoclose_database,
4093        proj_context_set_autoclose_database_false) {
4094     test(false);
4095 }
4096 
4097 // ---------------------------------------------------------------------------
4098 
TEST_F(CApi,proj_context_copy_from_default)4099 TEST_F(CApi, proj_context_copy_from_default) {
4100     auto c_path = proj_context_get_database_path(m_ctxt);
4101     ASSERT_TRUE(c_path != nullptr);
4102     std::string path(c_path);
4103 
4104     FILE *f = fopen(path.c_str(), "rb");
4105     ASSERT_NE(f, nullptr);
4106     fseek(f, 0, SEEK_END);
4107     auto length = ftell(f);
4108     std::string content;
4109     content.resize(static_cast<size_t>(length));
4110     fseek(f, 0, SEEK_SET);
4111     auto read_bytes = fread(&content[0], 1, content.size(), f);
4112     ASSERT_EQ(read_bytes, content.size());
4113     fclose(f);
4114     const char *tempdir = getenv("TEMP");
4115     if (!tempdir) {
4116         tempdir = getenv("TMP");
4117     }
4118     if (!tempdir) {
4119         tempdir = "/tmp";
4120     }
4121     std::string tmp_filename(std::string(tempdir) +
4122                              "/test_proj_context_set_autoclose_database.db");
4123     f = fopen(tmp_filename.c_str(), "wb");
4124     if (!f) {
4125         std::cerr << "Cannot create " << tmp_filename << std::endl;
4126         return;
4127     }
4128     fwrite(content.data(), 1, content.size(), f);
4129     fclose(f);
4130 
4131     auto c_default_path = proj_context_get_database_path(nullptr);
4132     std::string default_path(c_default_path ? c_default_path : "");
4133     EXPECT_TRUE(proj_context_set_database_path(nullptr, tmp_filename.c_str(),
4134                                                nullptr, nullptr));
4135 
4136     PJ_CONTEXT *new_ctx = proj_context_create();
4137     EXPECT_TRUE(proj_context_set_database_path(
4138         nullptr, default_path.empty() ? nullptr : default_path.c_str(), nullptr,
4139         nullptr));
4140 
4141     EXPECT_NE(new_ctx, nullptr);
4142     PjContextKeeper keeper_ctxt(new_ctx);
4143     auto c_new_path = proj_context_get_database_path(new_ctx);
4144     ASSERT_TRUE(c_new_path != nullptr);
4145     std::string new_db_path(c_new_path);
4146     ASSERT_EQ(new_db_path, tmp_filename);
4147 }
4148 
4149 // ---------------------------------------------------------------------------
4150 
TEST_F(CApi,proj_context_clone)4151 TEST_F(CApi, proj_context_clone) {
4152     int new_init_rules =
4153         proj_context_get_use_proj4_init_rules(NULL, 0) > 0 ? 0 : 1;
4154     PJ_CONTEXT *new_ctx = proj_context_create();
4155     EXPECT_NE(new_ctx, nullptr);
4156     PjContextKeeper keeper_ctxt(new_ctx);
4157     proj_context_use_proj4_init_rules(new_ctx, new_init_rules);
4158     PJ_CONTEXT *clone_ctx = proj_context_clone(new_ctx);
4159     EXPECT_NE(clone_ctx, nullptr);
4160     PjContextKeeper keeper_clone_ctxt(clone_ctx);
4161     ASSERT_EQ(proj_context_get_use_proj4_init_rules(new_ctx, 0),
4162               proj_context_get_use_proj4_init_rules(clone_ctx, 0));
4163     EXPECT_NE(proj_context_get_use_proj4_init_rules(NULL, 0),
4164               proj_context_get_use_proj4_init_rules(clone_ctx, 0));
4165 }
4166 
4167 // ---------------------------------------------------------------------------
4168 
TEST_F(CApi,proj_create_crs_to_crs_from_pj)4169 TEST_F(CApi, proj_create_crs_to_crs_from_pj) {
4170 
4171     auto src = proj_create(m_ctxt, "EPSG:4326");
4172     ObjectKeeper keeper_src(src);
4173     ASSERT_NE(src, nullptr);
4174 
4175     auto dst = proj_create(m_ctxt, "EPSG:32631");
4176     ObjectKeeper keeper_dst(dst);
4177     ASSERT_NE(dst, nullptr);
4178 
4179     auto P = proj_create_crs_to_crs_from_pj(m_ctxt, src, dst, nullptr, nullptr);
4180     ObjectKeeper keeper_P(P);
4181     ASSERT_NE(P, nullptr);
4182     auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
4183     ObjectKeeper keeper_Pnormalized(Pnormalized);
4184     ASSERT_NE(Pnormalized, nullptr);
4185     auto projstr = proj_as_proj_string(m_ctxt, Pnormalized, PJ_PROJ_5, nullptr);
4186     ASSERT_NE(projstr, nullptr);
4187     EXPECT_EQ(std::string(projstr),
4188               "+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad "
4189               "+step +proj=utm +zone=31 +ellps=WGS84");
4190 }
4191 
4192 // ---------------------------------------------------------------------------
4193 
4194 static void
check_axis_is_latitude(PJ_CONTEXT * ctx,PJ * cs,int axis_number,const char * unit_name="degree",double unit_conv_factor=0.017453292519943295,const char * auth="EPSG",const char * code="9122")4195 check_axis_is_latitude(PJ_CONTEXT *ctx, PJ *cs, int axis_number,
4196                        const char *unit_name = "degree",
4197                        double unit_conv_factor = 0.017453292519943295,
4198                        const char *auth = "EPSG", const char *code = "9122") {
4199 
4200     const char *name = nullptr;
4201     const char *abbrev = nullptr;
4202     const char *direction = nullptr;
4203     double unitConvFactor = 0.0;
4204     const char *unitName = nullptr;
4205     const char *unitAuthority = nullptr;
4206     const char *unitCode = nullptr;
4207 
4208     EXPECT_TRUE(proj_cs_get_axis_info(ctx, cs, axis_number, &name, &abbrev,
4209                                       &direction, &unitConvFactor, &unitName,
4210                                       &unitAuthority, &unitCode));
4211     ASSERT_NE(name, nullptr);
4212     ASSERT_NE(abbrev, nullptr);
4213     ASSERT_NE(direction, nullptr);
4214     ASSERT_NE(unitName, nullptr);
4215     if (auth) {
4216         ASSERT_NE(unitAuthority, nullptr);
4217         ASSERT_NE(unitCode, nullptr);
4218     }
4219     EXPECT_EQ(std::string(name), "Latitude");
4220     EXPECT_EQ(std::string(abbrev), "lat");
4221     EXPECT_EQ(std::string(direction), "north");
4222     EXPECT_EQ(unitConvFactor, unit_conv_factor) << unitConvFactor;
4223     EXPECT_EQ(std::string(unitName), unit_name);
4224     if (auth) {
4225         EXPECT_EQ(std::string(unitAuthority), auth);
4226         EXPECT_EQ(std::string(unitCode), code);
4227     }
4228 }
4229 
4230 // ---------------------------------------------------------------------------
4231 
4232 static void
check_axis_is_longitude(PJ_CONTEXT * ctx,PJ * cs,int axis_number,const char * unit_name="degree",double unit_conv_factor=0.017453292519943295,const char * auth="EPSG",const char * code="9122")4233 check_axis_is_longitude(PJ_CONTEXT *ctx, PJ *cs, int axis_number,
4234                         const char *unit_name = "degree",
4235                         double unit_conv_factor = 0.017453292519943295,
4236                         const char *auth = "EPSG", const char *code = "9122") {
4237 
4238     const char *name = nullptr;
4239     const char *abbrev = nullptr;
4240     const char *direction = nullptr;
4241     double unitConvFactor = 0.0;
4242     const char *unitName = nullptr;
4243     const char *unitAuthority = nullptr;
4244     const char *unitCode = nullptr;
4245 
4246     EXPECT_TRUE(proj_cs_get_axis_info(ctx, cs, axis_number, &name, &abbrev,
4247                                       &direction, &unitConvFactor, &unitName,
4248                                       &unitAuthority, &unitCode));
4249     ASSERT_NE(name, nullptr);
4250     ASSERT_NE(abbrev, nullptr);
4251     ASSERT_NE(direction, nullptr);
4252     ASSERT_NE(unitName, nullptr);
4253     if (auth) {
4254         ASSERT_NE(unitAuthority, nullptr);
4255         ASSERT_NE(unitCode, nullptr);
4256     }
4257     EXPECT_EQ(std::string(name), "Longitude");
4258     EXPECT_EQ(std::string(abbrev), "lon");
4259     EXPECT_EQ(std::string(direction), "east");
4260     EXPECT_EQ(unitConvFactor, unit_conv_factor) << unitConvFactor;
4261     EXPECT_EQ(std::string(unitName), unit_name);
4262     if (auth) {
4263         EXPECT_EQ(std::string(unitAuthority), auth);
4264         EXPECT_EQ(std::string(unitCode), code);
4265     }
4266 }
4267 
4268 // ---------------------------------------------------------------------------
4269 
check_axis_is_height(PJ_CONTEXT * ctx,PJ * cs,int axis_number,const char * unit_name="metre",double unit_conv_factor=1,const char * auth="EPSG",const char * code="9001")4270 static void check_axis_is_height(PJ_CONTEXT *ctx, PJ *cs, int axis_number,
4271                                  const char *unit_name = "metre",
4272                                  double unit_conv_factor = 1,
4273                                  const char *auth = "EPSG",
4274                                  const char *code = "9001") {
4275 
4276     const char *name = nullptr;
4277     const char *abbrev = nullptr;
4278     const char *direction = nullptr;
4279     double unitConvFactor = 0.0;
4280     const char *unitName = nullptr;
4281     const char *unitAuthority = nullptr;
4282     const char *unitCode = nullptr;
4283 
4284     EXPECT_TRUE(proj_cs_get_axis_info(ctx, cs, axis_number, &name, &abbrev,
4285                                       &direction, &unitConvFactor, &unitName,
4286                                       &unitAuthority, &unitCode));
4287     ASSERT_NE(name, nullptr);
4288     ASSERT_NE(abbrev, nullptr);
4289     ASSERT_NE(direction, nullptr);
4290     ASSERT_NE(unitName, nullptr);
4291     if (auth) {
4292         ASSERT_NE(unitAuthority, nullptr);
4293         ASSERT_NE(unitCode, nullptr);
4294     }
4295     EXPECT_EQ(std::string(name), "Ellipsoidal height");
4296     EXPECT_EQ(std::string(abbrev), "h");
4297     EXPECT_EQ(std::string(direction), "up");
4298     EXPECT_EQ(unitConvFactor, unit_conv_factor) << unitConvFactor;
4299     EXPECT_EQ(std::string(unitName), unit_name);
4300     if (auth) {
4301         EXPECT_EQ(std::string(unitAuthority), auth);
4302         EXPECT_EQ(std::string(unitCode), code);
4303     }
4304 }
4305 
4306 // ---------------------------------------------------------------------------
4307 
TEST_F(CApi,proj_create_ellipsoidal_3D_cs)4308 TEST_F(CApi, proj_create_ellipsoidal_3D_cs) {
4309 
4310     {
4311         auto cs = proj_create_ellipsoidal_3D_cs(
4312             m_ctxt, PJ_ELLPS3D_LATITUDE_LONGITUDE_HEIGHT, nullptr, 0, nullptr,
4313             0);
4314         ObjectKeeper keeper_cs(cs);
4315         ASSERT_NE(cs, nullptr);
4316 
4317         EXPECT_EQ(proj_cs_get_type(m_ctxt, cs), PJ_CS_TYPE_ELLIPSOIDAL);
4318 
4319         EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3);
4320 
4321         check_axis_is_latitude(m_ctxt, cs, 0);
4322 
4323         check_axis_is_longitude(m_ctxt, cs, 1);
4324 
4325         check_axis_is_height(m_ctxt, cs, 2);
4326     }
4327 
4328     {
4329         auto cs = proj_create_ellipsoidal_3D_cs(
4330             m_ctxt, PJ_ELLPS3D_LONGITUDE_LATITUDE_HEIGHT, "foo", 0.5, "bar",
4331             0.6);
4332         ObjectKeeper keeper_cs(cs);
4333         ASSERT_NE(cs, nullptr);
4334 
4335         EXPECT_EQ(proj_cs_get_type(m_ctxt, cs), PJ_CS_TYPE_ELLIPSOIDAL);
4336 
4337         EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3);
4338 
4339         check_axis_is_longitude(m_ctxt, cs, 0, "foo", 0.5, nullptr, nullptr);
4340 
4341         check_axis_is_latitude(m_ctxt, cs, 1, "foo", 0.5, nullptr, nullptr);
4342 
4343         check_axis_is_height(m_ctxt, cs, 2, "bar", 0.6, nullptr, nullptr);
4344     }
4345 }
4346 
4347 // ---------------------------------------------------------------------------
4348 
TEST_F(CApi,proj_crs_promote_to_3D)4349 TEST_F(CApi, proj_crs_promote_to_3D) {
4350 
4351     auto crs2D =
4352         proj_create(m_ctxt, GeographicCRS::EPSG_4326
4353                                 ->exportToWKT(WKTFormatter::create().get())
4354                                 .c_str());
4355     ObjectKeeper keeper_crs2D(crs2D);
4356     EXPECT_NE(crs2D, nullptr);
4357 
4358     auto crs3D = proj_crs_promote_to_3D(m_ctxt, nullptr, crs2D);
4359     ObjectKeeper keeper_crs3D(crs3D);
4360     EXPECT_NE(crs3D, nullptr);
4361 
4362     auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D);
4363     ASSERT_NE(cs, nullptr);
4364     ObjectKeeper keeperCs(cs);
4365     EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3);
4366 
4367     auto code = proj_get_id_code(crs3D, 0);
4368     ASSERT_TRUE(code != nullptr);
4369     EXPECT_EQ(code, std::string("4979"));
4370 }
4371 
4372 // ---------------------------------------------------------------------------
4373 
TEST_F(CApi,proj_crs_demote_to_2D)4374 TEST_F(CApi, proj_crs_demote_to_2D) {
4375 
4376     auto crs3D =
4377         proj_create(m_ctxt, GeographicCRS::EPSG_4979
4378                                 ->exportToWKT(WKTFormatter::create().get())
4379                                 .c_str());
4380     ObjectKeeper keeper_crs3D(crs3D);
4381     EXPECT_NE(crs3D, nullptr);
4382 
4383     auto crs2D = proj_crs_demote_to_2D(m_ctxt, nullptr, crs3D);
4384     ObjectKeeper keeper_crs2D(crs2D);
4385     EXPECT_NE(crs2D, nullptr);
4386 
4387     auto cs = proj_crs_get_coordinate_system(m_ctxt, crs2D);
4388     ASSERT_NE(cs, nullptr);
4389     ObjectKeeper keeperCs(cs);
4390     EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 2);
4391 
4392     auto code = proj_get_id_code(crs2D, 0);
4393     ASSERT_TRUE(code != nullptr);
4394     EXPECT_EQ(code, std::string("4326"));
4395 }
4396 
4397 // ---------------------------------------------------------------------------
4398 
TEST_F(CApi,proj_crs_create_projected_3D_crs_from_2D)4399 TEST_F(CApi, proj_crs_create_projected_3D_crs_from_2D) {
4400 
4401     auto projCRS = proj_create_from_database(m_ctxt, "EPSG", "32631",
4402                                              PJ_CATEGORY_CRS, false, nullptr);
4403     ASSERT_NE(projCRS, nullptr);
4404     ObjectKeeper keeper_projCRS(projCRS);
4405 
4406     {
4407         auto geog3DCRS = proj_create_from_database(
4408             m_ctxt, "EPSG", "4979", PJ_CATEGORY_CRS, false, nullptr);
4409         ASSERT_NE(geog3DCRS, nullptr);
4410         ObjectKeeper keeper_geog3DCRS(geog3DCRS);
4411 
4412         auto crs3D = proj_crs_create_projected_3D_crs_from_2D(
4413             m_ctxt, nullptr, projCRS, geog3DCRS);
4414         ObjectKeeper keeper_crs3D(crs3D);
4415         EXPECT_NE(crs3D, nullptr);
4416 
4417         EXPECT_EQ(proj_get_type(crs3D), PJ_TYPE_PROJECTED_CRS);
4418 
4419         EXPECT_EQ(std::string(proj_get_name(crs3D)),
4420                   std::string(proj_get_name(projCRS)));
4421 
4422         auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D);
4423         ASSERT_NE(cs, nullptr);
4424         ObjectKeeper keeperCs(cs);
4425         EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3);
4426     }
4427 
4428     {
4429         auto crs3D = proj_crs_create_projected_3D_crs_from_2D(m_ctxt, nullptr,
4430                                                               projCRS, nullptr);
4431         ObjectKeeper keeper_crs3D(crs3D);
4432         EXPECT_NE(crs3D, nullptr);
4433 
4434         EXPECT_EQ(proj_get_type(crs3D), PJ_TYPE_PROJECTED_CRS);
4435 
4436         EXPECT_EQ(std::string(proj_get_name(crs3D)),
4437                   std::string(proj_get_name(projCRS)));
4438 
4439         auto cs = proj_crs_get_coordinate_system(m_ctxt, crs3D);
4440         ASSERT_NE(cs, nullptr);
4441         ObjectKeeper keeperCs(cs);
4442         EXPECT_EQ(proj_cs_get_axis_count(m_ctxt, cs), 3);
4443     }
4444 }
4445 
4446 // ---------------------------------------------------------------------------
4447 
TEST_F(CApi,proj_crs_create_bound_vertical_crs)4448 TEST_F(CApi, proj_crs_create_bound_vertical_crs) {
4449 
4450     auto vert_crs = proj_create_vertical_crs(m_ctxt, "myVertCRS", "myVertDatum",
4451                                              nullptr, 0.0);
4452     ObjectKeeper keeper_vert_crs(vert_crs);
4453     ASSERT_NE(vert_crs, nullptr);
4454 
4455     auto crs4979 = proj_create_from_wkt(
4456         m_ctxt,
4457         GeographicCRS::EPSG_4979->exportToWKT(WKTFormatter::create().get())
4458             .c_str(),
4459         nullptr, nullptr, nullptr);
4460     ObjectKeeper keeper_crs4979(crs4979);
4461     ASSERT_NE(crs4979, nullptr);
4462 
4463     auto bound_crs = proj_crs_create_bound_vertical_crs(m_ctxt, vert_crs,
4464                                                         crs4979, "foo.gtx");
4465     ObjectKeeper keeper_bound_crs(bound_crs);
4466     ASSERT_NE(bound_crs, nullptr);
4467 
4468     auto projCRS = proj_create_from_database(m_ctxt, "EPSG", "32631",
4469                                              PJ_CATEGORY_CRS, false, nullptr);
4470     ASSERT_NE(projCRS, nullptr);
4471     ObjectKeeper keeper_projCRS(projCRS);
4472 
4473     auto compound_crs =
4474         proj_create_compound_crs(m_ctxt, "myCompoundCRS", projCRS, bound_crs);
4475     ObjectKeeper keeper_compound_crss(compound_crs);
4476     ASSERT_NE(compound_crs, nullptr);
4477 
4478     auto proj_4 = proj_as_proj_string(m_ctxt, compound_crs, PJ_PROJ_4, nullptr);
4479     ASSERT_NE(proj_4, nullptr);
4480     EXPECT_EQ(std::string(proj_4),
4481               "+proj=utm +zone=31 +datum=WGS84 +units=m +geoidgrids=foo.gtx "
4482               "+vunits=m +no_defs +type=crs");
4483 }
4484 
4485 // ---------------------------------------------------------------------------
4486 
TEST_F(CApi,proj_create_crs_to_crs_with_only_ballpark_transformations)4487 TEST_F(CApi, proj_create_crs_to_crs_with_only_ballpark_transformations) {
4488     // ETRS89 / UTM zone 31N + EGM96 height to WGS 84 (G1762)
4489     auto P =
4490         proj_create_crs_to_crs(m_ctxt, "EPSG:25831+5773", "EPSG:7665", nullptr);
4491     ObjectKeeper keeper_P(P);
4492     ASSERT_NE(P, nullptr);
4493     auto Pnormalized = proj_normalize_for_visualization(m_ctxt, P);
4494     ObjectKeeper keeper_Pnormalized(Pnormalized);
4495     ASSERT_NE(Pnormalized, nullptr);
4496 
4497     PJ_COORD coord;
4498     coord.xyzt.x = 500000;
4499     coord.xyzt.y = 4500000;
4500     coord.xyzt.z = 0;
4501     coord.xyzt.t = 0;
4502     coord = proj_trans(Pnormalized, PJ_FWD, coord);
4503     EXPECT_NEAR(coord.xyzt.x, 3.0, 1e-9);
4504     EXPECT_NEAR(coord.xyzt.y, 40.65085651660555, 1e-9);
4505     EXPECT_NEAR(coord.xyzt.z, 47.72600023608570, 1e-3);
4506 }
4507 
4508 // ---------------------------------------------------------------------------
4509 
TEST_F(CApi,proj_create_crs_to_crs_from_custom_compound_crs_with_NAD83_2011_and_geoidgrid_ref_against_WGS84_to_WGS84_G1762)4510 TEST_F(
4511     CApi,
4512     proj_create_crs_to_crs_from_custom_compound_crs_with_NAD83_2011_and_geoidgrid_ref_against_WGS84_to_WGS84_G1762) {
4513 
4514     PJ *P;
4515 
4516     PJ *inCrsH = proj_create_from_database(m_ctxt, "EPSG", "6340",
4517                                            PJ_CATEGORY_CRS, false, nullptr);
4518     ASSERT_NE(inCrsH, nullptr);
4519 
4520     PJ *inDummyCrs = proj_create_vertical_crs(m_ctxt, "VerticalDummyCrs",
4521                                               "DummyDatum", "metre", 1.0);
4522     ASSERT_NE(inDummyCrs, nullptr);
4523 
4524     auto crs4979 = proj_create_from_database(m_ctxt, "EPSG", "4979",
4525                                              PJ_CATEGORY_CRS, false, nullptr);
4526     ASSERT_NE(crs4979, nullptr);
4527 
4528     PJ *inCrsV = proj_crs_create_bound_vertical_crs(m_ctxt, inDummyCrs, crs4979,
4529                                                     "egm96_15.gtx");
4530     ASSERT_NE(inCrsV, nullptr);
4531     proj_destroy(inDummyCrs);
4532     proj_destroy(crs4979);
4533 
4534     PJ *inCompound =
4535         proj_create_compound_crs(m_ctxt, "Compound", inCrsH, inCrsV);
4536     ASSERT_NE(inCompound, nullptr);
4537     proj_destroy(inCrsH);
4538     proj_destroy(inCrsV);
4539 
4540     PJ *outCrs = proj_create(m_ctxt, "EPSG:7665");
4541     ASSERT_NE(outCrs, nullptr);
4542 
4543     // In this particular case, PROJ computes a transformation from NAD83(2011)
4544     // (EPSG:6318) to WGS84 (EPSG:4979) for the initial horizontal adjustment
4545     // before the geoidgrids application. There are 6 candidate transformations
4546     // for that in subzones of the US and one last no-op transformation flagged
4547     // as ballpark. That one used to be eliminated because by
4548     // proj_create_crs_to_crs() because there were non Ballpark transformations
4549     // available. This resulted thus in an error when transforming outside of
4550     // those few subzones.
4551     P = proj_create_crs_to_crs_from_pj(m_ctxt, inCompound, outCrs, nullptr,
4552                                        nullptr);
4553     ASSERT_NE(P, nullptr);
4554     proj_destroy(inCompound);
4555     proj_destroy(outCrs);
4556 
4557     PJ_COORD in_coord;
4558     in_coord.xyzt.x = 350499.911;
4559     in_coord.xyzt.y = 3884807.956;
4560     in_coord.xyzt.z = 150.072;
4561     in_coord.xyzt.t = 2010;
4562 
4563     PJ_COORD outcoord = proj_trans(P, PJ_FWD, in_coord);
4564     proj_destroy(P);
4565 
4566     EXPECT_NEAR(outcoord.xyzt.x, 35.09499307271, 1e-9);
4567     EXPECT_NEAR(outcoord.xyzt.y, -118.64014868921, 1e-9);
4568     EXPECT_NEAR(outcoord.xyzt.z, 117.655, 1e-3);
4569 }
4570 
4571 // ---------------------------------------------------------------------------
4572 
TEST_F(CApi,proj_create_crs_to_crs_from_custom_compound_crs_with_NAD83_2011_and_geoidgrid_ref_against_NAD83_2011_to_WGS84_G1762)4573 TEST_F(
4574     CApi,
4575     proj_create_crs_to_crs_from_custom_compound_crs_with_NAD83_2011_and_geoidgrid_ref_against_NAD83_2011_to_WGS84_G1762) {
4576 
4577     PJ *P;
4578 
4579     // NAD83(2011) 2D
4580     PJ *inCrsH = proj_create_from_database(m_ctxt, "EPSG", "6318",
4581                                            PJ_CATEGORY_CRS, false, nullptr);
4582     ASSERT_NE(inCrsH, nullptr);
4583 
4584     PJ *inDummyCrs = proj_create_vertical_crs(m_ctxt, "VerticalDummyCrs",
4585                                               "DummyDatum", "metre", 1.0);
4586     ASSERT_NE(inDummyCrs, nullptr);
4587 
4588     // NAD83(2011) 3D
4589     PJ *inGeog3DCRS = proj_create_from_database(
4590         m_ctxt, "EPSG", "6319", PJ_CATEGORY_CRS, false, nullptr);
4591     ASSERT_NE(inCrsH, nullptr);
4592 
4593     // Note: this is actually a bad example since we tell here that egm96_15.gtx
4594     // is referenced against NAD83(2011)
4595     PJ *inCrsV = proj_crs_create_bound_vertical_crs(
4596         m_ctxt, inDummyCrs, inGeog3DCRS, "egm96_15.gtx");
4597     ASSERT_NE(inCrsV, nullptr);
4598     proj_destroy(inDummyCrs);
4599     proj_destroy(inGeog3DCRS);
4600 
4601     PJ *inCompound =
4602         proj_create_compound_crs(m_ctxt, "Compound", inCrsH, inCrsV);
4603     ASSERT_NE(inCompound, nullptr);
4604     proj_destroy(inCrsH);
4605     proj_destroy(inCrsV);
4606 
4607     // WGS84 (G1762)
4608     PJ *outCrs = proj_create(m_ctxt, "EPSG:7665");
4609     ASSERT_NE(outCrs, nullptr);
4610 
4611     P = proj_create_crs_to_crs_from_pj(m_ctxt, inCompound, outCrs, nullptr,
4612                                        nullptr);
4613     ASSERT_NE(P, nullptr);
4614     proj_destroy(inCompound);
4615     proj_destroy(outCrs);
4616 
4617     PJ_COORD in_coord;
4618     in_coord.xyzt.x = 35;
4619     in_coord.xyzt.y = -118;
4620     in_coord.xyzt.z = 0;
4621     in_coord.xyzt.t = 2010;
4622 
4623     PJ_COORD outcoord = proj_trans(P, PJ_FWD, in_coord);
4624     proj_destroy(P);
4625 
4626     EXPECT_NEAR(outcoord.xyzt.x, 35.000003665064803, 1e-9);
4627     EXPECT_NEAR(outcoord.xyzt.y, -118.00001414221214, 1e-9);
4628     EXPECT_NEAR(outcoord.xyzt.z, -32.8110, 1e-3);
4629 }
4630 
4631 // ---------------------------------------------------------------------------
4632 
TEST_F(CApi,proj_create_vertical_crs_ex)4633 TEST_F(CApi, proj_create_vertical_crs_ex) {
4634 
4635     // NAD83(2011) / UTM zone 11N
4636     auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340",
4637                                                PJ_CATEGORY_CRS, false, nullptr);
4638     ObjectKeeper keeper_horiz_crs(horiz_crs);
4639     ASSERT_NE(horiz_crs, nullptr);
4640 
4641     const char *options[] = {"ACCURACY=123", nullptr};
4642     auto vert_crs = proj_create_vertical_crs_ex(
4643         m_ctxt, "myVertCRS (ftUS)", "myVertDatum", nullptr, nullptr,
4644         "US survey foot", 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr,
4645         nullptr, options);
4646     ObjectKeeper keeper_vert_crs(vert_crs);
4647     ASSERT_NE(vert_crs, nullptr);
4648 
4649     auto compound =
4650         proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs);
4651     ObjectKeeper keeper_compound(compound);
4652     ASSERT_NE(compound, nullptr);
4653 
4654     // NAD83(2011) 3D
4655     PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319");
4656     ObjectKeeper keeper_geog_crs(geog_crs);
4657     ASSERT_NE(geog_crs, nullptr);
4658 
4659     PJ_OPERATION_FACTORY_CONTEXT *ctxt =
4660         proj_create_operation_factory_context(m_ctxt, nullptr);
4661     ASSERT_NE(ctxt, nullptr);
4662     ContextKeeper keeper_ctxt(ctxt);
4663     proj_operation_factory_context_set_grid_availability_use(
4664         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
4665     proj_operation_factory_context_set_spatial_criterion(
4666         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
4667     PJ_OBJ_LIST *operations =
4668         proj_create_operations(m_ctxt, compound, geog_crs, ctxt);
4669     ASSERT_NE(operations, nullptr);
4670     ObjListKeeper keeper_operations(operations);
4671     EXPECT_GE(proj_list_get_count(operations), 1);
4672     auto P = proj_list_get(m_ctxt, operations, 0);
4673     ObjectKeeper keeper_transform(P);
4674 
4675     auto name = proj_get_name(P);
4676     ASSERT_TRUE(name != nullptr);
4677     EXPECT_EQ(name,
4678               std::string("Inverse of UTM zone 11N + "
4679                           "Transformation from myVertCRS (ftUS) to myVertCRS + "
4680                           "Transformation from myVertCRS to NAD83(2011)"));
4681 
4682     auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr);
4683     ASSERT_NE(proj_5, nullptr);
4684     EXPECT_EQ(std::string(proj_5),
4685               "+proj=pipeline "
4686               "+step +inv +proj=utm +zone=11 +ellps=GRS80 "
4687               "+step +proj=unitconvert +z_in=us-ft +z_out=m "
4688               "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
4689               "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
4690               "+step +proj=axisswap +order=2,1");
4691 
4692     ASSERT_EQ(proj_coordoperation_get_accuracy(m_ctxt, P), 123.0);
4693 }
4694 
4695 // ---------------------------------------------------------------------------
4696 
TEST_F(CApi,proj_create_vertical_crs_ex_with_geog_crs)4697 TEST_F(CApi, proj_create_vertical_crs_ex_with_geog_crs) {
4698 
4699     // NAD83(2011) / UTM zone 11N
4700     auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340",
4701                                                PJ_CATEGORY_CRS, false, nullptr);
4702     ObjectKeeper keeper_horiz_crs(horiz_crs);
4703     ASSERT_NE(horiz_crs, nullptr);
4704 
4705     // WGS84
4706     PJ *wgs84 = proj_create(m_ctxt, "EPSG:4979");
4707     ObjectKeeper keeper_wgs84(wgs84);
4708     ASSERT_NE(wgs84, nullptr);
4709 
4710     auto vert_crs = proj_create_vertical_crs_ex(
4711         m_ctxt, "myVertCRS", "myVertDatum", nullptr, nullptr, "US survey foot",
4712         0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr, wgs84, nullptr);
4713     ObjectKeeper keeper_vert_crs(vert_crs);
4714     ASSERT_NE(vert_crs, nullptr);
4715 
4716     auto compound =
4717         proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs);
4718     ObjectKeeper keeper_compound(compound);
4719     ASSERT_NE(compound, nullptr);
4720 
4721     // NAD83(2011) 3D
4722     PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319");
4723     ObjectKeeper keeper_geog_crs(geog_crs);
4724     ASSERT_NE(geog_crs, nullptr);
4725 
4726     PJ_OPERATION_FACTORY_CONTEXT *ctxt =
4727         proj_create_operation_factory_context(m_ctxt, nullptr);
4728     ASSERT_NE(ctxt, nullptr);
4729     ContextKeeper keeper_ctxt(ctxt);
4730     proj_operation_factory_context_set_grid_availability_use(
4731         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
4732     proj_operation_factory_context_set_spatial_criterion(
4733         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
4734     PJ_OBJ_LIST *operations =
4735         proj_create_operations(m_ctxt, compound, geog_crs, ctxt);
4736     ASSERT_NE(operations, nullptr);
4737     ObjListKeeper keeper_operations(operations);
4738     EXPECT_GE(proj_list_get_count(operations), 1);
4739     auto P = proj_list_get(m_ctxt, operations, 0);
4740     ObjectKeeper keeper_transform(P);
4741 
4742     auto name = proj_get_name(P);
4743     ASSERT_TRUE(name != nullptr);
4744     EXPECT_EQ(
4745         name,
4746         std::string("Inverse of UTM zone 11N + "
4747                     "Ballpark geographic offset from NAD83(2011) to WGS 84 + "
4748                     "Transformation from myVertCRS to myVertCRS (metre) + "
4749                     "Transformation from myVertCRS (metre) to WGS 84 + "
4750                     "Ballpark geographic offset from WGS 84 to NAD83(2011)"));
4751 
4752     auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr);
4753     ASSERT_NE(proj_5, nullptr);
4754     EXPECT_EQ(std::string(proj_5),
4755               "+proj=pipeline "
4756               "+step +inv +proj=utm +zone=11 +ellps=GRS80 "
4757               "+step +proj=unitconvert +z_in=us-ft +z_out=m "
4758               "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
4759               "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
4760               "+step +proj=axisswap +order=2,1");
4761 
4762     // Check that we get the same results after an export of compoundCRS to
4763     // PROJJSON and a re-import from it.
4764     auto projjson = proj_as_projjson(m_ctxt, compound, nullptr);
4765     ASSERT_NE(projjson, nullptr);
4766     auto compound_from_projjson = proj_create(m_ctxt, projjson);
4767     ObjectKeeper keeper_compound_from_projjson(compound_from_projjson);
4768     ASSERT_NE(compound_from_projjson, nullptr);
4769 
4770     PJ_OBJ_LIST *operations2 =
4771         proj_create_operations(m_ctxt, compound_from_projjson, geog_crs, ctxt);
4772     ASSERT_NE(operations2, nullptr);
4773     ObjListKeeper keeper_operations2(operations2);
4774     EXPECT_GE(proj_list_get_count(operations2), 1);
4775     auto P2 = proj_list_get(m_ctxt, operations2, 0);
4776     ObjectKeeper keeper_transform2(P2);
4777 
4778     auto name_bis = proj_get_name(P2);
4779     ASSERT_TRUE(name_bis != nullptr);
4780     EXPECT_EQ(std::string(name_bis), std::string(name));
4781 
4782     auto proj_5_bis = proj_as_proj_string(m_ctxt, P2, PJ_PROJ_5, nullptr);
4783     ASSERT_NE(proj_5_bis, nullptr);
4784     EXPECT_EQ(std::string(proj_5_bis), std::string(proj_5));
4785 }
4786 
4787 // ---------------------------------------------------------------------------
4788 
TEST_F(CApi,proj_create_vertical_crs_ex_implied_accuracy)4789 TEST_F(CApi, proj_create_vertical_crs_ex_implied_accuracy) {
4790 
4791     PJ *crsH = proj_create(m_ctxt, "EPSG:4283"); // GDA94
4792     ASSERT_NE(crsH, nullptr);
4793     ObjectKeeper keeper_crsH(crsH);
4794     PJ *crsV = proj_create(m_ctxt, "EPSG:5711"); // AHD height
4795     ASSERT_NE(crsV, nullptr);
4796     ObjectKeeper keeper_crsV(crsV);
4797     PJ *crsGeoid = proj_create(m_ctxt, "EPSG:4939"); // GDA94 3D
4798     ASSERT_NE(crsGeoid, nullptr);
4799     ObjectKeeper keeper_crsGeoid(crsGeoid);
4800 
4801     PJ *vertDatum = proj_crs_get_datum(m_ctxt, crsV);
4802     ObjectKeeper keeper_vertDatum(vertDatum);
4803     const char *vertDatumName = proj_get_name(vertDatum);
4804     const char *vertDatumAuthority = proj_get_id_auth_name(vertDatum, 0);
4805     const char *vertDatumCode = proj_get_id_code(vertDatum, 0);
4806     PJ *crsVGeoid = proj_create_vertical_crs_ex(
4807         m_ctxt, "Vertical", vertDatumName, vertDatumAuthority, vertDatumCode,
4808         "metre", 1.0, "PROJ au_ga_AUSGeoid09_V1.01.tif", nullptr, nullptr,
4809         crsGeoid, nullptr);
4810     ObjectKeeper keeper_crsVGeoid(crsVGeoid);
4811     PJ *crsCompoundGeoid = proj_create_compound_crs(
4812         m_ctxt, "Compound with geoid", crsH, crsVGeoid);
4813     ObjectKeeper keeper_crsCompoundGeoid(crsCompoundGeoid);
4814 
4815     PJ_OPERATION_FACTORY_CONTEXT *ctxt =
4816         proj_create_operation_factory_context(m_ctxt, nullptr);
4817     ASSERT_NE(ctxt, nullptr);
4818     ContextKeeper keeper_ctxt(ctxt);
4819     proj_operation_factory_context_set_grid_availability_use(
4820         m_ctxt, ctxt, PROJ_GRID_AVAILABILITY_IGNORED);
4821     proj_operation_factory_context_set_spatial_criterion(
4822         m_ctxt, ctxt, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION);
4823     PJ_OBJ_LIST *operations =
4824         proj_create_operations(m_ctxt, crsCompoundGeoid, crsGeoid, ctxt);
4825     ASSERT_NE(operations, nullptr);
4826     ObjListKeeper keeper_operations(operations);
4827     EXPECT_GE(proj_list_get_count(operations), 1);
4828     PJ *transform = proj_list_get(m_ctxt, operations, 0);
4829     ObjectKeeper keeper_transform(transform);
4830 
4831     // This is the accuracy of operations EPSG:5656 / 5657
4832     ASSERT_EQ(proj_coordoperation_get_accuracy(m_ctxt, transform), 0.15);
4833 }
4834 
4835 // ---------------------------------------------------------------------------
4836 
TEST_F(CApi,proj_create_derived_geographic_crs)4837 TEST_F(CApi, proj_create_derived_geographic_crs) {
4838 
4839     PJ *crs_4326 = proj_create(m_ctxt, "EPSG:4326");
4840     ObjectKeeper keeper_crs_4326(crs_4326);
4841     ASSERT_NE(crs_4326, nullptr);
4842 
4843     PJ *conversion = proj_create_conversion_pole_rotation_grib_convention(
4844         m_ctxt, 2, 3, 4, "Degree", 0.0174532925199433);
4845     ObjectKeeper keeper_conversion(conversion);
4846     ASSERT_NE(conversion, nullptr);
4847 
4848     PJ *cs = proj_crs_get_coordinate_system(m_ctxt, crs_4326);
4849     ObjectKeeper keeper_cs(cs);
4850     ASSERT_NE(cs, nullptr);
4851 
4852     ASSERT_EQ(
4853         proj_create_derived_geographic_crs(m_ctxt, "my rotated CRS",
4854                                            conversion, // wrong type of object
4855                                            conversion, cs),
4856         nullptr);
4857 
4858     ASSERT_EQ(
4859         proj_create_derived_geographic_crs(m_ctxt, "my rotated CRS", crs_4326,
4860                                            crs_4326, // wrong type of object
4861                                            cs),
4862         nullptr);
4863 
4864     ASSERT_EQ(proj_create_derived_geographic_crs(
4865                   m_ctxt, "my rotated CRS", crs_4326, conversion,
4866                   conversion // wrong type of object
4867                   ),
4868               nullptr);
4869 
4870     PJ *derived_crs = proj_create_derived_geographic_crs(
4871         m_ctxt, "my rotated CRS", crs_4326, conversion, cs);
4872     ObjectKeeper keeper_derived_crs(derived_crs);
4873     ASSERT_NE(derived_crs, nullptr);
4874 
4875     EXPECT_FALSE(proj_is_derived_crs(m_ctxt, crs_4326));
4876     EXPECT_TRUE(proj_is_derived_crs(m_ctxt, derived_crs));
4877 
4878     auto wkt = proj_as_wkt(m_ctxt, derived_crs, PJ_WKT2_2019, nullptr);
4879     const char *expected_wkt =
4880         "GEOGCRS[\"my rotated CRS\",\n"
4881         "    BASEGEOGCRS[\"WGS 84\",\n"
4882         "        DATUM[\"World Geodetic System 1984\",\n"
4883         "            ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
4884         "                LENGTHUNIT[\"metre\",1]]],\n"
4885         "        PRIMEM[\"Greenwich\",0,\n"
4886         "            ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
4887         "    DERIVINGCONVERSION[\"Pole rotation (GRIB convention)\",\n"
4888         "        METHOD[\"Pole rotation (GRIB convention)\"],\n"
4889         "        PARAMETER[\"Latitude of the southern pole (GRIB "
4890         "convention)\",2,\n"
4891         "            ANGLEUNIT[\"degree\",0.0174532925199433,\n"
4892         "                ID[\"EPSG\",9122]]],\n"
4893         "        PARAMETER[\"Longitude of the southern pole (GRIB "
4894         "convention)\",3,\n"
4895         "            ANGLEUNIT[\"degree\",0.0174532925199433,\n"
4896         "                ID[\"EPSG\",9122]]],\n"
4897         "        PARAMETER[\"Axis rotation (GRIB convention)\",4,\n"
4898         "            ANGLEUNIT[\"degree\",0.0174532925199433,\n"
4899         "                ID[\"EPSG\",9122]]]],\n"
4900         "    CS[ellipsoidal,2],\n"
4901         "        AXIS[\"geodetic latitude (Lat)\",north,\n"
4902         "            ORDER[1],\n"
4903         "            ANGLEUNIT[\"degree\",0.0174532925199433,\n"
4904         "                ID[\"EPSG\",9122]]],\n"
4905         "        AXIS[\"geodetic longitude (Lon)\",east,\n"
4906         "            ORDER[2],\n"
4907         "            ANGLEUNIT[\"degree\",0.0174532925199433,\n"
4908         "                ID[\"EPSG\",9122]]]]";
4909 
4910     ASSERT_NE(wkt, nullptr);
4911     EXPECT_EQ(wkt, std::string(expected_wkt));
4912 
4913     auto proj_5 = proj_as_proj_string(m_ctxt, derived_crs, PJ_PROJ_5, nullptr);
4914     ASSERT_NE(proj_5, nullptr);
4915     EXPECT_EQ(proj_5, std::string("+proj=ob_tran +o_proj=longlat +o_lon_p=-4 "
4916                                   "+o_lat_p=-2 +lon_0=3 +datum=WGS84 +no_defs "
4917                                   "+type=crs"));
4918 }
4919 
4920 // ---------------------------------------------------------------------------
4921 
TEST_F(CApi,proj_context_set_sqlite3_vfs_name)4922 TEST_F(CApi, proj_context_set_sqlite3_vfs_name) {
4923 
4924     PJ_CONTEXT *ctx = proj_context_create();
4925     proj_log_func(ctx, nullptr, [](void *, int, const char *) -> void {});
4926 
4927     // Set a dummy VFS and check it is taken into account
4928     // (failure to open proj.db)
4929     proj_context_set_sqlite3_vfs_name(ctx, "dummy_vfs_name");
4930     ASSERT_EQ(proj_create(ctx, "EPSG:4326"), nullptr);
4931 
4932     // Restore default VFS
4933     proj_context_set_sqlite3_vfs_name(ctx, nullptr);
4934     PJ *crs_4326 = proj_create(ctx, "EPSG:4326");
4935     ASSERT_NE(crs_4326, nullptr);
4936     proj_destroy(crs_4326);
4937 
4938     proj_context_destroy(ctx);
4939 }
4940 
4941 // ---------------------------------------------------------------------------
4942 
TEST_F(CApi,proj_context_set_sqlite3_vfs_name__from_global_context)4943 TEST_F(CApi, proj_context_set_sqlite3_vfs_name__from_global_context) {
4944 
4945     // Set a dummy VFS and check it is taken into account
4946     // (failure to open proj.db)
4947     proj_context_set_sqlite3_vfs_name(nullptr, "dummy_vfs_name");
4948 
4949     PJ_CONTEXT *ctx = proj_context_create();
4950     proj_log_func(ctx, nullptr, [](void *, int, const char *) -> void {});
4951 
4952     ASSERT_EQ(proj_create(ctx, "EPSG:4326"), nullptr);
4953 
4954     // Restore default VFS
4955     proj_context_set_sqlite3_vfs_name(nullptr, nullptr);
4956     proj_context_destroy(ctx);
4957 }
4958 
4959 // ---------------------------------------------------------------------------
4960 
TEST_F(CApi,use_proj4_init_rules)4961 TEST_F(CApi, use_proj4_init_rules) {
4962     PJ_CONTEXT *ctx = proj_context_create();
4963     proj_context_use_proj4_init_rules(ctx, true);
4964     ASSERT_TRUE(proj_context_get_use_proj4_init_rules(ctx, true));
4965     proj_context_use_proj4_init_rules(ctx, false);
4966     ASSERT_TRUE(!proj_context_get_use_proj4_init_rules(ctx, true));
4967     proj_context_destroy(ctx);
4968 }
4969 
4970 // ---------------------------------------------------------------------------
4971 
TEST_F(CApi,use_proj4_init_rules_from_global_context)4972 TEST_F(CApi, use_proj4_init_rules_from_global_context) {
4973 
4974     int initial_rules = proj_context_get_use_proj4_init_rules(nullptr, true);
4975     proj_context_use_proj4_init_rules(nullptr, true);
4976     PJ_CONTEXT *ctx = proj_context_create();
4977     ASSERT_TRUE(proj_context_get_use_proj4_init_rules(ctx, true));
4978     proj_context_destroy(ctx);
4979     proj_context_use_proj4_init_rules(nullptr, false);
4980     ctx = proj_context_create();
4981     ASSERT_TRUE(!proj_context_get_use_proj4_init_rules(ctx, true));
4982     proj_context_destroy(ctx);
4983     proj_context_use_proj4_init_rules(nullptr, initial_rules);
4984 }
4985 
4986 // ---------------------------------------------------------------------------
4987 
TEST_F(CApi,proj_is_equivalent_to_with_ctx)4988 TEST_F(CApi, proj_is_equivalent_to_with_ctx) {
4989     auto from_epsg = proj_create_from_database(m_ctxt, "EPSG", "7844",
4990                                                PJ_CATEGORY_CRS, false, nullptr);
4991     ObjectKeeper keeper_from_epsg(from_epsg);
4992     ASSERT_NE(from_epsg, nullptr);
4993 
4994     auto wkt = "GEOGCRS[\"GDA2020\",\n"
4995                "    DATUM[\"GDA2020\",\n"
4996                "        ELLIPSOID[\"GRS_1980\",6378137,298.257222101,\n"
4997                "            LENGTHUNIT[\"metre\",1]]],\n"
4998                "    PRIMEM[\"Greenwich\",0,\n"
4999                "        ANGLEUNIT[\"Degree\",0.0174532925199433]],\n"
5000                "    CS[ellipsoidal,2],\n"
5001                "        AXIS[\"geodetic latitude (Lat)\",north,\n"
5002                "            ORDER[1],\n"
5003                "            ANGLEUNIT[\"degree\",0.0174532925199433]],\n"
5004                "        AXIS[\"geodetic longitude (Lon)\",east,\n"
5005                "            ORDER[2],\n"
5006                "            ANGLEUNIT[\"degree\",0.0174532925199433]]]";
5007     auto from_wkt =
5008         proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr);
5009     ObjectKeeper keeper_from_wkt(from_wkt);
5010     EXPECT_NE(from_wkt, nullptr);
5011 
5012     EXPECT_TRUE(proj_is_equivalent_to_with_ctx(m_ctxt, from_epsg, from_wkt,
5013                                                PJ_COMP_EQUIVALENT));
5014 }
5015 
5016 // ---------------------------------------------------------------------------
5017 
TEST_F(CApi,datum_ensemble)5018 TEST_F(CApi, datum_ensemble) {
5019     auto wkt =
5020         "GEOGCRS[\"ETRS89\","
5021         "    ENSEMBLE[\"European Terrestrial Reference System 1989 ensemble\","
5022         "        MEMBER[\"European Terrestrial Reference Frame 1989\"],"
5023         "        MEMBER[\"European Terrestrial Reference Frame 1990\"],"
5024         "        MEMBER[\"European Terrestrial Reference Frame 1991\"],"
5025         "        MEMBER[\"European Terrestrial Reference Frame 1992\"],"
5026         "        MEMBER[\"European Terrestrial Reference Frame 1993\"],"
5027         "        MEMBER[\"European Terrestrial Reference Frame 1994\"],"
5028         "        MEMBER[\"European Terrestrial Reference Frame 1996\"],"
5029         "        MEMBER[\"European Terrestrial Reference Frame 1997\"],"
5030         "        MEMBER[\"European Terrestrial Reference Frame 2000\"],"
5031         "        MEMBER[\"European Terrestrial Reference Frame 2005\"],"
5032         "        MEMBER[\"European Terrestrial Reference Frame 2014\"],"
5033         "        ELLIPSOID[\"GRS 1980\",6378137,298.257222101,"
5034         "            LENGTHUNIT[\"metre\",1]],"
5035         "        ENSEMBLEACCURACY[0.1]],"
5036         "    PRIMEM[\"Greenwich\",0,"
5037         "        ANGLEUNIT[\"degree\",0.0174532925199433]],"
5038         "    CS[ellipsoidal,2],"
5039         "        AXIS[\"geodetic latitude (Lat)\",north,"
5040         "            ORDER[1],"
5041         "            ANGLEUNIT[\"degree\",0.0174532925199433]],"
5042         "        AXIS[\"geodetic longitude (Lon)\",east,"
5043         "            ORDER[2],"
5044         "            ANGLEUNIT[\"degree\",0.0174532925199433]]]";
5045     auto from_wkt =
5046         proj_create_from_wkt(m_ctxt, wkt, nullptr, nullptr, nullptr);
5047     ObjectKeeper keeper_from_wkt(from_wkt);
5048     EXPECT_NE(from_wkt, nullptr);
5049 
5050     auto datum = proj_crs_get_datum(m_ctxt, from_wkt);
5051     ObjectKeeper keeper_datum(datum);
5052     ASSERT_EQ(datum, nullptr);
5053 
5054     auto datum_ensemble = proj_crs_get_datum_ensemble(m_ctxt, from_wkt);
5055     ObjectKeeper keeper_datum_ensemble(datum_ensemble);
5056     ASSERT_NE(datum_ensemble, nullptr);
5057 
5058     ASSERT_EQ(proj_datum_ensemble_get_member_count(m_ctxt, datum_ensemble), 11);
5059     ASSERT_EQ(proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, -1),
5060               nullptr);
5061     ASSERT_EQ(proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, 11),
5062               nullptr);
5063 
5064     {
5065         auto member = proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, 0);
5066         ObjectKeeper keeper_member(member);
5067         ASSERT_NE(member, nullptr);
5068 
5069         EXPECT_EQ(proj_get_name(member),
5070                   std::string("European Terrestrial Reference Frame 1989"));
5071     }
5072 
5073     {
5074         auto member =
5075             proj_datum_ensemble_get_member(m_ctxt, datum_ensemble, 10);
5076         ObjectKeeper keeper_member(member);
5077         ASSERT_NE(member, nullptr);
5078 
5079         EXPECT_EQ(proj_get_name(member),
5080                   std::string("European Terrestrial Reference Frame 2014"));
5081     }
5082 
5083     ASSERT_EQ(proj_datum_ensemble_get_accuracy(m_ctxt, datum_ensemble), 0.1);
5084 
5085     auto datum_forced = proj_crs_get_datum_forced(m_ctxt, from_wkt);
5086     ObjectKeeper keeper_datum_forced(datum_forced);
5087     ASSERT_NE(datum_forced, nullptr);
5088 
5089     EXPECT_EQ(proj_get_name(datum_forced),
5090               std::string("European Terrestrial Reference System 1989"));
5091 
5092     auto cs = proj_crs_get_coordinate_system(m_ctxt, from_wkt);
5093     ObjectKeeper keeper_cs(cs);
5094     EXPECT_NE(cs, nullptr);
5095 
5096     {
5097         auto built_crs = proj_create_geographic_crs_from_datum(
5098             m_ctxt, proj_get_name(from_wkt), datum_ensemble, cs);
5099         ObjectKeeper keeper_built_crs(built_crs);
5100         EXPECT_NE(built_crs, nullptr);
5101 
5102         EXPECT_TRUE(proj_is_equivalent_to_with_ctx(m_ctxt, built_crs, from_wkt,
5103                                                    PJ_COMP_EQUIVALENT));
5104     }
5105 
5106     {
5107         auto built_crs = proj_create_geocentric_crs_from_datum(
5108             m_ctxt, proj_get_name(from_wkt), datum_ensemble, "metre", 1.0);
5109         ObjectKeeper keeper_built_crs(built_crs);
5110         EXPECT_NE(built_crs, nullptr);
5111     }
5112 }
5113 
5114 } // namespace
5115