Coverage Report

Created: 2025-07-23 06:58

/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