aboutsummaryrefslogtreecommitdiff
path: root/src/apps/projinfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/apps/projinfo.cpp')
-rw-r--r--src/apps/projinfo.cpp1067
1 files changed, 1067 insertions, 0 deletions
diff --git a/src/apps/projinfo.cpp b/src/apps/projinfo.cpp
new file mode 100644
index 00000000..d604365a
--- /dev/null
+++ b/src/apps/projinfo.cpp
@@ -0,0 +1,1067 @@
+/******************************************************************************
+ *
+ * Project: PROJ
+ * Purpose: projinfo utility
+ * Author: Even Rouault <even dot rouault at spatialys dot com>
+ *
+ ******************************************************************************
+ * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ ****************************************************************************/
+
+//! @cond Doxygen_Suppress
+
+#define FROM_PROJ_CPP
+
+#include <cstdlib>
+#include <fstream> // std::ifstream
+#include <iostream>
+#include <utility>
+
+#include "projects.h"
+
+#include <proj/common.hpp>
+#include <proj/coordinateoperation.hpp>
+#include <proj/crs.hpp>
+#include <proj/io.hpp>
+#include <proj/metadata.hpp>
+#include <proj/util.hpp>
+
+#include "proj/internal/internal.hpp" // for split
+
+using namespace NS_PROJ::common;
+using namespace NS_PROJ::crs;
+using namespace NS_PROJ::io;
+using namespace NS_PROJ::metadata;
+using namespace NS_PROJ::operation;
+using namespace NS_PROJ::util;
+using namespace NS_PROJ::internal;
+
+// ---------------------------------------------------------------------------
+
+namespace { // anonymous namespace
+struct OutputOptions {
+ bool quiet = false;
+ bool PROJ5 = false;
+ bool PROJ4 = false;
+ bool WKT2_2018 = false;
+ bool WKT2_2018_SIMPLIFIED = false;
+ bool WKT2_2015 = false;
+ bool WKT2_2015_SIMPLIFIED = false;
+ bool WKT1_GDAL = false;
+ bool WKT1_ESRI = false;
+ bool c_ify = false;
+ bool singleLine = false;
+};
+} // anonymous namespace
+
+// ---------------------------------------------------------------------------
+
+static void usage() {
+ std::cerr
+ << "usage: projinfo [-o formats] [-k crs|operation] [--summary] [-q]"
+ << std::endl
+ << " ([--area name_or_code] | "
+ "[--bbox min_long,min_lat,max_long,max_lat]) "
+ << std::endl
+ << " [--spatial-test contains|intersects]" << std::endl
+ << " [--crs-extent-use none|both|intersection|smallest]"
+ << std::endl
+ << " [--grid-check none|discard_missing|sort] "
+ "[--show-superseded]"
+ << std::endl
+ << " [--pivot-crs none|{auth:code[,auth:code]*}]"
+ << std::endl
+ << " [--boundcrs-to-wgs84]" << std::endl
+ << " [--main-db-path path] [--aux-db-path path]*"
+ << std::endl
+ << " [--identify]" << std::endl
+ << " [--c-ify] [--single-line]" << std::endl
+ << " {object_definition} | (-s {srs_def} -t {srs_def})"
+ << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "-o: formats is a comma separated combination of: "
+ "all,default,PROJ4,PROJ,WKT_ALL,WKT2_2015,WKT2_2018,WKT1_GDAL,"
+ "WKT1_ESRI"
+ << std::endl;
+ std::cerr << " Except 'all' and 'default', other format can be preceded "
+ "by '-' to disable them"
+ << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "{object_definition} might be a PROJ string, a WKT string, "
+ " a AUTHORITY:CODE, or urn:ogc:def:OBJECT_TYPE:AUTHORITY::CODE"
+ << std::endl;
+ std::exit(1);
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string un_c_ify_string(const std::string &str) {
+ std::string out(str);
+ out = out.substr(1, out.size() - 2);
+ out = replaceAll(out, "\\\"", "{ESCAPED_DOUBLE_QUOTE}");
+ out = replaceAll(out, "\\n\"", "");
+ out = replaceAll(out, "\"", "");
+ out = replaceAll(out, "{ESCAPED_DOUBLE_QUOTE}", "\"");
+ return out;
+}
+
+// ---------------------------------------------------------------------------
+
+static std::string c_ify_string(const std::string &str) {
+ std::string out(str);
+ out = replaceAll(out, "\"", "{DOUBLE_QUOTE}");
+ out = replaceAll(out, "\n", "\\n\"\n\"");
+ out = replaceAll(out, "{DOUBLE_QUOTE}", "\\\"");
+ return "\"" + out + "\"";
+}
+
+// ---------------------------------------------------------------------------
+
+static BaseObjectNNPtr buildObject(DatabaseContextPtr dbContext,
+ const std::string &user_string,
+ bool kindIsCRS, const std::string &context,
+ bool buildBoundCRSToWGS84, bool allowPivots,
+ bool quiet) {
+ BaseObjectPtr obj;
+
+ std::string l_user_string(user_string);
+ if (!user_string.empty() && user_string[0] == '@') {
+ std::ifstream fs;
+ auto filename = user_string.substr(1);
+ fs.open(filename, std::fstream::in | std::fstream::binary);
+ if (!fs.is_open()) {
+ std::cerr << context << ": cannot open " << filename << std::endl;
+ std::exit(1);
+ }
+ l_user_string.clear();
+ while (!fs.eof()) {
+ char buffer[256];
+ fs.read(buffer, sizeof(buffer));
+ l_user_string.append(buffer, static_cast<size_t>(fs.gcount()));
+ if (l_user_string.size() > 100 * 1000) {
+ fs.close();
+ std::cerr << context << ": too big file" << std::endl;
+ std::exit(1);
+ }
+ }
+ fs.close();
+ }
+ if (!l_user_string.empty() && l_user_string.back() == '\n') {
+ l_user_string.resize(l_user_string.size() - 1);
+ }
+ if (!l_user_string.empty() && l_user_string.back() == '\r') {
+ l_user_string.resize(l_user_string.size() - 1);
+ }
+
+ try {
+ auto tokens = split(l_user_string, ':');
+ if (!kindIsCRS && tokens.size() == 2) {
+ auto urn = "urn:ogc:def:coordinateOperation:" + tokens[0] + "::" +
+ tokens[1];
+ obj = createFromUserInput(urn, dbContext).as_nullable();
+ } else {
+ // Convenience to be able to use C escaped strings...
+ if (l_user_string.size() > 2 && l_user_string[0] == '"' &&
+ l_user_string.back() == '"' &&
+ l_user_string.find("\\\"") != std::string::npos) {
+ l_user_string = un_c_ify_string(l_user_string);
+ }
+ WKTParser wktParser;
+ if (wktParser.guessDialect(l_user_string) !=
+ WKTParser::WKTGuessedDialect::NOT_WKT) {
+ wktParser.setStrict(false);
+ wktParser.attachDatabaseContext(dbContext);
+ obj = wktParser.createFromWKT(l_user_string).as_nullable();
+ if (!quiet) {
+ auto warnings = wktParser.warningList();
+ if (!warnings.empty()) {
+ for (const auto &str : warnings) {
+ std::cerr << "Warning: " << str << std::endl;
+ }
+ }
+ }
+ } else {
+ obj =
+ createFromUserInput(l_user_string, dbContext).as_nullable();
+ }
+ }
+ } catch (const std::exception &e) {
+ std::cerr << context << ": parsing of user string failed: " << e.what()
+ << std::endl;
+ std::exit(1);
+ }
+
+ if (buildBoundCRSToWGS84) {
+ auto crs = std::dynamic_pointer_cast<CRS>(obj);
+ if (crs) {
+ obj = crs->createBoundCRSToWGS84IfPossible(dbContext, allowPivots)
+ .as_nullable();
+ }
+ }
+
+ return NN_NO_CHECK(obj);
+}
+
+// ---------------------------------------------------------------------------
+
+static void outputObject(DatabaseContextPtr dbContext, BaseObjectNNPtr obj,
+ bool allowPivots, const OutputOptions &outputOpt) {
+
+ auto identified = dynamic_cast<const IdentifiedObject *>(obj.get());
+ if (!outputOpt.quiet && identified && identified->isDeprecated()) {
+ std::cout << "Warning: object is deprecated" << std::endl;
+ auto crs = dynamic_cast<const CRS *>(obj.get());
+ if (crs && dbContext) {
+ try {
+ auto list = crs->getNonDeprecated(NN_NO_CHECK(dbContext));
+ if (!list.empty()) {
+ std::cout << "Alternative non-deprecated CRS:" << std::endl;
+ }
+ for (const auto &altCRS : list) {
+ const auto &ids = altCRS->identifiers();
+ if (!ids.empty()) {
+ std::cout << " " << *(ids[0]->codeSpace()) << ":"
+ << ids[0]->code() << std::endl;
+ }
+ }
+ } catch (const std::exception &) {
+ }
+ }
+ std::cout << std::endl;
+ }
+
+ auto projStringExportable =
+ nn_dynamic_pointer_cast<IPROJStringExportable>(obj);
+ bool alreadyOutputed = false;
+ if (projStringExportable) {
+ if (outputOpt.PROJ5) {
+ try {
+ if (!outputOpt.quiet) {
+ std::cout << "PROJ string:" << std::endl;
+ }
+ std::cout << projStringExportable->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_5,
+ dbContext)
+ .get())
+ << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to PROJ string: " << e.what()
+ << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+
+ if (outputOpt.PROJ4) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "PROJ.4 string:" << std::endl;
+ }
+
+ auto crs = nn_dynamic_pointer_cast<CRS>(obj);
+ std::shared_ptr<IPROJStringExportable> objToExport;
+ if (crs) {
+ objToExport =
+ nn_dynamic_pointer_cast<IPROJStringExportable>(
+ crs->createBoundCRSToWGS84IfPossible(dbContext,
+ allowPivots));
+ }
+ if (!objToExport) {
+ objToExport = projStringExportable;
+ }
+
+ std::cout << objToExport->exportToPROJString(
+ PROJStringFormatter::create(
+ PROJStringFormatter::Convention::PROJ_4,
+ dbContext)
+ .get())
+ << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to PROJ string: " << e.what()
+ << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+ }
+
+ auto wktExportable = nn_dynamic_pointer_cast<IWKTExportable>(obj);
+ if (wktExportable) {
+ if (outputOpt.WKT2_2015) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "WKT2_2015 string:" << std::endl;
+ }
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2015);
+ if (outputOpt.singleLine) {
+ formatter->setMultiLine(false);
+ }
+ auto wkt = wktExportable->exportToWKT(formatter.get());
+ if (outputOpt.c_ify) {
+ wkt = c_ify_string(wkt);
+ }
+ std::cout << wkt << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to WKT2_2015: " << e.what()
+ << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+
+ if (outputOpt.WKT2_2015_SIMPLIFIED) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "WKT2_2015_SIMPLIFIED string:" << std::endl;
+ }
+ auto formatter = WKTFormatter::create(
+ WKTFormatter::Convention::WKT2_2015_SIMPLIFIED);
+ if (outputOpt.singleLine) {
+ formatter->setMultiLine(false);
+ }
+ auto wkt = wktExportable->exportToWKT(formatter.get());
+ if (outputOpt.c_ify) {
+ wkt = c_ify_string(wkt);
+ }
+ std::cout << wkt << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to WKT2_2015_SIMPLIFIED: "
+ << e.what() << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+
+ if (outputOpt.WKT2_2018) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "WKT2_2018 string:" << std::endl;
+ }
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT2_2018);
+ if (outputOpt.singleLine) {
+ formatter->setMultiLine(false);
+ }
+ auto wkt = wktExportable->exportToWKT(formatter.get());
+ if (outputOpt.c_ify) {
+ wkt = c_ify_string(wkt);
+ }
+ std::cout << wkt << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to WKT2_2018: " << e.what()
+ << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+
+ if (outputOpt.WKT2_2018_SIMPLIFIED) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "WKT2_2018_SIMPLIFIED string:" << std::endl;
+ }
+ auto formatter = WKTFormatter::create(
+ WKTFormatter::Convention::WKT2_2018_SIMPLIFIED);
+ if (outputOpt.singleLine) {
+ formatter->setMultiLine(false);
+ }
+ auto wkt = wktExportable->exportToWKT(formatter.get());
+ if (outputOpt.c_ify) {
+ wkt = c_ify_string(wkt);
+ }
+ std::cout << wkt << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to WKT2_2018_SIMPLIFIED: "
+ << e.what() << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+
+ if (outputOpt.WKT1_GDAL && !nn_dynamic_pointer_cast<Conversion>(obj)) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "WKT1_GDAL:" << std::endl;
+ }
+
+ auto crs = nn_dynamic_pointer_cast<CRS>(obj);
+ std::shared_ptr<IWKTExportable> objToExport;
+ if (crs) {
+ objToExport = nn_dynamic_pointer_cast<IWKTExportable>(
+ crs->createBoundCRSToWGS84IfPossible(dbContext,
+ allowPivots));
+ }
+ if (!objToExport) {
+ objToExport = wktExportable;
+ }
+
+ auto formatter =
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL);
+ if (outputOpt.singleLine) {
+ formatter->setMultiLine(false);
+ }
+ auto wkt = objToExport->exportToWKT(formatter.get());
+ if (outputOpt.c_ify) {
+ wkt = c_ify_string(wkt);
+ }
+ std::cout << wkt << std::endl;
+ std::cout << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to WKT1_GDAL: " << e.what()
+ << std::endl;
+ }
+ alreadyOutputed = true;
+ }
+
+ if (outputOpt.WKT1_ESRI && !nn_dynamic_pointer_cast<Conversion>(obj)) {
+ try {
+ if (alreadyOutputed) {
+ std::cout << std::endl;
+ }
+ if (!outputOpt.quiet) {
+ std::cout << "WKT1_ESRI:" << std::endl;
+ }
+
+ auto wkt = wktExportable->exportToWKT(
+ WKTFormatter::create(WKTFormatter::Convention::WKT1_ESRI,
+ dbContext)
+ .get());
+ if (outputOpt.c_ify) {
+ wkt = c_ify_string(wkt);
+ }
+ std::cout << wkt << std::endl;
+ std::cout << std::endl;
+ } catch (const std::exception &e) {
+ std::cerr << "Error when exporting to WKT1_ESRI: " << e.what()
+ << std::endl;
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+static void outputOperationSummary(const CoordinateOperationNNPtr &op) {
+ auto ids = op->identifiers();
+ if (!ids.empty()) {
+ std::cout << *(ids[0]->codeSpace()) << ":" << ids[0]->code();
+ } else {
+ std::cout << "unknown id";
+ }
+
+ std::cout << ", ";
+
+ auto name = op->nameStr();
+ if (!name.empty()) {
+ std::cout << name;
+ } else {
+ std::cout << "unknown name";
+ }
+
+ std::cout << ", ";
+
+ auto accuracies = op->coordinateOperationAccuracies();
+ if (!accuracies.empty()) {
+ std::cout << accuracies[0]->value() << " m";
+ } else {
+ if (std::dynamic_pointer_cast<Conversion>(op.as_nullable())) {
+ std::cout << "0 m";
+ } else {
+ std::cout << "unknown accuracy";
+ }
+ }
+
+ std::cout << ", ";
+
+ auto domains = op->domains();
+ if (!domains.empty() && domains[0]->domainOfValidity() &&
+ domains[0]->domainOfValidity()->description().has_value()) {
+ std::cout << *(domains[0]->domainOfValidity()->description());
+ } else {
+ std::cout << "unknown domain of validity";
+ }
+
+ std::cout << std::endl;
+}
+
+// ---------------------------------------------------------------------------
+
+static void outputOperations(
+ DatabaseContextPtr dbContext, const std::string &sourceCRSStr,
+ const std::string &targetCRSStr, const ExtentPtr &bboxFilter,
+ CoordinateOperationContext::SpatialCriterion spatialCriterion,
+ CoordinateOperationContext::SourceTargetCRSExtentUse crsExtentUse,
+ CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse,
+ bool allowPivots,
+ const std::vector<std::pair<std::string, std::string>> &pivots,
+ const std::string &authority, bool usePROJGridAlternatives,
+ bool showSuperseded, const OutputOptions &outputOpt, bool summary) {
+ auto sourceObj = buildObject(dbContext, sourceCRSStr, true, "source CRS",
+ false, false, outputOpt.quiet);
+ auto sourceCRS = nn_dynamic_pointer_cast<CRS>(sourceObj);
+ if (!sourceCRS) {
+ std::cerr << "source CRS string is not a CRS" << std::endl;
+ std::exit(1);
+ }
+
+ auto targetObj = buildObject(dbContext, targetCRSStr, true, "target CRS",
+ false, false, outputOpt.quiet);
+ auto targetCRS = nn_dynamic_pointer_cast<CRS>(targetObj);
+ if (!targetCRS) {
+ std::cerr << "target CRS string is not a CRS" << std::endl;
+ std::exit(1);
+ }
+
+ std::vector<CoordinateOperationNNPtr> list;
+ try {
+ auto authFactory =
+ dbContext
+ ? AuthorityFactory::create(NN_NO_CHECK(dbContext), authority)
+ .as_nullable()
+ : nullptr;
+ auto ctxt =
+ CoordinateOperationContext::create(authFactory, bboxFilter, 0);
+ ctxt->setSpatialCriterion(spatialCriterion);
+ ctxt->setSourceAndTargetCRSExtentUse(crsExtentUse);
+ ctxt->setGridAvailabilityUse(gridAvailabilityUse);
+ ctxt->setAllowUseIntermediateCRS(allowPivots);
+ ctxt->setIntermediateCRS(pivots);
+ ctxt->setUsePROJAlternativeGridNames(usePROJGridAlternatives);
+ ctxt->setDiscardSuperseded(!showSuperseded);
+ list = CoordinateOperationFactory::create()->createOperations(
+ NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), ctxt);
+ } catch (const std::exception &e) {
+ std::cerr << "createOperations() failed with: " << e.what()
+ << std::endl;
+ std::exit(1);
+ }
+ if (outputOpt.quiet && !list.empty()) {
+ outputObject(dbContext, list[0], allowPivots, outputOpt);
+ return;
+ }
+ if (summary) {
+ std::cout << "Candidate operations found: " << list.size() << std::endl;
+ for (const auto &op : list) {
+ outputOperationSummary(op);
+ }
+ } else {
+ bool first = true;
+ for (size_t i = 0; i < list.size(); ++i) {
+ const auto &op = list[i];
+ if (list.size() > 1) {
+ if (!first) {
+ std::cout << std::endl;
+ }
+ first = false;
+ std::cout << "-------------------------------------"
+ << std::endl;
+ std::cout << "Operation n"
+ "\xC2\xB0"
+ << (i + 1) << ":" << std::endl
+ << std::endl;
+ }
+ outputOperationSummary(op);
+ std::cout << std::endl;
+ outputObject(dbContext, op, allowPivots, outputOpt);
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+int main(int argc, char **argv) {
+
+ if (argc == 1) {
+ std::cerr << pj_get_release() << std::endl;
+ usage();
+ }
+
+ std::string user_string;
+ bool user_string_specified = false;
+ std::string sourceCRSStr;
+ std::string targetCRSStr;
+ bool outputSwithSpecified = false;
+ OutputOptions outputOpt;
+ bool kindIsCRS = true;
+ bool summary = false;
+ ExtentPtr bboxFilter = nullptr;
+ std::string area;
+ CoordinateOperationContext::SpatialCriterion spatialCriterion =
+ CoordinateOperationContext::SpatialCriterion::STRICT_CONTAINMENT;
+ CoordinateOperationContext::SourceTargetCRSExtentUse crsExtentUse =
+ CoordinateOperationContext::SourceTargetCRSExtentUse::SMALLEST;
+ bool buildBoundCRSToWGS84 = false;
+ CoordinateOperationContext::GridAvailabilityUse gridAvailabilityUse =
+ CoordinateOperationContext::GridAvailabilityUse::USE_FOR_SORTING;
+ bool allowPivots = true;
+ std::vector<std::pair<std::string, std::string>> pivots;
+ bool usePROJGridAlternatives = true;
+ std::string mainDBPath;
+ std::vector<std::string> auxDBPath;
+ bool guessDialect = false;
+ std::string authority;
+ bool identify = false;
+ bool showSuperseded = false;
+
+ for (int i = 1; i < argc; i++) {
+ std::string arg(argv[i]);
+ if (arg == "-o" && i + 1 < argc) {
+ outputSwithSpecified = true;
+ i++;
+ auto formats(split(argv[i], ','));
+ for (auto format : formats) {
+ if (ci_equal(format, "all")) {
+ outputOpt.PROJ5 = true;
+ outputOpt.PROJ4 = true;
+ outputOpt.WKT2_2018 = true;
+ outputOpt.WKT2_2015 = true;
+ outputOpt.WKT1_GDAL = true;
+ outputOpt.WKT1_ESRI = true;
+ } else if (ci_equal(format, "default")) {
+ outputOpt.PROJ5 = true;
+ outputOpt.PROJ4 = false;
+ outputOpt.WKT2_2018 = false;
+ outputOpt.WKT2_2015 = true;
+ outputOpt.WKT1_GDAL = false;
+ } else if (ci_equal(format, "PROJ4") ||
+ ci_equal(format, "PROJ.4")) {
+ outputOpt.PROJ4 = true;
+ } else if (ci_equal(format, "-PROJ4") ||
+ ci_equal(format, "-PROJ.4")) {
+ outputOpt.PROJ4 = false;
+ } else if (ci_equal(format, "PROJ")) {
+ outputOpt.PROJ5 = true;
+ } else if (ci_equal(format, "-PROJ")) {
+ outputOpt.PROJ5 = false;
+ } else if (ci_equal(format, "WKT_ALL") ||
+ ci_equal(format, "WKT-ALL")) {
+ outputOpt.WKT2_2018 = true;
+ outputOpt.WKT2_2015 = true;
+ outputOpt.WKT1_GDAL = true;
+ } else if (ci_equal(format, "-WKT_ALL") ||
+ ci_equal(format, "-WKT-ALL")) {
+ outputOpt.WKT2_2018 = false;
+ outputOpt.WKT2_2015 = false;
+ outputOpt.WKT1_GDAL = false;
+ } else if (ci_equal(format, "WKT2_2018") ||
+ ci_equal(format, "WKT2-2018") ||
+ ci_equal(format, "WKT2:2018")) {
+ outputOpt.WKT2_2018 = true;
+ } else if (ci_equal(format, "WKT2_2018_SIMPLIFIED") ||
+ ci_equal(format, "WKT2-2018_SIMPLIFIED") ||
+ ci_equal(format, "WKT2:2018_SIMPLIFIED")) {
+ outputOpt.WKT2_2018_SIMPLIFIED = true;
+ } else if (ci_equal(format, "-WKT2_2018") ||
+ ci_equal(format, "-WKT2-2018") ||
+ ci_equal(format, "-WKT2:2018")) {
+ outputOpt.WKT2_2018 = false;
+ } else if (ci_equal(format, "WKT2_2015") ||
+ ci_equal(format, "WKT2-2015") ||
+ ci_equal(format, "WKT2:2015")) {
+ outputOpt.WKT2_2015 = true;
+ } else if (ci_equal(format, "WKT2_2015_SIMPLIFIED") ||
+ ci_equal(format, "WKT2-2015_SIMPLIFIED") ||
+ ci_equal(format, "WKT2:2015_SIMPLIFIED")) {
+ outputOpt.WKT2_2015_SIMPLIFIED = true;
+ } else if (ci_equal(format, "-WKT2_2015") ||
+ ci_equal(format, "-WKT2-2015") ||
+ ci_equal(format, "-WKT2:2015")) {
+ outputOpt.WKT2_2015 = false;
+ } else if (ci_equal(format, "WKT1_GDAL") ||
+ ci_equal(format, "WKT1-GDAL") ||
+ ci_equal(format, "WKT1:GDAL")) {
+ outputOpt.WKT1_GDAL = true;
+ } else if (ci_equal(format, "-WKT1_GDAL") ||
+ ci_equal(format, "-WKT1-GDAL") ||
+ ci_equal(format, "-WKT1:GDAL")) {
+ outputOpt.WKT1_GDAL = false;
+ } else if (ci_equal(format, "WKT1_ESRI") ||
+ ci_equal(format, "WKT1-ESRI") ||
+ ci_equal(format, "WKT1:ESRI")) {
+ outputOpt.WKT1_ESRI = true;
+ } else if (ci_equal(format, "-WKT1_ESRI") ||
+ ci_equal(format, "-WKT1-ESRI") ||
+ ci_equal(format, "-WKT1:ESRI")) {
+ outputOpt.WKT1_ESRI = false;
+ } else {
+ std::cerr << "Unrecognized value for option -o: " << format
+ << std::endl;
+ usage();
+ }
+ }
+ } else if (arg == "--bbox" && i + 1 < argc) {
+ i++;
+ auto bboxStr(argv[i]);
+ auto bbox(split(bboxStr, ','));
+ if (bbox.size() != 4) {
+ std::cerr << "Incorrect number of values for option --bbox: "
+ << bboxStr << std::endl;
+ usage();
+ }
+ try {
+ bboxFilter = Extent::createFromBBOX(
+ c_locale_stod(bbox[0]), c_locale_stod(bbox[1]),
+ c_locale_stod(bbox[2]), c_locale_stod(bbox[3]))
+ .as_nullable();
+ } catch (const std::exception &e) {
+ std::cerr << "Invalid value for option --bbox: " << bboxStr
+ << ", " << e.what() << std::endl;
+ usage();
+ }
+ } else if (arg == "--area" && i + 1 < argc) {
+ i++;
+ area = argv[i];
+ } else if (arg == "-k" && i + 1 < argc) {
+ i++;
+ std::string kind(argv[i]);
+ if (ci_equal(kind, "crs") || ci_equal(kind, "srs")) {
+ kindIsCRS = true;
+ } else if (ci_equal(kind, "operation")) {
+ kindIsCRS = false;
+ } else {
+ std::cerr << "Unrecognized value for option -k: " << kind
+ << std::endl;
+ usage();
+ }
+ } else if ((arg == "-s" || arg == "--ssource-crs") && i + 1 < argc) {
+ i++;
+ sourceCRSStr = argv[i];
+ } else if ((arg == "-t" || arg == "--target-crs") && i + 1 < argc) {
+ i++;
+ targetCRSStr = argv[i];
+ } else if (arg == "-q" || arg == "--quiet") {
+ outputOpt.quiet = true;
+ } else if (arg == "--c-ify") {
+ outputOpt.c_ify = true;
+ } else if (arg == "--single-line") {
+ outputOpt.singleLine = true;
+ } else if (arg == "--summary") {
+ summary = true;
+ } else if (ci_equal(arg, "--boundcrs-to-wgs84")) {
+ buildBoundCRSToWGS84 = true;
+
+ // undocumented: only for debugging purposes
+ } else if (ci_equal(arg, "--no-proj-grid-alternatives")) {
+ usePROJGridAlternatives = false;
+
+ } else if (arg == "--spatial-test" && i + 1 < argc) {
+ i++;
+ std::string value(argv[i]);
+ if (ci_equal(value, "contains")) {
+ spatialCriterion = CoordinateOperationContext::
+ SpatialCriterion::STRICT_CONTAINMENT;
+ } else if (ci_equal(value, "intersects")) {
+ spatialCriterion = CoordinateOperationContext::
+ SpatialCriterion::PARTIAL_INTERSECTION;
+ } else {
+ std::cerr << "Unrecognized value for option --spatial-test: "
+ << value << std::endl;
+ usage();
+ }
+ } else if (arg == "--crs-extent-use" && i + 1 < argc) {
+ i++;
+ std::string value(argv[i]);
+ if (ci_equal(value, "NONE")) {
+ crsExtentUse =
+ CoordinateOperationContext::SourceTargetCRSExtentUse::NONE;
+ } else if (ci_equal(value, "BOTH")) {
+ crsExtentUse =
+ CoordinateOperationContext::SourceTargetCRSExtentUse::BOTH;
+ } else if (ci_equal(value, "INTERSECTION")) {
+ crsExtentUse = CoordinateOperationContext::
+ SourceTargetCRSExtentUse::INTERSECTION;
+ } else if (ci_equal(value, "SMALLEST")) {
+ crsExtentUse = CoordinateOperationContext::
+ SourceTargetCRSExtentUse::SMALLEST;
+ } else {
+ std::cerr << "Unrecognized value for option --crs-extent-use: "
+ << value << std::endl;
+ usage();
+ }
+ } else if (arg == "--grid-check" && i + 1 < argc) {
+ i++;
+ std::string value(argv[i]);
+ if (ci_equal(value, "none")) {
+ gridAvailabilityUse = CoordinateOperationContext::
+ GridAvailabilityUse::IGNORE_GRID_AVAILABILITY;
+ } else if (ci_equal(value, "discard_missing")) {
+ gridAvailabilityUse = CoordinateOperationContext::
+ GridAvailabilityUse::DISCARD_OPERATION_IF_MISSING_GRID;
+ } else if (ci_equal(value, "sort")) {
+ gridAvailabilityUse = CoordinateOperationContext::
+ GridAvailabilityUse::USE_FOR_SORTING;
+ } else {
+ std::cerr << "Unrecognized value for option --grid-check: "
+ << value << std::endl;
+ usage();
+ }
+ } else if (arg == "--pivot-crs" && i + 1 < argc) {
+ i++;
+ auto value(argv[i]);
+ if (ci_equal(std::string(value), "none")) {
+ allowPivots = false;
+ } else {
+ auto splitValue(split(value, ','));
+ for (const auto &v : splitValue) {
+ auto auth_code = split(v, ':');
+ if (auth_code.size() != 2) {
+ std::cerr
+ << "Unrecognized value for option --grid-check: "
+ << value << std::endl;
+ usage();
+ }
+ pivots.emplace_back(
+ std::make_pair(auth_code[0], auth_code[1]));
+ }
+ }
+ } else if (arg == "--main-db-path" && i + 1 < argc) {
+ i++;
+ mainDBPath = argv[i];
+ } else if (arg == "--aux-db-path" && i + 1 < argc) {
+ i++;
+ auxDBPath.push_back(argv[i]);
+ } else if (arg == "--guess-dialect") {
+ guessDialect = true;
+ } else if (arg == "--authority" && i + 1 < argc) {
+ i++;
+ authority = argv[i];
+ } else if (arg == "--identify") {
+ identify = true;
+ } else if (arg == "--show-superseded") {
+ showSuperseded = true;
+ } else if (arg == "-?" || arg == "--help") {
+ usage();
+ } else if (arg[0] == '-') {
+ std::cerr << "Unrecognized option: " << arg << std::endl;
+ usage();
+ } else {
+ if (!user_string_specified) {
+ user_string_specified = true;
+ user_string = arg;
+ } else {
+ std::cerr << "Too many parameters: " << arg << std::endl;
+ usage();
+ }
+ }
+ }
+
+ if (bboxFilter && !area.empty()) {
+ std::cerr << "ERROR: --bbox and --area are exclusive" << std::endl;
+ std::exit(1);
+ }
+
+ DatabaseContextPtr dbContext;
+ try {
+ dbContext =
+ DatabaseContext::create(mainDBPath, auxDBPath).as_nullable();
+ } catch (const std::exception &e) {
+ if (!mainDBPath.empty() || !auxDBPath.empty() || !area.empty()) {
+ std::cerr << "ERROR: Cannot create database connection: "
+ << e.what() << std::endl;
+ std::exit(1);
+ }
+ std::cerr << "WARNING: Cannot create database connection: " << e.what()
+ << std::endl;
+ }
+
+ if (!sourceCRSStr.empty() && targetCRSStr.empty()) {
+ std::cerr << "Source CRS specified, but missing target CRS"
+ << std::endl;
+ usage();
+ } else if (sourceCRSStr.empty() && !targetCRSStr.empty()) {
+ std::cerr << "Target CRS specified, but missing source CRS"
+ << std::endl;
+ usage();
+ } else if (!sourceCRSStr.empty() && !targetCRSStr.empty()) {
+ if (user_string_specified) {
+ std::cerr << "Unused extra value" << std::endl;
+ usage();
+ }
+ } else if (!user_string_specified) {
+ std::cerr << "Missing user string" << std::endl;
+ usage();
+ }
+
+ if (!outputSwithSpecified) {
+ outputOpt.PROJ5 = true;
+ outputOpt.WKT2_2015 = true;
+ }
+
+ if (outputOpt.quiet &&
+ (outputOpt.PROJ5 + outputOpt.PROJ4 + outputOpt.WKT2_2018 +
+ outputOpt.WKT2_2015 + outputOpt.WKT1_GDAL) != 1) {
+ std::cerr << "-q can only be used with a single output format"
+ << std::endl;
+ usage();
+ }
+
+ if (!user_string.empty()) {
+ auto obj(buildObject(dbContext, user_string, kindIsCRS, "input string",
+ buildBoundCRSToWGS84, allowPivots,
+ outputOpt.quiet));
+ if (guessDialect) {
+ auto dialect = WKTParser().guessDialect(user_string);
+ std::cout << "Guessed WKT dialect: ";
+ if (dialect == WKTParser::WKTGuessedDialect::WKT2_2018) {
+ std::cout << "WKT2_2018";
+ } else if (dialect == WKTParser::WKTGuessedDialect::WKT2_2015) {
+ std::cout << "WKT2_2015";
+ } else if (dialect == WKTParser::WKTGuessedDialect::WKT1_GDAL) {
+ std::cout << "WKT1_GDAL";
+ } else if (dialect == WKTParser::WKTGuessedDialect::WKT1_ESRI) {
+ std::cout << "WKT1_ESRI";
+ } else {
+ std::cout << "Not WKT / unknown";
+ }
+ std::cout << std::endl;
+ }
+ outputObject(dbContext, obj, allowPivots, outputOpt);
+ if (identify) {
+ auto crs = dynamic_cast<CRS *>(obj.get());
+ if (crs) {
+ try {
+ auto res = crs->identify(
+ dbContext
+ ? AuthorityFactory::create(NN_NO_CHECK(dbContext),
+ authority)
+ .as_nullable()
+ : nullptr);
+ std::cout << std::endl;
+ std::cout << "Identification match count: " << res.size()
+ << std::endl;
+ for (const auto &pair : res) {
+ const auto &identifiedCRS = pair.first;
+ const auto &ids = identifiedCRS->identifiers();
+ if (!ids.empty()) {
+ std::cout << *ids[0]->codeSpace() << ":"
+ << ids[0]->code() << ": " << pair.second
+ << " %" << std::endl;
+ } else {
+ auto boundCRS =
+ dynamic_cast<BoundCRS *>(identifiedCRS.get());
+ if (boundCRS &&
+ !boundCRS->baseCRS()->identifiers().empty()) {
+ const auto &idsBase =
+ boundCRS->baseCRS()->identifiers();
+ std::cout << "BoundCRS of "
+ << *idsBase[0]->codeSpace() << ":"
+ << idsBase[0]->code() << ": "
+ << pair.second << " %" << std::endl;
+ } else {
+ std::cout
+ << "un-identifier CRS: " << pair.second
+ << " %" << std::endl;
+ }
+ }
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "Identification failed: " << e.what()
+ << std::endl;
+ }
+ }
+ }
+ } else {
+
+ if (!area.empty()) {
+ assert(dbContext);
+ try {
+ if (area.find(' ') == std::string::npos &&
+ area.find(':') != std::string::npos) {
+ auto tokens = split(area, ':');
+ if (tokens.size() == 2) {
+ const std::string &areaAuth = tokens[0];
+ const std::string &areaCode = tokens[1];
+ bboxFilter = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), areaAuth)
+ ->createExtent(areaCode)
+ .as_nullable();
+ }
+ }
+ if (!bboxFilter) {
+ auto authFactory = AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), std::string());
+ auto res = authFactory->listAreaOfUseFromName(area, false);
+ if (res.size() == 1) {
+ bboxFilter =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext),
+ res.front().first)
+ ->createExtent(res.front().second)
+ .as_nullable();
+ } else {
+ res = authFactory->listAreaOfUseFromName(area, true);
+ if (res.size() == 1) {
+ bboxFilter =
+ AuthorityFactory::create(NN_NO_CHECK(dbContext),
+ res.front().first)
+ ->createExtent(res.front().second)
+ .as_nullable();
+ } else if (res.empty()) {
+ std::cerr << "No area of use matching provided name"
+ << std::endl;
+ std::exit(1);
+ } else {
+ std::cerr << "Several candidates area of use "
+ "matching provided name :"
+ << std::endl;
+ for (const auto &candidate : res) {
+ auto obj =
+ AuthorityFactory::create(
+ NN_NO_CHECK(dbContext), candidate.first)
+ ->createExtent(candidate.second);
+ std::cerr << " " << candidate.first << ":"
+ << candidate.second << " : "
+ << *obj->description() << std::endl;
+ }
+ std::exit(1);
+ }
+ }
+ }
+ } catch (const std::exception &e) {
+ std::cerr << "Area of use retrieval failed: " << e.what()
+ << std::endl;
+ std::exit(1);
+ }
+ }
+
+ outputOperations(
+ dbContext, sourceCRSStr, targetCRSStr, bboxFilter, spatialCriterion,
+ crsExtentUse, gridAvailabilityUse, allowPivots, pivots, authority,
+ usePROJGridAlternatives, showSuperseded, outputOpt, summary);
+ }
+
+ return 0;
+}
+
+//! @endcond