Coverage Report

Created: 2025-06-20 06:58

/src/PROJ/src/iso19111/operation/conversion.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include "proj/common.hpp"
34
#include "proj/coordinateoperation.hpp"
35
#include "proj/crs.hpp"
36
#include "proj/io.hpp"
37
#include "proj/metadata.hpp"
38
#include "proj/util.hpp"
39
40
#include "proj/internal/internal.hpp"
41
#include "proj/internal/io_internal.hpp"
42
#include "proj/internal/tracing.hpp"
43
44
#include "coordinateoperation_internal.hpp"
45
#include "esriparammappings.hpp"
46
#include "operationmethod_private.hpp"
47
#include "oputils.hpp"
48
#include "parammappings.hpp"
49
#include "vectorofvaluesparams.hpp"
50
51
// PROJ include order is sensitive
52
// clang-format off
53
#include "proj.h"
54
#include "proj_internal.h" // M_PI
55
// clang-format on
56
#include "proj_constants.h"
57
58
#include "proj_json_streaming_writer.hpp"
59
60
#include <algorithm>
61
#include <cassert>
62
#include <cmath>
63
#include <cstring>
64
#include <memory>
65
#include <set>
66
#include <string>
67
#include <vector>
68
69
using namespace NS_PROJ::internal;
70
71
// ---------------------------------------------------------------------------
72
73
NS_PROJ_START
74
namespace operation {
75
76
//! @cond Doxygen_Suppress
77
constexpr double UTM_LATITUDE_OF_NATURAL_ORIGIN = 0.0;
78
constexpr double UTM_SCALE_FACTOR = 0.9996;
79
constexpr double UTM_FALSE_EASTING = 500000.0;
80
constexpr double UTM_NORTH_FALSE_NORTHING = 0.0;
81
constexpr double UTM_SOUTH_FALSE_NORTHING = 10000000.0;
82
83
//! @endcond
84
85
// ---------------------------------------------------------------------------
86
87
//! @cond Doxygen_Suppress
88
struct Conversion::Private {};
89
//! @endcond
90
91
// ---------------------------------------------------------------------------
92
93
Conversion::Conversion(const OperationMethodNNPtr &methodIn,
94
                       const std::vector<GeneralParameterValueNNPtr> &values)
95
361k
    : SingleOperation(methodIn), d(nullptr) {
96
361k
    setParameterValues(values);
97
361k
}
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
68.3k
    : SingleOperation(methodIn), d(nullptr) {
96
68.3k
    setParameterValues(values);
97
68.3k
}
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
293k
    : SingleOperation(methodIn), d(nullptr) {
96
293k
    setParameterValues(values);
97
293k
}
98
99
// ---------------------------------------------------------------------------
100
101
Conversion::Conversion(const Conversion &other)
102
80.3k
    : 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
80.3k
    : CoordinateOperation(other), SingleOperation(other), d(nullptr) {}
103
104
// ---------------------------------------------------------------------------
105
106
//! @cond Doxygen_Suppress
107
441k
Conversion::~Conversion() = default;
108
//! @endcond
109
110
// ---------------------------------------------------------------------------
111
112
//! @cond Doxygen_Suppress
113
79.8k
ConversionNNPtr Conversion::shallowClone() const {
114
79.8k
    auto conv = Conversion::nn_make_shared<Conversion>(*this);
115
79.8k
    conv->assignSelf(conv);
116
79.8k
    conv->setCRSs(this, false);
117
79.8k
    return conv;
118
79.8k
}
119
120
48.2k
CoordinateOperationNNPtr Conversion::_shallowClone() const {
121
48.2k
    return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
122
48.2k
}
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
293k
{
193
293k
    if (methodIn->parameters().size() != values.size()) {
194
0
        throw InvalidOperation(
195
0
            "Inconsistent number of parameters and parameter values");
196
0
    }
197
293k
    auto conv = Conversion::nn_make_shared<Conversion>(methodIn, values);
198
293k
    conv->assignSelf(conv);
199
293k
    conv->setProperties(properties);
200
293k
    return conv;
201
293k
}
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
292k
{
223
292k
    OperationMethodNNPtr op(
224
292k
        OperationMethod::create(propertiesOperationMethod, parameters));
225
226
292k
    if (parameters.size() != values.size()) {
227
0
        throw InvalidOperation(
228
0
            "Inconsistent number of parameters and parameter values");
229
0
    }
230
292k
    std::vector<GeneralParameterValueNNPtr> generalParameterValues;
231
292k
    generalParameterValues.reserve(values.size());
232
351k
    for (size_t i = 0; i < values.size(); i++) {
233
58.7k
        generalParameterValues.push_back(
234
58.7k
            OperationParameterValue::create(parameters[i], values[i]));
235
58.7k
    }
236
292k
    return create(propertiesConversion, op, generalParameterValues);
237
292k
}
238
239
// ---------------------------------------------------------------------------
240
241
//! @cond Doxygen_Suppress
242
243
// ---------------------------------------------------------------------------
244
245
static util::PropertyMap
246
getUTMConversionProperty(const util::PropertyMap &properties, int zone,
247
96
                         bool north) {
248
96
    if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
249
96
        std::string conversionName("UTM zone ");
250
96
        conversionName += toString(zone);
251
96
        conversionName += (north ? 'N' : 'S');
252
253
96
        return createMapNameEPSGCode(conversionName,
254
96
                                     (north ? 16000 : 16100) + zone);
255
96
    } else {
256
0
        return properties;
257
0
    }
258
96
}
259
260
// ---------------------------------------------------------------------------
261
262
static ConversionNNPtr
263
createConversion(const util::PropertyMap &properties,
264
                 const MethodMapping *mapping,
265
2.91k
                 const std::vector<ParameterValueNNPtr> &values) {
266
267
2.91k
    std::vector<OperationParameterNNPtr> parameters;
268
3.80k
    for (int i = 0; mapping->params != nullptr && mapping->params[i] != nullptr;
269
2.91k
         i++) {
270
894
        const auto *param = mapping->params[i];
271
894
        auto paramProperties = util::PropertyMap().set(
272
894
            common::IdentifiedObject::NAME_KEY, param->wkt2_name);
273
894
        if (param->epsg_code != 0) {
274
894
            paramProperties
275
894
                .set(metadata::Identifier::CODESPACE_KEY,
276
894
                     metadata::Identifier::EPSG)
277
894
                .set(metadata::Identifier::CODE_KEY, param->epsg_code);
278
894
        }
279
894
        parameters.push_back(OperationParameter::create(paramProperties));
280
894
    }
281
282
2.91k
    auto methodProperties = util::PropertyMap().set(
283
2.91k
        common::IdentifiedObject::NAME_KEY, mapping->wkt2_name);
284
2.91k
    if (mapping->epsg_code != 0) {
285
180
        methodProperties
286
180
            .set(metadata::Identifier::CODESPACE_KEY,
287
180
                 metadata::Identifier::EPSG)
288
180
            .set(metadata::Identifier::CODE_KEY, mapping->epsg_code);
289
180
    }
290
2.91k
    return Conversion::create(
291
2.91k
        addDefaultNameIfNeeded(properties, mapping->wkt2_name),
292
2.91k
        methodProperties, parameters, values);
293
2.91k
}
294
//! @endcond
295
296
// ---------------------------------------------------------------------------
297
298
ConversionNNPtr
299
Conversion::create(const util::PropertyMap &properties, int method_epsg_code,
300
180
                   const std::vector<ParameterValueNNPtr> &values) {
301
180
    const MethodMapping *mapping = getMapping(method_epsg_code);
302
180
    assert(mapping);
303
180
    return createConversion(properties, mapping, values);
304
180
}
305
306
// ---------------------------------------------------------------------------
307
308
ConversionNNPtr
309
Conversion::create(const util::PropertyMap &properties,
310
                   const char *method_wkt2_name,
311
2.73k
                   const std::vector<ParameterValueNNPtr> &values) {
312
2.73k
    const MethodMapping *mapping = getMapping(method_wkt2_name);
313
2.73k
    assert(mapping);
314
2.73k
    return createConversion(properties, mapping, values);
315
2.73k
}
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
124
                                      int zone, bool north) {
336
124
    if (zone < 1 || zone > 60) {
337
37
        throw InvalidOperation("Invalid zone number");
338
37
    }
339
87
    return create(
340
87
        getUTMConversionProperty(properties, zone, north),
341
87
        EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
342
87
        createParams(common::Angle(UTM_LATITUDE_OF_NATURAL_ORIGIN),
343
87
                     common::Angle(zone * 6.0 - 183.0),
344
87
                     common::Scale(UTM_SCALE_FACTOR),
345
87
                     common::Length(UTM_FALSE_EASTING),
346
87
                     common::Length(north ? UTM_NORTH_FALSE_NORTHING
347
87
                                          : UTM_SOUTH_FALSE_NORTHING)));
348
124
}
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
14
    const common::Length &falseEasting, const common::Length &falseNorthing) {
373
14
    return create(properties, EPSG_CODE_METHOD_TRANSVERSE_MERCATOR,
374
14
                  createParams(centerLat, centerLong, scale, falseEasting,
375
14
                               falseNorthing));
376
14
}
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
31
    const common::Length &falseNorthing) {
1111
31
    return create(properties, EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL,
1112
31
                  createParams(latitudeFirstParallel, 0.0, longitudeNatOrigin,
1113
31
                               falseEasting, falseNorthing));
1114
31
}
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
14
    const common::Length &falseEasting, const common::Length &falseNorthing) {
1805
14
    return create(properties, PROJ_WKT2_NAME_METHOD_MOLLWEIDE,
1806
14
                  createParams(centerLong, falseEasting, falseNorthing));
1807
14
}
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
45
    const common::Length &falseNorthing) {
1888
45
    return create(
1889
45
        properties, EPSG_CODE_METHOD_ORTHOGRAPHIC,
1890
45
        createParams(centerLat, centerLong, falseEasting, falseNorthing));
1891
45
}
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
3
    const common::Length &falseNorthing) {
2006
3
    return create(properties, EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B,
2007
3
                  createParams(latitudeStandardParallel, longitudeOfOrigin,
2008
3
                               falseEasting, falseNorthing));
2009
3
}
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
3.40k
                                     const common::Scale &factor) {
2503
3.40k
    return create(
2504
3.40k
        properties,
2505
3.40k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
2506
3.40k
        VectorOfParameters{
2507
3.40k
            createOpParamNameEPSGCode(
2508
3.40k
                EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
2509
3.40k
        },
2510
3.40k
        VectorOfValues{
2511
3.40k
            factor,
2512
3.40k
        });
2513
3.40k
}
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
0
Conversion::createHeightDepthReversal(const util::PropertyMap &properties) {
2552
0
    return create(
2553
0
        properties,
2554
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL), {},
2555
0
        {});
2556
0
}
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
175k
ConversionNNPtr Conversion::createAxisOrderReversal(bool is3D) {
2574
175k
    if (is3D) {
2575
89.0k
        return create(createMapNameEPSGCode(AXIS_ORDER_CHANGE_3D_NAME, 15499),
2576
89.0k
                      createMethodMapNameEPSGCode(
2577
89.0k
                          EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D),
2578
89.0k
                      {}, {});
2579
89.0k
    }
2580
86.7k
    return create(
2581
86.7k
        createMapNameEPSGCode(AXIS_ORDER_CHANGE_2D_NAME, 15498),
2582
86.7k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D),
2583
86.7k
        {}, {});
