text: /******************************************************************************
   *
   * Project:  PROJ
   * Purpose:  ISO19111:2019 implementation
   * 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.
   ****************************************************************************/
  
  #ifndef FROM_PROJ_CPP
  #define FROM_PROJ_CPP
  #endif
  
  #include "proj/common.hpp"
  #include "proj/io.hpp"
  #include "proj/metadata.hpp"
  #include "proj/util.hpp"
  
  #include "proj/internal/internal.hpp"
  #include "proj/internal/io_internal.hpp"
  
  #include "proj.h"
  
  #include <cmath> // M_PI
  #include <cstdlib>
  #include <memory>
  #include <string>
  #include <vector>
  
  using namespace NS_PROJ::internal;
  using namespace NS_PROJ::io;
  using namespace NS_PROJ::metadata;
  using namespace NS_PROJ::util;
  
  #if 0
  namespace dropbox{ namespace oxygen {
  template<> nn<NS_PROJ::common::IdentifiedObjectPtr>::~nn() = default;
  template<> nn<NS_PROJ::common::ObjectDomainPtr>::~nn() = default;
  template<> nn<NS_PROJ::common::ObjectUsagePtr>::~nn() = default;
  template<> nn<NS_PROJ::common::UnitOfMeasurePtr>::~nn() = default;
  }}
  #endif
  
  NS_PROJ_START
  namespace common {
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  struct UnitOfMeasure::Private {
      std::string name_{};
      double toSI_ = 1.0;
      UnitOfMeasure::Type type_{UnitOfMeasure::Type::UNKNOWN};
      std::string codeSpace_{};
      std::string code_{};
  
      Private(const std::string &nameIn, double toSIIn,
              UnitOfMeasure::Type typeIn, const std::string &codeSpaceIn,
              const std::string &codeIn)
          : name_(nameIn), toSI_(toSIIn), type_(typeIn), codeSpace_(codeSpaceIn),
            code_(codeIn) {}
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Creates a UnitOfMeasure. */
  UnitOfMeasure::UnitOfMeasure(const std::string &nameIn, double toSIIn,
                               UnitOfMeasure::Type typeIn,
                               const std::string &codeSpaceIn,
                               const std::string &codeIn)
      : d(internal::make_unique<Private>(nameIn, toSIIn, typeIn, codeSpaceIn,
                                         codeIn)) {}
  
  // ---------------------------------------------------------------------------
  
  UnitOfMeasure::UnitOfMeasure(const UnitOfMeasure &other)
      : d(internal::make_unique<Private>(*(other.d))) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  UnitOfMeasure::~UnitOfMeasure() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  UnitOfMeasure &UnitOfMeasure::operator=(const UnitOfMeasure &other) {
      if (this != &other) {
          *d = *(other.d);
      }
      return *this;
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  UnitOfMeasureNNPtr UnitOfMeasure::create(const UnitOfMeasure &other) {
      return util::nn_make_shared<UnitOfMeasure>(other);
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the name of the unit of measure. */
  const std::string &UnitOfMeasure::name() PROJ_PURE_DEFN { return d->name_; }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the conversion factor to the unit of the
   * International System of Units of the same Type.
   *
   * For example, for foot, this would be 0.3048 (metre)
   *
   * @return the conversion factor, or 0 if no conversion exists.
   */
  double UnitOfMeasure::conversionToSI() PROJ_PURE_DEFN { return d->toSI_; }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the type of the unit of measure.
   */
  UnitOfMeasure::Type UnitOfMeasure::type() PROJ_PURE_DEFN { return d->type_; }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the code space of the unit of measure.
   *
   * For example "EPSG"
   *
   * @return the code space, or empty string.
   */
  const std::string &UnitOfMeasure::codeSpace() PROJ_PURE_DEFN {
      return d->codeSpace_;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the code of the unit of measure.
   *
   * @return the code, or empty string.
   */
  const std::string &UnitOfMeasure::code() PROJ_PURE_DEFN { return d->code_; }
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  void UnitOfMeasure::_exportToWKT(
      WKTFormatter *formatter,
      const std::string &unitType) const // throw(FormattingException)
  {
      const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2;
  
      const auto l_type = type();
      if (formatter->forceUNITKeyword() && l_type != Type::PARAMETRIC) {
          formatter->startNode(WKTConstants::UNIT, !codeSpace().empty());
      } else if (!unitType.empty()) {
          formatter->startNode(unitType, !codeSpace().empty());
      } else {
          if (isWKT2 && l_type == Type::LINEAR) {
              formatter->startNode(WKTConstants::LENGTHUNIT,
                                   !codeSpace().empty());
          } else if (isWKT2 && l_type == Type::ANGULAR) {
              formatter->startNode(WKTConstants::ANGLEUNIT, !codeSpace().empty());
          } else if (isWKT2 && l_type == Type::SCALE) {
              formatter->startNode(WKTConstants::SCALEUNIT, !codeSpace().empty());
          } else if (isWKT2 && l_type == Type::TIME) {
              formatter->startNode(WKTConstants::TIMEUNIT, !codeSpace().empty());
          } else if (isWKT2 && l_type == Type::PARAMETRIC) {
              formatter->startNode(WKTConstants::PARAMETRICUNIT,
                                   !codeSpace().empty());
          } else {
              formatter->startNode(WKTConstants::UNIT, !codeSpace().empty());
          }
      }
  
      {
          const auto &l_name = name();
          const bool esri = formatter->useESRIDialect();
          if (esri) {
              if (ci_equal(l_name, "degree")) {
                  formatter->addQuotedString("Degree");
              } else if (ci_equal(l_name, "grad")) {
                  formatter->addQuotedString("Grad");
              } else if (ci_equal(l_name, "metre")) {
                  formatter->addQuotedString("Meter");
              } else {
                  formatter->addQuotedString(l_name);
              }
          } else {
              formatter->addQuotedString(l_name);
          }
          const auto &factor = conversionToSI();
          if (!isWKT2 || l_type != Type::TIME || factor != 0.0) {
              // Some TIMEUNIT do not have a conversion factor
              formatter->add(factor);
          }
          if (!codeSpace().empty() && formatter->outputId()) {
              formatter->startNode(
                  isWKT2 ? WKTConstants::ID : WKTConstants::AUTHORITY, false);
              formatter->addQuotedString(codeSpace());
              const auto &l_code = code();
              if (isWKT2) {
                  try {
                      (void)std::stoi(l_code);
                      formatter->add(l_code);
                  } catch (const std::exception &) {
                      formatter->addQuotedString(l_code);
                  }
              } else {
                  formatter->addQuotedString(l_code);
              }
              formatter->endNode();
          }
      }
      formatter->endNode();
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** Returns whether two units of measures are equal.
   *
   * The comparison is based on the name.
   */
  bool UnitOfMeasure::operator==(const UnitOfMeasure &other) PROJ_PURE_DEFN {
      return name() == other.name();
  }
  
  // ---------------------------------------------------------------------------
  
  /** Returns whether two units of measures are different.
   *
   * The comparison is based on the name.
   */
  bool UnitOfMeasure::operator!=(const UnitOfMeasure &other) PROJ_PURE_DEFN {
      return name() != other.name();
  }
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  std::string UnitOfMeasure::exportToPROJString() const {
      if (type() == Type::LINEAR) {
          auto proj_units = proj_list_units();
          for (int i = 0; proj_units[i].id != nullptr; i++) {
              if (::fabs(proj_units[i].factor - conversionToSI()) <
                  1e-10 * conversionToSI()) {
                  return proj_units[i].id;
              }
          }
      } else if (type() == Type::ANGULAR) {
          auto proj_angular_units = proj_list_angular_units();
          for (int i = 0; proj_angular_units[i].id != nullptr; i++) {
              if (::fabs(proj_angular_units[i].factor - conversionToSI()) <
                  1e-10 * conversionToSI()) {
                  return proj_angular_units[i].id;
              }
          }
      }
      return std::string();
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  bool UnitOfMeasure::_isEquivalentTo(
      const UnitOfMeasure &other, util::IComparable::Criterion criterion) const {
      if (criterion == util::IComparable::Criterion::STRICT) {
          return operator==(other);
      }
      return std::fabs(conversionToSI() - other.conversionToSI()) <=
             1e-10 * std::fabs(conversionToSI());
  }
  
  //! @endcond
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  struct Measure::Private {
      double value_ = 0.0;
      UnitOfMeasure unit_{};
  
      Private(double valueIn, const UnitOfMeasure &unitIn)
          : value_(valueIn), unit_(unitIn) {}
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Measure.
   */
  Measure::Measure(double valueIn, const UnitOfMeasure &unitIn)
      : d(internal::make_unique<Private>(valueIn, unitIn)) {}
  
  // ---------------------------------------------------------------------------
  
  Measure::Measure(const Measure &other)
      : d(internal::make_unique<Private>(*(other.d))) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Measure::~Measure() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the unit of the Measure.
   */
  const UnitOfMeasure &Measure::unit() PROJ_PURE_DEFN { return d->unit_; }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the value of the Measure, after conversion to the
   * corresponding
   * unit of the International System.
   */
  double Measure::getSIValue() PROJ_PURE_DEFN {
      return d->value_ * d->unit_.conversionToSI();
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the value of the measure, expressed in the unit()
   */
  double Measure::value() PROJ_PURE_DEFN { return d->value_; }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the value of this measure expressed into the provided unit.
   */
  double Measure::convertToUnit(const UnitOfMeasure &otherUnit) PROJ_PURE_DEFN {
      return getSIValue() / otherUnit.conversionToSI();
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return whether two measures are equal.
   *
   * The comparison is done both on the value and the unit.
   */
  bool Measure::operator==(const Measure &other) PROJ_PURE_DEFN {
      return d->value_ == other.d->value_ && d->unit_ == other.d->unit_;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Returns whether an object is equivalent to another one.
   * @param other other object to compare to
   * @param criterion comparaison criterion.
   * @param maxRelativeError Maximum relative error allowed.
   * @return true if objects are equivalent.
   */
  bool Measure::_isEquivalentTo(const Measure &other,
                                util::IComparable::Criterion criterion,
                                double maxRelativeError) const {
      if (criterion == util::IComparable::Criterion::STRICT) {
          return operator==(other);
      }
      return std::fabs(getSIValue() - other.getSIValue()) <=
             maxRelativeError * std::fabs(getSIValue());
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Scale.
   *
   * @param valueIn value
   */
  Scale::Scale(double valueIn) : Measure(valueIn, UnitOfMeasure::SCALE_UNITY) {}
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Scale.
   *
   * @param valueIn value
   * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::SCALE
   */
  Scale::Scale(double valueIn, const UnitOfMeasure &unitIn)
      : Measure(valueIn, unitIn) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Scale::Scale(const Scale &) = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Scale::~Scale() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Angle.
   *
   * @param valueIn value
   */
  Angle::Angle(double valueIn) : Measure(valueIn, UnitOfMeasure::DEGREE) {}
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Angle.
   *
   * @param valueIn value
   * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::ANGULAR
   */
  Angle::Angle(double valueIn, const UnitOfMeasure &unitIn)
      : Measure(valueIn, unitIn) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Angle::Angle(const Angle &) = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Angle::~Angle() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Length.
   *
   * @param valueIn value
   */
  Length::Length(double valueIn) : Measure(valueIn, UnitOfMeasure::METRE) {}
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a Length.
   *
   * @param valueIn value
   * @param unitIn unit. Constraint: unit.type() == UnitOfMeasure::Type::LINEAR
   */
  Length::Length(double valueIn, const UnitOfMeasure &unitIn)
      : Measure(valueIn, unitIn) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Length::Length(const Length &) = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  Length::~Length() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  struct DateTime::Private {
      std::string str_{};
  
      explicit Private(const std::string &str) : str_(str) {}
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  DateTime::DateTime() : d(internal::make_unique<Private>(std::string())) {}
  
  // ---------------------------------------------------------------------------
  
  DateTime::DateTime(const std::string &str)
      : d(internal::make_unique<Private>(str)) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  DateTime::DateTime(const DateTime &other)
      : d(internal::make_unique<Private>(*(other.d))) {}
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  DateTime::~DateTime() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a DateTime. */
  DateTime DateTime::create(const std::string &str) { return DateTime(str); }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return whether the DateTime is ISO:8601 compliant.
   *
   * \remark The current implementation is really simplistic, and aimed at
   * detecting date-times that are not ISO:8601 compliant.
   */
  bool DateTime::isISO_8601() const {
      return !d->str_.empty() && d->str_[0] >= '0' && d->str_[0] <= '9' &&
             d->str_.find(' ') == std::string::npos;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the DateTime as a string.
   */
  std::string DateTime::toString() const { return d->str_; }
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  // cppcheck-suppress copyCtorAndEqOperator
  struct IdentifiedObject::Private {
      IdentifierNNPtr name{Identifier::create()};
      std::vector<IdentifierNNPtr> identifiers{};
      std::vector<GenericNameNNPtr> aliases{};
      std::string remarks{};
      bool isDeprecated{};
  
      void setIdentifiers(const PropertyMap &properties);
      void setName(const PropertyMap &properties);
      void setAliases(const PropertyMap &properties);
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  IdentifiedObject::IdentifiedObject() : d(internal::make_unique<Private>()) {}
  
  // ---------------------------------------------------------------------------
  
  IdentifiedObject::IdentifiedObject(const IdentifiedObject &other)
      : d(internal::make_unique<Private>(*(other.d))) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  IdentifiedObject::~IdentifiedObject() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the name of the object.
   *
   * Generally, the only interesting field of the name will be
   * name()->description().
   */
  const IdentifierNNPtr &IdentifiedObject::name() PROJ_PURE_DEFN {
      return d->name;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the name of the object.
   *
   * Return *(name()->description())
   */
  const std::string &IdentifiedObject::nameStr() PROJ_PURE_DEFN {
      return *(d->name->description());
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the identifier(s) of the object
   *
   * Generally, those will have Identifier::code() and Identifier::codeSpace()
   * filled.
   */
  const std::vector<IdentifierNNPtr> &
  IdentifiedObject::identifiers() PROJ_PURE_DEFN {
      return d->identifiers;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the alias(es) of the object.
   */
  const std::vector<GenericNameNNPtr> &
  IdentifiedObject::aliases() PROJ_PURE_DEFN {
      return d->aliases;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the (first) alias of the object as a string.
   *
   * Shortcut for aliases()[0]->toFullyQualifiedName()->toString()
   */
  std::string IdentifiedObject::alias() PROJ_PURE_DEFN {
      if (d->aliases.empty())
          return std::string();
      return d->aliases[0]->toFullyQualifiedName()->toString();
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the EPSG code.
   * @return code, or 0 if not found
   */
  int IdentifiedObject::getEPSGCode() PROJ_PURE_DEFN {
      for (const auto &id : identifiers()) {
          if (ci_equal(*(id->codeSpace()), metadata::Identifier::EPSG)) {
              return ::atoi(id->code().c_str());
          }
      }
      return 0;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the remarks.
   */
  const std::string &IdentifiedObject::remarks() PROJ_PURE_DEFN {
      return d->remarks;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return whether the object is deprecated.
   *
   * \remark Extension of \ref ISO_19111_2019
   */
  bool IdentifiedObject::isDeprecated() PROJ_PURE_DEFN { return d->isDeprecated; }
  
  // ---------------------------------------------------------------------------
  //! @cond Doxygen_Suppress
  
  void IdentifiedObject::Private::setName(
      const PropertyMap &properties) // throw(InvalidValueTypeException)
  {
      const auto pVal = properties.get(NAME_KEY);
      if (!pVal) {
          return;
      }
      if (const auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) {
          if (genVal->type() == BoxedValue::Type::STRING) {
              name = Identifier::createFromDescription(genVal->stringValue());
          } else {
              throw InvalidValueTypeException("Invalid value type for " +
                                              NAME_KEY);
          }
      } else {
          if (auto identifier =
                  util::nn_dynamic_pointer_cast<Identifier>(*pVal)) {
              name = NN_NO_CHECK(identifier);
          } else {
              throw InvalidValueTypeException("Invalid value type for " +
                                              NAME_KEY);
          }
      }
  }
  
  // ---------------------------------------------------------------------------
  
  void IdentifiedObject::Private::setIdentifiers(
      const PropertyMap &properties) // throw(InvalidValueTypeException)
  {
      auto pVal = properties.get(IDENTIFIERS_KEY);
      if (!pVal) {
  
          pVal = properties.get(Identifier::CODE_KEY);
          if (pVal) {
              identifiers.push_back(
                  Identifier::create(std::string(), properties));
          }
          return;
      }
      if (auto identifier = util::nn_dynamic_pointer_cast<Identifier>(*pVal)) {
          identifiers.clear();
          identifiers.push_back(NN_NO_CHECK(identifier));
      } else {
          if (auto array = dynamic_cast<const ArrayOfBaseObject *>(pVal->get())) {
              identifiers.clear();
              for (const auto &val : *array) {
                  identifier = util::nn_dynamic_pointer_cast<Identifier>(val);
                  if (identifier) {
                      identifiers.push_back(NN_NO_CHECK(identifier));
                  } else {
                      throw InvalidValueTypeException("Invalid value type for " +
                                                      IDENTIFIERS_KEY);
                  }
              }
          } else {
              throw InvalidValueTypeException("Invalid value type for " +
                                              IDENTIFIERS_KEY);
          }
      }
  }
  
  // ---------------------------------------------------------------------------
  
  void IdentifiedObject::Private::setAliases(
      const PropertyMap &properties) // throw(InvalidValueTypeException)
  {
      const auto pVal = properties.get(ALIAS_KEY);
      if (!pVal) {
          return;
      }
      if (auto l_name = util::nn_dynamic_pointer_cast<GenericName>(*pVal)) {
          aliases.clear();
          aliases.push_back(NN_NO_CHECK(l_name));
      } else {
          if (const auto array =
                  dynamic_cast<const ArrayOfBaseObject *>(pVal->get())) {
              aliases.clear();
              for (const auto &val : *array) {
                  l_name = util::nn_dynamic_pointer_cast<GenericName>(val);
                  if (l_name) {
                      aliases.push_back(NN_NO_CHECK(l_name));
                  } else {
                      if (auto genVal =
                              dynamic_cast<const BoxedValue *>(val.get())) {
                          if (genVal->type() == BoxedValue::Type::STRING) {
                              aliases.push_back(NameFactory::createLocalName(
                                  nullptr, genVal->stringValue()));
                          } else {
                              throw InvalidValueTypeException(
                                  "Invalid value type for " + ALIAS_KEY);
                          }
                      } else {
                          throw InvalidValueTypeException(
                              "Invalid value type for " + ALIAS_KEY);
                      }
                  }
              }
          } else {
              std::string temp;
              if (properties.getStringValue(ALIAS_KEY, temp)) {
                  aliases.clear();
                  aliases.push_back(NameFactory::createLocalName(nullptr, temp));
              } else {
                  throw InvalidValueTypeException("Invalid value type for " +
                                                  ALIAS_KEY);
              }
          }
      }
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  void IdentifiedObject::setProperties(
      const PropertyMap &properties) // throw(InvalidValueTypeException)
  {
      d->setName(properties);
      d->setIdentifiers(properties);
      d->setAliases(properties);
  
      properties.getStringValue(REMARKS_KEY, d->remarks);
  
      {
          const auto pVal = properties.get(DEPRECATED_KEY);
          if (pVal) {
              if (const auto genVal =
                      dynamic_cast<const BoxedValue *>(pVal->get())) {
                  if (genVal->type() == BoxedValue::Type::BOOLEAN) {
                      d->isDeprecated = genVal->booleanValue();
                  } else {
                      throw InvalidValueTypeException("Invalid value type for " +
                                                      DEPRECATED_KEY);
                  }
              } else {
                  throw InvalidValueTypeException("Invalid value type for " +
                                                  DEPRECATED_KEY);
              }
          }
      }
  }
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  void IdentifiedObject::formatID(WKTFormatter *formatter) const {
      const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2;
      for (const auto &id : identifiers()) {
          id->_exportToWKT(formatter);
          if (!isWKT2) {
              break;
          }
      }
  }
  
  // ---------------------------------------------------------------------------
  
  void IdentifiedObject::formatRemarks(WKTFormatter *formatter) const {
      if (!remarks().empty()) {
          formatter->startNode(WKTConstants::REMARK, false);
          formatter->addQuotedString(remarks());
          formatter->endNode();
      }
  }
  
  // ---------------------------------------------------------------------------
  
  bool IdentifiedObject::_isEquivalentTo(
      const util::IComparable *other,
      util::IComparable::Criterion criterion) const {
      auto otherIdObj = dynamic_cast<const IdentifiedObject *>(other);
      if (!otherIdObj)
          return false;
      return _isEquivalentTo(otherIdObj, criterion);
  }
  
  // ---------------------------------------------------------------------------
  
  bool IdentifiedObject::_isEquivalentTo(const IdentifiedObject *otherIdObj,
                                         util::IComparable::Criterion criterion)
      PROJ_PURE_DEFN {
      if (criterion == util::IComparable::Criterion::STRICT) {
          if (!ci_equal(nameStr(), otherIdObj->nameStr())) {
              return false;
          }
          // TODO test id etc
      } else {
          if (!metadata::Identifier::isEquivalentName(
                  nameStr().c_str(), otherIdObj->nameStr().c_str())) {
              return false;
          }
      }
      return true;
  }
  
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  struct ObjectDomain::Private {
      optional<std::string> scope_{};
      ExtentPtr domainOfValidity_{};
  
      Private(const optional<std::string> &scopeIn, const ExtentPtr &extent)
          : scope_(scopeIn), domainOfValidity_(extent) {}
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  ObjectDomain::ObjectDomain(const optional<std::string> &scopeIn,
                             const ExtentPtr &extent)
      : d(internal::make_unique<Private>(scopeIn, extent)) {}
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  ObjectDomain::ObjectDomain(const ObjectDomain &other)
      : d(internal::make_unique<Private>(*(other.d))) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  ObjectDomain::~ObjectDomain() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the scope.
   *
   * @return the scope, or empty.
   */
  const optional<std::string> &ObjectDomain::scope() PROJ_PURE_DEFN {
      return d->scope_;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the domain of validity.
   *
   * @return the domain of validity, or nullptr.
   */
  const ExtentPtr &ObjectDomain::domainOfValidity() PROJ_PURE_DEFN {
      return d->domainOfValidity_;
  }
  
  // ---------------------------------------------------------------------------
  
  /** \brief Instantiate a ObjectDomain.
   */
  ObjectDomainNNPtr ObjectDomain::create(const optional<std::string> &scopeIn,
                                         const ExtentPtr &extent) {
      return ObjectDomain::nn_make_shared<ObjectDomain>(scopeIn, extent);
  }
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  void ObjectDomain::_exportToWKT(WKTFormatter *formatter) const {
      if (d->scope_.has_value()) {
          formatter->startNode(WKTConstants::SCOPE, false);
          formatter->addQuotedString(*(d->scope_));
          formatter->endNode();
      } else if (formatter->use2018Keywords()) {
          formatter->startNode(WKTConstants::SCOPE, false);
          formatter->addQuotedString("unknown");
          formatter->endNode();
      }
      if (d->domainOfValidity_) {
          if (d->domainOfValidity_->description().has_value()) {
              formatter->startNode(WKTConstants::AREA, false);
              formatter->addQuotedString(*(d->domainOfValidity_->description()));
              formatter->endNode();
          }
          if (d->domainOfValidity_->geographicElements().size() == 1) {
              const auto bbox = dynamic_cast<const GeographicBoundingBox *>(
                  d->domainOfValidity_->geographicElements()[0].get());
              if (bbox) {
                  formatter->startNode(WKTConstants::BBOX, false);
                  formatter->add(bbox->southBoundLatitude());
                  formatter->add(bbox->westBoundLongitude());
                  formatter->add(bbox->northBoundLatitude());
                  formatter->add(bbox->eastBoundLongitude());
                  formatter->endNode();
              }
          }
          if (d->domainOfValidity_->verticalElements().size() == 1) {
              auto extent = d->domainOfValidity_->verticalElements()[0];
              formatter->startNode(WKTConstants::VERTICALEXTENT, false);
              formatter->add(extent->minimumValue());
              formatter->add(extent->maximumValue());
              extent->unit()->_exportToWKT(formatter);
              formatter->endNode();
          }
          if (d->domainOfValidity_->temporalElements().size() == 1) {
              auto extent = d->domainOfValidity_->temporalElements()[0];
              formatter->startNode(WKTConstants::TIMEEXTENT, false);
              if (DateTime::create(extent->start()).isISO_8601()) {
                  formatter->add(extent->start());
              } else {
                  formatter->addQuotedString(extent->start());
              }
              if (DateTime::create(extent->stop()).isISO_8601()) {
                  formatter->add(extent->stop());
              } else {
                  formatter->addQuotedString(extent->stop());
              }
              formatter->endNode();
          }
      }
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  bool ObjectDomain::_isEquivalentTo(
      const util::IComparable *other,
      util::IComparable::Criterion criterion) const {
      auto otherDomain = dynamic_cast<const ObjectDomain *>(other);
      if (!otherDomain)
          return false;
      if (scope().has_value() != otherDomain->scope().has_value())
          return false;
      if (*scope() != *otherDomain->scope())
          return false;
      if ((domainOfValidity().get() != nullptr) ^
          (otherDomain->domainOfValidity().get() != nullptr))
          return false;
      return domainOfValidity().get() == nullptr ||
             domainOfValidity()->_isEquivalentTo(
                 otherDomain->domainOfValidity().get(), criterion);
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  struct ObjectUsage::Private {
      std::vector<ObjectDomainNNPtr> domains_{};
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  ObjectUsage::ObjectUsage() : d(internal::make_unique<Private>()) {}
  
  // ---------------------------------------------------------------------------
  
  ObjectUsage::ObjectUsage(const ObjectUsage &other)
      : IdentifiedObject(other), d(internal::make_unique<Private>(*(other.d))) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  ObjectUsage::~ObjectUsage() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the domains of the object.
   */
  const std::vector<ObjectDomainNNPtr> &ObjectUsage::domains() PROJ_PURE_DEFN {
      return d->domains_;
  }
  
  // ---------------------------------------------------------------------------
  
  void ObjectUsage::setProperties(
      const PropertyMap &properties) // throw(InvalidValueTypeException)
  {
      IdentifiedObject::setProperties(properties);
  
      optional<std::string> scope;
      properties.getStringValue(SCOPE_KEY, scope);
  
      ExtentPtr domainOfValidity;
      {
          const auto pVal = properties.get(DOMAIN_OF_VALIDITY_KEY);
          if (pVal) {
              domainOfValidity = util::nn_dynamic_pointer_cast<Extent>(*pVal);
              if (!domainOfValidity) {
                  throw InvalidValueTypeException("Invalid value type for " +
                                                  DOMAIN_OF_VALIDITY_KEY);
              }
          }
      }
  
      if (scope.has_value() || domainOfValidity) {
          d->domains_.emplace_back(ObjectDomain::create(scope, domainOfValidity));
      }
  
      {
          const auto pVal = properties.get(OBJECT_DOMAIN_KEY);
          if (pVal) {
              if (auto objectDomain =
                      util::nn_dynamic_pointer_cast<ObjectDomain>(*pVal)) {
                  d->domains_.emplace_back(NN_NO_CHECK(objectDomain));
              } else if (const auto array =
                             dynamic_cast<const ArrayOfBaseObject *>(
                                 pVal->get())) {
                  for (const auto &val : *array) {
                      objectDomain =
                          util::nn_dynamic_pointer_cast<ObjectDomain>(val);
                      if (objectDomain) {
                          d->domains_.emplace_back(NN_NO_CHECK(objectDomain));
                      } else {
                          throw InvalidValueTypeException(
                              "Invalid value type for " + OBJECT_DOMAIN_KEY);
                      }
                  }
              } else {
                  throw InvalidValueTypeException("Invalid value type for " +
                                                  OBJECT_DOMAIN_KEY);
              }
          }
      }
  }
  
  // ---------------------------------------------------------------------------
  
  void ObjectUsage::baseExportToWKT(WKTFormatter *formatter) const {
      const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2;
      if (isWKT2 && formatter->outputUsage()) {
          auto l_domains = domains();
          if (!l_domains.empty()) {
              if (formatter->use2018Keywords()) {
                  for (const auto &domain : l_domains) {
                      formatter->startNode(WKTConstants::USAGE, false);
                      domain->_exportToWKT(formatter);
                      formatter->endNode();
                  }
              } else {
                  l_domains[0]->_exportToWKT(formatter);
              }
          }
      }
      if (formatter->outputId()) {
          formatID(formatter);
      }
      if (isWKT2) {
          formatRemarks(formatter);
      }
  }
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  bool ObjectUsage::_isEquivalentTo(
      const util::IComparable *other,
      util::IComparable::Criterion criterion) const {
      auto otherObjUsage = dynamic_cast<const ObjectUsage *>(other);
      if (!otherObjUsage)
          return false;
  
      // TODO: incomplete
      return IdentifiedObject::_isEquivalentTo(other, criterion);
  }
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  struct DataEpoch::Private {
      Measure coordinateEpoch_{};
  
      explicit Private(const Measure &coordinateEpochIn)
          : coordinateEpoch_(coordinateEpochIn) {}
  };
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  DataEpoch::DataEpoch() : d(internal::make_unique<Private>(Measure())) {}
  
  // ---------------------------------------------------------------------------
  
  DataEpoch::DataEpoch(const Measure &coordinateEpochIn)
      : d(internal::make_unique<Private>(coordinateEpochIn)) {}
  
  // ---------------------------------------------------------------------------
  
  DataEpoch::DataEpoch(const DataEpoch &other)
      : d(internal::make_unique<Private>(*(other.d))) {}
  
  // ---------------------------------------------------------------------------
  
  //! @cond Doxygen_Suppress
  DataEpoch::~DataEpoch() = default;
  //! @endcond
  
  // ---------------------------------------------------------------------------
  
  /** \brief Return the coordinate epoch, as a measure in decimal year.
   */
  const Measure &DataEpoch::coordinateEpoch() const {
      return d->coordinateEpoch_;
  }
  
  } // namespace common
  NS_PROJ_END
  