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