2584
175k
}
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
89.6k
Conversion::createGeographicGeocentric(const util::PropertyMap &properties) {
2600
89.6k
    return create(
2601
89.6k
        properties,
2602
89.6k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC), {},
2603
89.6k
        {});
2604
89.6k
}
2605
2606
// ---------------------------------------------------------------------------
2607
2608
//! @cond Doxygen_Suppress
2609
2610
ConversionNNPtr
2611
Conversion::createGeographicGeocentric(const crs::CRSNNPtr &sourceCRS,
2612
47.1k
                                       const crs::CRSNNPtr &targetCRS) {
2613
47.1k
    auto properties = util::PropertyMap().set(
2614
47.1k
        common::IdentifiedObject::NAME_KEY,
2615
47.1k
        buildOpName("Conversion", sourceCRS, targetCRS));
2616
47.1k
    auto conv = createGeographicGeocentric(properties);
2617
47.1k
    conv->setCRSs(sourceCRS, targetCRS, nullptr);
2618
47.1k
    return conv;
2619
47.1k
}
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
296
                                               const crs::CRSNNPtr &targetCRS) {
2634
296
    auto properties = util::PropertyMap().set(
2635
296
        common::IdentifiedObject::NAME_KEY,
2636
296
        buildOpName("Conversion", sourceCRS, targetCRS));
2637
296
    auto conv = create(
2638
296
        properties, PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {});
2639
296
    conv->setCRSs(sourceCRS, targetCRS, nullptr);
2640
296
    return conv;
2641
296
}
2642
2643
// ---------------------------------------------------------------------------
2644
2645
InverseConversion::InverseConversion(const ConversionNNPtr &forward)
2646
68.3k
    : Conversion(
2647
68.3k
          OperationMethod::create(createPropertiesForInverse(forward->method()),
2648
68.3k
                                  forward->method()->parameters()),
2649
68.3k
          forward->parameterValues()),
2650
68.3k
      InverseCoordinateOperation(forward, true) {
2651
68.3k
    setPropertiesFromForward();
2652
68.3k
}
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
68.3k
    : Conversion(
2647
68.3k
          OperationMethod::create(createPropertiesForInverse(forward->method()),
2648
68.3k
                                  forward->method()->parameters()),
2649
68.3k
          forward->parameterValues()),
2650
68.3k
      InverseCoordinateOperation(forward, true) {
2651
68.3k
    setPropertiesFromForward();
2652
68.3k
}
2653
2654
// ---------------------------------------------------------------------------
2655
2656
68.3k
InverseConversion::~InverseConversion() = default;
2657
2658
// ---------------------------------------------------------------------------
2659
2660
5.28k
ConversionNNPtr InverseConversion::inverseAsConversion() const {
2661
5.28k
    return NN_NO_CHECK(
2662
5.28k
        util::nn_dynamic_pointer_cast<Conversion>(forwardOperation_));
2663
5.28k
}
2664
2665
// ---------------------------------------------------------------------------
2666
2667
CoordinateOperationNNPtr
2668
63.0k
InverseConversion::create(const ConversionNNPtr &forward) {
2669
63.0k
    auto conv = util::nn_make_shared<InverseConversion>(forward);
2670
63.0k
    conv->assignSelf(conv);
2671
63.0k
    return conv;
2672
63.0k
}
2673
2674
// ---------------------------------------------------------------------------
2675
2676
5.28k
CoordinateOperationNNPtr InverseConversion::_shallowClone() const {
2677
5.28k
    auto op = InverseConversion::nn_make_shared<InverseConversion>(
2678
5.28k
        inverseAsConversion()->shallowClone());
2679
5.28k
    op->assignSelf(op);
2680
5.28k
    op->setCRSs(this, false);
2681
5.28k
    return util::nn_static_pointer_cast<CoordinateOperation>(op);
2682
5.28k
}
2683
2684
//! @endcond
2685
2686
// ---------------------------------------------------------------------------
2687
2688
//! @cond Doxygen_Suppress
2689
2690
2.00M
static bool isAxisOrderReversal2D(int methodEPSGCode) {
2691
2.00M
    return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_2D;
2692
2.00M
}
2693
2694
1.64M
static bool isAxisOrderReversal3D(int methodEPSGCode) {
2695
1.64M
    return methodEPSGCode == EPSG_CODE_METHOD_AXIS_ORDER_REVERSAL_3D;
2696
1.64M
}
2697
2698
1.74M
bool isAxisOrderReversal(int methodEPSGCode) {
2699
1.74M
    return isAxisOrderReversal2D(methodEPSGCode) ||
2700
1.74M
           isAxisOrderReversal3D(methodEPSGCode);
2701
1.74M
}
2702
//! @endcond
2703
2704
// ---------------------------------------------------------------------------
2705
2706
265k
CoordinateOperationNNPtr Conversion::inverse() const {
2707
265k
    const int methodEPSGCode = method()->getEPSGCode();
2708
2709
265k
    if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
2710
1.73k
        const double convFactor = parameterValueNumericAsSI(
2711
1.73k
            EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
2712
1.73k
        if (convFactor == 0) {
2713
5
            throw InvalidOperation("Invalid conversion factor");
2714
5
        }
2715
        // coverity[divide_by_zero]
2716
1.72k
        const double invConvFactor = 1.0 / convFactor;
2717
1.72k
        auto conv = createChangeVerticalUnit(
2718
1.72k
            createPropertiesForInverse(this, false, false),
2719
1.72k
            common::Scale(invConvFactor));
2720
1.72k
        conv->setCRSs(this, true);
2721
1.72k
        return conv;
2722
1.73k
    }
2723
2724
263k
    if (methodEPSGCode ==
2725
263k
        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
263k
    const bool l_isAxisOrderReversal2D = isAxisOrderReversal2D(methodEPSGCode);
2733
263k
    const bool l_isAxisOrderReversal3D = isAxisOrderReversal3D(methodEPSGCode);
2734
263k
    if (l_isAxisOrderReversal2D || l_isAxisOrderReversal3D) {
2735
156k
        auto conv = createAxisOrderReversal(l_isAxisOrderReversal3D);
2736
156k
        conv->setCRSs(this, true);
2737
156k
        return conv;
2738
156k
    }
2739
2740
107k
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
2741
2742
42.4k
        auto conv = createGeographicGeocentric(
2743
42.4k
            createPropertiesForInverse(this, false, false));
2744
42.4k
        conv->setCRSs(this, true);
2745
42.4k
        return conv;
2746
42.4k
    }
2747
2748
65.4k
    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
65.4k
    if (method()->nameStr() ==
2757
65.4k
        PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) {
2758
2.42k
        auto conv =
2759
2.42k
            create(createPropertiesForInverse(this, false, false),
2760
2.42k
                   PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE, {});
2761
2.42k
        conv->setCRSs(this, true);
2762
2.42k
        return conv;
2763
2.42k
    }
2764
2765
63.0k
    return InverseConversion::create(NN_NO_CHECK(
2766
63.0k
        util::nn_dynamic_pointer_cast<Conversion>(shared_from_this())));
2767
65.4k
}
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
    const auto &methodName = l_method->nameStr();
3263
0
    const 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
