Coverage Report

Created: 2024-02-25 06:14

/src/PROJ/src/iso19111/operation/conversion.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) 2018, 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/common.hpp"
34
#include "proj/coordinateoperation.hpp"
35
#include "proj/crs.hpp"
36
#include "proj/io.hpp"
37
#include "proj/metadata.hpp"
38
#include "proj/util.hpp"
39
40
#include "proj/internal/internal.hpp"
41
#include "proj/internal/io_internal.hpp"
42
#include "proj/internal/tracing.hpp"
43
44
#include "coordinateoperation_internal.hpp"
45
#include "esriparammappings.hpp"
46
#include "operationmethod_private.hpp"
47
#include "oputils.hpp"
48
#include "parammappings.hpp"
49
#include "vectorofvaluesparams.hpp"
50
51
// PROJ include order is sensitive
52
// clang-format off
53
#include "proj.h"
54
#include "proj_internal.h" // M_PI
55
// clang-format on
56
#include "proj_constants.h"
57
58
#include "proj_json_streaming_writer.hpp"
59
60
#include <algorithm>
61
#include <cassert>
62
#include <cmath>
63
#include <cstring>
64
#include <memory>
65
#include <set>
66
#include <string>
67
#include <vector>
68
69
using namespace NS_PROJ::internal;
70
71
// ---------------------------------------------------------------------------
72
73
NS_PROJ_START
74
namespace operation {
75
76
//! @cond Doxygen_Suppress
77
constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0;
78
constexpr double UTM_SCALE_FACTOR = 0.9996;
79
constexpr double UTM_FALSE_EASTING = 500000.0;
80
constexpr double UTM_NORTH_FALSE_NORTHING = 0.0;
81
constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0;
82
83
//! @endcond
84
85
// ---------------------------------------------------------------------------
86
87
//! @cond Doxygen_Suppress
88
struct Conversion::Private {};
89
//! @endcond
90
91
// ---------------------------------------------------------------------------
92
93
Conversion::Conversion(const OperationMethodNNPtr &methodIn,
94
                       const std::vector<GeneralParameterValueNNPtr> &values)
95
44.4k
    : SingleOperation(methodIn), d(nullptr) {
96
44.4k
    setParameterValues(values);
97
44.4k
}
osgeo::proj::operation::Conversion::Conversion(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::OperationMethod> > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> > > > const&)
Line
Count
Source
95
8.94k
    : SingleOperation(methodIn), d(nullptr) {
96
8.94k
    setParameterValues(values);
97
8.94k
}
osgeo::proj::operation::Conversion::Conversion(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::OperationMethod> > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> > > > const&)
Line
Count
Source
95
35.5k
    : SingleOperation(methodIn), d(nullptr) {
96
35.5k
    setParameterValues(values);
97
35.5k
}
98
99
// ---------------------------------------------------------------------------
100
101
Conversion::Conversion(const Conversion &other)
102
8.37k
    : CoordinateOperation(other), SingleOperation(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::operation::Conversion::Conversion(osgeo::proj::operation::Conversion const&)
osgeo::proj::operation::Conversion::Conversion(osgeo::proj::operation::Conversion const&)
Line
Count
Source
102
8.37k
    : CoordinateOperation(other), SingleOperation(other), d(nullptr) {}
103
104
// ---------------------------------------------------------------------------
105
106
//! @cond Doxygen_Suppress
107
52.8k
Conversion::~Conversion() = default;
108
//! @endcond
109
110
// ---------------------------------------------------------------------------
111
112
//! @cond Doxygen_Suppress
113
7.52k
ConversionNNPtr Conversion::shallowClone() const {
114
7.52k
    auto conv = Conversion::nn_make_shared<Conversion>(*this);
115
7.52k
    conv->assignSelf(conv);
116
7.52k
    conv->setCRSs(this, false);
117
7.52k
    return conv;
118
7.52k
}
119
120
129
CoordinateOperationNNPtr Conversion::_shallowClone() const {
121
129
    return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
122
129
}
123
//! @endcond
124
125
// ---------------------------------------------------------------------------
126
127
//! @cond Doxygen_Suppress
128
ConversionNNPtr
129
Conversion::alterParametersLinearUnit(const common::UnitOfMeasure &unit,
130
0
                                      bool convertToNewUnit) const {
131
132
0
    std::vector<GeneralParameterValueNNPtr> newValues;
133
0
    bool changesDone = false;
134
0
    for (const auto &genOpParamvalue : parameterValues()) {
135
0
        bool updated = false;
136
0
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
137
0
            genOpParamvalue.get());
138
0
        if (opParamvalue) {
139
0
            const auto &paramValue = opParamvalue->parameterValue();
140
0
            if (paramValue->type() == ParameterValue::Type::MEASURE) {
141
0
                const auto &measure = paramValue->value();
142
0
                if (measure.unit().type() ==
143
0
                    common::UnitOfMeasure::Type::LINEAR) {
144
0
                    if (!measure.unit()._isEquivalentTo(
145
0
                            unit, util::IComparable::Criterion::EQUIVALENT)) {
146
0
                        const double newValue =
147
0
                            convertToNewUnit ? measure.convertToUnit(unit)
148
0
                                             : measure.value();
149
0
                        newValues.emplace_back(OperationParameterValue::create(
150
0
                            opParamvalue->parameter(),
151
0
                            ParameterValue::create(
152
0
                                common::Measure(newValue, unit))));
153
0
                        updated = true;
154
0
                    }
155
0
                }
156
0
            }
157
0
        }
158
0
        if (updated) {
159
0
            changesDone = true;
160
0
        } else {
161
0
            newValues.emplace_back(genOpParamvalue);
162
0
        }
163
0
    }
164
0
    if (changesDone) {
165
0
        auto conv = create(util::PropertyMap().set(
166
0
                               common::IdentifiedObject::NAME_KEY, "unknown"),
167
0
                           method(), newValues);
168
0
        conv->setCRSs(this, false);
169
0
        return conv;
170
0
    } else {
171
0
        return NN_NO_CHECK(
172
0
            util::nn_dynamic_pointer_cast<Conversion>(shared_from_this()));
173
0
    }
174
0
}
175
//! @endcond
176
177
// ---------------------------------------------------------------------------
178
179
/** \brief Instantiate a Conversion from a vector of GeneralParameterValue.
180
 *
181
 * @param properties See \ref general_properties. At minimum the name should be
182
 * defined.
183
 * @param methodIn the operation method.
184
 * @param values the values.
185
 * @return a new Conversion.
186
 * @throws InvalidOperation
187
 */
188
ConversionNNPtr Conversion::create(const util::PropertyMap &properties,
189
                                   const OperationMethodNNPtr &methodIn,
190
                                   const std::vector<GeneralParameterValueNNPtr>
191
                                       &values) // throw InvalidOperation
192
35.5k
{
193
35.5k
    if (methodIn->parameters().size() != values.size()) {
194
0
        throw InvalidOperation(
195
0
            "Inconsistent number of parameters and parameter values");
196
0
    }
197
35.5k
    auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values);
198
35.5k
    conv->assignSelf(conv);
199
35.5k
    conv->setProperties(properties);
200
35.5k
    return conv;
201
35.5k
}
202
203
// ---------------------------------------------------------------------------
204
205
/** \brief Instantiate a Conversion and its OperationMethod
206
 *
207
 * @param propertiesConversion See \ref general_properties of the conversion.
208
 * At minimum the name should be defined.
209
 * @param propertiesOperationMethod See \ref general_properties of the operation
210
 * method. At minimum the name should be defined.
211
 * @param parameters the operation parameters.
212
 * @param values the operation values. Constraint:
213
 * values.size() == parameters.size()
214
 * @return a new Conversion.
215
 * @throws InvalidOperation
216
 */
217
ConversionNNPtr Conversion::create(
218
    const util::PropertyMap &propertiesConversion,
219
    const util::PropertyMap &propertiesOperationMethod,
220
    const std::vector<OperationParameterNNPtr> &parameters,
221
    const std::vector<ParameterValueNNPtr> &values) // throw InvalidOperation
222
35.5k
{
223
35.5k
    OperationMethodNNPtr op(
224
35.5k
        OperationMethod::create(propertiesOperationMethod, parameters));
225
226
35.5k
    if (parameters.size() != values.size()) {
227
0
        throw InvalidOperation(
228
0
            "Inconsistent number of parameters and parameter values");
229
0
    }
230
35.5k
    std::vector<GeneralParameterValueNNPtr> generalParameterValues;
231
35.5k
    generalParameterValues.reserve(values.size());
232
82.2k
    for (size_t i = 0; i < values.size(); i++) {
233
46.7k
        generalParameterValues.push_back(
234
46.7k
            OperationParameterValue::create(parameters[i], values[i]));
235
46.7k
    }
236
35.5k
    return create(propertiesConversion, op, generalParameterValues);
237
35.5k
}
238
239
// ---------------------------------------------------------------------------
240
241
//! @cond Doxygen_Suppress
242
243
// ---------------------------------------------------------------------------
244
245
static util::PropertyMap
246
getUTMConversionProperty(const util::PropertyMap &properties, int zone,
247
65
                         bool north) {
248
65
    if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
249
65
        std::string conversionName("UTM zone ");
250
65
        conversionName += toString(zone);
251
65
        conversionName += (north ? 'N' : 'S');
252
253
65
        return createMapNameEPSGCode(conversionName,
254
65
                                     (north ? 16000 : 17000) + zone);
255
65
    } else {
256
0
        return properties;
257
0
    }
258
65
}
259
260
// ---------------------------------------------------------------------------
261
262
static ConversionNNPtr
263
createConversion(const util::PropertyMap &properties,
264
                 const MethodMapping *mapping,
265
530
                 const std::vector<ParameterValueNNPtr> &values) {
266
267
530
    std::vector<OperationParameterNNPtr> parameters;
268
1.28k
    for (int i = 0; mapping->params != nullptr && mapping->params[i] != nullptr;
269
752
         i++) {
270
752
        const auto *param = mapping->params[i];
271
752
        auto paramProperties = util::PropertyMap().set(
272
752
            common::IdentifiedObject::NAME_KEY, param->wkt2_name);
273
752
        if (param->epsg_code != 0) {
274
752
            paramProperties
275
752
                .set(metadata::Identifier::CODESPACE_KEY,
276
752
                     metadata::Identifier::EPSG)
277
752
                .set(metadata::Identifier::CODE_KEY, param->epsg_code);
278
752
        }
279
752
        auto parameter = OperationParameter::create(paramProperties);
280
752
        parameters.push_back(parameter);
281
752
    }
282
283
530
    auto methodProperties = util::PropertyMap().set(
284
530
        common::IdentifiedObject::NAME_KEY, mapping->wkt2_name);
285
530
    if (mapping->epsg_code != 0) {
286
119
        methodProperties
287
119
            .set(metadata::Identifier::CODESPACE_KEY,
288
119
                 metadata::Identifier::EPSG)
289
119
            .set(metadata::Identifier::CODE_KEY, mapping->epsg_code);
290
119
    }
291
530
    return Conversion::create(
292
530
        addDefaultNameIfNeeded(properties, mapping->wkt2_name),
293
530
        methodProperties, parameters, values);
294
530
}
295
//! @endcond
296
297
// ---------------------------------------------------------------------------
298
299
ConversionNNPtr
300
Conversion::create(const util::PropertyMap &properties, int method_epsg_code,
301
119
                   const std::vector<ParameterValueNNPtr> &values) {
302
119
    const MethodMapping *mapping = getMapping(method_epsg_code);
303
119
    assert(mapping);
304
119
    return createConversion(properties, mapping, values);
305
119
}
306
307
// ---------------------------------------------------------------------------
308
309
ConversionNNPtr
310
Conversion::create(const util::PropertyMap &properties,
311
                   const char *method_wkt2_name,
312
411
                   const std::vector<ParameterValueNNPtr> &values) {
313
411
    const MethodMapping *mapping = getMapping(method_wkt2_name);
314
411
    assert(mapping);
315
411
    return createConversion(properties, mapping, values);
316
411
}
317
318
// ---------------------------------------------------------------------------
319
320
/** \brief Instantiate a
321
 * <a href="../../../operations/projections/utm.html">
322
 * Universal Transverse Mercator</a> conversion.
323
 *
324
 * UTM is a family of conversions, of EPSG codes from 16001 to 16060 for the
325
 * northern hemisphere, and 17001 to 17060 for the southern hemisphere,
326
 * based on the Transverse Mercator projection method.
327
 *
328
 * @param properties See \ref general_properties of the conversion. If the name
329
 * is not provided, it is automatically set.
330
 * @param zone UTM zone number between 1 and 60.
331
 * @param north true for UTM northern hemisphere, false for UTM southern
332
 * hemisphere.
333
 * @return a new Conversion.
334
 */
335
ConversionNNPtr Conversion::createUTM(const util::PropertyMap &properties,
336
423
                                      int zone, bool north) {
337
423
    if (zone < 1 || zone > 60) {
338
358
        throw InvalidOperation("Invalid zone number");
339
358
    }
340
65
    return create(
341
65
        getUTMConversionProperty(properties, zone, north),
342
65
        EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
343
65
        createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN),
344
65
                     common::Angle(zone * 6.0 - 183.0),
345
65
                     common::Scale(UTM_SCALE_FACTOR),
346
65
                     common::Length(UTM_FALSE_EASTING),
347
65
                     common::Length(north ? UTM_NORTH_FALSE_NORTHING
348
65
                                          : UTM_SOUTH_FALSE_NORTHING)));
349
423
}
350
351
// ---------------------------------------------------------------------------
352
353
/** \brief Instantiate a conversion based on the
354
 * <a href="../../../operations/projections/tmerc.html">
355
 * Transverse Mercator</a> projection method.
356
 *
357
 * This method is defined as
358
 * <a href="https://epsg.org/coord-operation-method_9807/index.html">
359
 * EPSG:9807</a>.
360
 *
361
 * @param properties See \ref general_properties of the conversion. If the name
362
 * is not provided, it is automatically set.
363
 * @param centerLat See \ref center_latitude
364
 * @param centerLong See \ref center_longitude
365
 * @param scale See \ref scale
366
 * @param falseEasting See \ref false_easting
367
 * @param falseNorthing See \ref false_northing
368
 * @return a new Conversion.
369
 */
370
ConversionNNPtr Conversion::createTransverseMercator(
371
    const util::PropertyMap &properties, const common::Angle &centerLat,
372
    const common::Angle &centerLong, const common::Scale &scale,
373
11
    const common::Length &falseEasting, const common::Length &falseNorthing) {
374
11
    return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
375
11
                  createParams(centerLat, centerLong, scale, falseEasting,
376
11
                               falseNorthing));
377
11
}
378
379
// ---------------------------------------------------------------------------
380
381
/** \brief Instantiate a conversion based on the
382
 * <a href="../../../operations/projections/gstmerc.html">
383
 * Gauss Schreiber Transverse Mercator</a> projection method.
384
 *
385
 * This method is also known as Gauss-Laborde Reunion.
386
 *
387
 * There is no equivalent in EPSG.
388
 *
389
 * @param properties See \ref general_properties of the conversion. If the name
390
 * is not provided, it is automatically set.
391
 * @param centerLat See \ref center_latitude
392
 * @param centerLong See \ref center_longitude
393
 * @param scale See \ref scale
394
 * @param falseEasting See \ref false_easting
395
 * @param falseNorthing See \ref false_northing
396
 * @return a new Conversion.
397
 */
398
ConversionNNPtr Conversion::createGaussSchreiberTransverseMercator(
399
    const util::PropertyMap &properties, const common::Angle &centerLat,
400
    const common::Angle &centerLong, const common::Scale &scale,
401
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
402
0
    return create(properties,
403
0
                  PROJ_WKT2_NAME_METHOD_GAUSS_SCHREIBER_TRANSVERSE_MERCATOR,
404
0
                  createParams(centerLat, centerLong, scale, falseEasting,
405
0
                               falseNorthing));
406
0
}
407
408
// ---------------------------------------------------------------------------
409
410
/** \brief Instantiate a conversion based on the
411
 * <a href="../../../operations/projections/tmerc.html">
412
 * Transverse Mercator South Orientated</a> projection method.
413
 *
414
 * This method is defined as
415
 * <a href="https://epsg.org/coord-operation-method_9808/index.html">
416
 * EPSG:9808</a>.
417
 *
418
 * @param properties See \ref general_properties of the conversion. If the name
419
 * is not provided, it is automatically set.
420
 * @param centerLat See \ref center_latitude
421
 * @param centerLong See \ref center_longitude
422
 * @param scale See \ref scale
423
 * @param falseEasting See \ref false_easting
424
 * @param falseNorthing See \ref false_northing
425
 * @return a new Conversion.
426
 */
427
ConversionNNPtr Conversion::createTransverseMercatorSouthOriented(
428
    const util::PropertyMap &properties, const common::Angle &centerLat,
429
    const common::Angle &centerLong, const common::Scale &scale,
430
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
431
0
    return create(properties,
432
0
                  EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED,
433
0
                  createParams(centerLat, centerLong, scale, falseEasting,
434
0
                               falseNorthing));
435
0
}
436
437
// ---------------------------------------------------------------------------
438
439
/** \brief Instantiate a conversion based on the
440
 * <a href="../../../operations/projections/tpeqd.html">
441
 * Two Point Equidistant</a> projection method.
442
 *
443
 * There is no equivalent in EPSG.
444
 *
445
 * @param properties See \ref general_properties of the conversion. If the name
446
 * is not provided, it is automatically set.
447
 * @param latitudeFirstPoint Latitude of first point.
448
 * @param longitudeFirstPoint Longitude of first point.
449
 * @param latitudeSecondPoint Latitude of second point.
450
 * @param longitudeSeconPoint Longitude of second point.
451
 * @param falseEasting See \ref false_easting
452
 * @param falseNorthing See \ref false_northing
453
 * @return a new Conversion.
454
 */
455
ConversionNNPtr
456
Conversion::createTwoPointEquidistant(const util::PropertyMap &properties,
457
                                      const common::Angle &latitudeFirstPoint,
458
                                      const common::Angle &longitudeFirstPoint,
459
                                      const common::Angle &latitudeSecondPoint,
460
                                      const common::Angle &longitudeSeconPoint,
461
                                      const common::Length &falseEasting,
462
0
                                      const common::Length &falseNorthing) {
463
0
    return create(properties, PROJ_WKT2_NAME_METHOD_TWO_POINT_EQUIDISTANT,
464
0
                  createParams(latitudeFirstPoint, longitudeFirstPoint,
465
0
                               latitudeSecondPoint, longitudeSeconPoint,
466
0
                               falseEasting, falseNorthing));
467
0
}
468
469
// ---------------------------------------------------------------------------
470
471
/** \brief Instantiate a conversion based on the Tunisia Mapping Grid projection
472
 * method.
473
 *
474
 * This method is defined as
475
 * <a href="https://epsg.org/coord-operation-method_9816/index.html">
476
 * EPSG:9816</a>.
477
 *
478
 * \note There is currently no implementation of the method formulas in PROJ.
479
 *
480
 * @param properties See \ref general_properties of the conversion. If the name
481
 * is not provided, it is automatically set.
482
 * @param centerLat See \ref center_latitude
483
 * @param centerLong See \ref center_longitude
484
 * @param falseEasting See \ref false_easting
485
 * @param falseNorthing See \ref false_northing
486
 * @return a new Conversion.
487
 * @deprecated. Use createTunisiaMiningGrid() instead
488
 */
