My Project
 All Classes
recmap.h
1 //
2 // This file is part of recmap.
3 //
4 // recmap is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // recmap is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with recmap. If not, see <http://www.gnu.org/licenses/>.
16 
17 // Authors : Christian Panse <Christian.Panse@gmail.com>
18 // 2016-04-19/20/21/22 Bristol, UK
19 
20 #ifndef RECMAP_H
21 #define RECMAP_H
22 
23 #include <stdexcept>
24 #include <iterator>
25 #include <algorithm>
26 #include <vector>
27 #include <stack>
28 #include <list>
29 #include <iostream>
30 #include <string>
31 #include <iterator>
32 #include <sstream>
33 #include <cmath>
34 #include <set>
35 
36 namespace crecmap{
37 
38  // keeps map and pseudo dual
39  typedef struct {
40  double x, y, dx, dy, z;
41  int id;
42  double area_desired;
43  int placed;
44  std::string name;
45  std::vector<int> connected;
46  double topology_error;
47  double relative_position_error;
48  double relative_position_neighborhood_error;
49  int dfs_num;
50  } map_region;
51 
52 struct mbb_node {
53  double key;
54  int id;
55 
56  mbb_node(const double& strKey = 0, const int& intId = 0)
57  : key(strKey),
58  id(intId) {}
59 
60  bool operator<(const mbb_node& rhs) const {
61  return key < rhs.key;
62  }
63 };
64 
65  // use it as sorted list
66  // insert/upper_bound: O(ln(n))
67  typedef struct {
68  double max_dx;
69  double max_dy;
70  std::multiset<mbb_node> x;
71  std::multiset<mbb_node> y;
72  } mbb_set;
73 
74  typedef std::vector<map_region> recmapvector;
75 
76  // http://en.cppreference.com/w/cpp/numeric/math/atan2
77  double get_angle(const map_region &a, const map_region &b){
78  double alpha = std::atan2(b.x - a.x, b.y - a.y);
79  return (alpha);
80  }
81 
82  // http://gamemath.com/2011/09/detecting-whether-two-boxes-overlap/
83  bool mbb_check(const map_region &a, const map_region &b){
84 
85  if (a.x + a.dx < b.x - b.dx) return false; // a is left of b
86  else if (a.x - a.dx > b.x + b.dx) return false; // a is right of b
87  else if (a.y + a.dy < b.y - b.dy) return false; // a is above b
88  else if (a.y - a.dy > b.y + b.dy) return false; // a is below b
89 
90  // rectangles can touch each other but do not overlap
91  return true;
92  }
93 
94  // computes the new x-y value on the cartogram map region c
95  // map_region a has a fix position
96  // uses c.dx and c.dy for computation
97  // TODO(cp): consider giving eps als argument
98  void place_rectanle(const map_region &a, double alpha, map_region &c){
99  double tanx, tany;
100  double eps = 0.01;
101 
102  double dy = a.dy + c.dy + eps;
103  double dx = a.dx + c.dx + eps;
104 
105  if (std::sin(alpha) >= 0 & std::cos(alpha) >= 0){
106  // Quad I
107  tanx = a.x + (dx * std::tan(alpha));
108  tany = a.y + (dy * std::tan(PI/2 - alpha));
109 
110  c.x = a.x + dx;
111  c.y = a.y + dy;
112 
113  // there are always two intersection; choose the right one
114  if (tany >= c.y) c.x = tanx;
115  else c.y = tany;
116 
117  } else if (std::sin(alpha) >= 0 && std::cos(alpha) < 0) {
118  // Quad II
119  tanx = a.x + (dx * std::tan(PI - alpha));
120  tany = a.y - (dy * std::tan(alpha - PI/2));
121 
122  c.x = a.x + dx;
123  c.y = a.y - dy;
124 
125  if (tanx <= c.x) c.x = tanx;
126  else c.y = tany;
127 
128  } else if (std::sin(alpha) < 0 && std::cos(alpha) < 0) {
129  // Quad III
130  tanx = a.x - (dx * std::tan(alpha - PI));
131  tany = a.y - (dy * std::tan(3 * PI / 2 - alpha));
132 
133  c.x = a.x - dx;
134  c.y = a.y - dy;
135 
136  if (tany > c.y) c.y = tany;
137  else c.x = tanx;
138 
139  } else if (std::sin(alpha) < 0 && std::cos(alpha) > 0) {
140  // Quad IV
141  tanx = a.x - (dy * std::tan(2 * PI - alpha));
142  tany = a.y + (dx * std::tan(alpha - 3 * PI /2));
143 
144  c.x = a.x - dx;
145  c.y = a.y + dy;
146 
147  if (tanx < c.x) c.y = tany;
148  else c.x = tanx;
149 
150  } else {
151  // error
152  }
153  }
154 
155  // http://stackoverflow.com/questions/17787410/nested-range-based-for-loops
156  template<typename C, typename Op1>
157  void each_unique_pair(C& container, Op1 fun1){
158  for(auto it = container.begin(); it != container.end() - 1; ++it)
159  for(auto it2 = std::next(it); it2 != container.end(); ++it2)
160  fun1(*it, *it2, container);
161  }
162 
163  template<typename C, typename C1, typename Op1>
164  void each_unique_pair2(C& container, C1& container1, Op1 fun2){
165  for(auto it = container.begin(); it != container.end() - 1; ++it)
166  for(auto it2 = std::next(it); it2 != container.end(); ++it2)
167  fun2(*it, *it2, container, container1);
168  }
169 
170  class RecMap{
171  recmapvector Map;
172  recmapvector Cartogram;
173  mbb_set MBB;
174  int intersect_count;
175 
176  int num_regions;
177 
178  std::list<std::string> msg;
179  std::list<std::string> warnings;
180 
181  public:
182 
183  //template < class Tdouble >
184 
185  RecMap() {
186  num_regions = 0;
187  MBB.max_dx = 0;
188  MBB.max_dy = 0;
189  intersect_count = 0;
190  }
191 
192  void push(double x, double y, double dx, double dy, double z, std::string name){
193 
194  map_region R, R1;
195 
196  R.x=x; R.y=y; R.dx=dx; R.dy = dy; R.z =z;
197  R.id = num_regions;
198  R.area_desired = -1;
199  R.connected = {};
200  R.placed = 1;
201  R.name = name;
202  R.dfs_num = -1;
203 
204  R1.x=-1; R1.y=-1; R1.dx = dx; R1.dy = dy; R1.z =z;
205  R1.id = num_regions;
206  R1.area_desired = -1;
207  R1.connected = {};
208  R1.placed = 0;
209  R1.name = name;
210  R1.dfs_num = -1;
211  R1.topology_error = 100;
212 
213  Map.push_back(R);
214  Cartogram.push_back(R1);
215  num_regions++;
216 
217  if (num_regions != Map.size()){
218  // TODO(cp): call an exception
219  }
220  }
221 
222  std::string warnings_pop(){
223  std::string s =warnings.front(); warnings.pop_front();
224  return s;
225  }
226 
227  bool warnings_empty(){return warnings.empty();}
228 
229  int get_size(){return num_regions;}
230 
231  int get_intersect_count(){ return intersect_count; }
232 
233  map_region& get_map_region(int i){ return(Cartogram[i]); }
234 
235  void ComputePseudoDual(recmapvector &M){
236  each_unique_pair(M, [this](map_region &a, map_region &b, recmapvector &M){
237  // add edges tp pseudo dual graph iff boxes are connected
238  if ( mbb_check(a,b) ){
239  M[a.id].connected.push_back(b.id);
240  M[b.id].connected.push_back(a.id);
241  }
242  });
243  }
244 
245  // taken from the CartoDraw scanline approach date back to yr2000
246  void ComputeDesiredArea(recmapvector &M, recmapvector &C){
247  double sum_z = 0.0;
248  double sum_area = 0.0;
249 
250  std::for_each(M.begin(), M.end(), [&] (map_region &r) {sum_z += r.z;});
251  std::for_each(M.begin(), M.end(), [&] (map_region &r) {sum_area += (4 * r.dx * r.dy);});
252 
253  std::for_each(C.begin(), C.end(), [&] (map_region &r) {
254  double area_desired = r.z * sum_area / sum_z;
255  double ratio = r.dy / r.dx;
256  r.dx = sqrt(area_desired / (4 * ratio));
257  r.dy = r.dx * ratio;
258  });
259  }
260 
261 
262  // TODO(cp): has to be implemented
263  int ComputeCoreRegion(recmapvector &M, recmapvector &C){
264 
265  int core_region_id = num_regions / 2;
266 
267  C[core_region_id].x = M[core_region_id].x;
268  C[core_region_id].y = M[core_region_id].y;
269  C[core_region_id].placed++;
270 
271  mbb_node mn;
272  mn.key = C[core_region_id].x; mn.id = C[core_region_id].id;
273  MBB.x.insert(mn);
274 
275  mn.key = C[core_region_id].y; mn.id = C[core_region_id].id;
276  MBB.y.insert(mn);
277 
278  MBB.max_dx = C[core_region_id].dx;
279  MBB.max_dy = C[core_region_id].dy;
280 
281  return core_region_id;
282  }
283 
284  bool map_region_intersect(const recmapvector &C, const map_region &a){
285  for (map_region b : C){
286  if (a.id != b.id && b.placed > 0){
287  intersect_count++;
288  if(mbb_check(a, b)){
289  return true;
290  }}
291  }
292  return false;
293  }
294 
295  bool map_region_intersect_set(recmapvector &C, const mbb_set &S, const map_region &a){
296  double eps = 0.0;
297  auto lower_x = std::lower_bound(S.x.begin(), S.x.end(),
298  a.x - a.dx - S.max_dx - eps,
299  [](const mbb_node& f1, const mbb_node& f2) { return f1.key < f2.key; });
300 
301  auto upper_x = std::upper_bound(S.x.begin(), S.x.end(),
302  a.x + a.dx + S.max_dx + eps,
303  [](const mbb_node& f1, const mbb_node& f2) { return f1.key < f2.key; });
304 
305  auto lower_y = std::lower_bound(S.y.begin(), S.y.end(),
306  a.y - a.dy - S.max_dy - eps,
307  [](const mbb_node& f1, const mbb_node& f2) { return f1.key < f2.key; });
308 
309  auto upper_y = std::upper_bound(S.y.begin(), S.y.end(),
310  a.y + a.dy + S.max_dy + eps,
311  [](const mbb_node& f1, const mbb_node& f2) { return f1.key < f2.key; });
312 
313  /*
314  std::cout << std::distance(lower_x, upper_x) << "\t" << std::distance(lower_y, upper_y) << "\t"
315  << S.x.size() << "\t"
316  << S.y.size() << "\n";
317  */
318  for(auto it_x = lower_x; it_x != upper_x; ++it_x){
319  intersect_count++;
320  if ((*it_x).id != a.id && mbb_check(a, C[(*it_x).id])){
321  return true;
322  }
323  }
324 
325  for(auto it_y = lower_y; it_y != upper_y; ++it_y){
326  intersect_count++;
327  if ((*it_y).id != a.id && mbb_check(a, C[(*it_y).id])){
328  return true;
329  }
330  }
331 
332  return false;
333  }
334 
335  // place rectangle around predecessor_region_id
336  bool PlaceRectangle(recmapvector &M, recmapvector &C, int region_id){
337 
338  double alpha0, alpha;
339  mbb_node mn;
340 
341  double beta_sign = 1.0;
342 
343  // strategy one: try to place it in the neighborhood
344  for (double beta = 0.0; beta <= PI && C[region_id].placed == 0; beta += PI/180){
345 
346  // iterate over all already placed connected rectangles
347  for (int adj_region_id : M[region_id].connected){
348 
349  if (C[adj_region_id].placed > 0 ){
350 
351  alpha0 = get_angle(M[adj_region_id], M[region_id]);
352 
353  alpha = alpha0 + (beta_sign * beta);
354  beta_sign *= -1;
355 
356  place_rectanle(C[adj_region_id], alpha, C[region_id]);
357 
358 
359  // this is to enable the linear MBB check
360  //if (!map_region_intersect(C, C[region_id])) {
361  if (!map_region_intersect_set(C, MBB, C[region_id])){
362 
363  // DEBUG BEGIN
364  /*
365  if (map_region_intersect(C, C[region_id])){
366  std::cout << "ERROR"<< "\t"
367  << C[region_id].name << "\t"
368  << C[region_id].x << "\t"
369  << C[region_id].y << "\t"
370  << C[region_id].dx << "\t"
371  << C[region_id].dy << "\t"
372  << MBB.max_dx << "\t"
373  << MBB.max_dy << "\t"
374  << "\n";
375  }*/
376  // DEBUG END
377 
378  C[region_id].placed++;
379  C[region_id].topology_error = 0;
380 
381  mn.key = C[region_id].x; mn.id = C[region_id].id;
382  MBB.x.insert(mn);
383 
384  mn.key = C[region_id].y; mn.id = C[region_id].id;
385  MBB.y.insert(mn);
386 
387  if (C[region_id].dx > MBB.max_dx) {MBB.max_dx = C[region_id].dx;}
388  if (C[region_id].dy > MBB.max_dy) {MBB.max_dy = C[region_id].dy;}
389 
390  // update dual graph
391  C[adj_region_id].connected.push_back(region_id);
392  C[region_id].connected.push_back(adj_region_id);
393  return true;
394  }
395  }
396  } // END for (int adj_region_id : M[region_id].connected)
397  }
398 
399  // placement failed => make it as not placed
400  C[region_id].x = -1;
401  C[region_id].y = -1;
402  warnings.push_back(M[region_id].name + " could not be placed on the first attempt;");
403  return false;
404  }
405 
406 
407  // dfs explorer of existing map M / placement of rectangles in cartogram C
408  void DrawCartogram(recmapvector &M, recmapvector &C, int core_region_id){
409  std::list<int> stack;
410  std::vector<int> visited(num_regions, 0);
411  std::vector<int> dfs_num(num_regions, 0);
412 
413  int dfs_num_counter = 0;
414  int current_region_id = core_region_id;
415  stack.push_back(current_region_id);
416  visited[current_region_id]++;
417 
418  while (stack.size() > 0){
419  current_region_id = stack.back() ; stack.pop_back();
420  dfs_num[current_region_id] = dfs_num_counter++;
421  C[current_region_id].dfs_num = dfs_num[current_region_id];
422 
423  if (current_region_id != core_region_id){
424  if (!PlaceRectangle(M, C, current_region_id)){
425  // bad luck
426  }
427  }
428 
429  for(int adj_region_id : M[current_region_id].connected){
430  if (visited[adj_region_id] == 0) {
431  visited[adj_region_id]++;
432  stack.push_back(adj_region_id);
433  }
434  }
435  } // while
436 
437  std::for_each(C.begin(), C.end(), [&] (map_region &r) {
438  if (r.placed == 0){
439  PlaceRectangle(M, C, r.id);
440  if (r.placed == 0){
441  warnings.push_back(r.name + " was not placed!!");
442  }
443  //
444  }});
445  }
446 
447  void ComputeError(const recmapvector &M, recmapvector &C){
448  double gammaM, gammaC, delta;
449 
450  for (map_region a : M){
451  for (map_region b : M){
452  gammaM = get_angle(M[a.id], M[b.id]);
453  gammaC = get_angle(C[a.id], C[b.id]);
454  delta = fabs (gammaC - gammaM) / C.size();
455  C[a.id].relative_position_error += delta;
456  }
457 
458  for (auto idx : a.connected){
459  gammaM = get_angle(M[a.id], M[idx]);
460  gammaC = get_angle(C[a.id], C[idx]);
461  delta = fabs (gammaC - gammaM) / a.connected.size();
462  C[a.id].relative_position_neighborhood_error += delta;
463  }
464  }
465  }
466 
467  void run(){
468 
469  ComputePseudoDual(Map);
470 
471  ComputeDesiredArea(Map, Cartogram);
472 
473  int core_region_id = ComputeCoreRegion(Map, Cartogram);
474  msg.push_back("CORE REGION: " + Map[core_region_id].name);
475  DrawCartogram(Map, Cartogram, core_region_id);
476 
477  ComputeError(Map, Cartogram);
478 
479  }//run
480  };
481 }// namespace
482 #endif
Definition: recmap.h:36
Definition: recmap.h:52
Definition: recmap.h:39
Definition: recmap.h:67
Definition: recmap.h:170