aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEven Rouault <even.rouault@spatialys.com>2021-04-10 20:15:34 +0200
committerGitHub <noreply@github.com>2021-04-10 20:15:34 +0200
commit34ea13913717b20982778d5e5f13b4a5ee8288ab (patch)
treec16c443866a0eec19c488e415fc75815a090327e /src
parent84a679954b1fa4c41c7bdbac87013be78b64bea4 (diff)
parenta77ce5cf343c00fc95b5f3d9b2c27e02b6205fd3 (diff)
downloadPROJ-34ea13913717b20982778d5e5f13b4a5ee8288ab.tar.gz
PROJ-34ea13913717b20982778d5e5f13b4a5ee8288ab.zip
Merge pull request #2656 from rouault/createFromUserInput_improvements
createFromUserInput(): various enhancements
Diffstat (limited to 'src')
-rw-r--r--src/iso19111/factory.cpp73
-rw-r--r--src/iso19111/io.cpp236
-rw-r--r--src/iso19111/static.cpp11
3 files changed, 315 insertions, 5 deletions
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp
index fa2a0ef3..3367f55c 100644
--- a/src/iso19111/factory.cpp
+++ b/src/iso19111/factory.cpp
@@ -4951,6 +4951,79 @@ AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
if (crs) {
return NN_NO_CHECK(crs);
}
+
+ if (d->authority() == metadata::Identifier::OGC) {
+ if (code == "AnsiDate") {
+ // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
+ return crs::TemporalCRS::create(
+ util::PropertyMap()
+ // above URL indicates Julian Date" as name... likely wrong
+ .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
+ .set(metadata::Identifier::CODESPACE_KEY, d->authority())
+ .set(metadata::Identifier::CODE_KEY, code),
+ datum::TemporalDatum::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
+ "as day 1."),
+ common::DateTime::create("1600-12-31T00:00:00Z"),
+ datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
+ cs::TemporalCountCS::create(
+ util::PropertyMap(),
+ cs::CoordinateSystemAxis::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, "Time"),
+ "T", cs::AxisDirection::FUTURE,
+ common::UnitOfMeasure("day", 0,
+ UnitOfMeasure::Type::TIME))));
+ }
+ if (code == "JulianDate") {
+ // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
+ return crs::TemporalCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
+ .set(metadata::Identifier::CODESPACE_KEY, d->authority())
+ .set(metadata::Identifier::CODE_KEY, code),
+ datum::TemporalDatum::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY,
+ "The beginning of the Julian period."),
+ common::DateTime::create("-4714-11-24T12:00:00Z"),
+ datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
+ cs::TemporalCountCS::create(
+ util::PropertyMap(),
+ cs::CoordinateSystemAxis::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, "Time"),
+ "T", cs::AxisDirection::FUTURE,
+ common::UnitOfMeasure("day", 0,
+ UnitOfMeasure::Type::TIME))));
+ }
+ if (code == "UnixTime") {
+ // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
+ return crs::TemporalCRS::create(
+ util::PropertyMap()
+ .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
+ .set(metadata::Identifier::CODESPACE_KEY, d->authority())
+ .set(metadata::Identifier::CODE_KEY, code),
+ datum::TemporalDatum::create(
+ util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
+ "Unix epoch"),
+ common::DateTime::create("1970-01-01T00:00:00Z"),
+ datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
+ cs::TemporalCountCS::create(
+ util::PropertyMap(),
+ cs::CoordinateSystemAxis::create(
+ util::PropertyMap().set(
+ common::IdentifiedObject::NAME_KEY, "Time"),
+ "T", cs::AxisDirection::FUTURE,
+ common::UnitOfMeasure::SECOND)));
+ }
+ if (code == "84") {
+ return createCoordinateReferenceSystem("CRS84", false);
+ }
+ }
+
auto res = d->runWithCodeParam(
"SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
if (res.empty()) {
diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp
index 184d992f..bd29c310 100644
--- a/src/iso19111/io.cpp
+++ b/src/iso19111/io.cpp
@@ -6126,6 +6126,188 @@ 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);
+}
+
+// ---------------------------------------------------------------------------
+
+/* Import a CRS encoded as WMSAUTO string.
+ *
+ * Note that the WMS 1.3 specification does not include the
+ * units code, while apparently earlier specs do. We try to
+ * guess around this.
+ *
+ * (code derived from GDAL's importFromWMSAUTO())
+ */
+
+static CRSNNPtr importFromWMSAUTO(const std::string &text) {
+
+ int nUnitsId = 9001;
+ double dfRefLong;
+ double dfRefLat = 0.0;
+
+ assert(ci_starts_with(text, "AUTO:"));
+ const auto parts = split(text.substr(strlen("AUTO:")), ',');
+
+ try {
+ constexpr int AUTO_MOLLWEIDE = 42005;
+ if (parts.size() == 4) {
+ nUnitsId = std::stoi(parts[1]);
+ dfRefLong = c_locale_stod(parts[2]);
+ dfRefLat = c_locale_stod(parts[3]);
+ } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
+ nUnitsId = std::stoi(parts[1]);
+ dfRefLong = c_locale_stod(parts[2]);
+ } else if (parts.size() == 3) {
+ dfRefLong = c_locale_stod(parts[1]);
+ dfRefLat = c_locale_stod(parts[2]);
+ } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
+ dfRefLong = c_locale_stod(parts[1]);
+ } else {
+ throw ParsingException("invalid WMS AUTO CRS definition");
+ }
+
+ const auto getConversion = [=]() {
+ const int nProjId = std::stoi(parts[0]);
+ switch (nProjId) {
+ case 42001: // Auto UTM
+ if (!(dfRefLong >= -180 && dfRefLong < 180)) {
+ throw ParsingException("invalid WMS AUTO CRS definition: "
+ "invalid longitude");
+ }
+ return Conversion::createUTM(
+ util::PropertyMap(),
+ static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
+ dfRefLat >= 0.0);
+
+ case 42002: // Auto TM (strangely very UTM-like).
+ return Conversion::createTransverseMercator(
+ util::PropertyMap(), common::Angle(0),
+ common::Angle(dfRefLong), common::Scale(0.9996),
+ common::Length(500000),
+ common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0));
+
+ case 42003: // Auto Orthographic.
+ return Conversion::createOrthographic(
+ util::PropertyMap(), common::Angle(dfRefLat),
+ common::Angle(dfRefLong), common::Length(0),
+ common::Length(0));
+
+ case 42004: // Auto Equirectangular
+ return Conversion::createEquidistantCylindrical(
+ util::PropertyMap(), common::Angle(dfRefLat),
+ common::Angle(dfRefLong), common::Length(0),
+ common::Length(0));
+
+ case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant
+ return Conversion::createMollweide(
+ util::PropertyMap(), common::Angle(dfRefLong),
+ common::Length(0), common::Length(0));
+
+ default:
+ throw ParsingException("invalid WMS AUTO CRS definition: "
+ "unsupported projection id");
+ }
+ };
+
+ const auto getUnits = [=]() {
+ switch (nUnitsId) {
+ case 9001:
+ return UnitOfMeasure::METRE;
+
+ case 9002:
+ return UnitOfMeasure::FOOT;
+
+ case 9003:
+ return UnitOfMeasure::US_FOOT;
+
+ default:
+ throw ParsingException("invalid WMS AUTO CRS definition: "
+ "unsupported units code");
+ }
+ };
+
+ return crs::ProjectedCRS::create(
+ util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
+ crs::GeographicCRS::EPSG_4326, getConversion(),
+ cs::CartesianCS::createEastingNorthing(getUnits()));
+
+ } catch (const std::exception &) {
+ throw ParsingException("invalid WMS AUTO CRS definition");
+ }
+}
+
+// ---------------------------------------------------------------------------
+
static BaseObjectNNPtr createFromUserInput(const std::string &text,
const DatabaseContextPtr &dbContext,
bool usePROJ4InitRules,
@@ -6187,6 +6369,14 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
.createFromPROJString(text);
}
+ if (isCRSURL(text) && dbContext) {
+ return importFromCRSURL(text, NN_NO_CHECK(dbContext));
+ }
+
+ if (ci_starts_with(text, "AUTO:")) {
+ return importFromWMSAUTO(text);
+ }
+
auto tokens = split(text, ':');
if (tokens.size() == 2) {
if (!dbContext) {
@@ -6406,15 +6596,14 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
return ConcatenatedOperation::createComputeMetadata(components, true);
}
- // urn:ogc:def:crs:EPSG::4326
- if (tokens.size() == 7) {
+ const auto createFromURNPart =
+ [&dbContext](const std::string &type, const std::string &authName,
+ const std::string &code) -> BaseObjectNNPtr {
if (!dbContext) {
throw ParsingException("no database context specified");
}
- const auto &type = tokens[3];
auto factory =
- AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]);
- const auto &code = tokens[6];
+ AuthorityFactory::create(NN_NO_CHECK(dbContext), authName);
if (type == "crs") {
return factory->createCoordinateReferenceSystem(code);
}
@@ -6434,6 +6623,39 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text,
return factory->createPrimeMeridian(code);
}
throw ParsingException(concat("unhandled object type: ", type));
+ };
+
+ // urn:ogc:def:crs:EPSG::4326
+ if (tokens.size() == 7 && tokens[0] == "urn") {
+
+ const auto &type = tokens[3];
+ const auto &authName = tokens[4];
+ const auto &code = tokens[6];
+ return createFromURNPart(type, authName, code);
+ }
+
+ // urn:ogc:def:crs:OGC::AUTO42001:-117:33
+ if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" &&
+ ci_starts_with(tokens[6], "AUTO")) {
+ const auto textAUTO = text.substr(text.find(":AUTO") + 5);
+ return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ","));
+ }
+
+ // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to
+ // above)
+ if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") {
+ const auto &type = tokens[2];
+ const auto &authName = tokens[3];
+ const auto &code = tokens[5];
+ return createFromURNPart(type, authName, code);
+ }
+
+ // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version)
+ if (tokens.size() == 6 && tokens[0] == "urn") {
+ const auto &type = tokens[3];
+ const auto &authName = tokens[4];
+ const auto &code = tokens[5];
+ return createFromURNPart(type, authName, code);
}
if (dbContext) {
@@ -6590,6 +6812,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/src/iso19111/static.cpp b/src/iso19111/static.cpp
index d099cd6c..e4467882 100644
--- a/src/iso19111/static.cpp
+++ b/src/iso19111/static.cpp
@@ -317,6 +317,17 @@ const UnitOfMeasure UnitOfMeasure::METRE("metre", 1.0,
UnitOfMeasure::Type::LINEAR,
Identifier::EPSG, "9001");
+/** \brief Foot, unit of measure of type LINEAR. */
+const UnitOfMeasure UnitOfMeasure::FOOT("foot", 0.3048,
+ UnitOfMeasure::Type::LINEAR,
+ Identifier::EPSG, "9002");
+
+/** \brief US survey foot, unit of measure of type LINEAR. */
+const UnitOfMeasure UnitOfMeasure::US_FOOT("US survey foot",
+ 0.304800609601219241184,
+ UnitOfMeasure::Type::LINEAR,
+ Identifier::EPSG, "9003");
+
/** \brief Degree, unit of measure of type ANGULAR. */
const UnitOfMeasure UnitOfMeasure::DEGREE("degree", M_PI / 180.,
UnitOfMeasure::Type::ANGULAR,