Skip to content

Commit

Permalink
Drawing of complex X-Plane aprons accelerated by caching drawing path…
Browse files Browse the repository at this point in the history
…s in screen coordinates. Now from >100 ms to <1ms for large airports. #245
  • Loading branch information
albar965 committed Jul 23, 2018
1 parent b60ea44 commit 0e1624e
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 90 deletions.
6 changes: 4 additions & 2 deletions littlenavmap.pro
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ SOURCES += src/main.cpp\
src/search/searchbasetable.cpp \
src/mapgui/mapfunctions.cpp \
src/common/vehicleicons.cpp \
src/route/routeexport.cpp
src/route/routeexport.cpp \
src/mapgui/aprongeometrycache.cpp

HEADERS += src/gui/mainwindow.h \
src/search/columnlist.h \
Expand Down Expand Up @@ -337,7 +338,8 @@ HEADERS += src/gui/mainwindow.h \
src/search/searchbasetable.h \
src/mapgui/mapfunctions.h \
src/common/vehicleicons.h \
src/route/routeexport.h
src/route/routeexport.h \
src/mapgui/aprongeometrycache.h

FORMS += src/gui/mainwindow.ui \
src/db/databasedialog.ui \
Expand Down
3 changes: 2 additions & 1 deletion src/common/maptypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ struct MapRunwayEnd
/* Apron including full geometry */
struct MapApron
{
int apronId;
/* FSX/P3D simple geometry */
atools::geo::LineString vertices;

Expand All @@ -266,7 +267,7 @@ struct MapApron

int getId() const
{
return -1;
return apronId;
}

};
Expand Down
4 changes: 4 additions & 0 deletions src/gui/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "logging/loggingguiabort.h"
#include "query/airportquery.h"
#include "mapgui/mapwidget.h"
#include "mapgui/aprongeometrycache.h"
#include "profile/profilewidget.h"
#include "route/routecontroller.h"
#include "gui/filehistoryhandler.h"
Expand Down Expand Up @@ -247,6 +248,9 @@ MainWindow::MainWindow()

profileWidget->updateProfileShowFeatures();

// Initialize the X-Plane apron geometry cache
NavApp::getApronGeometryCache()->setViewportParams(NavApp::getMapWidget()->viewport());

loadNavmapLegend();
updateLegend();
updateWindowTitle();
Expand Down
187 changes: 187 additions & 0 deletions src/mapgui/aprongeometrycache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*****************************************************************************
* Copyright 2015-2018 Alexander Barthel albar965@mailbox.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/

#include "mapgui/aprongeometrycache.h"
#include "fs/common/xpgeometry.h"
#include "common/coordinateconverter.h"
#include "common/maptypes.h"

#include <QPainterPath>

// ======= Key ===============================================================
uint qHash(const ApronGeometryCache::Key& key)
{
return static_cast<uint>(key.apronId) ^ key.fast ^ static_cast<uint>(key.zoomDistanceMeter);
}

ApronGeometryCache::Key::Key(int apronIdParam, float zoomDistanceMeterParam, bool fastParam)
: apronId(apronIdParam), zoomDistanceMeter(static_cast<int>(zoomDistanceMeterParam)), fast(fastParam)
{

}

bool ApronGeometryCache::Key::operator==(const ApronGeometryCache::Key& other) const
{
return apronId == other.apronId && fast == other.fast && zoomDistanceMeter == other.zoomDistanceMeter;
}

bool ApronGeometryCache::Key::operator!=(const ApronGeometryCache::Key& other) const
{
return !(*this == other);
}

// ======= ApronGeometryCache ===============================================================
ApronGeometryCache::ApronGeometryCache()
: geometryCache(CACHE_SIZE)
{

}

ApronGeometryCache::~ApronGeometryCache()
{
delete converter;
}

void ApronGeometryCache::clear()
{
geometryCache.clear();
}

void ApronGeometryCache::setViewportParams(const Marble::ViewportParams *viewport)
{
if(converter != nullptr)
delete converter;

// Create a new converter for the viewport
converter = new CoordinateConverter(viewport);
}

QPainterPath ApronGeometryCache::getApronGeometry(const map::MapApron& apron, float zoomDistanceMeter, bool fast)
{
Q_ASSERT(converter != nullptr);

#if !defined(DEBUG_NO_XP_APRON_CACHE)

// Build key and get path from the cache
Key key(apron.apronId, zoomDistanceMeter, fast);
QPainterPath *painterPath = geometryCache.object(key);

if(painterPath != nullptr)
{
// Found - create a copy and translate it to the needed coordinates
QPainterPath boundaryPath(*painterPath);

// Calculate the coordinates of the reference point (first one) and move the whole path into required place for drawing
bool visible;
QPointF refPoint = converter->wToSF(apron.geometry.boundary.first().node,
CoordinateConverter::DEFAULT_WTOS_SIZE, &visible);
boundaryPath.translate(refPoint.x(), refPoint.y());

return boundaryPath;
}
else
#endif
{
// qDebug() << Q_FUNC_INFO << "Creating new apron";

// Nothing in cache - create the apron boundary
QPainterPath boundaryPath = pathForBoundary(apron.geometry.boundary, fast);

// Substract holes
for(atools::fs::common::Boundary hole : apron.geometry.holes)
boundaryPath = boundaryPath.subtracted(pathForBoundary(hole, fast));

#if !defined(DEBUG_NO_XP_APRON_CACHE)

// Calculate reference point (first)
bool visible;
QPointF refPoint = converter->wToSF(
apron.geometry.boundary.first().node, CoordinateConverter::DEFAULT_WTOS_SIZE, &visible);

// Create a copy for the cache
painterPath = new QPainterPath(boundaryPath);

// Move the whole path, so that the reference is at 0,0
painterPath->translate(-refPoint.x(), -refPoint.y());

// Insert path with reference 0,0
geometryCache.insert(key, painterPath);
#endif

// Return copy with correct position for drawing
return boundaryPath;
}
}

