Vidalia  0.2.21
TorMapImageView.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file TorMapImageView.cpp
13 ** \brief Displays Tor servers and circuits on a map of the world
14 */
15 
16 #include "config.h"
17 #include "TorMapImageView.h"
18 
19 #include <QStringList>
20 
21 #if defined(__sgi) && defined(_COMPILER_VERSION) && _COMPILER_VERSION >= 730
22 #include <math.h>
23 #else
24 #include <cmath>
25 #endif
26 
27 #define IMG_WORLD_MAP ":/images/map/world-map.png"
28 
29 /** QPens to use for drawing different map elements */
30 #define PEN_ROUTER QPen(QColor("#ff030d"), 1.0)
31 #define PEN_CIRCUIT QPen(Qt::yellow, 0.5)
32 #define PEN_SELECTED QPen(Qt::green, 2.0)
33 
34 /** Size of the map image */
35 #define IMG_WIDTH 1000
36 #define IMG_HEIGHT 507
37 
38 /** Border between the edge of the image and the actual map */
39 #define MAP_TOP 2
40 #define MAP_BOTTOM 2
41 #define MAP_RIGHT 5
42 #define MAP_LEFT 5
43 #define MAP_WIDTH (IMG_WIDTH-MAP_LEFT-MAP_RIGHT)
44 #define MAP_HEIGHT (IMG_HEIGHT-MAP_TOP-MAP_BOTTOM)
45 
46 /** Map offset from zero longitude */
47 #define MAP_ORIGIN -10
48 
49 /** Minimum allowable size for this widget */
50 #define MIN_SIZE QSize(512,256)
51 
52 /** Robinson projection table */
53 /** Length of the parallel of latitude */
54 static float plen[] = {
55  1.0000, 0.9986, 0.9954, 0.9900,
56  0.9822, 0.9730, 0.9600, 0.9427,
57  0.9216, 0.8962, 0.8679, 0.8350,
58  0.7986, 0.7597, 0.7186, 0.6732,
59  0.6213, 0.5722, 0.5322
60  };
61 
62 /** Distance of corresponding parallel from equator */
63 static float pdfe[] = {
64  0.0000, 0.0620, 0.1240, 0.1860,
65  0.2480, 0.3100, 0.3720, 0.4340,
66  0.4958, 0.5571, 0.6176, 0.6769,
67  0.7346, 0.7903, 0.8435, 0.8936,
68  0.9394, 0.9761, 1.0000
69  };
70 
71 /** Default constructor */
73 : ZImageView(parent)
74 {
75  QImage map(IMG_WORLD_MAP);
76  setImage(map);
77 }
78 
79 /** Destructor */
81 {
82  clear();
83 }
84 
85 /** Adds a router to the map. */
86 void
88 {
89  QString id = desc.id();
90  QPointF routerCoord = toMapSpace(geoip.latitude(), geoip.longitude());
91 
92  /* Add data the hash of known routers, and plot the point on the map */
93  if (_routers.contains(id))
94  _routers.value(id)->first = routerCoord;
95  else
96  _routers.insert(id, new QPair<QPointF,bool>(routerCoord, false));
97 }
98 
99 /** Adds a circuit to the map using the given ordered list of router IDs. */
100 void
101 TorMapImageView::addCircuit(const CircuitId &circid, const QStringList &path)
102 {
103  QPainterPath *circPainterPath = new QPainterPath;
104 
105  /* Build the new circuit */
106  for (int i = 0; i < path.size()-1; i++) {
107  QString fromNode = path.at(i);
108  QString toNode = path.at(i+1);
109 
110  /* Add the coordinates of the hops to the circuit */
111  if (_routers.contains(fromNode) && _routers.contains(toNode)) {
112  /* Find the two endpoints for this path segment */
113  QPointF fromPos = _routers.value(fromNode)->first;
114  QPointF endPos = _routers.value(toNode)->first;
115 
116  /* Draw the path segment */
117  circPainterPath->moveTo(fromPos);
118  circPainterPath->lineTo(endPos);
119  circPainterPath->moveTo(endPos);
120  }
121  }
122 
123  /** Add the data to the hash of known circuits and plot the circuit on the map */
124  if (_circuits.contains(circid)) {
125  /* This circuit is being updated, so just update the path, making sure we
126  * free the memory allocated to the old one. */
127  QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
128  delete circuitPair->first;
129  circuitPair->first = circPainterPath;
130  } else {
131  /* This is a new path, so just add it to our list */
132  _circuits.insert(circid, new QPair<QPainterPath*,bool>(circPainterPath,false));
133  }
134 }
135 
136 /** Removes a circuit from the map. */
137 void
139 {
140  QPair<QPainterPath*,bool> *circ = _circuits.take(circid);
141  QPainterPath *circpath = circ->first;
142  if (circpath) {
143  delete circpath;
144  }
145  delete circ;
146 }
147 
148 /** Selects and highlights the router on the map. */
149 void
151 {
152  if (_routers.contains(id)) {
153  QPair<QPointF, bool> *routerPair = _routers.value(id);
154  routerPair->second = true;
155  }
156  repaint();
157 }
158 
159 /** Selects and highlights the circuit with the id <b>circid</b>
160  * on the map. */
161 void
163 {
164  if (_circuits.contains(circid)) {
165  QPair<QPainterPath*, bool> *circuitPair = _circuits.value(circid);
166  circuitPair->second = true;
167  }
168  repaint();
169 }
170 
171 /** Deselects any highlighted routers or circuits */
172 void
174 {
175  /* Deselect all router points */
176  foreach (QString router, _routers.keys()) {
177  QPair<QPointF,bool> *routerPair = _routers.value(router);
178  routerPair->second = false;
179  }
180  /* Deselect all circuit paths */
181  foreach (CircuitId circid, _circuits.keys()) {
182  QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
183  circuitPair->second = false;
184  }
185 }
186 
187 /** Clears the list of routers and removes all the data on the map */
188 void
190 {
191  /* Clear out all the router points and free their memory */
192  foreach (QString router, _routers.keys()) {
193  delete _routers.take(router);
194  }
195  /* Clear out all the circuit paths and free their memory */
196  foreach (CircuitId circid, _circuits.keys()) {
197  QPair<QPainterPath*,bool> *circuitPair = _circuits.take(circid);
198  delete circuitPair->first;
199  delete circuitPair;
200  }
201 }
202 
203 /** Draws the routers and paths onto the map image. */
204 void
205 TorMapImageView::paintImage(QPainter *painter)
206 {
207  painter->setRenderHint(QPainter::Antialiasing);
208 
209  /* Draw the router points */
210  foreach(QString router, _routers.keys()) {
211  QPair<QPointF,bool> *routerPair = _routers.value(router);
212  painter->setPen((routerPair->second ? PEN_SELECTED : PEN_ROUTER));
213  painter->drawPoint(routerPair->first);
214  }
215  /* Draw the circuit paths */
216  foreach(CircuitId circid, _circuits.keys()) {
217  QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
218  painter->setPen((circuitPair->second ? PEN_SELECTED : PEN_CIRCUIT));
219  painter->drawPath(*(circuitPair->first));
220  }
221 }
222 
223 /** Converts world space coordinates into map space coordinates */
224 QPointF
225 TorMapImageView::toMapSpace(float latitude, float longitude)
226 {
227  float width = MAP_WIDTH;
228  float height = MAP_HEIGHT;
229  float deg = width / 360.0;
230  longitude += MAP_ORIGIN;
231 
232  float lat;
233  float lon;
234 
235  lat = floor(longitude * (deg * lerp(abs(int(latitude)), plen))
236  + width/2 + MAP_LEFT);
237 
238  if (latitude < 0) {
239  lon = floor((height/2) + (lerp(abs(int(latitude)), pdfe) * (height/2))
240  + MAP_TOP);
241  } else {
242  lon = floor((height/2) - (lerp(abs(int(latitude)), pdfe) * (height/2))
243  + MAP_TOP);
244  }
245 
246  return QPointF(lat, lon);
247 }
248 
249 /** Linearly interpolates using the values in the Robinson projection table */
250 float
251 TorMapImageView::lerp(float input, float *table)
252 {
253  int x = int(floor(input / 5));
254 
255  return ((table[x+1] - table[x]) /
256  (((x+1)*5) - (x*5))) * (input - x*5) + table[x];
257 }
258 
259 /** Returns the minimum size of the widget */
260 QSize
262 {
263  return MIN_SIZE;
264 }
265 
266 /** Zooms to fit all currently displayed circuits on the map. If there are no
267  * circuits on the map, the viewport will be returned to its default position
268  * (zoomed all the way out and centered). */
269 void
271 {
272  QRectF rect = circuitBoundingBox();
273 
274  if (rect.isNull()) {
275  /* If there are no circuits, zoom all the way out */
276  resetZoomPoint();
277  zoom(0.0);
278  } else {
279  /* Zoom in on the displayed circuits */
280  float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
281  rect.width()/float(MAP_WIDTH));
282 
283  zoom(rect.center().toPoint(), zoomLevel+0.2);
284  }
285 }
286 
287 /** Zoom to the circuit on the map with the given <b>circid</b>. */
288 void
290 {
291  if (_circuits.contains(circid)) {
292  QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
293  QRectF rect = ((QPainterPath *)pair->first)->boundingRect();
294  if (!rect.isNull()) {
295  float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
296  rect.width()/float(MAP_WIDTH));
297 
298  zoom(rect.center().toPoint(), zoomLevel+0.2);
299  }
300  }
301 }
302 
303 /** Zooms in on the router with the given <b>id</b>. */
304 void
306 {
307  QPair<QPointF,bool> *routerPair;
308 
309  if (_routers.contains(id)) {
310  deselectAll();
311  routerPair = _routers.value(id);
312  routerPair->second = true; /* Set the router point to "selected" */
313  zoom(routerPair->first.toPoint(), 1.0);
314  }
315 }
316 
317 /** Computes a bounding box around all currently displayed circuit paths on
318  * the map. */
319 QRectF
321 {
322  QRectF rect;
323 
324  /* Compute the union of bounding rectangles for all circuit paths */
325  foreach (CircuitId circid, _circuits.keys()) {
326  QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
327  QPainterPath *circuit = pair->first;
328  rect = rect.unite(circuit->boundingRect());
329  }
330  return rect;
331 }
332