]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // Boost.Geometry (aka GGL, Generic Geometry Library) |
2 | // Unit Test Helper | |
3 | ||
4 | // Copyright (c) 2010-2015 Barend Gehrels, Amsterdam, the Netherlands. | |
5 | // Use, modification and distribution is subject to the Boost Software License, | |
6 | // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at | |
7 | // http://www.boost.org/LICENSE_1_0.txt) | |
8 | ||
9 | ||
10 | #ifndef BOOST_GEOMETRY_TEST_BUFFER_SVG_HPP | |
11 | #define BOOST_GEOMETRY_TEST_BUFFER_SVG_HPP | |
12 | ||
13 | #include <fstream> | |
14 | #include <sstream> | |
15 | ||
16 | // Uncomment next lines if you want to have a zoomed view | |
17 | //#define BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX | |
18 | ||
19 | // If possible define box before including this unit with the right view | |
20 | #ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX | |
21 | # ifndef BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX | |
22 | # define BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX "BOX(0 0,100 100)" | |
23 | # endif | |
24 | #endif | |
25 | ||
26 | #include <boost/foreach.hpp> | |
27 | #include <boost/geometry/io/svg/svg_mapper.hpp> | |
b32b8144 | 28 | #include <boost/geometry/algorithms/intersection.hpp> |
7c673cae FG |
29 | |
30 | ||
31 | inline char piece_type_char(bg::strategy::buffer::piece_type const& type) | |
32 | { | |
33 | using namespace bg::strategy::buffer; | |
34 | switch(type) | |
35 | { | |
36 | case buffered_segment : return 's'; | |
37 | case buffered_join : return 'j'; | |
38 | case buffered_round_end : return 'r'; | |
39 | case buffered_flat_end : return 'f'; | |
40 | case buffered_point : return 'p'; | |
41 | case buffered_concave : return 'c'; | |
42 | default : return '?'; | |
43 | } | |
44 | } | |
45 | ||
46 | template <typename SvgMapper, typename Box> | |
47 | class svg_visitor | |
48 | { | |
49 | public : | |
50 | svg_visitor(SvgMapper& mapper) | |
51 | : m_mapper(mapper) | |
52 | , m_zoom(false) | |
53 | { | |
54 | bg::assign_inverse(m_alternate_box); | |
55 | } | |
56 | ||
57 | void set_alternate_box(Box const& box) | |
58 | { | |
59 | m_alternate_box = box; | |
60 | m_zoom = true; | |
61 | } | |
62 | ||
63 | template <typename PieceCollection> | |
64 | inline void apply(PieceCollection const& collection, int phase) | |
65 | { | |
66 | // Comment next return if you want to see pieces, turns, etc. | |
67 | return; | |
68 | ||
69 | if(phase == 0) | |
70 | { | |
71 | map_pieces(collection.m_pieces, collection.offsetted_rings, true, true); | |
72 | } | |
73 | if (phase == 1) | |
74 | { | |
75 | map_turns(collection.m_turns, true, false); | |
76 | } | |
77 | if (phase == 2 && ! m_zoom) | |
78 | { | |
79 | // map_traversed_rings(collection.traversed_rings); | |
80 | // map_offsetted_rings(collection.offsetted_rings); | |
81 | } | |
82 | } | |
83 | ||
84 | private : | |
85 | class si | |
86 | { | |
87 | private : | |
88 | bg::segment_identifier m_id; | |
89 | ||
90 | public : | |
91 | inline si(bg::segment_identifier const& id) | |
92 | : m_id(id) | |
93 | {} | |
94 | ||
95 | template <typename Char, typename Traits> | |
96 | inline friend std::basic_ostream<Char, Traits>& operator<<( | |
97 | std::basic_ostream<Char, Traits>& os, | |
98 | si const& s) | |
99 | { | |
100 | os << s.m_id.multi_index << "." << s.m_id.segment_index; | |
101 | return os; | |
102 | } | |
103 | }; | |
104 | ||
105 | template <typename Turns> | |
106 | inline void map_turns(Turns const& turns, bool label_good_turns, bool label_wrong_turns) | |
107 | { | |
108 | namespace bgdb = boost::geometry::detail::buffer; | |
109 | typedef typename boost::range_value<Turns const>::type turn_type; | |
110 | typedef typename turn_type::point_type point_type; | |
111 | typedef typename turn_type::robust_point_type robust_point_type; | |
112 | ||
113 | std::map<robust_point_type, int, bg::less<robust_point_type> > offsets; | |
114 | ||
115 | for (typename boost::range_iterator<Turns const>::type it = | |
116 | boost::begin(turns); it != boost::end(turns); ++it) | |
117 | { | |
118 | if (m_zoom && bg::disjoint(it->point, m_alternate_box)) | |
119 | { | |
120 | continue; | |
121 | } | |
122 | ||
123 | bool is_good = true; | |
124 | char color = 'g'; | |
125 | std::string fill = "fill:rgb(0,255,0);"; | |
126 | switch(it->location) | |
127 | { | |
128 | case bgdb::inside_buffer : | |
129 | fill = "fill:rgb(255,0,0);"; | |
130 | color = 'r'; | |
131 | is_good = false; | |
132 | break; | |
133 | case bgdb::location_discard : | |
134 | fill = "fill:rgb(0,0,255);"; | |
135 | color = 'b'; | |
136 | is_good = false; | |
137 | break; | |
138 | default: | |
139 | ; // to avoid "enumeration value not handled" warning | |
140 | } | |
141 | if (it->blocked()) | |
142 | { | |
143 | fill = "fill:rgb(128,128,128);"; | |
144 | color = '-'; | |
145 | is_good = false; | |
146 | } | |
147 | ||
148 | fill += "fill-opacity:0.7;"; | |
149 | ||
150 | m_mapper.map(it->point, fill, 4); | |
151 | ||
152 | if ((label_good_turns && is_good) || (label_wrong_turns && ! is_good)) | |
153 | { | |
154 | std::ostringstream out; | |
155 | out << it->turn_index; | |
156 | if (it->cluster_id >= 0) | |
157 | { | |
158 | out << " (" << it->cluster_id << ")"; | |
159 | } | |
160 | out | |
161 | << " " << it->operations[0].piece_index << "/" << it->operations[1].piece_index | |
162 | << " " << si(it->operations[0].seg_id) << "/" << si(it->operations[1].seg_id) | |
163 | ||
164 | // If you want to see travel information | |
165 | << std::endl | |
b32b8144 FG |
166 | << " nxt " << it->operations[0].enriched.get_next_turn_index() |
167 | << "/" << it->operations[1].enriched.get_next_turn_index() | |
7c673cae FG |
168 | //<< " frac " << it->operations[0].fraction |
169 | ||
170 | // If you want to see (robust)point coordinates (e.g. to find duplicates) | |
171 | << std::endl << std::setprecision(16) << bg::wkt(it->point) | |
172 | << std::endl << bg::wkt(it->robust_point) | |
173 | ||
174 | << std::endl; | |
175 | out << " " << bg::method_char(it->method) | |
176 | << ":" << bg::operation_char(it->operations[0].operation) | |
177 | << "/" << bg::operation_char(it->operations[1].operation); | |
178 | out << " " | |
179 | << (it->count_on_offsetted > 0 ? "b" : "") // b: offsetted border | |
180 | #if ! defined(BOOST_GEOMETRY_BUFFER_USE_SIDE_OF_INTERSECTION) | |
181 | << (it->count_within_near_offsetted > 0 ? "n" : "") | |
182 | #endif | |
183 | << (it->count_within > 0 ? "w" : "") | |
184 | << (it->count_on_helper > 0 ? "h" : "") | |
185 | << (it->count_on_multi > 0 ? "m" : "") | |
186 | ; | |
187 | ||
188 | offsets[it->get_robust_point()] += 10; | |
189 | int offset = offsets[it->get_robust_point()]; | |
190 | ||
191 | m_mapper.text(it->point, out.str(), "fill:rgb(0,0,0);font-family='Arial';font-size:9px;", 5, offset); | |
192 | ||
193 | offsets[it->get_robust_point()] += 25; | |
194 | } | |
195 | } | |
196 | } | |
197 | ||
198 | template <typename Pieces, typename OffsettedRings> | |
199 | inline void map_pieces(Pieces const& pieces, | |
200 | OffsettedRings const& offsetted_rings, | |
201 | bool do_pieces, bool do_indices) | |
202 | { | |
203 | typedef typename boost::range_value<Pieces const>::type piece_type; | |
204 | typedef typename boost::range_value<OffsettedRings const>::type ring_type; | |
205 | ||
206 | for(typename boost::range_iterator<Pieces const>::type it = boost::begin(pieces); | |
207 | it != boost::end(pieces); | |
208 | ++it) | |
209 | { | |
210 | const piece_type& piece = *it; | |
211 | bg::segment_identifier seg_id = piece.first_seg_id; | |
212 | if (seg_id.segment_index < 0) | |
213 | { | |
214 | continue; | |
215 | } | |
216 | ||
217 | ring_type corner; | |
218 | ||
219 | ||
220 | ring_type const& ring = offsetted_rings[seg_id.multi_index]; | |
221 | ||
222 | std::copy(boost::begin(ring) + seg_id.segment_index, | |
223 | boost::begin(ring) + piece.last_segment_index, | |
224 | std::back_inserter(corner)); | |
225 | std::copy(boost::begin(piece.helper_points), | |
226 | boost::end(piece.helper_points), | |
227 | std::back_inserter(corner)); | |
228 | ||
229 | if (corner.empty()) | |
230 | { | |
231 | continue; | |
232 | } | |
233 | if (m_zoom && bg::disjoint(corner, m_alternate_box)) | |
234 | { | |
235 | continue; | |
236 | } | |
237 | ||
238 | if (m_zoom && do_pieces) | |
239 | { | |
240 | try | |
241 | { | |
242 | std::string style = "opacity:0.3;stroke:rgb(0,0,0);stroke-width:1;"; | |
243 | typedef typename bg::point_type<Box>::type point_type; | |
244 | bg::model::multi_polygon<bg::model::polygon<point_type> > clipped; | |
245 | bg::intersection(ring, m_alternate_box, clipped); | |
246 | m_mapper.map(clipped, | |
247 | piece.type == bg::strategy::buffer::buffered_segment | |
248 | ? style + "fill:rgb(255,128,0);" | |
249 | : style + "fill:rgb(255,0,0);"); | |
250 | } | |
251 | catch (...) | |
252 | { | |
253 | std::cerr << "Error for piece " << piece.index << std::endl; | |
254 | } | |
255 | } | |
256 | else if (do_pieces) | |
257 | { | |
258 | std::string style = "opacity:0.3;stroke:rgb(0,0,0);stroke-width:1;"; | |
259 | m_mapper.map(corner, | |
260 | piece.type == bg::strategy::buffer::buffered_segment | |
261 | ? style + "fill:rgb(255,128,0);" | |
262 | : style + "fill:rgb(255,0,0);"); | |
263 | } | |
264 | ||
265 | if (do_indices) | |
266 | { | |
267 | // Label starting piece_index / segment_index | |
268 | typedef typename bg::point_type<ring_type>::type point_type; | |
269 | ||
270 | std::ostringstream out; | |
b32b8144 FG |
271 | out << piece.index |
272 | << (piece.is_flat_start ? " FS" : "") | |
273 | << (piece.is_flat_end ? " FE" : "") | |
274 | << " (" << piece_type_char(piece.type) << ") " | |
275 | << piece.first_seg_id.segment_index | |
276 | << ".." << piece.last_segment_index - 1; | |
7c673cae FG |
277 | point_type label_point = bg::return_centroid<point_type>(corner); |
278 | ||
279 | if ((piece.type == bg::strategy::buffer::buffered_concave | |
280 | || piece.type == bg::strategy::buffer::buffered_flat_end) | |
281 | && corner.size() >= 2u) | |
282 | { | |
283 | bg::set<0>(label_point, (bg::get<0>(corner[0]) + bg::get<0>(corner[1])) / 2.0); | |
284 | bg::set<1>(label_point, (bg::get<1>(corner[0]) + bg::get<1>(corner[1])) / 2.0); | |
285 | } | |
286 | m_mapper.text(label_point, out.str(), "fill:rgb(255,0,0);font-family='Arial';font-size:10px;", 5, 5); | |
287 | } | |
288 | } | |
289 | } | |
290 | ||
291 | template <typename TraversedRings> | |
292 | inline void map_traversed_rings(TraversedRings const& traversed_rings) | |
293 | { | |
294 | for(typename boost::range_iterator<TraversedRings const>::type it | |
295 | = boost::begin(traversed_rings); it != boost::end(traversed_rings); ++it) | |
296 | { | |
297 | m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(0,255,0);stroke-width:2"); | |
298 | } | |
299 | } | |
300 | ||
301 | template <typename OffsettedRings> | |
302 | inline void map_offsetted_rings(OffsettedRings const& offsetted_rings) | |
303 | { | |
304 | for(typename boost::range_iterator<OffsettedRings const>::type it | |
305 | = boost::begin(offsetted_rings); it != boost::end(offsetted_rings); ++it) | |
306 | { | |
307 | if (it->discarded()) | |
308 | { | |
309 | m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(255,0,0);stroke-width:2"); | |
310 | } | |
311 | else | |
312 | { | |
313 | m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(0,0,255);stroke-width:2"); | |
314 | } | |
315 | } | |
316 | } | |
317 | ||
318 | ||
319 | SvgMapper& m_mapper; | |
320 | Box m_alternate_box; | |
321 | bool m_zoom; | |
322 | ||
323 | }; | |
324 | ||
325 | template <typename Point> | |
326 | class buffer_svg_mapper | |
327 | { | |
328 | public : | |
329 | ||
330 | buffer_svg_mapper(std::string const& casename) | |
331 | : m_casename(casename) | |
332 | , m_zoom(false) | |
333 | { | |
334 | bg::assign_inverse(m_alternate_box); | |
335 | } | |
336 | ||
337 | template <typename Mapper, typename Visitor, typename Envelope> | |
338 | void prepare(Mapper& mapper, Visitor& visitor, Envelope const& envelope, double box_buffer_distance) | |
339 | { | |
340 | #ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX | |
341 | // Create a zoomed-in view | |
342 | bg::model::box<Point> alternate_box; | |
343 | bg::read_wkt(BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX, alternate_box); | |
344 | mapper.add(alternate_box); | |
345 | ||
346 | // Take care non-visible elements are skipped | |
347 | visitor.set_alternate_box(alternate_box); | |
348 | set_alternate_box(alternate_box); | |
349 | #else | |
350 | bg::model::box<Point> box = envelope; | |
351 | bg::buffer(box, box, box_buffer_distance); | |
352 | mapper.add(box); | |
353 | #endif | |
354 | ||
355 | boost::ignore_unused(visitor); | |
356 | } | |
357 | ||
358 | void set_alternate_box(bg::model::box<Point> const& box) | |
359 | { | |
360 | m_alternate_box = box; | |
361 | m_zoom = true; | |
362 | } | |
363 | ||
364 | template <typename Mapper, typename Geometry, typename GeometryBuffer> | |
365 | void map_input_output(Mapper& mapper, Geometry const& geometry, | |
366 | GeometryBuffer const& buffered, bool negative) | |
367 | { | |
368 | bool const areal = boost::is_same | |
369 | < | |
370 | typename bg::tag_cast | |
371 | < | |
372 | typename bg::tag<Geometry>::type, | |
373 | bg::areal_tag | |
374 | >::type, bg::areal_tag | |
375 | >::type::value; | |
376 | ||
377 | if (m_zoom) | |
378 | { | |
379 | map_io_zoomed(mapper, geometry, buffered, negative, areal); | |
380 | } | |
381 | else | |
382 | { | |
383 | map_io(mapper, geometry, buffered, negative, areal); | |
384 | } | |
385 | } | |
386 | ||
b32b8144 FG |
387 | template <typename Mapper, typename Geometry, typename Strategy, typename RescalePolicy> |
388 | void map_self_ips(Mapper& mapper, Geometry const& geometry, Strategy const& strategy, RescalePolicy const& rescale_policy) | |
7c673cae FG |
389 | { |
390 | typedef bg::detail::overlay::turn_info | |
391 | < | |
392 | Point, | |
393 | typename bg::segment_ratio_type<Point, RescalePolicy>::type | |
394 | > turn_info; | |
395 | ||
396 | std::vector<turn_info> turns; | |
397 | ||
398 | bg::detail::self_get_turn_points::no_interrupt_policy policy; | |
399 | bg::self_turns | |
400 | < | |
401 | bg::detail::overlay::assign_null_policy | |
b32b8144 | 402 | >(geometry, strategy, rescale_policy, turns, policy); |
7c673cae FG |
403 | |
404 | BOOST_FOREACH(turn_info const& turn, turns) | |
405 | { | |
406 | mapper.map(turn.point, "fill:rgb(255,128,0);stroke:rgb(0,0,100);stroke-width:1", 3); | |
407 | } | |
408 | } | |
409 | ||
410 | private : | |
411 | ||
412 | template <typename Mapper, typename Geometry, typename GeometryBuffer> | |
413 | void map_io(Mapper& mapper, Geometry const& geometry, | |
414 | GeometryBuffer const& buffered, bool negative, bool areal) | |
415 | { | |
416 | // Map input geometry in green | |
417 | if (areal) | |
418 | { | |
419 | mapper.map(geometry, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2"); | |
420 | } | |
421 | else | |
422 | { | |
423 | // TODO: clip input points/linestring | |
424 | mapper.map(geometry, "opacity:0.5;stroke:rgb(0,128,0);stroke-width:10"); | |
425 | } | |
426 | ||
427 | { | |
428 | // Map buffer in yellow (inflate) and with orange-dots (deflate) | |
429 | std::string style = negative | |
430 | ? "opacity:0.4;fill:rgb(255,255,192);stroke:rgb(255,128,0);stroke-width:3" | |
431 | : "opacity:0.4;fill:rgb(255,255,128);stroke:rgb(0,0,0);stroke-width:3"; | |
432 | ||
433 | mapper.map(buffered, style); | |
434 | } | |
435 | } | |
436 | ||
437 | template <typename Mapper, typename Geometry, typename GeometryBuffer> | |
438 | void map_io_zoomed(Mapper& mapper, Geometry const& geometry, | |
439 | GeometryBuffer const& buffered, bool negative, bool areal) | |
440 | { | |
441 | // Map input geometry in green | |
442 | if (areal) | |
443 | { | |
444 | // Assuming input is areal | |
445 | GeometryBuffer clipped; | |
446 | // TODO: the next line does NOT compile for multi-point, TODO: implement that line | |
447 | // bg::intersection(geometry, m_alternate_box, clipped); | |
448 | mapper.map(clipped, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2"); | |
449 | } | |
450 | else | |
451 | { | |
452 | // TODO: clip input (multi)point/linestring | |
453 | mapper.map(geometry, "opacity:0.5;stroke:rgb(0,128,0);stroke-width:10"); | |
454 | } | |
455 | ||
456 | { | |
457 | // Map buffer in yellow (inflate) and with orange-dots (deflate) | |
458 | std::string style = negative | |
459 | ? "opacity:0.4;fill:rgb(255,255,192);stroke:rgb(255,128,0);stroke-width:3" | |
460 | : "opacity:0.4;fill:rgb(255,255,128);stroke:rgb(0,0,0);stroke-width:3"; | |
461 | ||
462 | try | |
463 | { | |
464 | // Clip output multi-polygon with box | |
465 | GeometryBuffer clipped; | |
466 | bg::intersection(buffered, m_alternate_box, clipped); | |
467 | mapper.map(clipped, style); | |
468 | } | |
469 | catch (...) | |
470 | { | |
471 | std::cout << "Error for buffered output " << m_casename << std::endl; | |
472 | } | |
473 | } | |
474 | } | |
475 | ||
476 | bg::model::box<Point> m_alternate_box; | |
477 | bool m_zoom; | |
478 | std::string m_casename; | |
479 | }; | |
480 | ||
481 | ||
482 | #endif |