1 #ifndef OSMIUM_AREA_ASSEMBLER_LEGACY_HPP
2 #define OSMIUM_AREA_ASSEMBLER_LEGACY_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/area/assembler_config.hpp>
37 #include <osmium/area/detail/basic_assembler_with_tags.hpp>
38 #include <osmium/area/detail/proto_ring.hpp>
39 #include <osmium/area/detail/segment_list.hpp>
40 #include <osmium/area/problem_reporter.hpp>
41 #include <osmium/area/stats.hpp>
42 #include <osmium/builder/osm_object_builder.hpp>
43 #include <osmium/memory/buffer.hpp>
44 #include <osmium/memory/collection.hpp>
45 #include <osmium/osm/area.hpp>
46 #include <osmium/osm/item_type.hpp>
47 #include <osmium/osm/node_ref.hpp>
48 #include <osmium/osm/relation.hpp>
49 #include <osmium/osm/tag.hpp>
50 #include <osmium/osm/way.hpp>
51 #include <osmium/tags/filter.hpp>
52 
53 #include <algorithm>
54 #include <cassert>
55 #include <cstring>
56 #include <functional>
57 #include <iostream>
58 #include <iterator>
59 #include <map>
60 #include <set>
61 #include <string>
62 #include <utility>
63 #include <vector>
64 
65 namespace osmium {
66 
67     namespace area {
68 
69         /**
70          * Assembles area objects from closed ways or multipolygon relations
71          * and their members.
72          */
73         class AssemblerLegacy : public detail::BasicAssemblerWithTags {
74 
add_common_tags(osmium::builder::TagListBuilder & tl_builder,std::set<const osmium::Way * > & ways) const75             void add_common_tags(osmium::builder::TagListBuilder& tl_builder, std::set<const osmium::Way*>& ways) const {
76                 std::map<std::string, std::size_t> counter;
77                 for (const osmium::Way* way : ways) {
78                     for (const auto& tag : way->tags()) {
79                         std::string kv{tag.key()};
80                         kv.append(1, '\0');
81                         kv.append(tag.value());
82                         ++counter[kv];
83                     }
84                 }
85 
86                 const std::size_t num_ways = ways.size();
87                 for (const auto& t_c : counter) {
88                     if (debug()) {
89                         std::cerr << "        tag " << t_c.first << " is used " << t_c.second << " times in " << num_ways << " ways\n";
90                     }
91                     if (t_c.second == num_ways) {
92                         const std::size_t len = std::strlen(t_c.first.c_str());
93                         tl_builder.add_tag(t_c.first.c_str(), t_c.first.c_str() + len + 1);
94                     }
95                 }
96             }
97 
98             struct MPFilter : public osmium::tags::KeyFilter {
99 
MPFilterosmium::area::AssemblerLegacy::MPFilter100                 MPFilter() : osmium::tags::KeyFilter(true) {
101                     add(false, "type");
102                     add(false, "created_by");
103                     add(false, "source");
104                     add(false, "note");
105                     add(false, "test:id");
106                     add(false, "test:section");
107                 }
108 
109             }; // struct MPFilter
110 
filter()111             static const MPFilter& filter() noexcept {
112                 static const MPFilter filter;
113                 return filter;
114             }
115 
add_tags_to_area(osmium::builder::AreaBuilder & builder,const osmium::Relation & relation)116             void add_tags_to_area(osmium::builder::AreaBuilder& builder, const osmium::Relation& relation) {
117                 const auto count = std::count_if(relation.tags().cbegin(), relation.tags().cend(), std::cref(filter()));
118 
119                 if (debug()) {
120                     std::cerr << "  found " << count << " tags on relation (without ignored ones)\n";
121                 }
122 
123                 if (count > 0) {
124                     if (debug()) {
125                         std::cerr << "    use tags from relation\n";
126                     }
127 
128                     if (config().keep_type_tag) {
129                         builder.add_item(relation.tags());
130                     } else {
131                         copy_tags_without_type(builder, relation.tags());
132                     }
133                 } else {
134                     ++stats().no_tags_on_relation;
135                     if (debug()) {
136                         std::cerr << "    use tags from outer ways\n";
137                     }
138                     std::set<const osmium::Way*> ways;
139                     for (const auto& ring : rings()) {
140                         if (ring.is_outer()) {
141                             ring.get_ways(ways);
142                         }
143                     }
144                     if (ways.size() == 1) {
145                         if (debug()) {
146                             std::cerr << "      only one outer way\n";
147                         }
148                         builder.add_item((*ways.cbegin())->tags());
149                     } else {
150                         if (debug()) {
151                             std::cerr << "      multiple outer ways, get common tags\n";
152                         }
153                         osmium::builder::TagListBuilder tl_builder{builder};
154                         add_common_tags(tl_builder, ways);
155                     }
156                 }
157             }
158 
create_area(osmium::memory::Buffer & out_buffer,const osmium::Way & way)159             bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Way& way) {
160                 osmium::builder::AreaBuilder builder{out_buffer};
161                 builder.initialize_from_object(way);
162 
163                 const bool area_okay = create_rings();
164                 if (area_okay || config().create_empty_areas) {
165                     builder.add_item(way.tags());
166                 }
167                 if (area_okay) {
168                     add_rings_to_area(builder);
169                 }
170 
171                 if (report_ways()) {
172                     config().problem_reporter->report_way(way);
173                 }
174 
175                 return area_okay || config().create_empty_areas;
176             }
177 
create_area(osmium::memory::Buffer & out_buffer,const osmium::Relation & relation,const std::vector<const osmium::Way * > & members)178             bool create_area(osmium::memory::Buffer& out_buffer, const osmium::Relation& relation, const std::vector<const osmium::Way*>& members) {
179                 set_num_members(members.size());
180                 osmium::builder::AreaBuilder builder{out_buffer};
181                 builder.initialize_from_object(relation);
182 
183                 const bool area_okay = create_rings();
184                 if (area_okay || config().create_empty_areas) {
185                     add_tags_to_area(builder, relation);
186                 }
187                 if (area_okay) {
188                     add_rings_to_area(builder);
189                 }
190 
191                 if (report_ways()) {
192                     for (const osmium::Way* way : members) {
193                         config().problem_reporter->report_way(*way);
194                     }
195                 }
196 
197                 return area_okay || config().create_empty_areas;
198             }
199 
200         public:
201 
AssemblerLegacy(const config_type & config)202             explicit AssemblerLegacy(const config_type& config) :
203                 detail::BasicAssemblerWithTags(config) {
204             }
205 
206             /**
207              * Assemble an area from the given way.
208              * The resulting area is put into the out_buffer.
209              *
210              * @returns false if there was some kind of error building the
211              *          area, true otherwise.
212              */
operator ()(const osmium::Way & way,osmium::memory::Buffer & out_buffer)213             bool operator()(const osmium::Way& way, osmium::memory::Buffer& out_buffer) {
214                 if (!config().create_way_polygons) {
215                     return true;
216                 }
217 
218                 if (way.tags().has_tag("area", "no")) {
219                     return true;
220                 }
221 
222                 if (config().problem_reporter) {
223                     config().problem_reporter->set_object(osmium::item_type::way, way.id());
224                     config().problem_reporter->set_nodes(way.nodes().size());
225                 }
226 
227                 // Ignore (but count) ways without segments.
228                 if (way.nodes().size() < 2) {
229                     ++stats().short_ways;
230                     return false;
231                 }
232 
233                 if (!way.ends_have_same_id()) {
234                     ++stats().duplicate_nodes;
235                     if (config().problem_reporter) {
236                         config().problem_reporter->report_duplicate_node(way.nodes().front().ref(), way.nodes().back().ref(), way.nodes().front().location());
237                     }
238                 }
239 
240                 ++stats().from_ways;
241                 stats().invalid_locations = segment_list().extract_segments_from_way(config().problem_reporter,
242                                                                                      stats().duplicate_nodes,
243                                                                                      way);
244                 if (!config().ignore_invalid_locations && stats().invalid_locations > 0) {
245                     return false;
246                 }
247 
248                 if (config().debug_level > 0) {
249                     std::cerr << "\nAssembling way " << way.id() << " containing " << segment_list().size() << " nodes\n";
250                 }
251 
252                 // Now create the Area object and add the attributes and tags
253                 // from the way.
254                 const bool okay = create_area(out_buffer, way);
255                 if (okay) {
256                     out_buffer.commit();
257                 } else {
258                     out_buffer.rollback();
259                 }
260 
261                 if (debug()) {
262                     std::cerr << "Done: " << stats() << "\n";
263                 }
264 
265                 return okay;
266             }
267 
268             /**
269              * Assemble an area from the given relation and its members.
270              * The resulting area is put into the out_buffer.
271              *
272              * @returns false if there was some kind of error building the
273              *          area(s), true otherwise.
274              */
operator ()(const osmium::Relation & relation,const std::vector<const osmium::Way * > & members,osmium::memory::Buffer & out_buffer)275             bool operator()(const osmium::Relation& relation, const std::vector<const osmium::Way*>& members, osmium::memory::Buffer& out_buffer) {
276                 assert(relation.members().size() >= members.size());
277 
278                 if (config().problem_reporter) {
279                     config().problem_reporter->set_object(osmium::item_type::relation, relation.id());
280                 }
281 
282                 if (relation.members().empty()) {
283                     ++stats().no_way_in_mp_relation;
284                     return false;
285                 }
286 
287                 ++stats().from_relations;
288                 stats().invalid_locations = segment_list().extract_segments_from_ways(config().problem_reporter,
289                                                                                       stats().duplicate_nodes,
290                                                                                       stats().duplicate_ways,
291                                                                                       relation,
292                                                                                       members);
293                 if (!config().ignore_invalid_locations && stats().invalid_locations > 0) {
294                     return false;
295                 }
296                 stats().member_ways = members.size();
297 
298                 if (stats().member_ways == 1) {
299                     ++stats().single_way_in_mp_relation;
300                 }
301 
302                 if (config().debug_level > 0) {
303                     std::cerr << "\nAssembling relation " << relation.id() << " containing " << members.size() << " way members with " << segment_list().size() << " nodes\n";
304                 }
305 
306                 const std::size_t area_offset = out_buffer.committed();
307 
308                 // Now create the Area object and add the attributes and tags
309                 // from the relation.
310                 bool okay = create_area(out_buffer, relation, members);
311                 if (okay) {
312                     if ((config().create_new_style_polygons && stats().no_tags_on_relation == 0) ||
313                         (config().create_old_style_polygons && stats().no_tags_on_relation != 0)) {
314                         out_buffer.commit();
315                     } else {
316                         out_buffer.rollback();
317                     }
318                 } else {
319                     out_buffer.rollback();
320                 }
321 
322                 const osmium::TagList& area_tags = out_buffer.get<osmium::Area>(area_offset).tags(); // tags of the area we just built
323 
324                 // Find all closed ways that are inner rings and check their
325                 // tags. If they are not the same as the tags of the area we
326                 // just built, add them to a list and later build areas for
327                 // them, too.
328                 std::vector<const osmium::Way*> ways_that_should_be_areas;
329                 if (stats().wrong_role == 0) {
330                     detail::for_each_member(relation, members, [this, &ways_that_should_be_areas, &area_tags](const osmium::RelationMember& member, const osmium::Way& way) {
331                         if (!std::strcmp(member.role(), "inner")) {
332                             if (!way.nodes().empty() && way.is_closed() && !way.tags().empty()) {
333                                 const auto d = std::count_if(way.tags().cbegin(), way.tags().cend(), std::cref(filter()));
334                                 if (d > 0) {
335                                     osmium::tags::KeyFilter::iterator way_fi_begin(std::cref(filter()), way.tags().cbegin(), way.tags().cend());
336                                     osmium::tags::KeyFilter::iterator way_fi_end(std::cref(filter()), way.tags().cend(), way.tags().cend());
337                                     osmium::tags::KeyFilter::iterator area_fi_begin(std::cref(filter()), area_tags.cbegin(), area_tags.cend());
338                                     osmium::tags::KeyFilter::iterator area_fi_end(std::cref(filter()), area_tags.cend(), area_tags.cend());
339 #ifdef __cpp_lib_robust_nonmodifying_seq_ops
340                                     if (!std::equal(way_fi_begin, way_fi_end, area_fi_begin, area_fi_end)) {
341 #else
342                                     if (d != std::distance(area_fi_begin, area_fi_end) || !std::equal(way_fi_begin, way_fi_end, area_fi_begin)) {
343 #endif
344                                         ways_that_should_be_areas.push_back(&way);
345                                     } else {
346                                         ++stats().inner_with_same_tags;
347                                         if (config().problem_reporter) {
348                                             config().problem_reporter->report_inner_with_same_tags(way);
349                                         }
350                                     }
351                                 }
352                             }
353                         }
354                     });
355                 }
356 
357                 if (debug()) {
358                     std::cerr << "Done: " << stats() << "\n";
359                 }
360 
361                 // Now build areas for all ways found in the last step.
362                 for (const osmium::Way* way : ways_that_should_be_areas) {
363                     AssemblerLegacy assembler{config()};
364                     if (!assembler(*way, out_buffer)) {
365                         okay = false;
366                     }
367                     stats() += assembler.stats();
368                 }
369 
370                 return okay;
371             }
372 
373         }; // class AssemblerLegacy
374 
375     } // namespace area
376 
377 } // namespace osmium
378 
379 #endif // OSMIUM_AREA_ASSEMBLER_LEGACY_HPP
380