SVN checkout 11/12/2010
[monav:monav.git] / plugins / osmrenderer / rendererbase.cpp
1 /*
2 Copyright 2010  Christian Vetter veaac.fdirct@gmail.com
3
4 This file is part of MoNav.
5
6 MoNav 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 MoNav 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 MoNav.  If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include <QDir>
21 #include <QPainter>
22 #include <algorithm>
23 #include <cmath>
24 #include <QtDebug>
25
26 #include "rendererbase.h"
27 #include "utils/intersection.h"
28
29 RendererBase::RendererBase()
30 {
31         m_loaded = false;
32         setupPolygons();
33         m_tileSize = 256;
34         m_settingsDialog = NULL;
35 }
36
37 RendererBase::~RendererBase()
38 {
39         if ( m_settingsDialog != NULL )
40                 delete m_settingsDialog;
41 }
42
43 int RendererBase::GetMaxZoom()
44 {
45         return m_zoomLevels.size() - 1;
46 }
47
48 void RendererBase::reset()
49 {
50         m_cache.clear();
51         m_zoomLevels.clear();
52         unload();
53 }
54
55 void RendererBase::setupPolygons()
56 {
57         double leftPointer  = 135.0 / 180.0 * M_PI;
58         double rightPointer = -135.0 / 180.0 * M_PI;
59         m_arrow << QPointF( cos( leftPointer ), sin( leftPointer ) );
60         m_arrow << QPointF( 1, 0 );
61         m_arrow << QPointF( cos( rightPointer ), sin( rightPointer ) );
62         m_arrow << QPointF( -3.0 / 8, 0 );
63 }
64
65 void RendererBase::SetInputDirectory( const QString& dir )
66 {
67         m_directory = dir;
68 }
69
70 void RendererBase::ShowSettings()
71 {
72         assert( m_loaded );
73         m_settingsDialog->exec();
74         if ( !m_settingsDialog->getSettings( &m_settings ) )
75                 return;
76         m_cache.setMaxCost( 1024 * 1024 * m_settings.cacheSize );
77 }
78
79 bool RendererBase::LoadData()
80 {
81         if ( m_loaded )
82                 reset();
83
84         if ( m_settingsDialog == NULL )
85                 m_settingsDialog = new BRSettingsDialog();
86         if ( !m_settingsDialog->getSettings( &m_settings ) )
87                 return false;
88         m_cache.setMaxCost( 1024 * 1024 * m_settings.cacheSize );
89
90         if ( !load() )
91                 return false;
92
93         if ( m_zoomLevels.size() == 0 )
94                 return false;
95
96         m_loaded = true;
97         return true;
98 }
99
100 ProjectedCoordinate RendererBase::Move( int shiftX, int shiftY, const PaintRequest& request )
101 {
102         if ( !m_loaded )
103                 return request.center;
104         int zoom = m_zoomLevels[request.zoom];
105         ProjectedCoordinate center = request.center;
106         if ( request.rotation != 0 ) {
107                 int newX, newY;
108                 QTransform transform;
109                 transform.rotate( -request.rotation );
110                 transform.map( shiftX, shiftY, &newX, &newY );
111                 shiftX = newX;
112                 shiftY = newY;
113         }
114         center.x -= ( double ) shiftX / m_tileSize / ( 1u << zoom ) / ( request.virtualZoom );
115         center.y -= ( double ) shiftY / m_tileSize / ( 1u << zoom ) / ( request.virtualZoom );
116         return center;
117 }
118
119 ProjectedCoordinate RendererBase::PointToCoordinate( int shiftX, int shiftY, const PaintRequest& request )
120 {
121         return Move( -shiftX, -shiftY, request );
122 }
123
124 void RendererBase::SetUpdateSlot( QObject* obj, const char* slot )
125 {
126         connect( this, SIGNAL(changed()), obj, slot );
127 }
128
129 long long RendererBase::tileID( int x, int y, int zoom )
130 {
131         assert( zoom < 24 );
132         long long id = y;
133         id = ( id << 24 ) | x;
134         id = ( id << 5 ) | zoom;
135         return id;
136 }
137
138 bool RendererBase::Paint( QPainter* painter, const PaintRequest& request )
139 {
140         if ( !m_loaded )
141                 return false;
142         if ( request.zoom < 0 || request.zoom >= ( int ) m_zoomLevels.size() )
143                 return false;
144
145         int zoom = m_zoomLevels[request.zoom];
146
147         int sizeX = painter->device()->width();
148         int sizeY = painter->device()->height();
149
150         if ( sizeX <= 1 && sizeY <= 1 )
151                 return true;
152         double rotation = request.rotation;
153         if ( fmod( rotation / 90, 1 ) < 0.01 )
154                 rotation = 90 * floor( rotation / 90 );
155         else if ( fmod( rotation / 90, 1 ) > 0.99 )
156                 rotation = 90 * ceil( rotation / 90 );
157
158         double tileFactor = 1u << zoom;
159         double zoomFactor = tileFactor * m_tileSize;
160         painter->translate( sizeX / 2, sizeY / 2 );
161         if ( request.virtualZoom > 0 )
162                 painter->scale( request.virtualZoom, request.virtualZoom );
163         painter->rotate( rotation );
164
165         if ( m_settings.filter && fmod( rotation, 90 ) != 0 )
166                 painter->setRenderHint( QPainter::SmoothPixmapTransform );
167
168         if ( m_settings.hqAntiAliasing )
169                 painter->setRenderHint( QPainter::HighQualityAntialiasing );
170
171         QTransform transform = painter->worldTransform();
172         QTransform inverseTransform = transform.inverted();
173
174         const int xWidth = 1 << zoom;
175         const int yWidth = 1 << zoom;
176
177         QRect boundingBox = inverseTransform.mapRect( QRect(0, 0, sizeX, sizeY ) );
178
179         int minX = floor( ( double ) boundingBox.x() / m_tileSize + request.center.x * tileFactor );
180         int maxX = ceil( ( double ) boundingBox.right() / m_tileSize + request.center.x * tileFactor );
181         int minY = floor( ( double ) boundingBox.y() / m_tileSize + request.center.y * tileFactor );
182         int maxY = ceil( ( double ) boundingBox.bottom() / m_tileSize + request.center.y * tileFactor );
183
184         int posX = ( minX - request.center.x * tileFactor ) * m_tileSize;
185         for ( int x = minX; x < maxX; ++x ) {
186                 int posY = ( minY - request.center.y * tileFactor ) * m_tileSize;
187                 for ( int y = minY; y < maxY; ++y ) {
188
189                         QPixmap* tile = NULL;
190                         if ( x >= 0 && x < xWidth && y >= 0 && y < yWidth ) {
191                                 long long id = tileID( x, y, zoom );
192                                 if ( !m_cache.contains( id ) ) {
193                                         if ( !loadTile( x, y, zoom, &tile ) ) {
194                                                 tile = new QPixmap( m_tileSize, m_tileSize );
195                                                 tile->fill( QColor( 241, 238 , 232, 255 ) );
196                                         }
197
198                                         long long minCacheSize = 2 * m_tileSize * m_tileSize * tile->depth() / 8 * ( maxX - minX ) * ( maxY - minY );
199                                         if ( m_cache.maxCost() < minCacheSize ) {
200                                                 qDebug() << "had to increase cache size to accomodate all tiles for at least two images: " << minCacheSize / 1024 / 1024 << " MB";
201                                                 m_cache.setMaxCost( minCacheSize );
202                                         }
203
204                                         m_cache.insert( id, tile, m_tileSize * m_tileSize * tile->depth() / 8 );
205                                 }
206                                 else {
207                                         tile = m_cache.object( id );
208                                 }
209                         }
210
211                         if ( tile != NULL )
212                                 painter->drawPixmap( posX, posY, *tile );
213                         else
214                                 painter->fillRect( posX, posY,  m_tileSize, m_tileSize, QColor( 241, 238 , 232, 255 ) );
215                         posY += m_tileSize;
216                 }
217                 posX += m_tileSize;
218         }
219
220         if ( m_settings.antiAliasing )
221                 painter->setRenderHint( QPainter::Antialiasing );
222
223         if ( request.edgeSegments.size() > 0 && request.edges.size() > 0 ) {
224                 int position = 0;
225                 for ( int i = 0; i < request.edgeSegments.size(); i++ ) {
226                         QVector< ProjectedCoordinate > line;
227                         for ( ; position < request.edgeSegments[i]; position++ ) {
228                                 ProjectedCoordinate pos = request.edges[position].ToProjectedCoordinate();
229                                 line.push_back( ProjectedCoordinate( ( pos.x - request.center.x ) * zoomFactor, ( pos.y - request.center.y ) * zoomFactor ) );
230                         }
231                         drawPolyline( painter, boundingBox, line, QColor( 0, 0, 128, 128 ) );
232                 }
233         }
234
235         if ( request.route.size() > 0 ) {
236                 QVector< ProjectedCoordinate > line;
237                 for ( int i = 0; i < request.route.size(); i++ ){
238                         ProjectedCoordinate pos = request.route[i].coordinate.ToProjectedCoordinate();
239                         line.push_back( ProjectedCoordinate( ( pos.x - request.center.x ) * zoomFactor, ( pos.y - request.center.y ) * zoomFactor ) );
240                 }
241                 drawPolyline( painter, boundingBox, line, QColor( 0, 0, 128, 128 ) );
242         }
243
244         if ( request.POIs.size() > 0 ) {
245                 for ( int i = 0; i < request.POIs.size(); i++ ) {
246                         ProjectedCoordinate pos = request.POIs[i].ToProjectedCoordinate();
247                         drawIndicator( painter, transform, inverseTransform, ( pos.x - request.center.x ) * zoomFactor, ( pos.y - request.center.y ) * zoomFactor, sizeX, sizeY, request.virtualZoom, QColor( 196, 0, 0 ), QColor( 0, 0, 196 ) );
248                 }
249         }
250
251         if ( request.target.IsValid() )
252         {
253                 ProjectedCoordinate pos = request.target.ToProjectedCoordinate();
254                 drawIndicator( painter, transform, inverseTransform, ( pos.x - request.center.x ) * zoomFactor, ( pos.y - request.center.y ) * zoomFactor, sizeX, sizeY, request.virtualZoom, QColor( 0, 0, 128 ), QColor( 255, 0, 0 ) );
255         }
256
257         if ( request.position.IsValid() )
258         {
259                 ProjectedCoordinate pos = request.position.ToProjectedCoordinate();
260                 drawIndicator( painter, transform, inverseTransform, ( pos.x - request.center.x ) * zoomFactor, ( pos.y - request.center.y ) * zoomFactor, sizeX, sizeY, request.virtualZoom, QColor( 0, 128, 0 ), QColor( 255, 255, 0 ) );
261                 drawArrow( painter, ( pos.x - request.center.x ) * zoomFactor, ( pos.y - request.center.y ) * zoomFactor, request.heading - 90, QColor( 0, 128, 0 ), QColor( 255, 255, 0 ) );
262         }
263
264         return true;
265 }
266
267 void RendererBase::drawArrow( QPainter* painter, int x, int y, double rotation, QColor outer, QColor inner )
268 {
269         QMatrix arrowMatrix;
270         arrowMatrix.translate( x, y );
271         arrowMatrix.rotate( rotation );
272         arrowMatrix.scale( 8, 8 );
273
274         painter->setPen( QPen( outer, 5, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
275         painter->setBrush( outer );
276         painter->drawPolygon( arrowMatrix.map( m_arrow ) );
277         painter->setPen( QPen( inner, 1, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin ) );
278         painter->setBrush( inner );
279         painter->drawPolygon( arrowMatrix.map( m_arrow ) );
280 }
281
282 void RendererBase::drawIndicator( QPainter* painter, const QTransform& transform, const QTransform& inverseTransform, int x, int y, int sizeX, int sizeY, int virtualZoom, QColor outer, QColor inner )
283 {
284         QPoint mapped = transform.map( QPoint( x, y ) );
285         int margin = 9 * virtualZoom;
286         if ( mapped.x() < margin || mapped.y() < margin || mapped.x() >= sizeX - margin || mapped.y() >= sizeY - margin ) {
287                 //clip an imaginary line from the screen center to pos at the screen boundaries
288                 ProjectedCoordinate start( mapped.x(), mapped.y() );
289                 ProjectedCoordinate end( sizeX / 2, sizeY / 2 );
290                 clipEdge( &start, &end, ProjectedCoordinate( margin, margin ), ProjectedCoordinate( sizeX - margin, sizeY - margin ) );
291                 QPoint position = inverseTransform.map( QPoint( start.x, start.y ) );
292                 QPoint center = inverseTransform.map( QPoint( sizeX / 2, sizeY / 2 ) );
293                 double heading = atan2( ( double ) ( y - center.y() ), ( double ) ( x - center.x() ) ) * 360 / 2 / M_PI;
294                 drawArrow( painter, position.x(), position.y(), heading, outer, inner );
295         }
296         else {
297                 painter->setBrush( Qt::NoBrush );
298                 painter->setPen( QPen( outer, 5 ) );
299                 painter->drawEllipse( x - 8, y - 8, 16, 16);
300                 painter->setPen( QPen( inner, 2 ) );
301                 painter->drawEllipse( x - 8, y - 8, 16, 16);
302         }
303 }
304
305 void RendererBase::drawPolyline( QPainter* painter, const QRect& boundingBox, QVector< ProjectedCoordinate > line, QColor color )
306 {
307         painter->setPen( QPen( color, 8, Qt::SolidLine, Qt::FlatCap ) );
308
309         QVector< bool > isInside;
310
311         for ( int i = 0; i < line.size(); i++ ) {
312                 ProjectedCoordinate pos = line[i];
313                 QPoint point( pos.x, pos.y );
314                 isInside.push_back( boundingBox.contains( point ) );
315         }
316
317         QVector< bool > draw = isInside;
318         for ( int i = 1; i < line.size(); i++ ) {
319                 if ( isInside[i - 1] )
320                         draw[i] = true;
321                 if ( isInside[i] )
322                         draw[i - 1] = true;
323         }
324
325         QVector< QPoint > polygon;
326         bool firstPoint = true;
327         ProjectedCoordinate lastCoord;
328         for ( int i = 0; i < line.size(); i++ ) {
329                 if ( !draw[i] ) {
330                         if ( !polygon.empty() ) {
331                                 painter->drawPolyline( polygon.data(), polygon.size() );
332                                 polygon.clear();
333                         }
334                         firstPoint = true;
335                         continue;
336                 }
337                 ProjectedCoordinate pos = line[i];
338                 double xDiff = fabs( pos.x - lastCoord.x );
339                 double yDiff = fabs( pos.y - lastCoord.y );
340                 if ( !( firstPoint || i == line.size() - 1 || !draw[i + 1] ) &&  xDiff * xDiff + yDiff * yDiff <= 64 )
341                         continue;
342                 QPoint point( pos.x, pos.y );
343                 polygon.push_back( point );
344                 lastCoord = pos;
345                 firstPoint = false;
346         }
347         painter->drawPolyline( polygon.data(), polygon.size() );
348 }