489
ConversionNNPtr Conversion::createTunisiaMappingGrid(
490
    const util::PropertyMap &properties, const common::Angle &centerLat,
491
    const common::Angle &centerLong, const common::Length &falseEasting,
492
0
    const common::Length &falseNorthing) {
493
0
    return create(
494
0
        properties, EPSG_CODE_METHOD_TUNISIA_MINING_GRID,
495
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
496
0
}
497
498
// ---------------------------------------------------------------------------
499
500
/** \brief Instantiate a conversion based on the Tunisia Mining Grid projection
501
 * method.
502
 *
503
 * This method is defined as
504
 * <a href="https://epsg.org/coord-operation-method_9816/index.html">
505
 * EPSG:9816</a>.
506
 *
507
 * \note There is currently no implementation of the method formulas in PROJ.
508
 *
509
 * @param properties See \ref general_properties of the conversion. If the name
510
 * is not provided, it is automatically set.
511
 * @param centerLat See \ref center_latitude
512
 * @param centerLong See \ref center_longitude
513
 * @param falseEasting See \ref false_easting
514
 * @param falseNorthing See \ref false_northing
515
 * @return a new Conversion.
516
 * @since 9.2
517
 */
518
ConversionNNPtr Conversion::createTunisiaMiningGrid(
519
    const util::PropertyMap &properties, const common::Angle &centerLat,
520
    const common::Angle &centerLong, const common::Length &falseEasting,
521
0
    const common::Length &falseNorthing) {
522
0
    return create(
523
0
        properties, EPSG_CODE_METHOD_TUNISIA_MINING_GRID,
524
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
525
0
}
526
527
// ---------------------------------------------------------------------------
528
529
/** \brief Instantiate a conversion based on the
530
 * <a href="../../../operations/projections/aea.html">
531
 * Albers Conic Equal Area</a> projection method.
532
 *
533
 * This method is defined as
534
 * <a href="https://epsg.org/coord-operation-method_9822/index.html">
535
 * EPSG:9822</a>.
536
 *
537
 * @note the order of arguments is conformant with the corresponding EPSG
538
 * mode and different than OGRSpatialReference::setACEA() of GDAL &lt;= 2.3
539
 *
540
 * @param properties See \ref general_properties of the conversion. If the name
541
 * is not provided, it is automatically set.
542
 * @param latitudeFalseOrigin See \ref latitude_false_origin
543
 * @param longitudeFalseOrigin See \ref longitude_false_origin
544
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
545
 * @param latitudeSecondParallel See \ref latitude_second_std_parallel
546
 * @param eastingFalseOrigin See \ref easting_false_origin
547
 * @param northingFalseOrigin See \ref northing_false_origin
548
 * @return a new Conversion.
549
 */
550
ConversionNNPtr
551
Conversion::createAlbersEqualArea(const util::PropertyMap &properties,
552
                                  const common::Angle &latitudeFalseOrigin,
553
                                  const common::Angle &longitudeFalseOrigin,
554
                                  const common::Angle &latitudeFirstParallel,
555
                                  const common::Angle &latitudeSecondParallel,
556
                                  const common::Length &eastingFalseOrigin,
557
0
                                  const common::Length &northingFalseOrigin) {
558
0
    return create(properties, EPSG_CODE_METHOD_ALBERS_EQUAL_AREA,
559
0
                  createParams(latitudeFalseOrigin, longitudeFalseOrigin,
560
0
                               latitudeFirstParallel, latitudeSecondParallel,
561
0
                               eastingFalseOrigin, northingFalseOrigin));
562
0
}
563
564
// ---------------------------------------------------------------------------
565
566
/** \brief Instantiate a conversion based on the
567
 * <a href="../../../operations/projections/lcc.html">
568
 * Lambert Conic Conformal 1SP</a> projection method.
569
 *
570
 * This method is defined as
571
 * <a href="https://epsg.org/coord-operation-method_9801/index.html">
572
 * EPSG:9801</a>.
573
 *
574
 * @param properties See \ref general_properties of the conversion. If the name
575
 * is not provided, it is automatically set.
576
 * @param centerLat See \ref center_latitude
577
 * @param centerLong See \ref center_longitude
578
 * @param scale See \ref scale
579
 * @param falseEasting See \ref false_easting
580
 * @param falseNorthing See \ref false_northing
581
 * @return a new Conversion.
582
 */
583
ConversionNNPtr Conversion::createLambertConicConformal_1SP(
584
    const util::PropertyMap &properties, const common::Angle &centerLat,
585
    const common::Angle &centerLong, const common::Scale &scale,
586
17
    const common::Length &falseEasting, const common::Length &falseNorthing) {
587
17
    return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP,
588
17
                  createParams(centerLat, centerLong, scale, falseEasting,
589
17
                               falseNorthing));
590
17
}
591
592
// ---------------------------------------------------------------------------
593
594
/** \brief Instantiate a conversion based on the
595
 * <a href="../../../operations/projections/lcc.html">
596
 * Lambert Conic Conformal 1SP Variant B</a> projection method.
597
 *
598
 * This method is defined as
599
 * <a href="https://epsg.org/coord-operation-method_1102/index.html">
600
 * EPSG:1102</a>.
601
 *
602
 * @param properties See \ref general_properties of the conversion. If the name
603
 * is not provided, it is automatically set.
604
 * @param latitudeNatOrigin See \ref center_latitude
605
 * @param scale See \ref scale
606
 * @param latitudeFalseOrigin See \ref latitude_false_origin
607
 * @param longitudeFalseOrigin See \ref longitude_false_origin
608
 * @param eastingFalseOrigin See \ref easting_false_origin
609
 * @param northingFalseOrigin See \ref northing_false_origin
610
 * @return a new Conversion.
611
 * @since 9.2.1
612
 */
613
ConversionNNPtr Conversion::createLambertConicConformal_1SP_VariantB(
614
    const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
615
    const common::Scale &scale, const common::Angle &latitudeFalseOrigin,
616
    const common::Angle &longitudeFalseOrigin,
617
    const common::Length &eastingFalseOrigin,
618
0
    const common::Length &northingFalseOrigin) {
619
0
    return create(properties,
620
0
                  EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B,
621
0
                  createParams(latitudeNatOrigin, scale, latitudeFalseOrigin,
622
0
                               longitudeFalseOrigin, eastingFalseOrigin,
623
0
                               northingFalseOrigin));
624
0
}
625
626
// ---------------------------------------------------------------------------
627
628
/** \brief Instantiate a conversion based on the
629
 * <a href="../../../operations/projections/lcc.html">
630
 * Lambert Conic Conformal 2SP</a> projection method.
631
 *
632
 * This method is defined as
633
 * <a href="https://epsg.org/coord-operation-method_9802/index.html">
634
 * EPSG:9802</a>.
635
 *
636
 * @note the order of arguments is conformant with the corresponding EPSG
637
 * mode and different than OGRSpatialReference::setLCC() of GDAL &lt;= 2.3
638
 *
639
 * @param properties See \ref general_properties of the conversion. If the name
640
 * is not provided, it is automatically set.
641
 * @param latitudeFalseOrigin See \ref latitude_false_origin
642
 * @param longitudeFalseOrigin See \ref longitude_false_origin
643
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
644
 * @param latitudeSecondParallel See \ref latitude_second_std_parallel
645
 * @param eastingFalseOrigin See \ref easting_false_origin
646
 * @param northingFalseOrigin See \ref northing_false_origin
647
 * @return a new Conversion.
648
 */
649
ConversionNNPtr Conversion::createLambertConicConformal_2SP(
650
    const util::PropertyMap &properties,
651
    const common::Angle &latitudeFalseOrigin,
652
    const common::Angle &longitudeFalseOrigin,
653
    const common::Angle &latitudeFirstParallel,
654
    const common::Angle &latitudeSecondParallel,
655
    const common::Length &eastingFalseOrigin,
656
0
    const common::Length &northingFalseOrigin) {
657
0
    return create(properties, EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP,
658
0
                  createParams(latitudeFalseOrigin, longitudeFalseOrigin,
659
0
                               latitudeFirstParallel, latitudeSecondParallel,
660
0
                               eastingFalseOrigin, northingFalseOrigin));
661
0
}
662
663
// ---------------------------------------------------------------------------
664
665
/** \brief Instantiate a conversion based on the
666
 * <a href="../../../operations/projections/lcc.html">
667
 * Lambert Conic Conformal (2SP Michigan)</a> projection method.
668
 *
669
 * This method is defined as
670
 * <a href="https://epsg.org/coord-operation-method_1051/index.html">
671
 * EPSG:1051</a>.
672
 *
673
 * @param properties See \ref general_properties of the conversion. If the name
674
 * is not provided, it is automatically set.
675
 * @param latitudeFalseOrigin See \ref latitude_false_origin
676
 * @param longitudeFalseOrigin See \ref longitude_false_origin
677
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
678
 * @param latitudeSecondParallel See \ref latitude_second_std_parallel
679
 * @param eastingFalseOrigin See \ref easting_false_origin
680
 * @param northingFalseOrigin See \ref northing_false_origin
681
 * @param ellipsoidScalingFactor Ellipsoid scaling factor.
682
 * @return a new Conversion.
683
 */
684
ConversionNNPtr Conversion::createLambertConicConformal_2SP_Michigan(
685
    const util::PropertyMap &properties,
686
    const common::Angle &latitudeFalseOrigin,
687
    const common::Angle &longitudeFalseOrigin,
688
    const common::Angle &latitudeFirstParallel,
689
    const common::Angle &latitudeSecondParallel,
690
    const common::Length &eastingFalseOrigin,
691
    const common::Length &northingFalseOrigin,
692
0
    const common::Scale &ellipsoidScalingFactor) {
693
0
    return create(properties,
694
0
                  EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN,
695
0
                  createParams(latitudeFalseOrigin, longitudeFalseOrigin,
696
0
                               latitudeFirstParallel, latitudeSecondParallel,
697
0
                               eastingFalseOrigin, northingFalseOrigin,
698
0
                               ellipsoidScalingFactor));
699
0
}
700
701
// ---------------------------------------------------------------------------
702
703
/** \brief Instantiate a conversion based on the
704
 * <a href="../../../operations/projections/lcc.html">
705
 * Lambert Conic Conformal (2SP Belgium)</a> projection method.
706
 *
707
 * This method is defined as
708
 * <a href="https://epsg.org/coord-operation-method_9803/index.html">
709
 * EPSG:9803</a>.
710
 *
711
 * \warning The formulas used currently in PROJ are, incorrectly, the ones of
712
 * the regular LCC_2SP method.
713
 *
714
 * @note the order of arguments is conformant with the corresponding EPSG
715
 * mode and different than OGRSpatialReference::setLCCB() of GDAL &lt;= 2.3
716
 *
717
 * @param properties See \ref general_properties of the conversion. If the name
718
 * is not provided, it is automatically set.
719
 * @param latitudeFalseOrigin See \ref latitude_false_origin
720
 * @param longitudeFalseOrigin See \ref longitude_false_origin
721
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
722
 * @param latitudeSecondParallel See \ref latitude_second_std_parallel
723
 * @param eastingFalseOrigin See \ref easting_false_origin
724
 * @param northingFalseOrigin See \ref northing_false_origin
725
 * @return a new Conversion.
726
 */
727
ConversionNNPtr Conversion::createLambertConicConformal_2SP_Belgium(
728
    const util::PropertyMap &properties,
729
    const common::Angle &latitudeFalseOrigin,
730
    const common::Angle &longitudeFalseOrigin,
731
    const common::Angle &latitudeFirstParallel,
732
    const common::Angle &latitudeSecondParallel,
733
    const common::Length &eastingFalseOrigin,
734
0
    const common::Length &northingFalseOrigin) {
735
736
0
    return create(properties,
737
0
                  EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_BELGIUM,
738
0
                  createParams(latitudeFalseOrigin, longitudeFalseOrigin,
739
0
                               latitudeFirstParallel, latitudeSecondParallel,
740
0
                               eastingFalseOrigin, northingFalseOrigin));
741
0
}
742
743
// ---------------------------------------------------------------------------
744
745
/** \brief Instantiate a conversion based on the
746
 * <a href="../../../operations/projections/aeqd.html">
747
 * Azimuthal Equidistant</a> projection method.
748
 *
749
 * This method is defined as
750
 * <a href="https://epsg.org/coord-operation-method_1125/index.html">
751
 * EPSG:1125</a>.
752
 *
753
 * @param properties See \ref general_properties of the conversion. If the name
754
 * is not provided, it is automatically set.
755
 * @param latitudeNatOrigin See \ref center_latitude
756
 * @param longitudeNatOrigin See \ref center_longitude
757
 * @param falseEasting See \ref false_easting
758
 * @param falseNorthing See \ref false_northing
759
 * @return a new Conversion.
760
 */
761
ConversionNNPtr Conversion::createAzimuthalEquidistant(
762
    const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
763
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
764
0
    const common::Length &falseNorthing) {
765
0
    return create(properties, EPSG_CODE_METHOD_AZIMUTHAL_EQUIDISTANT,
766
0
                  createParams(latitudeNatOrigin, longitudeNatOrigin,
767
0
                               falseEasting, falseNorthing));
768
0
}
769
770
// ---------------------------------------------------------------------------
771
772
/** \brief Instantiate a conversion based on the
773
 * <a href="../../../operations/projections/aeqd.html">
774
 * Guam Projection</a> method.
775
 *
776
 * This method is defined as
777
 * <a href="https://epsg.org/coord-operation-method_9831/index.html">
778
 * EPSG:9831</a>.
779
 *
780
 * @param properties See \ref general_properties of the conversion. If the name
781
 *is
782
 * not provided, it is automatically set.
783
 * @param latitudeNatOrigin See \ref center_latitude
784
 * @param longitudeNatOrigin See \ref center_longitude
785
 * @param falseEasting See \ref false_easting
786
 * @param falseNorthing See \ref false_northing
787
 * @return a new Conversion.
788
 */
789
ConversionNNPtr Conversion::createGuamProjection(
790
    const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
791
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
792
0
    const common::Length &falseNorthing) {
793
0
    return create(properties, EPSG_CODE_METHOD_GUAM_PROJECTION,
794
0
                  createParams(latitudeNatOrigin, longitudeNatOrigin,
795
0
                               falseEasting, falseNorthing));
796
0
}
797
798
// ---------------------------------------------------------------------------
799
800
/** \brief Instantiate a conversion based on the
801
 * <a href="../../../operations/projections/bonne.html">
802
 * Bonne</a> projection method.
803
 *
804
 * This method is defined as
805
 * <a href="https://epsg.org/coord-operation-method_9827/index.html">
806
 * EPSG:9827</a>.
807
 *
808
 * @param properties See \ref general_properties of the conversion. If the name
809
 * is not provided, it is automatically set.
810
 * @param latitudeNatOrigin See \ref center_latitude . PROJ calls its the
811
 * standard parallel 1.
812
 * @param longitudeNatOrigin See \ref center_longitude
813
 * @param falseEasting See \ref false_easting
814
 * @param falseNorthing See \ref false_northing
815
 * @return a new Conversion.
816
 */
817
ConversionNNPtr Conversion::createBonne(const util::PropertyMap &properties,
818
                                        const common::Angle &latitudeNatOrigin,
819
                                        const common::Angle &longitudeNatOrigin,
820
                                        const common::Length &falseEasting,
821
0
                                        const common::Length &falseNorthing) {
822
0
    return create(properties, EPSG_CODE_METHOD_BONNE,
823
0
                  createParams(latitudeNatOrigin, longitudeNatOrigin,
824
0
                               falseEasting, falseNorthing));
825
0
}
826
827
// ---------------------------------------------------------------------------
828
829
/** \brief Instantiate a conversion based on the
830
 * <a href="../../../operations/projections/cea.html">
831
 * Lambert Cylindrical Equal Area (Spherical)</a> projection method.
832
 *
833
 * This method is defined as
834
 * <a href="https://epsg.org/coord-operation-method_9834/index.html">
835
 * EPSG:9834</a>.
836
 *
837
 * \warning The PROJ cea computation code would select the ellipsoidal form if
838
 * a non-spherical ellipsoid is used for the base GeographicCRS.
839
 *
840
 * @param properties See \ref general_properties of the conversion. If the name
841
 * is not provided, it is automatically set.
842
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
843
 * @param longitudeNatOrigin See \ref center_longitude
844
 * @param falseEasting See \ref false_easting
845
 * @param falseNorthing See \ref false_northing
846
 * @return a new Conversion.
847
 */
848
ConversionNNPtr Conversion::createLambertCylindricalEqualAreaSpherical(
849
    const util::PropertyMap &properties,
850
    const common::Angle &latitudeFirstParallel,
851
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
852
0
    const common::Length &falseNorthing) {
853
0
    return create(properties,
854
0
                  EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL,
855
0
                  createParams(latitudeFirstParallel, longitudeNatOrigin,
856
0
                               falseEasting, falseNorthing));
857
0
}
858
859
// ---------------------------------------------------------------------------
860
861
/** \brief Instantiate a conversion based on the
862
 * <a href="../../../operations/projections/cea.html">
863
 * Lambert Cylindrical Equal Area (ellipsoidal form)</a> projection method.
864
 *
865
 * This method is defined as
866
 * <a href="https://epsg.org/coord-operation-method_9835/index.html">
867
 * EPSG:9835</a>.
868
 *
869
 * @param properties See \ref general_properties of the conversion. If the name
870
 * is not provided, it is automatically set.
871
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
872
 * @param longitudeNatOrigin See \ref center_longitude
873
 * @param falseEasting See \ref false_easting
874
 * @param falseNorthing See \ref false_northing
875
 * @return a new Conversion.
876
 */
877
ConversionNNPtr Conversion::createLambertCylindricalEqualArea(
878
    const util::PropertyMap &properties,
879
    const common::Angle &latitudeFirstParallel,
880
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
881
0
    const common::Length &falseNorthing) {
882
0
    return create(properties, EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA,
883
0
                  createParams(latitudeFirstParallel, longitudeNatOrigin,
884
0
                               falseEasting, falseNorthing));
885
0
}
886
887
// ---------------------------------------------------------------------------
888
889
/** \brief Instantiate a conversion based on the
890
 * <a href="../../../operations/projections/cass.html">
891
 * Cassini-Soldner</a> projection method.
892
 *
893
 * This method is defined as
894
 * <a href="https://epsg.org/coord-operation-method_9806/index.html">
895
 * EPSG:9806</a>.
896
 *
897
 * @param properties See \ref general_properties of the conversion. If the name
898
 * is not provided, it is automatically set.
899
 * @param centerLat See \ref center_latitude
900
 * @param centerLong See \ref center_longitude
901
 * @param falseEasting See \ref false_easting
902
 * @param falseNorthing See \ref false_northing
903
 * @return a new Conversion.
904
 */
905
ConversionNNPtr Conversion::createCassiniSoldner(
906
    const util::PropertyMap &properties, const common::Angle &centerLat,
907
    const common::Angle &centerLong, const common::Length &falseEasting,
908
0
    const common::Length &falseNorthing) {
909
0
    return create(
910
0
        properties, EPSG_CODE_METHOD_CASSINI_SOLDNER,
911
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
912
0
}
913
914
// ---------------------------------------------------------------------------
915
916
/** \brief Instantiate a conversion based on the
917
 * <a href="../../../operations/projections/eqdc.html">
918
 * Equidistant Conic</a> projection method.
919
 *
920
 * This method is defined as
921
 * <a href="https://epsg.org/coord-operation-method_1119/index.html">
922
 * EPSG:1119</a>.
923
 *
924
 * @param properties See \ref general_properties of the conversion. If the name
925
 * is not provided, it is automatically set.
926
 * @param latitudeFalseOrigin See \ref latitude_false_origin
927
 * @param longitudeFalseOrigin See \ref longitude_false_origin
928
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
929
 * @param latitudeSecondParallel See \ref latitude_second_std_parallel
930
 * @param eastingFalseOrigin See \ref easting_false_origin
931
 * @param northingFalseOrigin See \ref northing_false_origin
932
 * @return a new Conversion.
933
 */
934
ConversionNNPtr
935
Conversion::createEquidistantConic(const util::PropertyMap &properties,
936
                                   const common::Angle &latitudeFalseOrigin,
937
                                   const common::Angle &longitudeFalseOrigin,
938
                                   const common::Angle &latitudeFirstParallel,
939
                                   const common::Angle &latitudeSecondParallel,
940
                                   const common::Length &eastingFalseOrigin,
941
0
                                   const common::Length &northingFalseOrigin) {
942
0
    return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CONIC,
943
0
                  createParams(latitudeFalseOrigin, longitudeFalseOrigin,
944
0
                               latitudeFirstParallel, latitudeSecondParallel,
945
0
                               eastingFalseOrigin, northingFalseOrigin));
946
0
}
947
948
// ---------------------------------------------------------------------------
949
950
/** \brief Instantiate a conversion based on the
951
 * <a href="../../../operations/projections/eck1.html">
952
 * Eckert I</a> projection method.
953
 *
954
 * There is no equivalent in EPSG.
955
 *
956
 * @param properties See \ref general_properties of the conversion. If the name
957
 * is not provided, it is automatically set.
958
 * @param centerLong See \ref center_longitude
959
 * @param falseEasting See \ref false_easting
960
 * @param falseNorthing See \ref false_northing
961
 * @return a new Conversion.
962
 */
963
ConversionNNPtr Conversion::createEckertI(const util::PropertyMap &properties,
964
                                          const common::Angle &centerLong,
965
                                          const common::Length &falseEasting,
966
0
                                          const common::Length &falseNorthing) {
967
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_I,
968
0
                  createParams(centerLong, falseEasting, falseNorthing));
969
0
}
970
971
// ---------------------------------------------------------------------------
972
973
/** \brief Instantiate a conversion based on the
974
 * <a href="../../../operations/projections/eck2.html">
975
 * Eckert II</a> projection method.
976
 *
977
 * There is no equivalent in EPSG.
978
 *
979
 * @param properties See \ref general_properties of the conversion. If the name
980
 * is not provided, it is automatically set.
981
 * @param centerLong See \ref center_longitude
982
 * @param falseEasting See \ref false_easting
983
 * @param falseNorthing See \ref false_northing
984
 * @return a new Conversion.
985
 */
986
ConversionNNPtr Conversion::createEckertII(
987
    const util::PropertyMap &properties, const common::Angle &centerLong,
988
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
989
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_II,
990
0
                  createParams(centerLong, falseEasting, falseNorthing));
991
0
}
992
993
// ---------------------------------------------------------------------------
994
995
/** \brief Instantiate a conversion based on the
996
 * <a href="../../../operations/projections/eck3.html">
997
 * Eckert III</a> projection method.
998
 *
999
 * There is no equivalent in EPSG.
1000
 *
1001
 * @param properties See \ref general_properties of the conversion. If the name
1002
 * is not provided, it is automatically set.
1003
 * @param centerLong See \ref center_longitude
1004
 * @param falseEasting See \ref false_easting
1005
 * @param falseNorthing See \ref false_northing
1006
 * @return a new Conversion.
1007
 */
1008
ConversionNNPtr Conversion::createEckertIII(
1009
    const util::PropertyMap &properties, const common::Angle &centerLong,
1010
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1011
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_III,
1012
0
                  createParams(centerLong, falseEasting, falseNorthing));
1013
0
}
1014
1015
// ---------------------------------------------------------------------------
1016
1017
/** \brief Instantiate a conversion based on the
1018
 * <a href="../../../operations/projections/eck4.html">
1019
 * Eckert IV</a> projection method.
1020
 *
1021
 * There is no equivalent in EPSG.
1022
 *
1023
 * @param properties See \ref general_properties of the conversion. If the name
1024
 * is not provided, it is automatically set.
1025
 * @param centerLong See \ref center_longitude
1026
 * @param falseEasting See \ref false_easting
1027
 * @param falseNorthing See \ref false_northing
1028
 * @return a new Conversion.
1029
 */
1030
ConversionNNPtr Conversion::createEckertIV(
1031
    const util::PropertyMap &properties, const common::Angle &centerLong,
1032
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1033
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_IV,
1034
0
                  createParams(centerLong, falseEasting, falseNorthing));
1035
0
}
1036
1037
// ---------------------------------------------------------------------------
1038
1039
/** \brief Instantiate a conversion based on the
1040
 * <a href="../../../operations/projections/eck5.html">
1041
 * Eckert V</a> projection method.
1042
 *
1043
 * There is no equivalent in EPSG.
1044
 *
1045
 * @param properties See \ref general_properties of the conversion. If the name
1046
 * is not provided, it is automatically set.
1047
 * @param centerLong See \ref center_longitude
1048
 * @param falseEasting See \ref false_easting
1049
 * @param falseNorthing See \ref false_northing
1050
 * @return a new Conversion.
1051
 */
1052
ConversionNNPtr Conversion::createEckertV(const util::PropertyMap &properties,
1053
                                          const common::Angle &centerLong,
1054
                                          const common::Length &falseEasting,
1055
0
                                          const common::Length &falseNorthing) {
1056
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_V,
1057
0
                  createParams(centerLong, falseEasting, falseNorthing));
1058
0
}
1059
1060
// ---------------------------------------------------------------------------
1061
1062
/** \brief Instantiate a conversion based on the
1063
 * <a href="../../../operations/projections/eck6.html">
1064
 * Eckert VI</a> projection method.
1065
 *
1066
 * There is no equivalent in EPSG.
1067
 *
1068
 * @param properties See \ref general_properties of the conversion. If the name
1069
 * is not provided, it is automatically set.
1070
 * @param centerLong See \ref center_longitude
1071
 * @param falseEasting See \ref false_easting
1072
 * @param falseNorthing See \ref false_northing
1073
 * @return a new Conversion.
1074
 */
1075
ConversionNNPtr Conversion::createEckertVI(
1076
    const util::PropertyMap &properties, const common::Angle &centerLong,
1077
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1078
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ECKERT_VI,
1079
0
                  createParams(centerLong, falseEasting, falseNorthing));
1080
0
}
1081
1082
// ---------------------------------------------------------------------------
1083
1084
/** \brief Instantiate a conversion based on the
1085
 * <a href="../../../operations/projections/eqc.html">
1086
 * Equidistant Cylindrical</a> projection method.
1087
 *
1088
 * This is also known as the Equirectangular method, and in the particular case
1089
 * where the latitude of first parallel is 0.
1090
 *
1091
 * This method is defined as
1092
 * <a href="https://epsg.org/coord-operation-method_1028/index.html">
1093
 * EPSG:1028</a>.
1094
 *
1095
 * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
1096
 * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
1097
 * where the lat_0 / center_latitude parameter is forced to 0.
1098
 *
1099
 * @param properties See \ref general_properties of the conversion. If the name
1100
 * is not provided, it is automatically set.
1101
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
1102
 * @param longitudeNatOrigin See \ref center_longitude
1103
 * @param falseEasting See \ref false_easting
1104
 * @param falseNorthing See \ref false_northing
1105
 * @return a new Conversion.
1106
 */
1107
ConversionNNPtr Conversion::createEquidistantCylindrical(
1108
    const util::PropertyMap &properties,
1109
    const common::Angle &latitudeFirstParallel,
1110
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
1111
14
    const common::Length &falseNorthing) {
1112
14
    return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
1113
14
                  createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
1114
14
                               falseEasting, falseNorthing));
1115
14
}
1116
1117
// ---------------------------------------------------------------------------
1118
1119
/** \brief Instantiate a conversion based on the
1120
 * <a href="../../../operations/projections/eqc.html">
1121
 * Equidistant Cylindrical (Spherical)</a> projection method.
1122
 *
1123
 * This is also known as the Equirectangular method, and in the particular case
1124
 * where the latitude of first parallel is 0.
1125
 *
1126
 * This method is defined as
1127
 * <a href="https://epsg.org/coord-operation-method_1029/index.html">
1128
 * EPSG:1029</a>.
1129
 *
1130
 * @note This is the equivalent OGRSpatialReference::SetEquirectangular2(
1131
 * 0.0, latitudeFirstParallel, falseEasting, falseNorthing ) of GDAL &lt;= 2.3,
1132
 * where the lat_0 / center_latitude parameter is forced to 0.
1133
 *
1134
 * @param properties See \ref general_properties of the conversion. If the name
1135
 * is not provided, it is automatically set.
1136
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel.
1137
 * @param longitudeNatOrigin See \ref center_longitude
1138
 * @param falseEasting See \ref false_easting
1139
 * @param falseNorthing See \ref false_northing
1140
 * @return a new Conversion.
1141
 */
1142
ConversionNNPtr Conversion::createEquidistantCylindricalSpherical(
1143
    const util::PropertyMap &properties,
1144
    const common::Angle &latitudeFirstParallel,
1145
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
1146
0
    const common::Length &falseNorthing) {
1147
0
    return create(properties,
1148
0
                  EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL,
1149
0
                  createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
1150
0
                               falseEasting, falseNorthing));
1151
0
}
1152
1153
// ---------------------------------------------------------------------------
1154
1155
/** \brief Instantiate a conversion based on the
1156
 * <a href="../../../operations/projections/gall.html">
1157
 * Gall (Stereographic)</a> projection method.
1158
 *
1159
 * There is no equivalent in EPSG.
1160
 *
1161
 * @param properties See \ref general_properties of the conversion. If the name
1162
 * is not provided, it is automatically set.
1163
 * @param centerLong See \ref center_longitude
1164
 * @param falseEasting See \ref false_easting
1165
 * @param falseNorthing See \ref false_northing
1166
 * @return a new Conversion.
1167
 */
1168
ConversionNNPtr Conversion::createGall(const util::PropertyMap &properties,
1169
                                       const common::Angle &centerLong,
1170
                                       const common::Length &falseEasting,
1171
0
                                       const common::Length &falseNorthing) {
1172
0
    return create(properties, PROJ_WKT2_NAME_METHOD_GALL_STEREOGRAPHIC,
1173
0
                  createParams(centerLong, falseEasting, falseNorthing));
1174
0
}
1175
1176
// ---------------------------------------------------------------------------
1177
1178
/** \brief Instantiate a conversion based on the
1179
 * <a href="../../../operations/projections/goode.html">
1180
 * Goode Homolosine</a> projection method.
1181
 *
1182
 * There is no equivalent in EPSG.
1183
 *
1184
 * @param properties See \ref general_properties of the conversion. If the name
1185
 * is not provided, it is automatically set.
1186
 * @param centerLong See \ref center_longitude
1187
 * @param falseEasting See \ref false_easting
1188
 * @param falseNorthing See \ref false_northing
1189
 * @return a new Conversion.
1190
 */
1191
ConversionNNPtr Conversion::createGoodeHomolosine(
1192
    const util::PropertyMap &properties, const common::Angle &centerLong,
1193
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1194
0
    return create(properties, PROJ_WKT2_NAME_METHOD_GOODE_HOMOLOSINE,
1195
0
                  createParams(centerLong, falseEasting, falseNorthing));
1196
0
}
1197
1198
// ---------------------------------------------------------------------------
1199
1200
/** \brief Instantiate a conversion based on the
1201
 * <a href="../../../operations/projections/igh.html">
1202
 * Interrupted Goode Homolosine</a> projection method.
1203
 *
1204
 * There is no equivalent in EPSG.
1205
 *
1206
 * @note OGRSpatialReference::SetIGH() of GDAL &lt;= 2.3 assumes the 3
1207
 * projection
1208
 * parameters to be zero and this is the nominal case.
1209
 *
1210
 * @param properties See \ref general_properties of the conversion. If the name
1211
 * is not provided, it is automatically set.
1212
 * @param centerLong See \ref center_longitude
1213
 * @param falseEasting See \ref false_easting
1214
 * @param falseNorthing See \ref false_northing
1215
 * @return a new Conversion.
1216
 */
