Vidalia  0.2.15
TorMapImageView.cpp
Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file TorMapImageView.cpp
00013 ** \brief Displays Tor servers and circuits on a map of the world
00014 */
00015 
00016 #include "config.h"
00017 #include "TorMapImageView.h"
00018 
00019 #include <QStringList>
00020 
00021 #if defined(__sgi) && defined(_COMPILER_VERSION) && _COMPILER_VERSION >= 730
00022 #include <math.h>
00023 #else
00024 #include <cmath>
00025 #endif
00026 
00027 #define IMG_WORLD_MAP   ":/images/map/world-map.png"
00028 
00029 /** QPens to use for drawing different map elements */
00030 #define PEN_ROUTER        QPen(QColor("#ff030d"), 1.0)
00031 #define PEN_CIRCUIT       QPen(Qt::yellow, 0.5)
00032 #define PEN_SELECTED      QPen(Qt::green, 2.0)
00033 
00034 /** Size of the map image */
00035 #define IMG_WIDTH       1000
00036 #define IMG_HEIGHT      507
00037 
00038 /** Border between the edge of the image and the actual map */
00039 #define MAP_TOP         2
00040 #define MAP_BOTTOM      2
00041 #define MAP_RIGHT       5
00042 #define MAP_LEFT        5
00043 #define MAP_WIDTH       (IMG_WIDTH-MAP_LEFT-MAP_RIGHT)
00044 #define MAP_HEIGHT      (IMG_HEIGHT-MAP_TOP-MAP_BOTTOM)
00045 
00046 /** Map offset from zero longitude */
00047 #define MAP_ORIGIN       -10
00048 
00049 /** Minimum allowable size for this widget */
00050 #define MIN_SIZE        QSize(512,256)
00051 
00052 /** Robinson projection table */
00053 /** Length of the parallel of latitude */
00054 static float  plen[] = {
00055     1.0000, 0.9986, 0.9954, 0.9900,
00056     0.9822, 0.9730, 0.9600, 0.9427,
00057     0.9216, 0.8962, 0.8679, 0.8350,
00058     0.7986, 0.7597, 0.7186, 0.6732,
00059     0.6213, 0.5722, 0.5322
00060   };
00061 
00062 /** Distance of corresponding parallel from equator */ 
00063 static float  pdfe[] = {
00064     0.0000, 0.0620, 0.1240, 0.1860,
00065     0.2480, 0.3100, 0.3720, 0.4340,
00066     0.4958, 0.5571, 0.6176, 0.6769,
00067     0.7346, 0.7903, 0.8435, 0.8936,
00068     0.9394, 0.9761, 1.0000
00069   };
00070 
00071 /** Default constructor */
00072 TorMapImageView::TorMapImageView(QWidget *parent)
00073 : ZImageView(parent)
00074 {
00075   QImage map(IMG_WORLD_MAP);
00076   setImage(map);
00077 }
00078 
00079 /** Destructor */
00080 TorMapImageView::~TorMapImageView()
00081 {
00082   clear();
00083 }
00084 
00085 /** Adds a router to the map. */
00086 void
00087 TorMapImageView::addRouter(const RouterDescriptor &desc, const GeoIpRecord &geoip)
00088 {
00089   QString id = desc.id();
00090   QPointF routerCoord = toMapSpace(geoip.latitude(), geoip.longitude());
00091   
00092   /* Add data the hash of known routers, and plot the point on the map */
00093   if (_routers.contains(id))
00094     _routers.value(id)->first = routerCoord;
00095   else
00096     _routers.insert(id, new QPair<QPointF,bool>(routerCoord, false));
00097 }
00098 
00099 /** Adds a circuit to the map using the given ordered list of router IDs. */
00100 void
00101 TorMapImageView::addCircuit(const CircuitId &circid, const QStringList &path)
00102 {
00103   QPainterPath *circPainterPath = new QPainterPath;
00104   
00105   /* Build the new circuit */
00106   for (int i = 0; i < path.size()-1; i++) {
00107     QString fromNode = path.at(i);
00108     QString toNode = path.at(i+1);
00109    
00110     /* Add the coordinates of the hops to the circuit */
00111     if (_routers.contains(fromNode) && _routers.contains(toNode)) {
00112       /* Find the two endpoints for this path segment */
00113       QPointF fromPos = _routers.value(fromNode)->first;
00114       QPointF endPos = _routers.value(toNode)->first;
00115       
00116       /* Draw the path segment */ 
00117       circPainterPath->moveTo(fromPos);
00118       circPainterPath->lineTo(endPos);
00119       circPainterPath->moveTo(endPos);
00120     }
00121   }
00122   
00123   /** Add the data to the hash of known circuits and plot the circuit on the map */
00124   if (_circuits.contains(circid)) {
00125     /* This circuit is being updated, so just update the path, making sure we
00126      * free the memory allocated to the old one. */
00127     QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
00128     delete circuitPair->first;
00129     circuitPair->first = circPainterPath;
00130   } else {
00131     /* This is a new path, so just add it to our list */
00132     _circuits.insert(circid, new QPair<QPainterPath*,bool>(circPainterPath,false));
00133   }
00134 }
00135 
00136 /** Removes a circuit from the map. */
00137 void
00138 TorMapImageView::removeCircuit(const CircuitId &circid)
00139 {
00140   QPair<QPainterPath*,bool> *circ = _circuits.take(circid);
00141   QPainterPath *circpath = circ->first;
00142   if (circpath) {
00143     delete circpath;
00144   }
00145   delete circ;
00146 }
00147 
00148 /** Selects and highlights the router on the map. */
00149 void
00150 TorMapImageView::selectRouter(const QString &id)
00151 {
00152   if (_routers.contains(id)) {
00153     QPair<QPointF, bool> *routerPair = _routers.value(id);
00154     routerPair->second = true;
00155   }
00156   repaint();
00157 }
00158 
00159 /** Selects and highlights the circuit with the id <b>circid</b> 
00160  * on the map. */
00161 void
00162 TorMapImageView::selectCircuit(const CircuitId &circid)
00163 {
00164   if (_circuits.contains(circid)) {
00165     QPair<QPainterPath*, bool> *circuitPair = _circuits.value(circid);
00166     circuitPair->second = true;
00167   }
00168   repaint();
00169 }
00170 
00171 /** Deselects any highlighted routers or circuits */
00172 void
00173 TorMapImageView::deselectAll()
00174 {
00175   /* Deselect all router points */
00176   foreach (QString router, _routers.keys()) {
00177     QPair<QPointF,bool> *routerPair = _routers.value(router);
00178     routerPair->second = false;
00179   }
00180   /* Deselect all circuit paths */
00181   foreach (CircuitId circid, _circuits.keys()) {
00182     QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
00183     circuitPair->second = false;
00184   }
00185 }
00186 
00187 /** Clears the list of routers and removes all the data on the map */
00188 void
00189 TorMapImageView::clear()
00190 {
00191   /* Clear out all the router points and free their memory */
00192   foreach (QString router, _routers.keys()) {
00193     delete _routers.take(router);
00194   }
00195   /* Clear out all the circuit paths and free their memory */
00196   foreach (CircuitId circid, _circuits.keys()) {
00197     QPair<QPainterPath*,bool> *circuitPair = _circuits.take(circid);
00198     delete circuitPair->first;
00199     delete circuitPair;
00200   }
00201 }
00202   
00203 /** Draws the routers and paths onto the map image. */
00204 void
00205 TorMapImageView::paintImage(QPainter *painter)
00206 {
00207   painter->setRenderHint(QPainter::Antialiasing);
00208   
00209   /* Draw the router points */
00210   foreach(QString router, _routers.keys()) {
00211     QPair<QPointF,bool> *routerPair = _routers.value(router);
00212     painter->setPen((routerPair->second ? PEN_SELECTED : PEN_ROUTER)); 
00213     painter->drawPoint(routerPair->first);
00214   }
00215   /* Draw the circuit paths */
00216   foreach(CircuitId circid, _circuits.keys()) {
00217     QPair<QPainterPath*,bool> *circuitPair = _circuits.value(circid);
00218     painter->setPen((circuitPair->second ? PEN_SELECTED : PEN_CIRCUIT));
00219     painter->drawPath(*(circuitPair->first));
00220   }
00221 }
00222 
00223 /** Converts world space coordinates into map space coordinates */
00224 QPointF
00225 TorMapImageView::toMapSpace(float latitude, float longitude)
00226 {
00227   float width  = MAP_WIDTH;
00228   float height = MAP_HEIGHT;
00229   float deg = width / 360.0;
00230   longitude += MAP_ORIGIN;
00231 
00232   float lat;
00233   float lon;
00234   
00235   lat = floor(longitude * (deg * lerp(abs(int(latitude)), plen))
00236               + width/2 + MAP_LEFT);
00237   
00238   if (latitude < 0) {
00239     lon = floor((height/2) + (lerp(abs(int(latitude)), pdfe) * (height/2))
00240                 + MAP_TOP);
00241   } else {
00242     lon = floor((height/2) - (lerp(abs(int(latitude)), pdfe) * (height/2))
00243                 + MAP_TOP);
00244   }
00245 
00246   return QPointF(lat, lon);
00247 }
00248   
00249 /** Linearly interpolates using the values in the Robinson projection table */
00250 float
00251 TorMapImageView::lerp(float input, float *table)
00252 {
00253   int x = int(floor(input / 5));
00254 
00255   return ((table[x+1] - table[x]) / 
00256           (((x+1)*5) - (x*5))) * (input - x*5) + table[x];
00257 }
00258 
00259 /** Returns the minimum size of the widget */
00260 QSize
00261 TorMapImageView::minimumSizeHint() const
00262 {
00263   return MIN_SIZE;
00264 }
00265 
00266 /** Zooms to fit all currently displayed circuits on the map. If there are no
00267  * circuits on the map, the viewport will be returned to its default position
00268  * (zoomed all the way out and centered). */
00269 void
00270 TorMapImageView::zoomToFit()
00271 {
00272   QRectF rect = circuitBoundingBox();
00273   
00274   if (rect.isNull()) {
00275     /* If there are no circuits, zoom all the way out */
00276     resetZoomPoint();
00277     zoom(0.0);
00278   } else {
00279     /* Zoom in on the displayed circuits */
00280     float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
00281                                  rect.width()/float(MAP_WIDTH));
00282     
00283     zoom(rect.center().toPoint(), zoomLevel+0.2);
00284   }
00285 }
00286 
00287 /** Zoom to the circuit on the map with the given <b>circid</b>. */
00288 void
00289 TorMapImageView::zoomToCircuit(const CircuitId &circid)
00290 {
00291   if (_circuits.contains(circid)) {
00292     QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
00293     QRectF rect = ((QPainterPath *)pair->first)->boundingRect();
00294     if (!rect.isNull()) {
00295       float zoomLevel = 1.0 - qMax(rect.height()/float(MAP_HEIGHT),
00296                                    rect.width()/float(MAP_WIDTH));
00297 
00298       zoom(rect.center().toPoint(), zoomLevel+0.2);
00299     }
00300   }
00301 }
00302 
00303 /** Zooms in on the router with the given <b>id</b>. */
00304 void
00305 TorMapImageView::zoomToRouter(const QString &id)
00306 {
00307   QPair<QPointF,bool> *routerPair;
00308   
00309   if (_routers.contains(id)) {
00310     deselectAll();
00311     routerPair = _routers.value(id);
00312     routerPair->second = true;  /* Set the router point to "selected" */
00313     zoom(routerPair->first.toPoint(), 1.0); 
00314   }
00315 }
00316 
00317 /** Computes a bounding box around all currently displayed circuit paths on
00318  * the map. */
00319 QRectF
00320 TorMapImageView::circuitBoundingBox()
00321 {
00322   QRectF rect;
00323 
00324   /* Compute the union of bounding rectangles for all circuit paths */
00325   foreach (CircuitId circid, _circuits.keys()) {
00326     QPair<QPainterPath*,bool> *pair = _circuits.value(circid);
00327     QPainterPath *circuit = pair->first;
00328     rect = rect.unite(circuit->boundingRect());
00329   }
00330   return rect;
00331 }
00332