0
    if (!isWKT2 && formatter->useESRIDialect()) {
3302
0
        const ESRIParamMapping *esriParams = nullptr;
3303
0
        const char *esriMethodName = nullptr;
3304
0
        getESRIMethodNameAndParams(this, methodName, methodEPSGCode,
3305
0
                                   esriMethodName, esriParams);
3306
0
        if (esriMethodName && esriParams) {
3307
0
            formatter->startNode(io::WKTConstants::PROJECTION, false);
3308
0
            formatter->addQuotedString(esriMethodName);
3309
0
            formatter->endNode();
3310
3311
0
            for (int i = 0; esriParams[i].esri_name != nullptr; i++) {
3312
0
                const auto &esriParam = esriParams[i];
3313
0
                formatter->startNode(io::WKTConstants::PARAMETER, false);
3314
0
                formatter->addQuotedString(esriParam.esri_name);
3315
0
                if (esriParam.wkt2_name) {
3316
0
                    const auto &pv = parameterValue(esriParam.wkt2_name,
3317
0
                                                    esriParam.epsg_code);
3318
0
                    if (pv && pv->type() == ParameterValue::Type::MEASURE) {
3319
0
                        const auto &v = pv->value();
3320
                        // as we don't output the natural unit, output
3321
                        // to the registered linear / angular unit.
3322
0
                        const auto &unitType = v.unit().type();
3323
0
                        if (unitType == common::UnitOfMeasure::Type::LINEAR) {
3324
0
                            formatter->add(v.convertToUnit(
3325
0
                                *(formatter->axisLinearUnit())));
3326
0
                        } else if (unitType ==
3327
0
                                   common::UnitOfMeasure::Type::ANGULAR) {
3328
0
                            const auto &angUnit =
3329
0
                                *(formatter->axisAngularUnit());
3330
0
                            double val = v.convertToUnit(angUnit);
3331
0
                            if (angUnit == common::UnitOfMeasure::DEGREE) {
3332
0
                                if (val > 180.0) {
3333
0
                                    val -= 360.0;
3334
0
                                } else if (val < -180.0) {
3335
0
                                    val += 360.0;
3336
0
                                }
3337
0
                            }
3338
0
                            formatter->add(val);
3339
0
                        } else {
3340
0
                            formatter->add(v.getSIValue());
3341
0
                        }
3342
0
                    } else if (ci_find(esriParam.esri_name, "scale") !=
3343
0
                               std::string::npos) {
3344
0
                        formatter->add(1.0);
3345
0
                    } else {
3346
0
                        formatter->add(0.0);
3347
0
                    }
3348
0
                } else {
3349
0
                    formatter->add(esriParam.fixed_value);
3350
0
                }
3351
0
                formatter->endNode();
3352
0
            }
3353
0
            bAlreadyWritten = true;
3354
0
        }
3355
0
    } else if (!isWKT2) {
3356
0
        if (methodEPSGCode ==
3357
0
            EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
3358
0
            const double latitudeOrigin = parameterValueNumeric(
3359
0
                EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
3360
0
                common::UnitOfMeasure::DEGREE);
3361
0
            if (latitudeOrigin != 0) {
3362
0
                throw io::FormattingException(
3363
0
                    std::string("Unsupported value for ") +
3364
0
                    EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
3365
0
            }
3366
3367
0
            bAlreadyWritten = true;
3368
0
            formatter->startNode(io::WKTConstants::PROJECTION, false);
3369
0
            formatter->addQuotedString("Mercator_1SP");
3370
0
            formatter->endNode();
3371
3372
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3373
0
            formatter->addQuotedString("central_meridian");
3374
0
            const double centralMeridian = parameterValueNumeric(
3375
0
                EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
3376
0
                common::UnitOfMeasure::DEGREE);
3377
0
            formatter->add(centralMeridian);
3378
0
            formatter->endNode();
3379
3380
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3381
0
            formatter->addQuotedString("scale_factor");
3382
0
            formatter->add(1.0);
3383
0
            formatter->endNode();
3384
3385
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3386
0
            formatter->addQuotedString("false_easting");
3387
0
            const double falseEasting =
3388
0
                parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
3389
0
            formatter->add(falseEasting);
3390
0
            formatter->endNode();
3391
3392
0
            formatter->startNode(io::WKTConstants::PARAMETER, false);
3393
0
            formatter->addQuotedString("false_northing");
3394
0
            const double falseNorthing =
3395
0
                parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
3396
0
            formatter->add(falseNorthing);
3397
0
            formatter->endNode();
3398
0
        } else if (starts_with(methodName, "PROJ ")) {
3399
0
            bAlreadyWritten = true;
3400
0
            formatter->startNode(io::WKTConstants::PROJECTION, false);
3401
0
            formatter->addQuotedString("custom_proj4");
3402
0
            formatter->endNode();
3403
0
        }
3404
0
    }
3405
3406
0
    if (!bAlreadyWritten) {
3407
0
        l_method->_exportToWKT(formatter);
3408
3409
0
        const MethodMapping *mapping =
3410
0
            !isWKT2 ? getMapping(l_method.get()) : nullptr;
3411
0
        bool hasInterpolationCRSParameter = false;
3412
0
        for (const auto &genOpParamvalue : parameterValues()) {
3413
0
            const auto opParamvalue =
3414
0
                dynamic_cast<const OperationParameterValue *>(
3415
0
                    genOpParamvalue.get());
3416
0
            const int paramEPSGCode =
3417
0
                opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;
3418
3419
            // EPSG has normally no Latitude of natural origin for Equidistant
3420
            // Cylindrical but PROJ can handle it, so output the parameter if
3421
            // not zero
3422
0
            if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
3423
0
                 methodEPSGCode ==
3424
0
                     EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
3425
0
                if (paramEPSGCode ==
3426
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN) {
3427
0
                    const auto &paramValue = opParamvalue->parameterValue();
3428
0
                    if (paramValue->type() == ParameterValue::Type::MEASURE) {
3429
0
                        const auto &measure = paramValue->value();
3430
0
                        if (measure.getSIValue() == 0) {
3431
0
                            continue;
3432
0
                        }
3433
0
                    }
3434
0
                }
3435
0
            }
3436
            // Same for false easting / false northing for Vertical Perspective
3437
0
            else if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE) {
3438
0
                if (paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_EASTING ||
3439
0
                    paramEPSGCode == EPSG_CODE_PARAMETER_FALSE_NORTHING) {
3440
0
                    const auto &paramValue = opParamvalue->parameterValue();
3441
0
                    if (paramValue->type() == ParameterValue::Type::MEASURE) {
3442
0
                        const auto &measure = paramValue->value();
3443
0
                        if (measure.getSIValue() == 0) {
3444
0
                            continue;
3445
0
                        }
3446
0
                    }
3447
0
                }
3448
0
            }
3449
0
            if (paramEPSGCode ==
3450
0
                    EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3451
0
                paramEPSGCode ==
3452
0
                    EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
3453
0
                hasInterpolationCRSParameter = true;
3454
0
            }
3455
0
            genOpParamvalue->_exportToWKT(formatter, mapping);
3456
0
        }
3457
3458
        // If we have an interpolation CRS that has a EPSG code, then
3459
        // we can export it as a PARAMETER[]
3460
0
        const auto l_interpolationCRS = interpolationCRS();
3461
0
        if (!hasInterpolationCRSParameter && l_interpolationCRS) {
3462
0
            const auto code = l_interpolationCRS->getEPSGCode();
3463
0
            if (code != 0) {
3464
0
                createOperationParameterValueFromInterpolationCRS(
3465
0
                    methodEPSGCode, code)
3466
0
                    ->_exportToWKT(formatter, mapping);
3467
0
            }
3468
0
        }
3469
0
    }
3470
3471
0
    if (isWKT2) {
3472
0
        if (formatter->outputId()) {
3473
0
            formatID(formatter);
3474
0
        }
3475
0
        formatter->endNode();
3476
0
    } else {
3477
0
        formatter->popOutputUnit();
3478
0
        formatter->popOutputId();
3479
0
        formatter->leave();
3480
0
    }
3481
0
}
3482
//! @endcond
3483
3484
// ---------------------------------------------------------------------------
3485
3486
//! @cond Doxygen_Suppress
3487
void Conversion::_exportToJSON(
3488
    io::JSONFormatter *formatter) const // throw(FormattingException)
3489
0
{
3490
0
    auto writer = formatter->writer();
3491
0
    auto objectContext(
3492
0
        formatter->MakeObjectContext("Conversion", !identifiers().empty()));
3493
3494
0
    writer->AddObjKey("name");
3495
0
    const auto &l_name = nameStr();
3496
0
    if (l_name.empty()) {
3497
0
        writer->Add("unnamed");
3498
0
    } else {
3499
0
        writer->Add(l_name);
3500
0
    }
3501
3502
0
    writer->AddObjKey("method");
3503
0
    formatter->setOmitTypeInImmediateChild();
3504
0
    formatter->setAllowIDInImmediateChild();
3505
0
    const auto &l_method = method();
3506
0
    l_method->_exportToJSON(formatter);
3507
3508
0
    const auto &l_parameterValues = parameterValues();
3509
0
    const auto l_interpolationCRS = interpolationCRS();
3510
0
    if (!l_parameterValues.empty() || l_interpolationCRS) {
3511
0
        writer->AddObjKey("parameters");
3512
0
        {
3513
0
            bool hasInterpolationCRSParameter = false;
3514
0
            auto parametersContext(writer->MakeArrayContext(false));
3515
0
            for (const auto &genOpParamvalue : l_parameterValues) {
3516
0
                const auto opParamvalue =
3517
0
                    dynamic_cast<const OperationParameterValue *>(
3518
0
                        genOpParamvalue.get());
3519
0
                const int paramEPSGCode =
3520
0
                    opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;
3521
0
                if (paramEPSGCode ==
3522
0
                        EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3523
0
                    paramEPSGCode ==
3524
0
                        EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
3525
0
                    hasInterpolationCRSParameter = true;
3526
0
                }
3527
0
                formatter->setAllowIDInImmediateChild();
3528
0
                formatter->setOmitTypeInImmediateChild();
3529
0
                genOpParamvalue->_exportToJSON(formatter);
3530
0
            }
3531
3532
            // If we have an interpolation CRS that has a EPSG code, then
3533
            // we can export it as a parameter
3534
0
            if (!hasInterpolationCRSParameter && l_interpolationCRS) {
3535
0
                const auto methodEPSGCode = l_method->getEPSGCode();
3536
0
                const auto code = l_interpolationCRS->getEPSGCode();
3537
0
                if (code != 0) {
3538
0
                    formatter->setAllowIDInImmediateChild();
3539
0
                    formatter->setOmitTypeInImmediateChild();
3540
0
                    createOperationParameterValueFromInterpolationCRS(
3541
0
                        methodEPSGCode, code)
3542
0
                        ->_exportToJSON(formatter);
3543
0
                }
3544
0
            }
3545
0
        }
3546
0
    }
3547
3548
0
    if (formatter->outputId()) {
3549
0
        formatID(formatter);
3550
0
    }
3551
0
}
3552
3553
//! @endcond
3554
3555
// ---------------------------------------------------------------------------
3556
3557
//! @cond Doxygen_Suppress
3558
static bool createPROJ4WebMercator(const Conversion *conv,
3559
2
                                   io::PROJStringFormatter *formatter) {
3560
2
    const double centralMeridian = conv->parameterValueNumeric(
3561
2
        EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
3562
2
        common::UnitOfMeasure::DEGREE);
3563
3564
2
    const double falseEasting =
3565
2
        conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_EASTING);
3566
3567
2
    const double falseNorthing =
3568
2
        conv->parameterValueNumericAsSI(EPSG_CODE_PARAMETER_FALSE_NORTHING);
3569
3570
2
    auto sourceCRS = conv->sourceCRS();
3571
2
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
3572
2
    if (!geogCRS) {
3573
0
        return false;
3574
0
    }
3575
3576
2
    std::string units("m");
3577
2
    auto targetCRS = conv->targetCRS();
3578
2
    auto targetProjCRS =
3579
2
        dynamic_cast<const crs::ProjectedCRS *>(targetCRS.get());
3580
2
    if (targetProjCRS) {
3581
2
        const auto &axisList = targetProjCRS->coordinateSystem()->axisList();
3582
2
        const auto &unit = axisList[0]->unit();
3583
2
        if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
3584
2
                                  util::IComparable::Criterion::EQUIVALENT)) {
3585
0
            auto projUnit = unit.exportToPROJString();
3586
0
            if (!projUnit.empty()) {
3587
0
                units = std::move(projUnit);
3588
0
            } else {
3589
0
                return false;
3590
0
            }
3591
0
        }
3592
2
    }
3593
3594
2
    formatter->addStep("merc");
3595
2
    const double a = geogCRS->ellipsoid()->semiMajorAxis().getSIValue();
3596
2
    formatter->addParam("a", a);
3597
2
    formatter->addParam("b", a);
3598
2
    formatter->addParam("lat_ts", 0.0);
3599
2
    formatter->addParam("lon_0", centralMeridian);
3600
2
    formatter->addParam("x_0", falseEasting);
3601
2
    formatter->addParam("y_0", falseNorthing);
3602
2
    formatter->addParam("k", 1.0);
3603
2
    formatter->addParam("units", units);
3604
2
    formatter->addParam("nadgrids", "@null");