1217
ConversionNNPtr Conversion::createInterruptedGoodeHomolosine(
1218
    const util::PropertyMap &properties, const common::Angle &centerLong,
1219
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1220
0
    return create(properties,
1221
0
                  PROJ_WKT2_NAME_METHOD_INTERRUPTED_GOODE_HOMOLOSINE,
1222
0
                  createParams(centerLong, falseEasting, falseNorthing));
1223
0
}
1224
1225
// ---------------------------------------------------------------------------
1226
1227
/** \brief Instantiate a conversion based on the
1228
 * <a href="../../../operations/projections/geos.html">
1229
 * Geostationary Satellite View</a> projection method,
1230
 * with the sweep angle axis of the viewing instrument being x
1231
 *
1232
 * There is no equivalent in EPSG.
1233
 *
1234
 * @param properties See \ref general_properties of the conversion. If the name
1235
 * is not provided, it is automatically set.
1236
 * @param centerLong See \ref center_longitude
1237
 * @param height Height of the view point above the Earth.
1238
 * @param falseEasting See \ref false_easting
1239
 * @param falseNorthing See \ref false_northing
1240
 * @return a new Conversion.
1241
 */
1242
ConversionNNPtr Conversion::createGeostationarySatelliteSweepX(
1243
    const util::PropertyMap &properties, const common::Angle &centerLong,
1244
    const common::Length &height, const common::Length &falseEasting,
1245
0
    const common::Length &falseNorthing) {
1246
0
    return create(
1247
0
        properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X,
1248
0
        createParams(centerLong, height, falseEasting, falseNorthing));
1249
0
}
1250
1251
// ---------------------------------------------------------------------------
1252
1253
/** \brief Instantiate a conversion based on the
1254
 * <a href="../../../operations/projections/geos.html">
1255
 * Geostationary Satellite View</a> projection method,
1256
 * with the sweep angle axis of the viewing instrument being y.
1257
 *
1258
 * There is no equivalent in EPSG.
1259
 *
1260
 * @param properties See \ref general_properties of the conversion. If the name
1261
 * is not provided, it is automatically set.
1262
 * @param centerLong See \ref center_longitude
1263
 * @param height Height of the view point above the Earth.
1264
 * @param falseEasting See \ref false_easting
1265
 * @param falseNorthing See \ref false_northing
1266
 * @return a new Conversion.
1267
 */
1268
ConversionNNPtr Conversion::createGeostationarySatelliteSweepY(
1269
    const util::PropertyMap &properties, const common::Angle &centerLong,
1270
    const common::Length &height, const common::Length &falseEasting,
1271
0
    const common::Length &falseNorthing) {
1272
0
    return create(
1273
0
        properties, PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y,
1274
0
        createParams(centerLong, height, falseEasting, falseNorthing));
1275
0
}
1276
1277
// ---------------------------------------------------------------------------
1278
1279
/** \brief Instantiate a conversion based on the
1280
 * <a href="../../../operations/projections/gnom.html">
1281
 * Gnomonic</a> projection method.
1282
 *
1283
 * There is no equivalent in EPSG.
1284
 *
1285
 * @param properties See \ref general_properties of the conversion. If the name
1286
 * is not provided, it is automatically set.
1287
 * @param centerLat See \ref center_latitude
1288
 * @param centerLong See \ref center_longitude
1289
 * @param falseEasting See \ref false_easting
1290
 * @param falseNorthing See \ref false_northing
1291
 * @return a new Conversion.
1292
 */
1293
ConversionNNPtr Conversion::createGnomonic(
1294
    const util::PropertyMap &properties, const common::Angle &centerLat,
1295
    const common::Angle &centerLong, const common::Length &falseEasting,
1296
0
    const common::Length &falseNorthing) {
1297
0
    return create(
1298
0
        properties, PROJ_WKT2_NAME_METHOD_GNOMONIC,
1299
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1300
0
}
1301
1302
// ---------------------------------------------------------------------------
1303
1304
/** \brief Instantiate a conversion based on the
1305
 * <a href="../../../operations/projections/omerc.html">
1306
 * Hotine Oblique Mercator (Variant A)</a> projection method.
1307
 *
1308
 * This is the variant with the no_uoff parameter, which corresponds to
1309
 * GDAL &gt;=2.3 Hotine_Oblique_Mercator projection.
1310
 * In this variant, the false grid coordinates are
1311
 * defined at the intersection of the initial line and the aposphere (the
1312
 * equator on one of the intermediate surfaces inherent in the method), that is
1313
 * at the natural origin of the coordinate system).
1314
 *
1315
 * This method is defined as
1316
 * <a href="https://epsg.org/coord-operation-method_9812/index.html">
1317
 * EPSG:9812</a>.
1318
 *
1319
 * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
1320
 * 90deg, this maps to the
1321
 * <a href="../../../operations/projections/somerc.html">
1322
 * Swiss Oblique Mercator</a> formulas.
1323
 *
1324
 * @param properties See \ref general_properties of the conversion. If the name
1325
 * is not provided, it is automatically set.
1326
 * @param latitudeProjectionCentre See \ref latitude_projection_centre
1327
 * @param longitudeProjectionCentre See \ref longitude_projection_centre
1328
 * @param azimuthInitialLine See \ref azimuth_initial_line
1329
 * @param angleFromRectifiedToSkrewGrid See
1330
 * \ref angle_from_recitfied_to_skrew_grid
1331
 * @param scale See \ref scale_factor_initial_line
1332
 * @param falseEasting See \ref false_easting
1333
 * @param falseNorthing See \ref false_northing
1334
 * @return a new Conversion.
1335
 */
1336
ConversionNNPtr Conversion::createHotineObliqueMercatorVariantA(
1337
    const util::PropertyMap &properties,
1338
    const common::Angle &latitudeProjectionCentre,
1339
    const common::Angle &longitudeProjectionCentre,
1340
    const common::Angle &azimuthInitialLine,
1341
    const common::Angle &angleFromRectifiedToSkrewGrid,
1342
    const common::Scale &scale, const common::Length &falseEasting,
1343
0
    const common::Length &falseNorthing) {
1344
0
    return create(
1345
0
        properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A,
1346
0
        createParams(latitudeProjectionCentre, longitudeProjectionCentre,
1347
0
                     azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
1348
0
                     falseEasting, falseNorthing));
1349
0
}
1350
1351
// ---------------------------------------------------------------------------
1352
1353
/** \brief Instantiate a conversion based on the
1354
 * <a href="../../../operations/projections/omerc.html">
1355
 * Hotine Oblique Mercator (Variant B)</a> projection method.
1356
 *
1357
 * This is the variant without the no_uoff parameter, which corresponds to
1358
 * GDAL &gt;=2.3 Hotine_Oblique_Mercator_Azimuth_Center projection.
1359
 * In this variant, the false grid coordinates are defined at the projection
1360
 *centre.
1361
 *
1362
 * This method is defined as
1363
 * <a href="https://epsg.org/coord-operation-method_9815/index.html">
1364
 * EPSG:9815</a>.
1365
 *
1366
 * \note In the case where azimuthInitialLine = angleFromRectifiedToSkrewGrid =
1367
 * 90deg, this maps to the
1368
 * <a href="../../../operations/projections/somerc.html">
1369
 * Swiss Oblique Mercator</a> formulas.
1370
 *
1371
 * @param properties See \ref general_properties of the conversion. If the name
1372
 * is not provided, it is automatically set.
1373
 * @param latitudeProjectionCentre See \ref latitude_projection_centre
1374
 * @param longitudeProjectionCentre See \ref longitude_projection_centre
1375
 * @param azimuthInitialLine See \ref azimuth_initial_line
1376
 * @param angleFromRectifiedToSkrewGrid See
1377
 * \ref angle_from_recitfied_to_skrew_grid
1378
 * @param scale See \ref scale_factor_initial_line
1379
 * @param eastingProjectionCentre See \ref easting_projection_centre
1380
 * @param northingProjectionCentre See \ref northing_projection_centre
1381
 * @return a new Conversion.
1382
 */
1383
ConversionNNPtr Conversion::createHotineObliqueMercatorVariantB(
1384
    const util::PropertyMap &properties,
1385
    const common::Angle &latitudeProjectionCentre,
1386
    const common::Angle &longitudeProjectionCentre,
1387
    const common::Angle &azimuthInitialLine,
1388
    const common::Angle &angleFromRectifiedToSkrewGrid,
1389
    const common::Scale &scale, const common::Length &eastingProjectionCentre,
1390
0
    const common::Length &northingProjectionCentre) {
1391
0
    return create(
1392
0
        properties, EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B,
1393
0
        createParams(latitudeProjectionCentre, longitudeProjectionCentre,
1394
0
                     azimuthInitialLine, angleFromRectifiedToSkrewGrid, scale,
1395
0
                     eastingProjectionCentre, northingProjectionCentre));
1396
0
}
1397
1398
// ---------------------------------------------------------------------------
1399
1400
/** \brief Instantiate a conversion based on the
1401
 * <a href="../../../operations/projections/omerc.html">
1402
 * Hotine Oblique Mercator Two Point Natural Origin</a> projection method.
1403
 *
1404
 * There is no equivalent in EPSG.
1405
 *
1406
 * @param properties See \ref general_properties of the conversion. If the name
1407
 * is not provided, it is automatically set.
1408
 * @param latitudeProjectionCentre See \ref latitude_projection_centre
1409
 * @param latitudePoint1 Latitude of point 1.
1410
 * @param longitudePoint1 Latitude of point 1.
1411
 * @param latitudePoint2 Latitude of point 2.
1412
 * @param longitudePoint2 Longitude of point 2.
1413
 * @param scale See \ref scale_factor_initial_line
1414
 * @param eastingProjectionCentre See \ref easting_projection_centre
1415
 * @param northingProjectionCentre See \ref northing_projection_centre
1416
 * @return a new Conversion.
1417
 */
1418
ConversionNNPtr Conversion::createHotineObliqueMercatorTwoPointNaturalOrigin(
1419
    const util::PropertyMap &properties,
1420
    const common::Angle &latitudeProjectionCentre,
1421
    const common::Angle &latitudePoint1, const common::Angle &longitudePoint1,
1422
    const common::Angle &latitudePoint2, const common::Angle &longitudePoint2,
1423
    const common::Scale &scale, const common::Length &eastingProjectionCentre,
1424
0
    const common::Length &northingProjectionCentre) {
1425
0
    return create(
1426
0
        properties,
1427
0
        PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN,
1428
0
        {
1429
0
            ParameterValue::create(latitudeProjectionCentre),
1430
0
            ParameterValue::create(latitudePoint1),
1431
0
            ParameterValue::create(longitudePoint1),
1432
0
            ParameterValue::create(latitudePoint2),
1433
0
            ParameterValue::create(longitudePoint2),
1434
0
            ParameterValue::create(scale),
1435
0
            ParameterValue::create(eastingProjectionCentre),
1436
0
            ParameterValue::create(northingProjectionCentre),
1437
0
        });
1438
0
}
1439
1440
// ---------------------------------------------------------------------------
1441
1442
/** \brief Instantiate a conversion based on the
1443
 * <a href="../../../operations/projections/labrd.html">
1444
 * Laborde Oblique Mercator</a> projection method.
1445
 *
1446
 * This method is defined as
1447
 * <a href="https://epsg.org/coord-operation-method_9813/index.html">
1448
 * EPSG:9813</a>.
1449
 *
1450
 * @param properties See \ref general_properties of the conversion. If the name
1451
 * is not provided, it is automatically set.
1452
 * @param latitudeProjectionCentre See \ref latitude_projection_centre
1453
 * @param longitudeProjectionCentre See \ref longitude_projection_centre
1454
 * @param azimuthInitialLine See \ref azimuth_initial_line
1455
 * @param scale See \ref scale_factor_initial_line
1456
 * @param falseEasting See \ref false_easting
1457
 * @param falseNorthing See \ref false_northing
1458
 * @return a new Conversion.
1459
 */
1460
ConversionNNPtr Conversion::createLabordeObliqueMercator(
1461
    const util::PropertyMap &properties,
1462
    const common::Angle &latitudeProjectionCentre,
1463
    const common::Angle &longitudeProjectionCentre,
1464
    const common::Angle &azimuthInitialLine, const common::Scale &scale,
1465
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1466
0
    return create(properties, EPSG_CODE_METHOD_LABORDE_OBLIQUE_MERCATOR,
1467
0
                  createParams(latitudeProjectionCentre,
1468
0
                               longitudeProjectionCentre, azimuthInitialLine,
1469
0
                               scale, falseEasting, falseNorthing));
1470
0
}
1471
1472
// ---------------------------------------------------------------------------
1473
1474
/** \brief Instantiate a conversion based on the
1475
 * <a href="../../../operations/projections/imw_p.html">
1476
 * International Map of the World Polyconic</a> projection method.
1477
 *
1478
 * There is no equivalent in EPSG.
1479
 *
1480
 * @note the order of arguments is conformant with the corresponding EPSG
1481
 * mode and different than OGRSpatialReference::SetIWMPolyconic() of GDAL &lt;=
1482
 *2.3
1483
 *
1484
 * @param properties See \ref general_properties of the conversion. If the name
1485
 * is not provided, it is automatically set.
1486
 * @param centerLong See \ref center_longitude
1487
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
1488
 * @param latitudeSecondParallel See \ref latitude_second_std_parallel
1489
 * @param falseEasting See \ref false_easting
1490
 * @param falseNorthing See \ref false_northing
1491
 * @return a new Conversion.
1492
 */
1493
ConversionNNPtr Conversion::createInternationalMapWorldPolyconic(
1494
    const util::PropertyMap &properties, const common::Angle &centerLong,
1495
    const common::Angle &latitudeFirstParallel,
1496
    const common::Angle &latitudeSecondParallel,
1497
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1498
0
    return create(properties, PROJ_WKT2_NAME_INTERNATIONAL_MAP_WORLD_POLYCONIC,
1499
0
                  createParams(centerLong, latitudeFirstParallel,
1500
0
                               latitudeSecondParallel, falseEasting,
1501
0
                               falseNorthing));
1502
0
}
1503
1504
// ---------------------------------------------------------------------------
1505
1506
/** \brief Instantiate a conversion based on the
1507
 * <a href="../../../operations/projections/krovak.html">
1508
 * Krovak (north oriented)</a> projection method.
1509
 *
1510
 * This method is defined as
1511
 * <a href="https://epsg.org/coord-operation-method_1041/index.html">
1512
 * EPSG:1041</a>.
1513
 *
1514
 * The coordinates are returned in the "GIS friendly" order: easting, northing.
1515
 * This method is similar to createKrovak(), except that the later returns
1516
 * projected values as southing, westing, where
1517
 * southing(Krovak) = -northing(Krovak_North) and
1518
 * westing(Krovak) = -easting(Krovak_North).
1519
 *
1520
 * @note The current PROJ implementation of Krovak hard-codes
1521
 * colatitudeConeAxis = 30deg17'17.30311"
1522
 * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
1523
 * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
1524
 * It also hard-codes the parameters of the Bessel ellipsoid typically used for
1525
 * Krovak.
1526
 *
1527
 * @param properties See \ref general_properties of the conversion. If the name
1528
 * is not provided, it is automatically set.
1529
 * @param latitudeProjectionCentre See \ref latitude_projection_centre
1530
 * @param longitudeOfOrigin See \ref longitude_of_origin
1531
 * @param colatitudeConeAxis See \ref colatitude_cone_axis
1532
 * @param latitudePseudoStandardParallel See \ref
1533
 *latitude_pseudo_standard_parallel
1534
 * @param scaleFactorPseudoStandardParallel See \ref
1535
 *scale_factor_pseudo_standard_parallel
1536
 * @param falseEasting See \ref false_easting
1537
 * @param falseNorthing See \ref false_northing
1538
 * @return a new Conversion.
1539
 */
1540
ConversionNNPtr Conversion::createKrovakNorthOriented(
1541
    const util::PropertyMap &properties,
1542
    const common::Angle &latitudeProjectionCentre,
1543
    const common::Angle &longitudeOfOrigin,
1544
    const common::Angle &colatitudeConeAxis,
1545
    const common::Angle &latitudePseudoStandardParallel,
1546
    const common::Scale &scaleFactorPseudoStandardParallel,
1547
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1548
0
    return create(properties, EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED,
1549
0
                  createParams(latitudeProjectionCentre, longitudeOfOrigin,
1550
0
                               colatitudeConeAxis,
1551
0
                               latitudePseudoStandardParallel,
1552
0
                               scaleFactorPseudoStandardParallel, falseEasting,
1553
0
                               falseNorthing));
1554
0
}
1555
1556
// ---------------------------------------------------------------------------
1557
1558
/** \brief Instantiate a conversion based on the
1559
 * <a href="../../../operations/projections/krovak.html">
1560
 * Krovak</a> projection method.
1561
 *
1562
 * This method is defined as
1563
 * <a href="https://epsg.org/coord-operation-method_9819/index.html">
1564
 * EPSG:9819</a>.
1565
 *
1566
 * The coordinates are returned in the historical order: southing, westing
1567
 * This method is similar to createKrovakNorthOriented(), except that the later
1568
 *returns
1569
 * projected values as easting, northing, where
1570
 * easting(Krovak_North) = -westing(Krovak) and
1571
 * northing(Krovak_North) = -southing(Krovak).
1572
 *
1573
 * @note The current PROJ implementation of Krovak hard-codes
1574
 * colatitudeConeAxis = 30deg17'17.30311"
1575
 * and latitudePseudoStandardParallel = 78deg30'N, which are the values used for
1576
 * the ProjectedCRS S-JTSK (Ferro) / Krovak East North (EPSG:5221).
1577
 * It also hard-codes the parameters of the Bessel ellipsoid typically used for
1578
 * Krovak.
1579
 *
1580
 * @param properties See \ref general_properties of the conversion. If the name
1581
 * is not provided, it is automatically set.
1582
 * @param latitudeProjectionCentre See \ref latitude_projection_centre
1583
 * @param longitudeOfOrigin See \ref longitude_of_origin
1584
 * @param colatitudeConeAxis See \ref colatitude_cone_axis
1585
 * @param latitudePseudoStandardParallel See \ref
1586
 *latitude_pseudo_standard_parallel
1587
 * @param scaleFactorPseudoStandardParallel See \ref
1588
 *scale_factor_pseudo_standard_parallel
1589
 * @param falseEasting See \ref false_easting
1590
 * @param falseNorthing See \ref false_northing
1591
 * @return a new Conversion.
1592
 */
1593
ConversionNNPtr
1594
Conversion::createKrovak(const util::PropertyMap &properties,
1595
                         const common::Angle &latitudeProjectionCentre,
1596
                         const common::Angle &longitudeOfOrigin,
1597
                         const common::Angle &colatitudeConeAxis,
1598
                         const common::Angle &latitudePseudoStandardParallel,
1599
                         const common::Scale &scaleFactorPseudoStandardParallel,
1600
                         const common::Length &falseEasting,
1601
0
                         const common::Length &falseNorthing) {
1602
0
    return create(properties, EPSG_CODE_METHOD_KROVAK,
1603
0
                  createParams(latitudeProjectionCentre, longitudeOfOrigin,
1604
0
                               colatitudeConeAxis,
1605
0
                               latitudePseudoStandardParallel,
1606
0
                               scaleFactorPseudoStandardParallel, falseEasting,
1607
0
                               falseNorthing));
1608
0
}
1609
1610
// ---------------------------------------------------------------------------
1611
1612
/** \brief Instantiate a conversion based on the
1613
 * <a href="../../../operations/projections/laea.html">
1614
 * Lambert Azimuthal Equal Area</a> projection method.
1615
 *
1616
 * This method is defined as
1617
 * <a href="https://epsg.org/coord-operation-method_9820/index.html">
1618
 * EPSG:9820</a>.
1619
 *
1620
 * @param properties See \ref general_properties of the conversion. If the name
1621
 * is not provided, it is automatically set.
1622
 * @param latitudeNatOrigin See \ref center_latitude
1623
 * @param longitudeNatOrigin See \ref center_longitude
1624
 * @param falseEasting See \ref false_easting
1625
 * @param falseNorthing See \ref false_northing
1626
 * @return a new Conversion.
1627
 */
1628
ConversionNNPtr Conversion::createLambertAzimuthalEqualArea(
1629
    const util::PropertyMap &properties, const common::Angle &latitudeNatOrigin,
1630
    const common::Angle &longitudeNatOrigin, const common::Length &falseEasting,
1631
0
    const common::Length &falseNorthing) {
1632
0
    return create(properties, EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA,
1633
0
                  createParams(latitudeNatOrigin, longitudeNatOrigin,
1634
0
                               falseEasting, falseNorthing));
1635
0
}
1636
1637
// ---------------------------------------------------------------------------
1638
1639
/** \brief Instantiate a conversion based on the
1640
 * <a href="../../../operations/projections/mill.html">
1641
 * Miller Cylindrical</a> projection method.
1642
 *
1643
 * There is no equivalent in EPSG.
1644
 *
1645
 * @param properties See \ref general_properties of the conversion. If the name
1646
 * is not provided, it is automatically set.
1647
 * @param centerLong See \ref center_longitude
1648
 * @param falseEasting See \ref false_easting
1649
 * @param falseNorthing See \ref false_northing
1650
 * @return a new Conversion.
1651
 */
1652
ConversionNNPtr Conversion::createMillerCylindrical(
1653
    const util::PropertyMap &properties, const common::Angle &centerLong,
1654
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1655
0
    return create(properties, PROJ_WKT2_NAME_METHOD_MILLER_CYLINDRICAL,
1656
0
                  createParams(centerLong, falseEasting, falseNorthing));
1657
0
}
1658
1659
// ---------------------------------------------------------------------------
1660
1661
/** \brief Instantiate a conversion based on the
1662
 * <a href="../../../operations/projections/merc.html">
1663
 * Mercator (variant A)</a> projection method.
1664
 *
1665
 * This is the A variant, also known as Mercator (1SP), defined with the scale
1666
 * factor. Note that latitude of natural origin (centerLat) is a parameter,
1667
 * but unused in the transformation formulas.
1668
 *
1669
 * This method is defined as
1670
 * <a href="https://epsg.org/coord-operation-method_9804/index.html">
1671
 * EPSG:9804</a>.
1672
 *
1673
 * @param properties See \ref general_properties of the conversion. If the name
1674
 * is not provided, it is automatically set.
1675
 * @param centerLat See \ref center_latitude . Should be 0.
1676
 * @param centerLong See \ref center_longitude
1677
 * @param scale See \ref scale
1678
 * @param falseEasting See \ref false_easting
1679
 * @param falseNorthing See \ref false_northing
1680
 * @return a new Conversion.
1681
 */
1682
ConversionNNPtr Conversion::createMercatorVariantA(
1683
    const util::PropertyMap &properties, const common::Angle &centerLat,
1684
    const common::Angle &centerLong, const common::Scale &scale,
1685
1
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1686
1
    return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_A,
1687
1
                  createParams(centerLat, centerLong, scale, falseEasting,
1688
1
                               falseNorthing));
1689
1
}
1690
1691
// ---------------------------------------------------------------------------
1692
1693
/** \brief Instantiate a conversion based on the
1694
 * <a href="../../../operations/projections/merc.html">
1695
 * Mercator (variant B)</a> projection method.
1696
 *
1697
 * This is the B variant, also known as Mercator (2SP), defined with the
1698
 * latitude of the first standard parallel (the second standard parallel is
1699
 * implicitly the opposite value). The latitude of natural origin is fixed to
1700
 * zero.
1701
 *
1702
 * This method is defined as
1703
 * <a href="https://epsg.org/coord-operation-method_9805/index.html">
1704
 * EPSG:9805</a>.
1705
 *
1706
 * @param properties See \ref general_properties of the conversion. If the name
1707
 * is not provided, it is automatically set.
1708
 * @param latitudeFirstParallel See \ref latitude_first_std_parallel
1709
 * @param centerLong See \ref center_longitude
1710
 * @param falseEasting See \ref false_easting
1711
 * @param falseNorthing See \ref false_northing
1712
 * @return a new Conversion.
1713
 */
1714
ConversionNNPtr Conversion::createMercatorVariantB(
1715
    const util::PropertyMap &properties,
1716
    const common::Angle &latitudeFirstParallel, const common::Angle &centerLong,
1717
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1718
0
    return create(properties, EPSG_CODE_METHOD_MERCATOR_VARIANT_B,
1719
0
                  createParams(latitudeFirstParallel, centerLong, falseEasting,
1720
0
                               falseNorthing));
1721
0
}
1722
1723
// ---------------------------------------------------------------------------
1724
1725
/** \brief Instantiate a conversion based on the
1726
 * <a href="../../../operations/projections/webmerc.html">
1727
 * Popular Visualisation Pseudo Mercator</a> projection method.
1728
 *
1729
 * Also known as WebMercator. Mostly/only used for Projected CRS EPSG:3857
1730
 * (WGS 84 / Pseudo-Mercator)
1731
 *
1732
 * This method is defined as
1733
 * <a href="https://epsg.org/coord-operation-method_1024/index.html">
1734
 * EPSG:1024</a>.
1735
 *
1736
 * @param properties See \ref general_properties of the conversion. If the name
1737
 * is not provided, it is automatically set.
1738
 * @param centerLat See \ref center_latitude . Usually 0
1739
 * @param centerLong See \ref center_longitude . Usually 0
1740
 * @param falseEasting See \ref false_easting . Usually 0
1741
 * @param falseNorthing See \ref false_northing . Usually 0
1742
 * @return a new Conversion.
1743
 */
