OsmHandlerLoad.cpp
Go to the documentation of this file.
1 #include <lanelet2_core/geometry/LineString.h>
2 #include <lanelet2_core/geometry/Polygon.h>
3 
4 #include <boost/geometry/algorithms/is_valid.hpp>
5 #include <fstream>
6 #include <iostream>
7 #include <pugixml.hpp>
8 #include <sstream>
9 
10 #include "lanelet2_io/Exceptions.h"
14 
15 using namespace std::string_literals;
16 
17 namespace lanelet {
18 namespace io_handlers {
19 
20 using Errors = std::vector<std::string>;
21 
22 namespace {
23 // register with factories
25 using traits::to2D;
26 bool isValid(const LineStrings3d& lss) {
27  BasicPolygon2d ls(utils::concatenate(lss, [](const auto& elem) { return to2D(elem).basicLineString(); }));
28  return boost::geometry::is_valid(ls);
29 }
30 
31 void reverse(LineStrings3d& lss) {
32  for (auto& ls : lss) {
33  ls = ls.invert();
34  }
35  std::reverse(lss.begin(), lss.end());
36 }
37 
38 template <typename PrimT>
39 PrimT getDummy(Id id) {
40  return PrimT(id);
41 }
42 template <>
43 RegulatoryElementPtr getDummy<RegulatoryElementPtr>(Id id) {
44  return std::make_shared<GenericRegulatoryElement>(std::make_shared<RegulatoryElementData>(id));
45 }
46 
47 Errors buildErrorMessage(const std::string& errorIntro, const Errors& errors) {
48  if (errors.empty()) {
49  return {};
50  }
51  Errors message{errorIntro};
52  message.reserve(errors.size() + 1);
53  for (const auto& error : errors) {
54  message.push_back("\t- " + error);
55  }
56  return message;
57 }
58 
59 class FromFileLoader { // NOLINT
60  public:
61  static std::unique_ptr<LaneletMap> loadMap(const osm::File& file, const Projector& projector, ErrorMessages& errors) {
62  FromFileLoader loader;
63 
64  loader.loadNodes(file.nodes, projector);
65  loader.loadWays(file.ways);
66  auto laneletsWithRelation = loader.loadLanelets(file.relations);
67  auto areasWithRelation = loader.loadAreas(file.relations);
68 
69  loader.loadRegulatoryElements(file.relations);
70  loader.addRegulatoryElements(laneletsWithRelation);
71  loader.addRegulatoryElements(areasWithRelation);
72  errors = std::move(loader.errors_);
73  return std::make_unique<LaneletMap>(loader.lanelets_, loader.areas_, loader.regulatoryElements_, loader.polygons_,
74  loader.lineStrings_, loader.points_);
75  }
76 
77  private:
78  template <typename PrimT>
79  using PrimitiveWithRegulatoryElement = std::pair<PrimT, const osm::Relation*>;
80 
81  template <typename PrimT>
82  using PrimitivesWithRegulatoryElement = std::vector<PrimitiveWithRegulatoryElement<PrimT>>;
83 
84  using AreasWithRegulatoryElements = PrimitivesWithRegulatoryElement<Area>;
85  using LaneletsWithRegulatoryElements = PrimitivesWithRegulatoryElement<Lanelet>;
86 
87  FromFileLoader() = default;
88 
89  void loadNodes(const lanelet::osm::Nodes& nodes, const Projector& projector) {
90  for (const auto& nodeElem : nodes) {
91  const auto& node = nodeElem.second;
92  try {
93  points_.emplace(node.id, Point3d(node.id, projector.forward(node.point), getAttributes(node.attributes)));
94  } catch (ForwardProjectionError& e) {
95  parserError(node.id, e.what());
96  }
97  }
98  }
99  void loadWays(const lanelet::osm::Ways& ways) {
100  for (const auto& wayElem : ways) {
101  const auto& way = wayElem.second;
102  // reconstruct points
103  Points3d points;
104  points = utils::transform(way.nodes,
105  [this, &way](const auto& n) { return this->getOrGetDummy(points_, n->id, way.id); });
106  if (points.empty()) {
107  parserError(way.id, "Ways must have at least one point!");
108  continue;
109  }
110 
111  const auto id = way.id;
112  const auto attributes = getAttributes(way.attributes);
113 
114  // determine area or way
115  auto isArea = attributes.find(AttributeNamesString::Area);
116  if (isArea != attributes.end() && isArea->second.asBool().get_value_or(false)) {
117  polygons_.emplace(id, Polygon3d(id, points, attributes));
118  } else {
119  lineStrings_.emplace(id, LineString3d(id, points, attributes));
120  }
121  }
122  }
123 
124  LaneletsWithRegulatoryElements loadLanelets(const lanelet::osm::Relations& relations) {
125  // The regulatory elements are not parsed yet. We store lanelets with one
126  // for
127  // later.
128  LaneletsWithRegulatoryElements llWithRegulatoryElement;
129  for (const auto& relElem : relations) {
130  const auto& llElem = relElem.second;
131  if (!isType<AttributeValueString::Lanelet>(llElem)) {
132  continue;
133  }
134  const auto id = llElem.id;
135  const auto attributes = getAttributes(llElem.attributes);
136  auto left = getLaneletBorder(llElem, RoleNameString::Left);
137  auto right = getLaneletBorder(llElem, RoleNameString::Right);
138 
139  // correct their orientation
140  std::tie(left, right) = geometry::align(left, right);
141 
142  // look for optional centerline
143  Lanelet lanelet(id, left, right, attributes);
144  if (findRole(llElem.members, RoleNameString::Centerline) != llElem.members.end()) {
145  auto center = getLaneletBorder(llElem, RoleNameString::Centerline);
146  lanelet.setCenterline(center);
147  }
148 
149  lanelets_.emplace(id, lanelet);
150 
151  // check for regulatory elements
152  if (findRole(llElem.members, RoleNameString::RegulatoryElement) != llElem.members.end()) {
153  llWithRegulatoryElement.push_back(std::make_pair(lanelet, &llElem));
154  }
155  }
156  return llWithRegulatoryElement;
157  }
158 
159  AreasWithRegulatoryElements loadAreas(const lanelet::osm::Relations& relations) {
160  // regElems are not parsed yet. We store areas with one for later.
161  AreasWithRegulatoryElements arWithRegulatoryElement;
162  for (const auto& relElem : relations) {
163  const auto& arElem = relElem.second;
164  if (!isType<AttributeValueString::Multipolygon>(arElem)) {
165  continue;
166  }
167  const auto id = arElem.id;
168  const auto attributes = getAttributes(arElem.attributes);
169 
170  auto outerRing = getOuterRing(arElem);
171  if (outerRing.empty()) {
172  // getOuter ring repors errors for us
173  continue;
174  }
175 
176  Area area(id, outerRing, getInnerRing(arElem), attributes);
177  areas_.emplace(id, area);
178 
179  // check for regulatory elements
180  if (findRole(arElem.members, RoleNameString::RegulatoryElement) != arElem.members.end()) {
181  arWithRegulatoryElement.push_back(std::make_pair(area, &arElem));
182  }
183  }
184  return arWithRegulatoryElement;
185  }
186 
187  void loadRegulatoryElements(const osm::Relations& relations) {
188  for (const auto& relElem : relations) {
189  const auto& regElem = relElem.second;
190  if (!isType<AttributeValueString::RegulatoryElement>(regElem)) {
191  continue;
192  }
193  const auto id = regElem.id;
194  const auto attributes = getAttributes(regElem.attributes);
195  const auto type = attributes.find(AttributeName::Subtype);
196  if (type == attributes.end()) {
197  parserError(id, "Regulatory element has no 'subtype' tag.");
198  continue;
199  }
200  auto rules = getRulesForRegulatoryElement(id, regElem.members);
201  auto regelemData = std::make_shared<RegulatoryElementData>(id, rules, attributes);
202  auto regelemType = type->second.value();
203  try {
204  auto regElem = RegulatoryElementFactory::create(regelemType, regelemData);
205  regulatoryElements_.emplace(id, regElem);
206  } catch (std::exception& e) {
207  parserError(id, "Creating a regulatory element of type "s + regelemType + " failed: " + e.what());
208  }
209  }
210  }
211 
212  template <typename PrimT>
213  void addRegulatoryElements(std::vector<std::pair<PrimT, const osm::Relation*>>& addTos) {
214  for (auto& addTo : addTos) {
215  osm::forEachMember(addTo.second->members, RoleNameString::RegulatoryElement, [&](const osm::Role& role) {
216  auto regElem = getOrGetDummy(regulatoryElements_, role.second->id, addTo.first.id());
217  addTo.first.addRegulatoryElement(regElem);
218  });
219  }
220  }
221 
222  // helper functions
223  template <const char* Type>
224  bool isType(const lanelet::osm::Relation& relation) {
225  auto attr = relation.attributes.find(AttributeNamesString::Type);
226  return attr != relation.attributes.end() && attr->second == Type;
227  }
228 
229  static lanelet::AttributeMap getAttributes(const lanelet::osm::Attributes& osmAttributes) {
230  lanelet::AttributeMap attributes;
231  for (const auto& osmAttr : osmAttributes) {
232  attributes.insert(std::make_pair(osmAttr.first, lanelet::Attribute(osmAttr.second)));
233  }
234  return attributes;
235  }
236 
237  LineString3d getLaneletBorder(const osm::Relation& llElem, const std::string& role) {
238  size_t numMembers = 0;
239  osm::forEachMember(llElem.members, role, [&](auto& /*role*/) { ++numMembers; });
240  if (numMembers != 1) {
241  parserError(llElem.id, "Lanelet has not exactly one "s + role + " border!");
242  return LineString3d(llElem.id);
243  }
244  auto member = osm::findRole(llElem.members, role);
245  if (member->second->type() != AttributeValueString::Way) {
246  parserError(llElem.id, "Lanelet "s + role + " border is not of type way!");
247  return LineString3d(llElem.id);
248  }
249  return getOrGetDummy(lineStrings_, member->second->id, llElem.id);
250  }
251 
252  LineStrings3d getLinestrings(const osm::Roles& roles, const std::string& roleName, Id refId) {
253  LineStrings3d linestrings;
254  osm::forEachMember(roles, roleName, [&](auto& member) {
255  if (member.second->type() != AttributeValueString::Way) {
256  auto msg = roleName + " ring must consist of ways but id " + std::to_string(member.second->id) +
257  " is of type " + member.second->type() + "!";
258  msg[0] = std::toupper(msg[0]);
259  this->parserError(refId, msg);
260  return;
261  }
262  auto elem = lineStrings_.find(member.second->id);
263  if (elem == lineStrings_.end()) {
264  this->parserError(refId, "Failed to get id "s + std::to_string(member.second->id) + " from map");
265  return;
266  }
267  linestrings.push_back(elem->second);
268  });
269  return linestrings;
270  }
271 
272  LineStrings3d getOuterRing(const osm::Relation& area) {
273  auto outerLs = getLinestrings(area.members, RoleNameString::Outer, area.id);
274  if (outerLs.empty()) {
275  parserError(area.id, "Areas must have at least one outer border!");
276  return {};
277  }
278  auto outerRings = assembleBoundary(outerLs, area.id);
279  if (outerRings.size() != 1) {
280  parserError(area.id, "Areas must have exactly one outer ring!");
281  return {};
282  }
283  return outerRings.front();
284  }
285 
286  std::vector<LineStrings3d> getInnerRing(const osm::Relation& area) {
287  auto innerLs = getLinestrings(area.members, RoleNameString::Inner, area.id);
288  return assembleBoundary(innerLs, area.id);
289  }
290 
291  RuleParameterMap getRulesForRegulatoryElement(Id currElemId, const osm::Roles& roles) {
292  RuleParameterMap rules;
293  for (const auto& memberPair : roles) {
294  const auto& member = memberPair.second;
295  if (member->type() == AttributeValueString::Node) {
296  auto newMember = getOrGetDummy(points_, member->id, currElemId);
297  rules[memberPair.first].emplace_back(newMember);
298  } else if (member->type() == AttributeValueString::Way) {
299  // can either be linestring or polygon
300  if (polygons_.find(member->id) != polygons_.end()) {
301  auto newMember = getOrGetDummy(polygons_, member->id, currElemId);
302  rules[memberPair.first].emplace_back(newMember);
303  } else {
304  auto newMember = getOrGetDummy(lineStrings_, member->id, currElemId);
305  rules[memberPair.first].emplace_back(newMember);
306  }
307  } else if (member->type() == AttributeValueString::Relation) {
308  // could be lanelet or area. regulatory element is not allowed.
309  auto type = member->attributes.find(AttributeNamesString::Type);
310  if (type == member->attributes.end()) {
311  parserError(currElemId,
312  "Relation refers to another relation "s + std::to_string(member->id) + " without a type tag!");
313  } else if (type->second == AttributeValueString::Lanelet) {
314  auto newMember = getOrGetDummy(lanelets_, member->id, currElemId);
315  rules[memberPair.first].emplace_back(newMember);
316  } else if (type->second == AttributeValueString::Multipolygon) {
317  auto newMember = getOrGetDummy(areas_, member->id, currElemId);
318  rules[memberPair.first].emplace_back(newMember);
319  } else if (type->second == AttributeValueString::RegulatoryElement) {
320  parserError(currElemId,
321  "Regulatory element refers to another "
322  "regulatory element. This is not "
323  "supported.");
324  } else {
325  parserError(currElemId, "Member of regulatory_element has unsupported type "s + type->second);
326  }
327  }
328  }
329  return rules;
330  }
331 
332  std::vector<LineStrings3d> assembleBoundary(LineStrings3d lineStrings, Id id) {
333  std::reverse(lineStrings.begin(), lineStrings.end()); // its easier to pop from a vector...
334  std::vector<LineStrings3d> rings;
335  rings.emplace_back(LineStrings3d());
336  while (!lineStrings.empty()) {
337  auto& currRing = rings.back();
338  if (currRing.empty()) {
339  currRing.push_back(lineStrings.back());
340  lineStrings.pop_back();
341  } else {
342  const auto lastId = currRing.back().back().id();
343  auto elem = std::find_if(lineStrings.rbegin(), lineStrings.rend(), [lastId](const auto& elem) {
344  return elem.back().id() == lastId || elem.front().id() == lastId;
345  });
346  // we are unable to close the current ring
347  if (elem == lineStrings.rend()) {
348  parserError(id, "Could not complete boundary around linestring " + std::to_string(currRing.back().id()));
349  rings.back() = LineStrings3d();
350  continue;
351  }
352  // we found the matching next linestring. add it in the correct order
353  auto newLineString = *elem;
354  lineStrings.erase(std::next(elem).base());
355  if (newLineString.back().id() == lastId) {
356  newLineString = newLineString.invert();
357  }
358  currRing.push_back(newLineString);
359  }
360 
361  // check if we closed the ring
362  if (currRing.back().back().id() == currRing.front().front().id()) {
363  // wohoo. Check the clockwise requirement.
364  if (!isValid(currRing)) {
365  reverse(currRing);
366  if (!isValid(currRing)) {
367  // most probably self-intersecting...
368  parserError(id, "Failed to generate boundary (self-intersecting?)");
369  rings.pop_back();
370  }
371  }
372  rings.emplace_back(LineStrings3d());
373  }
374  }
375  rings.pop_back(); // last ring will be empty or invalid
376  return rings;
377  }
378 
379  template <typename PrimT>
380  PrimT getOrGetDummy(const typename std::unordered_map<Id, PrimT>& map, Id id, Id currentPrimitiveId) {
381  try {
382  return map.at(id);
383  } catch (std::out_of_range&) {
384  parserError(currentPrimitiveId, "Failed to get id "s + std::to_string(id) + " from map");
385  return getDummy<PrimT>(id);
386  }
387  }
388 
389  void parserError(Id id, const std::string& what) {
390  auto errstr = "Error parsing primitive "s + std::to_string(id) + ": " + what;
391  errors_.push_back(errstr);
392  }
393 
395  LaneletLayer::Map lanelets_;
396  AreaLayer::Map areas_;
401 };
402 
403 template <typename MapT>
404 void registerIds(const MapT& map) {
405  if (!map.empty()) {
406  utils::registerId(map.rbegin()->first);
407  }
408 }
409 
410 void testAndPrintLocaleWarning(ErrorMessages& errors) {
411  auto* decimalPoint = std::localeconv()->decimal_point;
412  if (decimalPoint == nullptr || *decimalPoint != '.') {
413  std::stringstream ss;
414  ss << "Warning: Current decimal point of the C locale is set to \""
415  << (decimalPoint == nullptr ? ' ' : *decimalPoint) << "\". The loaded map will have wrong coordinates!\n";
416  errors.emplace_back(ss.str());
417  std::cerr << errors.back();
418  }
419 }
420 } // namespace
421 
422 std::unique_ptr<LaneletMap> OsmParser::parse(const std::string& filename, ErrorMessages& errors) const {
423  // read xml
424  pugi::xml_document doc;
425  auto result = doc.load_file(filename.c_str());
426  if (!result) {
427  throw lanelet::ParseError("Errors occured while parsing osm file: "s + result.description());
428  }
429  osm::Errors osmReadErrors;
430  testAndPrintLocaleWarning(osmReadErrors);
431  auto file = lanelet::osm::read(doc, &osmReadErrors);
432  auto map = fromOsmFile(file, errors);
433  // make sure ids in the file are known to Lanelet2 id management.
434  registerIds(file.nodes);
435  registerIds(file.ways);
436  registerIds(file.relations);
437  errors = buildErrorMessage("Errors ocurred while parsing Lanelet Map:", utils::concatenate({osmReadErrors, errors}));
438  return map;
439 }
440 
441 std::unique_ptr<LaneletMap> OsmParser::fromOsmFile(const osm::File& file, ErrorMessages& errors) const {
442  return FromFileLoader::loadMap(file, projector(), errors);
443 }
444 } // namespace io_handlers
445 } // namespace lanelet
lanelet::ErrorMessages
std::vector< std::string > ErrorMessages
Definition: Io.h:11
lanelet::LineStrings3d
std::vector< LineString3d > LineStrings3d
lanelet::Attribute
lanelets_
LaneletLayer::Map lanelets_
Definition: OsmHandlerLoad.cpp:395
regulatoryElements_
RegulatoryElementLayer::Map regulatoryElements_
Definition: OsmHandlerLoad.cpp:397
to2D
BoundingBox2d to2D(const BoundingBox3d &primitive)
lanelet
lanelet::RegulatoryElementPtr
std::shared_ptr< RegulatoryElement > RegulatoryElementPtr
file
osm::File & file
Definition: OsmHandlerWrite.cpp:245
lanelet::osm::Relations
std::map< Id, Relation > Relations
Definition: OsmFile.h:65
HybridMap< Attribute, decltype(AttributeNamesString::Map)&, AttributeNamesString::Map >::insert
std::pair< iterator, bool > insert(const value_type &v)
lanelet::Id
int64_t Id
lanelet::io_handlers::Errors
std::vector< std::string > Errors
Definition: OsmHandlerLoad.cpp:20
errors_
Errors errors_
Definition: OsmHandlerLoad.cpp:394
lanelet::osm::Relation::members
Roles members
Definition: OsmFile.h:60
OsmHandler.h
HybridMap< RuleParameters, decltype(RoleNameString::Map)&, RoleNameString::Map >::find
iterator find(const key_type &k)
lineStrings_
LineStringLayer::Map lineStrings_
Definition: OsmHandlerLoad.cpp:399
lanelet::osm::Nodes
std::map< Id, Node > Nodes
Definition: OsmFile.h:63
lanelet::Area
lanelet::osm::Relation
Osm relation object.
Definition: OsmFile.h:55
lanelet::Projector
Definition: Projection.h:22
lanelet::osm::read
File read(pugi::xml_document &node, lanelet::osm::Errors *errors=nullptr)
Definition: OsmFile.cpp:343
lanelet::osm::Ways
std::map< Id, Way > Ways
Definition: OsmFile.h:64
lanelet::osm::File
Intermediate representation of an osm file.
Definition: OsmFile.h:72
areas_
AreaLayer::Map areas_
Definition: OsmHandlerLoad.cpp:396
lanelet::Lanelet
AttributeName::Type
@ Type
lanelet::osm::Role
std::pair< std::string, Primitive * > Role
Definition: OsmFile.h:18
lanelet::Points3d
std::vector< Point3d > Points3d
PrimitiveLayer< RegulatoryElementPtr >::Map
std::unordered_map< Id, RegulatoryElementPtr > Map
lanelet::osm::Errors
std::vector< std::string > Errors
Definition: OsmFile.h:20
lanelet::osm::Attributes
std::map< std::string, std::string > Attributes
Definition: OsmFile.h:17
lanelet::osm::Roles
std::deque< Role > Roles
Definition: OsmFile.h:19
lanelet::osm::forEachMember
auto forEachMember(const Roles &roles, const std::string &roleName, Func &&f)
Definition: OsmFile.h:90
lanelet::io_handlers::RegisterParser
Registration object for a parser. Needs to be instanciated as static object once to register a parser...
Definition: Factory.h:153
lanelet::osm::Primitive::id
Id id
Definition: OsmFile.h:33
lanelet::Point3d
HybridMap< Attribute, decltype(AttributeNamesString::Map)&, AttributeNamesString::Map >
lanelet::Projector::forward
virtual BasicPoint3d forward(const GPSPoint &p) const =0
Project a point from lat/lon coordinates to a local coordinate system.
OsmFile.h
relation
Id relation
Definition: OsmFile.cpp:37
polygons_
PolygonLayer::Map polygons_
Definition: OsmHandlerLoad.cpp:398
lanelet::Polygon3d
lanelet::ParseError
Error thrown if some error occured during the parsing of the file.
Definition: Exceptions.h:42
Factory.h
lanelet::LineString3d
lanelet::ForwardProjectionError
Thrown by the projector classes if projection from lat/lon to x/y fails.
Definition: Exceptions.h:57
points_
PointLayer::Map points_
Definition: OsmHandlerLoad.cpp:400
lanelet::BasicPolygon2d
Exceptions.h
lanelet::osm::findRole
auto findRole(const Roles &roles, const std::string &roleName)
Definition: OsmFile.h:85


lanelet2_io
Author(s): Fabian Poggenhans
autogenerated on Thu Mar 6 2025 03:26:03