Coverage Report

Created: 2026-07-16 06:21

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