1744
ConversionNNPtr Conversion::createPopularVisualisationPseudoMercator(
1745
    const util::PropertyMap &properties, const common::Angle &centerLat,
1746
    const common::Angle &centerLong, const common::Length &falseEasting,
1747
0
    const common::Length &falseNorthing) {
1748
0
    return create(
1749
0
        properties, EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR,
1750
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1751
0
}
1752
1753
// ---------------------------------------------------------------------------
1754
1755
/** \brief Instantiate a conversion based on the
1756
 * <a href="../../../operations/projections/merc.html">
1757
 * Mercator</a> projection method, using its spherical formulation
1758
 *
1759
 * When used with an ellipsoid, the radius used is the radius of the conformal
1760
 * sphere at centerLat.
1761
 *
1762
 * This method is defined as
1763
 * <a href="https://epsg.org/coord-operation-method_1026/Mercator-Spherical.html">
1764
 * EPSG:1026</a>.
1765
 *
1766
 * @param properties See \ref general_properties of the conversion. If the name
1767
 * is not provided, it is automatically set.
1768
 * @param centerLat See \ref center_latitude . Usually 0
1769
 * @param centerLong See \ref center_longitude . Usually 0
1770
 * @param falseEasting See \ref false_easting . Usually 0
1771
 * @param falseNorthing See \ref false_northing . Usually 0
1772
 * @return a new Conversion.
1773
 * @since 9.3
1774
 */
1775
ConversionNNPtr Conversion::createMercatorSpherical(
1776
    const util::PropertyMap &properties, const common::Angle &centerLat,
1777
    const common::Angle &centerLong, const common::Length &falseEasting,
1778
0
    const common::Length &falseNorthing) {
1779
0
    return create(
1780
0
        properties, EPSG_CODE_METHOD_MERCATOR_SPHERICAL,
1781
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1782
0
}
1783
1784
// ---------------------------------------------------------------------------
1785
1786
/** \brief Instantiate a conversion based on the
1787
 * <a href="../../../operations/projections/moll.html">
1788
 * Mollweide</a> projection method.
1789
 *
1790
 * There is no equivalent in EPSG.
1791
 *
1792
 * @param properties See \ref general_properties of the conversion. If the name
1793
 * is not provided, it is automatically set.
1794
 * @param centerLong See \ref center_longitude
1795
 * @param falseEasting See \ref false_easting
1796
 * @param falseNorthing See \ref false_northing
1797
 * @return a new Conversion.
1798
 */
1799
ConversionNNPtr Conversion::createMollweide(
1800
    const util::PropertyMap &properties, const common::Angle &centerLong,
1801
56
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1802
56
    return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE,
1803
56
                  createParams(centerLong, falseEasting, falseNorthing));
1804
56
}
1805
1806
// ---------------------------------------------------------------------------
1807
1808
/** \brief Instantiate a conversion based on the
1809
 * <a href="../../../operations/projections/nzmg.html">
1810
 * New Zealand Map Grid</a> projection method.
1811
 *
1812
 * This method is defined as
1813
 * <a href="https://epsg.org/coord-operation-method_9811/index.html">
1814
 * EPSG:9811</a>.
1815
 *
1816
 * @param properties See \ref general_properties of the conversion. If the name
1817
 * is not provided, it is automatically set.
1818
 * @param centerLat See \ref center_latitude
1819
 * @param centerLong See \ref center_longitude
1820
 * @param falseEasting See \ref false_easting
1821
 * @param falseNorthing See \ref false_northing
1822
 * @return a new Conversion.
1823
 */
1824
ConversionNNPtr Conversion::createNewZealandMappingGrid(
1825
    const util::PropertyMap &properties, const common::Angle &centerLat,
1826
    const common::Angle &centerLong, const common::Length &falseEasting,
1827
0
    const common::Length &falseNorthing) {
1828
0
    return create(
1829
0
        properties, EPSG_CODE_METHOD_NZMG,
1830
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1831
0
}
1832
1833
// ---------------------------------------------------------------------------
1834
1835
/** \brief Instantiate a conversion based on the
1836
 * <a href="../../../operations/projections/sterea.html">
1837
 * Oblique Stereographic (alternative)</a> projection method.
1838
 *
1839
 * This method is defined as
1840
 * <a href="https://epsg.org/coord-operation-method_9809/index.html">
1841
 * EPSG:9809</a>.
1842
 *
1843
 * @param properties See \ref general_properties of the conversion. If the name
1844
 * is not provided, it is automatically set.
1845
 * @param centerLat See \ref center_latitude
1846
 * @param centerLong See \ref center_longitude
1847
 * @param scale See \ref scale
1848
 * @param falseEasting See \ref false_easting
1849
 * @param falseNorthing See \ref false_northing
1850
 * @return a new Conversion.
1851
 */
1852
ConversionNNPtr Conversion::createObliqueStereographic(
1853
    const util::PropertyMap &properties, const common::Angle &centerLat,
1854
    const common::Angle &centerLong, const common::Scale &scale,
1855
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1856
0
    return create(properties, EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC,
1857
0
                  createParams(centerLat, centerLong, scale, falseEasting,
1858
0
                               falseNorthing));
1859
0
}
1860
1861
// ---------------------------------------------------------------------------
1862
1863
/** \brief Instantiate a conversion based on the
1864
 * <a href="../../../operations/projections/ortho.html">
1865
 * Orthographic</a> projection method.
1866
 *
1867
 * This method is defined as
1868
 * <a href="https://epsg.org/coord-operation-method_9840/index.html">
1869
 * EPSG:9840</a>.
1870
 *
1871
 * \note Before PROJ 7.2, only the spherical formulation was implemented.
1872
 *
1873
 * @param properties See \ref general_properties of the conversion. If the name
1874
 * is not provided, it is automatically set.
1875
 * @param centerLat See \ref center_latitude
1876
 * @param centerLong See \ref center_longitude
1877
 * @param falseEasting See \ref false_easting
1878
 * @param falseNorthing See \ref false_northing
1879
 * @return a new Conversion.
1880
 */
1881
ConversionNNPtr Conversion::createOrthographic(
1882
    const util::PropertyMap &properties, const common::Angle &centerLat,
1883
    const common::Angle &centerLong, const common::Length &falseEasting,
1884
5
    const common::Length &falseNorthing) {
1885
5
    return create(
1886
5
        properties, EPSG_CODE_METHOD_ORTHOGRAPHIC,
1887
5
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1888
5
}
1889
1890
// ---------------------------------------------------------------------------
1891
1892
/** \brief Instantiate a conversion based on the
1893
 * <a href="../../../operations/projections/poly.html">
1894
 * American Polyconic</a> projection method.
1895
 *
1896
 * This method is defined as
1897
 * <a href="https://epsg.org/coord-operation-method_9818/index.html">
1898
 * EPSG:9818</a>.
1899
 *
1900
 * @param properties See \ref general_properties of the conversion. If the name
1901
 * is not provided, it is automatically set.
1902
 * @param centerLat See \ref center_latitude
1903
 * @param centerLong See \ref center_longitude
1904
 * @param falseEasting See \ref false_easting
1905
 * @param falseNorthing See \ref false_northing
1906
 * @return a new Conversion.
1907
 */
1908
ConversionNNPtr Conversion::createAmericanPolyconic(
1909
    const util::PropertyMap &properties, const common::Angle &centerLat,
1910
    const common::Angle &centerLong, const common::Length &falseEasting,
1911
0
    const common::Length &falseNorthing) {
1912
0
    return create(
1913
0
        properties, EPSG_CODE_METHOD_AMERICAN_POLYCONIC,
1914
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1915
0
}
1916
1917
// ---------------------------------------------------------------------------
1918
1919
/** \brief Instantiate a conversion based on the
1920
 * <a href="../../../operations/projections/stere.html">
1921
 * Polar Stereographic (Variant A)</a> projection method.
1922
 *
1923
 * This method is defined as
1924
 * <a href="https://epsg.org/coord-operation-method_9810/index.html">
1925
 * EPSG:9810</a>.
1926
 *
1927
 * This is the variant of polar stereographic defined with a scale factor.
1928
 *
1929
 * @param properties See \ref general_properties of the conversion. If the name
1930
 * is not provided, it is automatically set.
1931
 * @param centerLat See \ref center_latitude . Should be 90 deg ou -90 deg.
1932
 * @param centerLong See \ref center_longitude
1933
 * @param scale See \ref scale
1934
 * @param falseEasting See \ref false_easting
1935
 * @param falseNorthing See \ref false_northing
1936
 * @return a new Conversion.
1937
 */
1938
ConversionNNPtr Conversion::createPolarStereographicVariantA(
1939
    const util::PropertyMap &properties, const common::Angle &centerLat,
1940
    const common::Angle &centerLong, const common::Scale &scale,
1941
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1942
0
    return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A,
1943
0
                  createParams(centerLat, centerLong, scale, falseEasting,
1944
0
                               falseNorthing));
1945
0
}
1946
1947
// ---------------------------------------------------------------------------
1948
1949
/** \brief Instantiate a conversion based on the
1950
 * <a href="../../../operations/projections/stere.html">
1951
 * Polar Stereographic (Variant B)</a> projection method.
1952
 *
1953
 * This method is defined as
1954
 * <a href="https://epsg.org/coord-operation-method_9829/index.html">
1955
 * EPSG:9829</a>.
1956
 *
1957
 * This is the variant of polar stereographic defined with a latitude of
1958
 * standard parallel.
1959
 *
1960
 * @param properties See \ref general_properties of the conversion. If the name
1961
 * is not provided, it is automatically set.
1962
 * @param latitudeStandardParallel See \ref latitude_std_parallel
1963
 * @param longitudeOfOrigin See \ref longitude_of_origin
1964
 * @param falseEasting See \ref false_easting
1965
 * @param falseNorthing See \ref false_northing
1966
 * @return a new Conversion.
1967
 */
1968
ConversionNNPtr Conversion::createPolarStereographicVariantB(
1969
    const util::PropertyMap &properties,
1970
    const common::Angle &latitudeStandardParallel,
1971
    const common::Angle &longitudeOfOrigin, const common::Length &falseEasting,
1972
6
    const common::Length &falseNorthing) {
1973
6
    return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
1974
6
                  createParams(latitudeStandardParallel, longitudeOfOrigin,
1975
6
                               falseEasting, falseNorthing));
1976
6
}
1977
1978
// ---------------------------------------------------------------------------
1979
1980
/** \brief Instantiate a conversion based on the
1981
 * <a href="../../../operations/projections/robin.html">
1982
 * Robinson</a> projection method.
1983
 *
1984
 * There is no equivalent in EPSG.
1985
 *
1986
 * @param properties See \ref general_properties of the conversion. If the name
1987
 * is not provided, it is automatically set.
1988
 * @param centerLong See \ref center_longitude
1989
 * @param falseEasting See \ref false_easting
1990
 * @param falseNorthing See \ref false_northing
1991
 * @return a new Conversion.
1992
 */
1993
ConversionNNPtr Conversion::createRobinson(
1994
    const util::PropertyMap &properties, const common::Angle &centerLong,
1995
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1996
0
    return create(properties, PROJ_WKT2_NAME_METHOD_ROBINSON,
1997
0
                  createParams(centerLong, falseEasting, falseNorthing));
1998
0
}
1999
2000
// ---------------------------------------------------------------------------
2001
2002
/** \brief Instantiate a conversion based on the
2003
 * <a href="../../../operations/projections/sinu.html">
2004
 * Sinusoidal</a> projection method.
2005
 *
2006
 * There is no equivalent in EPSG.
2007
 *
2008
 * @param properties See \ref general_properties of the conversion. If the name
2009
 * is not provided, it is automatically set.
2010
 * @param centerLong See \ref center_longitude
2011
 * @param falseEasting See \ref false_easting
2012
 * @param falseNorthing See \ref false_northing
2013
 * @return a new Conversion.
2014
 */
2015
ConversionNNPtr Conversion::createSinusoidal(
2016
    const util::PropertyMap &properties, const common::Angle &centerLong,
2017
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2018
0
    return create(properties, PROJ_WKT2_NAME_METHOD_SINUSOIDAL,
2019
0
                  createParams(centerLong, falseEasting, falseNorthing));
2020
0
}
2021
2022
// ---------------------------------------------------------------------------
2023
2024
/** \brief Instantiate a conversion based on the
2025
 * <a href="../../../operations/projections/stere.html">
2026
 * Stereographic</a> projection method.
2027
 *
2028
 * There is no equivalent in EPSG. This method implements the original "Oblique
2029
 * Stereographic" method described in "Snyder's Map Projections - A Working
2030
 *manual",
2031
 * which is different from the "Oblique Stereographic (alternative)" method
2032
 * implemented in createObliqueStereographic().
2033
 *
2034
 * @param properties See \ref general_properties of the conversion. If the name
2035
 * is not provided, it is automatically set.
2036
 * @param centerLat See \ref center_latitude
2037
 * @param centerLong See \ref center_longitude
2038
 * @param scale See \ref scale
2039
 * @param falseEasting See \ref false_easting
2040
 * @param falseNorthing See \ref false_northing
2041
 * @return a new Conversion.
2042
 */
2043
ConversionNNPtr Conversion::createStereographic(
2044
    const util::PropertyMap &properties, const common::Angle &centerLat,
2045
    const common::Angle &centerLong, const common::Scale &scale,
2046
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2047
0
    return create(properties, PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC,
2048
0
                  createParams(centerLat, centerLong, scale, falseEasting,
2049
0
                               falseNorthing));
2050
0
}
2051
2052
// ---------------------------------------------------------------------------
2053
2054
/** \brief Instantiate a conversion based on the
2055
 * <a href="../../../operations/projections/vandg.html">
2056
 * Van der Grinten</a> projection method.
2057
 *
2058
 * There is no equivalent in EPSG.
2059
 *
2060
 * @param properties See \ref general_properties of the conversion. If the name
2061
 * is not provided, it is automatically set.
2062
 * @param centerLong See \ref center_longitude
2063
 * @param falseEasting See \ref false_easting
2064
 * @param falseNorthing See \ref false_northing
2065
 * @return a new Conversion.
2066
 */
2067
ConversionNNPtr Conversion::createVanDerGrinten(
2068
    const util::PropertyMap &properties, const common::Angle &centerLong,
2069
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2070
0
    return create(properties, PROJ_WKT2_NAME_METHOD_VAN_DER_GRINTEN,
2071
0
                  createParams(centerLong, falseEasting, falseNorthing));
2072
0
}
2073
2074
// ---------------------------------------------------------------------------
2075
2076
/** \brief Instantiate a conversion based on the
2077
 * <a href="../../../operations/projections/wag1.html">
2078
 * Wagner I</a> projection method.
2079
 *
2080
 * There is no equivalent in EPSG.
2081
 *
2082
 * @param properties See \ref general_properties of the conversion. If the name
2083
 * is not provided, it is automatically set.
2084
 * @param centerLong See \ref center_longitude
2085
 * @param falseEasting See \ref false_easting
2086
 * @param falseNorthing See \ref false_northing
2087
 * @return a new Conversion.
2088
 */
2089
ConversionNNPtr Conversion::createWagnerI(const util::PropertyMap &properties,
2090
                                          const common::Angle &centerLong,
2091
                                          const common::Length &falseEasting,
2092
0
                                          const common::Length &falseNorthing) {
2093
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_I,
2094
0
                  createParams(centerLong, falseEasting, falseNorthing));
2095
0
}
2096
2097
// ---------------------------------------------------------------------------
2098
2099
/** \brief Instantiate a conversion based on the
2100
 * <a href="../../../operations/projections/wag2.html">
2101
 * Wagner II</a> projection method.
2102
 *
2103
 * There is no equivalent in EPSG.
2104
 *
2105
 * @param properties See \ref general_properties of the conversion. If the name
2106
 * is not provided, it is automatically set.
2107
 * @param centerLong See \ref center_longitude
2108
 * @param falseEasting See \ref false_easting
2109
 * @param falseNorthing See \ref false_northing
2110
 * @return a new Conversion.
2111
 */
2112
ConversionNNPtr Conversion::createWagnerII(
2113
    const util::PropertyMap &properties, const common::Angle &centerLong,
2114
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2115
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_II,
2116
0
                  createParams(centerLong, falseEasting, falseNorthing));
2117
0
}
2118
2119
// ---------------------------------------------------------------------------
2120
2121
/** \brief Instantiate a conversion based on the
2122
 * <a href="../../../operations/projections/wag3.html">
2123
 * Wagner III</a> projection method.
2124
 *
2125
 * There is no equivalent in EPSG.
2126
 *
2127
 * @param properties See \ref general_properties of the conversion. If the name
2128
 * is not provided, it is automatically set.
2129
 * @param latitudeTrueScale Latitude of true scale.
2130
 * @param centerLong See \ref center_longitude
2131
 * @param falseEasting See \ref false_easting
2132
 * @param falseNorthing See \ref false_northing
2133
 * @return a new Conversion.
2134
 */
2135
ConversionNNPtr Conversion::createWagnerIII(
2136
    const util::PropertyMap &properties, const common::Angle &latitudeTrueScale,
2137
    const common::Angle &centerLong, const common::Length &falseEasting,
2138
0
    const common::Length &falseNorthing) {
2139
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_III,
2140
0
                  createParams(latitudeTrueScale, centerLong, falseEasting,
2141
0
                               falseNorthing));
2142
0
}
2143
2144
// ---------------------------------------------------------------------------
2145
2146
/** \brief Instantiate a conversion based on the
2147
 * <a href="../../../operations/projections/wag4.html">
2148
 * Wagner IV</a> projection method.
2149
 *
2150
 * There is no equivalent in EPSG.
2151
 *
2152
 * @param properties See \ref general_properties of the conversion. If the name
2153
 * is not provided, it is automatically set.
2154
 * @param centerLong See \ref center_longitude
2155
 * @param falseEasting See \ref false_easting
2156
 * @param falseNorthing See \ref false_northing
2157
 * @return a new Conversion.
2158
 */
2159
ConversionNNPtr Conversion::createWagnerIV(
2160
    const util::PropertyMap &properties, const common::Angle &centerLong,
2161
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2162
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_IV,
2163
0
                  createParams(centerLong, falseEasting, falseNorthing));
2164
0
}
2165
2166
// ---------------------------------------------------------------------------
2167
2168
/** \brief Instantiate a conversion based on the
2169
 * <a href="../../../operations/projections/wag5.html">
2170
 * Wagner V</a> projection method.
2171
 *
2172
 * There is no equivalent in EPSG.
2173
 *
2174
 * @param properties See \ref general_properties of the conversion. If the name
2175
 * is not provided, it is automatically set.
2176
 * @param centerLong See \ref center_longitude
2177
 * @param falseEasting See \ref false_easting
2178
 * @param falseNorthing See \ref false_northing
2179
 * @return a new Conversion.
2180
 */
2181
ConversionNNPtr Conversion::createWagnerV(const util::PropertyMap &properties,
2182
                                          const common::Angle &centerLong,
2183
                                          const common::Length &falseEasting,
2184
0
                                          const common::Length &falseNorthing) {
2185
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_V,
2186
0
                  createParams(centerLong, falseEasting, falseNorthing));
2187
0
}
2188
2189
// ---------------------------------------------------------------------------
2190
2191
/** \brief Instantiate a conversion based on the
2192
 * <a href="../../../operations/projections/wag6.html">
2193
 * Wagner VI</a> projection method.
2194
 *
2195
 * There is no equivalent in EPSG.
2196
 *
2197
 * @param properties See \ref general_properties of the conversion. If the name
2198
 * is not provided, it is automatically set.
2199
 * @param centerLong See \ref center_longitude
2200
 * @param falseEasting See \ref false_easting
2201
 * @param falseNorthing See \ref false_northing
2202
 * @return a new Conversion.
2203
 */
2204
ConversionNNPtr Conversion::createWagnerVI(
2205
    const util::PropertyMap &properties, const common::Angle &centerLong,
2206
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2207
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VI,
2208
0
                  createParams(centerLong, falseEasting, falseNorthing));
2209
0
}
2210
2211
// ---------------------------------------------------------------------------
2212
2213
/** \brief Instantiate a conversion based on the
2214
 * <a href="../../../operations/projections/wag7.html">
2215
 * Wagner VII</a> projection method.
2216
 *
2217
 * There is no equivalent in EPSG.
2218
 *
2219
 * @param properties See \ref general_properties of the conversion. If the name
2220
 * is not provided, it is automatically set.
2221
 * @param centerLong See \ref center_longitude
2222
 * @param falseEasting See \ref false_easting
2223
 * @param falseNorthing See \ref false_northing
2224
 * @return a new Conversion.
2225
 */
2226
ConversionNNPtr Conversion::createWagnerVII(
2227
    const util::PropertyMap &properties, const common::Angle &centerLong,
2228
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2229
0
    return create(properties, PROJ_WKT2_NAME_METHOD_WAGNER_VII,
2230
0
                  createParams(centerLong, falseEasting, falseNorthing));
2231
0
}
2232
2233
// ---------------------------------------------------------------------------
2234
2235
/** \brief Instantiate a conversion based on the
2236
 * <a href="../../../operations/projections/qsc.html">
2237
 * Quadrilateralized Spherical Cube</a> projection method.
2238
 *
2239
 * There is no equivalent in EPSG.
2240
 *
2241
 * @param properties See \ref general_properties of the conversion. If the name
2242
 * is not provided, it is automatically set.
2243
 * @param centerLat See \ref center_latitude
2244
 * @param centerLong See \ref center_longitude
2245
 * @param falseEasting See \ref false_easting
2246
 * @param falseNorthing See \ref false_northing
2247
 * @return a new Conversion.
2248
 */
