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>
28 #include <boost/geometry/algorithms/intersection.hpp>
29
30
piece_type_char(bg::strategy::buffer::piece_type const & type)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 :
svg_visitor(SvgMapper & mapper)50 svg_visitor(SvgMapper& mapper)
51 : m_mapper(mapper)
52 , m_zoom(false)
53 {
54 bg::assign_inverse(m_alternate_box);
55 }
56
set_alternate_box(Box const & box)57 void set_alternate_box(Box const& box)
58 {
59 m_alternate_box = box;
60 m_zoom = true;
61 }
62
63 template <typename PieceCollection>
apply(PieceCollection const & collection,int phase)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 :
si(bg::segment_identifier const & id)91 inline si(bg::segment_identifier const& id)
92 : m_id(id)
93 {}
94
95 template <typename Char, typename Traits>
operator <<(std::basic_ostream<Char,Traits> & os,si const & s)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>
map_turns(Turns const & turns,bool label_good_turns,bool label_wrong_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
112 std::map<point_type, int, bg::less<point_type> > offsets;
113
114 for (typename boost::range_iterator<Turns const>::type it =
115 boost::begin(turns); it != boost::end(turns); ++it)
116 {
117 if (m_zoom && bg::disjoint(it->point, m_alternate_box))
118 {
119 continue;
120 }
121
122 bool is_good = true;
123 char color = 'g';
124 std::string fill = "fill:rgb(0,255,0);";
125 if (! it->is_turn_traversable)
126 {
127 fill = "fill:rgb(255,0,0);";
128 color = 'r';
129 is_good = false;
130 }
131 if (it->blocked())
132 {
133 fill = "fill:rgb(128,128,128);";
134 color = '-';
135 is_good = false;
136 }
137
138 fill += "fill-opacity:0.7;";
139
140 m_mapper.map(it->point, fill, 4);
141
142 if ((label_good_turns && is_good) || (label_wrong_turns && ! is_good))
143 {
144 std::ostringstream out;
145 out << it->turn_index;
146 if (it->cluster_id >= 0)
147 {
148 out << " (" << it->cluster_id << ")";
149 }
150 out
151 << " " << it->operations[0].piece_index << "/" << it->operations[1].piece_index
152 << " " << si(it->operations[0].seg_id) << "/" << si(it->operations[1].seg_id)
153
154 // If you want to see travel information
155 << std::endl
156 << " nxt " << it->operations[0].enriched.get_next_turn_index()
157 << "/" << it->operations[1].enriched.get_next_turn_index()
158 //<< " frac " << it->operations[0].fraction
159
160 // If you want to see point coordinates (e.g. to find duplicates)
161 << std::endl << std::setprecision(16) << bg::wkt(it->point)
162
163 << std::endl;
164 out << " " << bg::method_char(it->method)
165 << ":" << bg::operation_char(it->operations[0].operation)
166 << "/" << bg::operation_char(it->operations[1].operation);
167 out << " "
168 << (it->is_turn_traversable ? "" : "w")
169 ;
170
171 offsets[it->point] += 10;
172 int offset = offsets[it->point];
173
174 m_mapper.text(it->point, out.str(), "fill:rgb(0,0,0);font-family='Arial';font-size:9px;", 5, offset);
175
176 offsets[it->point] += 25;
177 }
178 }
179 }
180
181 template <typename Pieces, typename OffsettedRings>
map_pieces(Pieces const & pieces,OffsettedRings const & offsetted_rings,bool do_pieces,bool do_indices)182 inline void map_pieces(Pieces const& pieces,
183 OffsettedRings const& offsetted_rings,
184 bool do_pieces, bool do_indices)
185 {
186 typedef typename boost::range_value<Pieces const>::type piece_type;
187 typedef typename boost::range_value<OffsettedRings const>::type ring_type;
188 typedef typename bg::point_type<ring_type>::type point_type;
189
190 for(typename boost::range_iterator<Pieces const>::type it = boost::begin(pieces);
191 it != boost::end(pieces);
192 ++it)
193 {
194 const piece_type& piece = *it;
195 bg::segment_identifier seg_id = piece.first_seg_id;
196 if (seg_id.segment_index < 0)
197 {
198 continue;
199 }
200
201 ring_type const& ring = offsetted_rings[seg_id.multi_index];
202
203 #if 0 // Does not compile (SVG is not enabled by default)
204 if (m_zoom && bg::disjoint(corner, m_alternate_box))
205 {
206 continue;
207 }
208 #endif
209
210 bg::model::ring<point_type> const& corner = piece.m_piece_border.get_full_ring();
211
212 if (m_zoom && do_pieces)
213 {
214 try
215 {
216 std::string style = "opacity:0.3;stroke:rgb(0,0,0);stroke-width:1;";
217 typedef typename bg::point_type<Box>::type point_type;
218 bg::model::multi_polygon<bg::model::polygon<point_type> > clipped;
219 bg::intersection(ring, m_alternate_box, clipped);
220 m_mapper.map(clipped,
221 piece.type == bg::strategy::buffer::buffered_segment
222 ? style + "fill:rgb(255,128,0);"
223 : style + "fill:rgb(255,0,0);");
224 }
225 catch (...)
226 {
227 std::cerr << "Error for piece " << piece.index << std::endl;
228 }
229 }
230 else if (do_pieces && ! corner.empty())
231 {
232 std::string style = "opacity:0.3;stroke:rgb(0,0,0);stroke-width:1;";
233 m_mapper.map(corner,
234 piece.type == bg::strategy::buffer::buffered_segment
235 ? style + "fill:rgb(255,128,0);"
236 : style + "fill:rgb(255,0,0);");
237 }
238
239 if (do_indices)
240 {
241 // Label starting piece_index / segment_index
242
243 std::ostringstream out;
244 out << piece.index
245 << (piece.is_flat_start ? " FS" : "")
246 << (piece.is_flat_end ? " FE" : "")
247 << " (" << piece_type_char(piece.type) << ") "
248 << piece.first_seg_id.segment_index
249 << ".." << piece.beyond_last_segment_index - 1
250 ;
251 point_type label_point
252 = corner.empty()
253 ? piece.m_label_point
254 : bg::return_centroid<point_type>(corner);
255
256 if ((piece.type == bg::strategy::buffer::buffered_concave
257 || piece.type == bg::strategy::buffer::buffered_flat_end)
258 && corner.size() >= 2u)
259 {
260 bg::set<0>(label_point, (bg::get<0>(corner[0]) + bg::get<0>(corner[1])) / 2.0);
261 bg::set<1>(label_point, (bg::get<1>(corner[0]) + bg::get<1>(corner[1])) / 2.0);
262 }
263 m_mapper.text(label_point, out.str(), "fill:rgb(255,0,0);font-family='Arial';font-size:10px;", 5, 5);
264 }
265 }
266 }
267
268 template <typename TraversedRings>
map_traversed_rings(TraversedRings const & traversed_rings)269 inline void map_traversed_rings(TraversedRings const& traversed_rings)
270 {
271 for(typename boost::range_iterator<TraversedRings const>::type it
272 = boost::begin(traversed_rings); it != boost::end(traversed_rings); ++it)
273 {
274 m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(0,255,0);stroke-width:2");
275 }
276 }
277
278 template <typename OffsettedRings>
map_offsetted_rings(OffsettedRings const & offsetted_rings)279 inline void map_offsetted_rings(OffsettedRings const& offsetted_rings)
280 {
281 for(typename boost::range_iterator<OffsettedRings const>::type it
282 = boost::begin(offsetted_rings); it != boost::end(offsetted_rings); ++it)
283 {
284 if (it->discarded())
285 {
286 m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(255,0,0);stroke-width:2");
287 }
288 else
289 {
290 m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(0,0,255);stroke-width:2");
291 }
292 }
293 }
294
295
296 SvgMapper& m_mapper;
297 Box m_alternate_box;
298 bool m_zoom;
299
300 };
301
302 template <typename Point>
303 class buffer_svg_mapper
304 {
305 public :
306
buffer_svg_mapper(std::string const & casename)307 buffer_svg_mapper(std::string const& casename)
308 : m_casename(casename)
309 , m_zoom(false)
310 {
311 bg::assign_inverse(m_alternate_box);
312 }
313
314 template <typename Mapper, typename Visitor, typename Envelope>
prepare(Mapper & mapper,Visitor & visitor,Envelope const & envelope,double box_buffer_distance)315 void prepare(Mapper& mapper, Visitor& visitor, Envelope const& envelope, double box_buffer_distance)
316 {
317 #ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX
318 // Create a zoomed-in view
319 bg::model::box<Point> alternate_box;
320 bg::read_wkt(BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX, alternate_box);
321 mapper.add(alternate_box);
322
323 // Take care non-visible elements are skipped
324 visitor.set_alternate_box(alternate_box);
325 set_alternate_box(alternate_box);
326 #else
327 bg::model::box<Point> box = envelope;
328 bg::buffer(box, box, box_buffer_distance);
329 mapper.add(box);
330 #endif
331
332 boost::ignore_unused(visitor);
333 }
334
set_alternate_box(bg::model::box<Point> const & box)335 void set_alternate_box(bg::model::box<Point> const& box)
336 {
337 m_alternate_box = box;
338 m_zoom = true;
339 }
340
341 template <typename Mapper, typename Geometry, typename GeometryBuffer>
map_input_output(Mapper & mapper,Geometry const & geometry,GeometryBuffer const & buffered,bool negative)342 void map_input_output(Mapper& mapper, Geometry const& geometry,
343 GeometryBuffer const& buffered, bool negative)
344 {
345 bool const areal = boost::is_same
346 <
347 typename bg::tag_cast
348 <
349 typename bg::tag<Geometry>::type,
350 bg::areal_tag
351 >::type, bg::areal_tag
352 >::type::value;
353
354 if (m_zoom)
355 {
356 map_io_zoomed(mapper, geometry, buffered, negative, areal);
357 }
358 else
359 {
360 map_io(mapper, geometry, buffered, negative, areal);
361 }
362 }
363
364 template <typename Mapper, typename Geometry, typename Strategy, typename RescalePolicy>
map_self_ips(Mapper & mapper,Geometry const & geometry,Strategy const & strategy,RescalePolicy const & rescale_policy)365 void map_self_ips(Mapper& mapper, Geometry const& geometry, Strategy const& strategy, RescalePolicy const& rescale_policy)
366 {
367 typedef bg::detail::overlay::turn_info
368 <
369 Point,
370 typename bg::detail::segment_ratio_type<Point, RescalePolicy>::type
371 > turn_info;
372
373 std::vector<turn_info> turns;
374
375 bg::detail::self_get_turn_points::no_interrupt_policy policy;
376 bg::self_turns
377 <
378 bg::detail::overlay::assign_null_policy
379 >(geometry, strategy, rescale_policy, turns, policy);
380
381 BOOST_FOREACH(turn_info const& turn, turns)
382 {
383 mapper.map(turn.point, "fill:rgb(255,128,0);stroke:rgb(0,0,100);stroke-width:1", 3);
384 }
385 }
386
387 private :
388
389 template <typename Mapper, typename Geometry, typename GeometryBuffer>
map_io(Mapper & mapper,Geometry const & geometry,GeometryBuffer const & buffered,bool negative,bool areal)390 void map_io(Mapper& mapper, Geometry const& geometry,
391 GeometryBuffer const& buffered, bool negative, bool areal)
392 {
393 // Map input geometry in green
394 if (areal)
395 {
396 mapper.map(geometry, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2");
397 }
398 else
399 {
400 // TODO: clip input points/linestring
401 mapper.map(geometry, "opacity:0.5;stroke:rgb(0,128,0);stroke-width:10");
402 }
403
404 {
405 // Map buffer in yellow (inflate) and with orange-dots (deflate)
406 std::string style = negative
407 ? "opacity:0.4;fill:rgb(255,255,192);stroke:rgb(255,128,0);stroke-width:3"
408 : "opacity:0.4;fill:rgb(255,255,128);stroke:rgb(0,0,0);stroke-width:3";
409
410 mapper.map(buffered, style);
411 }
412 }
413
414 template <typename Mapper, typename Geometry, typename GeometryBuffer>
map_io_zoomed(Mapper & mapper,Geometry const & geometry,GeometryBuffer const & buffered,bool negative,bool areal)415 void map_io_zoomed(Mapper& mapper, Geometry const& geometry,
416 GeometryBuffer const& buffered, bool negative, bool areal)
417 {
418 // Map input geometry in green
419 if (areal)
420 {
421 // Assuming input is areal
422 GeometryBuffer clipped;
423 // TODO: the next line does NOT compile for multi-point, TODO: implement that line
424 // bg::intersection(geometry, m_alternate_box, clipped);
425 mapper.map(clipped, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2");
426 }
427 else
428 {
429 // TODO: clip input (multi)point/linestring
430 mapper.map(geometry, "opacity:0.5;stroke:rgb(0,128,0);stroke-width:10");
431 }
432
433 {
434 // Map buffer in yellow (inflate) and with orange-dots (deflate)
435 std::string style = negative
436 ? "opacity:0.4;fill:rgb(255,255,192);stroke:rgb(255,128,0);stroke-width:3"
437 : "opacity:0.4;fill:rgb(255,255,128);stroke:rgb(0,0,0);stroke-width:3";
438
439 try
440 {
441 // Clip output multi-polygon with box
442 GeometryBuffer clipped;
443 bg::intersection(buffered, m_alternate_box, clipped);
444 mapper.map(clipped, style);
445 }
446 catch (...)
447 {
448 std::cout << "Error for buffered output " << m_casename << std::endl;
449 }
450 }
451 }
452
453 bg::model::box<Point> m_alternate_box;
454 bool m_zoom;
455 std::string m_casename;
456 };
457
458
459 #endif
460