diff --git a/data/sql/commit.sql b/data/sql/commit.sql index 9e8f777bb2..081d2a7f30 100644 --- a/data/sql/commit.sql +++ b/data/sql/commit.sql @@ -28,6 +28,13 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'corrupt definition of authority_list') WHERE (SELECT 1 FROM authority_list LIMIT 1) = 0; + -- check that the auth_name of all objects in object_view is recorded in builtin_authorities + SELECT RAISE(ABORT, 'One or several authorities referenced in object_view are missing in builtin_authorities') + WHERE EXISTS ( + SELECT DISTINCT o.auth_name FROM object_view o WHERE NOT EXISTS ( + SELECT 1 FROM builtin_authorities b WHERE o.auth_name = b.auth_name) + ); + -- check that a usage is registered for most objects where this is needed SELECT RAISE(ABORT, 'One or several objects lack a corresponding record in the usage table') WHERE EXISTS ( diff --git a/data/sql/metadata.sql b/data/sql/metadata.sql index 4cf768400b..3ea7dd0afd 100644 --- a/data/sql/metadata.sql +++ b/data/sql/metadata.sql @@ -7,7 +7,7 @@ -- DATABASE_LAYOUT_VERSION_MINOR constants in src/iso19111/factory.cpp must be -- updated as well. INSERT INTO "metadata" VALUES('DATABASE.LAYOUT.VERSION.MAJOR', 1); -INSERT INTO "metadata" VALUES('DATABASE.LAYOUT.VERSION.MINOR', 3); +INSERT INTO "metadata" VALUES('DATABASE.LAYOUT.VERSION.MINOR', 4); INSERT INTO "metadata" VALUES('EPSG.VERSION', 'v11.004'); INSERT INTO "metadata" VALUES('EPSG.DATE', '2024-02-24'); diff --git a/data/sql/proj_db_table_defs.sql b/data/sql/proj_db_table_defs.sql index 0a45ed5330..b14c8512da 100644 --- a/data/sql/proj_db_table_defs.sql +++ b/data/sql/proj_db_table_defs.sql @@ -306,6 +306,20 @@ CREATE TABLE vertical_crs( CONSTRAINT fk_vertical_crs_datum FOREIGN KEY (datum_auth_name, datum_code) REFERENCES vertical_datum(auth_name, code) ON DELETE CASCADE ) WITHOUT ROWID; +-- Authorities provided by the upstream PROJ +-- This is used to check unicity of object names +CREATE TABLE builtin_authorities(auth_name TEXT NOT NULL PRIMARY KEY) WITHOUT ROWID; +INSERT INTO builtin_authorities VALUES + ('EPSG'), + ('ESRI'), + ('IAU_2015'), + ('IGNF'), + ('NKG'), + ('NRCAN'), + ('OGC'), + ('PROJ') +; + CREATE TRIGGER vertical_crs_insert_trigger BEFORE INSERT ON vertical_crs FOR EACH ROW BEGIN @@ -313,6 +327,12 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on vertical_crs violates constraint: (auth_name, code) must not already exist in crs_view') WHERE EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.auth_name AND crs_view.code = NEW.code); + SELECT RAISE(ABORT, 'insert on vertical_crs violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) crs_view') + WHERE EXISTS (SELECT 1 FROM crs_view WHERE crs_view.name = NEW.name AND crs_view.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + AND NOT(NEW.auth_name = 'ESRI' and crs_view.table_name = 'geodetic_crs') -- some ESRI vertical CRS are an ellipsoidal height CRS derived from a geodetic CRS + ); + SELECT RAISE(ABORT, 'insert on vertical_crs violates constraint: datum must not be deprecated when vertical_crs is not deprecated') WHERE EXISTS(SELECT 1 FROM vertical_crs datum WHERE datum.auth_name = NEW.datum_auth_name AND datum.code = NEW.datum_code AND datum.deprecated != 0) AND NEW.deprecated = 0; @@ -723,6 +743,11 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on projected_crs violates constraint: (auth_name, code) must not already exist in crs_view') WHERE EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.auth_name AND crs_view.code = NEW.code); + SELECT RAISE(ABORT, 'insert on projected_crs violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) crs_view') + WHERE EXISTS (SELECT 1 FROM crs_view WHERE crs_view.name = NEW.name AND crs_view.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + ); + SELECT RAISE(ABORT, 'insert on projected_crs violates constraint: geodetic_crs must not be deprecated when projected_crs is not deprecated') WHERE EXISTS(SELECT 1 FROM geodetic_crs WHERE geodetic_crs.auth_name = NEW.geodetic_crs_auth_name AND geodetic_crs.code = NEW.geodetic_crs_code AND geodetic_crs.deprecated != 0 AND geodetic_crs.name NOT LIKE 'Unknown datum%' AND geodetic_crs.name NOT LIKE 'Unspecified datum%') AND NEW.deprecated = 0 AND NOT (NEW.auth_name = 'ESRI' AND NEW.geodetic_crs_auth_name != 'ESRI'); @@ -764,6 +789,11 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on compound_crs violates constraint: (auth_name, code) must not already exist in crs_view') WHERE EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.auth_name AND crs_view.code = NEW.code); + SELECT RAISE(ABORT, 'insert on compound_crs violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) crs_view') + WHERE EXISTS (SELECT 1 FROM crs_view WHERE crs_view.name = NEW.name AND crs_view.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + ); + SELECT RAISE(ABORT, 'insert on compound_crs violates constraint: horiz_crs(auth_name, code) not found') WHERE NOT EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.horiz_crs_auth_name AND crs_view.code = NEW.horiz_crs_code); @@ -1026,6 +1056,13 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on helmert_transformation violates constraint: (auth_name, code) must not already exist in coordinate_operation_with_conversion_view') WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.auth_name = NEW.auth_name AND covwv.code = NEW.code); + SELECT RAISE(ABORT, 'insert on helmert_transformation violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) coordinate_operation_with_conversion_view') + WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.name = NEW.name AND covwv.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + AND NEW.name != 'NKG_ETRF00 to ETRF96@2000.0' -- NKG:P1_2008_EE and NKG:P1_2008_FI have the same name + AND NEW.name != 'NKG_ETRF14 to ETRF96@2000.0' -- NKG:PAR_2020_EE and NKG:PAR_2020_FI have the same name + ); + SELECT RAISE(ABORT, 'insert on helmert_transformation violates constraint: translation_uom.type must be ''length''') WHERE (SELECT type FROM unit_of_measure WHERE unit_of_measure.auth_name = NEW.translation_uom_auth_name AND unit_of_measure.code = NEW.translation_uom_code) != 'length'; SELECT RAISE(ABORT, 'insert on helmert_transformation violates constraint: rotation_uom.type must be ''angle''') @@ -1096,6 +1133,15 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on grid_transformation violates constraint: (auth_name, code) must not already exist in coordinate_operation_with_conversion_view') WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.auth_name = NEW.auth_name AND covwv.code = NEW.code); + SELECT RAISE(ABORT, 'insert on grid_transformation violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) coordinate_operation_with_conversion_view') + WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.name = NEW.name AND covwv.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + AND NEW.name != 'NAD83(CSRS)v2 to NAD83(CSRS)v3 (1)' -- duplicate entry in EPSG + AND NEW.name != 'ETRS89 to ETRS89 + Baltic 1957 height (1)' -- duplicate entry in EPSG + AND NOT (NEW.description LIKE 'Reversible alternative to%' AND covwv.description NOT LIKE 'Reversible alternative to%') + AND NEW.code NOT LIKE '%_WITH_NAD83CSRSV7_INTERPOLATION' + ); + SELECT RAISE(ABORT, 'insert on grid_transformation violates constraint: source_crs(auth_name, code) not found') WHERE NOT EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.source_crs_auth_name AND crs_view.code = NEW.source_crs_code); @@ -1266,6 +1312,17 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on other_transformation violates constraint: (auth_name, code) must not already exist in coordinate_operation_with_conversion_view') WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.auth_name = NEW.auth_name AND covwv.code = NEW.code); + SELECT RAISE(ABORT, 'insert on other_transformation violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) coordinate_operation_with_conversion_view') + WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.name = NEW.name AND covwv.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + AND NEW.name != 'NKG_ETRF14 to ETRF93@2000.0' -- NKG:PAR_2020_NO and NKG:NKG_ETRF14_ETRF93_2000 have the same name + AND NEW.name != 'ETRF96@2000.0 to ETRF96@1997.56' -- NKG:ETRF96_2000_TO_ETRF96_1997_56 and NKG:EE_2020_INTRAPLATE have the same name + AND NEW.name != 'ETRF93@2000.0 to ETRF93@1995.0' -- NKG:ETRF93_2000_TO_ETRF93_1995 and NKG:NO_2020_INTRAPLATE have the same name + AND NEW.name != 'ETRF92@2000.0 to ETRF92@1994.704' -- NKG:ETRF92_2000_TO_ETRF92_1994 and NKG:DK_2020_INTRAPLATE have the same name + AND NEW.name != 'ETRF96@2000.0 to ETRF96@1997.0' -- NKG:ETRF96_2000_TO_ETRF96_1997 AND NKG:FI_2020_INTRAPLATE have the same name + AND NEW.name != 'ETRF97@2000.0 to ETRF97@1999.5' -- NKG:ETRF97_2000_TO_ETRF97_1999 and NKG:SE_2020_INTRAPLATE have the same name + ); + SELECT RAISE(ABORT, 'insert on other_transformation violates constraint: source_crs(auth_name, code) not found') WHERE NOT EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.source_crs_auth_name AND crs_view.code = NEW.source_crs_code); @@ -1311,6 +1368,11 @@ FOR EACH ROW BEGIN SELECT RAISE(ABORT, 'insert on concatenated_operation violates constraint: (auth_name, code) must not already exist in coordinate_operation_with_conversion_view') WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.auth_name = NEW.auth_name AND covwv.code = NEW.code); + SELECT RAISE(ABORT, 'insert on concatenated_operation violates constraint: name (of a non-deprecated entry) must not already exist in (a non-deprecated entry of) coordinate_operation_with_conversion_view') + WHERE EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.name = NEW.name AND covwv.deprecated = 0 AND NEW.deprecated = 0 + AND NEW.auth_name IN (SELECT auth_name FROM builtin_authorities WHERE auth_name != 'IGNF') + ); + SELECT RAISE(ABORT, 'insert on concatenated_operation violates constraint: source_crs(auth_name, code) not found') WHERE NOT EXISTS (SELECT 1 FROM crs_view WHERE crs_view.auth_name = NEW.source_crs_auth_name AND crs_view.code = NEW.source_crs_code); @@ -1469,8 +1531,8 @@ CREATE VIEW coordinate_operation_view AS ; CREATE VIEW coordinate_operation_with_conversion_view AS - SELECT auth_name, code, table_name AS type FROM coordinate_operation_view UNION ALL - SELECT auth_name, code, CAST('conversion' AS TEXT) FROM conversion_table; + SELECT auth_name, code, name, description, table_name AS type, deprecated FROM coordinate_operation_view UNION ALL + SELECT auth_name, code, name, description, CAST('conversion' AS TEXT) AS type, deprecated FROM conversion_table; CREATE VIEW crs_view AS SELECT CAST('geodetic_crs' AS TEXT) AS table_name, auth_name, code, name, type, diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index 5b4bfc4ec8..370aa9783a 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -126,7 +126,7 @@ constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE; constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1; // If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR // must be incremented. -constexpr int DATABASE_LAYOUT_VERSION_MINOR = 3; +constexpr int DATABASE_LAYOUT_VERSION_MINOR = 4; constexpr size_t N_MAX_PARAMS = 7; diff --git a/test/cli/testprojinfo_out.dist b/test/cli/testprojinfo_out.dist index 54bcc24102..f4f2fd6de6 100644 --- a/test/cli/testprojinfo_out.dist +++ b/test/cli/testprojinfo_out.dist @@ -1593,7 +1593,7 @@ CREATE TABLE unit_of_measure( Testing projinfo --dump-db-structure --output-id HOBU:XXXX EPSG:4326 | tail -n 4 INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR',1); -INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR',3); +INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR',4); INSERT INTO geodetic_crs VALUES('HOBU','XXXX','WGS 84','','geographic 2D','EPSG','6422','EPSG','6326',NULL,0); INSERT INTO usage VALUES('HOBU','USAGE_GEODETIC_CRS_XXXX','geodetic_crs','HOBU','XXXX','EPSG','1262','EPSG','1183'); diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp index 8f8854257c..a633cddf1f 100644 --- a/test/unit/test_factory.cpp +++ b/test/unit/test_factory.cpp @@ -1898,7 +1898,7 @@ class FactoryWithTmpDatabase : public ::testing::Test { ASSERT_TRUE(execute( "INSERT INTO helmert_transformation " - "VALUES('EPSG','DUMMY_HELMERT','name',NULL,'EPSG','9603','" + "VALUES('EPSG','DUMMY_HELMERT','dummy_helmert',NULL,'EPSG','9603','" "Geocentric translations (geog2D domain)','EPSG','4326'," "'EPSG','4326',44.0,-143." "0,-90.0,-294.0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL," @@ -1914,7 +1914,8 @@ class FactoryWithTmpDatabase : public ::testing::Test { ASSERT_TRUE(execute( "INSERT INTO grid_transformation " - "VALUES('EPSG','DUMMY_GRID_TRANSFORMATION','name',NULL," + "VALUES('EPSG','DUMMY_GRID_TRANSFORMATION'," + "'dummy_grid_transformation',NULL," "'EPSG','9615'" ",'NTv2','EPSG','4326','EPSG','4326',1.0,'EPSG','" "8656','Latitude and longitude difference " @@ -1936,7 +1937,8 @@ class FactoryWithTmpDatabase : public ::testing::Test { ASSERT_TRUE(execute( "INSERT INTO other_transformation " - "VALUES('EPSG','DUMMY_OTHER_TRANSFORMATION','name',NULL," + "VALUES('EPSG','DUMMY_OTHER_TRANSFORMATION'," + "'dummy_other_transformation',NULL," "'EPSG','9601','Longitude rotation'," "'EPSG','4326','EPSG','4326',0.0,'EPSG'" ",'8602','Longitude " @@ -1954,7 +1956,8 @@ class FactoryWithTmpDatabase : public ::testing::Test { << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation " - "VALUES('EPSG','DUMMY_CONCATENATED','name',NULL," + "VALUES('EPSG','DUMMY_CONCATENATED'," + "'dummy_concatenated',NULL," "'EPSG','4326','EPSG'" ",'4326',NULL,NULL,0);")) << last_error(); @@ -2381,7 +2384,8 @@ TEST_F(FactoryWithTmpDatabase, << last_error(); ASSERT_TRUE( execute("INSERT INTO other_transformation " - "VALUES('EPSG','4326_TO_OTHER_GEOG_CRS','name',NULL," + "VALUES('EPSG','4326_TO_OTHER_GEOG_CRS'," + "'4326_to_other_geog_crs',NULL," "'EPSG','9601','Longitude rotation'," "'EPSG','4326','EPSG','OTHER_GEOG_CRS',0.0,'EPSG'" ",'8602','Longitude " @@ -2392,7 +2396,8 @@ TEST_F(FactoryWithTmpDatabase, << last_error(); ASSERT_TRUE( execute("INSERT INTO other_transformation " - "VALUES('EPSG','OTHER_GEOG_CRS_TO_4326','name',NULL," + "VALUES('EPSG','OTHER_GEOG_CRS_TO_4326'," + "'other_geog_crs_to_4326',NULL," "'EPSG','9601','Longitude rotation'," "'EPSG','OTHER_GEOG_CRS','EPSG','4326',0.0,'EPSG'" ",'8602','Longitude " @@ -2402,7 +2407,8 @@ TEST_F(FactoryWithTmpDatabase, "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO concatenated_operation " - "VALUES('EPSG','DUMMY_CONCATENATED_2','name',NULL," + "VALUES('EPSG','DUMMY_CONCATENATED_2'," + "'dummy_concatenated_2',NULL," "'EPSG','4326','EPSG'" ",'4326',NULL,NULL,0);")) << last_error(); @@ -2441,7 +2447,7 @@ TEST_F(FactoryWithTmpDatabase, << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " - "VALUES('OTHER','OTHER_32631','WGS 84 / UTM zone " + "VALUES('OTHER','OTHER_32631','my WGS 84 / UTM zone " "31N',NULL,'EPSG','4400','OTHER','OTHER_4326'," "'EPSG','16031',NULL,0);")) << last_error(); @@ -2478,7 +2484,8 @@ TEST_F(FactoryWithTmpDatabase, ASSERT_TRUE(execute( "INSERT INTO grid_transformation " - "VALUES('OTHER','OTHER_GRID_TRANSFORMATION','name',NULL," + "VALUES('OTHER','OTHER_GRID_TRANSFORMATION'," + "'other_grid_transformation_2',NULL," "'EPSG','9615'" ",'NTv2','EPSG','4326','OTHER','OTHER_4326',1.0,'EPSG','" "8656','Latitude and longitude difference " @@ -3035,19 +3042,22 @@ TEST_F(FactoryWithTmpDatabase, custom_projected_crs) { populateWithFakeEPSG(); ASSERT_TRUE(execute("INSERT INTO projected_crs " - "VALUES('TEST_NS','TEST','my name',NULL,NULL," + "VALUES('TEST_NS','TEST'," + "'custom_projected_crs',NULL,NULL," "NULL,NULL,NULL,NULL,NULL," "'+proj=mbt_s +unused_flag',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " - "VALUES('TEST_NS','TEST_BOUND','my name',NULL," + "VALUES('TEST_NS','TEST_BOUND'," + "'custom_projected_crs2',NULL," "NULL,NULL,NULL,NULL,NULL,NULL," "'+proj=mbt_s +unused_flag +towgs84=1,2,3',0);")) << last_error(); ASSERT_TRUE(execute("INSERT INTO projected_crs " - "VALUES('TEST_NS','TEST_WRONG','my name',NULL," + "VALUES('TEST_NS','TEST_WRONG'," + "'custom_projected_crs3',NULL," "NULL,NULL,NULL,NULL,NULL,NULL," "'+proj=longlat',0);")) << last_error(); @@ -3097,7 +3107,7 @@ TEST_F(FactoryWithTmpDatabase, custom_projected_crs) { AuthorityFactory::create(DatabaseContext::create(m_ctxt), "TEST_NS"); { auto crs = factory->createProjectedCRS("TEST"); - EXPECT_EQ(*(crs->name()->description()), "my name"); + EXPECT_EQ(*(crs->name()->description()), "custom_projected_crs"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get()); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), @@ -3106,7 +3116,7 @@ TEST_F(FactoryWithTmpDatabase, custom_projected_crs) { } { auto crs = factory->createProjectedCRS("TEST_BOUND"); - EXPECT_EQ(*(crs->name()->description()), "my name"); + EXPECT_EQ(*(crs->name()->description()), "custom_projected_crs2"); EXPECT_EQ(crs->identifiers().size(), 1U); EXPECT_EQ(crs->derivingConversion()->targetCRS().get(), crs.get()); EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), @@ -3487,7 +3497,8 @@ TEST_F(FactoryWithTmpDatabase, ASSERT_TRUE(execute( "INSERT INTO other_transformation " - "VALUES('EPSG','NOOP_TRANSFORMATION_32631','name',NULL," + "VALUES('EPSG','NOOP_TRANSFORMATION_32631'," + "'NOOP_TRANSFORMATION_32631',NULL," "'PROJ','PROJString','+proj=noop'," "'EPSG','32631','EPSG','32631',0.0," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL," @@ -3506,7 +3517,8 @@ TEST_F(FactoryWithTmpDatabase, ASSERT_TRUE(execute( "INSERT INTO other_transformation " - "VALUES('EPSG','NOOP_TRANSFORMATION_4326','name',NULL," + "VALUES('EPSG','NOOP_TRANSFORMATION_4326'," + "'NOOP_TRANSFORMATION_4326',NULL," "'PROJ','PROJString','+proj=noop'," "'EPSG','4326','EPSG','4326',0.0," "NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,"