3605
2
    if (targetProjCRS && targetProjCRS->hasOver()) {
3606
0
        formatter->addParam("over");
3607
0
    }
3608
2
    formatter->addParam("wktext");
3609
2
    formatter->addParam("no_defs");
3610
2
    return true;
3611
2
}
3612
3613
// ---------------------------------------------------------------------------
3614
3615
static bool
3616
createPROJExtensionFromCustomProj(const Conversion *conv,
3617
                                  io::PROJStringFormatter *formatter,
3618
66.5k
                                  bool forExtensionNode) {
3619
66.5k
    const auto &methodName = conv->method()->nameStr();
3620
66.5k
    assert(starts_with(methodName, "PROJ "));
3621
66.5k
    auto tokens = split(methodName, ' ');
3622
3623
66.5k
    formatter->addStep(tokens[1]);
3624
3625
66.5k
    if (forExtensionNode) {
3626
0
        auto sourceCRS = conv->sourceCRS();
3627
0
        auto geogCRS =
3628
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
3629
0
        if (!geogCRS) {
3630
0
            return false;
3631
0
        }
3632
0
        geogCRS->addDatumInfoToPROJString(formatter);
3633
0
    }
3634
3635
320k
    for (size_t i = 2; i < tokens.size(); i++) {
3636
254k
        auto kv = split(tokens[i], '=');
3637
254k
        if (kv.size() == 2) {
3638
84.2k
            formatter->addParam(kv[0], kv[1]);
3639
170k
        } else {
3640
170k
            formatter->addParam(tokens[i]);
3641
170k
        }
3642
254k
    }
3643
3644
66.5k
    for (const auto &genOpParamvalue : conv->parameterValues()) {
3645
17.9k
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
3646
17.9k
            genOpParamvalue.get());
3647
17.9k
        if (opParamvalue) {
3648
17.9k
            const auto &paramName = opParamvalue->parameter()->nameStr();
3649
17.9k
            const auto &paramValue = opParamvalue->parameterValue();
3650
17.9k
            if (paramValue->type() == ParameterValue::Type::MEASURE) {
3651
17.9k
                const auto &measure = paramValue->value();
3652
17.9k
                const auto unitType = measure.unit().type();
3653
17.9k
                if (unitType == common::UnitOfMeasure::Type::LINEAR) {
3654
217
                    formatter->addParam(paramName, measure.getSIValue());
3655
17.7k
                } else if (unitType == common::UnitOfMeasure::Type::ANGULAR) {
3656
17.1k
                    formatter->addParam(
3657
17.1k
                        paramName,
3658
17.1k
                        measure.convertToUnit(common::UnitOfMeasure::DEGREE));
3659
17.1k
                } else {
3660
558
                    formatter->addParam(paramName, measure.value());
3661
558
                }
3662
17.9k
            }
3663
17.9k
        }
3664
17.9k
    }
3665
3666
66.5k
    if (forExtensionNode) {
3667
0
        formatter->addParam("wktext");
3668
0
        formatter->addParam("no_defs");
3669
0
    }
3670
66.5k
    return true;
3671
66.5k
}
3672
//! @endcond
3673
3674
// ---------------------------------------------------------------------------
3675
3676
0
bool Conversion::addWKTExtensionNode(io::WKTFormatter *formatter) const {
3677
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
3678
0
    if (!isWKT2) {
3679
0
        const auto &l_method = method();
3680
0
        const auto &methodName = l_method->nameStr();
3681
0
        const int methodEPSGCode = l_method->getEPSGCode();
3682
0
        if (l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
3683
0
            l_method->getPrivate()->projMethodOverride_ == "utm approx") {
3684
0
            auto projFormatter = io::PROJStringFormatter::create();
3685
0
            projFormatter->setCRSExport(true);
3686
0
            projFormatter->setUseApproxTMerc(true);
3687
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
3688
0
            formatter->addQuotedString("PROJ4");
3689
0
            _exportToPROJString(projFormatter.get());
3690
0
            projFormatter->addParam("no_defs");
3691
0
            formatter->addQuotedString(projFormatter->toString());
3692
0
            formatter->endNode();
3693
0
            return true;
3694
0
        } else if (methodEPSGCode ==
3695
0
                       EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR ||
3696
0
                   nameStr() == "Popular Visualisation Mercator") {
3697
3698
0
            auto projFormatter = io::PROJStringFormatter::create();
3699
0
            projFormatter->setCRSExport(true);
3700
0
            if (createPROJ4WebMercator(this, projFormatter.get())) {
3701
0
                formatter->startNode(io::WKTConstants::EXTENSION, false);
3702
0
                formatter->addQuotedString("PROJ4");
3703
0
                formatter->addQuotedString(projFormatter->toString());
3704
0
                formatter->endNode();
3705
0
                return true;
3706
0
            }
3707
0
        } else if (starts_with(methodName, "PROJ ")) {
3708
0
            auto projFormatter = io::PROJStringFormatter::create();
3709
0
            projFormatter->setCRSExport(true);
3710
0
            if (createPROJExtensionFromCustomProj(this, projFormatter.get(),
3711
0
                                                  true)) {
3712
0
                formatter->startNode(io::WKTConstants::EXTENSION, false);
3713
0
                formatter->addQuotedString("PROJ4");
3714
0
                formatter->addQuotedString(projFormatter->toString());
3715
0
                formatter->endNode();
3716
0
                return true;
3717
0
            }
3718
0
        } else if (methodName ==
3719
0
                   PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
3720
0
            auto projFormatter = io::PROJStringFormatter::create();
3721
0
            projFormatter->setCRSExport(true);
3722
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
3723
0
            formatter->addQuotedString("PROJ4");
3724
0
            _exportToPROJString(projFormatter.get());
3725
0
            projFormatter->addParam("no_defs");
3726
0
            formatter->addQuotedString(projFormatter->toString());
3727
0
            formatter->endNode();
3728
0
            return true;
3729
0
        }
3730
0
    }
3731
0
    return false;
3732
0
}
3733
3734
// ---------------------------------------------------------------------------
3735
3736
//! @cond Doxygen_Suppress
3737
void Conversion::_exportToPROJString(
3738
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
3739
576k
{
3740
576k
    const auto &l_method = method();
3741
576k
    const auto &methodName = l_method->nameStr();
3742
576k
    const int methodEPSGCode = l_method->getEPSGCode();
3743
576k
    const bool isZUnitConversion =
3744
576k
        methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
3745
576k
        methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR;
3746
576k
    const bool isAffineParametric =
3747
576k
        methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION;
3748
576k
    const bool isSimilarity =
3749
576k
        methodEPSGCode == EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION;
3750
576k
    const bool isGeographicGeocentric =
3751
576k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC;
3752
576k
    const bool isGeographicOffsets =
3753
576k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
3754
576k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS ||
3755
576k
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS;
3756
576k
    const bool isHeightDepthReversal =
3757
576k
        methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL;
3758
576k
    const bool applySourceCRSModifiers =
3759
576k
        !isZUnitConversion && !isAffineParametric && !isSimilarity &&
3760
576k
        !isAxisOrderReversal(methodEPSGCode) && !isGeographicGeocentric &&
3761
576k
        !isGeographicOffsets && !isHeightDepthReversal;
3762
576k
    bool applyTargetCRSModifiers = applySourceCRSModifiers;
3763
3764
576k
    if (formatter->getCRSExport()) {
3765
1.90k
        if (methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TOPOCENTRIC ||
3766
1.90k
            methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
3767
1
            throw io::FormattingException("Transformation cannot be exported "
3768
1
                                          "as a PROJ.4 string (but can be part "
3769
1
                                          "of a PROJ pipeline)");
3770
1
        }
3771
1.90k
    }
3772
3773
576k
    auto l_sourceCRS = sourceCRS();
3774
576k
    auto l_targetCRS = targetCRS();
3775
3776
576k
    if (methodName == PROJ_WKT2_NAME_METHOD_GEOGRAPHIC_GEOCENTRIC_LATITUDE) {
3777
3778
2.36k
        const auto extractGeodeticCRSIfGeodeticCRSOrEquivalent =
3779
4.72k
            [](const crs::CRSPtr &crs) {
3780
4.72k
                auto geodCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
3781
4.72k
                if (!geodCRS) {
3782
182
                    auto compoundCRS =
3783
182
                        std::dynamic_pointer_cast<crs::CompoundCRS>(crs);
3784
182
                    if (compoundCRS) {
3785
0
                        const auto &components =
3786
0
                            compoundCRS->componentReferenceSystems();
3787
0
                        if (!components.empty()) {
3788
0
                            geodCRS =
3789
0
                                util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
3790
0
                                    components[0]);
3791
0
                            if (!geodCRS) {
3792
0
                                auto boundCRS = util::nn_dynamic_pointer_cast<
3793
0
                                    crs::BoundCRS>(components[0]);
3794
0
                                if (boundCRS) {
3795
0
                                    geodCRS = util::nn_dynamic_pointer_cast<
3796
0
                                        crs::GeodeticCRS>(boundCRS->baseCRS());
3797
0
                                }
3798
0
                            }
3799
0
                        }
3800
182
                    } else {
3801
182
                        auto boundCRS =
3802
182
                            std::dynamic_pointer_cast<crs::BoundCRS>(crs);
3803
182
                        if (boundCRS) {
3804
182
                            geodCRS =
3805
182
                                util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
3806
182
                                    boundCRS->baseCRS());
3807
182
                        }
3808
182
                    }
3809
182
                }
3810
4.72k
                return geodCRS;
3811
4.72k
            };
3812
3813
2.36k
        auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(
3814
2.36k
            extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_sourceCRS).get());
3815
2.36k
        auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(
3816
2.36k
            extractGeodeticCRSIfGeodeticCRSOrEquivalent(l_targetCRS).get());
3817
2.36k
        if (sourceCRSGeod && targetCRSGeod) {
3818
2.36k
            auto sourceCRSGeog =
3819
2.36k
                dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
3820
2.36k
            auto targetCRSGeog =
3821
2.36k
                dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
3822
2.36k
            bool isSrcGeocentricLat =
3823
2.36k
                sourceCRSGeod->isSphericalPlanetocentric();
3824
2.36k
            bool isSrcGeographic = sourceCRSGeog != nullptr;
3825
2.36k
            bool isTargetGeocentricLat =
3826
2.36k
                targetCRSGeod->isSphericalPlanetocentric();
3827
2.36k
            bool isTargetGeographic = targetCRSGeog != nullptr;
3828
2.36k
            if ((isSrcGeocentricLat && isTargetGeographic) ||
3829
2.36k
                (isSrcGeographic && isTargetGeocentricLat)) {
3830
3831
2.36k
                formatter->setOmitProjLongLatIfPossible(true);
3832
2.36k
                formatter->startInversion();
3833
2.36k
                sourceCRSGeod->_exportToPROJString(formatter);
3834
2.36k
                formatter->stopInversion();
3835
3836
2.36k
                targetCRSGeod->_exportToPROJString(formatter);
3837
2.36k
                formatter->setOmitProjLongLatIfPossible(false);
3838
3839
2.36k
                return;
3840
2.36k
            }
3841
2.36k
        }
