Carma-platform v4.2.0
CARMA Platform is built on robot operating system (ROS) and utilizes open source software (OSS) that enables Cooperative Driving Automation (CDA) features to allow Automated Driving Systems to interact and cooperate with infrastructure and other vehicles through communication.
geodetic.cpp
Go to the documentation of this file.
1// Copyright 2023 Leidos
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
17#include <proj.h>
18#include <gsl/pointers>
19
20#include <algorithm>
21#include <string>
22
24
26{
27auto calculate_utm_zone(const Wgs84Coordinate & coordinate) -> UtmZone
28{
29 // Note: std::floor prevents this function from being constexpr (until C++23)
30
31 static constexpr std::size_t zone_width{6};
32 static constexpr std::size_t max_zones{60};
33
34 // Works for longitudes [-180, 360). Longitude of 360 will assign 61.
35 const auto number{
36 static_cast<std::size_t>(
37 (std::floor(carma_cooperative_perception::remove_units(coordinate.longitude) + 180) /
38 zone_width)) +
39 1};
40
41 UtmZone zone;
42
43 // std::min is used to handle the "UTM Zone 61" case.
44 zone.number = std::min(number, max_zones);
45
46 if (coordinate.latitude < units::angle::degree_t{0.0}) {
48 } else {
50 }
51
52 return zone;
53}
54
55auto project_to_carma_map(const Wgs84Coordinate & coordinate, std::string_view proj_string)
57{
58 gsl::owner<PJ_CONTEXT *> context = proj_context_create();
59 proj_log_level(context, PJ_LOG_NONE);
60
61 if (!context) {
62 const std::string error_string{proj_errno_string(proj_context_errno(context))};
63 throw std::invalid_argument("Could not create PROJ context: " + error_string + '.');
64 }
65
66 gsl ::owner<PJ *> transformation =
67 proj_create_crs_to_crs(context, "EPSG:4326", proj_string.data(), nullptr);
68
69 if (!transformation) {
70 const std::string error_string{proj_errno_string(proj_context_errno(context))};
71 throw std::invalid_argument("Could not create PROJ transform: " + error_string + '.');
72 }
73
74 const auto coord_wgs84 = proj_coord(
76 carma_cooperative_perception::remove_units(coordinate.longitude), 0, 0);
77 const auto coord_projected = proj_trans(transformation, PJ_FWD, coord_wgs84);
78
79 proj_destroy(transformation);
80 proj_context_destroy(context);
81
82 return {
83 units::length::meter_t{coord_projected.enu.e}, units::length::meter_t{coord_projected.enu.n},
84 units::length::meter_t{coordinate.elevation}};
85}
86
87auto project_to_utm(const Wgs84Coordinate & coordinate) -> UtmCoordinate
88{
89 gsl::owner<PJ_CONTEXT *> context = proj_context_create();
90 proj_log_level(context, PJ_LOG_NONE);
91
92 if (context == nullptr) {
93 const std::string error_string{proj_errno_string(proj_context_errno(context))};
94 throw std::invalid_argument("Could not create PROJ context: " + error_string + '.');
95 }
96
97 const auto utm_zone{calculate_utm_zone(coordinate)};
98 std::string proj_string{"+proj=utm +zone=" + std::to_string(utm_zone.number) + " +datum=WGS84"};
99
100 if (utm_zone.hemisphere == Hemisphere::kSouth) {
101 proj_string += " +south";
102 }
103
104 gsl ::owner<PJ *> utm_transformation =
105 proj_create_crs_to_crs(context, "EPSG:4326", proj_string.c_str(), nullptr);
106
107 if (utm_transformation == nullptr) {
108 const std::string error_string{proj_errno_string(proj_context_errno(context))};
109 throw std::invalid_argument("Could not create PROJ transform: " + error_string + '.');
110 }
111
112 auto coord_wgs84 = proj_coord(
114 carma_cooperative_perception::remove_units(coordinate.longitude), 0, 0);
115 auto coord_utm = proj_trans(utm_transformation, PJ_FWD, coord_wgs84);
116
117 proj_destroy(utm_transformation);
118 proj_context_destroy(context);
119
120 return {
121 utm_zone, units::length::meter_t{coord_utm.enu.e}, units::length::meter_t{coord_utm.enu.n},
122 units::length::meter_t{coordinate.elevation}};
123}
124
125auto calculate_grid_convergence(const Wgs84Coordinate & position, std::string_view georeference)
126 -> units::angle::degree_t
127{
128 gsl::owner<PJ_CONTEXT *> context = proj_context_create();
129 proj_log_level(context, PJ_LOG_NONE);
130
131 if (context == nullptr) {
132 const std::string error_string{proj_errno_string(proj_context_errno(context))};
133 throw std::invalid_argument("Could not create PROJ context: " + error_string + '.');
134 }
135
136 gsl::owner<PJ *> transform = proj_create(context, georeference.data());
137
138 const auto factors = proj_factors(
139 transform, proj_coord(
140 proj_torad(carma_cooperative_perception::remove_units(position.longitude)),
141 proj_torad(carma_cooperative_perception::remove_units(position.latitude)), 0, 0));
142
143 if (proj_context_errno(context) != 0) {
144 const std::string error_string{proj_errno_string(proj_context_errno(context))};
145 throw std::invalid_argument("Could not calculate PROJ factors: " + error_string + '.');
146 }
147
148 proj_destroy(transform);
149 proj_context_destroy(context);
150
151 return units::angle::degree_t{proj_todeg(factors.meridian_convergence)};
152}
153
154auto calculate_grid_convergence(const Wgs84Coordinate & position, const UtmZone & zone)
155 -> units::angle::degree_t
156{
157 // N.B. developers: PROJ and the related geodetic calculations seem particularly sensitive
158 // to the parameters in this PROJ string. If you run into problems with you calculation
159 // results, carefully check this or any other PROJ string.
160 std::string proj_string{
161 "+proj=utm +zone=" + std::to_string(zone.number) + " +datum=WGS84 +units=m +no_defs"};
162
163 if (zone.hemisphere == Hemisphere::kSouth) {
164 proj_string += " +south";
165 }
166
167 return calculate_grid_convergence(position, proj_string.c_str());
168}
169
170} // namespace carma_cooperative_perception
auto calculate_grid_convergence(const Wgs84Coordinate &position, const UtmZone &zone) -> units::angle::degree_t
Calculate grid convergence at a given position.
Definition: geodetic.cpp:154
auto project_to_carma_map(const Wgs84Coordinate &coordinate, std::string_view proj_string) -> MapCoordinate
Definition: geodetic.cpp:55
auto to_string(const UtmZone &zone) -> std::string
Definition: utm_zone.cpp:21
auto calculate_utm_zone(const Wgs84Coordinate &coordinate) -> UtmZone
Get the UTM zone number from a WGS-84 coordinate.
Definition: geodetic.cpp:27
constexpr auto remove_units(const T &value)
auto project_to_utm(const Wgs84Coordinate &coordinate) -> UtmCoordinate
Projects a Wgs84Coordinate to its corresponding UTM zone.
Definition: geodetic.cpp:87
Represents a position using UTM coordinates.
Definition: geodetic.hpp:50
Represents a position using WGS-84 coordinates.
Definition: geodetic.hpp:33