2249
ConversionNNPtr Conversion::createQuadrilateralizedSphericalCube(
2250
    const util::PropertyMap &properties, const common::Angle &centerLat,
2251
    const common::Angle &centerLong, const common::Length &falseEasting,
2252
0
    const common::Length &falseNorthing) {
2253
0
    return create(
2254
0
        properties, PROJ_WKT2_NAME_METHOD_QUADRILATERALIZED_SPHERICAL_CUBE,
2255
0
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
2256
0
}
2257
2258
// ---------------------------------------------------------------------------
2259
2260
/** \brief Instantiate a conversion based on the
2261
 * <a href="../../../operations/projections/sch.html">
2262
 * Spherical Cross-Track Height</a> projection method.
2263
 *
2264
 * There is no equivalent in EPSG.
2265
 *
2266
 * @param properties See \ref general_properties of the conversion. If the name
2267
 * is not provided, it is automatically set.
2268
 * @param pegPointLat Peg point latitude.
2269
 * @param pegPointLong Peg point longitude.
2270
 * @param pegPointHeading Peg point heading.
2271
 * @param pegPointHeight Peg point height.
2272
 * @return a new Conversion.
2273
 */
2274
ConversionNNPtr Conversion::createSphericalCrossTrackHeight(
2275
    const util::PropertyMap &properties, const common::Angle &pegPointLat,
2276
    const common::Angle &pegPointLong, const common::Angle &pegPointHeading,
2277
0
    const common::Length &pegPointHeight) {
2278
0
    return create(properties,
2279
0
                  PROJ_WKT2_NAME_METHOD_SPHERICAL_CROSS_TRACK_HEIGHT,
2280
0
                  createParams(pegPointLat, pegPointLong, pegPointHeading,
2281
0
                               pegPointHeight));
2282
0
}
2283
2284
// ---------------------------------------------------------------------------
2285
2286
/** \brief Instantiate a conversion based on the
2287
 * <a href="../../../operations/projections/eqearth.html">
2288
 * Equal Earth</a> projection method.
2289
 *
2290
 * This method is defined as
2291
 * <a href="https://epsg.org/coord-operation-method_1078/Equal-Earth.html">
2292
 * EPSG:1078</a>.
2293
 *
2294
 * @param properties See \ref general_properties of the conversion. If the name
2295
 * is not provided, it is automatically set.
2296
 * @param centerLong See \ref center_longitude
2297
 * @param falseEasting See \ref false_easting
2298
 * @param falseNorthing See \ref false_northing
2299
 * @return a new Conversion.
2300
 */
2301
ConversionNNPtr Conversion::createEqualEarth(
2302
    const util::PropertyMap &properties, const common::Angle &centerLong,
2303
0
    const common::Length &falseEasting, const common::Length &falseNorthing) {
2304
0
    return create(properties, EPSG_CODE_METHOD_EQUAL_EARTH,
2305
0
                  createParams(centerLong, falseEasting, falseNorthing));
2306
0
}
2307
2308
// ---------------------------------------------------------------------------
2309
2310
/** \brief Instantiate a conversion based on the
2311
 * <a href="../../../operations/projections/nsper.html">
2312
 * Vertical Perspective</a> projection method.
2313
 *
2314
 * This method is defined as
2315
 * <a href="https://epsg.org/coord-operation-method_9838/index.html">
2316
 * EPSG:9838</a>.
2317
 *
2318
 * The PROJ implementation of the EPSG Vertical Perspective has the current
2319
 * limitations with respect to the method described in EPSG:
2320
 * <ul>
2321
 * <li> it is a 2D-only method, ignoring the ellipsoidal height of the point to
2322
 *      project.</li>
2323
 * <li> it has only a spherical development.</li>
2324
 * <li> the height of the topocentric origin is ignored, and thus assumed to be
2325
 * 0.</li>
2326
 * </ul>
2327
 *
2328
 * For completeness, PROJ adds the falseEasting and falseNorthing parameter,
2329
 * which are not described in EPSG. They should usually be set to 0.
2330
 *
2331
 * @param properties See \ref general_properties of the conversion. If the name
2332
 * is not provided, it is automatically set.
2333
 * @param topoOriginLat Latitude of topocentric origin
2334
 * @param topoOriginLong Longitude of topocentric origin
2335
 * @param topoOriginHeight Ellipsoidal height of topocentric origin. Ignored by
2336
 * PROJ (that is assumed to be 0)
2337
 * @param viewPointHeight Viewpoint height with respect to the
2338
 * topocentric/mapping plane. In the case where topoOriginHeight = 0, this is
2339
 * the height above the ellipsoid surface at topoOriginLat, topoOriginLong.
2340
 * @param falseEasting See \ref false_easting . (not in EPSG)
2341
 * @param falseNorthing See \ref false_northing . (not in EPSG)
2342
 * @return a new Conversion.
2343
 *
2344
 * @since 6.3
2345
 */
2346
ConversionNNPtr Conversion::createVerticalPerspective(
2347
    const util::PropertyMap &properties, const common::Angle &topoOriginLat,
2348
    const common::Angle &topoOriginLong, const common::Length &topoOriginHeight,
2349
    const common::Length &viewPointHeight, const common::Length &falseEasting,
2350
0
    const common::Length &falseNorthing) {
2351
0
    return create(properties, EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE,
2352
0
                  createParams(topoOriginLat, topoOriginLong, topoOriginHeight,
2353
0
                               viewPointHeight, falseEasting, falseNorthing));
2354
0
}
2355
2356
// ---------------------------------------------------------------------------
2357
2358
/** \brief Instantiate a conversion based on the Pole Rotation method, using
2359
 * the conventions of the GRIB 1 and GRIB 2 data formats.
2360
 *
2361
 * Those are mentioned in the Note 2 of
2362
 * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml
2363
 *
2364
 * Several conventions for the pole rotation method exists.
2365
 * The parameters provided in this method are remapped to the PROJ ob_tran
2366
 * operation with:
2367
 * <pre>
2368
 * +proj=ob_tran +o_proj=longlat +o_lon_p=-rotationAngle
2369
 *                               +o_lat_p=-southPoleLatInUnrotatedCRS
2370
 *                               +lon_0=southPoleLongInUnrotatedCRS
2371
 * </pre>
2372
 *
2373
 * Another implementation of that convention is also in the netcdf-java library:
2374
 * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java
2375
 *
2376
 * The PROJ implementation of this method assumes a spherical ellipsoid.
2377
 *
2378
 * @param properties See \ref general_properties of the conversion. If the name
2379
 * is not provided, it is automatically set.
2380
 * @param southPoleLatInUnrotatedCRS Latitude of the point from the unrotated
2381
 * CRS, expressed in the unrotated CRS, that will become the south pole of the
2382
 * rotated CRS.
2383
 * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated
2384
 * CRS, expressed in the unrotated CRS, that will become the south pole of the
2385
 * rotated CRS.
2386
 * @param axisRotation The angle of rotation about the new polar
2387
 * axis (measured clockwise when looking from the southern to the northern pole)
2388
 * of the coordinate system, assuming the new axis to have been obtained by
2389
 * first rotating the sphere through southPoleLongInUnrotatedCRS degrees about
2390
 * the geographic polar axis and then rotating through
2391
 * (90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved
2392
 * along the (previously rotated) Greenwich meridian.
2393
 * @return a new Conversion.
2394
 *
2395
 * @since 7.0
2396
 */
2397
ConversionNNPtr Conversion::createPoleRotationGRIBConvention(
2398
    const util::PropertyMap &properties,
2399
    const common::Angle &southPoleLatInUnrotatedCRS,
2400
    const common::Angle &southPoleLongInUnrotatedCRS,
2401
0
    const common::Angle &axisRotation) {
2402
0
    return create(properties,
2403
0
                  PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION,
2404
0
                  createParams(southPoleLatInUnrotatedCRS,
2405
0
                               southPoleLongInUnrotatedCRS, axisRotation));
2406
0
}
2407
2408
// ---------------------------------------------------------------------------
2409
2410
/** \brief Instantiate a conversion based on the Pole Rotation method, using
2411
 * the conventions of the netCDF CF convention for the netCDF format.
2412
 *
2413
 * Those are mentioned in the Note 2 of
2414
 * https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#_rotated_pole
2415
 *
2416
 * Several conventions for the pole rotation method exists.
2417
 * The parameters provided in this method are remapped to the PROJ ob_tran
2418
 * operation with:
2419
 * <pre>
2420
 * +proj=ob_tran +o_proj=longlat +o_lon_p=northPoleGridLongitude
2421
 *                               +o_lat_p=gridNorthPoleLatitude
2422
 *                               +lon_0=180+gridNorthPoleLongitude
2423
 * </pre>
2424
 *
2425
 * Another implementation of that convention is also in the netcdf-java library:
2426
 * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedPole.java
2427
 *
2428
 * The PROJ implementation of this method assumes a spherical ellipsoid.
2429
 *
2430
 * @param properties See \ref general_properties of the conversion. If the name
2431
 * is not provided, it is automatically set.
2432
 * @param gridNorthPoleLatitude True latitude of the north pole of the rotated
2433
 * grid
2434
 * @param gridNorthPoleLongitude True longitude of the north pole of the rotated
2435
 * grid.
2436
 * @param northPoleGridLongitude Longitude of the true north pole in the rotated
2437
 * grid.
2438
 * @return a new Conversion.
2439
 *
2440
 * @since 8.2
2441
 */
2442
ConversionNNPtr Conversion::createPoleRotationNetCDFCFConvention(
2443
    const util::PropertyMap &properties,
2444
    const common::Angle &gridNorthPoleLatitude,
2445
    const common::Angle &gridNorthPoleLongitude,
2446
0
    const common::Angle &northPoleGridLongitude) {
2447
0
    return create(properties,
2448
0
                  PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION,
2449
0
                  createParams(gridNorthPoleLatitude, gridNorthPoleLongitude,
2450
0
                               northPoleGridLongitude));
2451
0
}
2452
2453
// ---------------------------------------------------------------------------
2454
2455
/** \brief Instantiate a conversion based on the Change of Vertical Unit
2456
 * method.
2457
 *
2458
 * This method is defined as
2459
 * <a href="https://epsg.org/coord-operation-method_1069/index.html">
2460
 * EPSG:1069</a> [DEPRECATED].
2461
 *
2462
 * @param properties See \ref general_properties of the conversion. If the name
2463
 * is not provided, it is automatically set.
2464
 * @param factor Conversion factor
2465
 * @return a new Conversion.
2466
 */
2467
ConversionNNPtr
2468
Conversion::createChangeVerticalUnit(const util::PropertyMap &properties,
2469
2.75k
                                     const common::Scale &factor) {
2470
2.75k
    return create(
2471
2.75k
        properties,
2472
2.75k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
2473
2.75k
        VectorOfParameters{
2474
2.75k
            createOpParamNameEPSGCode(
2475
2.75k
                EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
2476
2.75k
        },
2477
2.75k
        VectorOfValues{
2478
2.75k
            factor,
2479
2.75k
        });
2480
2.75k
}
2481
2482
// ---------------------------------------------------------------------------
2483
2484
/** \brief Instantiate a conversion based on the Change of Vertical Unit
2485
 * method (without explicit conversion factor)
2486
 *
2487
 * This method is defined as
2488
 * <a href="https://epsg.org/coord-operation-method_1104/index.html">
2489
 * EPSG:1104</a>.
2490
 *
2491
 * @param properties See \ref general_properties of the conversion. If the name
2492
 * is not provided, it is automatically set.
2493
 * @return a new Conversion.
2494
 */
2495
ConversionNNPtr
2496
1
Conversion::createChangeVerticalUnit(const util::PropertyMap &properties) {
2497
1
    return create(properties,
2498
1
                  createMethodMapNameEPSGCode(
2499
1
                      EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR),
2500
1
                  VectorOfParameters{}, VectorOfValues{});
2501
1
}
2502
2503
// ---------------------------------------------------------------------------
2504
2505
/** \brief Instantiate a conversion based on the Height Depth Reversal
2506
 * method.
2507
 *
2508
 * This method is defined as
2509
 * <a href="https://epsg.org/coord-operation-method_1068/index.html">
2510
 * EPSG:1068</a>.
2511
 *
2512
 * @param properties See \ref general_properties of the conversion. If the name
2513
 * is not provided, it is automatically set.
2514
 * @return a new Conversion.
2515
 * @since 6.3
2516
 */
2517
ConversionNNPtr
2518
1
Conversion::createHeightDepthReversal(const util::PropertyMap &properties) {
2519
1
    return create(
2520
1
        properties,
2521
1
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL), {},
2522
1
        {});
2523
1
}
2524
2525
// ---------------------------------------------------------------------------
2526
2527
/** \brief Instantiate a conversion based on the Axis order reversal method
2528
 *
2529
 * This swaps the longitude, latitude axis.
2530
 *
2531
 * This method is defined as
2532
 * <a href="https://epsg.org/coord-operation-method_9843/index.html">
2533
 * EPSG:9843</a> for 2D or
2534
 * <a href="https://epsg.org/coord-operation-method_9844/index.html">
2535
 * EPSG:9844</a> for Geographic3D horizontal.
2536
 *
2537
 * @param is3D Whether this should apply on 3D geographicCRS
2538
 * @return a new Conversion.
2539
 */
2540
5.85k
ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
2541
5.85k
    if (is3D) {
2542
2.65k
        return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499),
2543
2.65k
                      createMethodMapNameEPSGCode(
2544
2.65k
                          EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
2545
2.65k
                      {}, {});
2546
2.65k
    }
2547
3.20k
    return create(
2548
3.20k
        createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498),
2549
3.20k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D),
2550
3.20k
        {}, {});
2551
5.85k
}
2552
2553
// ---------------------------------------------------------------------------
2554
2555
/** \brief Instantiate a conversion based on the Geographic/Geocentric method.
2556
 *
2557
 * This method is defined as
2558
 * <a href="https://epsg.org/coord-operation-method_9602/index.html">
2559
 * EPSG:9602</a>.
2560
 *
2561
 * @param properties See \ref general_properties of the conversion. If the name
2562
 * is not provided, it is automatically set.
2563
 * @return a new Conversion.
2564
 */
2565
ConversionNNPtr
2566
4.13k
Conversion::createGeographicGeocentric(const util::PropertyMap &properties) {
2567
4.13k
    return create(
2568
4.13k
        properties,
2569
4.13k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC), {},
2570
4.13k
        {});
2571
4.13k
}
2572
2573
// ---------------------------------------------------------------------------
2574
2575
//! @cond Doxygen_Suppress
2576
2577
ConversionNNPtr
2578
Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS,
2579
1.72k
                                       const crs::CRSNNPtr &targetCRS) {
2580
1.72k
    auto properties = util::PropertyMap().set(
2581
1.72k
        common::IdentifiedObject::NAME_KEY,
2582
1.72k
        buildOpName("Conversion", sourceCRS, targetCRS));
2583
1.72k
    auto conv = createGeographicGeocentric(properties);
2584
1.72k
    conv->setCRSs(sourceCRS, targetCRS, nullptr);
2585
1.72k
    return conv;
2586
1.72k
}
2587
2588
// ---------------------------------------------------------------------------
2589
2590
/** \brief Instantiate a conversion between a GeographicCRS and a spherical
2591
 * planetocentric GeodeticCRS
2592
 *
2593
 * This method peforms conversion between geodetic latitude and geocentric
2594
 * latitude
2595
 *
2596
 * @return a new Conversion.
2597
 */
2598
ConversionNNPtr
2599
Conversion::createGeographicGeocentricLatitude(const crs::CRSNNPtr &sourceCRS,
2600
151
                                               const crs::CRSNNPtr &targetCRS) {
2601
151
    auto properties = util::PropertyMap().set(
2602
151
        common::IdentifiedObject::NAME_KEY,
2603
151
        buildOpName("Conversion", sourceCRS, targetCRS));
2604
151
    auto conv = create(
2605
151
        properties, PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {});
2606
151
    conv->setCRSs(sourceCRS, targetCRS, nullptr);
2607
151
    return conv;
2608
151
}
2609
2610
// ---------------------------------------------------------------------------
2611
2612
InverseConversion::InverseConversion(const ConversionNNPtr &forward)
2613
    : Conversion(
2614
          OperationMethod::create(createPropertiesForInverse(forward->method()),
2615
                                  forward->method()->parameters()),
2616
          forward->parameterValues()),
2617
8.94k
      InverseCoordinateOperation(forward, true) {
2618
8.94k
    setPropertiesFromForward();
2619
8.94k
}
Unexecuted instantiation: osgeo::proj::operation::InverseConversion::InverseConversion(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&)
osgeo::proj::operation::InverseConversion::InverseConversion(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&)
Line
Count
Source
2617
8.94k
      InverseCoordinateOperation(forward, true) {
2618
8.94k
    setPropertiesFromForward();
2619
8.94k
}
2620
2621
// ---------------------------------------------------------------------------
2622
2623
8.94k
InverseConversion::~InverseConversion() = default;
2624
2625
// ---------------------------------------------------------------------------
2626
2627
1.46k
ConversionNNPtr InverseConversion::inverseAsConversion() const {
2628
1.46k
    return NN_NO_CHECK(
2629
1.46k
        util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_));
2630
1.46k
}
2631
2632
// ---------------------------------------------------------------------------
2633
2634
CoordinateOperationNNPtr
2635
7.48k
InverseConversion::create(const ConversionNNPtr &forward) {
2636
7.48k
    auto conv = util::nn_make_shared<InverseConversion>(forward);
2637
7.48k
    conv->assignSelf(conv);
2638
7.48k
    return conv;
2639
7.48k
}
2640
2641
// ---------------------------------------------------------------------------
2642
2643
1.46k
CoordinateOperationNNPtr InverseConversion::_shallowClone() const {
2644
1.46k
    auto op = InverseConversion::nn_make_shared<InverseConversion>(
2645
1.46k
        inverseAsConversion()->shallowClone());
2646
1.46k
    op->assignSelf(op);
2647
1.46k
    op->setCRSs(this, false);
2648
1.46k
    return util::nn_static_pointer_cast<CoordinateOperation>(op);
2649
1.46k
}
2650
2651
//! @endcond
2652
2653
// ---------------------------------------------------------------------------
2654
2655
//! @cond Doxygen_Suppress
2656
2657
100k
static bool isAxisOrderReversal2D(int methodEPSGCode) {
2658
100k
    return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D;
2659
100k
}
2660
2661
94.3k
static bool isAxisOrderReversal3D(int methodEPSGCode) {
2662
94.3k
    return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D;
2663
94.3k
}
2664
2665
86.9k
bool isAxisOrderReversal(int methodEPSGCode) {
2666
86.9k
    return isAxisOrderReversal2D(methodEPSGCode) ||
2667
86.9k
           isAxisOrderReversal3D(methodEPSGCode);
2668
86.9k
}
2669
//! @endcond
2670
2671
// ---------------------------------------------------------------------------
2672
2673
14.4k
CoordinateOperationNNPtr Conversion::inverse() const {
2674
14.4k
    const int methodEPSGCode = method()->getEPSGCode();
2675
2676
14.4k
    if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
2677
1.11k
        const double convFactor = parameterValueNumericAsSI(
2678
1.11k
            EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
2679
1.11k
        if (convFactor == 0) {
2680
14
            throw InvalidOperation("Invalid conversion factor");
2681
14
        }
2682
1.09k
        auto conv = createChangeVerticalUnit(
2683
1.09k
            createPropertiesForInverse(this, false, false),
2684
1.09k
            common::Scale(1.0 / convFactor));
2685
1.09k
        conv->setCRSs(this, true);
2686
1.09k
        return conv;
2687
1.11k
    }
2688
2689
13.3k
    if (methodEPSGCode ==
2690
13.3k
        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR) {
2691
1
        auto conv = createChangeVerticalUnit(
2692
1
            createPropertiesForInverse(this, false, false));
2693
1
        conv->setCRSs(this, true);
2694
1
        return conv;
2695
1
    }
2696
2697
13.3k
    const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode);
2698
13.3k
    const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode);
2699
13.3k
    if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) {
2700
3.20k
        auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D);
2701
3.20k
        conv->setCRSs(this, true);
2702
3.20k
        return conv;
2703
3.20k
    }
2704
2705
10.0k
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
2706
2707
2.41k
        auto conv = createGeographicGeocentric(
2708
2.41k
            createPropertiesForInverse(this, false, false));
2709
2.41k
        conv->setCRSs(this, true);
2710
2.41k
        return conv;
2711
2.41k
    }
2712
2713
7.68k
    if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
2714
2715
1
        auto conv = createHeightDepthReversal(
2716
1
            createPropertiesForInverse(this, false, false));
2717
1
        conv->setCRSs(this, true);
2718
1
        return conv;
2719
1
    }
2720
2721
7.68k
    if (method()->nameStr() ==
2722
7.68k
        PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) {
2723
204
        auto conv =
2724
204
            create(createPropertiesForInverse(this, false, false),
2725
204
                   PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {});
2726
204
        conv->setCRSs(this, true);
2727
204
        return conv;
2728
204
    }
2729
2730
7.48k
    return InverseConversion::create(NN_NO_CHECK(
2731
7.48k
        util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())));
2732
7.68k
}
2733
2734
// ---------------------------------------------------------------------------
2735
2736
//! @cond Doxygen_Suppress
2737
2738
60
static double msfn(double phi, double e2) {
2739
60
    const double sinphi = std::sin(phi);
2740
60
    const double cosphi = std::cos(phi);
2741
60
    return pj_msfn(sinphi, cosphi, e2);
2742
60
}
2743
2744
// ---------------------------------------------------------------------------
2745
2746
76
static double tsfn(double phi, double ec) {
2747
76
    const double sinphi = std::sin(phi);
2748
76
    return pj_tsfn(phi, sinphi, ec);
2749
76
}
2750
2751
// ---------------------------------------------------------------------------
2752
2753
// Function whose zeroes are the sin of the standard parallels of LCC_2SP
2754
0
static double lcc_1sp_to_2sp_f(double sinphi, double K, double ec, double n) {
2755
0
    const double x = sinphi;
2756
0
    const double ecx = ec * x;
2757
0
    return (1 - x * x) / (1 - ecx * ecx) -
2758
0
           K * K *
2759
0
               std::pow((1.0 - x) / (1.0 + x) *
2760
0
                            std::pow((1.0 + ecx) / (1.0 - ecx), ec),
2761
0
                        n);
2762
0
}
2763
2764
// ---------------------------------------------------------------------------
2765
2766
// Find the sin of the standard parallels of LCC_2SP
2767
static double find_zero_lcc_1sp_to_2sp_f(double sinphi0, bool bNorth, double K,
2768
0
                                         double ec) {
2769
0
    double a, b;
2770
0
    double f_a;
2771
0
    if (bNorth) {
2772
        // Look for zero above phi0
2773
0
        a = sinphi0;
2774
0
        b = 1.0;   // sin(North pole)
2775
0
        f_a = 1.0; // some positive value, but we only care about the sign
2776
0
    } else {
2777
        // Look for zero below phi0
2778
0
        a = -1.0; // sin(South pole)
2779
0
        b = sinphi0;
2780
0
        f_a = -1.0; // minus infinity in fact, but we only care about the sign
2781
0
    }
2782
    // We use dichotomy search. lcc_1sp_to_2sp_f() is positive at sinphi_init,
2783
    // has a zero in ]-1,sinphi0[ and ]sinphi0,1[ ranges
2784
0
    for (int N = 0; N < 100; N++) {
2785
0
        double c = (a + b) / 2;
2786
0
        double f_c = lcc_1sp_to_2sp_f(c, K, ec, sinphi0);
2787
0
        if (f_c == 0.0 || (b - a) < 1e-18) {
2788
0
            return c;
2789
0
        }
2790
0
        if ((f_c > 0 && f_a > 0) || (f_c < 0 && f_a < 0)) {
2791
0
            a = c;
2792
0
            f_a = f_c;
2793
0
        } else {
2794
0
            b = c;
2795
0
        }
2796
0
    }
2797
0
    return (a + b) / 2;
2798
0
}
2799
2800
0
static inline double DegToRad(double x) { return x / 180.0 * M_PI; }
2801
17
static inline double RadToDeg(double x) { return x / M_PI * 180.0; }
2802
2803
//! @endcond
2804
2805
// ---------------------------------------------------------------------------
2806
2807
/**
2808
 * \brief Return an equivalent projection.
2809
 *
2810
 * Currently implemented:
2811
 * <ul>
2812
 * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP) to
2813
 * EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP)</li>
2814
 * <li>EPSG_CODE_METHOD_MERCATOR_VARIANT_B (2SP) to
2815
 * EPSG_CODE_METHOD_MERCATOR_VARIANT_A (1SP)</li>
2816
 * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP to
2817
 * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP</li>
2818
 * <li>EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP to
2819
 * EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP</li>
2820
 * </ul>
2821
 *
2822
 * @param targetEPSGCode EPSG code of the target method.
2823
 * @return new conversion, or nullptr
2824
 */