3842
3843
0
        throw io::FormattingException("Invalid nature of source and/or "
3844
0
                                      "targetCRS for Geographic latitude / "
3845
0
                                      "Geocentric latitude"
3846
0
                                      "conversion");
3847
2.36k
    }
3848
3849
573k
    crs::GeographicCRSPtr srcGeogCRS;
3850
573k
    if (!formatter->getCRSExport() && l_sourceCRS && applySourceCRSModifiers) {
3851
3852
165k
        crs::CRSPtr horiz = l_sourceCRS;
3853
165k
        const auto compound =
3854
165k
            dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get());
3855
165k
        if (compound) {
3856
42.1k
            const auto &components = compound->componentReferenceSystems();
3857
42.1k
            if (!components.empty()) {
3858
42.1k
                horiz = components.front().as_nullable();
3859
42.1k
                const auto boundCRS =
3860
42.1k
                    dynamic_cast<const crs::BoundCRS *>(horiz.get());
3861
42.1k
                if (boundCRS) {
3862
4.74k
                    horiz = boundCRS->baseCRS().as_nullable();
3863
4.74k
                }
3864
42.1k
            }
3865
42.1k
        }
3866
3867
165k
        auto srcGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(horiz.get());
3868
165k
        if (srcGeodCRS) {
3869
165k
            srcGeogCRS = std::dynamic_pointer_cast<crs::GeographicCRS>(horiz);
3870
165k
        }
3871
165k
        if (srcGeodCRS &&
3872
165k
            (srcGeogCRS || srcGeodCRS->isSphericalPlanetocentric())) {
3873
165k
            formatter->setOmitProjLongLatIfPossible(true);
3874
165k
            formatter->startInversion();
3875
165k
            srcGeodCRS->_exportToPROJString(formatter);
3876
165k
            formatter->stopInversion();
3877
165k
            formatter->setOmitProjLongLatIfPossible(false);
3878
165k
        }
3879
3880
165k
        auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz.get());
3881
165k
        if (projCRS) {
3882
0
            formatter->startInversion();
3883
0
            formatter->pushOmitZUnitConversion();
3884
0
            projCRS->addUnitConvertAndAxisSwap(formatter, false);
3885
0
            formatter->popOmitZUnitConversion();
3886
0
            formatter->stopInversion();
3887
0
        }
3888
165k
    }
3889
3890
573k
    const auto &convName = nameStr();
3891
573k
    bool bConversionDone = false;
3892
573k
    bool bEllipsoidParametersDone = false;
3893
573k
    bool useApprox = false;
3894
573k
    bool insertAxisWSU = false;
3895
573k
    bool negateScaleFactor = false;
3896
573k
    if (methodEPSGCode == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
3897
        // Check for UTM
3898
24.9k
        int zone = 0;
3899
24.9k
        bool north = true;
3900
24.9k
        useApprox =
3901
24.9k
            formatter->getUseApproxTMerc() ||
3902
24.9k
            l_method->getPrivate()->projMethodOverride_ == "tmerc approx" ||
3903
24.9k
            l_method->getPrivate()->projMethodOverride_ == "utm approx";
3904
24.9k
        if (isUTM(zone, north)) {
3905
1.14k
            bConversionDone = true;
3906
1.14k
            formatter->addStep("utm");
3907
1.14k
            if (useApprox) {
3908
0
                formatter->addParam("approx");
3909
0
            }
3910
1.14k
            formatter->addParam("zone", zone);
3911
1.14k
            if (!north) {
3912
0
                formatter->addParam("south");
3913
0
            }
3914
23.7k
        } else if (l_targetCRS &&
3915
23.7k
                   parameterValueNumeric(
3916
23.7k
                       EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN,
3917
23.7k
                       common::UnitOfMeasure::SCALE_UNITY) < 0 &&
3918
23.7k
                   parameterValueNumeric(EPSG_CODE_PARAMETER_FALSE_EASTING,
3919
0
                                         common::UnitOfMeasure::METRE) == 0 &&
3920
23.7k
                   parameterValueNumeric(EPSG_NAME_PARAMETER_FALSE_NORTHING,
3921
0
                                         common::UnitOfMeasure::METRE) == 0) {
3922
            // Deal with ESRI:102470 that use Transverse Mercator with k=-1
3923
            // to indicate a westing-southing coordinate system, by inserting a
3924
            // +axis=wsu and changing k to 1.
3925
0
            auto projCRS =
3926
0
                dynamic_cast<const crs::ProjectedCRS *>(l_targetCRS.get());
3927
0
            if (projCRS) {
3928
0
                const auto &axisList = projCRS->coordinateSystem()->axisList();
3929
0
                if (axisList[0]->direction() == cs::AxisDirection::EAST &&
3930
0
                    axisList[1]->direction() == cs::AxisDirection::NORTH) {
3931
0
                    insertAxisWSU = true;
3932
0
                    negateScaleFactor = true;
3933
0
                }
3934
0
            }
3935
0
        }
3936
3937
548k
    } else if (methodEPSGCode ==
3938
548k
               EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A) {
3939
33
        const double azimuth =
3940
33
            parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE,
3941
33
                                  common::UnitOfMeasure::DEGREE);
3942
33
        const double angleRectifiedToSkewGrid = parameterValueNumeric(
3943
33
            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
3944
33
            common::UnitOfMeasure::DEGREE);
3945
        // Map to Swiss Oblique Mercator / somerc
3946
33
        if (std::fabs(azimuth - 90) < 1e-4 &&
3947
33
            std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
3948
0
            bConversionDone = true;
3949
0
            formatter->addStep("somerc");
3950
0
            formatter->addParam(
3951
0
                "lat_0", parameterValueNumeric(
3952
0
                             EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
3953
0
                             common::UnitOfMeasure::DEGREE));
3954
0
            formatter->addParam(
3955
0
                "lon_0", parameterValueNumeric(
3956
0
                             EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
3957
0
                             common::UnitOfMeasure::DEGREE));
3958
0
            formatter->addParam(
3959
0
                "k_0", parameterValueNumericAsSI(
3960
0
                           EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE));
3961
0
            formatter->addParam("x_0", parameterValueNumericAsSI(
3962
0
                                           EPSG_CODE_PARAMETER_FALSE_EASTING));
3963
0
            formatter->addParam("y_0", parameterValueNumericAsSI(
3964
0
                                           EPSG_CODE_PARAMETER_FALSE_NORTHING));
3965
0
        }
