aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2021-04-10 16:59:08 +0200
committerEven Rouault <even.rouault@spatialys.com>2021-04-10 19:17:39 +0200
commit21f0af0f8b56ab76b93aaf6ce962580ec7978fcb (patch)
treef77790ea35031c0a55e376dac5c33962a40dbf8b
parent84a679954b1fa4c41c7bdbac87013be78b64bea4 (diff)
downloadPROJ-21f0af0f8b56ab76b93aaf6ce962580ec7978fcb.tar.gz
PROJ-21f0af0f8b56ab76b93aaf6ce962580ec7978fcb.zip
createFromUserInput(): add support for OGC URLs
e.g: http://www.opengis.net/def/crs/EPSG/0/4326 http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855
-rw-r--r--src/iso19111/io.cpp82
-rw-r--r--test/unit/test_io.cpp78
2 files changed, 160 insertions, 0 deletions
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index 184d992f..2d31fd59 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -6126,6 +6126,80 @@ EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) {
// ---------------------------------------------------------------------------
+// import a CRS encoded as OGC Best Practice document 11-135.
+
+static const char *const crsURLPrefixes[] = {
+ "http://opengis.net/def/crs", "https://opengis.net/def/crs",
+ "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs",
+ "www.opengis.net/def/crs",
+};
+
+static bool isCRSURL(const std::string &text) {
+ for (const auto crsURLPrefix : crsURLPrefixes) {
+ if (starts_with(text, crsURLPrefix)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static CRSNNPtr importFromCRSURL(const std::string &text,
+ const DatabaseContextNNPtr &dbContext) {
+ // e.g http://www.opengis.net/def/crs/EPSG/0/4326
+ std::vector<std::string> parts;
+ for (const auto crsURLPrefix : crsURLPrefixes) {
+ if (starts_with(text, crsURLPrefix)) {
+ parts = split(text.substr(strlen(crsURLPrefix)), '/');
+ break;
+ }
+ }
+
+ // e.g
+ // "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855"
+ if (!parts.empty() && starts_with(parts[0], "-compound?")) {
+ parts = split(text.substr(text.find('?') + 1), '&');
+ std::map<int, std::string> mapParts;
+ for (const auto &part : parts) {
+ const auto queryParam = split(part, '=');
+ if (queryParam.size() != 2) {
+ throw ParsingException("invalid OGC CRS URL");
+ }
+ try {
+ mapParts[std::stoi(queryParam[0])] = queryParam[1];
+ } catch (const std::exception &) {
+ throw ParsingException("invalid OGC CRS URL");
+ }
+ }
+ std::vector<CRSNNPtr> components;
+ std::string name;
+ for (size_t i = 1; i <= mapParts.size(); ++i) {
+ const auto iter = mapParts.find(static_cast<int>(i));
+ if (iter == mapParts.end()) {
+ throw ParsingException("invalid OGC CRS URL");
+ }
+ components.emplace_back(importFromCRSURL(iter->second, dbContext));
+ if (!name.empty()) {
+ name += " + ";
+ }
+ name += components.back()->nameStr();
+ }
+ return CompoundCRS::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
+ components);
+ }
+
+ if (parts.size() < 4) {
+ throw ParsingException("invalid OGC CRS URL");
+ }
+
+ const auto &auth_name = parts[1];
+ const auto &code = parts[3];
+ auto factoryCRS = AuthorityFactory::create(dbContext, auth_name);
+ return factoryCRS->createCoordinateReferenceSystem(code, true);
+}
+
+// ---------------------------------------------------------------------------
+
static BaseObjectNNPtr createFromUserInput(const std::string &text,
const DatabaseContextPtr &dbContext,
bool usePROJ4InitRules,
@@ -6187,6 +6261,10 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
.createFromPROJString(text);
}
+ if (isCRSURL(text) && dbContext) {
+ return importFromCRSURL(text, NN_NO_CHECK(dbContext));
+ }
+
auto tokens = split(text, ':');
if (tokens.size() == 2) {
if (!dbContext) {
@@ -6590,6 +6668,10 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
* <li> OGC URN combining references for concatenated operations
* e.g.
* "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
+ * <li>OGC URL for a single CRS. e.g.
+ * "http://www.opengis.net/def/crs/EPSG/0/4326</li> <li>OGC URL for a compound
+ * CRS. e.g
+ * "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855"</li>
* <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
* uniqueness is not guaranteed, the function may apply heuristics to
* determine the appropriate best match.</li>
diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp
index 9d627ce9..708b3874 100644
--- a/test/unit/test_io.cpp
+++ b/test/unit/test_io.cpp
@@ -10637,6 +10637,84 @@ TEST(io, createFromUserInput) {
// ---------------------------------------------------------------------------
+TEST(io, createFromUserInput_ogc_crs_url) {
+ auto dbContext = DatabaseContext::create();
+
+ {
+ auto obj = createFromUserInput(
+ "http://www.opengis.net/def/crs/EPSG/0/4326", dbContext);
+ auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ }
+
+ EXPECT_THROW(
+ createFromUserInput("http://www.opengis.net/def/crs", dbContext),
+ ParsingException);
+
+ EXPECT_THROW(
+ createFromUserInput("http://www.opengis.net/def/crs/EPSG/0", dbContext),
+ ParsingException);
+
+ EXPECT_THROW(createFromUserInput(
+ "http://www.opengis.net/def/crs/EPSG/0/XXXX", dbContext),
+ NoSuchAuthorityCodeException);
+
+ {
+ auto obj = createFromUserInput(
+ "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/"
+ "def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855",
+ dbContext);
+ auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj);
+ ASSERT_TRUE(crs != nullptr);
+ EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM2008 height");
+ }
+
+ // No part
+ EXPECT_THROW(createFromUserInput("http://www.opengis.net/def/crs-compound?",
+ dbContext),
+ ParsingException);
+
+ // Just one part
+ EXPECT_THROW(
+ createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://"
+ "www.opengis.net/def/crs/EPSG/0/4326",
+ dbContext),
+ InvalidCompoundCRSException);
+
+ // Invalid compound CRS
+ EXPECT_THROW(
+ createFromUserInput(
+ "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/"
+ "def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/4326",
+ dbContext),
+ InvalidCompoundCRSException);
+
+ // Missing 2=
+ EXPECT_THROW(
+ createFromUserInput(
+ "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/"
+ "def/crs/EPSG/0/4326&3=http://www.opengis.net/def/crs/EPSG/0/3855",
+ dbContext),
+ ParsingException);
+
+ // Invalid query parameter
+ EXPECT_THROW(
+ createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://"
+ "www.opengis.net/def/crs/EPSG/0/4326&bla",
+ dbContext),
+ ParsingException);
+
+ // Invalid query parameter
+ EXPECT_THROW(
+ createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://"
+ "www.opengis.net/def/crs/EPSG/0/4326&two=http://"
+ "www.opengis.net/def/crs/EPSG/0/3855",
+ dbContext),
+ ParsingException);
+}
+
+// ---------------------------------------------------------------------------
+
TEST(io, createFromUserInput_hack_EPSG_102100) {
auto dbContext = DatabaseContext::create();
auto obj = createFromUserInput("EPSG:102100", dbContext);