2825
22
ConversionPtr Conversion::convertToOtherMethod(int targetEPSGCode) const {
2826
22
    const int current_epsg_code = method()->getEPSGCode();
2827
22
    if (current_epsg_code == targetEPSGCode) {
2828
0
        return util::nn_dynamic_pointer_cast<Conversion>(shared_from_this());
2829
0
    }
2830
2831
22
    auto geogCRS = dynamic_cast<crs::GeodeticCRS *>(sourceCRS().get());
2832
22
    if (!geogCRS) {
2833
0
        return nullptr;
2834
0
    }
2835
2836
22
    const double e2 = geogCRS->ellipsoid()->squaredEccentricity();
2837
22
    if (e2 < 0) {
2838
0
        return nullptr;
2839
0
    }
2840
2841
22
    if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
2842
22
        targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
2843
22
        parameterValueNumericAsSI(
2844
0
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
2845
0
        const double k0 = parameterValueNumericAsSI(
2846
0
            EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
2847
0
        if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
2848
0
            return nullptr;
2849
0
        const double dfStdP1Lat =
2850
0
            (k0 >= 1.0)
2851
0
                ? 0.0
2852
0
                : std::acos(std::sqrt((1.0 - e2) / ((1.0 / (k0 * k0)) - e2)));
2853
0
        auto latitudeFirstParallel = common::Angle(
2854
0
            common::Angle(dfStdP1Lat, common::UnitOfMeasure::RADIAN)
2855
0
                .convertToUnit(common::UnitOfMeasure::DEGREE),
2856
0
            common::UnitOfMeasure::DEGREE);
2857
0
        auto conv = createMercatorVariantB(
2858
0
            util::PropertyMap(), latitudeFirstParallel,
2859
0
            common::Angle(parameterValueMeasure(
2860
0
                EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
2861
0
            common::Length(
2862
0
                parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
2863
0
            common::Length(
2864
0
                parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
2865
0
        conv->setCRSs(this, false);
2866
0
        return conv.as_nullable();
2867
0
    }
2868
2869
22
    if (current_epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
2870
22
        targetEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
2871
1
        const double phi1 = parameterValueNumericAsSI(
2872
1
            EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
2873
1
        if (!(std::fabs(phi1) < M_PI / 2))
2874
0
            return nullptr;
2875
1
        const double k0 = msfn(phi1, e2);
2876
1
        auto conv = createMercatorVariantA(
2877
1
            util::PropertyMap(),
2878
1
            common::Angle(0.0, common::UnitOfMeasure::DEGREE),
2879
1
            common::Angle(parameterValueMeasure(
2880
1
                EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
2881
1
            common::Scale(k0, common::UnitOfMeasure::SCALE_UNITY),
2882
1
            common::Length(
2883
1
                parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
2884
1
            common::Length(
2885
1
                parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
2886
1
        conv->setCRSs(this, false);
2887
1
        return conv.as_nullable();
2888
1
    }
2889
2890
21
    if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
2891
21
        targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
2892
        // Notations m0, t0, n, m1, t1, F are those of the EPSG guidance
2893
        // "1.3.1.1 Lambert Conic Conformal (2SP)" and
2894
        // "1.3.1.2 Lambert Conic Conformal (1SP)" and
2895
        // or Snyder pages 106-109
2896
0
        auto latitudeOfOrigin = common::Angle(parameterValueMeasure(
2897
0
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN));
2898
0
        const double phi0 = latitudeOfOrigin.getSIValue();
2899
0
        const double k0 = parameterValueNumericAsSI(
2900
0
            EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
2901
0
        if (!(std::fabs(phi0) < M_PI / 2))
2902
0
            return nullptr;
2903
0
        if (!(k0 > 0 && k0 <= 1.0 + 1e-10))
2904
0
            return nullptr;
2905
0
        const double ec = std::sqrt(e2);
2906
0
        const double m0 = msfn(phi0, e2);
2907
0
        const double t0 = tsfn(phi0, ec);
2908
0
        const double n = sin(phi0);
2909
0
        if (std::fabs(n) < 1e-10)
2910
0
            return nullptr;
2911
0
        if (fabs(k0 - 1.0) <= 1e-10) {
2912
0
            auto conv = createLambertConicConformal_2SP(
2913
0
                util::PropertyMap(), latitudeOfOrigin,
2914
0
                common::Angle(parameterValueMeasure(
2915
0
                    EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
2916
0
                latitudeOfOrigin, latitudeOfOrigin,
2917
0
                common::Length(
2918
0
                    parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
2919
0
                common::Length(
2920
0
                    parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_NORTHING)));
2921
0
            conv->setCRSs(this, false);
2922
0
            return conv.as_nullable();
2923
0
        } else {
2924
0
            const double K = k0 * m0 / std::pow(t0, n);
2925
0
            const double phi1 =
2926
0
                std::asin(find_zero_lcc_1sp_to_2sp_f(n, true, K, ec));
2927
0
            const double phi2 =
2928
0
                std::asin(find_zero_lcc_1sp_to_2sp_f(n, false, K, ec));
2929
0
            double phi1Deg = RadToDeg(phi1);
2930
0
            double phi2Deg = RadToDeg(phi2);
2931
2932
            // Try to round to hundreth of degree if very close to it
2933
0
            if (std::fabs(phi1Deg * 1000 - std::floor(phi1Deg * 1000 + 0.5)) <
2934
0
                1e-8)
2935
0
                phi1Deg = floor(phi1Deg * 1000 + 0.5) / 1000;
2936
0
            if (std::fabs(phi2Deg * 1000 - std::floor(phi2Deg * 1000 + 0.5)) <
2937
0
                1e-8)
2938
0
                phi2Deg = std::floor(phi2Deg * 1000 + 0.5) / 1000;
2939
2940
            // The following improvement is too turn the LCC1SP equivalent of
2941
            // EPSG:2154 to the real LCC2SP
2942
            // If the computed latitude of origin is close to .0 or .5 degrees
2943
            // then check if rounding it to it will get a false northing
2944
            // close to an integer
2945
0
            const double FN =
2946
0
                parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
2947
0
            const double latitudeOfOriginDeg =
2948
0
                latitudeOfOrigin.convertToUnit(common::UnitOfMeasure::DEGREE);
2949
0
            if (std::fabs(latitudeOfOriginDeg * 2 -
2950
0
                          std::floor(latitudeOfOriginDeg * 2 + 0.5)) < 0.2) {
2951
0
                const double dfRoundedLatOfOrig =
2952
0
                    std::floor(latitudeOfOriginDeg * 2 + 0.5) / 2;
2953
0
                const double m1 = msfn(phi1, e2);
2954
0
                const double t1 = tsfn(phi1, ec);
2955
0
                const double F = m1 / (n * std::pow(t1, n));
2956
0
                const double a =
2957
0
                    geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
2958
0
                const double tRoundedLatOfOrig =
2959
0
                    tsfn(DegToRad(dfRoundedLatOfOrig), ec);
2960
0
                const double FN_correction =
2961
0
                    a * F * (std::pow(tRoundedLatOfOrig, n) - std::pow(t0, n));
2962
0
                const double FN_corrected = FN - FN_correction;
2963
0
                const double FN_corrected_rounded =
2964
0
                    std::floor(FN_corrected + 0.5);
2965
0
                if (std::fabs(FN_corrected - FN_corrected_rounded) < 1e-8) {
2966
0
                    auto conv = createLambertConicConformal_2SP(
2967
0
                        util::PropertyMap(),
2968
0
                        common::Angle(dfRoundedLatOfOrig,
2969
0
                                      common::UnitOfMeasure::DEGREE),
2970
0
                        common::Angle(parameterValueMeasure(
2971
0
                            EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
2972
0
                        common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
2973
0
                        common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
2974
0
                        common::Length(parameterValueMeasure(
2975
0
                            EPSG_CODE_PARAMETER_FALSE_EASTING)),
2976
0
                        common::Length(FN_corrected_rounded));
2977
0
                    conv->setCRSs(this, false);
2978
0
                    return conv.as_nullable();
2979
0
                }
2980
0
            }
2981
2982
0
            auto conv = createLambertConicConformal_2SP(
2983
0
                util::PropertyMap(), latitudeOfOrigin,
2984
0
                common::Angle(parameterValueMeasure(
2985
0
                    EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN)),
2986
0
                common::Angle(phi1Deg, common::UnitOfMeasure::DEGREE),
2987
0
                common::Angle(phi2Deg, common::UnitOfMeasure::DEGREE),
2988
0
                common::Length(
2989
0
                    parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING)),
2990
0
                common::Length(FN));
2991
0
            conv->setCRSs(this, false);
2992
0
            return conv.as_nullable();
2993
0
        }
2994
0
    }
2995
2996
21
    if (current_epsg_code == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
2997
21
        targetEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP) {
2998
        // Notations m0, t0, m1, t1, m2, t2 n, F are those of the EPSG guidance
2999
        // "1.3.1.1 Lambert Conic Conformal (2SP)" and
3000
        // "1.3.1.2 Lambert Conic Conformal (1SP)" and
3001
        // or Snyder pages 106-109
3002
21
        const double phiF =
3003
21
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_FALSE_ORIGIN)
3004
21
                .getSIValue();
3005
21
        const double phi1 =
3006
21
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
3007
21
                .getSIValue();
3008
21
        const double phi2 =
3009
21
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
3010
21
                .getSIValue();
3011
21
        if (!(std::fabs(phiF) < M_PI / 2))
3012
0
            return nullptr;
3013
21
        if (!(std::fabs(phi1) < M_PI / 2))
3014
0
            return nullptr;
3015
21
        if (!(std::fabs(phi2) < M_PI / 2))
3016
0
            return nullptr;
3017
21
        const double ec = std::sqrt(e2);
3018
21
        const double m1 = msfn(phi1, e2);
3019
21
        const double m2 = msfn(phi2, e2);
3020
21
        const double t1 = tsfn(phi1, ec);
3021
21
        const double t2 = tsfn(phi2, ec);
3022
21
        const double n_denom = std::log(t1) - std::log(t2);
3023
21
        const double n = (std::fabs(n_denom) < 1e-10)
3024
21
                             ? std::sin(phi1)
3025
21
                             : (std::log(m1) - std::log(m2)) / n_denom;
3026
21
        if (std::fabs(n) < 1e-10)
3027
4
            return nullptr;
3028
17
        const double F = m1 / (n * std::pow(t1, n));
3029
17
        const double phi0 = std::asin(n);
3030
17
        const double m0 = msfn(phi0, e2);
3031
17
        const double t0 = tsfn(phi0, ec);
3032
17
        const double F0 = m0 / (n * std::pow(t0, n));
3033
17
        const double k0 = F / F0;
3034
17
        const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
3035
17
        const double tF = tsfn(phiF, ec);
3036
17
        const double FN_correction =
3037
17
            a * F * (std::pow(tF, n) - std::pow(t0, n));
3038
3039
17
        double phi0Deg = RadToDeg(phi0);
3040
        // Try to round to thousandth of degree if very close to it
3041
17
        if (std::fabs(phi0Deg * 1000 - std::floor(phi0Deg * 1000 + 0.5)) < 1e-8)
3042
6
            phi0Deg = std::floor(phi0Deg * 1000 + 0.5) / 1000;
3043
3044
17
        auto conv = createLambertConicConformal_1SP(
3045
17
            util::PropertyMap(),
3046
17
            common::Angle(phi0Deg, common::UnitOfMeasure::DEGREE),
3047
17
            common::Angle(parameterValueMeasure(
3048
17
                EPSG_CODE_PARAMETER_LONGITUDE_FALSE_ORIGIN)),
3049
17
            common::Scale(k0),
3050
17
            common::Length(parameterValueMeasure(
3051
17
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN)),
3052
17
            common::Length(
3053
17
                parameterValueNumericAsSI(
3054
17
                    EPSG_CODE_PARAMETER_NORTHING_FALSE_ORIGIN) +
3055
17
                (std::fabs(FN_correction) > 1e-8 ? FN_correction : 0)));
3056
17
        conv->setCRSs(this, false);
3057
17
        return conv.as_nullable();
3058
21
    }
3059
3060
0
    return nullptr;
3061
21
}
3062
3063
// ---------------------------------------------------------------------------
3064
3065
//! @cond Doxygen_Suppress
3066
3067
static const ESRIMethodMapping *getESRIMapping(const std::string &wkt2_name,
3068
0
                                               int epsg_code) {
3069
0
    size_t nEsriMappings = 0;
3070
0
    const auto esriMappings = getEsriMappings(nEsriMappings);
3071
0
    for (size_t i = 0; i < nEsriMappings; ++i) {
3072
0
        const auto &mapping = esriMappings[i];
3073
0
        if ((epsg_code != 0 && mapping.epsg_code == epsg_code) ||
3074
0
            ci_equal(wkt2_name, mapping.wkt2_name)) {
3075
0
            return &mapping;
3076
0
        }
3077
0
    }
3078
0
    return nullptr;
3079
0
}
3080
3081
// ---------------------------------------------------------------------------
3082
3083
static void getESRIMethodNameAndParams(const Conversion *conv,
3084
                                       const std::string &methodName,
3085
                                       int methodEPSGCode,
3086
                                       const char *&esriMethodName,
3087
0
                                       const ESRIParamMapping *&esriParams) {
3088
0
    esriParams = nullptr;
3089
0
    esriMethodName = nullptr;
3090
0
    const auto *esriMapping = getESRIMapping(methodName, methodEPSGCode);
3091
0
    const auto l_targetCRS = conv->targetCRS();
3092
0
    if (esriMapping) {
3093
0
        esriParams = esriMapping->params;
3094
0
        esriMethodName = esriMapping->esri_name;
3095
0
        if (esriMapping->epsg_code ==
3096
0
                EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
3097
0
            esriMapping->epsg_code ==
3098
0
                EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) {
3099
0
            if (l_targetCRS &&
3100
0
                ci_find(l_targetCRS->nameStr(), "Plate Carree") !=
3101
0
                    std::string::npos &&
3102
0
                conv->parameterValueNumericAsSI(
3103
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) == 0.0) {
3104
0
                esriParams = paramsESRI_Plate_Carree;
3105
0
                esriMethodName = "Plate_Carree";
3106
0
            } else {
3107
0
                esriParams = paramsESRI_Equidistant_Cylindrical;
3108
0
                esriMethodName = "Equidistant_Cylindrical";
3109
0
            }
3110
0
        } else if (esriMapping->epsg_code ==
3111
0
                   EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
3112
0
            if (ci_find(conv->nameStr(), "Gauss Kruger") != std::string::npos ||
3113
0
                (l_targetCRS && (ci_find(l_targetCRS->nameStr(), "Gauss") !=
3114
0
                                     std::string::npos ||
3115
0
                                 ci_find(l_targetCRS->nameStr(), "GK_") !=
3116
0
                                     std::string::npos))) {
3117
0
                esriParams = paramsESRI_Gauss_Kruger;
3118
0
                esriMethodName = "Gauss_Kruger";
3119
0
            } else {
3120
0
                esriParams = paramsESRI_Transverse_Mercator;
3121
0
                esriMethodName = "Transverse_Mercator";
3122
0
            }
3123
0
        } else if (esriMapping->epsg_code ==
3124
0
                   EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
3125
0
            if (std::abs(
3126
0
                    conv->parameterValueNumericAsSI(
3127
0
                        EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
3128
0
                    conv->parameterValueNumericAsSI(
3129
0
                        EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
3130
0
                1e-15) {
3131
0
                esriParams =
3132
0
                    paramsESRI_Hotine_Oblique_Mercator_Azimuth_Natural_Origin;
3133
0
                esriMethodName =
3134
0
                    "Hotine_Oblique_Mercator_Azimuth_Natural_Origin";
3135
0
            } else {
3136
0
                esriParams =
3137
0
                    paramsESRI_Rectified_Skew_Orthomorphic_Natural_Origin;
3138
0
                esriMethodName = "Rectified_Skew_Orthomorphic_Natural_Origin";
3139
0
            }
3140
0
        } else if (esriMapping->epsg_code ==
3141
0
                   EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
3142
0
            if (std::abs(
3143
0
                    conv->parameterValueNumericAsSI(
3144
0
                        EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE) -
3145
0
                    conv->parameterValueNumericAsSI(
3146
0
                        EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID)) <
3147
0
                1e-15) {
3148
0
                esriParams = paramsESRI_Hotine_Oblique_Mercator_Azimuth_Center;
3149
0
                esriMethodName = "Hotine_Oblique_Mercator_Azimuth_Center";
3150
0
            } else {
3151
0
                esriParams = paramsESRI_Rectified_Skew_Orthomorphic_Center;
3152
0
                esriMethodName = "Rectified_Skew_Orthomorphic_Center";
3153
0
            }
3154
0
        } else if (esriMapping->epsg_code ==
3155
0
                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A) {
3156
            // Quite empiric, but looking at pe_list_projcs.csv, the only
3157
            // CRS that use Polar_Stereographic_Variant_A are EPSG:5041 and 5042
3158
0
            if (l_targetCRS &&
3159
                // EPSG:5041
3160
0
                (l_targetCRS->nameStr() == "WGS 84 / UPS North (E,N)" ||
3161
                 // EPSG:5042
3162
0
                 l_targetCRS->nameStr() == "WGS 84 / UPS South (E,N)")) {
3163
0
                esriMethodName = "Polar_Stereographic_Variant_A";
3164
0
            } else {
3165
0
                esriMethodName = "Stereographic";
3166
0
            }
3167
0
        } else if (esriMapping->epsg_code ==
3168
0
                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
3169
0
            if (conv->parameterValueNumericAsSI(
3170
0
                    EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL) > 0) {
3171
0
                esriMethodName = "Stereographic_North_Pole";
3172
0
            } else {
3173
0
                esriMethodName = "Stereographic_South_Pole";
3174
0
            }
3175
0
        }
3176
0
    }
3177
0
}
3178
3179
// ---------------------------------------------------------------------------
3180
3181
0
const char *Conversion::getESRIMethodName() const {
3182
0
    const auto &l_method = method();
3183
0
    const auto &methodName = l_method->nameStr();
3184
0
    const auto methodEPSGCode = l_method->getEPSGCode();
3185
0
    const ESRIParamMapping *esriParams = nullptr;
3186
0
    const char *esriMethodName = nullptr;
3187
0
    getESRIMethodNameAndParams(this, methodName, methodEPSGCode, esriMethodName,
3188
0
                               esriParams);
3189
0
    return esriMethodName;
3190
0
}
3191
3192
//! @endcond
3193
3194
// ---------------------------------------------------------------------------
3195
3196
//! @cond Doxygen_Suppress
3197
0
const char *Conversion::getWKT1GDALMethodName() const {
3198
0
    const auto &l_method = method();
3199
0
    const auto methodEPSGCode = l_method->getEPSGCode();
3200
0
    if (methodEPSGCode ==
3201
0
        EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
3202
0
        return "Mercator_1SP";
3203
0
    }
3204
0
    const MethodMapping *mapping = getMapping(l_method.get());
3205
0
    return mapping ? mapping->wkt1_name : nullptr;
3206
0
}
3207
3208
//! @endcond
3209
3210
// ---------------------------------------------------------------------------
3211
3212
//! @cond Doxygen_Suppress
3213
3214
0
void Conversion::_exportToWKT(io::WKTFormatter *formatter) const {
3215
0
    const auto &l_method = method();
3216
0
    const auto &methodName = l_method->nameStr();
3217
0
    const auto methodEPSGCode = l_method->getEPSGCode();
3218
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
3219
3220
0
    if (!isWKT2 && formatter->useESRIDialect()) {
3221
0
        if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
3222
0
            auto eqConv =
3223
0
                convertToOtherMethod(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
3224
0
            if (eqConv) {
3225
0
                eqConv->_exportToWKT(formatter);
3226
0
                return;
3227
0
            }
3228
0
        }
3229
0
    }
3230
3231
0
    if (isWKT2) {
3232
0
        formatter->startNode(formatter->useDerivingConversion()
3233
0
                                 ? io::WKTConstants::DERIVINGCONVERSION
3234
0
                                 : io::WKTConstants::CONVERSION,
3235
0
                             !identifiers().empty());
3236
0
        formatter->addQuotedString(nameStr());
3237
0
    } else {
3238
0
        formatter->enter();
3239
0
        formatter->pushOutputUnit(false);
3240
0
        formatter->pushOutputId(false);
3241
0
    }
3242
3243
#ifdef DEBUG_CONVERSION_ID
3244
    if (sourceCRS() && targetCRS()) {
3245
        formatter->startNode("SOURCECRS_ID", false);
3246
        sourceCRS()->formatID(formatter);
3247
        formatter->endNode();
3248
        formatter->startNode("TARGETCRS_ID", false);
3249
        targetCRS()->formatID(formatter);
3250
        formatter->endNode();
3251
    }
3252
#endif
3253
3254
0
    bool bAlreadyWritten = false;
3255
0
    if (!isWKT2 && formatter->useESRIDialect()) {
3256
0
        const ESRIParamMapping *esriParams = nullptr;
3257
0
        const char *esriMethodName = nullptr;
3258
0
        getESRIMethodNameAndParams(this, methodName, methodEPSGCode,
3259
0
                                   esriMethodName, esriParams);
3260
0
        if (esriMethodName && esriParams) {
3261
0
            formatter->startNode(io::WKTConstants::PROJECTION, false);
3262
0
            formatter->addQuotedString(esriMethodName);
3263
0
            formatter->endNode();
3264
3265
0
            for (int i = 0; esriParams[i].esri_name != nullptr; i++) {
3266
0
                const auto &esriParam = esriParams[i];
3267
0
                formatter->startNode(io::WKTConstants::PARAMETER, false);
3268
0
                formatter->addQuotedString(esriParam.esri_name);
3269
0
                if (esriParam.wkt2_name) {
3270
0
                    const auto &pv = parameterValue(esriParam.wkt2_name,
3271
0
                                                    esriParam.epsg_code);
3272
0
                    if (pv && pv->type() == ParameterValue::Type::MEASURE) {
3273
0
                        const auto &v = pv->value();
3274
                        // as we don't output the natural unit, output
3275
                        // to the registered linear / angular unit.
3276
0
                        const auto &unitType = v.unit().type();
3277
0
                        if (unitType == common::UnitOfMeasure::Type::LINEAR) {
3278
0
                            formatter->add(v.convertToUnit(
3279
0
                                *(formatter->axisLinearUnit())));
3280
0
                        } else if (unitType ==
3281
0
                                   common::UnitOfMeasure::Type::ANGULAR) {
3282
0
                            const auto &angUnit =
3283
0
                                *(formatter->axisAngularUnit());
3284
0
                            double val = v.convertToUnit(angUnit);
3285
0
                            if (angUnit == common::UnitOfMeasure::DEGREE) {
3286
0
                                if (val > 180.0) {
3287
0
                                    val -= 360.0;
3288
0
                                } else if (val < -180.0) {
3289
0
                                    val += 360.0;
3290
0
                                }
3291
0
                            }
3292
0
                            formatter->add(val);
3293
0
                        } else {
3294
0
                            formatter->add(v.getSIValue());
3295
0
                        }
3296
0
                    } else if (ci_find(esriParam.esri_name, "scale") !=
3297
0
                               std::string::npos) {
3298
0
                        formatter->add(1.0);
3299
0
                    } else {
3300
0
                        formatter->add(0.0);
3301
0
                    }
3302
0
                } else {
3303
0
                    formatter->add(esriParam.fixed_value);
3304
0
                }
3305
0
                formatter->endNode();
3306
0
            }
3307
0
            bAlreadyWritten = true;
3308
0
        }
3309
0
    } else if (!isWKT2) {
3310
0
        if (methodEPSGCode ==
3311
0
            EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
3312
0
            const double latitudeOrigin = parameterValueNumeric(
3313
0
                EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
3314
0
                common::UnitOfMeasure::DEGREE);
3315
0
            if (latitudeOrigin != 0) {
3316
0
                throw io::FormattingException(
3317
0
                    std::string("Unsupported value for ") +
3318
0
                    EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
3319
0
            }
3320
3321
0
            bAlreadyWritten = true;
3322
0
            formatter->startNode(io::WKTConstants::PROJECTION, false);
3323
0
            formatter->addQuotedString("Mercator_1SP");
3324
0
            formatter->endNode();
3325
3326
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3327
0
            formatter->addQuotedString("central_meridian");
3328
0
            const double centralMeridian = parameterValueNumeric(
3329
0
                EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
3330
0
                common::UnitOfMeasure::DEGREE);
3331
0
            formatter->add(centralMeridian);
3332
0
            formatter->endNode();
3333
3334
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3335
0
            formatter->addQuotedString("scale_factor");
3336
0
            formatter->add(1.0);
3337
0
            formatter->endNode();
3338
3339
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3340
0
            formatter->addQuotedString("false_easting");
3341
0
            const double falseEasting =
3342
0
                parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
3343
0
            formatter->add(falseEasting);
3344
0
            formatter->endNode();
3345
3346
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3347
0
            formatter->addQuotedString("false_northing");
3348
0
            const double falseNorthing =
3349
0
                parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
3350
0
            formatter->add(falseNorthing);
3351
0
            formatter->endNode();
3352
0
        } else if (starts_with(methodName, "PROJ ")) {
3353
0
            bAlreadyWritten = true;
3354
0
            formatter->startNode(io::WKTConstants::PROJECTION, false);
3355
0
            formatter->addQuotedString("custom_proj4");
3356
0
            formatter->endNode();
3357
0
        }
3358
0
    }
3359
3360
0
    if (!bAlreadyWritten) {
3361
0
        l_method->_exportToWKT(formatter);
3362
3363
0
        const MethodMapping *mapping =
3364
0
            !isWKT2 ? getMapping(l_method.get()) : nullptr;
3365
0
        bool hasInterpolationCRSParameter = false;
3366
0
        for (const auto &genOpParamvalue : parameterValues()) {
3367
0
            const auto opParamvalue =
3368
0
                dynamic_cast<const OperationParameterValue *>(
3369
0
                    genOpParamvalue.get());
3370
0
            const int paramEPSGCode =
3371
0
                opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;
3372
3373
            // EPSG has normally no Latitude of natural origin for Equidistant
3374
            // Cylindrical but PROJ can handle it, so output the parameter if
3375
            // not zero
3376
0
            if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
3377
0
                 methodEPSGCode ==
3378
0
                     EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
3379
0
                if (paramEPSGCode ==
3380
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
3381
0
                    const auto &paramValue = opParamvalue->parameterValue();
3382
0
                    if (paramValue->type() == ParameterValue::Type::MEASURE) {
3383
0
                        const auto &measure = paramValue->value();
3384
0
                        if (measure.getSIValue() == 0) {
3385
0
                            continue;
3386
0
                        }
3387
0
                    }
3388
0
                }
3389
0
            }
3390
            // Same for false easting / false northing for Vertical Perspective
3391
0
            else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) {
3392
0
                if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
3393
0
                    paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
3394
0
                    const auto &paramValue = opParamvalue->parameterValue();
3395
0
                    if (paramValue->type() == ParameterValue::Type::MEASURE) {
3396
0
                        const auto &measure = paramValue->value();
3397
0
                        if (measure.getSIValue() == 0) {
3398
0
                            continue;
3399
0
                        }
3400
0
                    }
3401
0
                }
3402
0
            }
3403
0
            if (paramEPSGCode ==
3404
0
                    EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3405
0
                paramEPSGCode ==
3406
0
                    EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
3407
0
                hasInterpolationCRSParameter = true;
3408
0
            }
3409
0
            genOpParamvalue->_exportToWKT(formatter, mapping);
3410
0
        }
3411
3412
        // If we have an interpolation CRS that has a EPSG code, then
3413
        // we can export it as a PARAMETER[]
3414
0
        const auto l_interpolationCRS = interpolationCRS();
3415
0
        if (!hasInterpolationCRSParameter && l_interpolationCRS) {
3416
0
            const auto code = l_interpolationCRS->getEPSGCode();
3417
0
            if (code != 0) {
3418
0
                createOperationParameterValueFromInterpolationCRS(
3419
0
                    methodEPSGCode, code)
3420
0
                    ->_exportToWKT(formatter, mapping);
3421
0
            }
3422
0
        }
3423
0
    }
3424
3425
0
    if (isWKT2) {
3426
0
        if (formatter->outputId()) {
3427
0
            formatID(formatter);
3428
0
        }
3429
0
        formatter->endNode();
3430
0
    } else {
3431
0
        formatter->popOutputUnit();
3432
0
        formatter->popOutputId();
3433
0
        formatter->leave();
3434
0
    }
3435
0
}
3436
//! @endcond
3437
3438
// ---------------------------------------------------------------------------
3439
3440
//! @cond Doxygen_Suppress
3441
void Conversion::_exportToJSON(
3442
    io::JSONFormatter *formatter) const // throw(FormattingException)
3443
0
{
3444
0
    auto writer = formatter->writer();
3445
0
    auto objectContext(
3446
0
        formatter->MakeObjectContext("Conversion", !identifiers().empty()));
3447
3448
0
    writer->AddObjKey("name");
3449
0
    const auto &l_name = nameStr();
3450
0
    if (l_name.empty()) {
3451
0
        writer->Add("unnamed");
3452
0
    } else {
3453
0
        writer->Add(l_name);
3454
0
    }
3455
3456
0
    writer->AddObjKey("method");
3457
0
    formatter->setOmitTypeInImmediateChild();
3458
0
    formatter->setAllowIDInImmediateChild();
3459
0
    const auto &l_method = method();
3460
0
    l_method->_exportToJSON(formatter);
3461
3462
0
    const auto &l_parameterValues = parameterValues();
3463
0
    const auto l_interpolationCRS = interpolationCRS();
3464
0
    if (!l_parameterValues.empty() || l_interpolationCRS) {
3465
0
        writer->AddObjKey("parameters");
3466
0
        {
3467
0
            bool hasInterpolationCRSParameter = false;
3468
0
            auto parametersContext(writer->MakeArrayContext(false));
3469
0
            for (const auto &genOpParamvalue : l_parameterValues) {
3470
0
                const auto opParamvalue =
3471
0
                    dynamic_cast<const OperationParameterValue *>(
3472
0
                        genOpParamvalue.get());
3473
0
                const int paramEPSGCode =
3474
0
                    opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;
3475
0
                if (paramEPSGCode ==
3476
0
                        EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3477
0
                    paramEPSGCode ==
3478
0
                        EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
3479
0
                    hasInterpolationCRSParameter = true;
3480
0
                }
3481
0
                formatter->setAllowIDInImmediateChild();
3482
0
                formatter->setOmitTypeInImmediateChild();
3483
0
                genOpParamvalue->_exportToJSON(formatter);
3484
0
            }
3485
3486
            // If we have an interpolation CRS that has a EPSG code, then
3487
            // we can export it as a parameter
3488
0
            if (!hasInterpolationCRSParameter && l_interpolationCRS) {
3489
0
                const auto methodEPSGCode = l_method->getEPSGCode();
3490
0
                const auto code = l_interpolationCRS->getEPSGCode();
3491
0
                if (code != 0) {
3492
0
                    formatter->setAllowIDInImmediateChild();
3493
0
                    formatter->setOmitTypeInImmediateChild();
3494
0
                    createOperationParameterValueFromInterpolationCRS(
3495
0
                        methodEPSGCode, code)
3496
0
                        ->_exportToJSON(formatter);
3497
0
                }
3498
0
            }
3499
0
        }
3500
0
    }
3501
3502
0
    if (formatter->outputId()) {
3503
0
        formatID(formatter);
3504
0
    }
3505
0
}
3506
3507
//! @endcond
3508
3509
// ---------------------------------------------------------------------------
3510
3511
//! @cond Doxygen_Suppress
3512
static bool createPROJ4WebMercator(const Conversion *conv,
3513
3
                                   io::PROJStringFormatter *formatter) {
3514
3
    const double centralMeridian = conv->parameterValueNumeric(
3515
3
        EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
3516
3
        common::UnitOfMeasure::DEGREE);
3517
3518
3
    const double falseEasting =
3519
3
        conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
3520
3521
3
    const double falseNorthing =
3522
3
        conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
3523
3524
3
    auto sourceCRS = conv->sourceCRS();
3525
3
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
3526
3
    if (!geogCRS) {
3527
1
        return false;
3528
1
    }
3529
3530
2
    std::string units("m");
3531
2
    auto targetCRS = conv->targetCRS();
3532
2
    auto targetProjCRS =
3533
2
        dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get());
3534
2
    if (targetProjCRS) {
3535
2
        const auto &axisList = targetProjCRS->coordinateSystem()->axisList();
3536
2
        const auto &unit = axisList[0]->unit();
3537
2
        if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
3538
2
                                  util::IComparable::Criterion::EQUIVALENT)) {
3539
0
            auto projUnit = unit.exportToPROJString();
3540
0
            if (!projUnit.empty()) {
3541
0
                units = std::move(projUnit);
3542
0
            } else {
3543
0
                return false;
3544
0
            }
3545
0
        }
3546
2
    }
3547
3548
2
    formatter->addStep("merc");
3549
2
    const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
3550
2
    formatter->addParam("a", a);
3551
2
    formatter->addParam("b", a);
3552
2
    formatter->addParam("lat_ts", 0.0);
3553
2
    formatter->addParam("lon_0", centralMeridian);
3554
2
    formatter->addParam("x_0", falseEasting);
3555
2
    formatter->addParam("y_0", falseNorthing);
3556
2
    formatter->addParam("k", 1.0);
3557
2
    formatter->addParam("units", units);
3558
2
    formatter->addParam("nadgrids", "@null");
3559
2
    if (targetProjCRS && targetProjCRS->hasOver()) {
3560
0
        formatter->addParam("over");
3561
0
    }
3562
2
    formatter->addParam("wktext");
3563
2
    formatter->addParam("no_defs");
3564
2
    return true;
3565
2
}
3566
3567
// ---------------------------------------------------------------------------
3568
3569
static bool
3570
createPROJExtensionFromCustomProj(const Conversion *conv,
3571
                                  io::PROJStringFormatter *formatter,
3572
10.8k
                                  bool forExtensionNode) {
3573
10.8k
    const auto &methodName = conv->method()->nameStr();
3574
10.8k
    assert(starts_with(methodName, "PROJ "));
3575
10.8k
    auto tokens = split(methodName, ' ');
3576
3577
10.8k
    formatter->addStep(tokens[1]);
3578
3579
10.8k
    if (forExtensionNode) {
3580
0
        auto sourceCRS = conv->sourceCRS();
3581
0
        auto geogCRS =
3582
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
3583
0
        if (!geogCRS) {
3584
0
            return false;
3585
0
        }
3586
0
        geogCRS->addDatumInfoToPROJString(formatter);
3587
0
    }
3588
3589
61.3k
    for (size_t i = 2; i < tokens.size(); i++) {
3590
50.5k
        auto kv = split(tokens[i], '=');
3591
50.5k
        if (kv.size() == 2) {
3592
19.4k
            formatter->addParam(kv[0], kv[1]);
3593
31.0k
        } else {
3594
31.0k
            formatter->addParam(tokens[i]);
3595
31.0k
        }
3596
50.5k
    }
3597
3598
10.8k
    for (const auto &genOpParamvalue : conv->parameterValues()) {
3599
7.57k
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
3600
7.57k
            genOpParamvalue.get());
3601
7.57k
        if (opParamvalue) {
3602
7.57k
            const auto &paramName = opParamvalue->parameter()->nameStr();
3603
7.57k
            const auto &paramValue = opParamvalue->parameterValue();
3604
7.57k
            if (paramValue->type() == ParameterValue::Type::MEASURE) {
3605
7.55k
                const auto &measure = paramValue->value();
3606
7.55k
                const auto unitType = measure.unit().type();
3607
7.55k
                if (unitType == common::UnitOfMeasure::Type::LINEAR) {
3608
699
                    formatter->addParam(paramName, measure.getSIValue());
3609
6.85k
                } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
3610
6.18k
                    formatter->addParam(
3611
6.18k
                        paramName,
3612
6.18k
                        measure.convertToUnit(common::UnitOfMeasure::DEGREE));
3613
6.18k
                } else {
3614
672
                    formatter->addParam(paramName, measure.value());
3615
672
                }
3616
7.55k
            }
3617
7.57k
        }
3618
7.57k
    }
3619
3620
10.8k
    if (forExtensionNode) {
3621
0
        formatter->addParam("wktext");
3622
0
        formatter->addParam("no_defs");
3623
0
    }
3624
10.8k
    return true;
3625
10.8k
}
3626
//! @endcond
3627
3628
// ---------------------------------------------------------------------------
3629
3630
0
bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
3631
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
3632
0
    if (!isWKT2) {
3633
0
        const auto &l_method = method();
3634
0
        const auto &methodName = l_method->nameStr();
3635
0
        const int methodEPSGCode = l_method->getEPSGCode();
3636
0
        if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
3637
0
            l_method->getPrivate()->projMethodOverride_ == "utm approx") {
3638
0
            auto projFormatter = io::PROJStringFormatter::create();
3639
0
            projFormatter->setCRSExport(true);
3640
0
            projFormatter->setUseApproxTMerc(true);
3641
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
3642
0
            formatter->addQuotedString("PROJ4");
3643
0
            _exportToPROJString(projFormatter.get());
3644
0
            projFormatter->addParam("no_defs");
3645
0
            formatter->addQuotedString(projFormatter->toString());
3646
0
            formatter->endNode();
3647
0
            return true;
3648
0
        } else if (methodEPSGCode ==
3649
0
                       EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
3650
0
                   nameStr() == "Popular Visualisation Mercator") {
3651
3652
0
            auto projFormatter = io::PROJStringFormatter::create();
3653
0
            projFormatter->setCRSExport(true);
3654
0
            if (createPROJ4WebMercator(this, projFormatter.get())) {
3655
0
                formatter->startNode(io::WKTConstants::EXTENSION, false);
3656
0
                formatter->addQuotedString("PROJ4");
3657
0
                formatter->addQuotedString(projFormatter->toString());
3658
0
                formatter->endNode();
3659
0
                return true;
3660
0
            }
3661
0
        } else if (starts_with(methodName, "PROJ ")) {
3662
0
            auto projFormatter = io::PROJStringFormatter::create();
3663
0
            projFormatter->setCRSExport(true);
3664
0
            if (createPROJExtensionFromCustomProj(this, projFormatter.get(),
3665
0
                                                  true)) {
3666
0
                formatter->startNode(io::WKTConstants::EXTENSION, false);
3667
0
                formatter->addQuotedString("PROJ4");
3668
0
                formatter->addQuotedString(projFormatter->toString());
3669
0
                formatter->endNode();
3670
0
                return true;
3671
0
            }
3672
0
        } else if (methodName ==
3673
0
                   PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
3674
0
            auto projFormatter = io::PROJStringFormatter::create();
3675
0
            projFormatter->setCRSExport(true);
3676
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
3677
0
            formatter->addQuotedString("PROJ4");
3678
0
            _exportToPROJString(projFormatter.get());
3679
0
            projFormatter->addParam("no_defs");
3680
0
            formatter->addQuotedString(projFormatter->toString());
3681
0
            formatter->endNode();
3682
0
            return true;
3683
0
        }
3684
0
    }
3685
0
    return false;
3686
0
}
3687
3688
// ---------------------------------------------------------------------------
3689
3690
//! @cond Doxygen_Suppress
3691
void Conversion::_exportToPROJString(
3692
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
3693
32.6k
{
3694
32.6k
    const auto &l_method = method();
3695
32.6k
    const auto &methodName = l_method->nameStr();
3696
32.6k
    const int methodEPSGCode = l_method->getEPSGCode();
3697
32.6k
    const bool isZUnitConversion =
3698
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
3699
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR;
3700
32.6k
    const bool isAffineParametric =
3701
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION;
3702
32.6k
    const bool isSimilarity =
3703
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION;
3704
32.6k
    const bool isGeographicGeocentric =
3705
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC;
3706
32.6k
    const bool isGeographicOffsets =
3707
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
3708
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS ||
3709
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS;
3710
32.6k
    const bool isHeightDepthReversal =
3711
32.6k
        methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL;
3712
32.6k
    const bool applySourceCRSModifiers =
3713
32.6k
        !isZUnitConversion && !isAffineParametric && !isSimilarity &&
3714
32.6k
        !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric &&
3715
32.6k
        !isGeographicOffsets && !isHeightDepthReversal;
3716
32.6k
    bool applyTargetCRSModifiers = applySourceCRSModifiers;
3717
3718
32.6k
    if (formatter->getCRSExport()) {
3719
2.24k
        if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC ||
3720
2.24k
            methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
3721
1
            throw io::FormattingException("Transformation cannot be exported "
3722
1
                                          "as a PROJ.4 string (but can be part "
3723
1
                                          "of a PROJ pipeline)");
3724
1
        }
3725
2.24k
    }
3726
3727
32.6k
    auto l_sourceCRS = sourceCRS();
3728
32.6k
    auto l_targetCRS = targetCRS();
3729
3730
32.6k
    if (methodName == PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) {
3731
3732
204
        const auto extractGeodeticCRSIfGeodeticCRSOrEquivalent =
3733
408
            [](const crs::CRSPtr &crs) {
3734
408
                auto geodCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
3735
408
                if (!geodCRS) {
3736
96
                    auto compoundCRS =
3737
96
                        std::dynamic_pointer_cast<crs::CompoundCRS>(crs);
3738
96
                    if (compoundCRS) {
3739
0
                        const auto &components =
3740
0
                            compoundCRS->componentReferenceSystems();
3741
0
                        if (!components.empty()) {
3742
0
                            geodCRS =
3743
0
                                util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
3744
0
                                    components[0]);
3745
0
                            if (!geodCRS) {
3746
0
                                auto boundCRS = util::nn_dynamic_pointer_cast<
3747
0
                                    crs::BoundCRS>(components[0]);
3748
0
                                if (boundCRS) {
3749
0
                                    geodCRS = util::nn_dynamic_pointer_cast<
3750
0
                                        crs::GeodeticCRS>(boundCRS->baseCRS());
3751
0
                                }
3752
0
                            }
3753
0
                        }
3754
96
                    } else {
3755
96
                        auto boundCRS =
3756
96
                            std::dynamic_pointer_cast<crs::BoundCRS>(crs);
3757
96
                        if (boundCRS) {
3758
96
                            geodCRS =
3759
96
                                util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
3760
96
                                    boundCRS->baseCRS());
3761
96
                        }
3762
96
                    }
3763
96
                }
3764
408
                return geodCRS;
3765
408
            };
3766
3767
204
        auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(
3768
204
            extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_sourceCRS).get());
3769
204
        auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(
3770
204
            extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_targetCRS).get());
3771
204
        if (sourceCRSGeod && targetCRSGeod) {
3772
204
            auto sourceCRSGeog =
3773
204
                dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
3774
204
            auto targetCRSGeog =
3775
204
                dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
3776
204
            bool isSrcGeocentricLat =
3777
204
                sourceCRSGeod->isSphericalPlanetocentric();
3778
204
            bool isSrcGeographic = sourceCRSGeog != nullptr;
3779
204
            bool isTargetGeocentricLat =
3780
204
                targetCRSGeod->isSphericalPlanetocentric();
3781
204
            bool isTargetGeographic = targetCRSGeog != nullptr;
3782
204
            if ((isSrcGeocentricLat && isTargetGeographic) ||
3783
204
                (isSrcGeographic && isTargetGeocentricLat)) {
3784
3785
204
                formatter->setOmitProjLongLatIfPossible(true);
3786
204
                formatter->startInversion();
3787
204
                sourceCRSGeod->_exportToPROJString(formatter);
3788
204
                formatter->stopInversion();
3789
3790
204
                targetCRSGeod->_exportToPROJString(formatter);
3791
204
                formatter->setOmitProjLongLatIfPossible(false);
3792
3793
204
                return;
3794
204
            }
3795
204
        }
3796
3797
0
        throw io::FormattingException("Invalid nature of source and/or "
3798
0
                                      "targetCRS for Geographic latitude / "
3799
0
                                      "Geocentric latitude"
3800
0
                                      "conversion");
3801
204
    }
3802
3803
32.4k
    crs::GeographicCRSPtr srcGeogCRS;
3804
32.4k
    if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) {
3805
3806
12.1k
        crs::CRSPtr horiz = l_sourceCRS;
3807
12.1k
        const auto compound =
3808
12.1k
            dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get());
3809
12.1k
        if (compound) {
3810
1.79k
            const auto &components = compound->componentReferenceSystems();
3811
1.79k
            if (!components.empty()) {
3812
1.79k
                horiz = components.front().as_nullable();
3813
1.79k
                const auto boundCRS =
3814
1.79k
                    dynamic_cast<const crs::BoundCRS *>(horiz.get());
3815
1.79k
                if (boundCRS) {
3816
414
                    horiz = boundCRS->baseCRS().as_nullable();
3817
414
                }
3818
1.79k
            }
3819
1.79k
        }
3820
3821
12.1k
        auto srcGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(horiz.get());
3822
12.1k
        if (srcGeodCRS) {
3823
12.1k
            srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz);
3824
12.1k
        }
3825
12.1k
        if (srcGeodCRS &&
3826
12.1k
            (srcGeogCRS || srcGeodCRS->isSphericalPlanetocentric())) {
3827
12.1k
            formatter->setOmitProjLongLatIfPossible(true);
3828
12.1k
            formatter->startInversion();
3829
12.1k
            srcGeodCRS->_exportToPROJString(formatter);
3830
12.1k
            formatter->stopInversion();
3831
12.1k
            formatter->setOmitProjLongLatIfPossible(false);
3832
12.1k
        }
3833
3834
12.1k
        auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get());
3835
12.1k
        if (projCRS) {
3836
0
            formatter->startInversion();
3837
0
            formatter->pushOmitZUnitConversion();
3838
0
            projCRS->addUnitConvertAndAxisSwap(formatter, false);
3839
0
            formatter->popOmitZUnitConversion();
3840
0
            formatter->stopInversion();
3841
0
        }
3842
12.1k
    }
3843
3844
32.4k
    const auto &convName = nameStr();
3845
32.4k
    bool bConversionDone = false;
3846
32.4k
    bool bEllipsoidParametersDone = false;
3847
32.4k
    bool useApprox = false;
3848
32.4k
    if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
3849
        // Check for UTM
3850
266
        int zone = 0;
3851
266
        bool north = true;
3852
266
        useApprox =
3853
266
            formatter->getUseApproxTMerc() ||
3854
266
            l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
3855
266
            l_method->getPrivate()->projMethodOverride_ == "utm approx";
3856
266
        if (isUTM(zone, north)) {
3857
40
            bConversionDone = true;
3858
40
            formatter->addStep("utm");
3859
40
            if (useApprox) {
3860
0
                formatter->addParam("approx");
3861
0
            }
3862
40
            formatter->addParam("zone", zone);
3863
40
            if (!north) {
3864
8
                formatter->addParam("south");
3865
8
            }
3866
40
        }
3867
32.1k
    } else if (methodEPSGCode ==
3868
32.1k
               EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
3869
42
        const double azimuth =
3870
42
            parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
3871
42
                                  common::UnitOfMeasure::DEGREE);