3966
548k
    } else if (methodEPSGCode ==
3967
548k
               EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) {
3968
987
        const double azimuth =
3969
987
            parameterValueNumeric(EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE,
3970
987
                                  common::UnitOfMeasure::DEGREE);
3971
987
        const double angleRectifiedToSkewGrid = parameterValueNumeric(
3972
987
            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID,
3973
987
            common::UnitOfMeasure::DEGREE);
3974
        // Map to Swiss Oblique Mercator / somerc
3975
987
        if (std::fabs(azimuth - 90) < 1e-4 &&
3976
987
            std::fabs(angleRectifiedToSkewGrid - 90) < 1e-4) {
3977
479
            bConversionDone = true;
3978
479
            formatter->addStep("somerc");
3979
479
            formatter->addParam(
3980
479
                "lat_0", parameterValueNumeric(
3981
479
                             EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE,
3982
479
                             common::UnitOfMeasure::DEGREE));
3983
479
            formatter->addParam(
3984
479
                "lon_0", parameterValueNumeric(
3985
479
                             EPSG_CODE_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
3986
479
                             common::UnitOfMeasure::DEGREE));
3987
479
            formatter->addParam(
3988
479
                "k_0", parameterValueNumericAsSI(
3989
479
                           EPSG_CODE_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE));
3990
479
            formatter->addParam(
3991
479
                "x_0", parameterValueNumericAsSI(
3992
479
                           EPSG_CODE_PARAMETER_EASTING_PROJECTION_CENTRE));
3993
479
            formatter->addParam(
3994
479
                "y_0", parameterValueNumericAsSI(
3995
479
                           EPSG_CODE_PARAMETER_NORTHING_PROJECTION_CENTRE));
3996
479
        }
3997
547k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_KROVAK_NORTH_ORIENTED ||
3998
547k
               methodEPSGCode ==
3999
541k
                   EPSG_CODE_METHOD_KROVAK_MODIFIED_NORTH_ORIENTED) {
4000
7.77k
        double colatitude =
4001
7.77k
            parameterValueNumeric(EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS,
4002
7.77k
                                  common::UnitOfMeasure::DEGREE);
4003
7.77k
        double latitudePseudoStandardParallel = parameterValueNumeric(
4004
7.77k
            EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL,
4005
7.77k
            common::UnitOfMeasure::DEGREE);
4006
        // 30deg 17' 17.30311'' = 30.28813975277777776
4007
        // 30deg 17' 17.303''   = 30.288139722222223 as used in GDAL WKT1
4008
7.77k
        if (std::fabs(colatitude - 30.2881397) > 1e-7) {
4009
7
            throw io::FormattingException(
4010
7
                std::string("Unsupported value for ") +
4011
7
                EPSG_NAME_PARAMETER_COLATITUDE_CONE_AXIS);
4012
7
        }
4013
7.76k
        if (std::fabs(latitudePseudoStandardParallel - 78.5) > 1e-8) {
4014
0
            throw io::FormattingException(
4015
0
                std::string("Unsupported value for ") +
4016
0
                EPSG_NAME_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL);
4017
0
        }
4018
539k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A) {
4019
1.68k
        double latitudeOrigin = parameterValueNumeric(
4020
1.68k
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4021
1.68k
            common::UnitOfMeasure::DEGREE);
4022
1.68k
        if (latitudeOrigin != 0) {
4023
0
            throw io::FormattingException(
4024
0
                std::string("Unsupported value for ") +
4025
0
                EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4026
0
        }
4027
538k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B) {
4028
125
        const auto &scaleFactor = parameterValueMeasure(WKT1_SCALE_FACTOR, 0);
4029
125
        if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
4030
125
            std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
4031
0
            throw io::FormattingException(
4032
0
                "Unexpected presence of scale factor in Mercator (variant B)");
4033
0
        }
4034
125
        double latitudeOrigin = parameterValueNumeric(
4035
125
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4036
125
            common::UnitOfMeasure::DEGREE);
4037
125
        if (latitudeOrigin != 0) {
4038
0
            throw io::FormattingException(
4039
0
                std::string("Unsupported value for ") +
4040
0
                EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4041
0
        }
4042
538k
    } else if (methodEPSGCode ==
4043
538k
               EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
4044
        // We map TMSO to tmerc with axis=wsu. This only works if false easting
4045
        // and northings are zero, which is the case in practice for South
4046
        // African and Namibian EPSG CRS
4047
51
        const auto falseEasting = parameterValueNumeric(
4048
51
            EPSG_CODE_PARAMETER_FALSE_EASTING, common::UnitOfMeasure::METRE);
4049
51
        if (falseEasting != 0) {
4050
0
            throw io::FormattingException(
4051
0
                std::string("Unsupported value for ") +
4052
0
                EPSG_NAME_PARAMETER_FALSE_EASTING);
4053
0
        }
4054
51
        const auto falseNorthing = parameterValueNumeric(
4055
51
            EPSG_CODE_PARAMETER_FALSE_NORTHING, common::UnitOfMeasure::METRE);
4056
51
        if (falseNorthing != 0) {
4057
4
            throw io::FormattingException(
4058
4
                std::string("Unsupported value for ") +
4059
4
                EPSG_NAME_PARAMETER_FALSE_NORTHING);
4060
4
        }
4061
        // PROJ.4 specific hack for webmercator
4062
538k
    } else if (formatter->getCRSExport() &&
4063
538k
               methodEPSGCode ==
4064
1.85k
                   EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR) {
4065
2
        if (!createPROJ4WebMercator(this, formatter)) {
4066
0
            throw io::FormattingException(
4067
0
                std::string("Cannot export ") +
4068
0
                EPSG_NAME_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR +
4069
0
                " as PROJ.4 string outside of a ProjectedCRS context");
4070
0
        }
4071
2
        bConversionDone = true;
4072
2
        bEllipsoidParametersDone = true;
4073
2
        applyTargetCRSModifiers = false;
4074
538k
    } else if (ci_equal(convName, "Popular Visualisation Mercator")) {
4075
0
        if (formatter->getCRSExport()) {
4076
0
            if (!createPROJ4WebMercator(this, formatter)) {
4077
0
                throw io::FormattingException(concat(
4078
0
                    "Cannot export ", convName,
4079
0
                    " as PROJ.4 string outside of a ProjectedCRS context"));
4080
0
            }
4081
0
            applyTargetCRSModifiers = false;
4082
0
        } else {
4083
0
            formatter->addStep("webmerc");
4084
0
            if (l_sourceCRS) {
4085
0
                datum::Ellipsoid::WGS84->_exportToPROJString(formatter);
4086
0
            }
4087
0
        }
4088
0
        bConversionDone = true;
4089
0
        bEllipsoidParametersDone = true;
4090
538k
    } else if (starts_with(methodName, "PROJ ")) {
4091
66.5k
        bConversionDone = true;
4092
66.5k
        createPROJExtensionFromCustomProj(this, formatter, false);
4093
471k
    } else if (ci_equal(methodName,
4094
471k
                        PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
4095
1
        double southPoleLat = parameterValueNumeric(
4096
1
            PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION,
4097
1
            common::UnitOfMeasure::DEGREE);
4098
1
        double southPoleLong = parameterValueNumeric(
4099
1
            PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION,
4100
1
            common::UnitOfMeasure::DEGREE);
4101
1
        double rotation = parameterValueNumeric(
4102
1
            PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION,
4103
1
            common::UnitOfMeasure::DEGREE);
4104
1
        formatter->addStep("ob_tran");
4105
1
        formatter->addParam("o_proj", "longlat");
4106
1
        formatter->addParam("o_lon_p", -rotation);
4107
1
        formatter->addParam("o_lat_p", -southPoleLat);
4108
1
        formatter->addParam("lon_0", southPoleLong);
4109
1
        bConversionDone = true;
4110
471k
    } else if (ci_equal(
4111
471k
                   methodName,
4112
471k
                   PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) {
4113
0
        double gridNorthPoleLatitude = parameterValueNumeric(
4114
0
            PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION,
4115
0
            common::UnitOfMeasure::DEGREE);
4116
0
        double gridNorthPoleLongitude = parameterValueNumeric(
4117
0
            PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION,
4118
0
            common::UnitOfMeasure::DEGREE);
4119
0
        double northPoleGridLongitude = parameterValueNumeric(
4120
0
            PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION,
4121
0
            common::UnitOfMeasure::DEGREE);
4122
0
        formatter->addStep("ob_tran");
4123
0
        formatter->addParam("o_proj", "longlat");
4124
0
        formatter->addParam("o_lon_p", northPoleGridLongitude);
4125
0
        formatter->addParam("o_lat_p", gridNorthPoleLatitude);
4126
0
        formatter->addParam("lon_0", 180 + gridNorthPoleLongitude);
4127
0
        bConversionDone = true;
4128
471k
    } else if (ci_equal(methodName, "Adams_Square_II")) {
4129
        // Look for ESRI method and parameter names (to be opposed
4130
        // to the OGC WKT2 names we use elsewhere, because there's no mapping
4131
        // of those parameters to OGC WKT2)
4132
        // We at least support ESRI:54098 WGS_1984_Adams_Square_II and
4133
        // ESRI:54099 WGS_1984_Spilhaus_Ocean_Map_in_Square
4134
        // More generally, we think our implementation of +proj=spilhaus
4135
        // matches ESRI Adams_Square_II with just a sqrt(2) factor difference
4136
        // for the scale factor, with a ~20 cm difference (difference in
4137
        // ell_int_5() computation?)
4138
0
        const double falseEasting = parameterValueNumeric(
4139
0
            "False_Easting", common::UnitOfMeasure::METRE);
4140
0
        const double falseNorthing = parameterValueNumeric(
4141
0
            "False_Northing", common::UnitOfMeasure::METRE);
4142
0
        const double scaleFactor =
4143
0
            parameterValue("Scale_Factor", 0)
4144
0
                ? parameterValueNumeric("Scale_Factor",
4145
0
                                        common::UnitOfMeasure::SCALE_UNITY)
4146
0
                : 1.0;
4147
0
        const double azimuth =
4148
0
            parameterValueNumeric("Azimuth", common::UnitOfMeasure::DEGREE);
4149
0
        const double longitudeOfCenter = parameterValueNumeric(
4150
0
            "Longitude_Of_Center", common::UnitOfMeasure::DEGREE);
4151
0
        const double latitudeOfCenter = parameterValueNumeric(
4152
0
            "Latitude_Of_Center", common::UnitOfMeasure::DEGREE);
4153
0
        const double XYPlaneRotation = parameterValueNumeric(
4154
0
            "XY_Plane_Rotation", common::UnitOfMeasure::DEGREE);
4155
4156
0
        formatter->addStep("spilhaus");
4157
0
        formatter->addParam("lat_0", latitudeOfCenter);
4158
0
        formatter->addParam("lon_0", longitudeOfCenter);
4159
0
        formatter->addParam("azi", azimuth);
4160
0
        formatter->addParam("k_0", M_SQRT2 * scaleFactor);
4161
0
        formatter->addParam("rot", XYPlaneRotation);
4162
0
        formatter->addParam("x_0", falseEasting);
4163
0
        formatter->addParam("y_0", falseNorthing);
4164
0
        bConversionDone = true;
4165
471k
    } else if (ci_equal(methodName,
4166
471k
                        PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_SQUARE) ||
4167
471k
               ci_equal(methodName,
4168
471k
                        PROJ_WKT2_NAME_METHOD_PEIRCE_QUINCUNCIAL_DIAMOND)) {
4169
105
        const auto &scaleFactor = parameterValueMeasure(
4170
105
            EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4171
105
        if (scaleFactor.unit().type() != common::UnitOfMeasure::Type::UNKNOWN &&
4172
105
            std::fabs(scaleFactor.getSIValue() - 1.0) > 1e-10) {
4173
0
            throw io::FormattingException(
4174
0
                "Only scale factor = 1 handled for Peirce Quincuncial");
4175
0
        }
4176
105
        const auto &latitudeOfOriginDeg = parameterValueMeasure(
4177
105
            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4178
105
        if (latitudeOfOriginDeg.unit().type() !=
4179
105
                common::UnitOfMeasure::Type::UNKNOWN &&
4180
105
            std::fabs(parameterValueNumeric(
4181
105
                          EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4182
105
                          common::UnitOfMeasure::DEGREE) -
4183
105
                      90.0) > 1e-10) {
4184
0
            throw io::FormattingException("Only latitude of natural origin = "
4185
0
                                          "90 handled for Peirce Quincuncial");
4186
0
        }
4187
471k
    } else if (formatter->convention() ==
4188
471k
                   io::PROJStringFormatter::Convention::PROJ_5 &&
4189
471k
               isZUnitConversion) {
4190
33.0k
        double convFactor;
4191
33.0k
        if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
4192
33.0k
            convFactor = parameterValueNumericAsSI(
4193
33.0k
                EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
4194
33.0k
        } else {
4195
1
            assert(methodEPSGCode ==
4196
1
                   EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR);
4197
1
            const auto vertSrcCRS =
4198
1
                dynamic_cast<const crs::VerticalCRS *>(l_sourceCRS.get());
4199
1
            const auto vertTgtCRS =
4200
1
                dynamic_cast<const crs::VerticalCRS *>(l_targetCRS.get());
4201
1
            if (vertSrcCRS && vertTgtCRS) {
4202
0
                const double convSrc = vertSrcCRS->coordinateSystem()
4203
0
                                           ->axisList()[0]
4204
0
                                           ->unit()
4205
0
                                           .conversionToSI();
4206
0
                const double convDst = vertTgtCRS->coordinateSystem()
4207
0
                                           ->axisList()[0]
4208
0
                                           ->unit()
4209
0
                                           .conversionToSI();
4210
0
                convFactor = convSrc / convDst;
4211
1
            } else {
4212
1
                throw io::FormattingException(
4213
1
                    "Export of "
4214
1
                    "EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR "
4215
1
                    "conversion to a PROJ string "
4216
1
                    "requires an input and output vertical CRS");
4217
1
            }
4218
1
        }
4219
33.0k
        exportToPROJStringChangeVerticalUnit(formatter, convFactor);
4220
33.0k
        bConversionDone = true;
4221
33.0k
        bEllipsoidParametersDone = true;
4222
438k
    } else if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_TOPOCENTRIC) {
4223
0
        if (!srcGeogCRS) {
4224
0
            throw io::FormattingException(
4225
0
                "Export of Geographic/Topocentric conversion to a PROJ string "
4226
0
                "requires an input geographic CRS");
4227
0
        }
4228
4229
0
        formatter->addStep("cart");
4230
0
        srcGeogCRS->ellipsoid()->_exportToPROJString(formatter);
4231
4232
0
        formatter->addStep("topocentric");
4233
0
        const auto latOrigin = parameterValueNumeric(
4234
0
            EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN,
4235
0
            common::UnitOfMeasure::DEGREE);
4236
0
        const auto longOrigin = parameterValueNumeric(
4237
0
            EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN,
4238
0
            common::UnitOfMeasure::DEGREE);
4239
0
        const auto heightOrigin = parameterValueNumeric(
4240
0
            EPSG_CODE_PARAMETER_ELLIPSOIDAL_HEIGHT_TOPOCENTRIC_ORIGIN,
4241
0
            common::UnitOfMeasure::METRE);
4242
0
        formatter->addParam("lat_0", latOrigin);
4243
0
        formatter->addParam("lon_0", longOrigin);
4244
0
        formatter->addParam("h_0", heightOrigin);
4245
0
        bConversionDone = true;
4246
0
    }
4247
4248
573k
    bool bAxisSpecFound = false;
4249
573k
    if (!bConversionDone) {
4250
472k
        const MethodMapping *mapping = getMapping(l_method.get());
4251
472k
        if (mapping && mapping->proj_name_main) {
4252
99.1k
            formatter->addStep(mapping->proj_name_main);
4253
99.1k
            if (useApprox) {
4254
4
                formatter->addParam("approx");
4255
4
            }
4256
99.1k
            if (mapping->proj_name_aux) {
4257
6.83k
                bool addAux = true;
4258
6.83k
                if (internal::starts_with(mapping->proj_name_aux, "axis=")) {
4259
3.92k
                    if (mapping->epsg_code == EPSG_CODE_METHOD_KROVAK ||
4260
3.92k
                        mapping->epsg_code ==
4261
3.87k
                            EPSG_CODE_METHOD_KROVAK_MODIFIED) {
4262
3.87k
                        auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(
4263
3.87k
                            l_targetCRS.get());
4264
3.87k
                        if (projCRS) {
4265
2.31k
                            const auto &axisList =
4266
2.31k
                                projCRS->coordinateSystem()->axisList();
4267
2.31k
                            if (axisList[0]->direction() ==
4268
2.31k
                                    cs::AxisDirection::WEST &&
4269
2.31k
                                axisList[1]->direction() ==
4270
2.27k
                                    cs::AxisDirection::SOUTH) {
4271
2.27k
                                formatter->addParam("czech");
4272
2.27k
                                addAux = false;
4273
2.27k
                            }
4274
2.31k
                        }
4275
3.87k
                    }
4276
3.92k
                    bAxisSpecFound = true;
4277
3.92k
                }
4278
4279
                // No need to add explicit f=0 or R_A if the ellipsoid is a
4280
                // sphere
4281
6.83k
                if (strcmp(mapping->proj_name_aux, "f=0") == 0 ||
4282
6.83k
                    strcmp(mapping->proj_name_aux, "R_A") == 0) {
4283
2.43k
                    crs::CRS *horiz = l_sourceCRS.get();
4284
2.43k
                    const auto compound =
4285
2.43k
                        dynamic_cast<const crs::CompoundCRS *>(horiz);
4286
2.43k
                    if (compound) {
4287
1.00k
                        const auto &components =
4288
1.00k
                            compound->componentReferenceSystems();
4289
1.00k
                        if (!components.empty()) {
4290
1.00k
                            horiz = components.front().get();
4291
1.00k
                            const auto boundCRS =
4292
1.00k
                                dynamic_cast<const crs::BoundCRS *>(horiz);
4293
1.00k
                            if (boundCRS) {
4294
13
                                horiz = boundCRS->baseCRS().get();
4295
13
                            }
4296
1.00k
                        }
4297
1.00k
                    }
4298
4299
2.43k
                    auto geogCRS =
4300
2.43k
                        dynamic_cast<const crs::GeographicCRS *>(horiz);
4301
2.43k
                    if (geogCRS && geogCRS->ellipsoid()->isSphere()) {
4302
55
                        addAux = false;
4303
55
                    }
4304
2.43k
                }
4305
4306
6.83k
                if (addAux) {
4307
4.50k
                    auto kv = split(mapping->proj_name_aux, '=');
4308
4.50k
                    if (kv.size() == 2) {
4309
1.77k
                        formatter->addParam(kv[0], kv[1]);
4310
2.73k
                    } else {
4311
2.73k
                        formatter->addParam(mapping->proj_name_aux);
4312
2.73k
                    }
4313
4.50k
                }
4314
6.83k
            }
4315
4316
99.1k
            if (insertAxisWSU) {
4317
0
                formatter->addParam("axis", "wsu");
4318
0
            }
4319
4320
99.1k
            if (mapping->epsg_code ==
4321
99.1k
                EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4322
7
                double latitudeStdParallel = parameterValueNumeric(
4323
7
                    EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4324
7
                    common::UnitOfMeasure::DEGREE);
4325
7
                formatter->addParam("lat_0",
4326
7
                                    (latitudeStdParallel >= 0) ? 90.0 : -90.0);
4327
7
            }
4328
4329
530k
            for (int i = 0; mapping->params[i] != nullptr; i++) {
4330
431k
                const auto *param = mapping->params[i];
4331
431k
                if (!param->proj_name) {
4332
13.3k
                    continue;
4333
13.3k
                }
4334
418k
                const auto &value =
4335
418k
                    parameterValueMeasure(param->wkt2_name, param->epsg_code);
4336
418k
                double valueConverted = 0;
4337
418k
                if (value == nullMeasure) {
4338
                    // Deal with missing values. In an ideal world, this would
4339
                    // not happen
4340
626
                    if (param->epsg_code ==
4341
626
                        EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4342
54
                        valueConverted = 1.0;
4343
54
                    }
4344
626
                    if ((mapping->epsg_code ==
4345
626
                             EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4346
626
                         mapping->epsg_code ==
4347
486
                             EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
4348
626
                        param->epsg_code ==
4349
140
                            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
4350
                        // Do not use 0 as the default value for +gamma of
4351
                        // proj=omerc
4352
20
                        continue;
4353
20
                    }
4354
417k
                } else if (param->unit_type ==
4355
417k
                           common::UnitOfMeasure::Type::ANGULAR) {
4356
178k
                    valueConverted =
4357
178k
                        value.convertToUnit(common::UnitOfMeasure::DEGREE);
4358
239k
                } else {
4359
239k
                    valueConverted = value.getSIValue();
4360
239k
                }
4361
4362
418k
                if (mapping->epsg_code ==
4363
418k
                        EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
4364
418k
                    strcmp(param->proj_name, "lat_1") == 0) {
4365
263
                    formatter->addParam(param->proj_name, valueConverted);
4366
263
                    formatter->addParam("lat_0", valueConverted);
4367
418k
                } else if (
4368
418k
                    negateScaleFactor &&
4369
418k
                    param->epsg_code ==
4370
0
                        EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4371
0
                    formatter->addParam(param->proj_name, -valueConverted);
4372
418k
                } else {
4373
418k
                    formatter->addParam(param->proj_name, valueConverted);
4374
418k
                }
4375
418k
            }
4376
4377
373k
        } else {
4378
373k
            if (!exportToPROJStringGeneric(formatter)) {
4379
397
                throw io::FormattingException(
4380
397
                    concat("Unsupported conversion method: ", methodName));
4381
397
            }
4382
373k
        }
4383
472k
    }
4384
4385
573k
    if (l_targetCRS && applyTargetCRSModifiers) {
4386
167k
        crs::CRS *horiz = l_targetCRS.get();
4387
167k
        const auto compound = dynamic_cast<const crs::CompoundCRS *>(horiz);
4388
167k
        if (compound) {
4389
42.7k
            const auto &components = compound->componentReferenceSystems();
4390
42.7k
            if (!components.empty()) {
4391
42.7k
                horiz = components.front().get();
4392
42.7k
            }
4393
42.7k
        }
4394
4395
167k
        auto derivedProjCRS =
4396
167k
            dynamic_cast<const crs::DerivedProjectedCRS *>(horiz);
4397
4398
        // horiz != nullptr: only to make clang static analyzer happy
4399
167k
        if (!bEllipsoidParametersDone && horiz != nullptr &&
4400
167k
            derivedProjCRS == nullptr) {
4401
167k
            auto targetGeodCRS = horiz->extractGeodeticCRS();
4402
167k
            auto targetGeogCRS =
4403
167k
                std::dynamic_pointer_cast<crs::GeographicCRS>(targetGeodCRS);
4404
167k
            if (targetGeogCRS) {
4405
165k
                if (formatter->getCRSExport()) {
4406
1.89k
                    targetGeogCRS->addDatumInfoToPROJString(formatter);
4407
163k
                } else {
4408
163k
                    targetGeogCRS->ellipsoid()->_exportToPROJString(formatter);
4409
163k
                    targetGeogCRS->primeMeridian()->_exportToPROJString(
4410
163k
                        formatter);
4411
163k
                }
4412
165k
            } else if (targetGeodCRS) {
4413
2.06k
                targetGeodCRS->ellipsoid()->_exportToPROJString(formatter);
4414
2.06k
            }
4415
167k
        }
4416
4417
167k
        auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(horiz);
4418
167k
        if (projCRS == nullptr) {
4419
37.1k
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(horiz);
4420
37.1k
            if (boundCRS) {
4421
6.87k
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
4422
6.87k
                    boundCRS->baseCRS().get());
4423
6.87k
            }
4424
37.1k
        }
4425
167k
        if (projCRS) {
4426
135k
            formatter->pushOmitZUnitConversion();
4427
135k
            projCRS->addUnitConvertAndAxisSwap(formatter, bAxisSpecFound);
4428
135k
            formatter->popOmitZUnitConversion();
4429
135k
            if (projCRS->hasOver()) {
4430
0
                formatter->addParam("over");
4431
0
            }
4432
135k
        } else {
4433
31.8k
            if (derivedProjCRS) {
4434
0
                formatter->pushOmitZUnitConversion();
4435
0
                derivedProjCRS->addUnitConvertAndAxisSwap(formatter);
4436
0
                formatter->popOmitZUnitConversion();
4437
0
            }
4438
31.8k
        }
4439
4440
167k
        auto derivedGeographicCRS =
4441
167k
            dynamic_cast<const crs::DerivedGeographicCRS *>(horiz);
4442
167k
        if (!formatter->getCRSExport() && derivedGeographicCRS) {
4443
29.1k
            formatter->setOmitProjLongLatIfPossible(true);
4444
29.1k
            derivedGeographicCRS->addAngularUnitConvertAndAxisSwap(formatter);
4445
29.1k
            formatter->setOmitProjLongLatIfPossible(false);
4446
29.1k
        }
4447
167k
    }
4448
573k
}
4449
//! @endcond
4450
4451
// ---------------------------------------------------------------------------
4452
4453
/** \brief Return whether a conversion is a
4454
 * <a href="../../../operations/projections/utm.html">
4455
 * Universal Transverse Mercator</a> conversion.
4456
 *
4457
 * @param[out] zone UTM zone number between 1 and 60.
4458
 * @param[out] north true for UTM northern hemisphere, false for UTM southern
4459
 * hemisphere.
4460
 * @return true if it is a UTM conversion.
4461
 */
4462
24.9k
bool Conversion::isUTM(int &zone, bool &north) const {
4463
24.9k
    zone = 0;
4464
24.9k
    north = true;
4465
4466
24.9k
    if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
4467
        // Check for UTM
4468
4469
24.9k
        bool bLatitudeNatOriginUTM = false;
4470
24.9k
        bool bScaleFactorUTM = false;
4471
24.9k
        bool bFalseEastingUTM = false;
4472
24.9k
        bool bFalseNorthingUTM = false;
4473
124k
        for (const auto &genOpParamvalue : parameterValues()) {
4474
124k
            auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
4475
124k
                genOpParamvalue.get());
4476
124k
            if (opParamvalue) {
4477
124k
                const auto epsg_code = opParamvalue->parameter()->getEPSGCode();
4478
124k
                const auto &l_parameterValue = opParamvalue->parameterValue();
4479
124k
                if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
4480
124k
                    const auto &measure = l_parameterValue->value();
4481
124k
                    if (epsg_code ==
4482
124k
                            EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN &&
4483
124k
                        std::fabs(measure.value() -
4484
24.8k
                                  UTM_LATITUDE_OF_NATURAL_ORIGIN) < 1e-10) {
4485
24.8k
                        bLatitudeNatOriginUTM = true;
4486
99.6k
                    } else if (
4487
99.6k
                        (epsg_code ==
4488
99.6k
                             EPSG_CODE_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN ||
4489
99.6k
                         epsg_code ==
4490
74.7k
                             EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) &&
4491
99.6k
                        measure.unit()._isEquivalentTo(
4492
24.8k
                            common::UnitOfMeasure::DEGREE,
4493
24.8k
                            util::IComparable::Criterion::EQUIVALENT)) {
4494
24.8k
                        double dfZone = (measure.value() + 183.0) / 6.0;
4495
24.8k
                        if (dfZone > 0.9 && dfZone < 60.1 &&
4496
24.8k
                            std::abs(dfZone - std::round(dfZone)) < 1e-10) {
4497
1.15k
                            zone = static_cast<int>(std::lround(dfZone));
4498
1.15k
                        }
4499
74.7k
                    } else if (
4500
74.7k
                        epsg_code ==
4501
74.7k
                            EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN &&
4502
74.7k
                        measure.unit()._isEquivalentTo(
4503
24.8k
                            common::UnitOfMeasure::SCALE_UNITY,
4504
24.8k
                            util::IComparable::Criterion::EQUIVALENT) &&
4505
74.7k
                        std::fabs(measure.value() - UTM_SCALE_FACTOR) < 1e-10) {
4506
1.17k
                        bScaleFactorUTM = true;
4507
73.5k
                    } else if (epsg_code == EPSG_CODE_PARAMETER_FALSE_EASTING &&
4508
73.5k
                               measure.value() == UTM_FALSE_EASTING &&
4509
73.5k
                               measure.unit()._isEquivalentTo(
4510
1.17k
                                   common::UnitOfMeasure::METRE,
4511
1.17k
                                   util::IComparable::Criterion::EQUIVALENT)) {
4512
1.17k
                        bFalseEastingUTM = true;
4513
72.3k
                    } else if (epsg_code ==
4514
72.3k
                                   EPSG_CODE_PARAMETER_FALSE_NORTHING &&
4515
72.3k
                               measure.unit()._isEquivalentTo(
4516
24.8k
                                   common::UnitOfMeasure::METRE,
4517
24.8k
                                   util::IComparable::Criterion::EQUIVALENT)) {
4518
24.7k
                        if (std::fabs(measure.value() -
4519
24.7k
                                      UTM_NORTH_FALSE_NORTHING) < 1e-10) {
4520
24.7k
                            bFalseNorthingUTM = true;
4521
24.7k
                            north = true;
4522
24.7k
                        } else if (std::fabs(measure.value() -
4523
4
                                             UTM_SOUTH_FALSE_NORTHING) <
4524
4
                                   1e-10) {
4525
0
                            bFalseNorthingUTM = true;
4526
0
                            north = false;
4527
0
                        }
4528
24.7k
                    }
4529
124k
                }
4530
124k
            }
4531
124k
        }
4532
24.9k
        if (bLatitudeNatOriginUTM && zone > 0 && bScaleFactorUTM &&
4533
24.9k
            bFalseEastingUTM && bFalseNorthingUTM) {
4534
1.15k
            return true;
4535
1.15k
        }
4536
24.9k
    }
4537
23.7k
    return false;
4538
24.9k
}
4539
4540
// ---------------------------------------------------------------------------
4541
4542
/** \brief Return a Conversion object where some parameters are better
4543
 * identified.
4544
 *
4545
 * @return a new Conversion.
4546
 */
