1 #ifndef OSMIUM_GEOM_FACTORY_HPP 2 #define OSMIUM_GEOM_FACTORY_HPP 3 4 /* 5 6 This file is part of Osmium (https://osmcode.org/libosmium). 7 8 Copyright 2013-2021 Jochen Topf <jochen@topf.org> and others (see README). 9 10 Boost Software License - Version 1.0 - August 17th, 2003 11 12 Permission is hereby granted, free of charge, to any person or organization 13 obtaining a copy of the software and accompanying documentation covered by 14 this license (the "Software") to use, reproduce, display, distribute, 15 execute, and transmit the Software, and to prepare derivative works of the 16 Software, and to permit third-parties to whom the Software is furnished to 17 do so, all subject to the following: 18 19 The copyright notices in the Software and this entire statement, including 20 the above license grant, this restriction and the following disclaimer, 21 must be included in all copies of the Software, in whole or in part, and 22 all derivative works of the Software, unless such copies or derivative 23 works are solely in the form of machine-executable object code generated by 24 a source language processor. 25 26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 29 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 30 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 31 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 32 DEALINGS IN THE SOFTWARE. 33 34 */ 35 36 #include <osmium/geom/coordinates.hpp> 37 #include <osmium/memory/collection.hpp> 38 #include <osmium/memory/item.hpp> 39 #include <osmium/osm/area.hpp> 40 #include <osmium/osm/item_type.hpp> 41 #include <osmium/osm/location.hpp> 42 #include <osmium/osm/node.hpp> 43 #include <osmium/osm/node_ref.hpp> 44 #include <osmium/osm/node_ref_list.hpp> 45 #include <osmium/osm/types.hpp> 46 #include <osmium/osm/way.hpp> 47 #include <osmium/util/compatibility.hpp> 48 49 #include <cstddef> 50 #include <stdexcept> 51 #include <string> 52 #include <utility> 53 54 namespace osmium { 55 56 /** 57 * Exception thrown when an invalid geometry is encountered. An example 58 * would be a linestring with less than two points. 59 */ 60 class OSMIUM_EXPORT geometry_error : public std::runtime_error { 61 62 std::string m_message; 63 osmium::object_id_type m_id; 64 65 public: 66 geometry_error(const std::string & message,const char * object_type="",osmium::object_id_type id=0)67 explicit geometry_error(const std::string& message, const char* object_type = "", osmium::object_id_type id = 0) : 68 std::runtime_error(message), 69 m_message(message), 70 m_id(id) { 71 if (m_id != 0) { 72 m_message += " ("; 73 m_message += object_type; 74 m_message += "_id="; 75 m_message += std::to_string(m_id); 76 m_message += ")"; 77 } 78 } 79 set_id(const char * object_type,osmium::object_id_type id)80 void set_id(const char* object_type, osmium::object_id_type id) { 81 if (m_id == 0 && id != 0) { 82 m_message += " ("; 83 m_message += object_type; 84 m_message += "_id="; 85 m_message += std::to_string(id); 86 m_message += ")"; 87 } 88 m_id = id; 89 } 90 id() const91 osmium::object_id_type id() const noexcept { 92 return m_id; 93 } 94 what() const95 const char* what() const noexcept override { 96 return m_message.c_str(); 97 } 98 99 }; // class geometry_error 100 101 /** 102 * @brief Everything related to geometry handling. 103 */ 104 namespace geom { 105 106 /** 107 * Which nodes of a way to use for a linestring. 108 */ 109 enum class use_nodes : bool { 110 unique = true, ///< Remove consecutive nodes with same location. 111 all = false ///< Use all nodes. 112 }; // enum class use_nodes 113 114 /** 115 * Which direction the linestring created from a way 116 * should have. 117 */ 118 enum class direction : bool { 119 backward = true, ///< Linestring has reverse direction. 120 forward = false ///< Linestring has same direction as way. 121 }; // enum class direction 122 123 /** 124 * This pseudo projection just returns its WGS84 input unchanged. 125 * Used as a template parameter if a real projection is not needed. 126 */ 127 class IdentityProjection { 128 129 public: 130 operator ()(osmium::Location location) const131 Coordinates operator()(osmium::Location location) const { 132 return Coordinates{location.lon(), location.lat()}; 133 } 134 epsg()135 static int epsg() noexcept { 136 return 4326; 137 } 138 proj_string()139 static std::string proj_string() noexcept { 140 return "+proj=longlat +datum=WGS84 +no_defs"; 141 } 142 143 }; // class IdentityProjection 144 145 /** 146 * Geometry factory. 147 */ 148 template <typename TGeomImpl, typename TProjection = IdentityProjection> 149 class GeometryFactory { 150 151 /** 152 * Add all points of an outer or inner ring to a multipolygon. 153 */ add_points(const osmium::NodeRefList & nodes)154 void add_points(const osmium::NodeRefList& nodes) { 155 osmium::Location last_location; 156 for (const osmium::NodeRef& node_ref : nodes) { 157 if (last_location != node_ref.location()) { 158 last_location = node_ref.location(); 159 m_impl.multipolygon_add_location(m_projection(last_location)); 160 } 161 } 162 } 163 164 TProjection m_projection; 165 TGeomImpl m_impl; 166 167 public: 168 169 GeometryFactory<TGeomImpl, TProjection>() : 170 m_projection(), 171 m_impl(m_projection.epsg()) { 172 } 173 174 /** 175 * Constructor for default initialized projection. 176 */ 177 template <typename... TArgs> GeometryFactory(TArgs &&...args)178 explicit GeometryFactory<TGeomImpl, TProjection>(TArgs&&... args) : 179 m_projection(), 180 m_impl(m_projection.epsg(), std::forward<TArgs>(args)...) { 181 } 182 183 /** 184 * Constructor for explicitly initialized projection. Note that the 185 * projection is moved into the GeometryFactory. 186 */ 187 template <typename... TArgs> GeometryFactory(TProjection && projection,TArgs &&...args)188 explicit GeometryFactory<TGeomImpl, TProjection>(TProjection&& projection, TArgs&&... args) : 189 m_projection(std::move(projection)), 190 m_impl(m_projection.epsg(), std::forward<TArgs>(args)...) { 191 } 192 193 using projection_type = TProjection; 194 195 using point_type = typename TGeomImpl::point_type; 196 using linestring_type = typename TGeomImpl::linestring_type; 197 using polygon_type = typename TGeomImpl::polygon_type; 198 using multipolygon_type = typename TGeomImpl::multipolygon_type; 199 using ring_type = typename TGeomImpl::ring_type; 200 epsg() const201 int epsg() const noexcept { 202 return m_projection.epsg(); 203 } 204 proj_string() const205 std::string proj_string() const { 206 return m_projection.proj_string(); 207 } 208 209 /* Point */ 210 create_point(const osmium::Location & location) const211 point_type create_point(const osmium::Location& location) const { 212 return m_impl.make_point(m_projection(location)); 213 } 214 create_point(const osmium::Node & node)215 point_type create_point(const osmium::Node& node) { 216 try { 217 return create_point(node.location()); 218 } catch (osmium::geometry_error& e) { 219 e.set_id("node", node.id()); 220 throw; 221 } 222 } 223 create_point(const osmium::NodeRef & node_ref)224 point_type create_point(const osmium::NodeRef& node_ref) { 225 try { 226 return create_point(node_ref.location()); 227 } catch (osmium::geometry_error& e) { 228 e.set_id("node", node_ref.ref()); 229 throw; 230 } 231 } 232 233 /* LineString */ 234 linestring_start()235 void linestring_start() { 236 m_impl.linestring_start(); 237 } 238 239 template <typename TIter> fill_linestring(TIter it,TIter end)240 size_t fill_linestring(TIter it, TIter end) { 241 size_t num_points = 0; 242 for (; it != end; ++it, ++num_points) { 243 m_impl.linestring_add_location(m_projection(it->location())); 244 } 245 return num_points; 246 } 247 248 template <typename TIter> fill_linestring_unique(TIter it,TIter end)249 size_t fill_linestring_unique(TIter it, TIter end) { 250 size_t num_points = 0; 251 osmium::Location last_location; 252 for (; it != end; ++it) { 253 if (last_location != it->location()) { 254 last_location = it->location(); 255 m_impl.linestring_add_location(m_projection(last_location)); 256 ++num_points; 257 } 258 } 259 return num_points; 260 } 261 linestring_finish(size_t num_points)262 linestring_type linestring_finish(size_t num_points) { 263 return m_impl.linestring_finish(num_points); 264 } 265 create_linestring(const osmium::WayNodeList & wnl,use_nodes un=use_nodes::unique,direction dir=direction::forward)266 linestring_type create_linestring(const osmium::WayNodeList& wnl, use_nodes un = use_nodes::unique, direction dir = direction::forward) { 267 linestring_start(); 268 size_t num_points = 0; 269 270 if (un == use_nodes::unique) { 271 switch (dir) { 272 case direction::forward: 273 num_points = fill_linestring_unique(wnl.cbegin(), wnl.cend()); 274 break; 275 case direction::backward: 276 num_points = fill_linestring_unique(wnl.crbegin(), wnl.crend()); 277 break; 278 } 279 } else { 280 switch (dir) { 281 case direction::forward: 282 num_points = fill_linestring(wnl.cbegin(), wnl.cend()); 283 break; 284 case direction::backward: 285 num_points = fill_linestring(wnl.crbegin(), wnl.crend()); 286 break; 287 } 288 } 289 290 if (num_points < 2) { 291 throw osmium::geometry_error{"need at least two points for linestring"}; 292 } 293 294 return linestring_finish(num_points); 295 } 296 create_linestring(const osmium::Way & way,use_nodes un=use_nodes::unique,direction dir=direction::forward)297 linestring_type create_linestring(const osmium::Way& way, use_nodes un = use_nodes::unique, direction dir = direction::forward) { 298 try { 299 return create_linestring(way.nodes(), un, dir); 300 } catch (osmium::geometry_error& e) { 301 e.set_id("way", way.id()); 302 throw; 303 } 304 } 305 306 /* Polygon */ 307 polygon_start()308 void polygon_start() { 309 m_impl.polygon_start(); 310 } 311 312 template <typename TIter> fill_polygon(TIter it,TIter end)313 size_t fill_polygon(TIter it, TIter end) { 314 size_t num_points = 0; 315 for (; it != end; ++it, ++num_points) { 316 m_impl.polygon_add_location(m_projection(it->location())); 317 } 318 return num_points; 319 } 320 321 template <typename TIter> fill_polygon_unique(TIter it,TIter end)322 size_t fill_polygon_unique(TIter it, TIter end) { 323 size_t num_points = 0; 324 osmium::Location last_location; 325 for (; it != end; ++it) { 326 if (last_location != it->location()) { 327 last_location = it->location(); 328 m_impl.polygon_add_location(m_projection(last_location)); 329 ++num_points; 330 } 331 } 332 return num_points; 333 } 334 polygon_finish(size_t num_points)335 polygon_type polygon_finish(size_t num_points) { 336 return m_impl.polygon_finish(num_points); 337 } 338 create_polygon(const osmium::WayNodeList & wnl,use_nodes un=use_nodes::unique,direction dir=direction::forward)339 polygon_type create_polygon(const osmium::WayNodeList& wnl, use_nodes un = use_nodes::unique, direction dir = direction::forward) { 340 polygon_start(); 341 size_t num_points = 0; 342 343 if (un == use_nodes::unique) { 344 switch (dir) { 345 case direction::forward: 346 num_points = fill_polygon_unique(wnl.cbegin(), wnl.cend()); 347 break; 348 case direction::backward: 349 num_points = fill_polygon_unique(wnl.crbegin(), wnl.crend()); 350 break; 351 } 352 } else { 353 switch (dir) { 354 case direction::forward: 355 num_points = fill_polygon(wnl.cbegin(), wnl.cend()); 356 break; 357 case direction::backward: 358 num_points = fill_polygon(wnl.crbegin(), wnl.crend()); 359 break; 360 } 361 } 362 363 if (num_points < 4) { 364 throw osmium::geometry_error{"need at least four points for polygon"}; 365 } 366 367 return polygon_finish(num_points); 368 } 369 create_polygon(const osmium::Way & way,use_nodes un=use_nodes::unique,direction dir=direction::forward)370 polygon_type create_polygon(const osmium::Way& way, use_nodes un = use_nodes::unique, direction dir = direction::forward) { 371 try { 372 return create_polygon(way.nodes(), un, dir); 373 } catch (osmium::geometry_error& e) { 374 e.set_id("way", way.id()); 375 throw; 376 } 377 } 378 379 /* MultiPolygon */ 380 create_multipolygon(const osmium::Area & area)381 multipolygon_type create_multipolygon(const osmium::Area& area) { 382 try { 383 size_t num_polygons = 0; 384 size_t num_rings = 0; 385 m_impl.multipolygon_start(); 386 387 for (const auto& item : area) { 388 if (item.type() == osmium::item_type::outer_ring) { 389 const auto& ring = static_cast<const osmium::OuterRing&>(item); 390 if (num_polygons > 0) { 391 m_impl.multipolygon_polygon_finish(); 392 } 393 m_impl.multipolygon_polygon_start(); 394 m_impl.multipolygon_outer_ring_start(); 395 add_points(ring); 396 m_impl.multipolygon_outer_ring_finish(); 397 ++num_rings; 398 ++num_polygons; 399 } else if (item.type() == osmium::item_type::inner_ring) { 400 const auto& ring = static_cast<const osmium::InnerRing&>(item); 401 m_impl.multipolygon_inner_ring_start(); 402 add_points(ring); 403 m_impl.multipolygon_inner_ring_finish(); 404 ++num_rings; 405 } 406 } 407 408 // if there are no rings, this area is invalid 409 if (num_rings == 0) { 410 throw osmium::geometry_error{"invalid area"}; 411 } 412 413 m_impl.multipolygon_polygon_finish(); 414 return m_impl.multipolygon_finish(); 415 } catch (osmium::geometry_error& e) { 416 e.set_id("area", area.id()); 417 throw; 418 } 419 } 420 421 }; // class GeometryFactory 422 423 } // namespace geom 424 425 } // namespace osmium 426 427 #endif // OSMIUM_GEOM_FACTORY_HPP 428