3872
42
        const double angleRectifiedToSkewGrid = parameterValueNumeric(
3873
42
            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
3874
42
            common::UnitOfMeasure::DEGREE);
3875
        // Map to Swiss Oblique Mercator / somerc
3876
42
        if (std::fabs(azimuth - 90) < 1e-4 &&
3877
42
            std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
3878
0
            bConversionDone = true;
3879
0
            formatter->addStep("somerc");
3880
0
            formatter->addParam(
3881
0
                "lat_0", parameterValueNumeric(
3882
0
                             EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
3883
0
                             common::UnitOfMeasure::DEGREE));
3884
0
            formatter->addParam(
3885
0
                "lon_0", parameterValueNumeric(
3886
0
                             EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
3887
0
                             common::UnitOfMeasure::DEGREE));
3888
0
            formatter->addParam(
3889
0
                "k_0", parameterValueNumericAsSI(
3890
0
                           EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
3891
0
            formatter->addParam("x_0", parameterValueNumericAsSI(
3892
0
                                           EPSG_CODE_PARAMETER_FALSE_EASTING));
3893
0
            formatter->addParam("y_0", parameterValueNumericAsSI(
3894
0
                                           EPSG_CODE_PARAMETER_FALSE_NORTHING));
3895
0
        }
3896
32.1k
    } else if (methodEPSGCode ==
3897
32.1k
               EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
3898
114
        const double azimuth =
3899
114
            parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_INITIAL_LINE,
3900
114
                                  common::UnitOfMeasure::DEGREE);
3901
114
        const double angleRectifiedToSkewGrid = parameterValueNumeric(
3902
114
            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
3903
114
            common::UnitOfMeasure::DEGREE);
3904
        // Map to Swiss Oblique Mercator / somerc
3905
114
        if (std::fabs(azimuth - 90) < 1e-4 &&
3906
114
            std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
3907
57
            bConversionDone = true;
3908
57
            formatter->addStep("somerc");
3909
57
            formatter->addParam(
3910
57
                "lat_0", parameterValueNumeric(
3911
57
                             EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
3912
57
                             common::UnitOfMeasure::DEGREE));
3913
57
            formatter->addParam(
3914
57
                "lon_0", parameterValueNumeric(
3915
57
                             EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
3916
57
                             common::UnitOfMeasure::DEGREE));
3917
57
            formatter->addParam(
3918
57
                "k_0", parameterValueNumericAsSI(
3919
57
                           EPSG_CODE_PARAMETER_SCALE_FACTOR_INITIAL_LINE));
3920
57
            formatter->addParam(
3921
57
                "x_0", parameterValueNumericAsSI(
3922
57
                           EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE));
3923
57
            formatter->addParam(
3924
57
                "y_0", parameterValueNumericAsSI(
3925
57
                           EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE));
3926
57
        }
3927
31.9k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED ||
3928
31.9k
               methodEPSGCode ==
3929
30.6k
                   EPSG_CODE_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED) {
3930
1.36k
        double colatitude =
3931
1.36k
            parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS,
3932
1.36k
                                  common::UnitOfMeasure::DEGREE);
3933
1.36k
        double latitudePseudoStandardParallel = parameterValueNumeric(
3934
1.36k
            EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
3935
1.36k
            common::UnitOfMeasure::DEGREE);
3936
        // 30deg 17' 17.30311'' = 30.28813975277777776
3937
        // 30deg 17' 17.303''   = 30.288139722222223 as used in GDAL WKT1
3938
1.36k
        if (std::fabs(colatitude - 30.2881397) > 1e-7) {
3939
5
            throw io::FormattingException(
3940
5
                std::string("Unsupported value for ") +
3941
5
                EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS);
3942
5
        }
3943
1.35k
        if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) {
3944
0
            throw io::FormattingException(
3945
0
                std::string("Unsupported value for ") +
3946
0
                EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL);
3947
0
        }
3948
30.6k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
3949
327
        double latitudeOrigin = parameterValueNumeric(
3950
327
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
3951
327
            common::UnitOfMeasure::DEGREE);
3952
327
        if (latitudeOrigin != 0) {
3953
0
            throw io::FormattingException(
3954
0
                std::string("Unsupported value for ") +
3955
0
                EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
3956
0
        }
3957
30.3k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) {
3958
18
        const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0);
3959
18
        if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
3960
18
            std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
3961
0
            throw io::FormattingException(
3962
0
                "Unexpected presence of scale factor in Mercator (variant B)");
3963
0
        }
3964
18
        double latitudeOrigin = parameterValueNumeric(
3965
18
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
3966
18
            common::UnitOfMeasure::DEGREE);