4547
515
ConversionNNPtr Conversion::identify() const {
4548
515
    auto newConversion = Conversion::nn_make_shared<Conversion>(*this);
4549
515
    newConversion->assignSelf(newConversion);
4550
4551
515
    if (method()->getEPSGCode() == EPSG_CODE_METHOD_TRANSVERSE_MERCATOR) {
4552
        // Check for UTM
4553
12
        int zone = 0;
4554
12
        bool north = true;
4555
12
        if (isUTM(zone, north)) {
4556
9
            newConversion->setProperties(
4557
9
                getUTMConversionProperty(util::PropertyMap(), zone, north));
4558
9
        }
4559
12
    }
4560
4561
515
    return newConversion;
4562
515
}
4563
4564
// ---------------------------------------------------------------------------
4565
4566
/** \brief Instantiate a conversion with method Geographic 2D offsets
4567
 *
4568
 * This method is defined as
4569
 * <a href="https://epsg.org/coord-operation-method_9619/index.html">
4570
 * EPSG:9619</a>.
4571
 *
4572
 * @param properties See \ref general_properties of the conversion.
4573
 * At minimum the name should be defined.
4574
 * @param offsetLat Latitude offset to add.
4575
 * @param offsetLong Longitude offset to add.
4576
 * @return new conversion.
4577
 */
