aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2019-11-04 22:35:57 +0100
committerGitHub <noreply@github.com>2019-11-04 22:35:57 +0100
commit34dc695402ba5d10248ea47bec3ab88ed950eccb (patch)
tree56bfb7962cca13095a85a93af4e372ffac2e0be2
parent1bee3d54b05d2f6bd406749126ff7d6ac26e7013 (diff)
parent67e987ed84e19dd0ce46bdc529e8a73010af2c66 (diff)
downloadPROJ-34dc695402ba5d10248ea47bec3ab88ed950eccb.tar.gz
PROJ-34dc695402ba5d10248ea47bec3ab88ed950eccb.zip
Merge pull request #1710 from rouault/geoid_model
Add support for GEOIDMODEL
-rw-r--r--data/projjson.schema.json11
-rw-r--r--data/sql/commit.sql16
-rw-r--r--data/sql/customizations.sql28
-rw-r--r--data/sql/grid_alternatives.sql77
-rw-r--r--data/sql/proj_db_table_defs.sql17
-rw-r--r--docs/source/usage/projjson.rst10
-rw-r--r--include/proj/internal/io_internal.hpp1
-rw-r--r--include/proj/io.hpp4
-rw-r--r--schemas/v0.2/projjson.schema.json981
-rw-r--r--scripts/reference_exported_symbols.txt1
-rw-r--r--src/iso19111/c_api.cpp79
-rw-r--r--src/iso19111/coordinateoperation.cpp143
-rw-r--r--src/iso19111/crs.cpp31
-rw-r--r--src/iso19111/factory.cpp22
-rw-r--r--src/iso19111/io.cpp129
-rw-r--r--src/iso19111/static.cpp1
-rw-r--r--src/proj_experimental.h13
-rw-r--r--test/cli/testprojinfo_out.dist16
-rw-r--r--test/unit/test_c_api.cpp109
-rw-r--r--test/unit/test_io.cpp121
-rw-r--r--test/unit/test_operation.cpp130
21 files changed, 1843 insertions, 97 deletions
diff --git a/data/projjson.schema.json b/data/projjson.schema.json
index 60fca6df..e71e5031 100644
--- a/data/projjson.schema.json
+++ b/data/projjson.schema.json
@@ -1,5 +1,5 @@
{
- "$id": "https://proj.org/schemas/v0.1/projjson.schema.json",
+ "$id": "https://proj.org/schemas/v0.2/projjson.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "Schema for PROJJSON",
"$comment": "This file exists both in data/ and in schemas/vXXX/. Keep both in sync. And if changing the value of $id, change PROJJSON_CURRENT_VERSION accordingly in io.cpp",
@@ -932,6 +932,15 @@
},
"datum_ensemble": { "$ref": "#/definitions/datum_ensemble" },
"coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "geoid_model": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" }
+ },
+ "required" : [ "name" ],
+ "additionalProperties": false
+ },
"$schema" : {},
"scope": {},
"area": {},
diff --git a/data/sql/commit.sql b/data/sql/commit.sql
index eb49828a..a708df0f 100644
--- a/data/sql/commit.sql
+++ b/data/sql/commit.sql
@@ -29,6 +29,22 @@ FOR EACH ROW BEGIN
WHERE (SELECT 1 FROM object_view LIMIT 1) = 0;
SELECT RAISE(ABORT, 'corrupt definition of authority_list')
WHERE (SELECT 1 FROM authority_list LIMIT 1) = 0;
+
+ -- check geoid_model table
+ SELECT RAISE(ABORT, 'missing GEOID99 in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID99');
+ SELECT RAISE(ABORT, 'missing GEOID03 in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID03');
+ SELECT RAISE(ABORT, 'missing GEOID06 in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID06');
+ SELECT RAISE(ABORT, 'missing GEOID09 in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID09');
+ SELECT RAISE(ABORT, 'missing GEOID12A in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID12A');
+ SELECT RAISE(ABORT, 'missing GEOID12B in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID12B');
+ SELECT RAISE(ABORT, 'missing GEOID18 in geoid_model')
+ WHERE NOT EXISTS(SELECT 1 FROM geoid_model WHERE name = 'GEOID18');
END;
INSERT INTO dummy DEFAULT VALUES;
DROP TRIGGER final_checks;
diff --git a/data/sql/customizations.sql b/data/sql/customizations.sql
index 99891dc1..6301bda9 100644
--- a/data/sql/customizations.sql
+++ b/data/sql/customizations.sql
@@ -83,15 +83,19 @@ INSERT INTO "helmert_transformation" VALUES('PROJ','WGS84_TO_WGS84_G1674','WGS 8
INSERT INTO "helmert_transformation" VALUES('PROJ','WGS84_TO_WGS84_G1762','WGS 84 to WGS 84 (G1762)','','Accuracy 2m','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4326','EPSG','9057','EPSG','1262',2.0,0,0,0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
INSERT INTO "helmert_transformation" VALUES('PROJ','WGS84_TO_WGS84_TRANSIT','WGS 84 to WGS 84 (Transit)','','Accuracy 2m','EPSG','9603','Geocentric translations (geog2D domain)','EPSG','4326','EPSG','8888','EPSG','1262',2.0,0,0,0,'EPSG','9001',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,'',0);
---- Remove a few supersessions ---
-
--- TODO: remove this once https://github.com/OSGeo/proj-datumgrid/issues/55 is dealt with
--- The following supersessions are for geoid2012, replaced by geoid2018
--- but we don't have geoid2018 yet as PROJ grids, so remove for now those
--- supersessions
--- INSERT INTO "supersession" VALUES('grid_transformation','EPSG','6326','grid_transformation','EPSG','9229','EPSG');
--- INSERT INTO "supersession" VALUES('grid_transformation','EPSG','7646','grid_transformation','EPSG','9230','EPSG');
--- INSERT INTO "supersession" VALUES('grid_transformation','EPSG','7647','grid_transformation','EPSG','9231','EPSG');
-DELETE FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = 'EPSG' AND superseded_code = '6326';
-DELETE FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = 'EPSG' AND superseded_code = '7646';
-DELETE FROM supersession WHERE superseded_table_name = 'grid_transformation' AND superseded_auth_name = 'EPSG' AND superseded_code = '7647';
+---- Geoid models -----
+
+INSERT INTO "geoid_model" SELECT 'GEOID99', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g1999%' AND deprecated = 0;
+
+INSERT INTO "geoid_model" SELECT 'GEOID03', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'geoid03%' AND deprecated = 0;
+
+INSERT INTO "geoid_model" SELECT 'GEOID06', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'geoid06%' AND deprecated = 0;
+
+INSERT INTO "geoid_model" SELECT 'GEOID09', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'geoid09%' AND deprecated = 0;
+
+-- Geoid12A and Geoid12B are identical
+INSERT INTO "geoid_model" SELECT 'GEOID12A', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g2012b%' AND deprecated = 0;
+
+INSERT INTO "geoid_model" SELECT 'GEOID12B', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g2012b%' AND deprecated = 0;
+
+INSERT INTO "geoid_model" SELECT 'GEOID18', auth_name, code FROM grid_transformation WHERE auth_name = 'EPSG' AND grid_name LIKE 'g2018%' AND deprecated = 0;
diff --git a/data/sql/grid_alternatives.sql b/data/sql/grid_alternatives.sql
index eb752e44..ab81a2fa 100644
--- a/data/sql/grid_alternatives.sql
+++ b/data/sql/grid_alternatives.sql
@@ -361,6 +361,74 @@ INSERT INTO grid_alternatives(original_grid_name,
NULL, NULL, NULL, NULL);
----------------------------
+-- US GEOID99 height models
+----------------------------
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u01.bin', 'g1999u01.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u02.bin', 'g1999u02.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u03.bin', 'g1999u03.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u04.bin', 'g1999u04.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u05.bin', 'g1999u05.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u06.bin', 'g1999u06.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u07.bin', 'g1999u07.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g1999u08.bin', 'g1999u08.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+-- Not mapped:
+-- g1999a01.gtx to g1999a04.gtx : Alaska
+-- g1999h01.gtx : Hawaii
+-- g1999p01.gtx : Puerto Rico
+
+----------------------------
+-- US GEOID03 height models
+----------------------------
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('geoid03_conus.bin', 'geoid03_conus.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+-- Not mapped:
+-- g2003a01.gtx to g2003a04.gtx : Alaska
+-- g2003h01.gtx : Hawaii
+---g2003p01.gtx : Puerto Rico
+
+----------------------------
+-- US GEOID06 height models
+----------------------------
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('geoid06_ak.bin', 'geoid06_ak.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+----------------------------
+-- US GEOID09 height models
+----------------------------
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('geoid09_ak.bin', 'geoid09_ak.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('geoid09_conus.bin', 'geoid09_conus.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+-- Not mapped:
+-- g2009h01.gtx : Hawaii
+-- g2009g01.gtx : Guam and Northern Mariana Islands.
+-- g2009s01.gtx : American Samoa.
+-- g2009p01.gtx : Puerto Rico / U.S. Vigin Islands.
+
+----------------------------
-- US GEOID12B height models
----------------------------
@@ -444,6 +512,15 @@ INSERT INTO grid_alternatives(original_grid_name,
'proj-datumgrid-north-america',
NULL, NULL, NULL, NULL);
+----------------------------
+-- US GEOID18 height models
+----------------------------
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g2018u0.bin', 'g2018u0.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
+
+INSERT INTO grid_alternatives(original_grid_name, proj_grid_name, proj_grid_format, proj_method, inverse_direction, package_name, url, direct_download, open_license, directory)
+ VALUES ('g2018p0.bin', 'g2018p0.gtx', 'GTX', 'vgridshift', 1, 'proj-datumgrid-north-america', NULL, NULL, NULL, NULL);
----------------------------
-- French vertical grids
diff --git a/data/sql/proj_db_table_defs.sql b/data/sql/proj_db_table_defs.sql
index 8a02acf4..a97e75a8 100644
--- a/data/sql/proj_db_table_defs.sql
+++ b/data/sql/proj_db_table_defs.sql
@@ -1365,6 +1365,23 @@ FOR EACH ROW BEGIN
END;
+
+CREATE TABLE geoid_model(
+ name TEXT NOT NULL,
+ operation_auth_name TEXT NOT NULL,
+ operation_code TEXT NOT NULL,
+ CONSTRAINT pk_geoid_model PRIMARY KEY (name, operation_auth_name, operation_code)
+ -- CONSTRATINT fk_geoid_model_operation FOREIGN KEY (operation_auth_name, operation_code) REFERENCES coordinate_operation(auth_name, code)
+);
+
+CREATE TRIGGER geoid_model_insert_trigger
+BEFORE INSERT ON geoid_model
+FOR EACH ROW BEGIN
+ SELECT RAISE(ABORT, 'insert on geoid_model violates constraint: (operation_auth_name, operation_code) must already exist in coordinate_operation_with_conversion_view')
+ WHERE NOT EXISTS (SELECT 1 FROM coordinate_operation_with_conversion_view covwv WHERE covwv.auth_name = NEW.operation_auth_name AND covwv.code = NEW.operation_code);
+END;
+
+
CREATE TABLE alias_name(
table_name TEXT NOT NULL CHECK (table_name IN (
'unit_of_measure', 'celestial_body', 'ellipsoid',
diff --git a/docs/source/usage/projjson.rst b/docs/source/usage/projjson.rst
index 09123711..c7183185 100644
--- a/docs/source/usage/projjson.rst
+++ b/docs/source/usage/projjson.rst
@@ -13,13 +13,19 @@ the same as WKT2:2019.
PROJJSON is available as input and output of PROJ since PROJ 6.2.
-The current version is 0.1.
+The current version is 0.2.
Schema
------
A JSON schema of its grammar is available at
-https://proj.org/schemas/v0.1/projjson.schema.json
+https://proj.org/schemas/v0.2/projjson.schema.json
+
+History
+-------
+
+* v0.2: addition of geoid_model in VerticalCRS object.
+* v0.1: initial version for PROJ 6.2
Content
-------
diff --git a/include/proj/internal/io_internal.hpp b/include/proj/internal/io_internal.hpp
index 781dfa6c..1e2508cc 100644
--- a/include/proj/internal/io_internal.hpp
+++ b/include/proj/internal/io_internal.hpp
@@ -134,6 +134,7 @@ class WKTConstants {
static const std::string BASEPARAMCRS;
static const std::string BASETIMECRS;
static const std::string VERSION;
+ static const std::string GEOIDMODEL; // WKT2-2019
// WKT2 alternate (longer or shorter)
static const std::string GEODETICCRS;
diff --git a/include/proj/io.hpp b/include/proj/io.hpp
index f8ee1c97..17f0fea5 100644
--- a/include/proj/io.hpp
+++ b/include/proj/io.hpp
@@ -1118,6 +1118,10 @@ class PROJ_GCC_DLL AuthorityFactory {
getPreferredHubGeodeticReferenceFrames(
const std::string &geodeticReferenceFrameCode) const;
+ PROJ_INTERNAL std::vector<operation::CoordinateOperationNNPtr>
+ getTransformationsForGeoid(const std::string &geoidName,
+ bool usePROJAlternativeGridNames) const;
+
//! @endcond
protected:
diff --git a/schemas/v0.2/projjson.schema.json b/schemas/v0.2/projjson.schema.json
new file mode 100644
index 00000000..e71e5031
--- /dev/null
+++ b/schemas/v0.2/projjson.schema.json
@@ -0,0 +1,981 @@
+{
+ "$id": "https://proj.org/schemas/v0.2/projjson.schema.json",
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "description": "Schema for PROJJSON",
+ "$comment": "This file exists both in data/ and in schemas/vXXX/. Keep both in sync. And if changing the value of $id, change PROJJSON_CURRENT_VERSION accordingly in io.cpp",
+
+ "oneOf": [
+ { "$ref": "#/definitions/crs" },
+ { "$ref": "#/definitions/datum" },
+ { "$ref": "#/definitions/datum_ensemble" },
+ { "$ref": "#/definitions/ellipsoid" },
+ { "$ref": "#/definitions/prime_meridian" },
+ { "$ref": "#/definitions/single_operation" },
+ { "$ref": "#/definitions/concatenated_operation" }
+ ],
+
+ "definitions": {
+
+ "abridged_transformation": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["AbridgedTransformation"] },
+ "name": { "type": "string" },
+ "method": { "$ref": "#/definitions/method" },
+ "parameters": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/parameter_value" }
+ },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "method", "parameters" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "axis": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["Axis"] },
+ "name": { "type": "string" },
+ "abbreviation": { "type": "string" },
+ "direction": { "type": "string",
+ "enum": [ "north",
+ "northNorthEast",
+ "northEast",
+ "eastNorthEast",
+ "east",
+ "eastSouthEast",
+ "southEast",
+ "southSouthEast",
+ "south",
+ "southSouthWest",
+ "southWest",
+ "westSouthWest",
+ "west",
+ "westNorthWest",
+ "northWest",
+ "northNorthWest",
+ "up",
+ "down",
+ "geocentricX",
+ "geocentricY",
+ "geocentricZ",
+ "columnPositive",
+ "columnNegative",
+ "rowPositive",
+ "rowNegative",
+ "displayRight",
+ "displayLeft",
+ "displayUp",
+ "displayDown",
+ "forward",
+ "aft",
+ "port",
+ "starboard",
+ "clockwise",
+ "counterClockwise",
+ "towards",
+ "awayFrom",
+ "future",
+ "past",
+ "unspecified" ] },
+ "unit": { "$ref": "#/definitions/unit" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "abbreviation", "direction" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "bbox": {
+ "type": "object",
+ "properties": {
+ "east_longitude": { "type": "number" },
+ "west_longitude": { "type": "number" },
+ "south_latitude": { "type": "number" },
+ "north_latitude": { "type": "number" }
+ },
+ "required" : [ "east_longitude", "west_longitude",
+ "south_latitude", "north_latitude" ],
+ "additionalProperties": false
+ },
+
+ "bound_crs": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["BoundCRS"] },
+ "source_crs": { "$ref": "#/definitions/crs" },
+ "target_crs": { "$ref": "#/definitions/crs" },
+ "transformation": { "$ref": "#/definitions/abridged_transformation" }
+ },
+ "required" : [ "source_crs", "target_crs", "transformation" ],
+ "additionalProperties": false
+ },
+
+ "compound_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["CompoundCRS"] },
+ "name": { "type": "string" },
+ "components": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/crs" }
+ },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "components" ],
+ "additionalProperties": false
+ },
+
+ "concatenated_operation": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["ConcatenatedOperation"] },
+ "name": { "type": "string" },
+ "source_crs": { "$ref": "#/definitions/crs" },
+ "target_crs": { "$ref": "#/definitions/crs" },
+ "steps": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/single_operation" }
+ },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "source_crs", "target_crs", "steps" ],
+ "additionalProperties": false
+ },
+
+ "conversion": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["Conversion"] },
+ "name": { "type": "string" },
+ "method": { "$ref": "#/definitions/method" },
+ "parameters": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/parameter_value" }
+ },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "method" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "coordinate_system": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["CoordinateSystem"] },
+ "name": { "type": "string" },
+ "subtype": { "type": "string",
+ "enum": ["Cartesian",
+ "spherical",
+ "ellipsoidal",
+ "vertical",
+ "ordinal",
+ "parametric",
+ "TemporalDateTime",
+ "TemporalCount",
+ "TemporalMeasure"] },
+ "axis": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/axis" }
+ },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "subtype", "axis" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "crs": {
+ "oneOf": [
+ { "$ref": "#/definitions/bound_crs" },
+ { "$ref": "#/definitions/compound_crs" },
+ { "$ref": "#/definitions/derived_engineering_crs" },
+ { "$ref": "#/definitions/derived_geodetic_crs" },
+ { "$ref": "#/definitions/derived_parametric_crs" },
+ { "$ref": "#/definitions/derived_projected_crs" },
+ { "$ref": "#/definitions/derived_temporal_crs" },
+ { "$ref": "#/definitions/derived_vertical_crs" },
+ { "$ref": "#/definitions/engineering_crs" },
+ { "$ref": "#/definitions/geodetic_crs" },
+ { "$ref": "#/definitions/parametric_crs" },
+ { "$ref": "#/definitions/projected_crs" },
+ { "$ref": "#/definitions/temporal_crs" },
+ { "$ref": "#/definitions/vertical_crs" }
+ ]
+ },
+
+ "datum": {
+ "oneOf": [
+ { "$ref": "#/definitions/geodetic_reference_frame" },
+ { "$ref": "#/definitions/vertical_reference_frame" },
+ { "$ref": "#/definitions/dynamic_geodetic_reference_frame" },
+ { "$ref": "#/definitions/dynamic_vertical_reference_frame" },
+ { "$ref": "#/definitions/temporal_datum" },
+ { "$ref": "#/definitions/parametric_datum" },
+ { "$ref": "#/definitions/engineering_datum" }
+ ]
+ },
+
+ "datum_ensemble": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["DatumEnsemble"] },
+ "name": { "type": "string" },
+ "members": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ }
+ },
+ "ellipsoid": { "$ref": "#/definitions/ellipsoid" },
+ "accuracy": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "members", "accuracy" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "derived_engineering_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["DerivedEngineeringCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/engineering_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "derived_geodetic_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["DerivedGeodeticCRS",
+ "DerivedGeographicCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/geodetic_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "derived_parametric_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["DerivedParametricCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/parametric_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "derived_projected_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["DerivedProjectedCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/projected_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "derived_temporal_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["DerivedTemporalCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/temporal_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "derived_vertical_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["DerivedVerticalCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/vertical_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "dynamic_geodetic_reference_frame": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/geodetic_reference_frame" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["DynamicGeodeticReferenceFrame"] },
+ "name": {},
+ "anchor": {},
+ "ellipsoid": {},
+ "prime_meridian": {},
+ "frame_reference_epoch": { "type": "number" },
+ "deformation_model": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "ellipsoid", "frame_reference_epoch" ],
+ "additionalProperties": false
+ },
+
+ "dynamic_vertical_reference_frame": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/vertical_reference_frame" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["DynamicVerticalReferenceFrame"] },
+ "name": {},
+ "anchor": {},
+ "frame_reference_epoch": { "type": "number" },
+ "deformation_model": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "frame_reference_epoch" ],
+ "additionalProperties": false
+ },
+
+ "ellipsoid": {
+ "type": "object",
+ "oneOf":[
+ {
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["Ellipsoid"] },
+ "name": { "type": "string" },
+ "semi_major_axis": { "$ref": "#/definitions/value_in_metre_or_value_and_unit" },
+ "semi_minor_axis": { "$ref": "#/definitions/value_in_metre_or_value_and_unit" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "semi_major_axis", "semi_minor_axis" ],
+ "additionalProperties": false
+ },
+ {
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["Ellipsoid"] },
+ "name": { "type": "string" },
+ "semi_major_axis": { "$ref": "#/definitions/value_in_metre_or_value_and_unit" },
+ "inverse_flattening": { "type": "number" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "semi_major_axis", "inverse_flattening" ],
+ "additionalProperties": false
+ },
+ {
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["Ellipsoid"] },
+ "name": { "type": "string" },
+ "radius": { "$ref": "#/definitions/value_in_metre_or_value_and_unit" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "radius" ],
+ "additionalProperties": false
+ }
+ ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ]
+ },
+
+ "engineering_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["EngineeringCRS"] },
+ "name": { "type": "string" },
+ "datum": { "$ref": "#/definitions/engineering_datum" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "datum" ],
+ "additionalProperties": false
+ },
+
+ "engineering_datum": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["EngineeringDatum"] },
+ "name": { "type": "string" },
+ "anchor": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name" ],
+ "additionalProperties": false
+ },
+
+ "geodetic_crs": {
+ "type": "object",
+ "properties": {
+ "type": { "type": "string", "enum": ["GeodeticCRS", "GeographicCRS"] },
+ "name": { "type": "string" },
+ "datum": {
+ "oneOf": [
+ { "$ref": "#/definitions/geodetic_reference_frame" },
+ { "$ref": "#/definitions/dynamic_geodetic_reference_frame" }
+ ]
+ },
+ "datum_ensemble": { "$ref": "#/definitions/datum_ensemble" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name" ],
+ "description": "One and only one of datum and datum_ensemble must be provided",
+ "allOf": [
+ { "$ref": "#/definitions/object_usage" },
+ { "$ref": "#/definitions/one_and_only_one_of_datum_or_datum_ensemble" }
+ ],
+ "additionalProperties": false
+ },
+
+ "geodetic_reference_frame": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["GeodeticReferenceFrame"] },
+ "name": { "type": "string" },
+ "anchor": { "type": "string" },
+ "ellipsoid": { "$ref": "#/definitions/ellipsoid" },
+ "prime_meridian": { "$ref": "#/definitions/prime_meridian" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "ellipsoid" ],
+ "additionalProperties": false
+ },
+
+ "id": {
+ "type": "object",
+ "properties": {
+ "authority": { "type": "string" },
+ "code": {
+ "oneOf": [ { "type": "string" }, { "type": "integer" } ]
+ }
+ },
+ "required" : [ "authority", "code" ],
+ "additionalProperties": false
+ },
+
+ "ids": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/id" }
+ },
+
+ "method": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["OperationMethod"]},
+ "name": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "id_ids_mutually_exclusive": {
+ "not": {
+ "type": "object",
+ "required": [ "id", "ids" ]
+ }
+ },
+
+ "one_and_only_one_of_datum_or_datum_ensemble": {
+ "allOf": [
+ {
+ "not": {
+ "type": "object",
+ "required": [ "datum", "datum_ensemble" ]
+ }
+ },
+ {
+ "oneOf": [
+ { "type": "object", "required": ["datum"] },
+ { "type": "object", "required": ["datum_ensemble"] }
+ ]
+ }
+ ]
+ },
+
+ "object_usage": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "scope": { "type": "string" },
+ "area": { "type": "string" },
+ "bbox": { "$ref": "#/definitions/bbox" },
+ "remarks": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "usages": { "$ref": "#/definitions/usages" },
+ "remarks": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ]
+ }
+ ]
+ },
+
+ "parameter_value": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["ParameterValue"] },
+ "name": { "type": "string" },
+ "value": {
+ "oneOf": [
+ { "type": "string" },
+ { "type": "number" }
+ ]
+ },
+ "unit": { "$ref": "#/definitions/unit" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name", "value" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "parametric_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["ParametricCRS"] },
+ "name": { "type": "string" },
+ "datum": { "$ref": "#/definitions/parametric_datum" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "datum" ],
+ "additionalProperties": false
+ },
+
+ "parametric_datum": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["ParametricDatum"] },
+ "name": { "type": "string" },
+ "anchor": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name" ],
+ "additionalProperties": false
+ },
+
+ "prime_meridian": {
+ "type": "object",
+ "properties": {
+ "$schema" : { "type": "string" },
+ "type": { "type": "string", "enum": ["PrimeMeridian"] },
+ "name": { "type": "string" },
+ "longitude": { "$ref": "#/definitions/value_and_unit" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "name" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ },
+
+ "single_operation": {
+ "oneOf": [
+ { "$ref": "#/definitions/conversion" },
+ { "$ref": "#/definitions/transformation" }
+ ]
+ },
+
+ "projected_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["ProjectedCRS"] },
+ "name": { "type": "string" },
+ "base_crs": { "$ref": "#/definitions/geodetic_crs" },
+ "conversion": { "$ref": "#/definitions/conversion" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "base_crs", "conversion", "coordinate_system" ],
+ "additionalProperties": false
+ },
+
+ "temporal_crs": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["TemporalCRS"] },
+ "name": { "type": "string" },
+ "datum": { "$ref": "#/definitions/temporal_datum" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "datum" ],
+ "additionalProperties": false
+ },
+
+ "temporal_datum": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["TemporalDatum"] },
+ "name": { "type": "string" },
+ "calendar": { "type": "string" },
+ "time_origin": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "calendar" ],
+ "additionalProperties": false
+ },
+
+ "transformation": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["Transformation"] },
+ "name": { "type": "string" },
+ "source_crs": { "$ref": "#/definitions/crs" },
+ "target_crs": { "$ref": "#/definitions/crs" },
+ "interpolation_crs": { "$ref": "#/definitions/crs" },
+ "method": { "$ref": "#/definitions/method" },
+ "parameters": {
+ "type": "array",
+ "items": { "$ref": "#/definitions/parameter_value" }
+ },
+ "accuracy": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name", "source_crs", "target_crs", "method", "parameters" ],
+ "additionalProperties": false
+ },
+
+ "unit": {
+ "oneOf": [
+ {
+ "type": "string",
+ "enum": ["metre", "degree", "unity"]
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": { "type": "string",
+ "enum": ["LinearUnit", "AngularUnit", "ScaleUnit",
+ "TimeUnit", "ParametricUnit", "Unit"] },
+ "name": { "type": "string" },
+ "conversion_factor": { "type": "number" },
+ "id": { "$ref": "#/definitions/id" },
+ "ids": { "$ref": "#/definitions/ids" }
+ },
+ "required" : [ "type", "name" ],
+ "allOf": [
+ { "$ref": "#/definitions/id_ids_mutually_exclusive" }
+ ],
+ "additionalProperties": false
+ }
+ ]
+ },
+
+ "usages": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "scope": { "type": "string" },
+ "area": { "type": "string" },
+ "bbox": { "$ref": "#/definitions/bbox" }
+ },
+ "additionalProperties": false
+ }
+ },
+
+ "value_and_unit": {
+ "type": "object",
+ "properties": {
+ "value": { "type": "number" },
+ "unit": { "$ref": "#/definitions/unit" }
+ },
+ "required" : [ "value", "unit" ],
+ "additionalProperties": false
+ },
+
+ "value_in_metre_or_value_and_unit": {
+ "oneOf": [
+ { "type": "number" },
+ { "$ref": "#/definitions/value_and_unit" }
+ ]
+ },
+
+ "vertical_crs": {
+ "type": "object",
+ "properties": {
+ "type": { "type": "string", "enum": ["VerticalCRS"] },
+ "name": { "type": "string" },
+ "datum": {
+ "oneOf": [
+ { "$ref": "#/definitions/vertical_reference_frame" },
+ { "$ref": "#/definitions/dynamic_vertical_reference_frame" }
+ ]
+ },
+ "datum_ensemble": { "$ref": "#/definitions/datum_ensemble" },
+ "coordinate_system": { "$ref": "#/definitions/coordinate_system" },
+ "geoid_model": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "id": { "$ref": "#/definitions/id" }
+ },
+ "required" : [ "name" ],
+ "additionalProperties": false
+ },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name"],
+ "description": "One and only one of datum and datum_ensemble must be provided",
+ "allOf": [
+ { "$ref": "#/definitions/object_usage" },
+ { "$ref": "#/definitions/one_and_only_one_of_datum_or_datum_ensemble" }
+ ],
+ "additionalProperties": false
+ },
+
+ "vertical_reference_frame": {
+ "type": "object",
+ "allOf": [{ "$ref": "#/definitions/object_usage" }],
+ "properties": {
+ "type": { "type": "string", "enum": ["VerticalReferenceFrame"] },
+ "name": { "type": "string" },
+ "anchor": { "type": "string" },
+ "$schema" : {},
+ "scope": {},
+ "area": {},
+ "bbox": {},
+ "usages": {},
+ "remarks": {},
+ "id": {}, "ids": {}
+ },
+ "required" : [ "name" ],
+ "additionalProperties": false
+ }
+
+ }
+}
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index cb568566..67d4837f 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -899,6 +899,7 @@ proj_create_operations
proj_create_projected_crs
proj_create_transformation
proj_create_vertical_crs
+proj_create_vertical_crs_ex
proj_crs_alter_cs_angular_unit
proj_crs_alter_cs_linear_unit
proj_crs_alter_geodetic_crs
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 337de313..f3badb51 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -2615,13 +2615,19 @@ int proj_coordoperation_get_method_info(PJ_CONTEXT *ctx,
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
-static PropertyMap createPropertyMapName(const char *c_name) {
+static PropertyMap createPropertyMapName(const char *c_name,
+ const char *auth_name = nullptr,
+ const char *code = nullptr) {
std::string name(c_name ? c_name : "unnamed");
PropertyMap properties;
if (ends_with(name, " (deprecated)")) {
name.resize(name.size() - strlen(" (deprecated)"));
properties.set(common::IdentifiedObject::DEPRECATED_KEY, true);
}
+ if (auth_name && code) {
+ properties.set(metadata::Identifier::CODESPACE_KEY, auth_name);
+ properties.set(metadata::Identifier::CODE_KEY, code);
+ }
return properties.set(common::IdentifiedObject::NAME_KEY, name);
}
@@ -2946,15 +2952,76 @@ PJ *proj_create_vertical_crs(PJ_CONTEXT *ctx, const char *crs_name,
const char *datum_name, const char *linear_units,
double linear_units_conv) {
+ return proj_create_vertical_crs_ex(
+ ctx, crs_name, datum_name, nullptr, nullptr, linear_units,
+ linear_units_conv, nullptr, nullptr, nullptr, nullptr, nullptr);
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Create a VerticalCRS
+ *
+ * The returned object must be unreferenced with proj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * This is an extented (_ex) version of proj_create_vertical_crs() that adds
+ * the capability of defining a geoid model.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param datum_name Name of the VerticalReferenceFrame. Or NULL
+ * @param datum_auth_name Authority name of the VerticalReferenceFrame. Or NULL
+ * @param datum_code Code of the VerticalReferenceFrame. Or NULL
+ * @param linear_units Name of the linear units. Or NULL for Metre
+ * @param linear_units_conv Conversion factor from the linear unit to metre. Or
+ * 0 for Metre if linear_units == NULL. Otherwise should be not NULL
+ * @param geoid_model_name Geoid model name, or NULL. Can be a name from the
+ * geoid_model name or a string "PROJ foo.gtx"
+ * @param geoid_model_auth_name Authority name of the transformation for
+ * the geoid model. or NULL
+ * @param geoid_model_code Code of the transformation for
+ * the geoid model. or NULL
+ * @param geoid_geog_crs Geographic CRS for the geoid transformation, or NULL.
+ * @param options should be set to NULL for now
+ * @return Object of type VerticalCRS that must be unreferenced with
+ * proj_destroy(), or NULL in case of error.
+ */
+PJ *proj_create_vertical_crs_ex(
+ PJ_CONTEXT *ctx, const char *crs_name, const char *datum_name,
+ const char *datum_auth_name, const char *datum_code,
+ const char *linear_units, double linear_units_conv,
+ const char *geoid_model_name, const char *geoid_model_auth_name,
+ const char *geoid_model_code, const PJ *geoid_geog_crs,
+ const char *const *options) {
SANITIZE_CTX(ctx);
+ (void)options;
try {
const UnitOfMeasure linearUnit(
createLinearUnit(linear_units, linear_units_conv));
- auto datum =
- VerticalReferenceFrame::create(createPropertyMapName(datum_name));
- auto vertCRS = VerticalCRS::create(
- createPropertyMapName(crs_name), datum,
- cs::VerticalCS::createGravityRelatedHeight(linearUnit));
+ auto datum = VerticalReferenceFrame::create(
+ createPropertyMapName(datum_name, datum_auth_name, datum_code));
+ auto props = createPropertyMapName(crs_name);
+ auto cs = cs::VerticalCS::createGravityRelatedHeight(linearUnit);
+ if (geoid_model_name) {
+ auto propsModel = createPropertyMapName(
+ geoid_model_name, geoid_model_auth_name, geoid_model_code);
+ const auto vertCRSWithoutGeoid =
+ VerticalCRS::create(props, datum, cs);
+ const auto interpCRS =
+ geoid_geog_crs && std::dynamic_pointer_cast<GeographicCRS>(
+ geoid_geog_crs->iso_obj)
+ ? std::dynamic_pointer_cast<CRS>(geoid_geog_crs->iso_obj)
+ : nullptr;
+ const auto model(Transformation::create(
+ propsModel, vertCRSWithoutGeoid, GeographicCRS::EPSG_4979,
+ interpCRS,
+ OperationMethod::create(PropertyMap(),
+ std::vector<OperationParameterNNPtr>()),
+ {}, {}));
+ props.set("GEOID_MODEL", model);
+ }
+ auto vertCRS = VerticalCRS::create(props, datum, cs);
return pj_obj_create(ctx, vertCRS);
} catch (const std::exception &e) {
proj_log_error(ctx, __FUNCTION__, e.what());
diff --git a/src/iso19111/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 0983c471..c51b4d3d 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -10480,6 +10480,12 @@ struct CoordinateOperationFactory::Private {
std::vector<CoordinateOperationNNPtr> &res);
static std::vector<CoordinateOperationNNPtr>
+ createOperationsGeogToVertFromGeoid(const crs::CRSNNPtr &sourceCRS,
+ const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst,
+ Context &context);
+
+ static std::vector<CoordinateOperationNNPtr>
createOperationsGeogToVertWithIntermediateVert(
const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
const crs::VerticalCRS *vertDst, Context &context);
@@ -12813,6 +12819,18 @@ bool CoordinateOperationFactory::Private::createOperationsFromDatabase(
ENTER_FUNCTION();
+ if (geogSrc && vertDst) {
+ res = createOperationsGeogToVertFromGeoid(sourceCRS, targetCRS, vertDst,
+ context);
+ } else if (geogDst && vertSrc) {
+ res = applyInverse(createOperationsGeogToVertFromGeoid(
+ targetCRS, sourceCRS, vertSrc, context));
+ }
+
+ if (!res.empty()) {
+ return true;
+ }
+
res = findOpsInRegistryDirect(sourceCRS, targetCRS, context);
// If we get at least a result with perfect accuracy, do not
@@ -12936,6 +12954,116 @@ findCandidateVertCRSForDatum(const io::AuthorityFactoryPtr &authFactory,
// ---------------------------------------------------------------------------
+std::vector<CoordinateOperationNNPtr>
+CoordinateOperationFactory::Private::createOperationsGeogToVertFromGeoid(
+ const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
+ const crs::VerticalCRS *vertDst, Private::Context &context) {
+
+ ENTER_FUNCTION();
+
+ const auto useTransf = [&targetCRS, &context,
+ vertDst](const CoordinateOperationNNPtr &op) {
+ const auto targetOp =
+ dynamic_cast<const crs::VerticalCRS *>(op->targetCRS().get());
+ assert(targetOp);
+ if (targetOp->_isEquivalentTo(
+ vertDst, util::IComparable::Criterion::EQUIVALENT)) {
+ return op;
+ }
+ std::vector<CoordinateOperationNNPtr> tmp;
+ createOperationsVertToVert(NN_NO_CHECK(op->targetCRS()), targetCRS,
+ context, targetOp, vertDst, tmp);
+ assert(!tmp.empty());
+ auto ret = ConcatenatedOperation::createComputeMetadata(
+ {op, tmp.front()}, !allowEmptyIntersection);
+ return ret;
+ };
+
+ const auto getProjGeoidTransformation = [&sourceCRS, &targetCRS, &vertDst](
+ const CoordinateOperationNNPtr &model,
+ const std::string &projFilename) {
+
+ const auto getNameVertCRSMetre = [](const std::string &name) {
+ if (name.empty())
+ return std::string("unnamed");
+ auto ret(name);
+ bool haveOriginalUnit = false;
+ if (name.back() == ')') {
+ const auto pos = ret.rfind(" (");
+ if (pos != std::string::npos) {
+ haveOriginalUnit = true;
+ ret = ret.substr(0, pos);
+ }
+ }
+ const auto pos = ret.rfind(" depth");
+ if (pos != std::string::npos) {
+ ret = ret.substr(0, pos) + " height";
+ }
+ if (!haveOriginalUnit) {
+ ret += " (metre)";
+ }
+ return ret;
+ };
+
+ const auto &axis = vertDst->coordinateSystem()->axisList()[0];
+ const auto geogSrcCRS =
+ dynamic_cast<crs::GeographicCRS *>(model->interpolationCRS().get())
+ ? NN_NO_CHECK(model->interpolationCRS())
+ : sourceCRS;
+ const auto vertCRSMetre =
+ axis->unit() == common::UnitOfMeasure::METRE &&
+ axis->direction() == cs::AxisDirection::UP
+ ? targetCRS
+ : util::nn_static_pointer_cast<crs::CRS>(
+ crs::VerticalCRS::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ getNameVertCRSMetre(targetCRS->nameStr())),
+ vertDst->datum(), vertDst->datumEnsemble(),
+ cs::VerticalCS::createGravityRelatedHeight(
+ common::UnitOfMeasure::METRE)));
+ const auto properties = util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ buildOpName("Transformation", vertCRSMetre, geogSrcCRS));
+ return Transformation::createGravityRelatedHeightToGeographic3D(
+ properties, vertCRSMetre, geogSrcCRS, nullptr, projFilename, {});
+ };
+
+ std::vector<CoordinateOperationNNPtr> res;
+ const auto &authFactory = context.context->getAuthorityFactory();
+ if (authFactory) {
+ const auto &models = vertDst->geoidModel();
+ for (const auto &model : models) {
+ const auto &modelName = model->nameStr();
+ const auto transformations =
+ starts_with(modelName, "PROJ ")
+ ? std::vector<
+ CoordinateOperationNNPtr>{getProjGeoidTransformation(
+ model, modelName.substr(strlen("PROJ ")))}
+ : authFactory->getTransformationsForGeoid(
+ modelName,
+ context.context->getUsePROJAlternativeGridNames());
+ for (const auto &transf : transformations) {
+ if (dynamic_cast<crs::GeographicCRS *>(
+ transf->sourceCRS().get()) &&
+ dynamic_cast<crs::VerticalCRS *>(
+ transf->targetCRS().get())) {
+ res.push_back(useTransf(transf));
+ } else if (dynamic_cast<crs::GeographicCRS *>(
+ transf->targetCRS().get()) &&
+ dynamic_cast<crs::VerticalCRS *>(
+ transf->sourceCRS().get())) {
+ res.push_back(useTransf(transf->inverse()));
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+// ---------------------------------------------------------------------------
+
std::vector<CoordinateOperationNNPtr> CoordinateOperationFactory::Private::
createOperationsGeogToVertWithIntermediateVert(
const crs::CRSNNPtr &sourceCRS, const crs::CRSNNPtr &targetCRS,
@@ -14261,16 +14389,25 @@ getResolvedCRS(const crs::CRSNNPtr &crs,
} else {
auto outCrs = tryToIdentifyByName(
io::AuthorityFactory::ObjectType::COMPOUND_CRS);
+ const auto &components = compoundCrs->componentReferenceSystems();
if (outCrs.get() != crs.get()) {
- return outCrs;
+ bool hasGeoid = false;
+ if (components.size() == 2) {
+ auto vertCRS =
+ dynamic_cast<crs::VerticalCRS *>(components[1].get());
+ if (vertCRS && !vertCRS->geoidModel().empty()) {
+ hasGeoid = true;
+ }
+ }
+ if (!hasGeoid) {
+ return outCrs;
+ }
}
if (approxExtent || !extentOut) {
// If we still did not get a reliable extent, then try to
// resolve the components of the compoundCRS, and take the
// intersection of their extent.
extentOut = metadata::ExtentPtr();
- const auto &components =
- compoundCrs->componentReferenceSystems();
for (const auto &component : components) {
metadata::ExtentPtr componentExtent;
getResolvedCRS(component, context, componentExtent);
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index 26725e24..b9b38c80 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -2478,6 +2478,14 @@ void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
cs->_exportToWKT(formatter);
formatter->setOutputAxis(oldAxisOutputRule);
+ if (isWKT2 && formatter->use2019Keywords() && !d->geoidModel.empty()) {
+ const auto &model = d->geoidModel[0];
+ formatter->startNode(io::WKTConstants::GEOIDMODEL, false);
+ formatter->addQuotedString(model->nameStr());
+ model->formatID(formatter);
+ formatter->endNode();
+ }
+
ObjectUsage::baseExportToWKT(formatter);
formatter->endNode();
}
@@ -2539,6 +2547,15 @@ void VerticalCRS::_exportToJSON(
formatter->setOmitTypeInImmediateChild();
coordinateSystem()->_exportToJSON(formatter);
+ if (!d->geoidModel.empty()) {
+ const auto &model = d->geoidModel[0];
+ writer.AddObjKey("geoid_model");
+ auto objectContext2(formatter->MakeObjectContext(nullptr, false));
+ writer.AddObjKey("name");
+ writer.Add(model->nameStr());
+ model->formatID(formatter);
+ }
+
ObjectUsage::baseExportToJSON(formatter);
}
//! @endcond
@@ -2572,7 +2589,8 @@ void VerticalCRS::addLinearUnitConvert(
* cs::VerticalCS.
*
* @param properties See \ref general_properties.
- * At minimum the name should be defined.
+ * At minimum the name should be defined. The GEOID_MODEL property can be set
+ * to a TransformationNNPtr object.
* @param datumIn The datum of the CRS.
* @param csIn a VerticalCS.
* @return new VerticalCRS.
@@ -2592,7 +2610,8 @@ VerticalCRS::create(const util::PropertyMap &properties,
* One and only one of datum or datumEnsemble should be set to a non-null value.
*
* @param properties See \ref general_properties.
- * At minimum the name should be defined.
+ * At minimum the name should be defined. The GEOID_MODEL property can be set
+ * to a TransformationNNPtr object.
* @param datumIn The datum of the CRS, or nullptr
* @param datumEnsembleIn The datum ensemble of the CRS, or nullptr.
* @param csIn a VerticalCS.
@@ -2607,6 +2626,14 @@ VerticalCRS::create(const util::PropertyMap &properties,
csIn));
crs->assignSelf(crs);
crs->setProperties(properties);
+ const auto geoidModelPtr = properties.get("GEOID_MODEL");
+ if (geoidModelPtr) {
+ auto transf = util::nn_dynamic_pointer_cast<operation::Transformation>(
+ *geoidModelPtr);
+ if (transf) {
+ crs->d->geoidModel.emplace_back(NN_NO_CHECK(transf));
+ }
+ }
return crs;
}
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index 307c3a07..d9917996 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -5380,6 +5380,28 @@ AuthorityFactory::getPreferredHubGeodeticReferenceFrames(
// ---------------------------------------------------------------------------
//! @cond Doxygen_Suppress
+std::vector<operation::CoordinateOperationNNPtr>
+AuthorityFactory::getTransformationsForGeoid(
+ const std::string &geoidName, bool usePROJAlternativeGridNames) const {
+ std::vector<operation::CoordinateOperationNNPtr> res;
+
+ const std::string sql("SELECT operation_auth_name, operation_code FROM "
+ "geoid_model WHERE name = ?");
+ auto sqlRes = d->run(sql, {geoidName});
+ for (const auto &row : sqlRes) {
+ const auto &auth_name = row[0];
+ const auto &code = row[1];
+ res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
+ code, usePROJAlternativeGridNames));
+ }
+
+ return res;
+}
+//! @endcond
+
+// ---------------------------------------------------------------------------
+
+//! @cond Doxygen_Suppress
FactoryException::FactoryException(const char *message) : Exception(message) {}
// ---------------------------------------------------------------------------
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index 50ad5a87..645bec0b 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -89,7 +89,7 @@ static const std::string emptyString{};
// If changing that value, change it in data/projjson.schema.json as well
#define PROJJSON_CURRENT_VERSION \
- "https://proj.org/schemas/v0.1/projjson.schema.json"
+ "https://proj.org/schemas/v0.2/projjson.schema.json"
//! @endcond
#if 0
@@ -3873,28 +3873,110 @@ CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
ThrowNotExpectedCSType("vertical");
}
+ auto &props = buildProperties(node);
+
+ // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name,
+ // following conventions from
+ // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf
+ // page 9
+ if (ci_equal(nodeValue, WKTConstants::VERT_CS)) {
+ std::string name;
+ if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) {
+ std::string geoidName;
+ for (const char *prefix :
+ {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ",
+ "NAVD88 height (ftUS) - "}) {
+ if (starts_with(name, prefix)) {
+ geoidName = name.substr(strlen(prefix));
+ auto pos = geoidName.find_first_of(" (");
+ if (pos != std::string::npos) {
+ geoidName.resize(pos);
+ }
+ break;
+ }
+ }
+ if (!geoidName.empty()) {
+ const auto &axis = verticalCS->axisList()[0];
+ const auto &dir = axis->direction();
+ if (dir == cs::AxisDirection::UP) {
+ if (axis->unit() == common::UnitOfMeasure::METRE) {
+ props.set(IdentifiedObject::NAME_KEY, "NAVD88 height");
+ props.set(Identifier::CODE_KEY, 5703);
+ props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
+ } else if (axis->unit().name() == "US survey foot") {
+ props.set(IdentifiedObject::NAME_KEY,
+ "NAVD88 height (ftUS)");
+ props.set(Identifier::CODE_KEY, 6360);
+ props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
+ }
+ }
+ PropertyMap propsModel;
+ propsModel.set(IdentifiedObject::NAME_KEY, toupper(geoidName));
+ PropertyMap propsDatum;
+ propsDatum.set(IdentifiedObject::NAME_KEY,
+ "North American Vertical Datum 1988");
+ propsDatum.set(Identifier::CODE_KEY, 5103);
+ propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
+ datum =
+ VerticalReferenceFrame::create(propsDatum).as_nullable();
+ const auto dummyCRS =
+ VerticalCRS::create(PropertyMap(), datum, datumEnsemble,
+ NN_NO_CHECK(verticalCS));
+ const auto model(Transformation::create(
+ propsModel, dummyCRS, dummyCRS, nullptr,
+ OperationMethod::create(
+ PropertyMap(), std::vector<OperationParameterNNPtr>()),
+ {}, {}));
+ props.set("GEOID_MODEL", model);
+ }
+ }
+ }
+
+ auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL);
+ if (!isNull(geoidModelNode)) {
+ auto &propsModel = buildProperties(geoidModelNode);
+ const auto dummyCRS = VerticalCRS::create(
+ PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
+ const auto model(Transformation::create(
+ propsModel, dummyCRS, dummyCRS, nullptr,
+ OperationMethod::create(PropertyMap(),
+ std::vector<OperationParameterNNPtr>()),
+ {}, {}));
+ props.set("GEOID_MODEL", model);
+ }
+
auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
- buildProperties(node), datum, datumEnsemble, NN_NO_CHECK(verticalCS)));
+ props, datum, datumEnsemble, NN_NO_CHECK(verticalCS)));
if (!isNull(datumNode)) {
auto &extensionNode = datumNode->lookForChild(WKTConstants::EXTENSION);
const auto &extensionChildren = extensionNode->GP()->children();
if (extensionChildren.size() == 2) {
if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
- std::string transformationName(crs->nameStr());
- if (!ends_with(transformationName, " height")) {
- transformationName += " height";
+ const auto gridName(stripQuotes(extensionChildren[1]));
+ // This is the expansion of EPSG:5703 by old GDAL versions.
+ // See
+ // https://trac.osgeo.org/metacrs/changeset?reponame=&new=2281%40geotiff%2Ftrunk%2Flibgeotiff%2Fcsv%2Fvertcs.override.csv&old=1893%40geotiff%2Ftrunk%2Flibgeotiff%2Fcsv%2Fvertcs.override.csv
+ // It is unlikely that the user really explictly wants this.
+ if (gridName != "g2003conus.gtx,g2003alaska.gtx,"
+ "g2003h01.gtx,g2003p01.gtx" &&
+ gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
+ "g2012a_guam.gtx,g2012a_hawaii.gtx,"
+ "g2012a_puertorico.gtx,g2012a_samoa.gtx") {
+ std::string transformationName(crs->nameStr());
+ if (!ends_with(transformationName, " height")) {
+ transformationName += " height";
+ }
+ transformationName += " to WGS84 ellipsoidal height";
+ auto transformation = Transformation::
+ createGravityRelatedHeightToGeographic3D(
+ PropertyMap().set(IdentifiedObject::NAME_KEY,
+ transformationName),
+ crs, GeographicCRS::EPSG_4979, nullptr, gridName,
+ std::vector<PositionalAccuracyNNPtr>());
+ return nn_static_pointer_cast<CRS>(BoundCRS::create(
+ crs, GeographicCRS::EPSG_4979, transformation));
}
- transformationName += " to WGS84 ellipsoidal height";
- auto transformation =
- Transformation::createGravityRelatedHeightToGeographic3D(
- PropertyMap().set(IdentifiedObject::NAME_KEY,
- transformationName),
- crs, GeographicCRS::EPSG_4979, nullptr,
- stripQuotes(extensionChildren[1]),
- std::vector<PositionalAccuracyNNPtr>());
- return nn_static_pointer_cast<CRS>(BoundCRS::create(
- crs, GeographicCRS::EPSG_4979, transformation));
}
}
}
@@ -5040,7 +5122,22 @@ VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) {
if (!verticalCS) {
throw ParsingException("expected a vertical CS");
}
- return VerticalCRS::create(buildProperties(j), datum, datumEnsemble,
+
+ auto props = buildProperties(j);
+ if (j.contains("geoid_model")) {
+ auto geoidModelJ = getObject(j, "geoid_model");
+ auto propsModel = buildProperties(geoidModelJ);
+ const auto dummyCRS = VerticalCRS::create(
+ PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
+ const auto model(Transformation::create(
+ propsModel, dummyCRS, dummyCRS, nullptr,
+ OperationMethod::create(PropertyMap(),
+ std::vector<OperationParameterNNPtr>()),
+ {}, {}));
+ props.set("GEOID_MODEL", model);
+ }
+
+ return VerticalCRS::create(props, datum, datumEnsemble,
NN_NO_CHECK(verticalCS));
}
diff --git a/src/iso19111/static.cpp b/src/iso19111/static.cpp
index 824047f0..0ed08c95 100644
--- a/src/iso19111/static.cpp
+++ b/src/iso19111/static.cpp
@@ -274,6 +274,7 @@ DEFINE_WKT_CONSTANT(BASEENGCRS);
DEFINE_WKT_CONSTANT(BASEPARAMCRS);
DEFINE_WKT_CONSTANT(BASETIMECRS);
DEFINE_WKT_CONSTANT(VERSION);
+DEFINE_WKT_CONSTANT(GEOIDMODEL);
DEFINE_WKT_CONSTANT(GEODETICCRS);
DEFINE_WKT_CONSTANT(GEODETICDATUM);
diff --git a/src/proj_experimental.h b/src/proj_experimental.h
index c6d5bc45..0886ba28 100644
--- a/src/proj_experimental.h
+++ b/src/proj_experimental.h
@@ -248,6 +248,19 @@ PJ PROJ_DLL *proj_create_vertical_crs(PJ_CONTEXT *ctx,
const char *linear_units,
double linear_units_conv);
+PJ PROJ_DLL *proj_create_vertical_crs_ex(PJ_CONTEXT *ctx,
+ const char *crs_name,
+ const char *datum_name,
+ const char *datum_auth_name,
+ const char* datum_code,
+ const char *linear_units,
+ double linear_units_conv,
+ const char* geoid_model_name,
+ const char* geoid_model_auth_name,
+ const char* geoid_model_code,
+ const PJ* geoid_geog_crs,
+ const char *const *options);
+
PJ PROJ_DLL *proj_create_compound_crs(PJ_CONTEXT *ctx,
const char *crs_name,
PJ* horiz_crs,
diff --git a/test/cli/testprojinfo_out.dist b/test/cli/testprojinfo_out.dist
index c1d9f3e0..2875626b 100644
--- a/test/cli/testprojinfo_out.dist
+++ b/test/cli/testprojinfo_out.dist
@@ -137,7 +137,7 @@ GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.25722
PROJJSON:
{
- "$schema": "https://proj.org/schemas/v0.1/projjson.schema.json",
+ "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json",
"type": "GeographicCRS",
"name": "WGS 84",
"datum": {
@@ -947,14 +947,14 @@ COORDINATEOPERATION["RH2000 height to SWEREF99",
ID["PROJ","EPSG_5613_TO_EPSG_4977"]]
Testing NAD83(2011) + NAVD88 height -> NAD83(2011) : projinfo -s EPSG:6349 -t EPSG:6319 --spatial-test intersects -o PROJ
-Candidate operations found: 3
+Candidate operations found: 2
-------------------------------------
Operation n°1:
-unknown id, Inverse of NAD83(2011) to NAVD88 height (1), 0.02 m, USA - CONUS - onshore
+unknown id, Inverse of NAD83(2011) to NAVD88 height (3), 0.015 m, USA - CONUS - onshore
PROJ string:
-+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=g2012bu0.gtx +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1
++proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=g2018u0.gtx +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1
-------------------------------------
Operation n°2:
@@ -964,14 +964,6 @@ unknown id, Inverse of NAD83(2011) to NAVD88 height (2), 0.02 m, USA - Alaska
PROJ string:
+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=vgridshift +grids=g2012ba0.gtx +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1
--------------------------------------
-Operation n°3:
-
-unknown id, Inverse of NAD83(2011) to NAVD88 height (3), 0.015 m, USA - CONUS - onshore
-
-PROJ string:
-+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +inv +proj=vgridshift +grids=g2018u0.bin +multiplier=1 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1
-
Testing NGF IGN69 height to RGF93: projinfo -s EPSG:5720 -t EPSG:4965 -o PROJ
Candidate operations found: 2
-------------------------------------
diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp
index b8310ce5..bdadc8b8 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -3602,7 +3602,7 @@ TEST_F(CApi, proj_as_projjson) {
EXPECT_EQ(std::string(projjson),
"{\n"
" \"$schema\": "
- "\"https://proj.org/schemas/v0.1/projjson.schema.json\",\n"
+ "\"https://proj.org/schemas/v0.2/projjson.schema.json\",\n"
" \"type\": \"Ellipsoid\",\n"
" \"name\": \"WGS 84\",\n"
" \"semi_major_axis\": 6378137,\n"
@@ -4215,4 +4215,111 @@ TEST_F(
EXPECT_NEAR(outcoord.xyzt.z, -32.5823, 1e-3);
}
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_create_vertical_crs_ex) {
+
+ // NAD83(2011) / UTM zone 11N
+ auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340",
+ PJ_CATEGORY_CRS, false, nullptr);
+ ObjectKeeper keeper_horiz_crs(horiz_crs);
+ ASSERT_NE(horiz_crs, nullptr);
+
+ auto vert_crs = proj_create_vertical_crs_ex(
+ m_ctxt, "myVertCRS (ftUS)", "myVertDatum", nullptr, nullptr,
+ "US survey foot", 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr,
+ nullptr, nullptr);
+ ObjectKeeper keeper_vert_crs(vert_crs);
+ ASSERT_NE(vert_crs, nullptr);
+
+ auto compound =
+ proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs);
+ ObjectKeeper keeper_compound(compound);
+ ASSERT_NE(compound, nullptr);
+
+ // NAD83(2011) 3D
+ PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319");
+ ObjectKeeper keeper_geog_crs(geog_crs);
+ ASSERT_NE(geog_crs, nullptr);
+
+ auto P = proj_create_crs_to_crs_from_pj(m_ctxt, compound, geog_crs, nullptr,
+ nullptr);
+ ObjectKeeper keeper_P(P);
+ ASSERT_NE(P, nullptr);
+
+ auto name = proj_get_name(P);
+ ASSERT_TRUE(name != nullptr);
+ EXPECT_EQ(name,
+ std::string("Inverse of UTM zone 11N + "
+ "Transformation from myVertCRS (ftUS) to myVertCRS + "
+ "Transformation from myVertCRS to NAD83(2011)"));
+
+ auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr);
+ ASSERT_NE(proj_5, nullptr);
+ EXPECT_EQ(std::string(proj_5),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=11 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_create_vertical_crs_ex_with_geog_crs) {
+
+ // NAD83(2011) / UTM zone 11N
+ auto horiz_crs = proj_create_from_database(m_ctxt, "EPSG", "6340",
+ PJ_CATEGORY_CRS, false, nullptr);
+ ObjectKeeper keeper_horiz_crs(horiz_crs);
+ ASSERT_NE(horiz_crs, nullptr);
+
+ // WGS84
+ PJ *wgs84 = proj_create(m_ctxt, "EPSG:4979");
+ ObjectKeeper keeper_wgs84(wgs84);
+ ASSERT_NE(wgs84, nullptr);
+
+ auto vert_crs = proj_create_vertical_crs_ex(
+ m_ctxt, "myVertCRS", "myVertDatum", nullptr, nullptr, "US survey foot",
+ 0.304800609601219, "PROJ @foo.gtx", nullptr, nullptr, wgs84, nullptr);
+ ObjectKeeper keeper_vert_crs(vert_crs);
+ ASSERT_NE(vert_crs, nullptr);
+
+ auto compound =
+ proj_create_compound_crs(m_ctxt, "Compound", horiz_crs, vert_crs);
+ ObjectKeeper keeper_compound(compound);
+ ASSERT_NE(compound, nullptr);
+
+ // NAD83(2011) 3D
+ PJ *geog_crs = proj_create(m_ctxt, "EPSG:6319");
+ ObjectKeeper keeper_geog_crs(geog_crs);
+ ASSERT_NE(geog_crs, nullptr);
+
+ auto P = proj_create_crs_to_crs_from_pj(m_ctxt, compound, geog_crs, nullptr,
+ nullptr);
+ ObjectKeeper keeper_P(P);
+ ASSERT_NE(P, nullptr);
+
+ auto name = proj_get_name(P);
+ ASSERT_TRUE(name != nullptr);
+ EXPECT_EQ(
+ name,
+ std::string("Inverse of UTM zone 11N + "
+ "Ballpark geographic offset from NAD83(2011) to WGS 84 + "
+ "Transformation from myVertCRS to myVertCRS (metre) + "
+ "Transformation from myVertCRS (metre) to WGS 84 + "
+ "Ballpark geographic offset from WGS 84 to NAD83(2011)"));
+
+ auto proj_5 = proj_as_proj_string(m_ctxt, P, PJ_PROJ_5, nullptr);
+ ASSERT_NE(proj_5, nullptr);
+ EXPECT_EQ(std::string(proj_5),
+ "+proj=pipeline "
+ "+step +inv +proj=utm +zone=11 +ellps=GRS80 "
+ "+step +proj=unitconvert +z_in=us-ft +z_out=m "
+ "+step +proj=vgridshift +grids=@foo.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
} // namespace
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
index 8aff0908..a71b63bb 100644
--- a/test/unit/test_io.cpp
+++ b/test/unit/test_io.cpp
@@ -1994,16 +1994,21 @@ TEST(wkt_parse, vertcrs_VRF_WKT2) {
// ---------------------------------------------------------------------------
TEST(wkt_parse, vertcrs_with_GEOIDMODEL) {
- auto wkt = "VERTCRS[\"CGVD2013\","
- " VRF[\"Canadian Geodetic Vertical Datum of 2013\"],"
- " CS[vertical,1],"
- " AXIS[\"gravity-related height (H)\",up],"
- " LENGTHUNIT[\"metre\",1.0],"
- " GEOIDMODEL[\"CGG2013\",ID[\"EPSG\",6648]]]";
+ auto wkt = "VERTCRS[\"CGVD2013\",\n"
+ " VDATUM[\"Canadian Geodetic Vertical Datum of 2013\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " GEOIDMODEL[\"CGG2013\",\n"
+ " ID[\"EPSG\",6648]]]";
auto obj = WKTParser().createFromWKT(wkt);
auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(
+ crs->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2019).get()),
+ wkt);
}
// ---------------------------------------------------------------------------
@@ -2060,6 +2065,71 @@ TEST(wkt_parse, vertcrs_WKT1_GDAL_minimum) {
// ---------------------------------------------------------------------------
+TEST(wkt_parse, vertcrs_WKT1_LAS_ftUS) {
+ auto wkt = "VERT_CS[\"NAVD88 - Geoid03 (Feet)\","
+ " VERT_DATUM[\"unknown\",2005],"
+ " UNIT[\"US survey foot\",0.3048006096012192,"
+ " AUTHORITY[\"EPSG\",\"9003\"]],"
+ " AXIS[\"Up\",UP]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "NAVD88 height (ftUS)");
+ ASSERT_EQ(crs->identifiers().size(), 1U);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "6360");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &geoidModel = crs->geoidModel();
+ ASSERT_TRUE(!geoidModel.empty());
+ EXPECT_EQ(geoidModel[0]->nameStr(), "GEOID03");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "North American Vertical Datum 1988");
+ ASSERT_EQ(datum->identifiers().size(), 1U);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "5103");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &axis = crs->coordinateSystem()->axisList()[0];
+ EXPECT_EQ(axis->direction(), AxisDirection::UP);
+ EXPECT_EQ(axis->unit().name(), "US survey foot");
+ EXPECT_NEAR(axis->unit().conversionToSI(), 0.3048006096012192, 1e-16);
+}
+
+// ---------------------------------------------------------------------------
+
+TEST(wkt_parse, vertcrs_WKT1_LAS_metre) {
+ auto wkt = "VERT_CS[\"NAVD88 via Geoid09\","
+ " VERT_DATUM[\"unknown\",2005],"
+ " UNIT[\"metre\",1.0,"
+ " AUTHORITY[\"EPSG\",\"9001\"]],"
+ " AXIS[\"Up\",UP]]";
+
+ auto obj = WKTParser().createFromWKT(wkt);
+ auto crs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "NAVD88 height");
+ ASSERT_EQ(crs->identifiers().size(), 1U);
+ EXPECT_EQ(crs->identifiers()[0]->code(), "5703");
+ EXPECT_EQ(*(crs->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &geoidModel = crs->geoidModel();
+ ASSERT_TRUE(!geoidModel.empty());
+ EXPECT_EQ(geoidModel[0]->nameStr(), "GEOID09");
+
+ auto datum = crs->datum();
+ EXPECT_EQ(datum->nameStr(), "North American Vertical Datum 1988");
+ ASSERT_EQ(datum->identifiers().size(), 1U);
+ EXPECT_EQ(datum->identifiers()[0]->code(), "5103");
+ EXPECT_EQ(*(datum->identifiers()[0]->codeSpace()), "EPSG");
+
+ const auto &axis = crs->coordinateSystem()->axisList()[0];
+ EXPECT_EQ(axis->direction(), AxisDirection::UP);
+ EXPECT_EQ(axis->unit(), UnitOfMeasure::METRE);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(wkt_parse, dynamic_vertical_reference_frame) {
auto obj = WKTParser().createFromWKT(
"VERTCRS[\"RH2000\","
@@ -10842,6 +10912,45 @@ TEST(json_import, vertical_crs_with_datum_ensemble) {
// ---------------------------------------------------------------------------
+TEST(json_import, vertical_crs_with_geoid_model) {
+ auto json = "{\n"
+ " \"$schema\": \"foo\",\n"
+ " \"type\": \"VerticalCRS\",\n"
+ " \"name\": \"CGVD2013\",\n"
+ " \"datum\": {\n"
+ " \"type\": \"VerticalReferenceFrame\",\n"
+ " \"name\": \"Canadian Geodetic Vertical Datum of 2013\"\n"
+ " },\n"
+ " \"coordinate_system\": {\n"
+ " \"subtype\": \"vertical\",\n"
+ " \"axis\": [\n"
+ " {\n"
+ " \"name\": \"Gravity-related height\",\n"
+ " \"abbreviation\": \"H\",\n"
+ " \"direction\": \"up\",\n"
+ " \"unit\": \"metre\"\n"
+ " }\n"
+ " ]\n"
+ " },\n"
+ " \"geoid_model\": {\n"
+ " \"name\": \"CGG2013\",\n"
+ " \"id\": {\n"
+ " \"authority\": \"EPSG\",\n"
+ " \"code\": 6648\n"
+ " }\n"
+ " }\n"
+ "}";
+
+ // No database
+ auto obj = createFromUserInput(json, nullptr);
+ auto vcrs = nn_dynamic_pointer_cast<VerticalCRS>(obj);
+ ASSERT_TRUE(vcrs != nullptr);
+ EXPECT_EQ(vcrs->exportToJSON(&(JSONFormatter::create()->setSchema("foo"))),
+ json);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(json_import, parametric_crs) {
auto json = "{\n"
" \"$schema\": \"foo\",\n"
diff --git a/test/unit/test_operation.cpp b/test/unit/test_operation.cpp
index 8df785b1..07b0daea 100644
--- a/test/unit/test_operation.cpp
+++ b/test/unit/test_operation.cpp
@@ -7072,17 +7072,13 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
"+proj=pipeline "
"+step +proj=axisswap +order=2,1 "
"+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- // Inv here since the grid is not known
- "+step +inv +proj=vgridshift +grids=geoid09_conus.bin "
+ "+step +proj=vgridshift +grids=geoid09_conus.gtx "
"+multiplier=1 "
"+step +proj=unitconvert +xy_in=rad +xy_out=deg "
"+step +proj=axisswap +order=2,1");
}
- // CompoundCRS to Geog3DCRS, with same vertical unit, and with
- // ellipsoid height <--> vertical height correction that requires a
- // horizontal adjustment before and after (which is empty in practice here
- // as NAD83 to NAD83(2011) is no-op)
+ // NAD83 + NAVD88 height --> WGS 84
{
auto ctxt =
CoordinateOperationContext::create(authFactory, nullptr, 0.0);
@@ -7105,10 +7101,10 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
ASSERT_GE(list.size(), 2U);
EXPECT_EQ(list[0]->nameStr(),
- "NAD83 to NAD83(2011) (1) + "
- "Inverse of NAD83(2011) to NAVD88 height (1) + "
- "Inverse of NAD83 to NAD83(2011) (1) + "
- "NAD83 to WGS 84 (1)");
+ "NAD83 to WGS 84 (1) + "
+ "Inverse of NAD83(NSRS2007) to WGS 84 (1) + "
+ "Inverse of NAD83(NSRS2007) to NAVD88 height (1) + "
+ "NAD83(NSRS2007) to WGS 84 (1)");
EXPECT_EQ(list[0]->exportToPROJString(
PROJStringFormatter::create(
PROJStringFormatter::Convention::PROJ_5,
@@ -7117,30 +7113,10 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
"+proj=pipeline "
"+step +proj=axisswap +order=2,1 "
"+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=g2012bu0.gtx "
+ "+step +proj=vgridshift +grids=geoid09_conus.gtx "
"+multiplier=1 "
"+step +proj=unitconvert +xy_in=rad +xy_out=deg "
"+step +proj=axisswap +order=2,1");
-
- // Shows vertical step, and then horizontal step
- EXPECT_EQ(list[1]->nameStr(),
- "NAD83 to NAD83(2011) (1) + "
- "Inverse of NAD83(2011) to NAVD88 height (1) + "
- "Inverse of NAD83 to NAD83(2011) (1) + "
- "NAD83 to WGS 84 (18)");
- EXPECT_EQ(list[1]->exportToPROJString(
- PROJStringFormatter::create(
- PROJStringFormatter::Convention::PROJ_5,
- authFactory->databaseContext())
- .get()),
- "+proj=pipeline "
- "+step +proj=axisswap +order=2,1 "
- "+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=g2012bu0.gtx "
- "+multiplier=1 "
- "+step +proj=hgridshift +grids=FL "
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
- "+step +proj=axisswap +order=2,1");
}
// Another variation, but post horizontal adjustment is in two steps
@@ -7166,7 +7142,7 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
ASSERT_GE(list.size(), 2U);
EXPECT_EQ(list[0]->nameStr(),
- "Inverse of NAD83(2011) to NAVD88 height (1) + "
+ "Inverse of NAD83(2011) to NAVD88 height (3) + "
"Inverse of NAD83 to NAD83(2011) (1) + "
"NAD83 to WGS 84 (1)");
EXPECT_EQ(list[0]->exportToPROJString(
@@ -7177,14 +7153,14 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
"+proj=pipeline "
"+step +proj=axisswap +order=2,1 "
"+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=g2012bu0.gtx "
+ "+step +proj=vgridshift +grids=g2018u0.gtx "
"+multiplier=1 "
"+step +proj=unitconvert +xy_in=rad +xy_out=deg "
"+step +proj=axisswap +order=2,1");
// Shows vertical step, and then horizontal step
EXPECT_EQ(list[1]->nameStr(),
- "Inverse of NAD83(2011) to NAVD88 height (1) + "
+ "Inverse of NAD83(2011) to NAVD88 height (3) + "
"Inverse of NAD83 to NAD83(2011) (1) + "
"NAD83 to WGS 84 (18)");
EXPECT_EQ(list[1]->exportToPROJString(
@@ -7195,7 +7171,7 @@ TEST(operation, compoundCRS_to_geogCRS_3D_context) {
"+proj=pipeline "
"+step +proj=axisswap +order=2,1 "
"+step +proj=unitconvert +xy_in=deg +xy_out=rad "
- "+step +proj=vgridshift +grids=g2012bu0.gtx "
+ "+step +proj=vgridshift +grids=g2018u0.gtx "
"+multiplier=1 "
"+step +proj=hgridshift +grids=FL "
"+step +proj=unitconvert +xy_in=rad +xy_out=deg "
@@ -7227,7 +7203,7 @@ TEST(operation, compoundCRS_to_geogCRS_2D_promote_to_3D_context) {
ctxt);
// The checked value is not that important, but in case this changes,
// likely due to a EPSG upgrade, worth checking
- ASSERT_EQ(listCompoundToGeog2D.size(), 469U);
+ ASSERT_EQ(listCompoundToGeog2D.size(), 467U);
auto listGeog2DToCompound =
CoordinateOperationFactory::create()->createOperations(dst, nnSrc,
@@ -7623,6 +7599,88 @@ TEST(
// ---------------------------------------------------------------------------
+TEST(operation, compoundCRS_of_vertCRS_with_geoid_model_to_geogCRS) {
+ auto authFactory =
+ AuthorityFactory::create(DatabaseContext::create(), "EPSG");
+ auto ctxt = CoordinateOperationContext::create(authFactory, nullptr, 0.0);
+ ctxt->setSpatialCriterion(
+ CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
+ ctxt->setGridAvailabilityUse(
+ CoordinateOperationContext::GridAvailabilityUse::
+ IGNORE_GRID_AVAILABILITY);
+ auto wkt =
+ "COMPOUNDCRS[\"NAD83 / Pennsylvania South + NAVD88 height\",\n"
+ " PROJCRS[\"NAD83 / Pennsylvania South\",\n"
+ " BASEGEOGCRS[\"NAD83\",\n"
+ " DATUM[\"North American Datum 1983\",\n"
+ " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " CONVERSION[\"SPCS83 Pennsylvania South zone (meters)\",\n"
+ " METHOD[\"Lambert Conic Conformal (2SP)\",\n"
+ " ID[\"EPSG\",9802]],\n"
+ " PARAMETER[\"Latitude of false origin\",39.3333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8821]],\n"
+ " PARAMETER[\"Longitude of false origin\",-77.75,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8822]],\n"
+ " PARAMETER[\"Latitude of 1st standard "
+ "parallel\",40.9666666666667,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8823]],\n"
+ " PARAMETER[\"Latitude of 2nd standard "
+ "parallel\",39.9333333333333,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433],\n"
+ " ID[\"EPSG\",8824]],\n"
+ " PARAMETER[\"Easting at false origin\",600000,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8826]],\n"
+ " PARAMETER[\"Northing at false origin\",0,\n"
+ " LENGTHUNIT[\"metre\",1],\n"
+ " ID[\"EPSG\",8827]]],\n"
+ " CS[Cartesian,2],\n"
+ " AXIS[\"easting (X)\",east,\n"
+ " ORDER[1],\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " AXIS[\"northing (Y)\",north,\n"
+ " ORDER[2],\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " VERTCRS[\"NAVD88 height\",\n"
+ " VDATUM[\"North American Vertical Datum 1988\"],\n"
+ " CS[vertical,1],\n"
+ " AXIS[\"gravity-related height (H)\",up,\n"
+ " LENGTHUNIT[\"metre\",1]],\n"
+ " GEOIDMODEL[\"GEOID12B\"]]]";
+ auto srcObj =
+ createFromUserInput(wkt, authFactory->databaseContext(), false);
+ auto src = nn_dynamic_pointer_cast<CRS>(srcObj);
+ ASSERT_TRUE(src != nullptr);
+ auto dst = authFactory->createCoordinateReferenceSystem("4269"); // NAD83
+
+ auto list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(src), dst, ctxt);
+ ASSERT_TRUE(!list.empty());
+ EXPECT_EQ(list[0]->nameStr(),
+ "Inverse of SPCS83 Pennsylvania South zone (meters) + "
+ "Ballpark geographic offset from NAD83 to NAD83(2011) + "
+ "Inverse of NAD83(2011) to NAVD88 height (1) + "
+ "Ballpark geographic offset from NAD83(2011) to NAD83");
+ auto op_proj =
+ list[0]->exportToPROJString(PROJStringFormatter::create().get());
+ EXPECT_EQ(op_proj,
+ "+proj=pipeline "
+ "+step +inv +proj=lcc +lat_0=39.3333333333333 +lon_0=-77.75 "
+ "+lat_1=40.9666666666667 +lat_2=39.9333333333333 +x_0=600000 "
+ "+y_0=0 +ellps=GRS80 "
+ "+step +proj=vgridshift +grids=g2012bu0.gtx +multiplier=1 "
+ "+step +proj=unitconvert +xy_in=rad +xy_out=deg "
+ "+step +proj=axisswap +order=2,1");
+}
+
+// ---------------------------------------------------------------------------
+
TEST(operation, compoundCRS_from_WKT2_to_geogCRS_3D_context) {
auto authFactory =
AuthorityFactory::create(DatabaseContext::create(), "EPSG");