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