4578
ConversionNNPtr
4579
Conversion::createGeographic2DOffsets(const util::PropertyMap &properties,
4580
                                      const common::Angle &offsetLat,
4581
0
                                      const common::Angle &offsetLong) {
4582
0
    return create(
4583
0
        properties,
4584
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
4585
0
        VectorOfParameters{
4586
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
4587
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
4588
0
        VectorOfValues{offsetLat, offsetLong});
4589
0
}
4590
4591
// ---------------------------------------------------------------------------
4592
4593
/** \brief Instantiate a conversion with method Geographic 3D offsets
4594
 *
4595
 * This method is defined as
4596
 * <a href="https://epsg.org/coord-operation-method_9660/index.html">
4597
 * EPSG:9660</a>.
4598
 *
4599
 * @param properties See \ref general_properties of the Conversion.
4600
 * At minimum the name should be defined.
4601
 * @param offsetLat Latitude offset to add.
4602
 * @param offsetLong Longitude offset to add.
4603
 * @param offsetHeight Height offset to add.
4604
 * @return new Conversion.
4605
 */
4606
ConversionNNPtr Conversion::createGeographic3DOffsets(
4607
    const util::PropertyMap &properties, const common::Angle &offsetLat,
4608
0
    const common::Angle &offsetLong, const common::Length &offsetHeight) {
4609
0
    return create(
4610
0
        properties,
4611
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
4612
0
        VectorOfParameters{
4613
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
4614
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
4615
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
4616
0
        VectorOfValues{offsetLat, offsetLong, offsetHeight});
4617
0
}
4618
4619
// ---------------------------------------------------------------------------
4620
4621
/** \brief Instantiate a conversion with method Geographic 2D with
4622
 * height
4623
 * offsets
4624
 *
4625
 * This method is defined as
4626
 * <a href="https://epsg.org/coord-operation-method_9618/index.html">
4627
 * EPSG:9618</a>.
4628
 *
4629
 * @param properties See \ref general_properties of the Conversion.
4630
 * At minimum the name should be defined.
4631
 * @param offsetLat Latitude offset to add.
4632
 * @param offsetLong Longitude offset to add.
4633
 * @param offsetHeight Geoid undulation to add.
4634
 * @return new Conversion.
4635
 */
4636
ConversionNNPtr Conversion::createGeographic2DWithHeightOffsets(
4637
    const util::PropertyMap &properties, const common::Angle &offsetLat,
4638
0
    const common::Angle &offsetLong, const common::Length &offsetHeight) {
4639
0
    return create(
4640
0
        properties,
4641
0
        createMethodMapNameEPSGCode(
4642
0
            EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_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_GEOID_HEIGHT)},
4647
0
        VectorOfValues{offsetLat, offsetLong, offsetHeight});
4648
0
}
4649
4650
// ---------------------------------------------------------------------------
4651
4652
/** \brief Instantiate a conversion with method Vertical Offset.
4653
 *
4654
 * This method is defined as
4655
 * <a href="https://epsg.org/coord-operation-method_9616/index.html">
4656
 * EPSG:9616</a>.
4657
 *
4658
 * @param properties See \ref general_properties of the Conversion.
4659
 * At minimum the name should be defined.
4660
 * @param offsetHeight Geoid undulation to add.
4661
 * @return new Conversion.
4662
 */
4663
ConversionNNPtr
4664
Conversion::createVerticalOffset(const util::PropertyMap &properties,
4665
0
                                 const common::Length &offsetHeight) {
4666
0
    return create(properties,
4667
0
                  createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
4668
0
                  VectorOfParameters{createOpParamNameEPSGCode(
4669
0
                      EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
4670
0
                  VectorOfValues{offsetHeight});
4671
0
}
4672
4673
// ---------------------------------------------------------------------------
4674
4675
} // namespace operation
4676
NS_PROJ_END