/* Calculate X-Plane aprons including bezier curves */
QPainterPath ApronGeometryCache::pathForBoundary(const atools::fs::common::Boundary& boundaryNodes, bool fast)
{
bool visible;
QPainterPath apronPath;
atools::fs::common::Node lastNode;

// Create a copy and close the geometry
atools::fs::common::Boundary boundary = boundaryNodes;

if(!boundary.isEmpty())
boundary.append(boundary.first());

int i = 0;
for(const atools::fs::common::Node& node : boundary)
{
QPointF lastPt = converter->wToSF(lastNode.node, CoordinateConverter::DEFAULT_WTOS_SIZE, &visible);
QPointF pt = converter->wToSF(node.node, CoordinateConverter::DEFAULT_WTOS_SIZE, &visible);

if(i == 0)
// First point
apronPath.moveTo(converter->wToS(node.node, CoordinateConverter::DEFAULT_WTOS_SIZE, &visible));
else if(fast)
// Use lines only for fast drawing
apronPath.lineTo(pt);
else
{
if(lastNode.control.isValid() && node.control.isValid())
{
// Two successive control points - use cubic curve
QPointF controlPoint1 = converter->wToSF(lastNode.control, CoordinateConverter::DEFAULT_WTOS_SIZE, &visible);
QPointF controlPoint2 = converter->wToSF(node.control, CoordinateConverter::DEFAULT_WTOS_SIZE, &visible);
apronPath.cubicTo(controlPoint1, pt + (pt - controlPoint2), pt);
}
else if(lastNode.control.isValid())
{
// One control point from last - use quad curve
if(lastPt != pt)
apronPath.quadTo(converter->wToSF(lastNode.control,
CoordinateConverter::DEFAULT_WTOS_SIZE, &visible), pt);
}
else if(node.control.isValid())
{
// One control point from current - use quad curve
if(lastPt != pt)
apronPath.quadTo(pt + (pt - converter->wToSF(node.control,
CoordinateConverter::DEFAULT_WTOS_SIZE, &visible)), pt);
}
else
// No control point - simple line
apronPath.lineTo(pt);
}

lastNode = node;
i++;
}
return apronPath;
}
85 changes: 85 additions & 0 deletions src/mapgui/aprongeometrycache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*****************************************************************************
* Copyright 2015-2018 Alexander Barthel albar965@mailbox.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/

#ifndef LNM_APRONGEOMETRYCACHE_H
#define LNM_APRONGEOMETRYCACHE_H

#include "fs/common/xpgeometry.h"

#include <QCache>
#include <QPainterPath>

class QPainterPath;
class CoordinateConverter;
namespace Marble {
class ViewportParams;
}
namespace map {
struct MapApron;

}

/*
* Caches the complex X-Plane apron geometry in screen coordinates by zoom level and draw fast flag.
*
* This will keep a copy of a QPainterPath of an apron for a given zoomlevel until the cache overflows.
*/
class ApronGeometryCache
{
public:
ApronGeometryCache();
~ApronGeometryCache();

/* Get apron geometry in screen coordinates from the cache or create it from map::MapApron.
* Combined key is apron ID, zoom and draw fast flag */
QPainterPath getApronGeometry(const map::MapApron& apron, float zoomDistanceMeter, bool fast);

/* Clear the cache */
void clear();

/* Has to be set before using it */
void setViewportParams(const Marble::ViewportParams *viewport);

private:
/* Cache key used to identify a QPainterPath for an apron */
struct Key
{
Key(int apronIdParam, float zoomDistanceMeterParam, bool fastParam);

int apronId;
int zoomDistanceMeter;
bool fast; /* Draw fast flag - no curves if true */

bool operator!=(const ApronGeometryCache::Key& other) const;
bool operator==(const ApronGeometryCache::Key& other) const;

};

friend uint qHash(const ApronGeometryCache::Key& key);

/* Calculate X-Plane aprons including bezier curves */
QPainterPath pathForBoundary(const atools::fs::common::Boundary& boundaryNodes, bool fast);

/* Some airport have more than 100 apron parts */
static const int CACHE_SIZE = 2000;

/* Used to convert world to screen coordinates */
CoordinateConverter *converter = nullptr;
QCache<Key, QPainterPath> geometryCache;
};

#endif // LNM_APRONGEOMETRYCACHE_H
Loading

0 comments on commit 0e1624e

Please sign in to comment.