/src/PROJ/src/iso19111/coordinates.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: PROJ |
4 | | * Purpose: ISO19111:2019 implementation |
5 | | * Author: Even Rouault <even dot rouault at spatialys dot com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com> |
9 | | * |
10 | | * Permission is hereby granted, free of charge, to any person obtaining a |
11 | | * copy of this software and associated documentation files (the "Software"), |
12 | | * to deal in the Software without restriction, including without limitation |
13 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
14 | | * and/or sell copies of the Software, and to permit persons to whom the |
15 | | * Software is furnished to do so, subject to the following conditions: |
16 | | * |
17 | | * The above copyright notice and this permission notice shall be included |
18 | | * in all copies or substantial portions of the Software. |
19 | | * |
20 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
21 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
22 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
23 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
25 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
26 | | * DEALINGS IN THE SOFTWARE. |
27 | | ****************************************************************************/ |
28 | | |
29 | | #ifndef FROM_PROJ_CPP |
30 | | #define FROM_PROJ_CPP |
31 | | #endif |
32 | | |
33 | | #include "proj/coordinates.hpp" |
34 | | #include "proj/common.hpp" |
35 | | #include "proj/crs.hpp" |
36 | | #include "proj/io.hpp" |
37 | | |
38 | | #include "proj/internal/internal.hpp" |
39 | | #include "proj/internal/io_internal.hpp" |
40 | | |
41 | | #include "proj_json_streaming_writer.hpp" |
42 | | |
43 | | #include <cmath> |
44 | | #include <limits> |
45 | | |
46 | | using namespace NS_PROJ::internal; |
47 | | |
48 | | NS_PROJ_START |
49 | | |
50 | | namespace coordinates { |
51 | | |
52 | | // --------------------------------------------------------------------------- |
53 | | |
54 | | //! @cond Doxygen_Suppress |
55 | | struct CoordinateMetadata::Private { |
56 | | crs::CRSNNPtr crs_; |
57 | | util::optional<common::DataEpoch> coordinateEpoch_{}; |
58 | | |
59 | 2 | explicit Private(const crs::CRSNNPtr &crs) : crs_(crs) {} |
60 | | Private(const crs::CRSNNPtr &crs, const common::DataEpoch &coordinateEpoch) |
61 | 79 | : crs_(crs), coordinateEpoch_(coordinateEpoch) {} |
62 | | }; |
63 | | //! @endcond |
64 | | |
65 | | // --------------------------------------------------------------------------- |
66 | | |
67 | | CoordinateMetadata::CoordinateMetadata(const crs::CRSNNPtr &crsIn) |
68 | 2 | : d(std::make_unique<Private>(crsIn)) {} |
69 | | |
70 | | // --------------------------------------------------------------------------- |
71 | | |
72 | | CoordinateMetadata::CoordinateMetadata(const crs::CRSNNPtr &crsIn, |
73 | | double coordinateEpochAsDecimalYearIn) |
74 | 79 | : d(std::make_unique<Private>(crsIn, common::DataEpoch(common::Measure( |
75 | 79 | coordinateEpochAsDecimalYearIn, |
76 | 79 | common::UnitOfMeasure::YEAR)))) {} |
77 | | |
78 | | // --------------------------------------------------------------------------- |
79 | | |
80 | | //! @cond Doxygen_Suppress |
81 | 81 | CoordinateMetadata::~CoordinateMetadata() = default; |
82 | | //! @endcond |
83 | | |
84 | | // --------------------------------------------------------------------------- |
85 | | |
86 | | /** \brief Instantiate a CoordinateMetadata from a static CRS. |
87 | | * @param crsIn a static CRS |
88 | | * @return new CoordinateMetadata. |
89 | | * @throw util::Exception if crsIn is a dynamic CRS. |
90 | | */ |
91 | 2 | CoordinateMetadataNNPtr CoordinateMetadata::create(const crs::CRSNNPtr &crsIn) { |
92 | | |
93 | 2 | if (crsIn->isDynamic(/*considerWGS84AsDynamic=*/false)) { |
94 | 0 | throw util::Exception( |
95 | 0 | "Coordinate epoch should be provided for a dynamic CRS"); |
96 | 0 | } |
97 | | |
98 | 2 | auto coordinateMetadata( |
99 | 2 | CoordinateMetadata::nn_make_shared<CoordinateMetadata>(crsIn)); |
100 | 2 | coordinateMetadata->assignSelf(coordinateMetadata); |
101 | 2 | return coordinateMetadata; |
102 | 2 | } |
103 | | |
104 | | // --------------------------------------------------------------------------- |
105 | | |
106 | | /** \brief Instantiate a CoordinateMetadata from a dynamic CRS and an associated |
107 | | * coordinate epoch. |
108 | | * |
109 | | * @param crsIn a dynamic CRS |
110 | | * @param coordinateEpochIn coordinate epoch expressed in decimal year. |
111 | | * @return new CoordinateMetadata. |
112 | | * @throw util::Exception if crsIn is a static CRS. |
113 | | */ |
114 | | CoordinateMetadataNNPtr CoordinateMetadata::create(const crs::CRSNNPtr &crsIn, |
115 | 0 | double coordinateEpochIn) { |
116 | |
|
117 | 0 | return create(crsIn, coordinateEpochIn, nullptr); |
118 | 0 | } |
119 | | |
120 | | // --------------------------------------------------------------------------- |
121 | | |
122 | | /** \brief Instantiate a CoordinateMetadata from a dynamic CRS and an associated |
123 | | * coordinate epoch. |
124 | | * |
125 | | * @param crsIn a dynamic CRS |
126 | | * @param coordinateEpochIn coordinate epoch expressed in decimal year. |
127 | | * @param dbContext Database context (may be null) |
128 | | * @return new CoordinateMetadata. |
129 | | * @throw util::Exception if crsIn is a static CRS. |
130 | | */ |
131 | | CoordinateMetadataNNPtr |
132 | | CoordinateMetadata::create(const crs::CRSNNPtr &crsIn, double coordinateEpochIn, |
133 | 92 | const io::DatabaseContextPtr &dbContext) { |
134 | | |
135 | 92 | if (!crsIn->isDynamic(/*considerWGS84AsDynamic=*/true)) { |
136 | 14 | bool ok = false; |
137 | 14 | if (dbContext) { |
138 | 9 | auto geodCrs = crsIn->extractGeodeticCRS(); |
139 | 9 | if (geodCrs) { |
140 | 9 | auto factory = io::AuthorityFactory::create( |
141 | 9 | NN_NO_CHECK(dbContext), std::string()); |
142 | 9 | ok = !factory |
143 | 9 | ->getPointMotionOperationsFor(NN_NO_CHECK(geodCrs), |
144 | 9 | false) |
145 | 9 | .empty(); |
146 | 9 | } |
147 | 9 | } |
148 | 14 | if (!ok) { |
149 | 13 | throw util::Exception( |
150 | 13 | "Coordinate epoch should not be provided for a static CRS"); |
151 | 13 | } |
152 | 14 | } |
153 | | |
154 | 79 | auto coordinateMetadata( |
155 | 79 | CoordinateMetadata::nn_make_shared<CoordinateMetadata>( |
156 | 79 | crsIn, coordinateEpochIn)); |
157 | 79 | coordinateMetadata->assignSelf(coordinateMetadata); |
158 | 79 | return coordinateMetadata; |
159 | 92 | } |
160 | | |
161 | | // --------------------------------------------------------------------------- |
162 | | |
163 | | /** \brief Get the CRS associated with this CoordinateMetadata object. |
164 | | */ |
165 | 48 | const crs::CRSNNPtr &CoordinateMetadata::crs() PROJ_PURE_DEFN { |
166 | 48 | return d->crs_; |
167 | 48 | } |
168 | | |
169 | | // --------------------------------------------------------------------------- |
170 | | |
171 | | /** \brief Get the coordinate epoch associated with this CoordinateMetadata |
172 | | * object. |
173 | | * |
174 | | * The coordinate epoch is mandatory for a dynamic CRS, |
175 | | * and forbidden for a static CRS. |
176 | | */ |
177 | | const util::optional<common::DataEpoch> & |
178 | 80 | CoordinateMetadata::coordinateEpoch() PROJ_PURE_DEFN { |
179 | 80 | return d->coordinateEpoch_; |
180 | 80 | } |
181 | | |
182 | | // --------------------------------------------------------------------------- |
183 | | |
184 | | /** \brief Get the coordinate epoch associated with this CoordinateMetadata |
185 | | * object, as decimal year. |
186 | | * |
187 | | * The coordinate epoch is mandatory for a dynamic CRS, |
188 | | * and forbidden for a static CRS. |
189 | | */ |
190 | 0 | double CoordinateMetadata::coordinateEpochAsDecimalYear() PROJ_PURE_DEFN { |
191 | 0 | if (d->coordinateEpoch_.has_value()) { |
192 | 0 | return getRoundedEpochInDecimalYear( |
193 | 0 | d->coordinateEpoch_->coordinateEpoch().convertToUnit( |
194 | 0 | common::UnitOfMeasure::YEAR)); |
195 | 0 | } |
196 | 0 | return std::numeric_limits<double>::quiet_NaN(); |
197 | 0 | } |
198 | | |
199 | | // --------------------------------------------------------------------------- |
200 | | |
201 | | /** \brief Return a variant of this CoordinateMetadata "promoted" to a 3D one, |
202 | | * if not already the case. |
203 | | * |
204 | | * @param newName Name of the new underlying CRS. If empty, nameStr() will be |
205 | | * used. |
206 | | * @param dbContext Database context to look for potentially already registered |
207 | | * 3D CRS. May be nullptr. |
208 | | * @return a new CoordinateMetadata object promoted to 3D, or the current one if |
209 | | * already 3D or not applicable. |
210 | | */ |
211 | | CoordinateMetadataNNPtr |
212 | | CoordinateMetadata::promoteTo3D(const std::string &newName, |
213 | 0 | const io::DatabaseContextPtr &dbContext) const { |
214 | 0 | auto crs = d->crs_->promoteTo3D(newName, dbContext); |
215 | 0 | if (d->coordinateEpoch_.has_value()) { |
216 | 0 | auto coordinateMetadata( |
217 | 0 | CoordinateMetadata::nn_make_shared<CoordinateMetadata>( |
218 | 0 | crs, coordinateEpochAsDecimalYear())); |
219 | 0 | coordinateMetadata->assignSelf(coordinateMetadata); |
220 | 0 | return coordinateMetadata; |
221 | 0 | } else { |
222 | 0 | auto coordinateMetadata( |
223 | 0 | CoordinateMetadata::nn_make_shared<CoordinateMetadata>(crs)); |
224 | 0 | coordinateMetadata->assignSelf(coordinateMetadata); |
225 | 0 | return coordinateMetadata; |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | // --------------------------------------------------------------------------- |
230 | | |
231 | | //! @cond Doxygen_Suppress |
232 | 0 | void CoordinateMetadata::_exportToWKT(io::WKTFormatter *formatter) const { |
233 | 0 | if (formatter->version() != io::WKTFormatter::Version::WKT2 || |
234 | 0 | !formatter->use2019Keywords()) { |
235 | 0 | io::FormattingException::Throw( |
236 | 0 | "CoordinateMetadata can only be exported since WKT2:2019"); |
237 | 0 | } |
238 | 0 | formatter->startNode(io::WKTConstants::COORDINATEMETADATA, false); |
239 | |
|
240 | 0 | crs()->_exportToWKT(formatter); |
241 | |
|
242 | 0 | if (d->coordinateEpoch_.has_value()) { |
243 | 0 | formatter->startNode(io::WKTConstants::EPOCH, false); |
244 | 0 | formatter->add(coordinateEpochAsDecimalYear()); |
245 | 0 | formatter->endNode(); |
246 | 0 | } |
247 | |
|
248 | 0 | formatter->endNode(); |
249 | 0 | } |
250 | | //! @endcond |
251 | | |
252 | | // --------------------------------------------------------------------------- |
253 | | |
254 | | //! @cond Doxygen_Suppress |
255 | | void CoordinateMetadata::_exportToJSON( |
256 | | io::JSONFormatter *formatter) const // throw(io::FormattingException) |
257 | 0 | { |
258 | 0 | auto writer = formatter->writer(); |
259 | 0 | auto objectContext( |
260 | 0 | formatter->MakeObjectContext("CoordinateMetadata", false)); |
261 | |
|
262 | 0 | writer->AddObjKey("crs"); |
263 | 0 | crs()->_exportToJSON(formatter); |
264 | |
|
265 | 0 | if (d->coordinateEpoch_.has_value()) { |
266 | 0 | writer->AddObjKey("coordinateEpoch"); |
267 | 0 | writer->Add(coordinateEpochAsDecimalYear()); |
268 | 0 | } |
269 | 0 | } |
270 | | //! @endcond |
271 | | |
272 | | } // namespace coordinates |
273 | | |
274 | | NS_PROJ_END |