3967
18
        if (latitudeOrigin != 0) {
3968
0
            throw io::FormattingException(
3969
0
                std::string("Unsupported value for ") +
3970
0
                EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
3971
0
        }
3972
30.2k
    } else if (methodEPSGCode ==
3973
30.2k
               EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
3974
        // We map TMSO to tmerc with axis=wsu. This only works if false easting
3975
        // and northings are zero, which is the case in practice for South
3976
        // African and Namibian EPSG CRS
3977
26
        const auto falseEasting = parameterValueNumeric(
3978
26
            EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE);
3979
26
        if (falseEasting != 0) {
3980
3
            throw io::FormattingException(
3981
3
                std::string("Unsupported value for ") +
3982
3
                EPSG_NAME_PARAMETER_FALSE_EASTING);
3983
3
        }
3984
23
        const auto falseNorthing = parameterValueNumeric(
3985
23
            EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE);
3986
23
        if (falseNorthing != 0) {
3987
3
            throw io::FormattingException(
3988
3
                std::string("Unsupported value for ") +
3989
3
                EPSG_NAME_PARAMETER_FALSE_NORTHING);
3990
3
        }
3991
        // PROJ.4 specific hack for webmercator
3992
30.2k
    } else if (formatter->getCRSExport() &&
3993
30.2k
               methodEPSGCode ==
3994
2.17k
                   EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
3995
3
        if (!createPROJ4WebMercator(this, formatter)) {
3996
1
            throw io::FormattingException(
3997
1
                std::string("Cannot export ") +
3998
1
                EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR +
3999
1
                " as PROJ.4 string outside of a ProjectedCRS context");
4000
1
        }
4001
2
        bConversionDone = true;
4002
2
        bEllipsoidParametersDone = true;
4003
2
        applyTargetCRSModifiers = false;
4004
30.2k
    } else if (ci_equal(convName, "Popular Visualisation Mercator")) {
4005
0
        if (formatter->getCRSExport()) {
4006
0
            if (!createPROJ4WebMercator(this, formatter)) {
4007
0
                throw io::FormattingException(concat(
4008
0
                    "Cannot export ", convName,
4009
0
                    " as PROJ.4 string outside of a ProjectedCRS context"));
4010
0
            }
4011
0
            applyTargetCRSModifiers = false;
4012
0
        } else {
4013
0
            formatter->addStep("webmerc");
4014
0
            if (l_sourceCRS) {
4015
0
                datum::Ellipsoid::WGS84->_exportToPROJString(formatter);
4016
0
            }
4017
0
        }
4018
0
        bConversionDone = true;
4019
0
        bEllipsoidParametersDone = true;
4020
30.2k
    } else if (starts_with(methodName, "PROJ ")) {
4021
10.8k
        bConversionDone = true;
4022
10.8k
        createPROJExtensionFromCustomProj(this, formatter, false);
4023
19.4k
    } else if (ci_equal(methodName,
4024
19.4k
                        PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
4025
1
        double southPoleLat = parameterValueNumeric(
4026
1
            PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION,
4027
1
            common::UnitOfMeasure::DEGREE);
4028
1
        double southPoleLong = parameterValueNumeric(
4029
1
            PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION,
4030
1
            common::UnitOfMeasure::DEGREE);
4031
1
        double rotation = parameterValueNumeric(
4032
1
            PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION,
4033
1
            common::UnitOfMeasure::DEGREE);
4034
1
        formatter->addStep("ob_tran");
4035
1
        formatter->addParam("o_proj", "longlat");
4036
1
        formatter->addParam("o_lon_p", -rotation);
4037
1
        formatter->addParam("o_lat_p", -southPoleLat);
4038
1
        formatter->addParam("lon_0", southPoleLong);
4039
1
        bConversionDone = true;
4040
19.4k
    } else if (ci_equal(
4041
19.4k
                   methodName,
4042
19.4k
                   PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) {
4043
0
        double gridNorthPoleLatitude = parameterValueNumeric(
4044
0
            PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION,
4045
0
            common::UnitOfMeasure::DEGREE);
4046
0
        double gridNorthPoleLongitude = parameterValueNumeric(
4047
0
            PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION,
4048
0
            common::UnitOfMeasure::DEGREE);
4049
0
        double northPoleGridLongitude = parameterValueNumeric(
4050
0
            PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION,
4051
0
            common::UnitOfMeasure::DEGREE);
4052
0
        formatter->addStep("ob_tran");
4053
0
        formatter->addParam("o_proj", "longlat");
4054
0
        formatter->addParam("o_lon_p", northPoleGridLongitude);
4055
0
        formatter->addParam("o_lat_p", gridNorthPoleLatitude);
4056
0
        formatter->addParam("lon_0", 180 + gridNorthPoleLongitude);
4057
0
        bConversionDone = true;
4058
19.4k
    } else if (ci_equal(methodName, "Adams_Square_II")) {
4059
        // Look for ESRI method and parameter names (to be opposed
4060
        // to the OGC WKT2 names we use elsewhere, because there's no mapping
4061
        // of those parameters to OGC WKT2)
4062
        // We also reject non-default values for a number of parameters,
4063
        // because they are not implemented on PROJ side. The subset we
4064
        // support can handle ESRI:54098 WGS_1984_Adams_Square_II, but not
4065
        // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square
4066
0
        const double falseEasting = parameterValueNumeric(
4067
0
            "False_Easting", common::UnitOfMeasure::METRE);
4068
0
        const double falseNorthing = parameterValueNumeric(
4069
0
            "False_Northing", common::UnitOfMeasure::METRE);
4070
0
        const double scaleFactor =
4071
0
            parameterValue("Scale_Factor", 0)
4072
0
                ? parameterValueNumeric("Scale_Factor",
4073
0
                                        common::UnitOfMeasure::SCALE_UNITY)
4074
0
                : 1.0;
4075
0
        const double azimuth =
4076
0
            parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE);
4077
0
        const double longitudeOfCenter = parameterValueNumeric(
4078
0
            "Longitude_Of_Center", common::UnitOfMeasure::DEGREE);
4079
0
        const double latitudeOfCenter = parameterValueNumeric(
4080
0
            "Latitude_Of_Center", common::UnitOfMeasure::DEGREE);
4081
0
        const double XYPlaneRotation = parameterValueNumeric(
4082
0
            "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE);
4083
0
        if (scaleFactor != 1.0 || azimuth != 0.0 || latitudeOfCenter != 0.0 ||
4084
0
            XYPlaneRotation != 0.0) {
4085
0
            throw io::FormattingException("Unsupported value for one or "
4086
0
                                          "several parameters of "
4087
0
                                          "Adams_Square_II");
4088
0
        }
4089
0
        formatter->addStep("adams_ws2");
4090
0
        formatter->addParam("lon_0", longitudeOfCenter);
4091
0
        formatter->addParam("x_0", falseEasting);
4092
0
        formatter->addParam("y_0", falseNorthing);
4093
0
        bConversionDone = true;
4094
19.4k
    } else if (ci_equal(methodName,
4095
19.4k
                        PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE) ||
4096
19.4k
               ci_equal(methodName,
4097
19.4k
                        PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND)) {
4098
22
        const auto &scaleFactor = parameterValueMeasure(
4099
22
            EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4100
22
        if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
4101
22
            std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
4102
0
            throw io::FormattingException(
4103
0
                "Only scale factor = 1 handled for Peirce Quincuncial");
4104
0
        }
4105
22
        const auto &latitudeOfOriginDeg = parameterValueMeasure(
4106
22
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4107
22
        if (latitudeOfOriginDeg.unit().type() !=
4108
22
                common::UnitOfMeasure::Type::UNKNOWN &&
4109
22
            std::fabs(parameterValueNumeric(
4110
10
                          EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4111
10
                          common::UnitOfMeasure::DEGREE) -
4112
10
                      90.0) > 1e-10) {
4113
0
            throw io::FormattingException("Only latitude of natural origin = "
4114
0
                                          "90 handled for Peirce Quincuncial");
4115
0
        }
4116
19.3k
    } else if (formatter->convention() ==
4117
19.3k
                   io::PROJStringFormatter::Convention::PROJ_5 &&
4118
19.3k
               isZUnitConversion) {
4119
5.11k
        double convFactor;
4120
5.11k
        if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
4121
5.11k
            convFactor = parameterValueNumericAsSI(
4122
5.11k
                EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
4123
5.11k
        } else {
4124
2
            assert(methodEPSGCode ==
4125
2
                   EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR);
4126
2
            const auto vertSrcCRS =
4127
2
                dynamic_cast<const crs::VerticalCRS *>(l_sourceCRS.get());
4128
2
            const auto vertTgtCRS =
4129
2
                dynamic_cast<const crs::VerticalCRS *>(l_targetCRS.get());
4130
2
            if (vertSrcCRS && vertTgtCRS) {
4131
0
                const double convSrc = vertSrcCRS->coordinateSystem()
4132
0
                                           ->axisList()[0]
4133
0
                                           ->unit()
4134
0
                                           .conversionToSI();
4135
0
                const double convDst = vertTgtCRS->coordinateSystem()
4136
0
                                           ->axisList()[0]
4137
0
                                           ->unit()
4138
0
                                           .conversionToSI();
4139
0
                convFactor = convSrc / convDst;
4140
2
            } else {
4141
2
                throw io::FormattingException(
4142
2
                    "Export of "
4143
2
                    "EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR "
4144
2
                    "conversion to a PROJ string "
4145
2
                    "requires an input and output vertical CRS");
4146
2
            }
4147
2
        }
4148
5.11k
        exportToPROJStringChangeVerticalUnit(formatter, convFactor);
4149
5.11k
        bConversionDone = true;
4150
5.11k
        bEllipsoidParametersDone = true;
4151
14.2k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
4152
3
        if (!srcGeogCRS) {
4153
3
            throw io::FormattingException(
4154
3
                "Export of Geographic/Topocentric conversion to a PROJ string "
4155
3
                "requires an input geographic CRS");
4156
3
        }
4157
4158
0
        formatter->addStep("cart");
4159
0
        srcGeogCRS->ellipsoid()->_exportToPROJString(formatter);
4160
4161
0
        formatter->addStep("topocentric");
4162
0
        const auto latOrigin = parameterValueNumeric(
4163
0
            EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
4164
0
            common::UnitOfMeasure::DEGREE);
4165
0
        const auto longOrigin = parameterValueNumeric(
4166
0
            EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
4167
0
            common::UnitOfMeasure::DEGREE);
4168
0
        const auto heightOrigin = parameterValueNumeric(
4169
0
            EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
4170
0
            common::UnitOfMeasure::METRE);
4171
0
        formatter->addParam("lat_0", latOrigin);
4172
0
        formatter->addParam("lon_0", longOrigin);
4173
0
        formatter->addParam("h_0", heightOrigin);
4174
0
        bConversionDone = true;
4175
0
    }
4176
4177
32.4k
    bool bAxisSpecFound = false;
4178
32.4k
    if (!bConversionDone) {
4179
16.3k
        const MethodMapping *mapping = getMapping(l_method.get());
4180
16.3k
        if (mapping && mapping->proj_name_main) {
4181
3.57k
            formatter->addStep(mapping->proj_name_main);
4182
3.57k
            if (useApprox) {
4183
10
                formatter->addParam("approx");
4184
10
            }
4185
3.57k
            if (mapping->proj_name_aux) {
4186
326
                bool addAux = true;
4187
326
                if (internal::starts_with(mapping->proj_name_aux, "axis=")) {
4188
143
                    if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK ||
4189
143
                        mapping->epsg_code ==
4190
123
                            EPSG_CODE_METHOD_KROVAK_MODIFIED) {
4191
123
                        auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(
4192
123
                            l_targetCRS.get());
4193
123
                        if (projCRS) {
4194
94
                            const auto &axisList =
4195
94
                                projCRS->coordinateSystem()->axisList();
4196
94
                            if (axisList[0]->direction() ==
4197
94
                                    cs::AxisDirection::WEST &&
4198
94
                                axisList[1]->direction() ==
4199
59
                                    cs::AxisDirection::SOUTH) {
4200
59
                                formatter->addParam("czech");
4201
59
                                addAux = false;
4202
59
                            }
4203
94
                        }
4204
123
                    }
4205
143
                    bAxisSpecFound = true;
4206
143
                }
4207
4208
                // No need to add explicit f=0 or R_A if the ellipsoid is a
4209
                // sphere
4210
326
                if (strcmp(mapping->proj_name_aux, "f=0") == 0 ||
4211
326
                    strcmp(mapping->proj_name_aux, "R_A") == 0) {
4212
80
                    crs::CRS *horiz = l_sourceCRS.get();
4213
80
                    const auto compound =
4214
80
                        dynamic_cast<const crs::CompoundCRS *>(horiz);
4215
80
                    if (compound) {
4216
12
                        const auto &components =
4217
12
                            compound->componentReferenceSystems();
4218
12
                        if (!components.empty()) {
4219
12
                            horiz = components.front().get();
4220
12
                            const auto boundCRS =
4221
12
                                dynamic_cast<const crs::BoundCRS *>(horiz);
4222
12
                            if (boundCRS) {
4223
10
                                horiz = boundCRS->baseCRS().get();
4224
10
                            }
4225
12
                        }
4226
12
                    }
4227
4228
80
                    auto geogCRS =
4229
80
                        dynamic_cast<const crs::GeographicCRS *>(horiz);
4230
80
                    if (geogCRS && geogCRS->ellipsoid()->isSphere()) {
4231
45
                        addAux = false;
4232
45
                    }
4233
80
                }
4234
4235
326
                if (addAux) {
4236
222
                    auto kv = split(mapping->proj_name_aux, '=');
4237
222
                    if (kv.size() == 2) {
4238
112
                        formatter->addParam(kv[0], kv[1]);
4239
112
                    } else {
4240
110
                        formatter->addParam(mapping->proj_name_aux);
4241
110
                    }
4242
222
                }
4243
326
            }
4244
4245
3.57k
            if (mapping->epsg_code ==
4246
3.57k
                EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4247
10
                double latitudeStdParallel = parameterValueNumeric(
4248
10
                    EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4249
10
                    common::UnitOfMeasure::DEGREE);
4250
10
                formatter->addParam("lat_0",
4251
10
                                    (latitudeStdParallel >= 0) ? 90.0 : -90.0);
4252
10
            }
4253
4254
23.8k
            for (int i = 0; mapping->params[i] != nullptr; i++) {
4255
20.2k
                const auto *param = mapping->params[i];
4256
20.2k
                if (!param->proj_name) {
4257
1.80k
                    continue;
4258
1.80k
                }
4259
18.4k
                const auto &value =
4260
18.4k
                    parameterValueMeasure(param->wkt2_name, param->epsg_code);
4261
18.4k
                double valueConverted = 0;
4262
18.4k
                if (value == nullMeasure) {
4263
                    // Deal with missing values. In an ideal world, this would
4264
                    // not happen
4265
2.04k
                    if (param->epsg_code ==
4266
2.04k
                        EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4267
145
                        valueConverted = 1.0;
4268
145
                    }
4269
2.04k
                    if ((mapping->epsg_code ==
4270
2.04k
                             EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4271
2.04k
                         mapping->epsg_code ==
4272
1.82k
                             EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
4273
2.04k
                        param->epsg_code ==
4274
348
                            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
4275
                        // Do not use 0 as the default value for +gamma of
4276
                        // proj=omerc
4277
28
                        continue;
4278
28
                    }
4279
16.4k
                } else if (param->unit_type ==
4280
16.4k
                           common::UnitOfMeasure::Type::ANGULAR) {
4281
7.81k
                    valueConverted =
4282
7.81k
                        value.convertToUnit(common::UnitOfMeasure::DEGREE);
4283
8.61k
                } else {
4284
8.61k
                    valueConverted = value.getSIValue();
4285
8.61k
                }
4286
4287
18.4k
                if (mapping->epsg_code ==
4288
18.4k
                        EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
4289
18.4k
                    strcmp(param->proj_name, "lat_1") == 0) {
4290
15
                    formatter->addParam(param->proj_name, valueConverted);
4291
15
                    formatter->addParam("lat_0", valueConverted);
4292
18.4k
                } else {
4293
18.4k
                    formatter->addParam(param->proj_name, valueConverted);
4294
18.4k
                }
4295
18.4k
            }
4296
4297
12.7k
        } else {
4298
12.7k
            if (!exportToPROJStringGeneric(formatter)) {
4299
1.24k
                throw io::FormattingException(
4300
1.24k
                    concat("Unsupported conversion method: ", methodName));
4301
1.24k
            }
4302
12.7k
        }
4303
16.3k
    }
4304
4305
31.1k
    if (l_targetCRS && applyTargetCRSModifiers) {
4306
14.2k
        crs::CRS *horiz = l_targetCRS.get();
4307
14.2k
        const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz);
4308
14.2k
        if (compound) {
4309
1.81k
            const auto &components = compound->componentReferenceSystems();
4310
1.81k
            if (!components.empty()) {
4311
1.81k
                horiz = components.front().get();
4312
1.81k
            }
4313
1.81k
        }
4314
4315
14.2k
        auto derivedProjCRS =
4316
14.2k
            dynamic_cast<const crs::DerivedProjectedCRS *>(horiz);
4317
4318
        // horiz != nullptr: only to make clang static analyzer happy
4319
14.2k
        if (!bEllipsoidParametersDone && horiz != nullptr &&
4320
14.2k
            derivedProjCRS == nullptr) {
4321
14.2k
            auto targetGeodCRS = horiz->extractGeodeticCRS();
4322
14.2k
            auto targetGeogCRS =
4323
14.2k
                std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS);
4324
14.2k
            if (targetGeogCRS) {
4325
14.0k
                if (formatter->getCRSExport()) {
4326
2.18k
                    targetGeogCRS->addDatumInfoToPROJString(formatter);
4327
11.8k
                } else {
4328
11.8k
                    targetGeogCRS->ellipsoid()->_exportToPROJString(formatter);
4329
11.8k
                    targetGeogCRS->primeMeridian()->_exportToPROJString(
4330
11.8k
                        formatter);
4331
11.8k
                }
4332
14.0k
            } else if (targetGeodCRS) {
4333
231
                targetGeodCRS->ellipsoid()->_exportToPROJString(formatter);
4334
231
            }
4335
14.2k
        }
4336
4337
14.2k
        auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz);
4338
14.2k
        if (projCRS == nullptr) {
4339
5.11k
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(horiz);
4340
5.11k
            if (boundCRS) {
4341
486
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
4342
486
                    boundCRS->baseCRS().get());
4343
486
            }
4344
5.11k
        }
4345
14.2k
        if (projCRS) {
4346
9.47k
            formatter->pushOmitZUnitConversion();
4347
9.47k
            projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound);
4348
9.47k
            formatter->popOmitZUnitConversion();
4349
9.47k
            if (projCRS->hasOver()) {
4350
0
                formatter->addParam("over");
4351
0
            }
4352
9.47k
        } else {
4353
4.76k
            if (derivedProjCRS) {
4354
0
                formatter->pushOmitZUnitConversion();
4355
0
                derivedProjCRS->addUnitConvertAndAxisSwap(formatter);
4356
0
                formatter->popOmitZUnitConversion();
4357
0
            }
4358
4.76k
        }
4359
4360
14.2k
        auto derivedGeographicCRS =
4361
14.2k
            dynamic_cast<const crs::DerivedGeographicCRS *>(horiz);
4362
14.2k
        if (!formatter->getCRSExport() && derivedGeographicCRS) {
4363
3.41k
            formatter->setOmitProjLongLatIfPossible(true);
4364
3.41k
            derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter);
4365
3.41k
            formatter->setOmitProjLongLatIfPossible(false);
4366
3.41k
        }
4367
14.2k
    }
4368
31.1k
}
4369
//! @endcond
4370
4371
// ---------------------------------------------------------------------------
4372
4373
/** \brief Return whether a conversion is a
4374
 * <a href="../../../operations/projections/utm.html">
4375
 * Universal Transverse Mercator</a> conversion.
4376
 *
4377
 * @param[out] zone UTM zone number between 1 and 60.
4378
 * @param[out] north true for UTM northern hemisphere, false for UTM southern
4379
 * hemisphere.
4380
 * @return true if it is a UTM conversion.
4381
 */
4382
309
bool Conversion::isUTM(int &zone, bool &north) const {
4383
309
    zone = 0;
4384
309
    north = true;
4385
4386
309
    if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
4387
        // Check for UTM
4388
4389
309
        bool bLatitudeNatOriginUTM = false;
4390
309
        bool bScaleFactorUTM = false;
4391
309
        bool bFalseEastingUTM = false;
4392
309
        bool bFalseNorthingUTM = false;
4393
1.12k
        for (const auto &genOpParamvalue : parameterValues()) {
4394
1.12k
            auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
4395
1.12k
                genOpParamvalue.get());
4396
1.12k
            if (opParamvalue) {
4397
1.12k
                const auto epsg_code = opParamvalue->parameter()->getEPSGCode();
4398
1.12k
                const auto &l_parameterValue = opParamvalue->parameterValue();
4399
1.12k
                if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
4400
901
                    const auto &measure = l_parameterValue->value();
4401
901
                    if (epsg_code ==
4402
901
                            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN &&
4403
901
                        std::fabs(measure.value() -
4404
145
                                  UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) {
4405
131
                        bLatitudeNatOriginUTM = true;
4406
770
                    } else if (
4407
770
                        (epsg_code ==
4408
770
                             EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN ||
4409
770
                         epsg_code ==
4410
625
                             EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) &&
4411
770
                        measure.unit()._isEquivalentTo(
4412
193
                            common::UnitOfMeasure::DEGREE,
4413
193
                            util::IComparable::Criterion::EQUIVALENT)) {
4414
175
                        double dfZone = (measure.value() + 183.0) / 6.0;
4415
175
                        if (dfZone > 0.9 && dfZone < 60.1 &&
4416
175
                            std::abs(dfZone - std::round(dfZone)) < 1e-10) {
4417
43
                            zone = static_cast<int>(std::lround(dfZone));
4418
43
                        }
4419
595
                    } else if (
4420
595
                        epsg_code ==
4421
595
                            EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
4422
595
                        measure.unit()._isEquivalentTo(
4423
167
                            common::UnitOfMeasure::SCALE_UNITY,
4424
167
                            util::IComparable::Criterion::EQUIVALENT) &&
4425
595
                        std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) {
4426
46
                        bScaleFactorUTM = true;
4427
549
                    } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
4428
549
                               measure.value() == UTM_FALSE_EASTING &&
4429
549
                               measure.unit()._isEquivalentTo(
4430
46
                                   common::UnitOfMeasure::METRE,
4431
46
                                   util::IComparable::Criterion::EQUIVALENT)) {
4432
46
                        bFalseEastingUTM = true;
4433
503
                    } else if (epsg_code ==
4434
503
                                   EPSG_CODE_PARAMETER_FALSE_NORTHING &&
4435
503
                               measure.unit()._isEquivalentTo(
4436
145
                                   common::UnitOfMeasure::METRE,
4437
145
                                   util::IComparable::Criterion::EQUIVALENT)) {
4438
132
                        if (std::fabs(measure.value() -
4439
132
                                      UTM_NORTH_FALSE_NORTHING) < 1e-10) {
4440
117
                            bFalseNorthingUTM = true;
4441
117
                            north = true;
4442
117
                        } else if (std::fabs(measure.value() -
4443
15
                                             UTM_SOUTH_FALSE_NORTHING) <
4444
15
                                   1e-10) {
4445
10
                            bFalseNorthingUTM = true;
4446
10
                            north = false;
4447
10
                        }
4448
132
                    }
4449
901
                }
4450
1.12k
            }
4451
1.12k
        }
4452
309
        if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM &&
4453
309
            bFalseEastingUTM && bFalseNorthingUTM) {
4454
40
            return true;
4455
40
        }
4456
309
    }
4457
269
    return false;
4458
309
}
4459
4460
// ---------------------------------------------------------------------------
4461
4462
/** \brief Return a Conversion object where some parameters are better
4463
 * identified.
4464
 *
4465
 * @return a new Conversion.
4466
 */
4467
844
ConversionNNPtr Conversion::identify() const {
4468
844
    auto newConversion = Conversion::nn_make_shared<Conversion>(*this);
4469
844
    newConversion->assignSelf(newConversion);
4470
4471
844
    if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
4472
        // Check for UTM
4473
43
        int zone = 0;
4474
43
        bool north = true;
4475
43
        if (isUTM(zone, north)) {
4476
0
            newConversion->setProperties(
4477
0
                getUTMConversionProperty(util::PropertyMap(), zone, north));
4478
0
        }
4479
43
    }
4480
4481
844
    return newConversion;
4482
844
}
4483
4484
// ---------------------------------------------------------------------------
4485
4486
/** \brief Instantiate a conversion with method Geographic 2D offsets
4487
 *
4488
 * This method is defined as
4489
 * <a href="https://epsg.org/coord-operation-method_9619/index.html">
4490
 * EPSG:9619</a>.
4491
 *
4492
 * @param properties See \ref general_properties of the conversion.
4493
 * At minimum the name should be defined.
4494
 * @param offsetLat Latitude offset to add.
4495
 * @param offsetLong Longitude offset to add.
4496
 * @return new conversion.
4497
 */
4498
ConversionNNPtr
4499
Conversion::createGeographic2DOffsets(const util::PropertyMap &properties,
4500
                                      const common::Angle &offsetLat,
4501
0
                                      const common::Angle &offsetLong) {
4502
0
    return create(
4503
0
        properties,
4504
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
4505
0
        VectorOfParameters{
4506
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
4507
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
4508
0
        VectorOfValues{offsetLat, offsetLong});
4509
0
}
4510
4511
// ---------------------------------------------------------------------------
4512
4513
/** \brief Instantiate a conversion with method Geographic 3D offsets
4514
 *
4515
 * This method is defined as
4516
 * <a href="https://epsg.org/coord-operation-method_9660/index.html">
4517
 * EPSG:9660</a>.
4518
 *
4519
 * @param properties See \ref general_properties of the Conversion.
4520
 * At minimum the name should be defined.
4521
 * @param offsetLat Latitude offset to add.
4522
 * @param offsetLong Longitude offset to add.
4523
 * @param offsetHeight Height offset to add.
4524
 * @return new Conversion.
4525
 */
4526
ConversionNNPtr Conversion::createGeographic3DOffsets(
4527
    const util::PropertyMap &properties, const common::Angle &offsetLat,
4528
0
    const common::Angle &offsetLong, const common::Length &offsetHeight) {
4529
0
    return create(
4530
0
        properties,
4531
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
4532
0
        VectorOfParameters{
4533
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
4534
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
4535
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
4536
0
        VectorOfValues{offsetLat, offsetLong, offsetHeight});
4537
0
}
4538
4539
// ---------------------------------------------------------------------------
4540
4541
/** \brief Instantiate a conversion with method Geographic 2D with
4542
 * height
4543
 * offsets
4544
 *
4545
 * This method is defined as
4546
 * <a href="https://epsg.org/coord-operation-method_9618/index.html">
4547
 * EPSG:9618</a>.
4548
 *
4549
 * @param properties See \ref general_properties of the Conversion.
4550
 * At minimum the name should be defined.
4551
 * @param offsetLat Latitude offset to add.
4552
 * @param offsetLong Longitude offset to add.
4553
 * @param offsetHeight Geoid undulation to add.
4554
 * @return new Conversion.
4555
 */
4556
ConversionNNPtr Conversion::createGeographic2DWithHeightOffsets(
4557
    const util::PropertyMap &properties, const common::Angle &offsetLat,
4558
0
    const common::Angle &offsetLong, const common::Length &offsetHeight) {
4559
0
    return create(
4560
0
        properties,
4561
0
        createMethodMapNameEPSGCode(
4562
0
            EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
4563
0
        VectorOfParameters{
4564
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
4565
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
4566
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)},
4567
0
        VectorOfValues{offsetLat, offsetLong, offsetHeight});
4568
0
}
4569
4570
// ---------------------------------------------------------------------------
4571
4572
/** \brief Instantiate a conversion with method Vertical Offset.
4573
 *
4574
 * This method is defined as
4575
 * <a href="https://epsg.org/coord-operation-method_9616/index.html">
4576
 * EPSG:9616</a>.
4577
 *
4578
 * @param properties See \ref general_properties of the Conversion.
4579
 * At minimum the name should be defined.
4580
 * @param offsetHeight Geoid undulation to add.
4581
 * @return new Conversion.
4582
 */
4583
ConversionNNPtr
4584
Conversion::createVerticalOffset(const util::PropertyMap &properties,
4585
0
                                 const common::Length &offsetHeight) {
4586
0
    return create(properties,
4587
0
                  createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
4588
0
                  VectorOfParameters{createOpParamNameEPSGCode(
4589
0
                      EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
4590
0
                  VectorOfValues{offsetHeight});
4591
0
}
4592
4593
// ---------------------------------------------------------------------------
4594
4595
} // namespace operation
4596
NS_PROJ_END