diff --git a/examples/cpp/squad-interpolation/squad-interpolation.cpp b/examples/cpp/squad-interpolation/squad-interpolation.cpp index ee26a90db..d90c1836a 100644 --- a/examples/cpp/squad-interpolation/squad-interpolation.cpp +++ b/examples/cpp/squad-interpolation/squad-interpolation.cpp @@ -1,241 +1,241 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2014 Dennis Nienhüser // #include "squad-interpolation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Marble { MyPaintLayer::MyPaintLayer ( MarbleWidget *widget ) : m_widget ( widget ), m_fraction ( 0.0 ), m_delta( 0.02 ), m_index ( 0 ) { GeoDataCoordinates::Unit const degree = GeoDataCoordinates::Degree; m_cities << GeoDataCoordinates( 7.64573, 45.04981, 0.0, degree ); // Torino m_cities << GeoDataCoordinates( 8.33439, 49.01673, 0.0, degree ); // Karlsruhe m_cities << GeoDataCoordinates( 14.41637, 50.09329, 0.0, degree ); // Praha m_cities << GeoDataCoordinates( 15.97254, 45.80268, 0.0, degree ); // Zagred addInterpolatedPoint(); } QStringList MyPaintLayer::renderPosition() const { return QStringList(QStringLiteral("USER_TOOLS")); } bool MyPaintLayer::render ( GeoPainter *painter, ViewportParams *viewport, const QString &, GeoSceneLayer * ) { if ( m_index < 20 ) { // Gray dotted line connects all current cities QPen grayPen = Marble::Oxygen::aluminumGray4; grayPen.setWidth ( 3 ); grayPen.setStyle ( Qt::DotLine ); painter->setPen ( grayPen ); painter->drawPolyline ( m_cities ); } // Blue circle around each city painter->setBrush ( QBrush ( QColor ( Marble::Oxygen::skyBlue4 ) ) ); painter->setPen ( QColor ( Marble::Oxygen::aluminumGray4 ) ); for ( int i = 0; i < m_cities.size(); ++i ) { painter->drawEllipse ( m_cities[i], 32, 32 ); } if (m_index < 10) { // Show how squad interpolation works internally Q_ASSERT( m_cities.size() == 4 ); painter->setBrush ( QBrush ( QColor ( Marble::Oxygen::grapeViolet4 ) ) ); painter->setPen ( QColor ( Marble::Oxygen::aluminumGray4 ) ); GeoDataCoordinates a2 = basePoint( m_cities[0], m_cities[1], m_cities[2] ); painter->drawEllipse ( a2, 8, 8 ); qreal x, y; if ( viewport->screenCoordinates ( a2, x, y ) ) { painter->drawText(x+5, y, QStringLiteral("A")); } GeoDataCoordinates b1 = basePoint( m_cities[1], m_cities[2], m_cities[3] ); painter->drawEllipse ( b1, 8, 8 ); if ( viewport->screenCoordinates ( b1, x, y ) ) { painter->drawText(x+5, y, QStringLiteral("B")); } QPen grapePen = Marble::Oxygen::grapeViolet4; grapePen.setWidth ( 2 ); painter->setPen ( grapePen ); GeoDataLineString string; string << m_cities[0] << a2 << b1 << m_cities[3]; painter->drawPolyline ( string ); GeoDataCoordinates i1 = m_cities[1].interpolate( m_cities[2], m_fraction-m_delta ); GeoDataCoordinates i2 = a2.interpolate( b1, m_fraction-m_delta ); QPen raspberryPen = Marble::Oxygen::burgundyPurple4; raspberryPen.setWidth ( 2 ); painter->setPen ( raspberryPen ); GeoDataLineString inter; inter << i1 << i2; painter->drawPolyline ( inter ); } // Green linestring shows interpolation path QPen greenPen = Marble::Oxygen::forestGreen4; greenPen.setWidth ( 3 ); painter->setPen ( greenPen ); painter->drawPolyline ( m_interpolated, QStringLiteral("Squad\nInterpolation"), LineEnd ); // Increasing city indices with some transparency effect for readability QFont font = painter->font(); font.setBold( true ); painter->setFont( font ); QColor blue = QColor ( Marble::Oxygen::skyBlue4 ); blue.setAlpha( 150 ); painter->setBrush ( QBrush ( blue ) ); int const h = painter->fontMetrics().height(); for ( int i = 0; i < m_cities.size(); ++i ) { qreal x, y; QString const text = QString::number ( m_index + i ); int const w = painter->fontMetrics().width( text ); painter->setPen ( Qt::NoPen ); painter->drawEllipse ( m_cities[i], 1.5*w, 1.5*h ); painter->setPen ( QColor ( Marble::Oxygen::aluminumGray4 ) ); if ( viewport->screenCoordinates ( m_cities[i], x, y ) ) { painter->drawText ( x-w/2, y+h/3, text ); } } return true; } GeoDataLatLonBox MyPaintLayer::center() const { GeoDataLinearRing ring; foreach( const GeoDataCoordinates &city, m_cities ) { ring << city; } return ring.latLonAltBox(); } void MyPaintLayer::addRandomCity ( double minDistance, double maxDistance ) { minDistance *= KM2METER; maxDistance *= KM2METER; GeoDataTreeModel* tree = m_widget->model()->treeModel(); if ( !tree || tree->rowCount() < 6 || m_cities.isEmpty() ) { return; } // Traverse Marble's internal city database and add a random one // which is in the requested distance range to the last one for ( int i = 0; i < tree->rowCount(); ++i ) { QVariant const data = tree->data ( tree->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole ); GeoDataObject *object = qvariant_cast ( data ); Q_ASSERT ( object ); if (const auto document = geodata_cast(object)) { if (document->name() == QLatin1String("cityplacemarks")) { QVector placemarks = document->placemarkList(); for ( int i = qrand() % placemarks.size(); i < placemarks.size(); ++i ) { - double const distance = EARTH_RADIUS * distanceSphere ( m_cities.last(), placemarks[i]->coordinate() ); + const double distance = EARTH_RADIUS * m_cities.last().sphericalDistanceTo(placemarks[i]->coordinate()); if ( distance >= minDistance && distance <= maxDistance ) { m_cities << placemarks[i]->coordinate(); return; } } } } } addRandomCity(); } GeoDataCoordinates MyPaintLayer::basePoint( const GeoDataCoordinates &c1, const GeoDataCoordinates &c2, const GeoDataCoordinates &c3 ) { Quaternion const a = (c2.quaternion().inverse() * c3.quaternion()).log(); Quaternion const b = (c2.quaternion().inverse() * c1.quaternion()).log(); Quaternion const c = c2.quaternion() * ((a+b)*-0.25).exp(); qreal lon, lat; c.getSpherical( lon, lat ); return GeoDataCoordinates( lon, lat ); } void MyPaintLayer::addInterpolatedPoint() { while ( m_interpolated.size() > 2.0/m_delta ) { m_interpolated.remove ( 0 ); } m_delta = m_index < 20 ? 0.01 : 0.04; Q_ASSERT ( m_cities.size() == 4 ); // Interpolate for the current city m_interpolated << m_cities[1].interpolate ( m_cities[0], m_cities[2], m_cities[3], m_fraction ); m_fraction += m_delta; // If current city is done, move one forward if ( m_fraction > 1.0 ) { m_fraction = 0.0; m_cities.remove ( 0 ); addRandomCity(); ++m_index; } // Repaint map, recenter if out of view bool hidden; qreal x; qreal y; if ( m_widget->viewport()->screenCoordinates ( m_interpolated.last(), x, y, hidden ) ) { m_widget->update(); } else { m_widget->centerOn ( center() ); } int const timeout = qBound( 0, 150 - 50 * m_index, 150 ); QTimer::singleShot ( timeout, this, SLOT (addInterpolatedPoint()) ); } } int main ( int argc, char** argv ) { using namespace Marble; QApplication app ( argc, argv ); MarbleWidget *mapWidget = new MarbleWidget; mapWidget->setWindowTitle(QStringLiteral("Marble - Squad Interpolation")); // Create and register our paint layer MyPaintLayer* layer = new MyPaintLayer ( mapWidget ); mapWidget->addLayer ( layer ); mapWidget->centerOn ( layer->center() ); // Finish widget creation. mapWidget->setMapThemeId(QStringLiteral("earth/plain/plain.dgml")); mapWidget->setShowCities( false ); mapWidget->setShowCrosshairs( false ); mapWidget->setShowOtherPlaces( false ); mapWidget->setShowPlaces( false ); mapWidget->setShowTerrain( false ); mapWidget->show(); return app.exec(); } #include "moc_squad-interpolation.cpp" diff --git a/src/lib/marble/AutoNavigation.cpp b/src/lib/marble/AutoNavigation.cpp index a2c109b55..e4cf8efff 100644 --- a/src/lib/marble/AutoNavigation.cpp +++ b/src/lib/marble/AutoNavigation.cpp @@ -1,390 +1,390 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2010 Siddharth Srivastava // Copyright 2011 Bernhard Beschow // #include "AutoNavigation.h" #include "GeoDataCoordinates.h" #include "PositionTracking.h" #include "MarbleDebug.h" #include "MarbleModel.h" #include "MarbleMath.h" #include "ViewportParams.h" #include "MarbleGlobal.h" #include "RoutingManager.h" #include "RoutingModel.h" #include "Route.h" #include #include #include #include #include namespace Marble { class Q_DECL_HIDDEN AutoNavigation::Private { public: AutoNavigation *const m_parent; const MarbleModel *const m_model; const ViewportParams *const m_viewport; const PositionTracking *const m_tracking; AutoNavigation::CenterMode m_recenterMode; bool m_adjustZoom; QTimer m_lastWidgetInteraction; bool m_selfInteraction; /** Constructor */ Private( MarbleModel *model, const ViewportParams *viewport, AutoNavigation *parent ); /** * @brief To center on when reaching custom defined border * @param position current gps location * @param speed optional speed argument */ void moveOnBorderToCenter( const GeoDataCoordinates &position, qreal speed ); /** * For calculating intersection point of projected LineString from * current gps location with the map border * @param position current gps location */ GeoDataCoordinates findIntersection( qreal currentX, qreal currentY ) const; /** * @brief Adjust the zoom value of the map * @param currentPosition current location of the gps device */ void adjustZoom( const GeoDataCoordinates ¤tPosition, qreal speed ); /** * Center the widget on the given position unless recentering is currently inhibited */ void centerOn( const GeoDataCoordinates &position ); }; AutoNavigation::Private::Private( MarbleModel *model, const ViewportParams *viewport, AutoNavigation *parent ) : m_parent( parent ), m_model( model ), m_viewport( viewport ), m_tracking( model->positionTracking() ), m_recenterMode( AutoNavigation::DontRecenter ), m_adjustZoom( 0 ), m_selfInteraction( false ) { m_lastWidgetInteraction.setInterval( 10 * 1000 ); m_lastWidgetInteraction.setSingleShot( true ); } void AutoNavigation::Private::moveOnBorderToCenter( const GeoDataCoordinates &position, qreal ) { qreal x = 0.0; qreal y = 0.0; //recenter if initially the gps location is not visible on the screen if(!( m_viewport->screenCoordinates( position, x, y ) ) ) { centerOn( position ); } qreal centerLon = m_viewport->centerLongitude(); qreal centerLat = m_viewport->centerLatitude(); qreal centerX = 0.0; qreal centerY = 0.0; m_viewport->screenCoordinates( centerLon, centerLat, centerX, centerY ); const qreal borderRatio = 0.25; //defining the default border distance from map center int shiftX = qRound( centerX * borderRatio ); int shiftY = qRound( centerY * borderRatio ); QRect recenterBorderBound; recenterBorderBound.setCoords( centerX-shiftX, centerY-shiftY, centerX+shiftX, centerY+shiftY ); if( !recenterBorderBound.contains( x,y ) ) { centerOn( position ); } } GeoDataCoordinates AutoNavigation::Private::findIntersection( qreal currentX, qreal currentY ) const { qreal direction = m_tracking->direction(); if ( direction >= 360 ) { direction = fmod( direction,360.0 ); } const qreal width = m_viewport->width(); const qreal height = m_viewport->height(); QPointF intercept; QPointF destinationHorizontal; QPointF destinationVertical; QPointF destination; bool crossHorizontal = false; bool crossVertical = false; //calculation of intersection point if( 0 < direction && direction < 90 ) { const qreal angle = direction; //Intersection with line x = width intercept.setX( width - currentX ); intercept.setY( intercept.x() / tan( angle ) ); destinationVertical.setX( width ); destinationVertical.setY( currentY-intercept.y() ); //Intersection with line y = 0 intercept.setY( currentY ); intercept.setX( intercept.y() * tan( angle ) ); destinationHorizontal.setX( currentX + intercept.x() ); destinationHorizontal.setY( 0 ); if ( destinationVertical.y() < 0 ) { crossHorizontal = true; } else if( destinationHorizontal.x() > width ) { crossVertical = true; } } else if( 270 < direction && direction < 360 ) { const qreal angle = direction - 270; //Intersection with line y = 0 intercept.setY( currentY ); intercept.setX( intercept.y() / tan( angle ) ); destinationHorizontal.setX( currentX - intercept.x() ); destinationHorizontal.setY( 0 ); //Intersection with line x = 0 intercept.setX( currentX ); intercept.setY( intercept.x() * tan( angle ) ); destinationVertical.setY( currentY - intercept.y() ); destinationVertical.setX( 0 ); if( destinationHorizontal.x() > width ) { crossVertical = true; } else if( destinationVertical.y() < 0 ) { crossHorizontal = true; } } else if( 180 < direction && direction < 270 ) { const qreal angle = direction - 180; //Intersection with line x = 0 intercept.setX( currentX ); intercept.setY( intercept.x() / tan( angle ) ); destinationVertical.setY( currentY + intercept.y() ); destinationVertical.setX( 0 ); //Intersection with line y = height intercept.setY( currentY ); intercept.setX( intercept.y() * tan( angle ) ); destinationHorizontal.setX( currentX - intercept.x() ); destinationHorizontal.setY( height ); if ( destinationVertical.y() > height ) { crossHorizontal = true; } else if ( destinationHorizontal.x() < 0 ) { crossVertical = true; } } else if( 90 < direction && direction < 180 ) { const qreal angle = direction - 90; //Intersection with line y = height intercept.setY( height - currentY ); intercept.setX( intercept.y() / tan( angle ) ); destinationHorizontal.setX( currentX + intercept.x() ); destinationHorizontal.setY( height ); //Intersection with line x = width intercept.setX( width - currentX ); intercept.setY( intercept.x() * tan( angle ) ); destinationVertical.setX( width ); destinationVertical.setY( currentY + intercept.y() ); if ( destinationHorizontal.x() > width ) { crossVertical = true; } else if( destinationVertical.y() > height ) { crossHorizontal = true; } } else if( direction == 0 ) { destinationHorizontal.setX( currentX ); destinationHorizontal.setY( 0 ); crossHorizontal = true; } else if( direction == 90 ) { destinationVertical.setX( width ); destinationVertical.setY( currentY ); crossVertical = true; } else if( direction == 190 ) { destinationHorizontal.setX( currentX ); destinationHorizontal.setY( height ); crossHorizontal = true; } else if( direction == 270 ) { destinationVertical.setX( 0 ); destinationVertical.setY( currentY ); crossVertical = true; } if ( crossHorizontal == true && crossVertical == false ) { destination.setX( destinationHorizontal.x() ); destination.setY( destinationHorizontal.y() ); } else if ( crossVertical == true && crossHorizontal == false ) { destination.setX( destinationVertical.x() ); destination.setY( destinationVertical.y() ); } qreal destinationLon = 0.0; qreal destinationLat = 0.0; m_viewport->geoCoordinates( destination.x(), destination.y(), destinationLon, destinationLat, GeoDataCoordinates::Radian ); GeoDataCoordinates destinationCoord( destinationLon, destinationLat, GeoDataCoordinates::Radian ); return destinationCoord; } void AutoNavigation::Private::adjustZoom( const GeoDataCoordinates ¤tPosition, qreal speed ) { qreal currentX = 0; qreal currentY = 0; if( !m_viewport->screenCoordinates(currentPosition, currentX, currentY ) ) { return; } const GeoDataCoordinates destination = findIntersection( currentX, currentY ); - qreal greatCircleDistance = distanceSphere( currentPosition, destination ); + const qreal greatCircleDistance = currentPosition.sphericalDistanceTo(destination); qreal radius = m_model->planetRadius(); qreal distance = greatCircleDistance * radius; if( speed != 0 ) { // time (in seconds) remaining to reach the border of the map qreal remainingTime = distance / speed; // tolerance time limits (in seconds) before auto zooming qreal thresholdLow = 15; qreal thresholdHigh = 120; m_selfInteraction = true; if ( remainingTime < thresholdLow ) { emit m_parent->zoomOut( Instant ); } else if ( remainingTime > thresholdHigh ) { emit m_parent->zoomIn( Instant ); } m_selfInteraction = false; } } void AutoNavigation::Private::centerOn( const GeoDataCoordinates &position ) { m_selfInteraction = true; RoutingManager const * routingManager = m_model->routingManager(); RoutingModel const * routingModel = routingManager->routingModel(); if (!routingManager->guidanceModeEnabled() || routingModel->deviatedFromRoute()){ emit m_parent->centerOn( position, false ); } else { GeoDataCoordinates positionOnRoute = routingModel->route().positionOnRoute(); emit m_parent->centerOn( positionOnRoute, false ); } m_selfInteraction = false; } AutoNavigation::AutoNavigation( MarbleModel *model, const ViewportParams *viewport, QObject *parent ) : QObject( parent ), d( new AutoNavigation::Private( model, viewport, this ) ) { connect( d->m_tracking, SIGNAL(gpsLocation(GeoDataCoordinates,qreal)), this, SLOT(adjust(GeoDataCoordinates,qreal)) ); } AutoNavigation::~AutoNavigation() { delete d; } void AutoNavigation::adjust( const GeoDataCoordinates &position, qreal speed ) { if ( d->m_lastWidgetInteraction.isActive() ) { return; } switch( d->m_recenterMode ) { case DontRecenter: /* nothing to do */ break; case AlwaysRecenter: d->centerOn( position ); break; case RecenterOnBorder: d->moveOnBorderToCenter( position, speed ); break; } if ( d->m_adjustZoom ) { switch( d->m_recenterMode ) { case DontRecenter: /* nothing to do */ break; case AlwaysRecenter: case RecenterOnBorder: // fallthrough d->adjustZoom( position, speed ); break; } } } void AutoNavigation::setAutoZoom( bool autoZoom ) { d->m_adjustZoom = autoZoom; emit autoZoomToggled( autoZoom ); } void AutoNavigation::setRecenter( CenterMode recenterMode ) { d->m_recenterMode = recenterMode; emit recenterModeChanged( recenterMode ); } void AutoNavigation::inhibitAutoAdjustments() { if ( !d->m_selfInteraction ) { d->m_lastWidgetInteraction.start(); } } AutoNavigation::CenterMode AutoNavigation::recenterMode() const { return d->m_recenterMode; } bool AutoNavigation::autoZoom() const { return d->m_adjustZoom; } } // namespace Marble #include "moc_AutoNavigation.cpp" diff --git a/src/lib/marble/MarbleMath.h b/src/lib/marble/MarbleMath.h index 240c07268..574487187 100644 --- a/src/lib/marble/MarbleMath.h +++ b/src/lib/marble/MarbleMath.h @@ -1,129 +1,111 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Torsten Rahn // Copyright 2007 Inge Wallin // #ifndef MARBLE_MARBLEMATH_H #define MARBLE_MARBLEMATH_H #include -#include "GeoDataCoordinates.h" #include namespace { const qreal a1 = 1.0/6.0; const qreal a2 = 1.0/24.0; const qreal a3 = 61.0/5040; const qreal a4 = 277.0/72576.0; const qreal a5 = 50521.0/39916800.0; const qreal a6 = 41581.0/95800320.0; const qreal a7 = 199360981.0/1307674368000.0; const qreal a8 = 228135437.0/4184557977600.0; const qreal a9 = 2404879675441.0/121645100408832000.0; const qreal a10 = 14814847529501.0/2043637686868377600.0; const qreal a11 = 69348874393137901.0/25852016738884976640000.0; const qreal a12 = 238685140977801337.0/238634000666630553600000.0; const qreal a13 = 4087072509293123892361.0/10888869450418352160768000000.0; const qreal a14 = 454540704683713199807.0/3209350995912777478963200000.0; const qreal a15 = 441543893249023104553682821.0/8222838654177922817725562880000000.0; const qreal a16 = 2088463430347521052196056349.0/102156677868375135241390522368000000.0; } namespace Marble { /** * @brief This method calculates the shortest distance between two points on a sphere. * @brief See: http://en.wikipedia.org/wiki/Great-circle_distance * @param lon1 longitude of first point in radians * @param lat1 latitude of first point in radians * @param lon2 longitude of second point in radians * @param lat2 latitude of second point in radians */ inline qreal distanceSphere( qreal lon1, qreal lat1, qreal lon2, qreal lat2 ) { qreal h1 = sin( 0.5 * ( lat2 - lat1 ) ); qreal h2 = sin( 0.5 * ( lon2 - lon1 ) ); qreal d = h1 * h1 + cos( lat1 ) * cos( lat2 ) * h2 * h2; return 2.0 * atan2( sqrt( d ), sqrt( 1.0 - d ) ); } -/** - * @brief This method calculates the shortest distance between two points on a sphere. - * @brief See: http://en.wikipedia.org/wiki/Great-circle_distance - */ -inline qreal distanceSphere( const GeoDataCoordinates& coords1, const GeoDataCoordinates& coords2 ) { - - qreal lon1, lat1; - coords1.geoCoordinates( lon1, lat1 ); - qreal lon2, lat2; - coords2.geoCoordinates( lon2, lat2 ); - - // FIXME: Take the altitude into account! - - return distanceSphere( lon1, lat1, lon2, lat2 ); -} - - /** * @brief This method roughly calculates the shortest distance between two points on a sphere. * @brief It's probably faster than distanceSphere(...) but for 7 significant digits only has * @brief an accuracy of about 1 arcmin. * @brief See: http://en.wikipedia.org/wiki/Great-circle_distance */ inline qreal distanceSphereApprox( qreal lon1, qreal lat1, qreal lon2, qreal lat2 ) { return acos( sin( lat1 ) * sin( lat2 ) + cos( lat1 ) * cos( lat2 ) * cos( lon1 - lon2 ) ); } /** * @brief This method is a fast Mac Laurin power series approximation of the * @brief inverse Gudermannian. The inverse Gudermannian gives the vertical * @brief position y in the Mercator projection in terms of the latitude. * @brief See: http://en.wikipedia.org/wiki/Mercator_projection */ inline qreal gdInv( qreal x ) { const qreal x2 = x * x; return x + x * x2 * ( a1 + x2 * ( a2 + x2 * ( a3 + x2 * ( a4 + x2 * ( a5 + x2 * ( a6 + x2 * ( a7 + x2 * ( a8 + x2 * ( a9 + x2 * ( a10 + x2 * ( a11 + x2 * ( a12 + x2 * ( a13 + x2 * ( a14 + x2 * ( a15 + x2 * ( a16 ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ); } } /** * @brief This method is a fast Mac Laurin power series approximation of the * Gudermannian. The Gudermannian gives the latitude * in the Mercator projection in terms of the vertical position y. * See: http://en.wikipedia.org/wiki/Mercator_projection */ inline qreal gd( qreal x ) { /* const qreal x2 = x * x; return x - x * x2 * ( a1 - x2 * ( a2 - x2 * ( a3 - x2 * ( a4 - x2 * ( a5 - x2 * ( a6 - x2 * ( a7 - x2 * ( a8 - x2 * ( a9 - x2 * ( a10 - x2 * ( a11 - x2 * ( a12 - x2 * ( a13 - x2 * ( a14 - x2 * ( a15 - x2 * ( a16 ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ); */ return atan ( sinh ( x ) ); } #endif diff --git a/src/lib/marble/PlacemarkPositionProviderPlugin.cpp b/src/lib/marble/PlacemarkPositionProviderPlugin.cpp index 8f1077f9d..b93e688da 100644 --- a/src/lib/marble/PlacemarkPositionProviderPlugin.cpp +++ b/src/lib/marble/PlacemarkPositionProviderPlugin.cpp @@ -1,189 +1,189 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Guillaume Martres // Copyright 2011,2012 Bernhard Beschow // #include "PlacemarkPositionProviderPlugin.h" #include "GeoDataPlacemark.h" #include "MarbleClock.h" #include "MarbleMath.h" #include "MarbleModel.h" #include "MarbleDebug.h" #include using namespace Marble; PlacemarkPositionProviderPlugin::PlacemarkPositionProviderPlugin( MarbleModel *marbleModel, QObject* parent ) : PositionProviderPlugin(parent), m_marbleModel( marbleModel ), m_placemark( 0 ), m_speed( 0 ), m_direction( 0.0 ), m_status( PositionProviderStatusUnavailable ), m_isInitialized( false ) { m_accuracy.level = GeoDataAccuracy::Detailed; } QString PlacemarkPositionProviderPlugin::name() const { return tr( "Placemark position provider Plugin" ); } QString PlacemarkPositionProviderPlugin::nameId() const { return QStringLiteral("Placemark"); } QString PlacemarkPositionProviderPlugin::guiString() const { return tr( "Placemark" ); } QString PlacemarkPositionProviderPlugin::version() const { return QStringLiteral("1.0"); } QString PlacemarkPositionProviderPlugin::description() const { return tr( "Reports the position of a placemark" ); } QString PlacemarkPositionProviderPlugin::copyrightYears() const { return QStringLiteral("2011, 2012"); } QVector PlacemarkPositionProviderPlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Guillaume Martres"), QStringLiteral("smarter@ubuntu.com")) << PluginAuthor(QStringLiteral("Bernhard Beschow"), QStringLiteral("bbeschow@cs.tu-berlin.de")); } QIcon PlacemarkPositionProviderPlugin::icon() const { return QIcon(); } void PlacemarkPositionProviderPlugin::initialize() { if ( m_marbleModel ) { setPlacemark( m_marbleModel->trackedPlacemark() ); connect( m_marbleModel, SIGNAL(trackedPlacemarkChanged(const GeoDataPlacemark*)), this, SLOT(setPlacemark(const GeoDataPlacemark*)) ); } else { mDebug() << "PlacemarkPositionProviderPlugin: MarbleModel not set, cannot track placemarks."; } m_isInitialized = true; } bool PlacemarkPositionProviderPlugin::isInitialized() const { return m_isInitialized; } PositionProviderPlugin* PlacemarkPositionProviderPlugin::newInstance() const { return new PlacemarkPositionProviderPlugin( m_marbleModel ); } PositionProviderStatus PlacemarkPositionProviderPlugin::status() const { return m_status; } GeoDataCoordinates PlacemarkPositionProviderPlugin::position() const { return m_coordinates; } GeoDataAccuracy PlacemarkPositionProviderPlugin::accuracy() const { return m_accuracy; } qreal PlacemarkPositionProviderPlugin::speed() const { return m_speed; } qreal PlacemarkPositionProviderPlugin::direction() const { return m_direction; } QDateTime PlacemarkPositionProviderPlugin::timestamp() const { return m_marbleModel->clockDateTime(); } void PlacemarkPositionProviderPlugin::setPlacemark( const GeoDataPlacemark *placemark ) { const GeoDataPlacemark *const oldPlacemark = m_placemark; if ( oldPlacemark != 0 ) { emit statusChanged( PositionProviderStatusUnavailable ); } m_placemark = placemark; m_timestamp = placemark ? m_marbleModel->clockDateTime() : QDateTime(); GeoDataCoordinates const newCoordinates = placemark ? placemark->coordinate( m_timestamp ) : GeoDataCoordinates(); if ( m_coordinates.isValid() && newCoordinates.isValid() ) { m_direction = m_coordinates.bearing( newCoordinates, GeoDataCoordinates::Degree, GeoDataCoordinates::FinalBearing ); } m_coordinates = newCoordinates; m_status = placemark ? PositionProviderStatusAvailable : PositionProviderStatusUnavailable; m_speed = 0.0; disconnect( m_marbleModel->clock(), SIGNAL(timeChanged()), this, SLOT(updatePosition()) ); if ( placemark ) { connect( m_marbleModel->clock(), SIGNAL(timeChanged()), this, SLOT(updatePosition()) ); } if ( oldPlacemark != m_placemark && m_placemark != 0 ) { emit statusChanged( m_status ); } if ( m_status == PositionProviderStatusAvailable ) { emit positionChanged( m_coordinates, m_accuracy ); } } void PlacemarkPositionProviderPlugin::updatePosition() { if ( m_placemark == 0 ) { return; } Q_ASSERT( m_marbleModel && "MarbleModel missing in PlacemarkPositionProviderPlugin" ); const GeoDataCoordinates previousCoordinates = m_coordinates; m_coordinates = m_placemark->coordinate( m_marbleModel->clock()->dateTime() ); m_direction = previousCoordinates.bearing( m_coordinates, GeoDataCoordinates::Degree, GeoDataCoordinates::FinalBearing ); if ( m_timestamp.isValid() ) { const qreal averageAltitude = ( m_coordinates.altitude() + m_coordinates.altitude() ) / 2.0 + m_marbleModel->planetRadius(); - const qreal distance = distanceSphere( previousCoordinates, m_coordinates ) * averageAltitude; + const qreal distance = previousCoordinates.sphericalDistanceTo(m_coordinates) * averageAltitude; const qreal seconds = m_timestamp.msecsTo( m_marbleModel->clockDateTime() ) / 1000.0; m_speed = ( seconds > 0 ) ? ( distance / seconds ) : 0; } else { m_speed = 0; } m_timestamp = m_marbleModel->clockDateTime(); emit positionChanged( m_coordinates, m_accuracy ); } #include "moc_PlacemarkPositionProviderPlugin.cpp" diff --git a/src/lib/marble/PositionTracking.cpp b/src/lib/marble/PositionTracking.cpp index 60c9f59a6..6f4854ee5 100644 --- a/src/lib/marble/PositionTracking.cpp +++ b/src/lib/marble/PositionTracking.cpp @@ -1,399 +1,399 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2007 Andrew Manson // Copyright 2009 Eckhart Wörner // Copyright 2010 Thibaut Gridel // #include "PositionTracking.h" #include "GeoDataDocument.h" #include "GeoDataMultiTrack.h" #include "GeoDataPlacemark.h" #include "GeoDataParser.h" #include "GeoDataStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataStyleMap.h" #include "GeoDataTrack.h" #include "GeoDataTreeModel.h" #include "GeoDataLineString.h" #include "GeoDataAccuracy.h" #include "GeoDataDocumentWriter.h" #include "KmlElementDictionary.h" #include "FileManager.h" #include "MarbleMath.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "PositionProviderPlugin.h" #include namespace Marble { class PositionTrackingPrivate { public: PositionTrackingPrivate( GeoDataTreeModel *model, PositionTracking *parent ) : q( parent ), m_treeModel( model ), m_currentPositionPlacemark( new GeoDataPlacemark ), m_currentTrackPlacemark( new GeoDataPlacemark ), m_trackSegments( new GeoDataMultiTrack ), m_document(), m_currentTrack( 0 ), m_positionProvider( 0 ), m_length( 0.0 ) { } void updatePosition(); void updateStatus(); static QString statusFile(); PositionTracking *const q; GeoDataTreeModel *const m_treeModel; GeoDataPlacemark *const m_currentPositionPlacemark; GeoDataPlacemark *m_currentTrackPlacemark; GeoDataMultiTrack *m_trackSegments; GeoDataDocument m_document; GeoDataCoordinates m_gpsPreviousPosition; GeoDataTrack *m_currentTrack; PositionProviderPlugin* m_positionProvider; qreal m_length; }; void PositionTrackingPrivate::updatePosition() { Q_ASSERT( m_positionProvider != 0 ); const GeoDataAccuracy accuracy = m_positionProvider->accuracy(); const GeoDataCoordinates position = m_positionProvider->position(); const QDateTime timestamp = m_positionProvider->timestamp(); if ( m_positionProvider->status() == PositionProviderStatusAvailable ) { if ( accuracy.horizontal < 250 ) { if ( m_currentTrack->size() ) { - m_length += distanceSphere( m_currentTrack->coordinatesAt( m_currentTrack->size() - 1 ), position ); + m_length += m_currentTrack->coordinatesAt(m_currentTrack->size() - 1).sphericalDistanceTo(position); } m_currentTrack->addPoint( timestamp, position ); } //if the position has moved then update the current position if ( m_gpsPreviousPosition != position ) { m_currentPositionPlacemark->setCoordinate( position ); qreal speed = m_positionProvider->speed(); emit q->gpsLocation( position, speed ); } } } void PositionTrackingPrivate::updateStatus() { Q_ASSERT( m_positionProvider != 0 ); const PositionProviderStatus status = m_positionProvider->status(); if (status == PositionProviderStatusAvailable) { m_currentTrack = new GeoDataTrack; m_treeModel->removeFeature( m_currentTrackPlacemark ); m_trackSegments->append( m_currentTrack ); m_treeModel->addFeature( &m_document, m_currentTrackPlacemark ); } emit q->statusChanged( status ); } QString PositionTrackingPrivate::statusFile() { QString const subdir = "tracking"; QDir dir( MarbleDirs::localPath() ); if ( !dir.exists( subdir ) ) { if ( !dir.mkdir( subdir ) ) { mDebug() << "Unable to create dir " << dir.absoluteFilePath( subdir ); return dir.absolutePath(); } } if ( !dir.cd( subdir ) ) { mDebug() << "Cannot change into " << dir.absoluteFilePath( subdir ); } return dir.absoluteFilePath( "track.kml" ); } PositionTracking::PositionTracking( GeoDataTreeModel *model ) : QObject( model ), d( new PositionTrackingPrivate( model, this ) ) { d->m_document.setDocumentRole( TrackingDocument ); d->m_document.setName(QStringLiteral("Position Tracking")); // First point is current position d->m_currentPositionPlacemark->setName(QStringLiteral("Current Position")); d->m_currentPositionPlacemark->setVisible(false); d->m_document.append( d->m_currentPositionPlacemark ); // Second point is position track d->m_currentTrack = new GeoDataTrack; d->m_trackSegments->append(d->m_currentTrack); d->m_currentTrackPlacemark->setGeometry(d->m_trackSegments); d->m_currentTrackPlacemark->setName(QStringLiteral("Current Track")); GeoDataStyle::Ptr style(new GeoDataStyle); GeoDataLineStyle lineStyle; QColor transparentRed = Oxygen::brickRed4; transparentRed.setAlpha( 200 ); lineStyle.setColor( transparentRed ); lineStyle.setWidth( 4 ); style->setLineStyle(lineStyle); style->setId(QStringLiteral("track")); GeoDataStyleMap styleMap; styleMap.setId(QStringLiteral("map-track")); styleMap.insert(QStringLiteral("normal"), QLatin1Char('#') + style->id()); d->m_document.addStyleMap(styleMap); d->m_document.addStyle(style); d->m_document.append( d->m_currentTrackPlacemark ); d->m_currentTrackPlacemark->setStyleUrl(QLatin1Char('#') + styleMap.id()); d->m_treeModel->addDocument( &d->m_document ); } PositionTracking::~PositionTracking() { d->m_treeModel->removeDocument( &d->m_document ); delete d; } void PositionTracking::setPositionProviderPlugin( PositionProviderPlugin* plugin ) { const PositionProviderStatus oldStatus = status(); if ( d->m_positionProvider ) { delete d->m_positionProvider; } d->m_positionProvider = plugin; if ( d->m_positionProvider ) { d->m_positionProvider->setParent( this ); mDebug() << "Initializing position provider:" << d->m_positionProvider->name(); connect( d->m_positionProvider, SIGNAL(statusChanged(PositionProviderStatus)), this, SLOT(updateStatus()) ); connect( d->m_positionProvider, SIGNAL(positionChanged(GeoDataCoordinates,GeoDataAccuracy)), this, SLOT(updatePosition()) ); d->m_positionProvider->initialize(); } emit positionProviderPluginChanged( plugin ); if ( oldStatus != status() ) { emit statusChanged( status() ); } if ( status() == PositionProviderStatusAvailable ) { emit gpsLocation( d->m_positionProvider->position(), d->m_positionProvider->speed() ); } } PositionProviderPlugin* PositionTracking::positionProviderPlugin() { return d->m_positionProvider; } QString PositionTracking::error() const { return d->m_positionProvider ? d->m_positionProvider->error() : QString(); } //get speed from provider qreal PositionTracking::speed() const { return d->m_positionProvider ? d->m_positionProvider->speed() : 0 ; } //get direction from provider qreal PositionTracking::direction() const { return d->m_positionProvider ? d->m_positionProvider->direction() : 0 ; } QDateTime PositionTracking::timestamp() const { return d->m_positionProvider ? d->m_positionProvider->timestamp() : QDateTime(); } bool PositionTracking::trackVisible() const { return d->m_currentTrackPlacemark->isVisible(); } void PositionTracking::setTrackVisible( bool visible ) { d->m_currentTrackPlacemark->setVisible( visible ); d->m_treeModel->updateFeature( d->m_currentTrackPlacemark ); } bool PositionTracking::saveTrack( const QString& fileName ) { if ( fileName.isEmpty() ) { return false; } GeoDataDocument *document = new GeoDataDocument; QFileInfo fileInfo( fileName ); QString name = fileInfo.baseName(); document->setName( name ); for( const GeoDataStyle::Ptr &style: d->m_document.styles() ) { document->addStyle( style ); } for( const GeoDataStyleMap &map: d->m_document.styleMaps() ) { document->addStyleMap( map ); } GeoDataPlacemark *track = new GeoDataPlacemark( *d->m_currentTrackPlacemark ); track->setName(QLatin1String("Track ") + name); document->append( track ); bool const result = GeoDataDocumentWriter::write(fileName, *document); delete document; return result; } void PositionTracking::clearTrack() { d->m_treeModel->removeFeature( d->m_currentTrackPlacemark ); d->m_currentTrack = new GeoDataTrack; d->m_trackSegments->clear(); d->m_trackSegments->append( d->m_currentTrack ); d->m_treeModel->addFeature( &d->m_document, d->m_currentTrackPlacemark ); d->m_length = 0.0; } void PositionTracking::readSettings() { QFile file( d->statusFile() ); if ( !file.open( QIODevice::ReadOnly ) ) { mDebug() << "Can not read track from " << file.fileName(); return; } GeoDataParser parser( GeoData_KML ); if ( !parser.read( &file ) ) { mDebug() << "Could not parse tracking file: " << parser.errorString(); return; } GeoDataDocument *doc = dynamic_cast( parser.releaseDocument() ); file.close(); if( !doc ){ mDebug() << "tracking document not available"; return; } GeoDataPlacemark *track = dynamic_cast( doc->child( 0 ) ); if( !track ) { mDebug() << "tracking document doesn't have a placemark"; delete doc; return; } d->m_trackSegments = dynamic_cast( track->geometry() ); if( !d->m_trackSegments ) { mDebug() << "tracking document doesn't have a multitrack"; delete doc; return; } if( d->m_trackSegments->size() < 1 ) { mDebug() << "tracking document doesn't have a track"; delete doc; return; } d->m_currentTrack = dynamic_cast( d->m_trackSegments->child( d->m_trackSegments->size() - 1 ) ); if( !d->m_currentTrack ) { mDebug() << "tracking document doesn't have a last track"; delete doc; return; } doc->remove( 0 ); delete doc; d->m_treeModel->removeDocument( &d->m_document ); d->m_document.remove( 1 ); delete d->m_currentTrackPlacemark; d->m_currentTrackPlacemark = track; d->m_currentTrackPlacemark->setName(QStringLiteral("Current Track")); d->m_document.append( d->m_currentTrackPlacemark ); d->m_currentTrackPlacemark->setStyleUrl( d->m_currentTrackPlacemark->styleUrl() ); d->m_treeModel->addDocument( &d->m_document ); d->m_length = 0.0; for ( int i = 0; i < d->m_trackSegments->size(); ++i ) { d->m_length += d->m_trackSegments->at( i ).lineString()->length( 1 ); } } void PositionTracking::writeSettings() { saveTrack( d->statusFile() ); } bool PositionTracking::isTrackEmpty() const { if ( d->m_trackSegments->size() < 1 ) { return true; } if ( d->m_trackSegments->size() == 1 ) { return ( d->m_currentTrack->size() == 0 ); } return false; } qreal PositionTracking::length( qreal planetRadius ) const { return d->m_length * planetRadius; } GeoDataAccuracy PositionTracking::accuracy() const { return d->m_positionProvider ? d->m_positionProvider->accuracy() : GeoDataAccuracy(); } GeoDataCoordinates PositionTracking::currentLocation() const { return d->m_positionProvider ? d->m_positionProvider->position() : GeoDataCoordinates(); } PositionProviderStatus PositionTracking::status() const { return d->m_positionProvider ? d->m_positionProvider->status() : PositionProviderStatusUnavailable; } } #include "moc_PositionTracking.cpp" diff --git a/src/lib/marble/RouteSimulationPositionProviderPlugin.cpp b/src/lib/marble/RouteSimulationPositionProviderPlugin.cpp index ad5741b08..b197d5c65 100644 --- a/src/lib/marble/RouteSimulationPositionProviderPlugin.cpp +++ b/src/lib/marble/RouteSimulationPositionProviderPlugin.cpp @@ -1,289 +1,289 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konrad Enzensberger // Copyright 2011 Dennis Nienhüser // Copyright 2012 Bernhard Beschow // #include "RouteSimulationPositionProviderPlugin.h" #include "MarbleMath.h" #include "MarbleModel.h" #include "routing/Route.h" #include "routing/RoutingManager.h" #include "routing/RoutingModel.h" #include "GeoDataAccuracy.h" #include namespace Marble { namespace { qreal const c_frequency = 4.0; // Hz } QString RouteSimulationPositionProviderPlugin::name() const { return tr( "Current Route Position Provider Plugin" ); } QString RouteSimulationPositionProviderPlugin::nameId() const { return QStringLiteral("RouteSimulationPositionProviderPlugin"); } QString RouteSimulationPositionProviderPlugin::guiString() const { return tr( "Current Route" ); } QString RouteSimulationPositionProviderPlugin::version() const { return QStringLiteral("1.1"); } QString RouteSimulationPositionProviderPlugin::description() const { return tr( "Simulates traveling along the current route." ); } QString RouteSimulationPositionProviderPlugin::copyrightYears() const { return QStringLiteral("2011, 2012"); } QVector RouteSimulationPositionProviderPlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Konrad Enzensberger"), QStringLiteral("e.konrad@mpegcode.com")) << PluginAuthor(QStringLiteral("Dennis Nienhüser"), QStringLiteral("nienhueser@kde.org")) << PluginAuthor(QStringLiteral("Bernhard Beschow"), QStringLiteral("bbeschow@cs.tu-berlin.de")); } QIcon RouteSimulationPositionProviderPlugin::icon() const { return QIcon(); } PositionProviderPlugin* RouteSimulationPositionProviderPlugin::newInstance() const { return new RouteSimulationPositionProviderPlugin( m_marbleModel ); } PositionProviderStatus RouteSimulationPositionProviderPlugin::status() const { return m_status; } GeoDataCoordinates RouteSimulationPositionProviderPlugin::position() const { return m_currentPositionWithNoise; } GeoDataAccuracy RouteSimulationPositionProviderPlugin::accuracy() const { GeoDataAccuracy result; // faked values result.level = GeoDataAccuracy::Detailed; result.horizontal = 10.0; result.vertical = 10.0; return result; } RouteSimulationPositionProviderPlugin::RouteSimulationPositionProviderPlugin(MarbleModel *marbleModel , QObject *parent) : PositionProviderPlugin(parent), m_marbleModel( marbleModel ), m_currentIndex( -2 ), m_status( PositionProviderStatusUnavailable ), m_currentDateTime(), m_speed( 0.0 ), m_direction( 0.0 ), m_directionWithNoise(0.0) { connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(update())); } RouteSimulationPositionProviderPlugin::~RouteSimulationPositionProviderPlugin() { } void RouteSimulationPositionProviderPlugin::initialize() { updateRoute(); connect(m_marbleModel->routingManager()->routingModel(), SIGNAL(currentRouteChanged()), this, SLOT(updateRoute())); } bool RouteSimulationPositionProviderPlugin::isInitialized() const { return ( m_currentIndex > -2 ); } qreal RouteSimulationPositionProviderPlugin::speed() const { return m_speed; } qreal RouteSimulationPositionProviderPlugin::direction() const { return m_directionWithNoise; } QDateTime RouteSimulationPositionProviderPlugin::timestamp() const { return m_currentDateTime; } void RouteSimulationPositionProviderPlugin::updateRoute(){ m_currentIndex = -1; m_lineString = m_lineStringInterpolated = m_marbleModel->routingManager()->routingModel()->route().path(); m_speed = 0; //initialize speed to be around 25 m/s; bool const canWork = !m_lineString.isEmpty() || m_currentPosition.isValid(); if (canWork) { changeStatus(PositionProviderStatusAcquiring); m_updateTimer.start(1000.0 / c_frequency); } else { changeStatus(PositionProviderStatusUnavailable); m_updateTimer.stop(); } } void RouteSimulationPositionProviderPlugin::update() { if (m_lineString.isEmpty() && m_currentPosition.isValid()) { m_currentPositionWithNoise = addNoise(m_currentPosition, accuracy()); changeStatus(PositionProviderStatusAvailable); emit positionChanged(position(), accuracy()); return; } if ( m_currentIndex >= 0 && m_currentIndex < m_lineStringInterpolated.size() ) { changeStatus(PositionProviderStatusAvailable); GeoDataCoordinates newPosition = m_lineStringInterpolated.at( m_currentIndex ); const QDateTime newDateTime = QDateTime::currentDateTime(); qreal time= m_currentDateTime.msecsTo(newDateTime)/1000.0; if ( m_currentPosition.isValid() ) { //speed calculations //Max speed is set on points (m_lineStringInterpolated) based on formula. (max speed before points is calculated so the acceleration won't be exceeded) const qreal acceleration = 1.5; const qreal lookForwardDistance = 1000; - qreal checkedDistance = distanceSphere( m_currentPosition, m_lineStringInterpolated.at(m_currentIndex) )* m_marbleModel->planetRadius(); + qreal checkedDistance = m_currentPosition.sphericalDistanceTo(m_lineStringInterpolated.at(m_currentIndex))* m_marbleModel->planetRadius(); const qreal maxSpeed = 25; const qreal minSpeed = 2; qreal newSpeed = qMin((m_speed + acceleration*time), maxSpeed); for (int i=qMax(1,m_currentIndex); i180) { differenceHeading = 360 - differenceHeading; } angleSum +=differenceHeading; qreal maxSpeedAtTurn = qMax((1 - (static_cast(angleSum/60.0/curveLength*10.0))*maxSpeed), minSpeed);//speed limit at turn if( checkedDistance<25 && maxSpeedAtTurnplanetRadius(); + curveLength += m_lineStringInterpolated.at(j - 1).sphericalDistanceTo(m_lineStringInterpolated.at(j)) * m_marbleModel->planetRadius(); } - checkedDistance += distanceSphere( m_lineStringInterpolated.at( i ), m_lineStringInterpolated.at( i+1 ) )* m_marbleModel->planetRadius(); + checkedDistance += m_lineStringInterpolated.at(i).sphericalDistanceTo(m_lineStringInterpolated.at(i + 1)) * m_marbleModel->planetRadius(); } m_speed=newSpeed; //Assume the car's moving at m_speed m/s. The distance moved will be speed*time which is equal to the speed of the car if time is equal to one. //If the function isn't called once exactly after a second, multiplying by the time will compensate for the error and maintain the speed. - qreal fraction = m_speed*time/(distanceSphere( m_currentPosition, newPosition )* m_marbleModel->planetRadius()); + qreal fraction = m_speed*time/(m_currentPosition.sphericalDistanceTo(newPosition) * m_marbleModel->planetRadius()); //Interpolate and find the next point to move to if needed. if(fraction>0 && fraction <1){ GeoDataCoordinates newPoint = m_currentPosition.interpolate(newPosition,fraction); newPosition=newPoint; } else if ( fraction > 1 ) { bool isCurrentIndexValid = true; while ( fraction > 1 ) { ++m_currentIndex; if ( m_currentIndex >= m_lineStringInterpolated.size() ) { isCurrentIndexValid = false; break; } newPosition = m_lineStringInterpolated.at( m_currentIndex ); - fraction = m_speed*time / (distanceSphere( m_currentPosition, newPosition )* m_marbleModel->planetRadius()); + fraction = m_speed*time / (m_currentPosition.sphericalDistanceTo(newPosition) * m_marbleModel->planetRadius()); } if ( isCurrentIndexValid ) { GeoDataCoordinates newPoint = m_currentPosition.interpolate( newPosition, fraction ); newPosition = newPoint; } } else { m_currentIndex++; } m_direction = m_currentPosition.bearing( newPosition, GeoDataCoordinates::Degree, GeoDataCoordinates::FinalBearing ); m_directionWithNoise = addNoise(m_direction); } m_currentPosition = newPosition; m_currentPositionWithNoise = addNoise(m_currentPosition, accuracy()); m_currentDateTime = newDateTime; emit positionChanged( position(), accuracy() ); } else { // Repeat from start m_currentIndex = 0; m_lineStringInterpolated = m_lineString; m_currentPosition = GeoDataCoordinates(); //Reset the current position so that the simulation starts from the correct starting point. m_currentPositionWithNoise = GeoDataCoordinates(); m_speed = 0; changeStatus(PositionProviderStatusUnavailable); } } GeoDataCoordinates RouteSimulationPositionProviderPlugin::addNoise(const Marble::GeoDataCoordinates &position, const Marble::GeoDataAccuracy &accuracy ) const { qreal randomBearing = static_cast(qrand()) / (static_cast(RAND_MAX/M_PI)); qreal randomDistance = static_cast(qrand()) / (static_cast(RAND_MAX/(accuracy.horizontal / 2.0 / m_marbleModel->planetRadius()))); return position.moveByBearing(randomBearing, randomDistance); } qreal RouteSimulationPositionProviderPlugin::addNoise(qreal bearing) { qreal const maxBearingError = 30.0; return bearing + static_cast(qrand()) / (static_cast(RAND_MAX/maxBearingError/2.0)) - maxBearingError / 2.0; } void RouteSimulationPositionProviderPlugin::changeStatus(PositionProviderStatus status) { if (m_status != status) { m_status = status; emit statusChanged(m_status); } } } // namespace Marble #include "moc_RouteSimulationPositionProviderPlugin.cpp" diff --git a/src/lib/marble/SearchRunnerManager.cpp b/src/lib/marble/SearchRunnerManager.cpp index 60879c8a2..61e7a45fa 100644 --- a/src/lib/marble/SearchRunnerManager.cpp +++ b/src/lib/marble/SearchRunnerManager.cpp @@ -1,234 +1,233 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008 Henry de Valence // Copyright 2010 Dennis Nienhüser // Copyright 2010-2013 Bernhard Beschow // Copyright 2011 Thibaut Gridel #include "SearchRunnerManager.h" #include "MarblePlacemarkModel.h" #include "MarbleDebug.h" #include "MarbleModel.h" #include "MarbleMath.h" #include "Planet.h" #include "GeoDataPlacemark.h" #include "PluginManager.h" #include "ParseRunnerPlugin.h" #include "ReverseGeocodingRunnerPlugin.h" #include "RoutingRunnerPlugin.h" #include "SearchRunnerPlugin.h" #include "RunnerTask.h" #include "routing/RouteRequest.h" #include "routing/RoutingProfilesModel.h" #include #include #include #include #include #include namespace Marble { class MarbleModel; class Q_DECL_HIDDEN SearchRunnerManager::Private { public: Private( SearchRunnerManager *parent, const MarbleModel *marbleModel ); template QList plugins( const QList &plugins ) const; void addSearchResult( const QVector &result ); void cleanupSearchTask( SearchTask *task ); void notifySearchResultChange(); void notifySearchFinished(); SearchRunnerManager *const q; const MarbleModel *const m_marbleModel; const PluginManager* m_pluginManager; QString m_lastSearchTerm; GeoDataLatLonBox m_lastPreferredBox; QMutex m_modelMutex; MarblePlacemarkModel m_model; QList m_searchTasks; QVector m_placemarkContainer; }; SearchRunnerManager::Private::Private( SearchRunnerManager *parent, const MarbleModel *marbleModel ) : q( parent ), m_marbleModel( marbleModel ), m_pluginManager( marbleModel->pluginManager() ), m_model( new MarblePlacemarkModel( parent ) ) { m_model.setPlacemarkContainer( &m_placemarkContainer ); qRegisterMetaType >( "QVector" ); } template QList SearchRunnerManager::Private::plugins( const QList &plugins ) const { QList result; for( T* plugin: plugins ) { if ( ( m_marbleModel && m_marbleModel->workOffline() && !plugin->canWorkOffline() ) ) { continue; } if ( !plugin->canWork() ) { continue; } if ( m_marbleModel && !plugin->supportsCelestialBody( m_marbleModel->planet()->id() ) ) { continue; } result << plugin; } return result; } void SearchRunnerManager::Private::addSearchResult( const QVector &result ) { mDebug() << "Runner reports" << result.size() << " search results"; if( result.isEmpty() ) return; m_modelMutex.lock(); int start = m_placemarkContainer.size(); int count = 0; bool distanceCompare = m_marbleModel->planet() != 0; for( int i=0; icoordinate(), - m_placemarkContainer[j]->coordinate() ) + (result[i]->coordinate().sphericalDistanceTo(m_placemarkContainer[j]->coordinate()) * m_marbleModel->planet()->radius() < 1 ) ) { same = true; } } if ( !same ) { m_placemarkContainer.append( result[i] ); ++count; } } m_model.addPlacemarks( start, count ); m_modelMutex.unlock(); notifySearchResultChange(); } void SearchRunnerManager::Private::cleanupSearchTask( SearchTask *task ) { m_searchTasks.removeAll( task ); mDebug() << "removing search task" << m_searchTasks.size() << (quintptr)task; if ( m_searchTasks.isEmpty() ) { if( m_placemarkContainer.isEmpty() ) { notifySearchResultChange(); } notifySearchFinished(); } } void SearchRunnerManager::Private::notifySearchResultChange() { emit q->searchResultChanged(&m_model); emit q->searchResultChanged(m_placemarkContainer); } void SearchRunnerManager::Private::notifySearchFinished() { emit q->searchFinished(m_lastSearchTerm); emit q->placemarkSearchFinished(); } SearchRunnerManager::SearchRunnerManager( const MarbleModel *marbleModel, QObject *parent ) : QObject( parent ), d( new Private( this, marbleModel ) ) { if ( QThreadPool::globalInstance()->maxThreadCount() < 4 ) { QThreadPool::globalInstance()->setMaxThreadCount( 4 ); } } SearchRunnerManager::~SearchRunnerManager() { delete d; } void SearchRunnerManager::findPlacemarks( const QString &searchTerm, const GeoDataLatLonBox &preferred ) { if ( searchTerm == d->m_lastSearchTerm && preferred == d->m_lastPreferredBox ) { d->notifySearchResultChange(); d->notifySearchFinished(); return; } d->m_lastSearchTerm = searchTerm; d->m_lastPreferredBox = preferred; d->m_searchTasks.clear(); d->m_modelMutex.lock(); bool placemarkContainerChanged = false; if (!d->m_placemarkContainer.isEmpty()) { d->m_model.removePlacemarks( "PlacemarkRunnerManager", 0, d->m_placemarkContainer.size() ); qDeleteAll( d->m_placemarkContainer ); d->m_placemarkContainer.clear(); placemarkContainerChanged = true; } d->m_modelMutex.unlock(); if (placemarkContainerChanged) { d->notifySearchResultChange(); } if ( searchTerm.trimmed().isEmpty() ) { d->notifySearchFinished(); return; } QList plugins = d->plugins( d->m_pluginManager->searchRunnerPlugins() ); for( const SearchRunnerPlugin *plugin: plugins ) { SearchTask *task = new SearchTask( plugin->newRunner(), this, d->m_marbleModel, searchTerm, preferred ); connect( task, SIGNAL(finished(SearchTask*)), this, SLOT(cleanupSearchTask(SearchTask*)) ); d->m_searchTasks << task; mDebug() << "search task " << plugin->nameId() << " " << (quintptr)task; } for( SearchTask *task: d->m_searchTasks ) { QThreadPool::globalInstance()->start( task ); } if ( plugins.isEmpty() ) { d->cleanupSearchTask( 0 ); } } QVector SearchRunnerManager::searchPlacemarks( const QString &searchTerm, const GeoDataLatLonBox &preferred, int timeout ) { QEventLoop localEventLoop; QTimer watchdog; watchdog.setSingleShot(true); connect( &watchdog, SIGNAL(timeout()), &localEventLoop, SLOT(quit())); connect(this, SIGNAL(placemarkSearchFinished()), &localEventLoop, SLOT(quit()), Qt::QueuedConnection ); watchdog.start( timeout ); findPlacemarks( searchTerm, preferred ); localEventLoop.exec(); return d->m_placemarkContainer; } } #include "moc_SearchRunnerManager.cpp" diff --git a/src/lib/marble/cloudsync/BookmarkSyncManager.cpp b/src/lib/marble/cloudsync/BookmarkSyncManager.cpp index 4aa5ce189..4d41f3bd8 100644 --- a/src/lib/marble/cloudsync/BookmarkSyncManager.cpp +++ b/src/lib/marble/cloudsync/BookmarkSyncManager.cpp @@ -1,809 +1,809 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2013 Utku Aydın // #include "BookmarkSyncManager.h" #include "GeoWriter.h" #include "MarbleMath.h" #include "MarbleDirs.h" #include "MarbleDebug.h" #include "GeoDataParser.h" #include "GeoDataFolder.h" #include "GeoDataDocument.h" #include "GeoDataLookAt.h" #include "CloudSyncManager.h" #include "GeoDataCoordinates.h" #include "OwncloudSyncBackend.h" #include "MarbleModel.h" #include "BookmarkManager.h" #include #include #include #include #include #include #include namespace Marble { class DiffItem { public: enum Action { NoAction, Created, Changed, Deleted }; enum Status { Source, Destination }; QString m_path; Action m_action; Status m_origin; GeoDataPlacemark m_placemarkA; GeoDataPlacemark m_placemarkB; }; class Q_DECL_HIDDEN BookmarkSyncManager::Private { public: Private( BookmarkSyncManager* parent, CloudSyncManager *cloudSyncManager ); BookmarkSyncManager* m_q; CloudSyncManager *m_cloudSyncManager; QNetworkAccessManager m_network; QString m_uploadEndpoint; QString m_downloadEndpoint; QString m_timestampEndpoint; QNetworkReply* m_uploadReply; QNetworkReply* m_downloadReply; QNetworkReply* m_timestampReply; QString m_cloudTimestamp; QString m_cachePath; QString m_localBookmarksPath; QString m_bookmarksTimestamp; QList m_diffA; QList m_diffB; QList m_merged; DiffItem m_conflictItem; BookmarkManager* m_bookmarkManager; QTimer m_syncTimer; bool m_bookmarkSyncEnabled; /** * Returns an API endpoint * @param endpoint Endpoint itself without server info * @return Complete API URL as QUrl */ QUrl endpointUrl( const QString &endpoint ) const; /** * Uploads local bookmarks.kml to cloud. */ void uploadBookmarks(); /** * Downloads bookmarks.kml from cloud. */ void downloadBookmarks(); /** * Gets cloud bookmarks.kml's timestamp from cloud. */ void downloadTimestamp(); /** * Compares cloud bookmarks.kml's timestamp to last synced bookmarks.kml's timestamp. * @return true if cloud one is different from last synced one. */ bool cloudBookmarksModified( const QString &cloudTimestamp ) const; /** * Removes all KMLs in the cache except the * one with yougnest timestamp. */ void clearCache(); /** * Finds the last synced bookmarks.kml file and returns its path * @return Path of last synced bookmarks.kml file. */ QString lastSyncedKmlPath() const; /** * Gets all placemarks in a document as DiffItems, compares them to another document and puts the result in a list. * @param document The document whose placemarks will be compared to another document's placemarks. * @param other The document whose placemarks will be compared to the first document's placemarks. * @param diffDirection Direction of comparison, e.g. must be DiffItem::Destination if direction is source to destination. * @return A list of DiffItems */ QList getPlacemarks(GeoDataDocument *document, GeoDataDocument *other, DiffItem::Status diffDirection ); /** * Gets all placemarks in a document as DiffItems, compares them to another document and puts the result in a list. * @param folder The folder whose placemarks will be compared to another document's placemarks. * @param path Path of the folder. * @param other The document whose placemarks will be compared to the first document's placemarks. * @param diffDirection Direction of comparison, e.g. must be DiffItem::Destination if direction is source to destination. * @return A list of DiffItems */ QList getPlacemarks( GeoDataFolder *folder, QString &path, GeoDataDocument *other, DiffItem::Status diffDirection ); /** * Finds the placemark which has the same coordinates with given bookmark * @param container Container of placemarks which will be compared. Can be document or folder. * @param bookmark The bookmark whose counterpart will be searched in the container. * @return Counterpart of the given placemark. */ const GeoDataPlacemark* findPlacemark( GeoDataContainer* container, const GeoDataPlacemark &bookmark ) const; /** * Determines the status (created, deleted, changed or unchanged) of given DiffItem * by comparing the item's placemark with placemarks of given GeoDataDocument. * @param item The item whose status will be determined. * @param document The document whose placemarks will be used to determine DiffItem's status. */ void determineDiffStatus( DiffItem &item, GeoDataDocument* document ) const; /** * Finds differences between two bookmark files. * @param sourcePath Source bookmark * @param destinationPath Destination bookmark * @return A list of differences */ QList diff( QString &sourcePath, QString &destinationPath ); QList diff( QString &sourcePath, QIODevice* destination ); QList diff( QIODevice* source, QString &destinationPath ); QList diff( QIODevice *source, QIODevice* destination ); /** * Merges two diff lists. * @param diffListA First diff list. * @param diffListB Second diff list. * @return Merged DiffItems. */ void merge(); /** * Creates GeoDataFolders using strings in path list. * @param container Container which created GeoDataFolder will be attached to. * @param pathList Names of folders. Note that each item will be the child of the previous one. * @return A pointer to created folder. */ GeoDataFolder* createFolders( GeoDataContainer *container, QStringList &pathList ); /** * Creates a GeoDataDocument using a list of DiffItems. * @param mergedList DiffItems which will be used as placemarks. * @return A pointer to created document. */ GeoDataDocument* constructDocument( const QList &mergedList ); void saveDownloadedToCache( const QByteArray &kml ); void parseTimestamp(); void copyLocalToCache(); void continueSynchronization(); void completeSynchronization(); void completeMerge(); void completeUpload(); }; BookmarkSyncManager::Private::Private(BookmarkSyncManager *parent, CloudSyncManager *cloudSyncManager ) : m_q( parent ), m_cloudSyncManager( cloudSyncManager ), m_bookmarkManager( 0 ), m_bookmarkSyncEnabled( false ) { m_cachePath = MarbleDirs::localPath() + QLatin1String("/cloudsync/cache/bookmarks"); m_localBookmarksPath = MarbleDirs::localPath() + QLatin1String("/bookmarks/bookmarks.kml"); m_downloadEndpoint = "bookmarks/kml"; m_uploadEndpoint = "bookmarks/update"; m_timestampEndpoint = "bookmarks/timestamp"; } BookmarkSyncManager::BookmarkSyncManager( CloudSyncManager *cloudSyncManager ) : QObject(), d( new Private( this, cloudSyncManager ) ) { d->m_syncTimer.setInterval( 60 * 60 * 1000 ); // 1 hour. TODO: Make this configurable. connect( &d->m_syncTimer, SIGNAL(timeout()), this, SLOT(startBookmarkSync()) ); } BookmarkSyncManager::~BookmarkSyncManager() { delete d; } QDateTime BookmarkSyncManager::lastSync() const { const QString last = d->lastSyncedKmlPath(); if (last.isEmpty()) return QDateTime(); return QFileInfo(last).created(); } bool BookmarkSyncManager::isBookmarkSyncEnabled() const { return d->m_bookmarkSyncEnabled; } void BookmarkSyncManager::setBookmarkSyncEnabled( bool enabled ) { bool const old_state = isBookmarkSyncEnabled(); d->m_bookmarkSyncEnabled = enabled; if ( old_state != isBookmarkSyncEnabled() ) { emit bookmarkSyncEnabledChanged( d->m_bookmarkSyncEnabled ); if ( isBookmarkSyncEnabled() ) { startBookmarkSync(); } } } void BookmarkSyncManager::setBookmarkManager(BookmarkManager *manager) { d->m_bookmarkManager = manager; connect( manager, SIGNAL(bookmarksChanged()), this, SLOT(startBookmarkSync()) ); startBookmarkSync(); } void BookmarkSyncManager::startBookmarkSync() { if ( !d->m_cloudSyncManager->isSyncEnabled() || !isBookmarkSyncEnabled() ) { return; } d->m_syncTimer.start(); d->downloadTimestamp(); } QUrl BookmarkSyncManager::Private::endpointUrl( const QString &endpoint ) const { return QUrl(m_cloudSyncManager->apiUrl().toString() + QLatin1Char('/') + endpoint); } void BookmarkSyncManager::Private::uploadBookmarks() { QByteArray data; QByteArray lineBreak = "\r\n"; QString word = "----MarbleCloudBoundary"; QString boundary = QString( "--%0" ).arg( word ); QNetworkRequest request( endpointUrl( m_uploadEndpoint ) ); request.setHeader( QNetworkRequest::ContentTypeHeader, QString( "multipart/form-data; boundary=%0" ).arg( word ) ); data.append( QString( boundary + lineBreak ).toUtf8() ); data.append( "Content-Disposition: form-data; name=\"bookmarks\"; filename=\"bookmarks.kml\"" + lineBreak ); data.append( "Content-Type: application/vnd.google-earth.kml+xml" + lineBreak + lineBreak ); QFile bookmarksFile( m_localBookmarksPath ); if( !bookmarksFile.open( QFile::ReadOnly ) ) { mDebug() << "Failed to open file" << bookmarksFile.fileName() << ". It is either missing or not readable."; return; } QByteArray kmlContent = bookmarksFile.readAll(); data.append( kmlContent + lineBreak + lineBreak ); data.append( QString( boundary ).toUtf8() ); bookmarksFile.close(); m_uploadReply = m_network.post( request, data ); connect( m_uploadReply, SIGNAL(uploadProgress(qint64,qint64)), m_q, SIGNAL(uploadProgress(qint64,qint64)) ); connect( m_uploadReply, SIGNAL(finished()), m_q, SLOT(completeUpload()) ); } void BookmarkSyncManager::Private::downloadBookmarks() { QNetworkRequest request( endpointUrl( m_downloadEndpoint ) ); m_downloadReply = m_network.get( request ); connect( m_downloadReply, SIGNAL(finished()), m_q, SLOT(completeSynchronization()) ); connect( m_downloadReply, SIGNAL(downloadProgress(qint64,qint64)), m_q, SIGNAL(downloadProgress(qint64,qint64)) ); } void BookmarkSyncManager::Private::downloadTimestamp() { mDebug() << "Determining remote bookmark state."; m_timestampReply = m_network.get( QNetworkRequest( endpointUrl( m_timestampEndpoint ) ) ); connect( m_timestampReply, SIGNAL(finished()), m_q, SLOT(parseTimestamp()) ); } bool BookmarkSyncManager::Private::cloudBookmarksModified( const QString &cloudTimestamp ) const { QStringList entryList = QDir( m_cachePath ).entryList( // TODO: replace with regex filter that only // allows timestamp filenames QStringList() << "*.kml", QDir::NoFilter, QDir::Name ); if( !entryList.isEmpty() ) { QString lastSynced = entryList.last(); lastSynced.chop( 4 ); return cloudTimestamp != lastSynced; } else { return true; // That will let cloud one get downloaded. } } void BookmarkSyncManager::Private::clearCache() { QDir cacheDir( m_cachePath ); QFileInfoList fileInfoList = cacheDir.entryInfoList( QStringList() << "*.kml", QDir::NoFilter, QDir::Name ); if( !fileInfoList.isEmpty() ) { for ( const QFileInfo& fileInfo: fileInfoList ) { QFile file( fileInfo.absoluteFilePath() ); bool removed = file.remove(); if( !removed ) { mDebug() << "Could not delete" << file.fileName() << "Make sure you have sufficient permissions."; } } } } QString BookmarkSyncManager::Private::lastSyncedKmlPath() const { QDir cacheDir( m_cachePath ); QFileInfoList fileInfoList = cacheDir.entryInfoList( QStringList() << "*.kml", QDir::NoFilter, QDir::Name ); if( !fileInfoList.isEmpty() ) { return fileInfoList.last().absoluteFilePath(); } else { return QString(); } } QList BookmarkSyncManager::Private::getPlacemarks( GeoDataDocument *document, GeoDataDocument *other, DiffItem::Status diffDirection ) { QList diffItems; for ( GeoDataFolder *folder: document->folderList() ) { QString path = QString( "/%0" ).arg( folder->name() ); diffItems.append( getPlacemarks( folder, path, other, diffDirection ) ); } return diffItems; } QList BookmarkSyncManager::Private::getPlacemarks( GeoDataFolder *folder, QString &path, GeoDataDocument *other, DiffItem::Status diffDirection ) { QList diffItems; for ( GeoDataFolder *subFolder: folder->folderList() ) { QString newPath = QString( "%0/%1" ).arg( path, subFolder->name() ); diffItems.append( getPlacemarks( subFolder, newPath, other, diffDirection ) ); } for( GeoDataPlacemark *placemark: folder->placemarkList() ) { DiffItem diffItem; diffItem.m_path = path; diffItem.m_placemarkA = *placemark; switch ( diffDirection ) { case DiffItem::Source: diffItem.m_origin = DiffItem::Destination; break; case DiffItem::Destination: diffItem.m_origin = DiffItem::Source; break; default: break; } determineDiffStatus( diffItem, other ); if( !( diffItem.m_action == DiffItem::NoAction && diffItem.m_origin == DiffItem::Destination ) && !( diffItem.m_action == DiffItem::Changed && diffItem.m_origin == DiffItem::Source ) ) { diffItems.append( diffItem ); } } return diffItems; } const GeoDataPlacemark* BookmarkSyncManager::Private::findPlacemark( GeoDataContainer* container, const GeoDataPlacemark &bookmark ) const { for( GeoDataPlacemark* placemark: container->placemarkList() ) { - if ( EARTH_RADIUS * distanceSphere( placemark->coordinate(), bookmark.coordinate() ) <= 1 ) { + if (EARTH_RADIUS * placemark->coordinate().sphericalDistanceTo(bookmark.coordinate()) <= 1) { return placemark; } } for( GeoDataFolder* folder: container->folderList() ) { const GeoDataPlacemark* placemark = findPlacemark( folder, bookmark ); if ( placemark ) { return placemark; } } return 0; } void BookmarkSyncManager::Private::determineDiffStatus( DiffItem &item, GeoDataDocument *document ) const { const GeoDataPlacemark *match = findPlacemark( document, item.m_placemarkA ); if( match != 0 ) { item.m_placemarkB = *match; bool nameChanged = item.m_placemarkA.name() != item.m_placemarkB.name(); bool descChanged = item.m_placemarkA.description() != item.m_placemarkB.description(); bool lookAtChanged = item.m_placemarkA.lookAt()->latitude() != item.m_placemarkB.lookAt()->latitude() || item.m_placemarkA.lookAt()->longitude() != item.m_placemarkB.lookAt()->longitude() || item.m_placemarkA.lookAt()->altitude() != item.m_placemarkB.lookAt()->altitude() || item.m_placemarkA.lookAt()->range() != item.m_placemarkB.lookAt()->range(); if( nameChanged || descChanged || lookAtChanged ) { item.m_action = DiffItem::Changed; } else { item.m_action = DiffItem::NoAction; } } else { switch( item.m_origin ) { case DiffItem::Source: item.m_action = DiffItem::Deleted; item.m_placemarkB = item.m_placemarkA; // for conflict purposes break; case DiffItem::Destination: item.m_action = DiffItem::Created; break; } } } QList BookmarkSyncManager::Private::diff( QString &sourcePath, QString &destinationPath ) { QFile fileB( destinationPath ); if( !fileB.open( QFile::ReadOnly ) ) { mDebug() << "Could not open file " << fileB.fileName(); } return diff( sourcePath, &fileB ); } QList BookmarkSyncManager::Private::diff( QString &sourcePath, QIODevice *fileB ) { QFile fileA( sourcePath ); if( !fileA.open( QFile::ReadOnly ) ) { mDebug() << "Could not open file " << fileA.fileName(); } return diff( &fileA, fileB ); } QList BookmarkSyncManager::Private::diff( QIODevice *source, QString &destinationPath ) { QFile fileB( destinationPath ); if( !fileB.open( QFile::ReadOnly ) ) { mDebug() << "Could not open file " << fileB.fileName(); } return diff( source, &fileB ); } QList BookmarkSyncManager::Private::diff( QIODevice *fileA, QIODevice *fileB ) { GeoDataParser parserA( GeoData_KML ); parserA.read( fileA ); GeoDataDocument *documentA = dynamic_cast( parserA.releaseDocument() ); GeoDataParser parserB( GeoData_KML ); parserB.read( fileB ); GeoDataDocument *documentB = dynamic_cast( parserB.releaseDocument() ); QList diffItems = getPlacemarks( documentA, documentB, DiffItem::Destination ); // Compare old to new diffItems.append( getPlacemarks( documentB, documentA, DiffItem::Source ) ); // Compare new to old // Compare paths for( int i = 0; i < diffItems.count(); i++ ) { for( int p = i + 1; p < diffItems.count(); p++ ) { if( ( diffItems[i].m_origin == DiffItem::Source ) && ( diffItems[i].m_action == DiffItem::NoAction ) - && ( EARTH_RADIUS * distanceSphere( diffItems[i].m_placemarkA.coordinate(), diffItems[p].m_placemarkB.coordinate() ) <= 1 ) - && ( EARTH_RADIUS * distanceSphere( diffItems[i].m_placemarkB.coordinate(), diffItems[p].m_placemarkA.coordinate() ) <= 1 ) + && ( EARTH_RADIUS * diffItems[i].m_placemarkA.coordinate().sphericalDistanceTo(diffItems[p].m_placemarkB.coordinate()) <= 1 ) + && ( EARTH_RADIUS * diffItems[i].m_placemarkB.coordinate().sphericalDistanceTo(diffItems[p].m_placemarkA.coordinate()) <= 1 ) && ( diffItems[i].m_path != diffItems[p].m_path ) ) { diffItems[p].m_action = DiffItem::Changed; } } } return diffItems; } void BookmarkSyncManager::Private::merge() { for( const DiffItem &itemA: m_diffA ) { if( itemA.m_action == DiffItem::NoAction ) { bool deleted = false; bool changed = false; DiffItem other; for( const DiffItem &itemB: m_diffB ) { - if( EARTH_RADIUS * distanceSphere( itemA.m_placemarkA.coordinate(), itemB.m_placemarkA.coordinate() ) <= 1 ) { + if( EARTH_RADIUS * itemA.m_placemarkA.coordinate().sphericalDistanceTo(itemB.m_placemarkA.coordinate()) <= 1 ) { if( itemB.m_action == DiffItem::Deleted ) { deleted = true; } else if( itemB.m_action == DiffItem::Changed ) { changed = true; other = itemB; } } } if( changed ) { m_merged.append( other ); } else if( !deleted ) { m_merged.append( itemA ); } } else if( itemA.m_action == DiffItem::Created ) { m_merged.append( itemA ); } else if( itemA.m_action == DiffItem::Changed || itemA.m_action == DiffItem::Deleted ) { bool conflict = false; DiffItem other; for( const DiffItem &itemB: m_diffB ) { - if( EARTH_RADIUS * distanceSphere( itemA.m_placemarkB.coordinate(), itemB.m_placemarkB.coordinate() ) <= 1 ) { + if (EARTH_RADIUS * itemA.m_placemarkB.coordinate().sphericalDistanceTo(itemB.m_placemarkB.coordinate()) <= 1) { if( ( itemA.m_action == DiffItem::Changed && ( itemB.m_action == DiffItem::Changed || itemB.m_action == DiffItem::Deleted ) ) || ( itemA.m_action == DiffItem::Deleted && itemB.m_action == DiffItem::Changed ) ) { conflict = true; other = itemB; } } } if( !conflict && itemA.m_action == DiffItem::Changed ) { m_merged.append( itemA ); } else if ( conflict ) { m_conflictItem = other; MergeItem *mergeItem = new MergeItem(); mergeItem->setPathA( itemA.m_path ); mergeItem->setPathB( other.m_path ); mergeItem->setPlacemarkA( itemA.m_placemarkA ); mergeItem->setPlacemarkB( other.m_placemarkA ); switch( itemA.m_action ) { case DiffItem::Changed: mergeItem->setActionA( MergeItem::Changed ); break; case DiffItem::Deleted: mergeItem->setActionA( MergeItem::Deleted ); break; default: break; } switch( other.m_action ) { case DiffItem::Changed: mergeItem->setActionB( MergeItem::Changed ); break; case DiffItem::Deleted: mergeItem->setActionB( MergeItem::Deleted ); break; default: break; } emit m_q->mergeConflict( mergeItem ); return; } } if( !m_diffA.isEmpty() ) { m_diffA.removeFirst(); } } for( const DiffItem &itemB: m_diffB ) { if( itemB.m_action == DiffItem::Created ) { m_merged.append( itemB ); } } completeMerge(); } GeoDataFolder* BookmarkSyncManager::Private::createFolders( GeoDataContainer *container, QStringList &pathList ) { GeoDataFolder *folder = 0; if( pathList.count() > 0 ) { QString name = pathList.takeFirst(); for( GeoDataFolder *otherFolder: container->folderList() ) { if( otherFolder->name() == name ) { folder = otherFolder; } } if( folder == 0 ) { folder = new GeoDataFolder(); folder->setName( name ); container->append( folder ); } if( pathList.count() == 0 ) { return folder; } } return createFolders( folder, pathList ); } GeoDataDocument* BookmarkSyncManager::Private::constructDocument( const QList &mergedList ) { GeoDataDocument *document = new GeoDataDocument(); document->setName( tr( "Bookmarks" ) ); for( const DiffItem &item: mergedList ) { GeoDataPlacemark *placemark = new GeoDataPlacemark( item.m_placemarkA ); QStringList splitten = item.m_path.split(QLatin1Char('/'), QString::SkipEmptyParts); GeoDataFolder *folder = createFolders( document, splitten ); folder->append( placemark ); } return document; } void BookmarkSyncManager::resolveConflict( MergeItem *item ) { DiffItem diffItem; switch( item->resolution() ) { case MergeItem::A: if( !d->m_diffA.isEmpty() ) { diffItem = d->m_diffA.first(); break; } case MergeItem::B: diffItem = d->m_conflictItem; break; default: return; // It shouldn't happen. } if( diffItem.m_action != DiffItem::Deleted ) { d->m_merged.append( diffItem ); } if( !d->m_diffA.isEmpty() ) { d->m_diffA.removeFirst(); } d->merge(); } void BookmarkSyncManager::Private::saveDownloadedToCache( const QByteArray &kml ) { QString localBookmarksDir = m_localBookmarksPath; QDir().mkdir( localBookmarksDir.remove( "bookmarks.kml" ) ); QFile bookmarksFile( m_localBookmarksPath ); if( !bookmarksFile.open( QFile::ReadWrite ) ) { mDebug() << "Failed to open file" << bookmarksFile.fileName() << ". It is either missing or not readable."; return; } bookmarksFile.write( kml ); bookmarksFile.close(); copyLocalToCache(); } void BookmarkSyncManager::Private::parseTimestamp() { QJsonDocument jsonDoc = QJsonDocument::fromJson(m_timestampReply->readAll()); QJsonValue dataValue = jsonDoc.object().value(QStringLiteral("data")); m_cloudTimestamp = dataValue.toString(); mDebug() << "Remote bookmark timestamp is " << m_cloudTimestamp; continueSynchronization(); } void BookmarkSyncManager::Private::copyLocalToCache() { QDir().mkpath( m_cachePath ); clearCache(); QFile bookmarksFile( m_localBookmarksPath ); bookmarksFile.copy( QString( "%0/%1.kml" ).arg( m_cachePath, m_cloudTimestamp ) ); } // Bookmark synchronization steps void BookmarkSyncManager::Private::continueSynchronization() { bool cloudModified = cloudBookmarksModified( m_cloudTimestamp ); if( cloudModified ) { downloadBookmarks(); } else { QString lastSyncedPath = lastSyncedKmlPath(); if( lastSyncedPath.isEmpty() ) { mDebug() << "Never synced. Uploading bookmarks."; uploadBookmarks(); } else { QList diffList = diff( lastSyncedPath, m_localBookmarksPath ); bool localModified = false; for( const DiffItem &item: diffList ) { if( item.m_action != DiffItem::NoAction ) { localModified = true; } } if( localModified ) { mDebug() << "Local modifications, uploading."; uploadBookmarks(); } } } } void BookmarkSyncManager::Private::completeSynchronization() { mDebug() << "Merging remote and local bookmark file"; QString lastSyncedPath = lastSyncedKmlPath(); QFile localBookmarksFile( m_localBookmarksPath ); QByteArray result = m_downloadReply->readAll(); QBuffer buffer( &result ); buffer.open( QIODevice::ReadOnly ); if( lastSyncedPath.isEmpty() ) { if( localBookmarksFile.exists() ) { mDebug() << "Conflict between remote bookmarks and local ones"; m_diffA = diff( &buffer, m_localBookmarksPath ); m_diffB = diff( m_localBookmarksPath, &buffer ); } else { saveDownloadedToCache( result ); return; } } else { m_diffA = diff( lastSyncedPath, m_localBookmarksPath ); m_diffB = diff( lastSyncedPath, &buffer ); } m_merged.clear(); merge(); } void BookmarkSyncManager::Private::completeMerge() { QFile localBookmarksFile( m_localBookmarksPath ); GeoDataDocument *doc = constructDocument( m_merged ); GeoWriter writer; localBookmarksFile.remove(); localBookmarksFile.open( QFile::ReadWrite ); writer.write( &localBookmarksFile, doc ); localBookmarksFile.close(); uploadBookmarks(); } void BookmarkSyncManager::Private::completeUpload() { QJsonDocument jsonDoc = QJsonDocument::fromJson(m_uploadReply->readAll()); QJsonValue dataValue = jsonDoc.object().value(QStringLiteral("data")); m_cloudTimestamp = dataValue.toString(); mDebug() << "Uploaded bookmarks to remote server. Timestamp is " << m_cloudTimestamp; copyLocalToCache(); emit m_q->syncComplete(); } } #include "moc_BookmarkSyncManager.cpp" diff --git a/src/lib/marble/declarative/Bookmarks.cpp b/src/lib/marble/declarative/Bookmarks.cpp index a31d29c1a..f38c9d48d 100644 --- a/src/lib/marble/declarative/Bookmarks.cpp +++ b/src/lib/marble/declarative/Bookmarks.cpp @@ -1,217 +1,217 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2012 Dennis Nienhüser // #include "Bookmarks.h" #include "Planet.h" #include "MarbleQuickItem.h" #include "MarbleModel.h" #include "MarbleMath.h" #include "MarblePlacemarkModel.h" #include "BookmarkManager.h" #include "GeoDataDocument.h" #include "GeoDataPlacemark.h" #include "GeoDataFolder.h" #include "GeoDataTypes.h" #include "GeoDataExtendedData.h" #include "GeoDataTreeModel.h" #include "kdescendantsproxymodel.h" #include "osm/OsmPlacemarkData.h" #include namespace Marble { Bookmarks::Bookmarks( QObject* parent ) : QObject( parent ), m_marbleQuickItem( 0 ), m_proxyModel( 0 ) { // nothing to do } MarbleQuickItem *Bookmarks::map() { return m_marbleQuickItem; } void Bookmarks::setMap( MarbleQuickItem* item ) { m_marbleQuickItem = item; if (item) { connect(item->model()->bookmarkManager(), SIGNAL(bookmarksChanged()), this, SLOT(updateBookmarkDocument())); } updateBookmarkDocument(); emit modelChanged(); } bool Bookmarks::isBookmark( qreal longitude, qreal latitude ) const { if ( !m_marbleQuickItem || !m_marbleQuickItem->model()->bookmarkManager() ) { return false; } Marble::BookmarkManager* manager = m_marbleQuickItem->model()->bookmarkManager(); Marble::GeoDataDocument *bookmarks = manager->document(); Marble::GeoDataCoordinates const compareTo( longitude, latitude, 0.0, Marble::GeoDataCoordinates::Degree ); qreal planetRadius = m_marbleQuickItem->model()->planet()->radius(); for( const Marble::GeoDataFolder* folder: bookmarks->folderList() ) { for( const Marble::GeoDataPlacemark * const placemark: folder->placemarkList() ) { - if ( distanceSphere( placemark->coordinate(), compareTo ) * planetRadius < 5 ) { + if (placemark->coordinate().sphericalDistanceTo(compareTo) * planetRadius < 5) { return true; } } } return false; } Placemark *Bookmarks::placemark(int row) { Placemark* placemark = new Placemark; QModelIndex index = model()->index(row, 0); GeoDataObject *object = model()->data(index, MarblePlacemarkModel::ObjectPointerRole ).value(); if (GeoDataPlacemark *geoDataPlacemark = geodata_cast(object)) { placemark->setGeoDataPlacemark(*geoDataPlacemark); } return placemark; } void Bookmarks::addBookmark(Placemark *placemark, const QString &folderName ) { if ( !m_marbleQuickItem || !m_marbleQuickItem->model()->bookmarkManager() ) { return; } Marble::BookmarkManager* manager = m_marbleQuickItem->model()->bookmarkManager(); Marble::GeoDataDocument *bookmarks = manager->document(); Marble::GeoDataContainer *target = 0; for( Marble::GeoDataFolder* const folder: bookmarks->folderList() ) { if ( folder->name() == folderName ) { target = folder; break; } } if ( !target ) { manager->addNewBookmarkFolder( bookmarks, folderName ); for( Marble::GeoDataFolder* const folder: bookmarks->folderList() ) { if ( folder->name() == folderName ) { target = folder; break; } } Q_ASSERT( target ); } Marble::GeoDataPlacemark bookmark = placemark->placemark(); if (bookmark.name().isEmpty()) { bookmark.setName(placemark->address()); } if (bookmark.name().isEmpty()) { bookmark.setName(bookmark.coordinate().toString(GeoDataCoordinates::Decimal).trimmed()); } bookmark.clearOsmData(); bookmark.setCoordinate(bookmark.coordinate()); // replace non-point geometries with their center manager->addBookmark( target, bookmark ); } void Bookmarks::removeBookmark( qreal longitude, qreal latitude ) { if ( !m_marbleQuickItem || !m_marbleQuickItem->model()->bookmarkManager() ) { return; } Marble::BookmarkManager* manager = m_marbleQuickItem->model()->bookmarkManager(); Marble::GeoDataDocument *bookmarks = manager->document(); Marble::GeoDataCoordinates const compareTo( longitude, latitude, 0.0, Marble::GeoDataCoordinates::Degree ); qreal planetRadius = m_marbleQuickItem->model()->planet()->radius(); for( const Marble::GeoDataFolder* folder: bookmarks->folderList() ) { for( Marble::GeoDataPlacemark * placemark: folder->placemarkList() ) { - if ( distanceSphere( placemark->coordinate(), compareTo ) * planetRadius < 5 ) { + if (placemark->coordinate().sphericalDistanceTo(compareTo) * planetRadius < 5) { manager->removeBookmark( placemark ); return; } } } } void Bookmarks::updateBookmarkDocument() { if (m_marbleQuickItem) { Marble::BookmarkManager* manager = m_marbleQuickItem->model()->bookmarkManager(); m_treeModel.setRootDocument( manager->document() ); } } BookmarksModel *Bookmarks::model() { if ( !m_proxyModel && m_marbleQuickItem && m_marbleQuickItem->model()->bookmarkManager() ) { KDescendantsProxyModel* flattener = new KDescendantsProxyModel( this ); flattener->setSourceModel(&m_treeModel); m_proxyModel = new BookmarksModel( this ); m_proxyModel->setFilterFixedString( Marble::GeoDataTypes::GeoDataPlacemarkType ); m_proxyModel->setFilterKeyColumn( 1 ); m_proxyModel->setSourceModel( flattener ); } return m_proxyModel; } BookmarksModel::BookmarksModel( QObject *parent ) : QSortFilterProxyModel( parent ) { connect( this, SIGNAL(layoutChanged()), this, SIGNAL(countChanged()) ); connect( this, SIGNAL(modelReset()), this, SIGNAL(countChanged()) ); connect( this, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SIGNAL(countChanged()) ); connect( this, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SIGNAL(countChanged()) ); } int BookmarksModel::count() const { return rowCount(); } qreal BookmarksModel::longitude( int idx ) const { if ( idx >= 0 && idx < rowCount() ) { QVariant const value = data( index( idx, 0 ), Marble::MarblePlacemarkModel::CoordinateRole ); Marble::GeoDataCoordinates const coordinates = value.value(); return coordinates.longitude( Marble::GeoDataCoordinates::Degree ); } return 0.0; } qreal BookmarksModel::latitude( int idx ) const { if ( idx >= 0 && idx < rowCount() ) { QVariant const value = data( index( idx, 0 ), Marble::MarblePlacemarkModel::CoordinateRole ); Marble::GeoDataCoordinates const coordinates = value.value(); return coordinates.latitude( Marble::GeoDataCoordinates::Degree ); } return 0.0; } QString BookmarksModel::name( int idx ) const { if ( idx >= 0 && idx < rowCount() ) { return data( index( idx, 0 ) ).toString(); } return QString(); } } #include "moc_Bookmarks.cpp" diff --git a/src/lib/marble/declarative/Coordinate.cpp b/src/lib/marble/declarative/Coordinate.cpp index f59812b0e..94fa11246 100644 --- a/src/lib/marble/declarative/Coordinate.cpp +++ b/src/lib/marble/declarative/Coordinate.cpp @@ -1,101 +1,101 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Dennis Nienhüser // #include "Coordinate.h" #include "MarbleMath.h" using Marble::GeoDataCoordinates; using Marble::EARTH_RADIUS; using Marble::DEG2RAD; Coordinate::Coordinate( qreal lon, qreal lat, qreal alt, QObject *parent ) : QObject( parent ) { setLongitude( lon ); setLatitude( lat ); setAltitude( alt ); } Coordinate::Coordinate(const Marble::GeoDataCoordinates &coordinates) { setCoordinates(coordinates); } qreal Coordinate::longitude() const { return m_coordinate.longitude( GeoDataCoordinates::Degree ); } void Coordinate::setLongitude( qreal lon ) { m_coordinate.setLongitude( lon, GeoDataCoordinates::Degree ); emit longitudeChanged(); } qreal Coordinate::latitude() const { return m_coordinate.latitude( GeoDataCoordinates::Degree ); } void Coordinate::setLatitude( qreal lat ) { m_coordinate.setLatitude( lat, GeoDataCoordinates::Degree ); emit latitudeChanged(); } qreal Coordinate::altitude() const { return m_coordinate.altitude(); } void Coordinate::setAltitude( qreal alt ) { m_coordinate.setAltitude( alt ); emit altitudeChanged(); } GeoDataCoordinates Coordinate::coordinates() const { return m_coordinate; } void Coordinate::setCoordinates( const GeoDataCoordinates &coordinates ) { m_coordinate = coordinates; } qreal Coordinate::distance( qreal longitude, qreal latitude ) const { GeoDataCoordinates::Unit deg = GeoDataCoordinates::Degree; GeoDataCoordinates other( longitude, latitude, 0, deg ); - return EARTH_RADIUS * distanceSphere( coordinates(), other ); + return EARTH_RADIUS * coordinates().sphericalDistanceTo(other); } qreal Coordinate::bearing( qreal longitude, qreal latitude ) const { qreal deltaLon = longitude * DEG2RAD - m_coordinate.longitude(); qreal y = sin( deltaLon ) * cos( latitude * DEG2RAD ); qreal x = cos( m_coordinate.latitude() ) * sin( latitude * DEG2RAD ) - sin( m_coordinate.latitude() ) * cos( latitude * DEG2RAD ) * cos( deltaLon ); return Marble::RAD2DEG * atan2( y, x ); } bool Coordinate::operator == ( const Coordinate &other ) const { return m_coordinate == other.m_coordinate; } bool Coordinate::operator != ( const Coordinate &other ) const { return !operator == ( other ); } #include "moc_Coordinate.cpp" diff --git a/src/lib/marble/declarative/Navigation.cpp b/src/lib/marble/declarative/Navigation.cpp index c9625b8ff..e7e97c811 100644 --- a/src/lib/marble/declarative/Navigation.cpp +++ b/src/lib/marble/declarative/Navigation.cpp @@ -1,355 +1,355 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Dennis Nienhüser // #include "Navigation.h" #include "Planet.h" #include "MarbleModel.h" #include "MarbleQuickItem.h" #include "routing/Route.h" #include "routing/RoutingManager.h" #include "routing/RoutingModel.h" #include "PositionTracking.h" #include "MarbleMath.h" #include "AutoNavigation.h" #include "routing/VoiceNavigationModel.h" #include "ViewportParams.h" #include "GeoDataAccuracy.h" namespace Marble { class NavigationPrivate { public: NavigationPrivate(); MarbleQuickItem * m_marbleQuickItem; bool m_muted; RouteSegment m_currentSegment; AutoNavigation* m_autoNavigation; VoiceNavigationModel m_voiceNavigation; qreal m_nextInstructionDistance; qreal m_destinationDistance; double m_screenAccuracy; QPointF m_screenPosition; RouteSegment nextRouteSegment() const; void updateNextInstructionDistance( const Route &route ); MarbleModel * model() const; QPointF positionOnRoute() const; QPointF currentPosition() const; RouteSegment m_secondLastSegment; RouteSegment m_lastSegment; }; NavigationPrivate::NavigationPrivate() : m_marbleQuickItem( nullptr ), m_muted( false ), m_autoNavigation( 0 ), m_nextInstructionDistance( 0.0 ), m_destinationDistance( 0.0 ), m_screenAccuracy(0) { // nothing to do } void NavigationPrivate::updateNextInstructionDistance( const Route &route ) { const GeoDataCoordinates position = route.position(); const GeoDataCoordinates interpolated = route.positionOnRoute(); const GeoDataCoordinates onRoute = route.currentWaypoint(); qreal planetRadius = 0; if (model()){ planetRadius = model()->planet()->radius(); } - qreal distance = planetRadius * ( distanceSphere( position, interpolated ) + distanceSphere( interpolated, onRoute ) ); + qreal distance = planetRadius * (position.sphericalDistanceTo(interpolated) + interpolated.sphericalDistanceTo(onRoute)); qreal remaining = 0.0; const RouteSegment &segment = route.currentSegment(); for ( int i=0; imodel() : nullptr; } RouteSegment NavigationPrivate::nextRouteSegment() const { // Not using m_currentSegment on purpose return m_marbleQuickItem ? model()->routingManager()->routingModel()->route().currentSegment().nextRouteSegment() : RouteSegment(); } Navigation::Navigation( QObject* parent) : QObject( parent ), d( new NavigationPrivate ) { connect( &d->m_voiceNavigation, SIGNAL(instructionChanged()), this, SIGNAL(voiceNavigationAnnouncementChanged()) ); } Navigation::~Navigation() { delete d; } bool Navigation::guidanceModeEnabled() const { return d->m_marbleQuickItem ? d->model()->routingManager()->guidanceModeEnabled() : false; } void Navigation::setGuidanceModeEnabled( bool enabled ) { if ( d->m_marbleQuickItem ) { d->model()->routingManager()->setGuidanceModeEnabled( enabled ); d->m_autoNavigation->setAutoZoom( enabled ); d->m_autoNavigation->setRecenter( enabled ? AutoNavigation::RecenterOnBorder : AutoNavigation::DontRecenter ); if ( enabled && !d->m_muted ) { //d->m_audio.announceStart(); } } } bool Navigation::muted() const { return d->m_muted; } void Navigation::setMuted(bool enabled) { d->m_muted = enabled; } QString Navigation::nextInstructionText() const { return d->nextRouteSegment().maneuver().instructionText(); } QString Navigation::nextRoad() const { return d->nextRouteSegment().maneuver().roadName(); } QString Navigation::nextInstructionImage() const { switch ( d->nextRouteSegment().maneuver().direction() ) { case Maneuver::Continue: return QStringLiteral("qrc:/marble/turn-continue.svg"); case Maneuver::Merge: return QStringLiteral("qrc:/marble/turn-merge.svg"); case Maneuver::Straight: return QStringLiteral("qrc:/marble/turn-continue.svg"); case Maneuver::SlightRight: return QStringLiteral("qrc:/marble/turn-slight-right.svg"); case Maneuver::Right: return QStringLiteral("qrc:/marble/turn-right.svg"); case Maneuver::SharpRight: return QStringLiteral("qrc:/marble/turn-sharp-right.svg"); case Maneuver::TurnAround: return QStringLiteral("qrc:/marble/turn-around.svg"); case Maneuver::SharpLeft: return QStringLiteral("qrc:/marble/turn-sharp-left.svg"); case Maneuver::Left: return QStringLiteral("qrc:/marble/turn-left.svg"); case Maneuver::SlightLeft: return QStringLiteral("qrc:/marble/turn-slight-left.svg"); case Maneuver::RoundaboutFirstExit: return QStringLiteral("qrc:/marble/turn-roundabout-first.svg"); case Maneuver::RoundaboutSecondExit: return QStringLiteral("qrc:/marble/turn-roundabout-second.svg"); case Maneuver::RoundaboutThirdExit: return QStringLiteral("qrc:/marble/turn-roundabout-third.svg"); case Maneuver::RoundaboutExit: return QStringLiteral("qrc:/marble/turn-roundabout-far.svg"); case Maneuver::ExitLeft: return QStringLiteral("qrc:/marble/turn-exit-left.svg"); case Maneuver::ExitRight: return QStringLiteral("qrc:/marble/turn-exit-right.svg"); case Maneuver::Unknown: default: return QString(); } } qreal Navigation::nextInstructionDistance() const { return d->m_nextInstructionDistance; } qreal Navigation::destinationDistance() const { return d->m_destinationDistance; } QString Navigation::voiceNavigationAnnouncement() const { return d->m_voiceNavigation.instruction(); } QString Navigation::speaker() const { return d->m_voiceNavigation.speaker(); } void Navigation::setSpeaker( const QString &speaker ) { d->m_voiceNavigation.setSpeaker( speaker ); } bool Navigation::deviated() const { if ( d->m_marbleQuickItem ) { RoutingModel const * routingModel = d->model()->routingManager()->routingModel(); return routingModel->deviatedFromRoute(); } return true; } MarbleQuickItem *Navigation::marbleQuickItem() const { return d->m_marbleQuickItem; } QPointF NavigationPrivate::positionOnRoute() const { RoutingModel const * routingModel = model()->routingManager()->routingModel(); GeoDataCoordinates coordinates = routingModel->route().positionOnRoute(); qreal x = 0; qreal y = 0; if (coordinates.isValid()) { m_marbleQuickItem->map()->viewport()->screenCoordinates(coordinates, x, y); } return QPointF(x,y); } QPointF NavigationPrivate::currentPosition() const { GeoDataCoordinates coordinates = model()->positionTracking()->currentLocation(); qreal x = 0; qreal y = 0; m_marbleQuickItem->map()->viewport()->screenCoordinates(coordinates, x, y); return QPointF(x,y); } QPointF Navigation::screenPosition() const { return d->m_screenPosition; } double Navigation::screenAccuracy() const { return d->m_screenAccuracy; } void Navigation::setMarbleQuickItem(MarbleQuickItem *marbleQuickItem) { if ( d->m_marbleQuickItem == marbleQuickItem) { return; } if (d->m_marbleQuickItem) { disconnect( d->model()->routingManager()->routingModel(), SIGNAL(positionChanged()), this, SLOT(update()) ); disconnect( d->m_autoNavigation, SIGNAL(zoomIn(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomIn()) ); disconnect( d->m_autoNavigation, SIGNAL(zoomOut(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomOut()) ); disconnect( d->m_autoNavigation, SIGNAL(centerOn(GeoDataCoordinates,bool)), d->m_marbleQuickItem, SLOT(centerOn(GeoDataCoordinates)) ); disconnect( d->m_marbleQuickItem, SIGNAL(visibleLatLonAltBoxChanged()), d->m_autoNavigation, SLOT(inhibitAutoAdjustments()) ); } d->m_marbleQuickItem = marbleQuickItem; if ( d->m_marbleQuickItem ) { d->model()->routingManager()->setShowGuidanceModeStartupWarning( false ); connect( d->model()->routingManager()->routingModel(), SIGNAL(positionChanged()), this, SLOT(update()) ); connect( d->model()->routingManager()->routingModel(), SIGNAL(deviatedFromRoute(bool)), this, SIGNAL(deviationChanged()) ); delete d->m_autoNavigation; d->m_autoNavigation = new AutoNavigation( d->model(), d->m_marbleQuickItem->map()->viewport(), this ); connect( d->m_autoNavigation, SIGNAL(zoomIn(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomIn()) ); connect( d->m_autoNavigation, SIGNAL(zoomOut(FlyToMode)), d->m_marbleQuickItem, SLOT(zoomOut()) ); connect( d->m_autoNavigation, SIGNAL(centerOn(GeoDataCoordinates,bool)), d->m_marbleQuickItem, SLOT(centerOn(GeoDataCoordinates)) ); connect( d->m_marbleQuickItem, SIGNAL(visibleLatLonAltBoxChanged()), d->m_autoNavigation, SLOT(inhibitAutoAdjustments()) ); connect( d->m_marbleQuickItem, SIGNAL(visibleLatLonAltBoxChanged()), this, SLOT(updateScreenPosition()) ); connect( d->model()->positionTracking(), SIGNAL(gpsLocation(GeoDataCoordinates,qreal)), this, SLOT(updateScreenPosition()) ); connect( d->model()->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)), this, SLOT(updateScreenPosition()) ); } emit marbleQuickItemChanged(marbleQuickItem); } void Navigation::update() { if (!d->model()) { return; } RoutingModel const * routingModel = d->model()->routingManager()->routingModel(); d->updateNextInstructionDistance( routingModel->route() ); emit nextInstructionDistanceChanged(); emit destinationDistanceChanged(); RouteSegment segment = routingModel->route().currentSegment(); if ( !d->m_muted ) { d->m_voiceNavigation.update( routingModel->route(), d->m_nextInstructionDistance, d->m_destinationDistance, routingModel->deviatedFromRoute() ); } if ( segment != d->m_currentSegment ) { d->m_currentSegment = segment; emit nextInstructionTextChanged(); emit nextInstructionImageChanged(); emit nextRoadChanged(); } updateScreenPosition(); } void Navigation::updateScreenPosition() { if(d->m_marbleQuickItem) { double distanceMeter = d->model()->positionTracking()->accuracy().horizontal; d->m_screenAccuracy = distanceMeter * d->m_marbleQuickItem->map()->radius() / d->model()->planetRadius(); emit screenAccuracyChanged(); d->m_screenPosition = deviated() ? d->currentPosition() : d->positionOnRoute(); emit screenPositionChanged(); } } } #include "moc_Navigation.cpp" diff --git a/src/lib/marble/geodata/data/GeoDataCoordinates.cpp b/src/lib/marble/geodata/data/GeoDataCoordinates.cpp index 2e7e9032c..e0a419641 100644 --- a/src/lib/marble/geodata/data/GeoDataCoordinates.cpp +++ b/src/lib/marble/geodata/data/GeoDataCoordinates.cpp @@ -1,1177 +1,1188 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2004-2007 Torsten Rahn // Copyright 2007-2008 Inge Wallin // Copyright 2008 Patrick Spendrin // Copyright 2011 Friedrich W. H. Kossebau // Copyright 2011 Bernhard Beschow // Copyright 2015 Alejandro Garcia Montoro // #include "GeoDataCoordinates.h" #include "GeoDataCoordinates_p.h" #include "LonLatParser_p.h" #include #include #include #include "MarbleGlobal.h" #include "MarbleDebug.h" +#include "MarbleMath.h" #include "Quaternion.h" namespace Marble { const qreal GeoDataCoordinatesPrivate::sm_semiMajorAxis = 6378137.0; const qreal GeoDataCoordinatesPrivate::sm_semiMinorAxis = 6356752.314; const qreal GeoDataCoordinatesPrivate::sm_eccentricitySquared = 6.69437999013e-03; const qreal GeoDataCoordinatesPrivate::sm_utmScaleFactor = 0.9996; GeoDataCoordinates::Notation GeoDataCoordinates::s_notation = GeoDataCoordinates::DMS; const GeoDataCoordinates GeoDataCoordinates::null = GeoDataCoordinates( 0, 0, 0 ); // don't use default constructor! GeoDataCoordinates::GeoDataCoordinates( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit, int _detail ) : d( new GeoDataCoordinatesPrivate( _lon, _lat, _alt, unit, _detail ) ) { d->ref.ref(); } /* simply copy the d pointer * it will be replaced in the detach function instead */ GeoDataCoordinates::GeoDataCoordinates( const GeoDataCoordinates& other ) : d( other.d ) { d->ref.ref(); } /* simply copy null's d pointer * it will be replaced in the detach function */ GeoDataCoordinates::GeoDataCoordinates() : d( null.d ) { d->ref.ref(); } /* * only delete the private d pointer if the number of references is 0 * remember that all copies share the same d pointer! */ GeoDataCoordinates::~GeoDataCoordinates() { delete d->m_q; d->m_q = 0; if (!d->ref.deref()) delete d; #ifdef DEBUG_GEODATA // mDebug() << "delete coordinates"; #endif } bool GeoDataCoordinates::isValid() const { return d != null.d; } /* * if only one copy exists, return * else make a new private d pointer object and assign the values of the current * one to it * at the end, if the number of references thus reaches 0 delete it * this state shouldn't happen, but if it does, we have to clean up behind us. */ void GeoDataCoordinates::detach() { delete d->m_q; d->m_q = 0; if(d->ref.load() == 1) { return; } GeoDataCoordinatesPrivate *new_d = new GeoDataCoordinatesPrivate( *d ); if (!d->ref.deref()) { delete d; } d = new_d; d->ref.ref(); } /* * call detach() at the start of all non-static, non-const functions */ void GeoDataCoordinates::set( qreal _lon, qreal _lat, qreal _alt, GeoDataCoordinates::Unit unit ) { detach(); d->m_altitude = _alt; switch( unit ){ default: case Radian: d->m_lon = _lon; d->m_lat = _lat; break; case Degree: d->m_lon = _lon * DEG2RAD; d->m_lat = _lat * DEG2RAD; break; } } /* * call detach() at the start of all non-static, non-const functions */ void GeoDataCoordinates::setLongitude( qreal _lon, GeoDataCoordinates::Unit unit ) { detach(); switch( unit ){ default: case Radian: d->m_lon = _lon; break; case Degree: d->m_lon = _lon * DEG2RAD; break; } } /* * call detach() at the start of all non-static, non-const functions */ void GeoDataCoordinates::setLatitude( qreal _lat, GeoDataCoordinates::Unit unit ) { detach(); switch( unit ){ case Radian: d->m_lat = _lat; break; case Degree: d->m_lat = _lat * DEG2RAD; break; } } void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, GeoDataCoordinates::Unit unit ) const { switch ( unit ) { default: case Radian: lon = d->m_lon; lat = d->m_lat; break; case Degree: lon = d->m_lon * RAD2DEG; lat = d->m_lat * RAD2DEG; break; } } void GeoDataCoordinates::geoCoordinates(qreal &lon, qreal &lat) const { lon = d->m_lon; lat = d->m_lat; } void GeoDataCoordinates::geoCoordinates( qreal& lon, qreal& lat, qreal& alt, GeoDataCoordinates::Unit unit ) const { geoCoordinates( lon, lat, unit ); alt = d->m_altitude; } void GeoDataCoordinates::geoCoordinates(qreal &lon, qreal &lat, qreal &alt) const { lon = d->m_lon; lat = d->m_lat; alt = d->m_altitude; } qreal GeoDataCoordinates::longitude( GeoDataCoordinates::Unit unit ) const { switch ( unit ) { default: case Radian: return d->m_lon; case Degree: return d->m_lon * RAD2DEG; } } qreal GeoDataCoordinates::longitude() const { return d->m_lon; } qreal GeoDataCoordinates::latitude( GeoDataCoordinates::Unit unit ) const { switch ( unit ) { default: case Radian: return d->m_lat; case Degree: return d->m_lat * RAD2DEG; } } qreal GeoDataCoordinates::latitude() const { return d->m_lat; } //static GeoDataCoordinates::Notation GeoDataCoordinates::defaultNotation() { return s_notation; } //static void GeoDataCoordinates::setDefaultNotation( GeoDataCoordinates::Notation notation ) { s_notation = notation; } //static qreal GeoDataCoordinates::normalizeLon( qreal lon, GeoDataCoordinates::Unit unit ) { qreal halfCircle; if ( unit == GeoDataCoordinates::Radian ) { halfCircle = M_PI; } else { halfCircle = 180; } if ( lon > halfCircle ) { int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) ); return lon - ( cycles * 2 * halfCircle ); } if ( lon < -halfCircle ) { int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) ); return lon - ( cycles * 2 * halfCircle ); } return lon; } //static qreal GeoDataCoordinates::normalizeLat( qreal lat, GeoDataCoordinates::Unit unit ) { qreal halfCircle; if ( unit == GeoDataCoordinates::Radian ) { halfCircle = M_PI; } else { halfCircle = 180; } if ( lat > ( halfCircle / 2.0 ) ) { int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { // pi/2 < lat < pi temp = halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( halfCircle / 2.0 ) ) { return ( halfCircle - temp ); } if ( temp < ( -halfCircle / 2.0 ) ) { return ( -halfCircle - temp ); } return temp; } if ( lat < ( -halfCircle / 2.0 ) ) { int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { temp = -halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( +halfCircle / 2.0 ) ) { return ( +halfCircle - temp ); } if ( temp < ( -halfCircle / 2.0 ) ) { return ( -halfCircle - temp ); } return temp; } return lat; } //static void GeoDataCoordinates::normalizeLonLat( qreal &lon, qreal &lat, GeoDataCoordinates::Unit unit ) { qreal halfCircle; if ( unit == GeoDataCoordinates::Radian ) { halfCircle = M_PI; } else { halfCircle = 180; } if ( lon > +halfCircle ) { int cycles = (int)( ( lon + halfCircle ) / ( 2 * halfCircle ) ); lon = lon - ( cycles * 2 * halfCircle ); } if ( lon < -halfCircle ) { int cycles = (int)( ( lon - halfCircle ) / ( 2 * halfCircle ) ); lon = lon - ( cycles * 2 * halfCircle ); } if ( lat > ( +halfCircle / 2.0 ) ) { int cycles = (int)( ( lat + halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { // pi/2 < lat < pi temp = halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( +halfCircle / 2.0 ) ) { lat = +halfCircle - temp; } if ( temp < ( -halfCircle / 2.0 ) ) { lat = -halfCircle - temp; } lat = temp; if( lon > 0 ) { lon = -halfCircle + lon; } else { lon = halfCircle + lon; } } if ( lat < ( -halfCircle / 2.0 ) ) { int cycles = (int)( ( lat - halfCircle ) / ( 2 * halfCircle ) ); qreal temp; if( cycles == 0 ) { temp = -halfCircle - lat; } else { temp = lat - ( cycles * 2 * halfCircle ); } if ( temp > ( +halfCircle / 2.0 ) ) { lat = +halfCircle - temp; } if ( temp < ( -halfCircle / 2.0 ) ) { lat = -halfCircle - temp; } lat = temp; if( lon > 0 ) { lon = -halfCircle + lon; } else { lon = halfCircle + lon; } } return; } GeoDataCoordinates GeoDataCoordinates::fromString( const QString& string, bool& successful ) { LonLatParser parser; successful = parser.parse(string); if (successful) { return GeoDataCoordinates( parser.lon(), parser.lat(), 0, GeoDataCoordinates::Degree ); } else { return GeoDataCoordinates(); } } QString GeoDataCoordinates::toString() const { return GeoDataCoordinates::toString( s_notation ); } QString GeoDataCoordinates::toString( GeoDataCoordinates::Notation notation, int precision ) const { QString coordString; if( notation == GeoDataCoordinates::UTM ){ int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat); // Handle lack of UTM zone number in the poles const QString zoneString = (zoneNumber > 0) ? QString::number(zoneNumber) : QString(); QString bandString = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat); QString eastingString = QString::number(GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat), 'f', 2); QString northingString = QString::number(GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat), 'f', 2); return QString("%1%2 %3 m E, %4 m N").arg(zoneString, bandString, eastingString, northingString); } else{ coordString = lonToString( d->m_lon, notation, Radian, precision ) + QLatin1String(", ") + latToString( d->m_lat, notation, Radian, precision ); } return coordString; } QString GeoDataCoordinates::lonToString( qreal lon, GeoDataCoordinates::Notation notation, GeoDataCoordinates::Unit unit, int precision, char format ) { if( notation == GeoDataCoordinates::UTM ){ /** * @FIXME: UTM needs lon + lat to know zone number and easting * By now, this code returns the zone+easting of the point * (lon, equator), but this can differ a lot at different locations * See bug 347536 https://bugs.kde.org/show_bug.cgi?id=347536 */ qreal lonRad = ( unit == Radian ) ? lon : lon * DEG2RAD; int zoneNumber = GeoDataCoordinatesPrivate::lonLatToZone(lonRad, 0); // Handle lack of UTM zone number in the poles QString result = (zoneNumber > 0) ? QString::number(zoneNumber) : QString(); if(precision > 0){ QString eastingString = QString::number( GeoDataCoordinatesPrivate::lonLatToEasting(lonRad, 0), 'f', 2 ); result += QString(" %1 m E").arg(eastingString); } return result; } QString weString = ( lon < 0 ) ? tr("W") : tr("E"); QString lonString; qreal lonDegF = ( unit == Degree ) ? fabs( lon ) : fabs( (qreal)(lon) * RAD2DEG ); // Take care of -1 case precision = ( precision < 0 ) ? 5 : precision; if ( notation == DMS || notation == DM ) { int lonDeg = (int) lonDegF; qreal lonMinF = 60 * (lonDegF - lonDeg); int lonMin = (int) lonMinF; qreal lonSecF = 60 * (lonMinF - lonMin); int lonSec = (int) lonSecF; // Adjustment for fuzziness (like 49.999999999999999999999) if ( precision == 0 ) { lonDeg = qRound( lonDegF ); } else if ( precision <= 2 ) { lonMin = qRound( lonMinF ); } else if ( precision <= 4 && notation == DMS ) { lonSec = qRound( lonSecF ); } else { if ( notation == DMS ) { lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); } else { lonMin = lonMinF = qRound( lonMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 ); } } if (lonSec > 59 && notation == DMS ) { lonSec = lonSecF = 0; lonMin = lonMinF = lonMinF + 1; } if (lonMin > 59) { lonMin = lonMinF = 0; lonDeg = lonDegF = lonDegF + 1; } // Evaluate the string lonString = QString::fromUtf8("%1\xc2\xb0").arg(lonDeg, 3, 10, QLatin1Char(' ')); if ( precision == 0 ) { return lonString + weString; } if ( notation == DMS || precision < 3 ) { lonString += QString(" %2\'").arg(lonMin, 2, 10, QLatin1Char('0')); } if ( precision < 3 ) { return lonString + weString; } if ( notation == DMS ) { // Includes -1 case! if ( precision < 5 ) { lonString += QString(" %3\"").arg(lonSec, 2, 'f', 0, QLatin1Char('0')); return lonString + weString; } lonString += QString(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QLatin1Char('0')); } else { lonString += QString(" %L3'").arg(lonMinF, precision + 1, 'f', precision - 2, QLatin1Char('0')); } } else if ( notation == GeoDataCoordinates::Decimal ) { lonString = QString::fromUtf8("%L1\xc2\xb0").arg(lonDegF, 4 + precision, format, precision, QLatin1Char(' ')); } else if ( notation == GeoDataCoordinates::Astro ) { if (lon < 0) { lon += ( unit == Degree ) ? 360 : 2 * M_PI; } qreal lonHourF = ( unit == Degree ) ? fabs( lon/15.0 ) : fabs( (qreal)(lon/15.0) * RAD2DEG ); int lonHour = (int) lonHourF; qreal lonMinF = 60 * (lonHourF - lonHour); int lonMin = (int) lonMinF; qreal lonSecF = 60 * (lonMinF - lonMin); int lonSec = (int) lonSecF; // Adjustment for fuzziness (like 49.999999999999999999999) if ( precision == 0 ) { lonHour = qRound( lonHourF ); } else if ( precision <= 2 ) { lonMin = qRound( lonMinF ); } else if ( precision <= 4 ) { lonSec = qRound( lonSecF ); } else { lonSec = lonSecF = qRound( lonSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); } if (lonSec > 59 ) { lonSec = lonSecF = 0; lonMin = lonMinF = lonMinF + 1; } if (lonMin > 59) { lonMin = lonMinF = 0; lonHour = lonHourF = lonHourF + 1; } // Evaluate the string lonString = QString::fromUtf8("%1h").arg(lonHour, 3, 10, QLatin1Char(' ')); if ( precision == 0 ) { return lonString; } lonString += QString(" %2\'").arg(lonMin, 2, 10, QLatin1Char('0')); if ( precision < 3 ) { return lonString; } // Includes -1 case! if ( precision < 5 ) { lonString += QString(" %3\"").arg(lonSec, 2, 'f', 0, QLatin1Char('0')); return lonString; } lonString += QString(" %L3\"").arg(lonSecF, precision - 1, 'f', precision - 4, QLatin1Char('0')); return lonString; } return lonString + weString; } QString GeoDataCoordinates::lonToString() const { return GeoDataCoordinates::lonToString( d->m_lon , s_notation ); } QString GeoDataCoordinates::latToString( qreal lat, GeoDataCoordinates::Notation notation, GeoDataCoordinates::Unit unit, int precision, char format ) { if( notation == GeoDataCoordinates::UTM ){ /** * @FIXME: UTM needs lon + lat to know latitude band and northing * By now, this code returns the band+northing of the point * (meridian, lat), but this can differ a lot at different locations * See bug 347536 https://bugs.kde.org/show_bug.cgi?id=347536 */ qreal latRad = ( unit == Radian ) ? lat : lat * DEG2RAD; QString result = GeoDataCoordinatesPrivate::lonLatToLatitudeBand(0, latRad); if ( precision > 0 ){ QString northingString = QString::number( GeoDataCoordinatesPrivate::lonLatToNorthing(0, latRad), 'f', 2 ); result += QString(" %1 m N").arg(northingString); } return result; } QString pmString; QString nsString; if (notation == GeoDataCoordinates::Astro){ pmString = ( lat > 0 ) ? "+" : "-"; } else { nsString = ( lat > 0 ) ? tr("N") : tr("S"); } QString latString; qreal latDegF = ( unit == Degree ) ? fabs( lat ) : fabs( (qreal)(lat) * RAD2DEG ); // Take care of -1 case precision = ( precision < 0 ) ? 5 : precision; if ( notation == DMS || notation == DM || notation == Astro) { int latDeg = (int) latDegF; qreal latMinF = 60 * (latDegF - latDeg); int latMin = (int) latMinF; qreal latSecF = 60 * (latMinF - latMin); int latSec = (int) latSecF; // Adjustment for fuzziness (like 49.999999999999999999999) if ( precision == 0 ) { latDeg = qRound( latDegF ); } else if ( precision <= 2 ) { latMin = qRound( latMinF ); } else if ( precision <= 4 && notation == DMS ) { latSec = qRound( latSecF ); } else { if ( notation == DMS || notation == Astro ) { latSec = latSecF = qRound( latSecF * qPow( 10, precision - 4 ) ) / qPow( 10, precision - 4 ); } else { latMin = latMinF = qRound( latMinF * qPow( 10, precision - 2 ) ) / qPow( 10, precision - 2 ); } } if (latSec > 59 && ( notation == DMS || notation == Astro )) { latSecF = 0; latSec = latSecF; latMin = latMin + 1; } if (latMin > 59) { latMinF = 0; latMin = latMinF; latDeg = latDeg + 1; } // Evaluate the string latString = QString::fromUtf8("%1\xc2\xb0").arg(latDeg, 3, 10, QLatin1Char(' ')); if ( precision == 0 ) { return pmString + latString + nsString; } if ( notation == DMS || notation == Astro || precision < 3 ) { latString += QString(" %2\'").arg(latMin, 2, 10, QLatin1Char('0')); } if ( precision < 3 ) { return pmString + latString + nsString; } if ( notation == DMS || notation == Astro ) { // Includes -1 case! if ( precision < 5 ) { latString += QString(" %3\"").arg(latSec, 2, 'f', 0, QLatin1Char('0')); return latString + nsString; } latString += QString(" %L3\"").arg(latSecF, precision - 1, 'f', precision - 4, QLatin1Char('0')); } else { latString += QString(" %L3'").arg(latMinF, precision + 1, 'f', precision - 2, QLatin1Char('0')); } } else // notation = GeoDataCoordinates::Decimal { latString = QString::fromUtf8("%L1\xc2\xb0").arg(latDegF, 4 + precision, format, precision, QLatin1Char(' ')); } return pmString + latString + nsString; } QString GeoDataCoordinates::latToString() const { return GeoDataCoordinates::latToString( d->m_lat, s_notation ); } bool GeoDataCoordinates::operator==( const GeoDataCoordinates &rhs ) const { return *d == *rhs.d; } bool GeoDataCoordinates::operator!=( const GeoDataCoordinates &rhs ) const { return *d != *rhs.d; } void GeoDataCoordinates::setAltitude( const qreal altitude ) { detach(); d->m_altitude = altitude; } qreal GeoDataCoordinates::altitude() const { return d->m_altitude; } int GeoDataCoordinates::utmZone() const{ return GeoDataCoordinatesPrivate::lonLatToZone(d->m_lon, d->m_lat); } qreal GeoDataCoordinates::utmEasting() const{ return GeoDataCoordinatesPrivate::lonLatToEasting(d->m_lon, d->m_lat); } QString GeoDataCoordinates::utmLatitudeBand() const{ return GeoDataCoordinatesPrivate::lonLatToLatitudeBand(d->m_lon, d->m_lat); } qreal GeoDataCoordinates::utmNorthing() const{ return GeoDataCoordinatesPrivate::lonLatToNorthing(d->m_lon, d->m_lat); } quint8 GeoDataCoordinates::detail() const { return d->m_detail; } void GeoDataCoordinates::setDetail(quint8 detail) { detach(); d->m_detail = detail; } GeoDataCoordinates GeoDataCoordinates::rotateAround( const GeoDataCoordinates &axis, qreal angle, Unit unit ) const { const Quaternion quatAxis = Quaternion::fromEuler( -axis.latitude() , axis.longitude(), 0 ); const Quaternion rotationAmount = Quaternion::fromEuler( 0, 0, unit == Radian ? angle : angle * DEG2RAD ); const Quaternion resultAxis = quatAxis * rotationAmount * quatAxis.inverse(); Quaternion rotatedQuat = quaternion(); rotatedQuat.rotateAroundAxis(resultAxis); qreal rotatedLon, rotatedLat; rotatedQuat.getSpherical(rotatedLon, rotatedLat); return GeoDataCoordinates(rotatedLon, rotatedLat, altitude()); } qreal GeoDataCoordinates::bearing( const GeoDataCoordinates &other, Unit unit, BearingType type ) const { if ( type == FinalBearing ) { double const offset = unit == Degree ? 180.0 : M_PI; return offset + other.bearing( *this, unit, InitialBearing ); } qreal const delta = other.d->m_lon - d->m_lon; double const bearing = atan2( sin ( delta ) * cos ( other.d->m_lat ), cos( d->m_lat ) * sin( other.d->m_lat ) - sin( d->m_lat ) * cos( other.d->m_lat ) * cos ( delta ) ); return unit == Radian ? bearing : bearing * RAD2DEG; } GeoDataCoordinates GeoDataCoordinates::moveByBearing( qreal bearing, qreal distance ) const { qreal newLat = asin( sin(d->m_lat) * cos(distance) + cos(d->m_lat) * sin(distance) * cos(bearing) ); qreal newLon = d->m_lon + atan2( sin(bearing) * sin(distance) * cos(d->m_lat), cos(distance) - sin(d->m_lat) * sin(newLat) ); return GeoDataCoordinates( newLon, newLat ); } const Quaternion& GeoDataCoordinates::quaternion() const { if (d->m_q == 0) { d->m_q = new Quaternion(Quaternion::fromSpherical( d->m_lon , d->m_lat )); } return *d->m_q; } GeoDataCoordinates GeoDataCoordinates::interpolate( const GeoDataCoordinates &target, double t_ ) const { double const t = qBound( 0.0, t_, 1.0 ); Quaternion const quat = Quaternion::slerp( quaternion(), target.quaternion(), t ); qreal lon, lat; quat.getSpherical( lon, lat ); double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude; return GeoDataCoordinates( lon, lat, alt ); } GeoDataCoordinates GeoDataCoordinates::interpolate( const GeoDataCoordinates &before, const GeoDataCoordinates &target, const GeoDataCoordinates &after, double t_ ) const { double const t = qBound( 0.0, t_, 1.0 ); Quaternion const b1 = GeoDataCoordinatesPrivate::basePoint( before.quaternion(), quaternion(), target.quaternion() ); Quaternion const a2 = GeoDataCoordinatesPrivate::basePoint( quaternion(), target.quaternion(), after.quaternion() ); Quaternion const a = Quaternion::slerp( quaternion(), target.quaternion(), t ); Quaternion const b = Quaternion::slerp( b1, a2, t ); Quaternion c = Quaternion::slerp( a, b, 2 * t * (1.0-t) ); qreal lon, lat; c.getSpherical( lon, lat ); // @todo spline interpolation of altitude? double const alt = (1.0-t) * d->m_altitude + t * target.d->m_altitude; return GeoDataCoordinates( lon, lat, alt ); } bool GeoDataCoordinates::isPole( Pole pole ) const { // Evaluate the most likely case first: // The case where we haven't hit the pole and where our latitude is normalized // to the range of 90 deg S ... 90 deg N if ( fabs( (qreal) 2.0 * d->m_lat ) < M_PI ) { return false; } else { if ( fabs( (qreal) 2.0 * d->m_lat ) == M_PI ) { // Ok, we have hit a pole. Now let's check whether it's the one we've asked for: if ( pole == AnyPole ){ return true; } else { if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) { return true; } if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) { return true; } return false; } } // else { // FIXME: Should we just normalize latitude and longitude and be done? // While this might work well for persistent data it would create some // possible overhead for temporary data, so this needs careful thinking. mDebug() << "GeoDataCoordinates not normalized!"; // Only as a last resort we cover the unlikely case where // the latitude is not normalized to the range of // 90 deg S ... 90 deg N if ( fabs( (qreal) 2.0 * normalizeLat( d->m_lat ) ) < M_PI ) { return false; } else { // Ok, we have hit a pole. Now let's check whether it's the one we've asked for: if ( pole == AnyPole ){ return true; } else { if ( pole == NorthPole && 2.0 * d->m_lat == +M_PI ) { return true; } if ( pole == SouthPole && 2.0 * d->m_lat == -M_PI ) { return true; } return false; } } } } } +qreal GeoDataCoordinates::sphericalDistanceTo(const GeoDataCoordinates &other) const +{ + qreal lon2, lat2; + other.geoCoordinates( lon2, lat2 ); + + // FIXME: Take the altitude into account! + + return distanceSphere(d->m_lon, d->m_lat, lon2, lat2); +} + GeoDataCoordinates& GeoDataCoordinates::operator=( const GeoDataCoordinates &other ) { qAtomicAssign(d, other.d); return *this; } void GeoDataCoordinates::pack( QDataStream& stream ) const { stream << d->m_lon; stream << d->m_lat; stream << d->m_altitude; } void GeoDataCoordinates::unpack( QDataStream& stream ) { // call detach even though it shouldn't be needed - one never knows detach(); stream >> d->m_lon; stream >> d->m_lat; stream >> d->m_altitude; } Quaternion GeoDataCoordinatesPrivate::basePoint( const Quaternion &q1, const Quaternion &q2, const Quaternion &q3 ) { Quaternion const a = (q2.inverse() * q3).log(); Quaternion const b = (q2.inverse() * q1).log(); return q2 * ((a+b)*-0.25).exp(); } qreal GeoDataCoordinatesPrivate::arcLengthOfMeridian( qreal phi ) { // Precalculate n qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis) / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis); // Precalculate alpha qreal const alpha = ( (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0) * (1.0 + (qPow (n, 2.0) / 4.0) + (qPow (n, 4.0) / 64.0) ); // Precalculate beta qreal const beta = (-3.0 * n / 2.0) + (9.0 * qPow (n, 3.0) / 16.0) + (-3.0 * qPow (n, 5.0) / 32.0); // Precalculate gamma qreal const gamma = (15.0 * qPow (n, 2.0) / 16.0) + (-15.0 * qPow (n, 4.0) / 32.0); // Precalculate delta qreal const delta = (-35.0 * qPow (n, 3.0) / 48.0) + (105.0 * qPow (n, 5.0) / 256.0); // Precalculate epsilon qreal const epsilon = (315.0 * qPow (n, 4.0) / 512.0); // Now calculate the sum of the series and return qreal const result = alpha * (phi + (beta * qSin (2.0 * phi)) + (gamma * qSin (4.0 * phi)) + (delta * qSin (6.0 * phi)) + (epsilon * qSin (8.0 * phi))); return result; } qreal GeoDataCoordinatesPrivate::centralMeridianUTM( qreal zone ) { return DEG2RAD*(-183.0 + (zone * 6.0)); } qreal GeoDataCoordinatesPrivate::footpointLatitude( qreal northing ) { // Precalculate n (Eq. 10.18) qreal const n = (GeoDataCoordinatesPrivate::sm_semiMajorAxis - GeoDataCoordinatesPrivate::sm_semiMinorAxis) / (GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis); // Precalculate alpha (Eq. 10.22) // (Same as alpha in Eq. 10.17) qreal const alpha = ((GeoDataCoordinatesPrivate::sm_semiMajorAxis + GeoDataCoordinatesPrivate::sm_semiMinorAxis) / 2.0) * (1 + (qPow (n, 2.0) / 4) + (qPow (n, 4.0) / 64)); // Precalculate y (Eq. 10.23) qreal const y = northing / alpha; // Precalculate beta (Eq. 10.22) qreal const beta = (3.0 * n / 2.0) + (-27.0 * qPow (n, 3.0) / 32.0) + (269.0 * qPow (n, 5.0) / 512.0); // Precalculate gamma (Eq. 10.22) qreal const gamma = (21.0 * qPow (n, 2.0) / 16.0) + (-55.0 * qPow (n, 4.0) / 32.0); // Precalculate delta (Eq. 10.22) qreal const delta = (151.0 * qPow (n, 3.0) / 96.0) + (-417.0 * qPow (n, 5.0) / 128.0); // Precalculate epsilon (Eq. 10.22) qreal const epsilon = (1097.0 * qPow (n, 4.0) / 512.0); // Now calculate the sum of the series (Eq. 10.21) qreal const result = y + (beta * qSin (2.0 * y)) + (gamma * qSin (4.0 * y)) + (delta * qSin (6.0 * y)) + (epsilon * qSin (8.0 * y)); return result; } QPointF GeoDataCoordinatesPrivate::mapLonLatToXY( qreal lambda, qreal phi, qreal lambda0 ) { // Equation (10.15) // Precalculate second numerical eccentricity qreal const ep2 = (qPow (GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) - qPow (GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0)) / qPow (GeoDataCoordinatesPrivate::sm_semiMinorAxis, 2.0); // Precalculate the square of nu, just an auxiliar quantity qreal const nu2 = ep2 * qPow (qCos(phi), 2.0); // Precalculate the radius of curvature in prime vertical qreal const N = qPow (GeoDataCoordinatesPrivate::sm_semiMajorAxis, 2.0) / (GeoDataCoordinatesPrivate::sm_semiMinorAxis * qSqrt (1 + nu2)); // Precalculate the tangent of phi and its square qreal const t = qTan (phi); qreal const t2 = t * t; // Precalculate longitude difference qreal const l = lambda - lambda0; /* * Precalculate coefficients for l**n in the equations below * so a normal human being can read the expressions for easting * and northing * -- l**1 and l**2 have coefficients of 1.0 * * The actual used coefficients starts at coef[1], just to * follow the meaningful nomenclature in equation 10.15 * (coef[n] corresponds to qPow(l,n) factor) */ QVector coef(9); coef[0] = coef[1] = coef[2] = 1.0; coef[3] = 1.0 - t2 + nu2; coef[4] = 5.0 - t2 + 9 * nu2 + 4.0 * (nu2 * nu2); coef[5] = 5.0 - 18.0 * t2 + (t2 * t2) + 14.0 * nu2 - 58.0 * t2 * nu2; coef[6] = 61.0 - 58.0 * t2 + (t2 * t2) + 270.0 * nu2 - 330.0 * t2 * nu2; coef[7] = 61.0 - 479.0 * t2 + 179.0 * (t2 * t2) - (t2 * t2 * t2); coef[8] = 1385.0 - 3111.0 * t2 + 543.0 * (t2 * t2) - (t2 * t2 * t2); // Calculate easting (x) qreal easting = N * qCos(phi) * coef[1] * l + (N / 6.0 * qPow (qCos(phi), 3.0) * coef[3] * qPow (l, 3.0)) + (N / 120.0 * qPow (qCos(phi), 5.0) * coef[5] * qPow (l, 5.0)) + (N / 5040.0 * qPow (qCos(phi), 7.0) * coef[7] * qPow (l, 7.0)); // Calculate northing (y) qreal northing = arcLengthOfMeridian (phi) + (t / 2.0 * N * qPow (qCos(phi), 2.0) * coef[2] * qPow (l, 2.0)) + (t / 24.0 * N * qPow (qCos(phi), 4.0) * coef[4] * qPow (l, 4.0)) + (t / 720.0 * N * qPow (qCos(phi), 6.0) * coef[6] * qPow (l, 6.0)) + (t / 40320.0 * N * qPow (qCos(phi), 8.0) * coef[8] * qPow (l, 8.0)); return QPointF(easting, northing); } int GeoDataCoordinatesPrivate::lonLatToZone( qreal lon, qreal lat ){ // Converts lon and lat to degrees qreal lonDeg = lon * RAD2DEG; qreal latDeg = lat * RAD2DEG; /* Round the value of the longitude when the distance to the nearest integer * is less than 0.0000001. This avoids fuzzy values such as -114.0000000001, which * can produce a misbehaviour when calculating the zone associated at the borders * of the zone intervals (for example, the interval [-114, -108[ is associated with * zone number 12; if the following rounding is not done, the value returned by * lonLatToZone(114,0) is 11 instead of 12, as the function actually receives * -114.0000000001, which is in the interval [-120,-114[, associated to zone 11 */ qreal precision = 0.0000001; if ( qAbs(lonDeg - qFloor(lonDeg)) < precision || qAbs(lonDeg - qCeil(lonDeg)) < precision ){ lonDeg = qRound(lonDeg); } // There is no numbering associated to the poles, special value 0 is returned. if ( latDeg < -80 || latDeg > 84 ) { return 0; } // Obtains the zone number handling all the so called "exceptions" // See problem: http://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system#Exceptions // See solution: http://gis.stackexchange.com/questions/13291/computing-utm-zone-from-lat-long-point // General int zoneNumber = static_cast( (lonDeg+180) / 6.0 ) + 1; // Southwest Norway if ( latDeg >= 56 && latDeg < 64 && lonDeg >= 3 && lonDeg < 12 ) { zoneNumber = 32; } // Svalbard if ( latDeg >= 72 && latDeg < 84 ) { if ( lonDeg >= 0 && lonDeg < 9 ) { zoneNumber = 31; } else if ( lonDeg >= 9 && lonDeg < 21 ) { zoneNumber = 33; } else if ( lonDeg >= 21 && lonDeg < 33 ) { zoneNumber = 35; } else if ( lonDeg >= 33 && lonDeg < 42 ) { zoneNumber = 37; } } return zoneNumber; } qreal GeoDataCoordinatesPrivate::lonLatToEasting( qreal lon, qreal lat ){ int zoneNumber = lonLatToZone( lon, lat ); if ( zoneNumber == 0 ){ qreal lonDeg = lon * RAD2DEG; zoneNumber = static_cast( (lonDeg+180) / 6.0 ) + 1; } QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) ); // Adjust easting and northing for UTM system qreal easting = coordinates.x() * GeoDataCoordinatesPrivate::sm_utmScaleFactor + 500000.0; return easting; } QString GeoDataCoordinatesPrivate::lonLatToLatitudeBand( qreal lon, qreal lat ){ // Obtains the latitude bands handling all the so called "exceptions" // Converts lon and lat to degrees qreal lonDeg = lon * RAD2DEG; qreal latDeg = lat * RAD2DEG; // Regular latitude bands between 80 S and 80 N (that is, between 10 and 170 in the [0,180] interval) int bandLetterIndex = 24; //Avoids "may be used uninitialized" warning if ( latDeg < -80 ) { // South pole (A for zones 1-30, B for zones 31-60) bandLetterIndex = ( (lonDeg+180) < 6*31 ) ? 0 : 1; } else if ( latDeg >= -80 && latDeg <= 80 ) { // General (+2 because the general lettering starts in C) bandLetterIndex = static_cast( (latDeg+80.0) / 8.0 ) + 2; } else if ( latDeg >= 80 && latDeg < 84 ) { // Band X is extended 4 more degrees bandLetterIndex = 21; } else if ( latDeg >= 84 ) { // North pole (Y for zones 1-30, Z for zones 31-60) bandLetterIndex = ((lonDeg+180) < 6*31) ? 22 : 23; } return QString( "ABCDEFGHJKLMNPQRSTUVWXYZ?" ).at( bandLetterIndex ); } qreal GeoDataCoordinatesPrivate::lonLatToNorthing( qreal lon, qreal lat ){ int zoneNumber = lonLatToZone( lon, lat ); if ( zoneNumber == 0 ){ qreal lonDeg = lon * RAD2DEG; zoneNumber = static_cast( (lonDeg+180) / 6.0 ) + 1; } QPointF coordinates = GeoDataCoordinatesPrivate::mapLonLatToXY( lon, lat, GeoDataCoordinatesPrivate::centralMeridianUTM(zoneNumber) ); qreal northing = coordinates.y() * GeoDataCoordinatesPrivate::sm_utmScaleFactor; if ( northing < 0.0 ) { northing += 10000000.0; } return northing; } uint qHash(const GeoDataCoordinates &coordinates) { QString lon, lat, alt; lon.setNum(coordinates.longitude(), 'f', 10); lat.setNum(coordinates.latitude(), 'f', 10); alt.setNum(coordinates.altitude(), 'f', 3); return qHash(lon % lat % alt); } } diff --git a/src/lib/marble/geodata/data/GeoDataCoordinates.h b/src/lib/marble/geodata/data/GeoDataCoordinates.h index 92f0bb7c1..cd6cd7b22 100644 --- a/src/lib/marble/geodata/data/GeoDataCoordinates.h +++ b/src/lib/marble/geodata/data/GeoDataCoordinates.h @@ -1,425 +1,431 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2007 Torsten Rahn // Copyright 2007-2008 Inge Wallin // Copyright 2008 Patrick Spendrin // Copyright 2015 Alejandro Garcia Montoro // #ifndef MARBLE_GEODATACOORDINATES_H #define MARBLE_GEODATACOORDINATES_H #include #include #include #include "geodata_export.h" #include "MarbleGlobal.h" class QString; namespace Marble { class GeoDataCoordinatesPrivate; class Quaternion; /** * @short A 3d point representation * * GeoDataCoordinates is the simple representation of a single three * dimensional point. It can be used all through out marble as the data type * for three dimensional objects. it comprises of a Quaternion for speed issues. * This class was introduced to reflect the difference between a simple 3d point * and the GeoDataGeometry object containing such a point. The latter is a * GeoDataPoint and is simply derived from GeoDataCoordinates. * @see GeoDataPoint */ class GEODATA_EXPORT GeoDataCoordinates { Q_DECLARE_TR_FUNCTIONS(GeoDataCoordinates) public: /** * @brief enum used constructor to specify the units used * * Internally we always use radian for mathematical convenience. * However the Marble's interfaces to the outside should default * to degrees. */ enum Unit{ Radian, Degree }; /** * @brief enum used to specify the notation / numerical system * * For degrees there exist two notations: * "Decimal" (base-10) and the "Sexagesimal DMS" (base-60) which is * traditionally used in cartography. Decimal notation * uses floating point numbers to specify parts of a degree. The * Sexagesimal DMS notation uses integer based * Degrees-(Arc)Minutes-(Arc)Seconds to describe parts of a degree. */ enum Notation{ Decimal, ///< "Decimal" notation (base-10) DMS, ///< "Sexagesimal DMS" notation (base-60) DM, ///< "Sexagesimal DM" notation (base-60) UTM, Astro /// < "RA and DEC" notation (used for astronomical sky coordinates) }; /** * @brief The BearingType enum specifies where to measure the bearing * along great circle arcs * * When traveling along a great circle arc defined by the two points * A and B, the bearing varies along the arc. The "InitialBearing" bearing * corresponds to the bearing value at A, the "FinalBearing" bearing to that * at B. */ enum BearingType { InitialBearing, FinalBearing }; // Type definitions typedef QVector Vector; typedef QVector PtrVector; GeoDataCoordinates( const GeoDataCoordinates& other ); /** * @brief constructs an invalid instance * * Constructs an invalid instance such that calling isValid() * on it will return @code false @endcode. */ GeoDataCoordinates(); /** * @brief create a geocoordinate from longitude and latitude * @param _lon longitude * @param _lat latitude * @param alt altitude in meters (default: 0) * @param _unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) * @param _detail detail (default: 0) */ GeoDataCoordinates( qreal lon, qreal lat, qreal alt = 0, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian, int detail = 0 ); virtual ~GeoDataCoordinates(); /** * @brief Returns @code true @endcode if the coordinate is valid, @code false @endcode otherwise. * @return whether the coordinate is valid * * A coordinate is valid, if at least one component has been set and the last * assignment was not an invalid GeoDataCoordinates object. */ bool isValid() const; /** * @brief (re)set the coordinates in a GeoDataCoordinates object * @param _lon longitude * @param _lat latitude * @param alt altitude in meters (default: 0) * @param _unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) */ void set( qreal lon, qreal lat, qreal alt = 0, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief use this function to get the longitude and latitude with one * call - use the unit parameter to switch between Radian and DMS * @param lon longitude * @param lat latitude * @param unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) */ void geoCoordinates(qreal& lon, qreal& lat, GeoDataCoordinates::Unit unit) const; void geoCoordinates(qreal& lon, qreal& lat) const; /** * @brief use this function to get the longitude, latitude and altitude * with one call - use the unit parameter to switch between Radian and DMS * @param lon longitude * @param lat latitude * @param alt altitude in meters * @param unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) */ void geoCoordinates(qreal& lon, qreal& lat, qreal& alt, GeoDataCoordinates::Unit unit) const; void geoCoordinates(qreal& lon, qreal& lat, qreal& alt) const; /** * @brief set the longitude in a GeoDataCoordinates object * @param _lon longitude * @param _unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) */ void setLongitude( qreal lon, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief retrieves the longitude of the GeoDataCoordinates object * use the unit parameter to switch between Radian and DMS * @param unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) * @return longitude */ qreal longitude(GeoDataCoordinates::Unit unit) const; qreal longitude() const; /** * @brief retrieves the latitude of the GeoDataCoordinates object * use the unit parameter to switch between Radian and DMS * @param unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) * @return latitude */ qreal latitude( GeoDataCoordinates::Unit unit ) const; qreal latitude() const; /** * @brief set the longitude in a GeoDataCoordinates object * @param _lat longitude * @param _unit units that lon and lat get measured in * (default for Radian: north pole at pi/2, southpole at -pi/2) */ void setLatitude( qreal lat, GeoDataCoordinates::Unit unit = GeoDataCoordinates::Radian ); /** * @brief return the altitude of the Point in meters */ qreal altitude() const; /** * @brief set the altitude of the Point in meters * @param altitude altitude */ void setAltitude( const qreal altitude ); /** * @brief retrieves the UTM zone of the GeoDataCoordinates object. * If the point is located on one of the poles (latitude < 80S or * latitude > 84N) there is no UTM zone associated; in this case, * 0 is returned. * @return UTM zone. */ int utmZone() const; /** * @brief retrieves the UTM easting of the GeoDataCoordinates object, * in meters. * @return UTM easting */ qreal utmEasting() const; /** * @brief retrieves the UTM latitude band of the GeoDataCoordinates object * @return UTM latitude band */ QString utmLatitudeBand() const; /** * @brief retrieves the UTM northing of the GeoDataCoordinates object, * in meters * @return UTM northing */ qreal utmNorthing() const; /** * @brief return the detail flag * detail range: 0 for most important points, 5 for least important */ quint8 detail() const; /** * @brief set the detail flag * @param det detail */ void setDetail(quint8 detail); /** * @brief Rotates one coordinate around another. * @param axis The coordinate that serves as a rotation axis * @param angle Rotation angle * @param unit Unit of the result * @return The coordinate rotated in anticlockwise direction */ GeoDataCoordinates rotateAround( const GeoDataCoordinates &axis, qreal angle, Unit unit = Radian ) const; /** * @brief Returns the bearing (true bearing, the angle between the line defined * by this point and the other and the prime meridian) * @param other The second point that, together with this point, defines a line * @param unit Unit of the result * @return The true bearing in the requested unit, not range normalized, * in clockwise direction, with the value 0 corresponding to north */ qreal bearing( const GeoDataCoordinates &other, Unit unit = Radian, BearingType type = InitialBearing ) const; /** * @brief Returns the coordinates of the resulting point after moving this point * according to the distance and bearing parameters * @param bearing the same as above * @param distance the distance on a unit sphere */ GeoDataCoordinates moveByBearing( qreal bearing, qreal distance ) const; /** * @brief return a Quaternion with the used coordinates */ const Quaternion &quaternion() const; /** * @brief slerp (spherical linear) interpolation between this coordinate and the given target coordinate * @param target Destination coordinate * @param t Fraction 0..1 to weight between this and target * @return Interpolated coordinate between this (t<=0.0) and target (t>=1.0) */ GeoDataCoordinates interpolate( const GeoDataCoordinates &target, double t ) const; /** * @brief squad (spherical and quadrangle) interpolation between b and c * @param before First base point * @param target Third base point (second interpolation point) * @param after Fourth base point * @param t Offset between b (t<=0) and c (t>=1) */ GeoDataCoordinates interpolate( const GeoDataCoordinates &before, const GeoDataCoordinates &target, const GeoDataCoordinates &after, double t ) const; /** * @brief return whether our coordinates represent a pole * This method can be used to check whether the coordinate equals one of * the poles. */ bool isPole( Pole = AnyPole ) const; + /** + * @brief This method calculates the shortest distance between two points on a sphere. + * @brief See: http://en.wikipedia.org/wiki/Great-circle_distance + */ + qreal sphericalDistanceTo(const GeoDataCoordinates &other) const; + /** * @brief return Notation of string representation */ static GeoDataCoordinates::Notation defaultNotation(); /** * @brief set the Notation of the string representation * @param notation Notation */ static void setDefaultNotation( GeoDataCoordinates::Notation notation ); /** * @brief normalize the longitude to always be -M_PI <= lon <= +M_PI (Radian). * @param lon longitude */ static qreal normalizeLon( qreal lon, GeoDataCoordinates::Unit = GeoDataCoordinates::Radian ); /** * @brief normalize latitude to always be in -M_PI / 2. <= lat <= +M_PI / 2 (Radian). * @param lat latitude */ static qreal normalizeLat( qreal lat, GeoDataCoordinates::Unit = GeoDataCoordinates::Radian ); /** * @brief normalize both longitude and latitude at the same time * This method normalizes both latitude and longitude, so that the * latitude and the longitude stay within the "usual" range. * NOTE: If the latitude exceeds M_PI/2 (+90.0 deg) or -M_PI/2 (-90.0 deg) * then this will be interpreted as a pole traversion where the point will * end up on the opposite side of the globe. Therefore the longitude will * change by M_PI (180 deg). * If you don't want this behaviour use both normalizeLat() and * normalizeLon() instead. * @param lon the longitude value * @param lat the latitude value */ static void normalizeLonLat( qreal &lon, qreal &lat, GeoDataCoordinates::Unit = GeoDataCoordinates::Radian ); /** * @brief try to parse the string into a coordinate pair * @param successful becomes true if the conversion succeeds * @return the geodatacoordinates */ static GeoDataCoordinates fromString( const QString &string, bool& successful ); /** * @brief return a string representation of the coordinate * this is a convenience function which uses the default notation */ QString toString() const; /** * @brief return a string with the notation given by notation * * @param notation set a notation different from the default one * @param precision set the number of digits below degrees. * The precision depends on the current notation: * For Decimal representation the precision is the number of * digits after the decimal point. * In DMS a precision of 1 or 2 shows the arc minutes; a precision * of 3 or 4 will show arc seconds. A precision beyond that will * increase the number of digits after the arc second decimal point. */ QString toString( GeoDataCoordinates::Notation notation, int precision = -1 ) const; static QString lonToString( qreal lon, GeoDataCoordinates::Notation notation, GeoDataCoordinates::Unit unit = Radian, int precision = -1, char format = 'f' ); /** * @brief return a string representation of longitude of the coordinate * convenience function that uses the default notation */ QString lonToString() const; static QString latToString( qreal lat, GeoDataCoordinates::Notation notation, GeoDataCoordinates::Unit unit = Radian, int precision = -1, char format = 'f' ); /** * @brief return a string representation of latitude of the coordinate * convenience function that uses the default notation */ QString latToString() const; bool operator==(const GeoDataCoordinates &other) const; bool operator!=(const GeoDataCoordinates &other) const; GeoDataCoordinates& operator=( const GeoDataCoordinates &other ); /** Serialize the contents of the feature to @p stream. */ void pack(QDataStream &stream) const; /** Unserialize the contents of the feature from @p stream. */ void unpack(QDataStream &stream); private: void detach(); GeoDataCoordinatesPrivate *d; static GeoDataCoordinates::Notation s_notation; static const GeoDataCoordinates null; }; GEODATA_EXPORT uint qHash(const GeoDataCoordinates& coordinates ); } Q_DECLARE_METATYPE( Marble::GeoDataCoordinates ) #endif diff --git a/src/lib/marble/geodata/data/GeoDataLineString.cpp b/src/lib/marble/geodata/data/GeoDataLineString.cpp index 653ed419c..e3536be7e 100644 --- a/src/lib/marble/geodata/data/GeoDataLineString.cpp +++ b/src/lib/marble/geodata/data/GeoDataLineString.cpp @@ -1,965 +1,965 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008 Torsten Rahn // Copyright 2009 Patrick Spendrin // #include "GeoDataLineString.h" #include "GeoDataLineString_p.h" #include "GeoDataLinearRing.h" #include "GeoDataTypes.h" #include "MarbleMath.h" #include "Quaternion.h" #include "MarbleDebug.h" #include namespace Marble { GeoDataLineString::GeoDataLineString( TessellationFlags f ) : GeoDataGeometry( new GeoDataLineStringPrivate( f ) ) { // mDebug() << "1) GeoDataLineString created:" << p(); } GeoDataLineString::GeoDataLineString( GeoDataLineStringPrivate* priv ) : GeoDataGeometry( priv ) { // mDebug() << "2) GeoDataLineString created:" << p(); } GeoDataLineString::GeoDataLineString( const GeoDataGeometry & other ) : GeoDataGeometry( other ) { // mDebug() << "3) GeoDataLineString created:" << p(); } GeoDataLineString::~GeoDataLineString() { #ifdef DEBUG_GEODATA mDebug() << "delete Linestring"; #endif } const char *GeoDataLineString::nodeType() const { return GeoDataTypes::GeoDataLineStringType; } EnumGeometryId GeoDataLineString::geometryId() const { return GeoDataLineStringId; } GeoDataGeometry *GeoDataLineString::copy() const { return new GeoDataLineString(*this); } void GeoDataLineStringPrivate::interpolateDateLine( const GeoDataCoordinates & previousCoords, const GeoDataCoordinates & currentCoords, GeoDataCoordinates & previousAtDateLine, GeoDataCoordinates & currentAtDateLine, TessellationFlags f ) const { GeoDataCoordinates dateLineCoords; // mDebug() << Q_FUNC_INFO; if ( f.testFlag( RespectLatitudeCircle ) && previousCoords.latitude() == currentCoords.latitude() ) { dateLineCoords = currentCoords; } else { int recursionCounter = 0; dateLineCoords = findDateLine( previousCoords, currentCoords, recursionCounter ); } previousAtDateLine = dateLineCoords; currentAtDateLine = dateLineCoords; if ( previousCoords.longitude() < 0 ) { previousAtDateLine.setLongitude( -M_PI ); currentAtDateLine.setLongitude( +M_PI ); } else { previousAtDateLine.setLongitude( +M_PI ); currentAtDateLine.setLongitude( -M_PI ); } } GeoDataCoordinates GeoDataLineStringPrivate::findDateLine( const GeoDataCoordinates & previousCoords, const GeoDataCoordinates & currentCoords, int recursionCounter ) const { int currentSign = ( currentCoords.longitude() < 0.0 ) ? -1 : +1 ; int previousSign = ( previousCoords.longitude() < 0.0 ) ? -1 : +1 ; qreal longitudeDiff = fabs( previousSign * M_PI - previousCoords.longitude() ) + fabs( currentSign * M_PI - currentCoords.longitude() ); if ( longitudeDiff < 0.001 || recursionCounter == 100 ) { // mDebug() << "stopped at recursion" << recursionCounter << " and longitude difference " << longitudeDiff; return currentCoords; } ++recursionCounter; qreal lon = 0.0; qreal lat = 0.0; qreal altDiff = currentCoords.altitude() - previousCoords.altitude(); const Quaternion itpos = Quaternion::nlerp( previousCoords.quaternion(), currentCoords.quaternion(), 0.5 ); itpos.getSpherical( lon, lat ); qreal altitude = previousCoords.altitude() + 0.5 * altDiff; GeoDataCoordinates interpolatedCoords( lon, lat, altitude ); int interpolatedSign = ( interpolatedCoords.longitude() < 0.0 ) ? -1 : +1 ; /* mDebug() << "SRC" << previousCoords.toString(); mDebug() << "TAR" << currentCoords.toString(); mDebug() << "IPC" << interpolatedCoords.toString(); */ if ( interpolatedSign != currentSign ) { return findDateLine( interpolatedCoords, currentCoords, recursionCounter ); } return findDateLine( previousCoords, interpolatedCoords, recursionCounter ); } quint8 GeoDataLineStringPrivate::levelForResolution(qreal resolution) const { if (m_previousResolution == resolution) return m_level; m_previousResolution = resolution; if (resolution < 0.0000005) m_level = 17; else if (resolution < 0.0000010) m_level = 16; else if (resolution < 0.0000020) m_level = 15; else if (resolution < 0.0000040) m_level = 14; else if (resolution < 0.0000080) m_level = 13; else if (resolution < 0.0000160) m_level = 12; else if (resolution < 0.0000320) m_level = 11; else if (resolution < 0.0000640) m_level = 10; else if (resolution < 0.0001280) m_level = 9; else if (resolution < 0.0002560) m_level = 8; else if (resolution < 0.0005120) m_level = 7; else if (resolution < 0.0010240) m_level = 6; else if (resolution < 0.0020480) m_level = 5; else if (resolution < 0.0040960) m_level = 4; else if (resolution < 0.0081920) m_level = 3; else if (resolution < 0.0163840) m_level = 2; else m_level = 1; return m_level; } qreal GeoDataLineStringPrivate::resolutionForLevel(int level) { switch (level) { case 0: return 0.0655360; break; case 1: return 0.0327680; break; case 2: return 0.0163840; break; case 3: return 0.0081920; break; case 4: return 0.0040960; break; case 5: return 0.0020480; break; case 6: return 0.0010240; break; case 7: return 0.0005120; break; case 8: return 0.0002560; break; case 9: return 0.0001280; break; case 10: return 0.0000640; break; case 11: return 0.0000320; break; case 12: return 0.0000160; break; case 13: return 0.0000080; break; case 14: return 0.0000040; break; case 15: return 0.0000020; break; case 16: return 0.0000010; break; default: case 17: return 0.0000005; break; } } void GeoDataLineStringPrivate::optimize (GeoDataLineString& lineString) const { QVector::iterator itCoords = lineString.begin(); QVector::const_iterator itEnd = lineString.constEnd(); if (lineString.size() < 2) return; // Calculate the least non-zero detail-level by checking the bounding box quint8 startLevel = levelForResolution( ( lineString.latLonAltBox().width() + lineString.latLonAltBox().height() ) / 2 ); quint8 currentLevel = startLevel; quint8 maxLevel = startLevel; GeoDataCoordinates currentCoords; lineString.first().setDetail(startLevel); // Iterate through the linestring to assign different detail levels to the nodes. // In general the first and last node should have the start level assigned as // a detail level. // Starting from the first node the algorithm picks those nodes which // have a distance from each other that is just above the resolution that is // associated with the start level (which we use as a "current level"). // Each of those nodes get the current level assigned as the detail level. // After iterating through the linestring we increment the current level value // and starting again with the first node we assign detail values in a similar way // to the remaining nodes which have no final detail level assigned yet. // We do as many iterations through the lineString as needed and bump up the // current level until all nodes have a non-zero detail level assigned. while ( currentLevel < 16 && currentLevel <= maxLevel + 1 ) { itCoords = lineString.begin(); currentCoords = *itCoords; ++itCoords; for( ; itCoords != itEnd; ++itCoords) { if (itCoords->detail() != 0 && itCoords->detail() < currentLevel) continue; if ( currentLevel == startLevel && (itCoords->longitude() == -M_PI || itCoords->longitude() == M_PI || itCoords->latitude() < -89 * DEG2RAD || itCoords->latitude() > 89 * DEG2RAD)) { itCoords->setDetail(startLevel); currentCoords = *itCoords; maxLevel = currentLevel; continue; } - if (distanceSphere( currentCoords, *itCoords ) < resolutionForLevel(currentLevel + 1)) { + if (currentCoords.sphericalDistanceTo(*itCoords) < resolutionForLevel(currentLevel + 1)) { itCoords->setDetail(currentLevel + 1); } else { itCoords->setDetail(currentLevel); currentCoords = *itCoords; maxLevel = currentLevel; } } ++currentLevel; } lineString.last().setDetail(startLevel); } bool GeoDataLineString::isEmpty() const { Q_D(const GeoDataLineString); return d->m_vector.isEmpty(); } int GeoDataLineString::size() const { Q_D(const GeoDataLineString); return d->m_vector.size(); } GeoDataCoordinates& GeoDataLineString::at( int pos ) { detach(); Q_D(GeoDataLineString); d->m_dirtyRange = true; d->m_dirtyBox = true; return d->m_vector[pos]; } const GeoDataCoordinates& GeoDataLineString::at( int pos ) const { Q_D(const GeoDataLineString); return d->m_vector.at(pos); } GeoDataCoordinates& GeoDataLineString::operator[]( int pos ) { detach(); Q_D(GeoDataLineString); d->m_dirtyRange = true; d->m_dirtyBox = true; return d->m_vector[pos]; } GeoDataLineString GeoDataLineString::mid(int pos, int length) const { GeoDataLineString substring; auto d = substring.d_func(); d->m_vector = d_func()->m_vector.mid(pos, length); d->m_dirtyBox = true; d->m_dirtyRange = true; d->m_tessellationFlags = d_func()->m_tessellationFlags; d->m_extrude = d_func()->m_extrude; return substring; } const GeoDataCoordinates& GeoDataLineString::operator[]( int pos ) const { Q_D(const GeoDataLineString); return d->m_vector[pos]; } GeoDataCoordinates& GeoDataLineString::last() { detach(); Q_D(GeoDataLineString); d->m_dirtyRange = true; d->m_dirtyBox = true; return d->m_vector.last(); } GeoDataCoordinates& GeoDataLineString::first() { detach(); Q_D(GeoDataLineString); return d->m_vector.first(); } const GeoDataCoordinates& GeoDataLineString::last() const { Q_D(const GeoDataLineString); return d->m_vector.last(); } const GeoDataCoordinates& GeoDataLineString::first() const { Q_D(const GeoDataLineString); return d->m_vector.first(); } QVector::Iterator GeoDataLineString::begin() { detach(); Q_D(GeoDataLineString); return d->m_vector.begin(); } QVector::ConstIterator GeoDataLineString::begin() const { Q_D(const GeoDataLineString); return d->m_vector.constBegin(); } QVector::Iterator GeoDataLineString::end() { detach(); Q_D(GeoDataLineString); return d->m_vector.end(); } QVector::ConstIterator GeoDataLineString::end() const { Q_D(const GeoDataLineString); return d->m_vector.constEnd(); } QVector::ConstIterator GeoDataLineString::constBegin() const { Q_D(const GeoDataLineString); return d->m_vector.constBegin(); } QVector::ConstIterator GeoDataLineString::constEnd() const { Q_D(const GeoDataLineString); return d->m_vector.constEnd(); } void GeoDataLineString::insert( int index, const GeoDataCoordinates& value ) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; d->m_vector.insert( index, value ); } void GeoDataLineString::append ( const GeoDataCoordinates& value ) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; d->m_vector.append( value ); } void GeoDataLineString::reserve(int size) { Q_D(GeoDataLineString); d->m_vector.reserve(size); } void GeoDataLineString::append(const QVector& values) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; #if QT_VERSION >= 0x050500 d->m_vector.append(values); #else d->m_vector.reserve(d->m_vector.size() + values.size()); for (const GeoDataCoordinates &coordinates: values) { d->m_vector.append(coordinates); } #endif } GeoDataLineString& GeoDataLineString::operator << ( const GeoDataCoordinates& value ) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; d->m_vector.append( value ); return *this; } GeoDataLineString& GeoDataLineString::operator << ( const GeoDataLineString& value ) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; QVector::const_iterator itCoords = value.constBegin(); QVector::const_iterator itEnd = value.constEnd(); d->m_vector.reserve(d->m_vector.size() + value.size()); for( ; itCoords != itEnd; ++itCoords ) { d->m_vector.append( *itCoords ); } return *this; } bool GeoDataLineString::operator==( const GeoDataLineString &other ) const { if ( !GeoDataGeometry::equals(other) || size() != other.size() || tessellate() != other.tessellate() ) { return false; } Q_D(const GeoDataLineString); const GeoDataLineStringPrivate* other_d = other.d_func(); QVector::const_iterator itCoords = d->m_vector.constBegin(); QVector::const_iterator otherItCoords = other_d->m_vector.constBegin(); QVector::const_iterator itEnd = d->m_vector.constEnd(); QVector::const_iterator otherItEnd = other_d->m_vector.constEnd(); for ( ; itCoords != itEnd && otherItCoords != otherItEnd; ++itCoords, ++otherItCoords ) { if ( *itCoords != *otherItCoords ) { return false; } } Q_ASSERT ( itCoords == itEnd && otherItCoords == otherItEnd ); return true; } bool GeoDataLineString::operator!=( const GeoDataLineString &other ) const { return !this->operator==(other); } void GeoDataLineString::clear() { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; d->m_vector.clear(); } bool GeoDataLineString::isClosed() const { return false; } bool GeoDataLineString::tessellate() const { Q_D(const GeoDataLineString); return d->m_tessellationFlags.testFlag(Tessellate); } void GeoDataLineString::setTessellate( bool tessellate ) { detach(); Q_D(GeoDataLineString); // According to the KML reference the tesselation of line strings in Google Earth // is generally done along great circles. However for subsequent points that share // the same latitude the latitude circles are followed. Our Tesselate and RespectLatitude // Flags provide this behaviour. For true polygons the latitude circles don't get considered. if (tessellate) { d->m_tessellationFlags |= (Tessellate | RespectLatitudeCircle); } else { d->m_tessellationFlags &= ~(Tessellate | RespectLatitudeCircle); } } TessellationFlags GeoDataLineString::tessellationFlags() const { Q_D(const GeoDataLineString); return d->m_tessellationFlags; } void GeoDataLineString::setTessellationFlags( TessellationFlags f ) { detach(); Q_D(GeoDataLineString); d->m_tessellationFlags = f; } void GeoDataLineString::reverse() { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; std::reverse(begin(), end()); } GeoDataLineString GeoDataLineString::toNormalized() const { Q_D(const GeoDataLineString); GeoDataLineString normalizedLineString; normalizedLineString.setTessellationFlags( tessellationFlags() ); qreal lon; qreal lat; // FIXME: Think about how we can avoid unnecessary copies // if the linestring stays the same. QVector::const_iterator end = d->m_vector.constEnd(); for( QVector::const_iterator itCoords = d->m_vector.constBegin(); itCoords != end; ++itCoords ) { itCoords->geoCoordinates( lon, lat ); qreal alt = itCoords->altitude(); GeoDataCoordinates::normalizeLonLat( lon, lat ); GeoDataCoordinates normalizedCoords( *itCoords ); normalizedCoords.set( lon, lat, alt ); normalizedLineString << normalizedCoords; } return normalizedLineString; } GeoDataLineString GeoDataLineString::toRangeCorrected() const { Q_D(const GeoDataLineString); if (d->m_dirtyRange) { delete d->m_rangeCorrected; if( isClosed() ) { d->m_rangeCorrected = new GeoDataLinearRing(toPoleCorrected()); } else { d->m_rangeCorrected = new GeoDataLineString(toPoleCorrected()); } d->m_dirtyRange = false; } return *d->m_rangeCorrected; } QVector GeoDataLineString::toDateLineCorrected() const { Q_D(const GeoDataLineString); QVector lineStrings; d->toDateLineCorrected(*this, lineStrings); return lineStrings; } GeoDataLineString GeoDataLineString::toPoleCorrected() const { Q_D(const GeoDataLineString); if( isClosed() ) { GeoDataLinearRing poleCorrected; d->toPoleCorrected(*this, poleCorrected); return poleCorrected; } else { GeoDataLineString poleCorrected; d->toPoleCorrected(*this, poleCorrected); return poleCorrected; } } void GeoDataLineStringPrivate::toPoleCorrected( const GeoDataLineString& q, GeoDataLineString& poleCorrected ) const { poleCorrected.setTessellationFlags( q.tessellationFlags() ); GeoDataCoordinates previousCoords; GeoDataCoordinates currentCoords; if ( q.isClosed() ) { if ( !( m_vector.first().isPole() ) && ( m_vector.last().isPole() ) ) { qreal firstLongitude = ( m_vector.first() ).longitude(); GeoDataCoordinates modifiedCoords( m_vector.last() ); modifiedCoords.setLongitude( firstLongitude ); poleCorrected << modifiedCoords; } } QVector::const_iterator itCoords = m_vector.constBegin(); QVector::const_iterator itEnd = m_vector.constEnd(); for( ; itCoords != itEnd; ++itCoords ) { currentCoords = *itCoords; if ( itCoords == m_vector.constBegin() ) { previousCoords = currentCoords; } if ( currentCoords.isPole() ) { if ( previousCoords.isPole() ) { continue; } else { qreal previousLongitude = previousCoords.longitude(); GeoDataCoordinates currentModifiedCoords( currentCoords ); currentModifiedCoords.setLongitude( previousLongitude ); poleCorrected << currentModifiedCoords; } } else { if ( previousCoords.isPole() ) { qreal currentLongitude = currentCoords.longitude(); GeoDataCoordinates previousModifiedCoords( previousCoords ); previousModifiedCoords.setLongitude( currentLongitude ); poleCorrected << previousModifiedCoords; poleCorrected << currentCoords; } else { // No poles at all. Nothing special to handle poleCorrected << currentCoords; } } previousCoords = currentCoords; } if ( q.isClosed() ) { if ( ( m_vector.first().isPole() ) && !( m_vector.last().isPole() ) ) { qreal lastLongitude = ( m_vector.last() ).longitude(); GeoDataCoordinates modifiedCoords( m_vector.first() ); modifiedCoords.setLongitude( lastLongitude ); poleCorrected << modifiedCoords; } } } void GeoDataLineStringPrivate::toDateLineCorrected( const GeoDataLineString & q, QVector & lineStrings ) const { const bool isClosed = q.isClosed(); const QVector::const_iterator itStartPoint = q.constBegin(); const QVector::const_iterator itEndPoint = q.constEnd(); QVector::const_iterator itPoint = itStartPoint; QVector::const_iterator itPreviousPoint = itPoint; TessellationFlags f = q.tessellationFlags(); GeoDataLineString * unfinishedLineString = 0; GeoDataLineString * dateLineCorrected = isClosed ? new GeoDataLinearRing( f ) : new GeoDataLineString( f ); qreal currentLon = 0.0; qreal previousLon = 0.0; int previousSign = 1; bool unfinished = false; for (; itPoint != itEndPoint; ++itPoint ) { currentLon = itPoint->longitude(); int currentSign = ( currentLon < 0.0 ) ? -1 : +1 ; if( itPoint == q.constBegin() ) { previousSign = currentSign; previousLon = currentLon; } // If we are crossing the date line ... if ( previousSign != currentSign && fabs(previousLon) + fabs(currentLon) > M_PI ) { unfinished = !unfinished; GeoDataCoordinates previousTemp; GeoDataCoordinates currentTemp; interpolateDateLine( *itPreviousPoint, *itPoint, previousTemp, currentTemp, q.tessellationFlags() ); *dateLineCorrected << previousTemp; if ( isClosed && unfinished ) { // If it's a linear ring and if it crossed the IDL only once then // store the current string inside the unfinishedLineString for later use ... unfinishedLineString = dateLineCorrected; // ... and start a new linear ring for now. dateLineCorrected = new GeoDataLinearRing( f ); } else { // Now it can only be a (finished) line string or a finished linear ring. // Store it in the vector if the size is not zero. if ( dateLineCorrected->size() > 0 ) { lineStrings << dateLineCorrected; } else { // Or delete it. delete dateLineCorrected; } // If it's a finished linear ring restore the "remembered" unfinished String if ( isClosed && !unfinished && unfinishedLineString ) { dateLineCorrected = unfinishedLineString; } else { // if it's a line string just create a new line string. dateLineCorrected = new GeoDataLineString( f ); } } *dateLineCorrected << currentTemp; *dateLineCorrected << *itPoint; } else { *dateLineCorrected << *itPoint; } previousSign = currentSign; previousLon = currentLon; itPreviousPoint = itPoint; } // If the line string doesn't cross the dateline an even number of times // then need to take care of the data stored in the unfinishedLineString if ( unfinished && unfinishedLineString && !unfinishedLineString->isEmpty() ) { *dateLineCorrected << *unfinishedLineString; delete unfinishedLineString; } lineStrings << dateLineCorrected; } const GeoDataLatLonAltBox& GeoDataLineString::latLonAltBox() const { Q_D(const GeoDataLineString); // GeoDataLatLonAltBox::fromLineString is very expensive // that's why we recreate it only if the m_dirtyBox // is TRUE. // DO NOT REMOVE THIS CONSTRUCT OR MARBLE WILL BE SLOW. if (d->m_dirtyBox) { d->m_latLonAltBox = GeoDataLatLonAltBox::fromLineString(*this); d->m_dirtyBox = false; } return d->m_latLonAltBox; } qreal GeoDataLineString::length( qreal planetRadius, int offset ) const { if( offset < 0 || offset >= size() ) { return 0; } Q_D(const GeoDataLineString); qreal length = 0.0; QVector const & vector = d->m_vector; int const start = qMax(offset+1, 1); int const end = d->m_vector.size(); for( int i=start; i::Iterator GeoDataLineString::erase ( const QVector::Iterator& pos ) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; return d->m_vector.erase( pos ); } QVector::Iterator GeoDataLineString::erase ( const QVector::Iterator& begin, const QVector::Iterator& end ) { detach(); Q_D(GeoDataLineString); delete d->m_rangeCorrected; d->m_rangeCorrected = 0; d->m_dirtyRange = true; d->m_dirtyBox = true; return d->m_vector.erase( begin, end ); } void GeoDataLineString::remove ( int i ) { detach(); Q_D(GeoDataLineString); d->m_dirtyRange = true; d->m_dirtyBox = true; d->m_vector.remove( i ); } GeoDataLineString GeoDataLineString::optimized () const { Q_D(const GeoDataLineString); if( isClosed() ) { GeoDataLinearRing linearRing(*this); d->optimize(linearRing); return linearRing; } else { GeoDataLineString lineString(*this); d->optimize(lineString); return lineString; } } void GeoDataLineString::pack( QDataStream& stream ) const { Q_D(const GeoDataLineString); GeoDataGeometry::pack( stream ); stream << size(); stream << (qint32)(d->m_tessellationFlags); for( QVector::const_iterator iterator = d->m_vector.constBegin(); iterator != d->m_vector.constEnd(); ++iterator ) { mDebug() << "innerRing: size" << d->m_vector.size(); GeoDataCoordinates coord = ( *iterator ); coord.pack( stream ); } } void GeoDataLineString::unpack( QDataStream& stream ) { detach(); Q_D(GeoDataLineString); GeoDataGeometry::unpack( stream ); qint32 size; qint32 tessellationFlags; stream >> size; stream >> tessellationFlags; d->m_tessellationFlags = (TessellationFlags)(tessellationFlags); d->m_vector.reserve(d->m_vector.size() + size); for(qint32 i = 0; i < size; i++ ) { GeoDataCoordinates coord; coord.unpack( stream ); d->m_vector.append( coord ); } } } diff --git a/src/lib/marble/geodata/data/GeoDataLinearRing.cpp b/src/lib/marble/geodata/data/GeoDataLinearRing.cpp index 5861bbc30..22fd51682 100644 --- a/src/lib/marble/geodata/data/GeoDataLinearRing.cpp +++ b/src/lib/marble/geodata/data/GeoDataLinearRing.cpp @@ -1,114 +1,114 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008 Torsten Rahn // #include "GeoDataLinearRing.h" #include "GeoDataLinearRing_p.h" #include "GeoDataTypes.h" #include "MarbleMath.h" #include "MarbleDebug.h" namespace Marble { GeoDataLinearRing::GeoDataLinearRing( TessellationFlags f ) : GeoDataLineString( new GeoDataLinearRingPrivate( f ) ) { } GeoDataLinearRing::GeoDataLinearRing( const GeoDataGeometry & other ) : GeoDataLineString( other ) { } GeoDataLinearRing::~GeoDataLinearRing() { } const char *GeoDataLinearRing::nodeType() const { return GeoDataTypes::GeoDataLinearRingType; } EnumGeometryId GeoDataLinearRing::geometryId() const { return GeoDataLinearRingId; } GeoDataGeometry *GeoDataLinearRing::copy() const { return new GeoDataLinearRing(*this); } bool GeoDataLinearRing::operator==( const GeoDataLinearRing &other ) const { return isClosed() == other.isClosed() && GeoDataLineString::operator==( other ); } bool GeoDataLinearRing::operator!=( const GeoDataLinearRing &other ) const { return !this->operator==(other); } bool GeoDataLinearRing::isClosed() const { return true; } qreal GeoDataLinearRing::length( qreal planetRadius, int offset ) const { qreal length = GeoDataLineString::length( planetRadius, offset ); - return length + planetRadius * distanceSphere( last(), first() ); + return length + planetRadius * last().sphericalDistanceTo(first()); } bool GeoDataLinearRing::contains( const GeoDataCoordinates &coordinates ) const { // Quick bounding box check if ( !latLonAltBox().contains( coordinates ) ) { return false; } int const points = size(); bool inside = false; // also true for points = 0 int j = points - 1; for ( int i=0; i= coordinates.longitude() ) || ( two.longitude() < coordinates.longitude() && one.longitude() >= coordinates.longitude() ) ) { if ( one.latitude() + ( coordinates.longitude() - one.longitude()) / ( two.longitude() - one.longitude()) * ( two.latitude()-one.latitude() ) < coordinates.latitude() ) { inside = !inside; } } j = i; } return inside; } bool GeoDataLinearRing::isClockwise() const { int const n = size(); qreal area = 0; for ( int i = 1; i < n; ++i ){ area += ( operator[]( i ).longitude() - operator[]( i - 1 ).longitude() ) * ( operator[]( i ).latitude() + operator[]( i - 1 ).latitude() ); } area += ( operator[]( 0 ).longitude() - operator[]( n - 1 ).longitude() ) * ( operator[] ( 0 ).latitude() + operator[]( n - 1 ).latitude() ); return area > 0; } } diff --git a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp index c2b600360..42cd96c64 100644 --- a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp @@ -1,512 +1,512 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Andrew Manson // #include "GeoLineStringGraphicsItem.h" #include "GeoDataLineString.h" #include "GeoDataLineStyle.h" #include "GeoDataLabelStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoPainter.h" #include "StyleBuilder.h" #include "ViewportParams.h" #include "GeoDataStyle.h" #include "GeoDataColorStyle.h" #include "MarbleDebug.h" #include "MarbleMath.h" #include "OsmPlacemarkData.h" #include #include namespace Marble { const GeoDataStyle *GeoLineStringGraphicsItem::s_previousStyle = 0; bool GeoLineStringGraphicsItem::s_paintInline = true; bool GeoLineStringGraphicsItem::s_paintOutline = true; GeoLineStringGraphicsItem::GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLineString *lineString) : GeoGraphicsItem(placemark), m_lineString(lineString), m_renderLineString(lineString), m_renderLabel(false), m_penWidth(0.0), m_name(placemark->name()) { QString const category = StyleBuilder::visualCategoryName(placemark->visualCategory()); QStringList paintLayers; paintLayers << QLatin1String("LineString/") + category + QLatin1String("/outline"); paintLayers << QLatin1String("LineString/") + category + QLatin1String("/inline"); if (!m_name.isEmpty()) { paintLayers << QLatin1String("LineString/") + category + QLatin1String("/label"); } setPaintLayers(paintLayers); } GeoLineStringGraphicsItem::~GeoLineStringGraphicsItem() { qDeleteAll(m_cachedPolygons); } void GeoLineStringGraphicsItem::setLineString( const GeoDataLineString* lineString ) { m_lineString = lineString; m_renderLineString = lineString; } const GeoDataLineString *GeoLineStringGraphicsItem::lineString() const { return m_lineString; } GeoDataLineString GeoLineStringGraphicsItem::merge(const QVector &lineStrings_) { if (lineStrings_.isEmpty()) { return GeoDataLineString(); } Q_ASSERT(!lineStrings_.isEmpty()); auto lineStrings = lineStrings_; GeoDataLineString result = *lineStrings.first(); lineStrings.pop_front(); for (bool matched = true; matched && !lineStrings.isEmpty();) { matched = false; for (auto lineString: lineStrings) { if (canMerge(result.first(), lineString->first())) { result.remove(0); result.reverse(); result << *lineString; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.last(), lineString->first())) { result.remove(result.size()-1); result << *lineString; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.first(), lineString->last())) { GeoDataLineString behind = result; result = *lineString; behind.remove(0); result << behind; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.last(), lineString->last())) { GeoDataLineString behind = *lineString; behind.reverse(); behind.remove(0); result << behind; lineStrings.removeOne(lineString); matched = true; break; } } if (!matched) { return GeoDataLineString(); } } return lineStrings.isEmpty() ? result : GeoDataLineString(); } void GeoLineStringGraphicsItem::setMergedLineString(const GeoDataLineString &mergedLineString) { m_mergedLineString = mergedLineString; m_renderLineString = mergedLineString.isEmpty() ? m_lineString : &m_mergedLineString; } const GeoDataLatLonAltBox& GeoLineStringGraphicsItem::latLonAltBox() const { return m_renderLineString->latLonAltBox(); } void GeoLineStringGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport , const QString &layer, int tileLevel) { setRenderContext(RenderContext(tileLevel)); if (layer.endsWith(QLatin1String("/outline"))) { qDeleteAll(m_cachedPolygons); m_cachedPolygons.clear(); m_cachedRegion = QRegion(); painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons); if (m_cachedPolygons.empty()) { return; } if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) { paintOutline(painter, viewport); } } else if (layer.endsWith(QLatin1String("/inline"))) { if (m_cachedPolygons.empty()) { return; } paintInline(painter, viewport); } else if (layer.endsWith(QLatin1String("/label"))) { if (!m_cachedPolygons.empty()) { if (m_renderLabel) { paintLabel(painter, viewport); } } } else { qDeleteAll(m_cachedPolygons); m_cachedPolygons.clear(); m_cachedRegion = QRegion(); painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons); if (m_cachedPolygons.empty()) { return; } for(const QPolygonF* itPolygon: m_cachedPolygons) { painter->drawPolyline(*itPolygon); } } } bool GeoLineStringGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *) const { if (m_penWidth <= 0.0) { return false; } if (m_cachedRegion.isNull()) { QPainterPath painterPath; for (auto polygon: m_cachedPolygons) { painterPath.addPolygon(*polygon); } QPainterPathStroker stroker; qreal const margin = 6.0; stroker.setWidth(m_penWidth + margin); QPainterPath strokePath = stroker.createStroke(painterPath); m_cachedRegion = QRegion(strokePath.toFillPolygon().toPolygon(), Qt::WindingFill); } return m_cachedRegion.contains(screenPosition); } void GeoLineStringGraphicsItem::handleRelationUpdate(const QVector &relations) { QHash names; for (auto relation: relations) { auto const ref = relation->osmData().tagValue(QStringLiteral("ref")); if (relation->isVisible() && !ref.isEmpty()) { names[relation->relationType()] << ref; } } if (names.isEmpty()) { m_name = feature()->name(); } else { QStringList namesList; for (auto iter = names.begin(); iter != names.end(); ++iter) { QString value; switch (iter.key()) { case GeoDataRelation::UnknownType: case GeoDataRelation::RouteRoad: break; case GeoDataRelation::RouteDetour: value = tr("Detour"); break; case GeoDataRelation::RouteFerry: value = tr("Ferry Route"); break; case GeoDataRelation::RouteTrain: value = tr("Train"); break; case GeoDataRelation::RouteSubway: value = tr("Subway"); break; case GeoDataRelation::RouteTram: value = tr("Tram"); break; case GeoDataRelation::RouteBus: value = tr("Bus"); break; case GeoDataRelation::RouteTrolleyBus: value = tr("Trolley Bus"); break; case GeoDataRelation::RouteBicycle: value = tr("Bicycle Route"); break; case GeoDataRelation::RouteMountainbike: value = tr("Mountainbike Route"); break; case GeoDataRelation::RouteFoot: value = tr("Walking Route"); break; case GeoDataRelation::RouteHiking: value = tr("Hiking Route"); break; case GeoDataRelation::RouteHorse: value = tr("Bridleway"); break; case GeoDataRelation::RouteInlineSkates: value = tr("Inline Skates Route"); break; case GeoDataRelation::RouteSkiDownhill: value = tr("Downhill Piste"); break; case GeoDataRelation::RouteSkiNordic: value = tr("Nordic Ski Trail"); break; case GeoDataRelation::RouteSkitour: value = tr("Skitour"); break; case GeoDataRelation::RouteSled: value = tr("Sled Trail"); break; } QStringList &references = iter.value(); std::sort(references.begin(), references.end()); auto const last = std::unique(references.begin(), references.end()); references.erase(last, references.end()); auto const routes = references.join(", "); namesList << (value.isEmpty() ? routes : QStringLiteral("%1 %2").arg(value, routes)); } auto const allRoutes = namesList.join(QStringLiteral("; ")); if (feature()->name().isEmpty()) { m_name = allRoutes; } else { m_name = QStringLiteral("%1 (%2)").arg(feature()->name(), allRoutes); } } } void GeoLineStringGraphicsItem::paintInline(GeoPainter* painter, const ViewportParams* viewport) { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } if (s_previousStyle != style().data()) { s_paintInline = configurePainterForLine(painter, viewport, false); } s_previousStyle = style().data(); if (s_paintInline) { m_renderLabel = painter->pen().widthF() >= 6.0f; m_penWidth = painter->pen().widthF(); for(const QPolygonF* itPolygon: m_cachedPolygons) { painter->drawPolyline(*itPolygon); } } } void GeoLineStringGraphicsItem::paintOutline(GeoPainter *painter, const ViewportParams *viewport) const { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } if (s_previousStyle != style().data()) { s_paintOutline = configurePainterForLine(painter, viewport, true); } s_previousStyle = style().data(); if (s_paintOutline) { for(const QPolygonF* itPolygon: m_cachedPolygons) { painter->drawPolyline(*itPolygon); } } } void GeoLineStringGraphicsItem::paintLabel(GeoPainter *painter, const ViewportParams *viewport) const { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } LabelPositionFlags labelPositionFlags = NoLabel; bool isValid = configurePainterForLabel(painter, viewport, labelPositionFlags); if (isValid) { GeoDataStyle::ConstPtr style = this->style(); // Activate the lines below to paint a label background which // prevents antialiasing overpainting glitches, but leads to // other glitches. //QColor const color = style->polyStyle().paintedColor(); //painter->setBackground(QBrush(color)); //painter->setBackgroundMode(Qt::OpaqueMode); const GeoDataLabelStyle& labelStyle = style->labelStyle(); painter->drawLabelsForPolygons(m_cachedPolygons, m_name, FollowLine, labelStyle.paintedColor()); } } bool GeoLineStringGraphicsItem::configurePainterForLine(GeoPainter *painter, const ViewportParams *viewport, const bool isOutline) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); painter->setBackground(QBrush(Qt::transparent)); painter->setBackgroundMode(Qt::TransparentMode); } else { if (isOutline && !style->polyStyle().outline()) { return false; } const GeoDataLineStyle& lineStyle = style->lineStyle(); // To save performance we avoid making changes to the painter's pen. // So we first take a copy of the actual painter pen, make changes to it // and only if the resulting pen is different from the actual pen // we replace the painter's pen with our new pen. // We want to avoid the mandatory detach in QPen::setColor(), // so we carefully check whether applying the setter is needed const QColor linePaintedColor = (!isOutline && (lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine)) ? style->polyStyle().paintedColor() : lineStyle.paintedColor(); if (currentPen.color() != linePaintedColor) { if (linePaintedColor.alpha() == 255) { currentPen.setColor(linePaintedColor); } else { if ( painter->mapQuality() != Marble::HighQuality && painter->mapQuality() != Marble::PrintQuality ) { QColor penColor = linePaintedColor; if (penColor.alpha() != 0) { penColor.setAlpha( 255 ); } if (currentPen.color() != penColor) { currentPen.setColor( penColor ); } } else { currentPen.setColor(linePaintedColor); } } } const float lineWidth = lineStyle.width(); const float linePhysicalWidth = lineStyle.physicalWidth(); float newLineWidth = lineWidth; if (linePhysicalWidth != 0.0) { const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth; newLineWidth = scaledLinePhysicalWidth > lineWidth ? scaledLinePhysicalWidth : lineWidth; } if (!isOutline && lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine) { if (newLineWidth > 2.5) { newLineWidth -= 2.0; } } const qreal lineDrawThreshold = isOutline ? 2.5 : 0.5; // We want to avoid the mandatory detach in QPen::setWidthF(), // so we carefully check whether applying the setter is needed if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) { if (newLineWidth < lineDrawThreshold) { if (painter->pen().style() != Qt::NoPen) { painter->setPen(Qt::NoPen); } return false; // Don't draw any outline and abort painter configuration early } currentPen.setWidthF(newLineWidth); } // No need to avoid detaches inside QPen::setCapsStyle() since Qt does that for us const Qt::PenCapStyle lineCapStyle = lineStyle.capStyle(); currentPen.setCapStyle(lineCapStyle); // No need to avoid detaches inside QPen::setStyle() since Qt does that for us if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) { const Qt::PenStyle linePenStyle = lineStyle.penStyle(); currentPen.setStyle(linePenStyle); if (linePenStyle == Qt::CustomDashLine) { // We want to avoid the mandatory detach in QPen::setDashPattern(), // so we carefully check whether applying the setter is needed if (currentPen.dashPattern() != lineStyle.dashPattern()) { currentPen.setDashPattern(lineStyle.dashPattern()); } } } else { currentPen.setStyle(Qt::SolidLine); } if ( painter->pen() != currentPen ) { painter->setPen( currentPen ); } // Set the background if (!isOutline) { if (lineStyle.background()) { QBrush brush = painter->background(); brush.setColor(style->polyStyle().paintedColor()); painter->setBackground( brush ); painter->setBackgroundMode( Qt::OpaqueMode ); } else { painter->setBackground(QBrush(Qt::transparent)); painter->setBackgroundMode(Qt::TransparentMode); } } else { painter->setBackground(QBrush(Qt::transparent)); painter->setBackgroundMode(Qt::TransparentMode); } } return true; } bool GeoLineStringGraphicsItem::configurePainterForLabel(GeoPainter *painter, const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { const GeoDataLineStyle& lineStyle = style->lineStyle(); // To save performance we avoid making changes to the painter's pen. // So we first take a copy of the actual painter pen, make changes to it // and only if the resulting pen is different from the actual pen // we replace the painter's pen with our new pen. // We want to avoid the mandatory detach in QPen::setColor(), // so we carefully check whether applying the setter is needed const float lineWidth = lineStyle.width(); const float linePhysicalWidth = lineStyle.physicalWidth(); float newLineWidth = lineWidth; if (linePhysicalWidth != 0.0) { const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth; newLineWidth = scaledLinePhysicalWidth > lineWidth ? scaledLinePhysicalWidth : lineWidth; } // We want to avoid the mandatory detach in QPen::setWidthF(), // so we carefully check whether applying the setter is needed if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) { if (newLineWidth < 6.0) { return false; // Don't draw any labels and abort painter configuration early } currentPen.setWidthF(newLineWidth); } if ( painter->pen() != currentPen ) { painter->setPen( currentPen ); } // else qDebug() << "Detach and painter change successfully Avoided!" << Q_FUNC_INFO; if (painter->brush().color() != Qt::transparent) { painter->setBrush(QColor(Qt::transparent)); } if (painter->backgroundMode() == Qt::OpaqueMode) { painter->setBackgroundMode(Qt::TransparentMode); painter->setBackground(QBrush(Qt::transparent)); } // label styles const GeoDataLabelStyle& labelStyle = style->labelStyle(); painter->setFont(labelStyle.font() ); switch (labelStyle.alignment()) { case GeoDataLabelStyle::Corner: case GeoDataLabelStyle::Right: labelPositionFlags |= LineStart; break; case GeoDataLabelStyle::Center: labelPositionFlags |= LineCenter; break; } } return true; } bool GeoLineStringGraphicsItem::canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b) { - return distanceSphere(a, b) * EARTH_RADIUS < 0.1; + return a.sphericalDistanceTo(b) * EARTH_RADIUS < 0.1; } } diff --git a/src/lib/marble/routing/AlternativeRoutesModel.cpp b/src/lib/marble/routing/AlternativeRoutesModel.cpp index 01d5880bf..8ade5c041 100644 --- a/src/lib/marble/routing/AlternativeRoutesModel.cpp +++ b/src/lib/marble/routing/AlternativeRoutesModel.cpp @@ -1,440 +1,440 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2010 Dennis Nienhüser // #include "AlternativeRoutesModel.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataDocument.h" #include "GeoDataFolder.h" #include "GeoDataExtendedData.h" #include "GeoDataLineString.h" #include "GeoDataPlacemark.h" #include "MarbleMath.h" #include #include namespace Marble { class Q_DECL_HIDDEN AlternativeRoutesModel::Private { public: Private(); /** * Returns true if there exists a route with high similarity to the given one */ bool filter( const GeoDataDocument* document ) const; /** * Returns a similarity measure in the range of [0..1]. Two routes with a similarity of 0 can * be treated as totally different (e.g. different route requests), two routes with a similarity * of 1 are considered equal. Otherwise the routes overlap to an extent indicated by the * similarity value -- the higher, the more they do overlap. * @note: The direction of routes is important; reversed routes are not considered equal */ static qreal similarity( const GeoDataDocument* routeA, const GeoDataDocument* routeB ); /** * Returns the distance between the given polygon and the given point */ static qreal distance( const GeoDataLineString &wayPoints, const GeoDataCoordinates &position ); /** * Returns the bearing of the great circle path defined by the coordinates one and two * Based on http://www.movable-type.co.uk/scripts/latlong.html */ static qreal bearing( const GeoDataCoordinates &one, const GeoDataCoordinates &two ); /** * Returns the distance between the given point and the line segment (not line) defined * by the two coordinates lineA and lineB */ static qreal distance( const GeoDataCoordinates &satellite, const GeoDataCoordinates &lineA, const GeoDataCoordinates &lineB ); /** * Returns the point reached when traveling the given distance from start with the given direction */ static GeoDataCoordinates coordinates( const GeoDataCoordinates &start, qreal distance, qreal bearing ); /** * Returns the similarity between routeA and routeB. This method is not symmetric, i.e. in * general unidirectionalSimilarity(a,b) != unidirectionalSimilarity(b,a) */ static qreal unidirectionalSimilarity( const GeoDataDocument* routeA, const GeoDataDocument* routeB ); /** * (Primitive) scoring for routes */ static bool higherScore( const GeoDataDocument* one, const GeoDataDocument* two ); /** * Returns true if the given route contains instructions (placemarks with turn instructions) */ static qreal instructionScore( const GeoDataDocument* document ); static const GeoDataLineString* waypoints( const GeoDataDocument* document ); static int nonZero( const QImage &image ); static QPolygonF polygon( const GeoDataLineString &lineString, qreal x, qreal y, qreal sx, qreal sy ); /** The currently shown alternative routes (model data) */ QVector m_routes; /** Pending route data (waiting for other results to come in) */ QVector m_restrainedRoutes; /** Counts the time between route request and first result */ QTime m_responseTime; int m_currentIndex; }; AlternativeRoutesModel::Private::Private() : m_currentIndex( -1 ) { // nothing to do } int AlternativeRoutesModel::Private::nonZero( const QImage &image ) { QRgb const black = qRgb( 0, 0, 0 ); int count = 0; for ( int y = 0; y < image.height(); ++y ) { QRgb* destLine = (QRgb*) image.scanLine( y ); for ( int x = 0; x < image.width(); ++x ) { count += destLine[x] == black ? 0 : 1; } } return count; } QPolygonF AlternativeRoutesModel::Private::polygon( const GeoDataLineString &lineString, qreal x, qreal y, qreal sx, qreal sy ) { QPolygonF poly; for ( int i = 0; i < lineString.size(); ++i ) { poly << QPointF( qAbs( ( lineString)[i].longitude() - x ) * sx, qAbs( ( lineString)[i].latitude() - y ) * sy ); } return poly; } bool AlternativeRoutesModel::Private::filter( const GeoDataDocument* document ) const { for ( int i=0; i 0.8 ) { return true; } } return false; } qreal AlternativeRoutesModel::Private::similarity( const GeoDataDocument* routeA, const GeoDataDocument* routeB ) { return qMax( unidirectionalSimilarity( routeA, routeB ), unidirectionalSimilarity( routeB, routeA ) ); } qreal AlternativeRoutesModel::Private::distance( const GeoDataLineString &wayPoints, const GeoDataCoordinates &position ) { Q_ASSERT( !wayPoints.isEmpty() ); qreal minDistance = 0; for ( int i = 1; i < wayPoints.size(); ++i ) { qreal dist = distance( position, wayPoints.at( i-1 ), wayPoints.at( i ) ); if ( minDistance <= 0 || dist < minDistance ) { minDistance = dist; } } return minDistance; } qreal AlternativeRoutesModel::Private::bearing( const GeoDataCoordinates &one, const GeoDataCoordinates &two ) { qreal delta = two.longitude() - one.longitude(); qreal lat1 = one.latitude(); qreal lat2 = two.latitude(); return fmod( atan2( sin ( delta ) * cos ( lat2 ), cos( lat1 ) * sin( lat2 ) - sin( lat1 ) * cos( lat2 ) * cos ( delta ) ), 2 * M_PI ); } GeoDataCoordinates AlternativeRoutesModel::Private::coordinates( const GeoDataCoordinates &start, qreal distance, qreal bearing ) { qreal lat1 = start.latitude(); qreal lon1 = start.longitude(); qreal lat2 = asin( sin( lat1 ) * cos( distance ) + cos( lat1 ) * sin( distance ) * cos( bearing ) ); qreal lon2 = lon1 + atan2( sin( bearing ) * sin( distance ) * cos( lat1 ), cos( distance ) - sin( lat1 ) * sin( lat2 ) ); return GeoDataCoordinates( lon2, lat2 ); } qreal AlternativeRoutesModel::Private::distance( const GeoDataCoordinates &satellite, const GeoDataCoordinates &lineA, const GeoDataCoordinates &lineB ) { - qreal dist = distanceSphere( lineA, satellite ); + const qreal dist = lineA.sphericalDistanceTo(satellite); qreal bearA = bearing( lineA, satellite ); qreal bearB = bearing( lineA, lineB ); qreal result = asin( sin ( dist ) * sin( bearB - bearA ) ); - Q_ASSERT( qMax( distanceSphere(satellite, lineA), distanceSphere(satellite, lineB) ) >= qAbs(result) ); + Q_ASSERT(qMax(satellite.sphericalDistanceTo(lineA), satellite.sphericalDistanceTo(lineB)) >= qAbs(result)); result = acos( cos( dist ) / cos( result ) ); /** @todo: This is a naive approach. Look into the maths. */ - qreal final = qMin( distanceSphere( satellite, lineA ), distanceSphere( satellite, lineB ) ); - if ( result >= 0 && result <= distanceSphere( lineA, lineB ) ) { + const qreal final = qMin(satellite.sphericalDistanceTo(lineA), satellite.sphericalDistanceTo(lineB)); + if ( result >= 0 && result <= lineA.sphericalDistanceTo(lineB)) { GeoDataCoordinates nearest = coordinates( lineA, result, bearB ); - return qMin( final, distanceSphere( satellite, nearest ) ); + return qMin(final, satellite.sphericalDistanceTo(nearest)); } else { return final; } } qreal AlternativeRoutesModel::Private::unidirectionalSimilarity( const GeoDataDocument* routeA, const GeoDataDocument* routeB ) { const GeoDataLineString* waypointsA = waypoints( routeA ); const GeoDataLineString* waypointsB = waypoints( routeB ); if ( !waypointsA || !waypointsB ) { return 0.0; } QImage image( 64, 64, QImage::Format_ARGB32_Premultiplied ); image.fill( qRgb( 0, 0, 0 ) ); GeoDataLatLonBox box = GeoDataLatLonBox::fromLineString( *waypointsA ); box = box.united( GeoDataLatLonBox::fromLineString( *waypointsB ) ); if ( !box.width() || !box.height() ) { return 0.0; } qreal const sw = image.width() / box.width(); qreal const sh = image.height() / box.height(); QPainter painter( &image ); painter.setPen( QColor( Qt::white ) ); painter.drawPoints( Private::polygon( *waypointsA, box.west(), box.north(), sw, sh ) ); int const countA = Private::nonZero( image ); painter.drawPoints( Private::polygon( *waypointsB, box.west(), box.north(), sw, sh ) ); int const countB = Private::nonZero( image ); Q_ASSERT( countA <= countB ); return countB ? 1.0 - qreal( countB - countA ) / countB : 0; } bool AlternativeRoutesModel::Private::higherScore( const GeoDataDocument* one, const GeoDataDocument* two ) { qreal instructionScoreA = instructionScore( one ); qreal instructionScoreB = instructionScore( two ); if ( instructionScoreA != instructionScoreB ) { return instructionScoreA > instructionScoreB; } qreal lengthA = waypoints( one )->length( EARTH_RADIUS ); qreal lengthB = waypoints( two )->length( EARTH_RADIUS ); return lengthA < lengthB; } qreal AlternativeRoutesModel::Private::instructionScore( const GeoDataDocument* document ) { bool hasInstructions = false; QStringList blacklist = QStringList() << "" << "Route" << "Tessellated"; QVector folders = document->folderList(); for( const GeoDataFolder *folder: folders ) { for( const GeoDataPlacemark *placemark: folder->placemarkList() ) { if ( !blacklist.contains( placemark->name() ) ) { hasInstructions = true; break; } } } for( const GeoDataPlacemark *placemark: document->placemarkList() ) { if ( !blacklist.contains( placemark->name() ) ) { hasInstructions = true; if (placemark->extendedData().contains(QStringLiteral("turnType"))) { return 1.0; } } } return hasInstructions ? 0.5 : 0.0; } const GeoDataLineString* AlternativeRoutesModel::Private::waypoints( const GeoDataDocument* document ) { QVector folders = document->folderList(); for( const GeoDataFolder *folder: folders ) { for( const GeoDataPlacemark *placemark: folder->placemarkList() ) { const GeoDataGeometry* geometry = placemark->geometry(); const GeoDataLineString* lineString = dynamic_cast( geometry ); if ( lineString ) { return lineString; } } } for( const GeoDataPlacemark *placemark: document->placemarkList() ) { const GeoDataGeometry* geometry = placemark->geometry(); const GeoDataLineString* lineString = dynamic_cast( geometry ); if ( lineString ) { return lineString; } } return 0; } AlternativeRoutesModel::AlternativeRoutesModel( QObject *parent ) : QAbstractListModel( parent ), d( new Private() ) { // nothing to do } AlternativeRoutesModel::~AlternativeRoutesModel() { clear(); delete d; } int AlternativeRoutesModel::rowCount ( const QModelIndex & ) const { return d->m_routes.size(); } QVariant AlternativeRoutesModel::headerData ( int, Qt::Orientation, int ) const { return QVariant(); } QVariant AlternativeRoutesModel::data ( const QModelIndex &index, int role ) const { QVariant result; if ( role == Qt::DisplayRole && index.column() == 0 && index.row() >= 0 && index.row() < d->m_routes.size() ) { result = d->m_routes.at( index.row() )->name(); } return result; } const GeoDataDocument *AlternativeRoutesModel::route(int index) const { if ( index >= 0 && index < d->m_routes.size() ) { return d->m_routes.at(index); } return 0; } void AlternativeRoutesModel::newRequest( RouteRequest * ) { d->m_responseTime.start(); d->m_currentIndex = -1; clear(); } void AlternativeRoutesModel::addRestrainedRoutes() { Q_ASSERT( d->m_routes.isEmpty() ); std::sort( d->m_restrainedRoutes.begin(), d->m_restrainedRoutes.end(), Private::higherScore ); for( GeoDataDocument* route: d->m_restrainedRoutes ) { if ( !d->filter( route ) ) { int affected = d->m_routes.size(); beginInsertRows( QModelIndex(), affected, affected ); // GeoDataDocument* base = d->m_routes.isEmpty() ? 0 : d->m_routes.first(); d->m_routes.push_back( route ); endInsertRows(); } } d->m_restrainedRoutes.clear(); Q_ASSERT( !d->m_routes.isEmpty() ); setCurrentRoute( 0 ); } void AlternativeRoutesModel::addRoute( GeoDataDocument* document, WritePolicy policy ) { if (policy != Instant) { if (d->m_routes.isEmpty()) { d->m_restrainedRoutes.push_back(document); if (d->m_restrainedRoutes.isEmpty()) { // First const int responseTime = d->m_responseTime.elapsed(); const int timeout = qMin(500, qMax(50, responseTime * 2)); QTimer::singleShot(timeout, this, SLOT(addRestrainedRoutes())); return; } } for ( int i=0; im_routes.size(); ++i ) { qreal similarity = Private::similarity( document, d->m_routes.at( i ) ); if ( similarity > 0.8 ) { if ( Private::higherScore( document, d->m_routes.at( i ) ) ) { d->m_routes[i] = document; QModelIndex changed = index( i ); emit dataChanged( changed, changed ); } return; } } } const int affected = d->m_routes.size(); beginInsertRows(QModelIndex(), affected, affected); d->m_routes.push_back(document); endInsertRows(); } const GeoDataLineString* AlternativeRoutesModel::waypoints( const GeoDataDocument* document ) { return Private::waypoints( document ); } void AlternativeRoutesModel::setCurrentRoute( int index ) { if ( index >= 0 && index < rowCount() && d->m_currentIndex != index ) { d->m_currentIndex = index; emit currentRouteChanged( currentRoute() ); emit currentRouteChanged( d->m_currentIndex ); } } const GeoDataDocument *AlternativeRoutesModel::currentRoute() const { const GeoDataDocument *result = 0; if ( d->m_currentIndex >= 0 && d->m_currentIndex < rowCount() ) { result = d->m_routes[d->m_currentIndex]; } return result; } void AlternativeRoutesModel::clear() { beginResetModel(); QVector routes = d->m_routes; d->m_currentIndex = -1; d->m_routes.clear(); qDeleteAll(routes); endResetModel(); } } // namespace Marble #include "moc_AlternativeRoutesModel.cpp" diff --git a/src/lib/marble/routing/RouteSegment.cpp b/src/lib/marble/routing/RouteSegment.cpp index b533ed578..2da35ef7f 100644 --- a/src/lib/marble/routing/RouteSegment.cpp +++ b/src/lib/marble/routing/RouteSegment.cpp @@ -1,237 +1,237 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Dennis Nienhüser // #include "RouteSegment.h" #include "MarbleMath.h" #include "GeoDataLatLonAltBox.h" namespace Marble { RouteSegment::RouteSegment() : m_valid( false ), m_distance( 0.0 ), m_travelTime( 0 ), m_nextRouteSegment( 0 ) { // nothing to do } qreal RouteSegment::distance() const { return m_distance; } const Maneuver & RouteSegment::maneuver() const { return m_maneuver; } void RouteSegment::setManeuver( const Maneuver &maneuver ) { m_maneuver = maneuver; m_valid = true; } const GeoDataLineString & RouteSegment::path() const { return m_path; } void RouteSegment::setPath( const GeoDataLineString &path ) { m_path = path; m_distance = m_path.length( EARTH_RADIUS ); m_bounds = m_path.latLonAltBox(); m_valid = true; } int RouteSegment::travelTime() const { return m_travelTime; } void RouteSegment::setTravelTime( int seconds ) { m_travelTime = seconds; m_valid = true; } GeoDataLatLonBox RouteSegment::bounds() const { return m_bounds; } const RouteSegment & RouteSegment::nextRouteSegment() const { if ( m_nextRouteSegment ) { return *m_nextRouteSegment; } static RouteSegment invalid; return invalid; } void RouteSegment::setNextRouteSegment( const RouteSegment* segment ) { m_nextRouteSegment = segment; if ( segment ) { m_valid = true; } } bool RouteSegment::isValid() const { return m_valid; } qreal RouteSegment::distancePointToLine(const GeoDataCoordinates &p, const GeoDataCoordinates &a, const GeoDataCoordinates &b) { qreal const y0 = p.latitude(); qreal const x0 = p.longitude(); qreal const y1 = a.latitude(); qreal const x1 = a.longitude(); qreal const y2 = b.latitude(); qreal const x2 = b.longitude(); qreal const y01 = x0 - x1; qreal const x01 = y0 - y1; qreal const y10 = x1 - x0; qreal const x10 = y1 - y0; qreal const y21 = x2 - x1; qreal const x21 = y2 - y1; qreal const len =(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2); qreal const t = (x01*x21 + y01*y21) / len; if ( t<0.0 ) { - return EARTH_RADIUS * distanceSphere(p, a); + return EARTH_RADIUS * p.sphericalDistanceTo(a); } else if ( t > 1.0 ) { - return EARTH_RADIUS * distanceSphere(p, b); + return EARTH_RADIUS * p.sphericalDistanceTo(b); } else { qreal const nom = qAbs( x21 * y10 - x10 * y21 ); qreal const den = sqrt( x21 * x21 + y21 * y21 ); return EARTH_RADIUS * nom / den; } } GeoDataCoordinates RouteSegment::projected(const GeoDataCoordinates &p, const GeoDataCoordinates &a, const GeoDataCoordinates &b) { qreal const y0 = p.latitude(); qreal const x0 = p.longitude(); qreal const y1 = a.latitude(); qreal const x1 = a.longitude(); qreal const y2 = b.latitude(); qreal const x2 = b.longitude(); qreal const y01 = x0 - x1; qreal const x01 = y0 - y1; qreal const y21 = x2 - x1; qreal const x21 = y2 - y1; qreal const len =(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2); qreal const t = (x01*x21 + y01*y21) / len; if ( t<0.0 ) { return a; } else if ( t > 1.0 ) { return b; } else { // a + t (b - a); qreal const lon = x1 + t * ( x2 - x1 ); qreal const lat = y1 + t * ( y2 - y1 ); return GeoDataCoordinates( lon, lat ); } } qreal RouteSegment::distanceTo( const GeoDataCoordinates &point, GeoDataCoordinates &closest, GeoDataCoordinates &interpolated ) const { Q_ASSERT( !m_path.isEmpty() ); if ( m_path.size() == 1 ) { closest = m_path.first(); - return EARTH_RADIUS * distanceSphere( m_path.first(), point ); + return EARTH_RADIUS * m_path.first().sphericalDistanceTo(point); } qreal minDistance = -1.0; int minIndex = 0; for ( int i=1; i // #include "RoutingModel.h" #include "Planet.h" #include "PlanetFactory.h" #include "MarbleMath.h" #include "Route.h" #include "RouteRequest.h" #include "PositionTracking.h" #include "MarbleGlobal.h" #include "GeoDataAccuracy.h" #include namespace Marble { class RoutingModelPrivate { public: enum RouteDeviation { Unknown, OnRoute, OffRoute }; explicit RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request); Route m_route; PositionTracking *const m_positionTracking; RouteRequest* const m_request; QHash m_roleNames; RouteDeviation m_deviation; void updateViaPoints( const GeoDataCoordinates &position ); }; RoutingModelPrivate::RoutingModelPrivate(PositionTracking *positionTracking, RouteRequest *request) : m_positionTracking(positionTracking), m_request(request), m_deviation(Unknown) { // nothing to do } void RoutingModelPrivate::updateViaPoints( const GeoDataCoordinates &position ) { // Mark via points visited after approaching them in a range of 500m or less qreal const threshold = 500 / EARTH_RADIUS; for( int i=0; isize(); ++i ) { if ( !m_request->visited( i ) ) { - if ( distanceSphere( position, m_request->at( i ) ) < threshold ) { + if (position.sphericalDistanceTo(m_request->at(i)) < threshold) { m_request->setVisited( i, true ); } } } } RoutingModel::RoutingModel(RouteRequest *request, PositionTracking *positionTracking, QObject *parent) : QAbstractListModel(parent), d(new RoutingModelPrivate(positionTracking, request)) { QObject::connect( d->m_positionTracking, SIGNAL(gpsLocation(GeoDataCoordinates,qreal)), this, SLOT(updatePosition(GeoDataCoordinates,qreal)) ); QHash roles; roles.insert( Qt::DisplayRole, "display" ); roles.insert( RoutingModel::TurnTypeIconRole, "turnTypeIcon" ); roles.insert( RoutingModel::LongitudeRole, "longitude" ); roles.insert( RoutingModel::LatitudeRole, "latitude" ); d->m_roleNames = roles; } RoutingModel::~RoutingModel() { delete d; } int RoutingModel::rowCount ( const QModelIndex &parent ) const { return parent.isValid() ? 0 : d->m_route.turnPoints().size(); } QVariant RoutingModel::headerData ( int section, Qt::Orientation orientation, int role ) const { if ( orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0 ) { return QString( "Instruction" ); } return QAbstractListModel::headerData( section, orientation, role ); } QVariant RoutingModel::data ( const QModelIndex & index, int role ) const { if ( !index.isValid() ) { return QVariant(); } if ( index.row() < d->m_route.turnPoints().size() && index.column() == 0 ) { const RouteSegment &segment = d->m_route.at( index.row() ); switch ( role ) { case Qt::DisplayRole: case Qt::ToolTipRole: return segment.maneuver().instructionText(); break; case Qt::DecorationRole: { bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( segment.maneuver().hasWaypoint() ) { int const size = smallScreen ? 64 : 32; return d->m_request->pixmap( segment.maneuver().waypointIndex(), size, size/4 ); } else { QPixmap const pixmap = segment.maneuver().directionPixmap(); return smallScreen ? pixmap : pixmap.scaled( 32, 32 ); } } break; case Qt::SizeHintRole: { bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; int const size = smallScreen ? 64 : 32; return QSize( size, size ); } break; case RoutingModel::CoordinateRole: return QVariant::fromValue( segment.maneuver().position() ); break; case RoutingModel::LongitudeRole: return QVariant(segment.maneuver().position().longitude(GeoDataCoordinates::Degree)); break; case RoutingModel::LatitudeRole: return QVariant(segment.maneuver().position().latitude(GeoDataCoordinates::Degree)); break; case RoutingModel::TurnTypeIconRole: return segment.maneuver().directionPixmap(); break; default: return QVariant(); } } return QVariant(); } QHash RoutingModel::roleNames() const { return d->m_roleNames; } void RoutingModel::setRoute( const Route &route ) { d->m_route = route; d->m_deviation = RoutingModelPrivate::Unknown; beginResetModel(); endResetModel(); emit currentRouteChanged(); } void RoutingModel::exportGpx( QIODevice *device ) const { QString content = QLatin1String("\n" "\n" "\n \n " "Marble Virtual Globe\n \n\n" " \n Route\n"); bool hasAltitude = false; for ( int i=0; !hasAltitude && im_route.size(); ++i ) { hasAltitude = d->m_route.at( i ).maneuver().position().altitude() != 0.0; } for ( int i=0; im_route.size(); ++i ) { const Maneuver &maneuver = d->m_route.at( i ).maneuver(); qreal lon = maneuver.position().longitude( GeoDataCoordinates::Degree ); qreal lat = maneuver.position().latitude( GeoDataCoordinates::Degree ); QString const text = maneuver.instructionText(); content += QString( " \n" ).arg( lat, 0, 'f', 7 ).arg( lon, 0, 'f', 7 ); content += QString( " %1\n").arg( text ); if ( hasAltitude ) { content += QString( " %1\n" ).arg( maneuver.position().altitude(), 0, 'f', 2 ); } content += QString( " \n" ); } content += QLatin1String(" \n" "\n Route\n \n"); GeoDataLineString points = d->m_route.path(); hasAltitude = false; for ( int i=0; !hasAltitude && i\n" ).arg( lat, 0, 'f', 7 ).arg( lon, 0, 'f', 7 ); if ( hasAltitude ) { content += QString( " %1\n" ).arg( point.altitude(), 0, 'f', 2 ); } content += QString( " \n" ); } content += QLatin1String(" \n \n" "\n"); device->write( content.toUtf8() ); } void RoutingModel::clear() { d->m_route = Route(); beginResetModel(); endResetModel(); emit currentRouteChanged(); } int RoutingModel::rightNeighbor( const GeoDataCoordinates &position, RouteRequest const *const route ) const { Q_ASSERT( route && "Must not pass a null route "); // Quick result for trivial cases if ( route->size() < 3 ) { return route->size() - 1; } // Generate an ordered list of all waypoints GeoDataLineString points = d->m_route.path(); QMap mapping; // Force first mapping point to match the route start mapping[0] = 0; // Calculate the mapping between waypoints and via points // Need two for loops to avoid getting stuck in local minima for ( int j=1; jsize()-1; ++j ) { qreal minDistance = -1.0; for ( int i=mapping[j-1]; iat(j) ); + const qreal distance = points[i].sphericalDistanceTo(route->at(j)); if (minDistance < 0.0 || distance < minDistance ) { mapping[j] = i; minDistance = distance; } } } // Determine waypoint with minimum distance to the provided position qreal minWaypointDistance = -1.0; int waypoint=0; for ( int i=0; isize()-1] = points.size()-1; // Determine neighbor based on the mapping QMap::const_iterator iter = mapping.constBegin(); for ( ; iter != mapping.constEnd(); ++iter ) { if ( iter.value() > waypoint ) { int index = iter.key(); Q_ASSERT( index >= 0 && index <= route->size() ); return index; } } return route->size()-1; } void RoutingModel::updatePosition( const GeoDataCoordinates& location, qreal speed ) { d->m_route.setPosition( location ); d->updateViaPoints( location ); const qreal planetRadius = PlanetFactory::construct("earth").radius(); - qreal distance = planetRadius * distanceSphere( location, d->m_route.positionOnRoute() ); + const qreal distance = planetRadius * location.sphericalDistanceTo(d->m_route.positionOnRoute()); emit positionChanged(); qreal deviation = 0.0; if ( d->m_positionTracking && d->m_positionTracking->accuracy().vertical > 0.0 ) { deviation = qMax( d->m_positionTracking->accuracy().vertical, d->m_positionTracking->accuracy().horizontal ); } qreal const threshold = deviation + qBound(10.0, speed*10.0, 150.0); RoutingModelPrivate::RouteDeviation const deviated = distance < threshold ? RoutingModelPrivate::OnRoute : RoutingModelPrivate::OffRoute; if ( d->m_deviation != deviated ) { d->m_deviation = deviated; emit deviatedFromRoute( deviated == RoutingModelPrivate::OffRoute ); } } bool RoutingModel::deviatedFromRoute() const { return d->m_deviation == RoutingModelPrivate::OffRoute; } const Route & RoutingModel::route() const { return d->m_route; } } // namespace Marble #include "moc_RoutingModel.cpp" diff --git a/src/lib/marble/routing/RoutingWidget.cpp b/src/lib/marble/routing/RoutingWidget.cpp index a533476bc..cdd7af1f4 100644 --- a/src/lib/marble/routing/RoutingWidget.cpp +++ b/src/lib/marble/routing/RoutingWidget.cpp @@ -1,1032 +1,1032 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2010 Dennis Nienhüser // #include "RoutingWidget.h" #include "GeoDataLineString.h" #include "GeoDataLookAt.h" #include "GeoDataPlaylist.h" #include "GeoDataTour.h" #include "GeoDataFlyTo.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataPlacemark.h" #include "TourPlayback.h" #include "Maneuver.h" #include "MarbleModel.h" #include "MarblePlacemarkModel.h" #include "MarbleWidget.h" #include "MarbleWidgetInputHandler.h" #include "Route.h" #include "RouteRequest.h" #include "RoutingInputWidget.h" #include "RoutingLayer.h" #include "RoutingManager.h" #include "RoutingModel.h" #include "RoutingProfilesModel.h" #include "RoutingProfileSettingsDialog.h" #include "GeoDataDocument.h" #include "GeoDataTreeModel.h" #include "GeoDataCreate.h" #include "GeoDataUpdate.h" #include "GeoDataDelete.h" #include "AlternativeRoutesModel.h" #include "RouteSyncManager.h" #include "CloudRoutesDialog.h" #include "CloudSyncManager.h" #include "PlaybackAnimatedUpdateItem.h" #include "GeoDataAnimatedUpdate.h" #include "MarbleMath.h" #include "Planet.h" #include #include #include #include #include #include #include #include #include "ui_RoutingWidget.h" namespace Marble { struct WaypointInfo { int index; double distance; // distance to route start GeoDataCoordinates coordinates; Maneuver maneuver; QString info; WaypointInfo( int index_, double distance_, const GeoDataCoordinates &coordinates_, Maneuver maneuver_, const QString& info_ ) : index( index_ ), distance( distance_ ), coordinates( coordinates_ ), maneuver( maneuver_ ), info( info_ ) { // nothing to do } }; class RoutingWidgetPrivate { public: Ui::RoutingWidget m_ui; MarbleWidget *const m_widget; RoutingManager *const m_routingManager; RoutingLayer *const m_routingLayer; RoutingInputWidget *m_activeInput; QVector m_inputWidgets; RoutingInputWidget *m_inputRequest; QAbstractItemModel *const m_routingModel; RouteRequest *const m_routeRequest; RouteSyncManager *m_routeSyncManager; bool m_zoomRouteAfterDownload; QTimer m_progressTimer; QVector m_progressAnimation; GeoDataDocument *m_document; GeoDataTour *m_tour; TourPlayback *m_playback; int m_currentFrame; int m_iconSize; int m_collapse_width; bool m_playing; QString m_planetId; QToolBar *m_toolBar; QToolButton *m_openRouteButton; QToolButton *m_saveRouteButton; QAction *m_cloudSyncSeparator; QAction *m_uploadToCloudAction; QAction *m_openCloudRoutesAction; QToolButton *m_addViaButton; QToolButton *m_reverseRouteButton; QToolButton *m_clearRouteButton; QToolButton *m_configureButton; QToolButton *m_playButton; QProgressDialog* m_routeUploadDialog; /** Constructor */ RoutingWidgetPrivate(RoutingWidget *parent, MarbleWidget *marbleWidget ); /** * @brief Toggle between simple search view and route view * If only one input field exists, hide all buttons */ void adjustInputWidgets(); void adjustSearchButton(); /** * @brief Change the active input widget * The active input widget influences what is shown in the paint layer * and in the list view: Either a set of placemarks that correspond to * a runner search result or the current route */ void setActiveInput( RoutingInputWidget* widget ); void setupToolBar(); private: void createProgressAnimation(); RoutingWidget *m_parent; }; RoutingWidgetPrivate::RoutingWidgetPrivate( RoutingWidget *parent, MarbleWidget *marbleWidget ) : m_widget( marbleWidget ), m_routingManager( marbleWidget->model()->routingManager() ), m_routingLayer( marbleWidget->routingLayer() ), m_activeInput( 0 ), m_inputRequest( 0 ), m_routingModel( m_routingManager->routingModel() ), m_routeRequest( marbleWidget->model()->routingManager()->routeRequest() ), m_routeSyncManager( 0 ), m_zoomRouteAfterDownload( false ), m_document( 0 ), m_tour( 0 ), m_playback( 0 ), m_currentFrame( 0 ), m_iconSize( 16 ), m_collapse_width( 0 ), m_playing( false ), m_planetId(marbleWidget->model()->planetId()), m_toolBar( 0 ), m_openRouteButton( 0 ), m_saveRouteButton( 0 ), m_cloudSyncSeparator( 0 ), m_uploadToCloudAction( 0 ), m_openCloudRoutesAction( 0 ), m_addViaButton( 0 ), m_reverseRouteButton( 0 ), m_clearRouteButton( 0 ), m_configureButton( 0 ), m_routeUploadDialog( 0 ), m_parent( parent ) { createProgressAnimation(); m_progressTimer.setInterval( 100 ); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { m_iconSize = 32; } } void RoutingWidgetPrivate::adjustInputWidgets() { for ( int i = 0; i < m_inputWidgets.size(); ++i ) { m_inputWidgets[i]->setIndex( i ); } adjustSearchButton(); } void RoutingWidgetPrivate::adjustSearchButton() { QString text = QObject::tr( "Get Directions" ); QString tooltip = QObject::tr( "Retrieve routing instructions for the selected destinations." ); int validInputs = 0; for ( int i = 0; i < m_inputWidgets.size(); ++i ) { if ( m_inputWidgets[i]->hasTargetPosition() ) { ++validInputs; } } if ( validInputs < 2 ) { text = QObject::tr( "Search" ); tooltip = QObject::tr( "Find places matching the search term" ); } m_ui.searchButton->setText( text ); m_ui.searchButton->setToolTip( tooltip ); } void RoutingWidgetPrivate::setActiveInput( RoutingInputWidget *widget ) { Q_ASSERT( widget && "Must not pass null" ); MarblePlacemarkModel *model = widget->searchResultModel(); m_activeInput = widget; m_ui.directionsListView->setModel( model ); m_routingLayer->setPlacemarkModel( model ); m_routingLayer->synchronizeWith( m_ui.directionsListView->selectionModel() ); } void RoutingWidgetPrivate::setupToolBar() { m_toolBar = new QToolBar; m_openRouteButton = new QToolButton; m_openRouteButton->setToolTip( QObject::tr("Open Route") ); m_openRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-open.png"))); m_toolBar->addWidget(m_openRouteButton); m_saveRouteButton = new QToolButton; m_saveRouteButton->setToolTip( QObject::tr("Save Route") ); m_saveRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-save.png"))); m_toolBar->addWidget(m_saveRouteButton); m_playButton = new QToolButton; m_playButton->setToolTip( QObject::tr("Preview Route") ); m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); m_toolBar->addWidget(m_playButton); m_cloudSyncSeparator = m_toolBar->addSeparator(); m_uploadToCloudAction = m_toolBar->addAction( QObject::tr("Upload to Cloud") ); m_uploadToCloudAction->setToolTip( QObject::tr("Upload to Cloud") ); m_uploadToCloudAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-upload.png"))); m_openCloudRoutesAction = m_toolBar->addAction( QObject::tr("Manage Cloud Routes") ); m_openCloudRoutesAction->setToolTip( QObject::tr("Manage Cloud Routes") ); m_openCloudRoutesAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-download.png"))); m_toolBar->addSeparator(); m_addViaButton = new QToolButton; m_addViaButton->setToolTip( QObject::tr("Add Via") ); m_addViaButton->setIcon(QIcon(QStringLiteral(":/marble/list-add.png"))); m_toolBar->addWidget(m_addViaButton); m_reverseRouteButton = new QToolButton; m_reverseRouteButton->setToolTip( QObject::tr("Reverse Route") ); m_reverseRouteButton->setIcon(QIcon(QStringLiteral(":/marble/reverse.png"))); m_toolBar->addWidget(m_reverseRouteButton); m_clearRouteButton = new QToolButton; m_clearRouteButton->setToolTip( QObject::tr("Clear Route") ); m_clearRouteButton->setIcon(QIcon(QStringLiteral(":/marble/edit-clear.png"))); m_toolBar->addWidget(m_clearRouteButton); m_toolBar->addSeparator(); m_configureButton = new QToolButton; m_configureButton->setToolTip( QObject::tr("Settings") ); m_configureButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/configure.png"))); m_toolBar->addWidget(m_configureButton); QObject::connect( m_openRouteButton, SIGNAL(clicked()), m_parent, SLOT(openRoute()) ); QObject::connect( m_saveRouteButton, SIGNAL(clicked()), m_parent, SLOT(saveRoute()) ); QObject::connect( m_uploadToCloudAction, SIGNAL(triggered()), m_parent, SLOT(uploadToCloud()) ); QObject::connect( m_openCloudRoutesAction, SIGNAL(triggered()), m_parent, SLOT(openCloudRoutesDialog())); QObject::connect( m_addViaButton, SIGNAL(clicked()), m_parent, SLOT(addInputWidget()) ); QObject::connect( m_reverseRouteButton, SIGNAL(clicked()), m_routingManager, SLOT(reverseRoute()) ); QObject::connect( m_clearRouteButton, SIGNAL(clicked()), m_routingManager, SLOT(clearRoute()) ); QObject::connect( m_configureButton, SIGNAL(clicked()), m_parent, SLOT(configureProfile()) ); QObject::connect( m_playButton, SIGNAL(clicked()), m_parent, SLOT(toggleRoutePlay()) ); m_toolBar->setIconSize(QSize(16, 16)); m_ui.toolBarLayout->addWidget(m_toolBar, 0, Qt::AlignLeft); } void RoutingWidgetPrivate::createProgressAnimation() { // Size parameters qreal const h = m_iconSize / 2.0; // Half of the icon size qreal const q = h / 2.0; // Quarter of the icon size qreal const d = 7.5; // Circle diameter qreal const r = d / 2.0; // Circle radius // Canvas parameters QImage canvas( m_iconSize, m_iconSize, QImage::Format_ARGB32 ); QPainter painter( &canvas ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.setPen( QColor ( Qt::gray ) ); painter.setBrush( QColor( Qt::white ) ); // Create all frames for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) { canvas.fill( Qt::transparent ); QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d ); QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d ); painter.drawEllipse( firstCircle ); painter.drawEllipse( secondCircle ); m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) ); } } RoutingWidget::RoutingWidget( MarbleWidget *marbleWidget, QWidget *parent ) : QWidget( parent ), d( new RoutingWidgetPrivate( this, marbleWidget ) ) { d->m_ui.setupUi( this ); d->setupToolBar(); d->m_ui.routeComboBox->setVisible( false ); d->m_ui.routeComboBox->setModel( d->m_routingManager->alternativeRoutesModel() ); layout()->setMargin( 0 ); d->m_ui.routingProfileComboBox->setModel( d->m_routingManager->profilesModel() ); connect( d->m_routingManager->profilesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(selectFirstProfile()) ); connect( d->m_routingManager->profilesModel(), SIGNAL(modelReset()), this, SLOT(selectFirstProfile()) ); connect( d->m_routingLayer, SIGNAL(placemarkSelected(QModelIndex)), this, SLOT(activatePlacemark(QModelIndex)) ); connect( d->m_routingManager, SIGNAL(stateChanged(RoutingManager::State)), this, SLOT(updateRouteState(RoutingManager::State)) ); connect( d->m_routeRequest, SIGNAL(positionAdded(int)), this, SLOT(insertInputWidget(int)) ); connect( d->m_routeRequest, SIGNAL(positionRemoved(int)), this, SLOT(removeInputWidget(int)) ); connect( d->m_routeRequest, SIGNAL(routingProfileChanged()), this, SLOT(updateActiveRoutingProfile()) ); connect( &d->m_progressTimer, SIGNAL(timeout()), this, SLOT(updateProgress()) ); connect( d->m_ui.routeComboBox, SIGNAL(currentIndexChanged(int)), d->m_routingManager->alternativeRoutesModel(), SLOT(setCurrentRoute(int)) ); connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(currentRouteChanged(int)), d->m_ui.routeComboBox, SLOT(setCurrentIndex(int)) ); connect( d->m_ui.routingProfileComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setRoutingProfile(int)) ); connect( d->m_ui.routingProfileComboBox, SIGNAL(activated(int)), this, SLOT(retrieveRoute()) ); connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(updateAlternativeRoutes()) ); d->m_ui.directionsListView->setModel( d->m_routingModel ); QItemSelectionModel *selectionModel = d->m_ui.directionsListView->selectionModel(); d->m_routingLayer->synchronizeWith( selectionModel ); connect( d->m_ui.directionsListView, SIGNAL(activated(QModelIndex)), this, SLOT(activateItem(QModelIndex)) ); // FIXME: apply for this sector connect( d->m_ui.searchButton, SIGNAL(clicked()), this, SLOT(retrieveRoute()) ); connect( d->m_ui.showInstructionsButton, SIGNAL(clicked(bool)), this, SLOT(showDirections()) ); for( int i=0; im_routeRequest->size(); ++i ) { insertInputWidget( i ); } for ( int i=0; i<2 && d->m_inputWidgets.size()<2; ++i ) { // Start with source and destination if the route is empty yet addInputWidget(); } //d->m_ui.descriptionLabel->setVisible( false ); d->m_ui.resultLabel->setVisible( false ); setShowDirectionsButtonVisible( false ); updateActiveRoutingProfile(); updateCloudSyncButtons(); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { d->m_ui.directionsListView->setVisible( false ); d->m_openRouteButton->setVisible( false ); d->m_saveRouteButton->setVisible( false ); } connect( marbleWidget->model(), SIGNAL(themeChanged(QString)), this, SLOT(handlePlanetChange()) ); } RoutingWidget::~RoutingWidget() { delete d->m_playback; delete d->m_tour; if( d->m_document ){ d->m_widget->model()->treeModel()->removeDocument( d->m_document ); delete d->m_document; } delete d; } void RoutingWidget::retrieveRoute() { if ( d->m_inputWidgets.size() == 1 ) { // Search mode d->m_inputWidgets.first()->findPlacemarks(); return; } int index = d->m_ui.routingProfileComboBox->currentIndex(); if ( index == -1 ) { return; } d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); Q_ASSERT( d->m_routeRequest->size() == d->m_inputWidgets.size() ); for ( int i = 0; i < d->m_inputWidgets.size(); ++i ) { RoutingInputWidget *widget = d->m_inputWidgets.at( i ); if ( !widget->hasTargetPosition() && widget->hasInput() ) { widget->findPlacemarks(); return; } } d->m_activeInput = 0; if ( d->m_routeRequest->size() > 1 ) { d->m_zoomRouteAfterDownload = true; d->m_routingLayer->setPlacemarkModel( 0 ); d->m_routingManager->retrieveRoute(); d->m_ui.directionsListView->setModel( d->m_routingModel ); d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() ); } if( d->m_playback ) { d->m_playback->stop(); } } void RoutingWidget::activateItem ( const QModelIndex &index ) { QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { GeoDataCoordinates position = qvariant_cast( data ); d->m_widget->centerOn( position, true ); } if ( d->m_activeInput && index.isValid() ) { QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { d->m_activeInput->setTargetPosition( data.value(), index.data().toString() ); } } } void RoutingWidget::handleSearchResult( RoutingInputWidget *widget ) { d->setActiveInput( widget ); MarblePlacemarkModel *model = widget->searchResultModel(); if ( model->rowCount() ) { QString const results = tr( "placemarks found: %1" ).arg( model->rowCount() ); d->m_ui.resultLabel->setText( results ); d->m_ui.resultLabel->setVisible( true ); // Make sure we have a selection activatePlacemark( model->index( 0, 0 ) ); } else { QString const results = tr( "No placemark found" ); d->m_ui.resultLabel->setText(QLatin1String("") + results + QLatin1String("")); d->m_ui.resultLabel->setVisible( true ); } GeoDataLineString placemarks; for ( int i = 0; i < model->rowCount(); ++i ) { QVariant data = model->index( i, 0 ).data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { placemarks << data.value(); } } if ( placemarks.size() > 1 ) { d->m_widget->centerOn( GeoDataLatLonBox::fromLineString( placemarks ) ); //d->m_ui.descriptionLabel->setVisible( false ); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { d->m_ui.directionsListView->setVisible( true ); } } } void RoutingWidget::centerOnInputWidget( RoutingInputWidget *widget ) { if ( widget->hasTargetPosition() ) { d->m_widget->centerOn( widget->targetPosition() ); } } void RoutingWidget::activatePlacemark( const QModelIndex &index ) { if ( d->m_activeInput && index.isValid() ) { QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( !data.isNull() ) { d->m_activeInput->setTargetPosition( data.value() ); } } d->m_ui.directionsListView->setCurrentIndex( index ); } void RoutingWidget::addInputWidget() { d->m_routeRequest->append( GeoDataCoordinates() ); } void RoutingWidget::insertInputWidget( int index ) { if ( index >= 0 && index <= d->m_inputWidgets.size() ) { RoutingInputWidget *input = new RoutingInputWidget( d->m_widget->model(), index, this ); d->m_inputWidgets.insert( index, input ); connect( input, SIGNAL(searchFinished(RoutingInputWidget*)), this, SLOT(handleSearchResult(RoutingInputWidget*)) ); connect( input, SIGNAL(removalRequest(RoutingInputWidget*)), this, SLOT(removeInputWidget(RoutingInputWidget*)) ); connect( input, SIGNAL(activityRequest(RoutingInputWidget*)), this, SLOT(centerOnInputWidget(RoutingInputWidget*)) ); connect( input, SIGNAL(mapInputModeEnabled(RoutingInputWidget*,bool)), this, SLOT(requestMapPosition(RoutingInputWidget*,bool)) ); connect( input, SIGNAL(targetValidityChanged(bool)), this, SLOT(adjustSearchButton()) ); d->m_ui.inputLayout->insertWidget( index, input ); d->adjustInputWidgets(); } } void RoutingWidget::removeInputWidget( RoutingInputWidget *widget ) { int index = d->m_inputWidgets.indexOf( widget ); if ( index >= 0 ) { if ( d->m_inputWidgets.size() < 3 ) { widget->clear(); } else { d->m_routeRequest->remove( index ); } d->m_routingManager->retrieveRoute(); } } void RoutingWidget::removeInputWidget( int index ) { if ( index >= 0 && index < d->m_inputWidgets.size() ) { RoutingInputWidget *widget = d->m_inputWidgets.at( index ); d->m_inputWidgets.remove( index ); d->m_ui.inputLayout->removeWidget( widget ); widget->deleteLater(); if ( widget == d->m_activeInput ) { d->m_activeInput = 0; d->m_routingLayer->setPlacemarkModel( 0 ); d->m_ui.directionsListView->setModel( d->m_routingModel ); d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() ); } d->adjustInputWidgets(); } if ( d->m_inputWidgets.size() < 2 ) { addInputWidget(); } } void RoutingWidget::updateRouteState( RoutingManager::State state ) { clearTour(); switch ( state ) { case RoutingManager::Downloading: d->m_ui.routeComboBox->setVisible( false ); d->m_ui.routeComboBox->clear(); d->m_progressTimer.start(); d->m_ui.resultLabel->setVisible( false ); break; case RoutingManager::Retrieved: { d->m_progressTimer.stop(); d->m_ui.searchButton->setIcon( QIcon() ); if ( d->m_routingManager->routingModel()->rowCount() == 0 ) { const QString results = tr( "No route found" ); d->m_ui.resultLabel->setText(QLatin1String("") + results + QLatin1String("")); d->m_ui.resultLabel->setVisible( true ); } } break; } d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 ); } void RoutingWidget::requestMapPosition( RoutingInputWidget *widget, bool enabled ) { pointSelectionCanceled(); if ( enabled ) { d->m_inputRequest = widget; d->m_widget->installEventFilter( this ); d->m_widget->setFocus( Qt::OtherFocusReason ); } } void RoutingWidget::retrieveSelectedPoint( const GeoDataCoordinates &coordinates ) { if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) { d->m_inputRequest->setTargetPosition( coordinates ); d->m_widget->update(); } d->m_inputRequest = 0; d->m_widget->removeEventFilter( this ); } void RoutingWidget::adjustSearchButton() { d->adjustSearchButton(); } void RoutingWidget::pointSelectionCanceled() { if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) { d->m_inputRequest->abortMapInputRequest(); } d->m_inputRequest = 0; d->m_widget->removeEventFilter( this ); } void RoutingWidget::configureProfile() { int index = d->m_ui.routingProfileComboBox->currentIndex(); if ( index != -1 ) { RoutingProfileSettingsDialog dialog( d->m_widget->model()->pluginManager(), d->m_routingManager->profilesModel(), this ); dialog.editProfile( d->m_ui.routingProfileComboBox->currentIndex() ); d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); } } void RoutingWidget::updateProgress() { if ( !d->m_progressAnimation.isEmpty() ) { d->m_currentFrame = ( d->m_currentFrame + 1 ) % d->m_progressAnimation.size(); QIcon frame = d->m_progressAnimation[d->m_currentFrame]; d->m_ui.searchButton->setIcon( frame ); } } void RoutingWidget::updateAlternativeRoutes() { if ( d->m_ui.routeComboBox->count() == 1) { // Parts of the route may lie outside the route trip points GeoDataLatLonBox const bbox = d->m_routingManager->routingModel()->route().bounds(); if ( d->m_zoomRouteAfterDownload ) { d->m_zoomRouteAfterDownload = false; d->m_widget->centerOn( bbox ); } } d->m_ui.routeComboBox->setVisible( d->m_ui.routeComboBox->count() > 0 ); if ( d->m_ui.routeComboBox->currentIndex() < 0 && d->m_ui.routeComboBox->count() > 0 ) { d->m_ui.routeComboBox->setCurrentIndex( 0 ); } QString const results = tr( "routes found: %1" ).arg( d->m_ui.routeComboBox->count() ); d->m_ui.resultLabel->setText( results ); d->m_ui.resultLabel->setVisible( true ); d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 ); } void RoutingWidget::setShowDirectionsButtonVisible( bool visible ) { d->m_ui.showInstructionsButton->setVisible( visible ); } void RoutingWidget::setRouteSyncManager(RouteSyncManager *manager) { d->m_routeSyncManager = manager; connect( d->m_routeSyncManager, SIGNAL(routeSyncEnabledChanged(bool)), this, SLOT(updateCloudSyncButtons()) ); updateCloudSyncButtons(); } void RoutingWidget::openRoute() { QString const file = QFileDialog::getOpenFileName( this, tr( "Open Route" ), d->m_routingManager->lastOpenPath(), tr("KML Files (*.kml)") ); if ( !file.isEmpty() ) { d->m_routingManager->setLastOpenPath( QFileInfo( file ).absolutePath() ); d->m_zoomRouteAfterDownload = true; d->m_routingManager->loadRoute( file ); updateAlternativeRoutes(); } } void RoutingWidget::selectFirstProfile() { int count = d->m_routingManager->profilesModel()->rowCount(); if ( count && d->m_ui.routingProfileComboBox->currentIndex() < 0 ) { d->m_ui.routingProfileComboBox->setCurrentIndex( 0 ); } } void RoutingWidget::setRoutingProfile( int index ) { if ( index >= 0 && index < d->m_routingManager->profilesModel()->rowCount() ) { d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) ); } } void RoutingWidget::showDirections() { d->m_ui.directionsListView->setVisible( true ); } void RoutingWidget::saveRoute() { QString fileName = QFileDialog::getSaveFileName( this, tr( "Save Route" ), // krazy:exclude=qclasses d->m_routingManager->lastSavePath(), tr( "KML files (*.kml)" ) ); if ( !fileName.isEmpty() ) { // maemo 5 file dialog does not append the file extension if ( !fileName.endsWith(QLatin1String( ".kml" ), Qt::CaseInsensitive) ) { fileName += QLatin1String(".kml"); } d->m_routingManager->setLastSavePath( QFileInfo( fileName ).absolutePath() ); d->m_routingManager->saveRoute( fileName ); } } void RoutingWidget::uploadToCloud() { Q_ASSERT( d->m_routeSyncManager ); if (!d->m_routeUploadDialog) { d->m_routeUploadDialog = new QProgressDialog( d->m_widget ); d->m_routeUploadDialog->setWindowTitle( tr( "Uploading route..." ) ); d->m_routeUploadDialog->setMinimum( 0 ); d->m_routeUploadDialog->setMaximum( 100 ); d->m_routeUploadDialog->setAutoClose( true ); d->m_routeUploadDialog->setAutoReset( true ); connect( d->m_routeSyncManager, SIGNAL(routeUploadProgress(qint64,qint64)), this, SLOT(updateUploadProgress(qint64,qint64)) ); } d->m_routeUploadDialog->show(); d->m_routeSyncManager->uploadRoute(); } void RoutingWidget::openCloudRoutesDialog() { Q_ASSERT( d->m_routeSyncManager ); d->m_routeSyncManager->prepareRouteList(); QPointer dialog = new CloudRoutesDialog( d->m_routeSyncManager->model(), d->m_widget ); connect( d->m_routeSyncManager, SIGNAL(routeListDownloadProgress(qint64,qint64)), dialog, SLOT(updateListDownloadProgressbar(qint64,qint64)) ); connect( dialog, SIGNAL(downloadButtonClicked(QString)), d->m_routeSyncManager, SLOT(downloadRoute(QString)) ); connect( dialog, SIGNAL(openButtonClicked(QString)), this, SLOT(openCloudRoute(QString)) ); connect( dialog, SIGNAL(deleteButtonClicked(QString)), d->m_routeSyncManager, SLOT(deleteRoute(QString)) ); connect( dialog, SIGNAL(removeFromCacheButtonClicked(QString)), d->m_routeSyncManager, SLOT(removeRouteFromCache(QString)) ); connect( dialog, SIGNAL(uploadToCloudButtonClicked(QString)), d->m_routeSyncManager, SLOT(uploadRoute(QString)) ); dialog->exec(); delete dialog; } void RoutingWidget::updateActiveRoutingProfile() { RoutingProfile const profile = d->m_routingManager->routeRequest()->routingProfile(); QList const profiles = d->m_routingManager->profilesModel()->profiles(); d->m_ui.routingProfileComboBox->setCurrentIndex( profiles.indexOf( profile ) ); } void RoutingWidget::updateCloudSyncButtons() { bool const show = d->m_routeSyncManager && d->m_routeSyncManager->isRouteSyncEnabled(); d->m_cloudSyncSeparator->setVisible( show ); d->m_uploadToCloudAction->setVisible( show ); d->m_openCloudRoutesAction->setVisible( show ); } void RoutingWidget::openCloudRoute(const QString &identifier) { Q_ASSERT( d->m_routeSyncManager ); d->m_routeSyncManager->openRoute( identifier ); d->m_widget->centerOn( d->m_routingManager->routingModel()->route().bounds() ); } void RoutingWidget::updateUploadProgress(qint64 sent, qint64 total) { Q_ASSERT( d->m_routeUploadDialog ); d->m_routeUploadDialog->setValue( 100.0 * sent / total ); } bool RoutingWidget::eventFilter( QObject *o, QEvent *event ) { if ( o != d->m_widget ) { return QWidget::eventFilter( o, event ); } Q_ASSERT( d->m_inputRequest != 0 ); Q_ASSERT( d->m_inputWidgets.contains( d->m_inputRequest ) ); if ( event->type() == QEvent::MouseButtonPress ) { QMouseEvent *e = static_cast( event ); return e->button() == Qt::LeftButton; } if ( event->type() == QEvent::MouseButtonRelease ) { QMouseEvent *e = static_cast( event ); qreal lon( 0.0 ), lat( 0.0 ); if ( e->button() == Qt::LeftButton && d->m_widget->geoCoordinates( e->pos().x(), e->pos().y(), lon, lat, GeoDataCoordinates::Radian ) ) { retrieveSelectedPoint( GeoDataCoordinates( lon, lat ) ); return true; } else { return QWidget::eventFilter( o, event ); } } if ( event->type() == QEvent::MouseMove ) { d->m_widget->setCursor( Qt::CrossCursor ); return true; } if ( event->type() == QEvent::KeyPress ) { QKeyEvent *e = static_cast( event ); if ( e->key() == Qt::Key_Escape ) { pointSelectionCanceled(); return true; } return QWidget::eventFilter( o, event ); } return QWidget::eventFilter( o, event ); } void RoutingWidget::resizeEvent(QResizeEvent *e) { QWidget::resizeEvent(e); } void RoutingWidget::toggleRoutePlay() { if( !d->m_playback ){ if( d->m_routingModel->rowCount() != 0 ){ initializeTour(); } } if (!d->m_playback) return; if( !d->m_playing ){ d->m_playing = true; d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png"))); if( d->m_playback ){ d->m_playback->play(); } } else { d->m_playing = false; d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); d->m_playback->pause(); } } void RoutingWidget::initializeTour() { d->m_tour = new GeoDataTour; if( d->m_document ){ d->m_widget->model()->treeModel()->removeDocument( d->m_document ); delete d->m_document; } d->m_document = new GeoDataDocument; d->m_document->setId(QStringLiteral("tourdoc")); d->m_document->append( d->m_tour ); d->m_tour->setPlaylist( new GeoDataPlaylist ); Route const route = d->m_widget->model()->routingManager()->routingModel()->route(); GeoDataLineString path = route.path(); if ( path.size() < 1 ){ return; } QList waypoints; double totalDistance = 0.0; for( int i=0; i const allWaypoints = waypoints; totalDistance = 0.0; GeoDataCoordinates last = path.at( 0 ); int j=0; // next waypoint qreal planetRadius = d->m_widget->model()->planet()->radius(); for( int i=1; i= allWaypoints[j].distance && j+1 1 && distance < step ){ continue; } last = coordinates; GeoDataLookAt* lookat = new GeoDataLookAt; // Choose a zoom distance of 400, 600 or 800 meters based on the distance to the closest waypoint double const range = waypointDistance < 400 ? 400 : ( waypointDistance < 2000 ? 600 : 800 ); coordinates.setAltitude( range ); lookat->setCoordinates( coordinates ); lookat->setRange( range ); GeoDataFlyTo* flyto = new GeoDataFlyTo; double const duration = 0.75; flyto->setDuration( duration ); flyto->setView( lookat ); flyto->setFlyToMode( GeoDataFlyTo::Smooth ); d->m_tour->playlist()->addPrimitive( flyto ); if( !waypoints.empty() && totalDistance > waypoints.first().distance-100 ){ WaypointInfo const waypoint = waypoints.first(); waypoints.pop_front(); GeoDataAnimatedUpdate *updateCreate = new GeoDataAnimatedUpdate; updateCreate->setUpdate( new GeoDataUpdate ); updateCreate->update()->setCreate( new GeoDataCreate ); GeoDataPlacemark *placemarkCreate = new GeoDataPlacemark; QString const waypointId = QString( "waypoint-%1" ).arg( i, 0, 10 ); placemarkCreate->setId( waypointId ); placemarkCreate->setTargetId( d->m_document->id() ); placemarkCreate->setCoordinate( waypoint.coordinates ); GeoDataStyle::Ptr style(new GeoDataStyle); style->iconStyle().setIconPath( waypoint.maneuver.directionPixmap() ); placemarkCreate->setStyle( style ); updateCreate->update()->create()->append( placemarkCreate ); d->m_tour->playlist()->addPrimitive( updateCreate ); GeoDataAnimatedUpdate *updateDelete = new GeoDataAnimatedUpdate; updateDelete->setDelayedStart( 2 ); updateDelete->setUpdate( new GeoDataUpdate ); updateDelete->update()->setDelete( new GeoDataDelete ); GeoDataPlacemark *placemarkDelete = new GeoDataPlacemark; placemarkDelete->setTargetId( waypointId ); updateDelete->update()->getDelete()->append( placemarkDelete ); d->m_tour->playlist()->addPrimitive( updateDelete ); } } d->m_playback = new TourPlayback; d->m_playback->setMarbleWidget( d->m_widget ); d->m_playback->setTour( d->m_tour ); d->m_widget->model()->treeModel()->addDocument( d->m_document ); QObject::connect( d->m_playback, SIGNAL(finished()), this, SLOT(seekTourToStart()) ); } void RoutingWidget::centerOn( const GeoDataCoordinates &coordinates ) { if ( d->m_widget ) { GeoDataLookAt lookat; lookat.setCoordinates( coordinates ); lookat.setRange( coordinates.altitude() ); d->m_widget->flyTo( lookat, Instant ); } } void RoutingWidget::clearTour() { d->m_playing = false; d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); delete d->m_playback; d->m_playback = 0; if( d->m_document ){ d->m_widget->model()->treeModel()->removeDocument( d->m_document ); delete d->m_document; d->m_document = 0; d->m_tour = 0; } } void RoutingWidget::seekTourToStart() { Q_ASSERT( d->m_playback ); d->m_playback->stop(); d->m_playback->seek( 0 ); d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png"))); d->m_playing = false; } void RoutingWidget::handlePlanetChange() { const QString newPlanetId = d->m_widget->model()->planetId(); if (newPlanetId == d->m_planetId) { return; } d->m_planetId = newPlanetId; d->m_routingManager->clearRoute(); } } // namespace Marble #include "moc_RoutingWidget.cpp" diff --git a/src/plugins/render/annotate/MergingPolygonNodesAnimation.cpp b/src/plugins/render/annotate/MergingPolygonNodesAnimation.cpp index 50fc3d001..6c8bf5f66 100644 --- a/src/plugins/render/annotate/MergingPolygonNodesAnimation.cpp +++ b/src/plugins/render/annotate/MergingPolygonNodesAnimation.cpp @@ -1,109 +1,109 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2014 Calin Cruceru // // Self #include "MergingPolygonNodesAnimation.h" // Marble #include "AreaAnnotation.h" #include "GeoDataCoordinates.h" #include "GeoDataPolygon.h" #include "GeoDataLinearRing.h" #include "GeoDataPlacemark.h" #include "MarbleMath.h" namespace Marble { MergingPolygonNodesAnimation::MergingPolygonNodesAnimation( AreaAnnotation *polygon ) : // To avoid long lines and repeated code first_i( polygon->m_firstMergedNode.first ), first_j( polygon->m_firstMergedNode.second ), second_i( polygon->m_secondMergedNode.first ), second_j( polygon->m_secondMergedNode.second ), m_timer( new QTimer( this ) ), // To avoid repeating this code section too often outerRing( static_cast( polygon->placemark()->geometry() )->outerBoundary() ), innerRings( static_cast( polygon->placemark()->geometry() )->innerBoundaries() ) { if ( first_j == -1 ) { Q_ASSERT( second_j == -1 ); m_boundary = OuterBoundary; m_firstInitialCoords = outerRing.at( first_i ); m_secondInitialCoords = outerRing.at( second_i ); } else { Q_ASSERT( first_j != -1 && second_j != -1 ); m_firstInitialCoords = innerRings.at(first_i).at(first_j); m_secondInitialCoords = innerRings.at(second_i).at(second_j); m_boundary = InnerBoundary; } connect( m_timer, SIGNAL(timeout()), this, SLOT(updateNodes()) ); } MergingPolygonNodesAnimation::~MergingPolygonNodesAnimation() { delete m_timer; } void MergingPolygonNodesAnimation::startAnimation() { static const int timeOffset = 1; m_timer->start( timeOffset ); } void MergingPolygonNodesAnimation::updateNodes() { static const qreal ratio = 0.05; - const qreal distanceOffset = distanceSphere( m_firstInitialCoords.interpolate( m_secondInitialCoords, ratio ), - m_firstInitialCoords ) + 0.001; + const qreal distanceOffset = m_firstInitialCoords.interpolate(m_secondInitialCoords, ratio) + .sphericalDistanceTo(m_firstInitialCoords) + 0.001; if ( nodesDistance() < distanceOffset ) { if ( m_boundary == OuterBoundary ) { outerRing[second_i] = newCoords(); outerRing.remove( first_i ); } else { innerRings[second_i][second_j] = newCoords(); innerRings[second_i].remove( first_j ); } emit animationFinished(); } else { if ( m_boundary == OuterBoundary ) { outerRing[first_i] = outerRing.at(first_i).interpolate( m_secondInitialCoords, ratio ); outerRing[second_i] = outerRing.at(second_i).interpolate( m_firstInitialCoords, ratio ); } else { innerRings[first_i][first_j] = innerRings.at(first_i).at(first_j).interpolate( m_secondInitialCoords, ratio ); innerRings[second_i][second_j] = innerRings.at(second_i).at(second_j).interpolate( m_firstInitialCoords, ratio ); } emit nodesMoved(); } } GeoDataCoordinates MergingPolygonNodesAnimation::newCoords() { return m_boundary == OuterBoundary ? outerRing.at(first_i).interpolate( outerRing.at(second_i), 0.5 ) : innerRings.at(first_i).at(first_j).interpolate( innerRings.at(second_i).at(second_j), 0.5 ); } qreal MergingPolygonNodesAnimation::nodesDistance() { return m_boundary == OuterBoundary ? - distanceSphere( outerRing.at(first_i), outerRing.at(second_i) ) : - distanceSphere( innerRings.at(first_i).at(first_j), innerRings.at(second_i).at(second_j) ); + outerRing.at(first_i).sphericalDistanceTo(outerRing.at(second_i)) : + innerRings.at(first_i).at(first_j).sphericalDistanceTo(innerRings.at(second_i).at(second_j)); } } // namespace Marble #include "moc_MergingPolygonNodesAnimation.cpp" diff --git a/src/plugins/render/annotate/MergingPolylineNodesAnimation.cpp b/src/plugins/render/annotate/MergingPolylineNodesAnimation.cpp index 093aa8797..2076e2c9c 100644 --- a/src/plugins/render/annotate/MergingPolylineNodesAnimation.cpp +++ b/src/plugins/render/annotate/MergingPolylineNodesAnimation.cpp @@ -1,85 +1,85 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2014 Calin Cruceru // // Self #include "MergingPolylineNodesAnimation.h" // Marble #include "PolylineAnnotation.h" #include "GeoDataCoordinates.h" #include "GeoDataPlacemark.h" #include "GeoDataLineString.h" #include "MarbleMath.h" namespace Marble { MergingPolylineNodesAnimation::MergingPolylineNodesAnimation( PolylineAnnotation *polyline ) : m_timer( new QTimer( this ) ), // To avoid long lines and repeated code m_firstNodeIndex( polyline->m_firstMergedNode ), m_secondNodeIndex( polyline->m_secondMergedNode ), m_lineString( static_cast( polyline->placemark()->geometry() ) ), m_firstInitialCoords( m_lineString->at( polyline->m_firstMergedNode ) ), m_secondInitialCoords( m_lineString->at( polyline->m_secondMergedNode ) ) { connect( m_timer, SIGNAL(timeout()), this, SLOT(updateNodes()) ); } MergingPolylineNodesAnimation::~MergingPolylineNodesAnimation() { delete m_timer; } void MergingPolylineNodesAnimation::startAnimation() { static const int timeOffset = 1; m_timer->start( timeOffset ); } void MergingPolylineNodesAnimation::updateNodes() { static const qreal ratio = 0.05; - const qreal distanceOffset = distanceSphere( m_firstInitialCoords.interpolate( m_secondInitialCoords, ratio ), - m_firstInitialCoords ) + 0.001; + const qreal distanceOffset = m_firstInitialCoords.interpolate(m_secondInitialCoords, ratio) + .sphericalDistanceTo(m_firstInitialCoords) + 0.001; if ( nodesDistance() < distanceOffset ) { m_lineString->at(m_secondNodeIndex) = newCoords(); m_lineString->remove( m_firstNodeIndex ); emit animationFinished(); } else { m_lineString->at(m_firstNodeIndex) = m_lineString->at(m_firstNodeIndex).interpolate( m_secondInitialCoords, ratio ); m_lineString->at(m_secondNodeIndex) = m_lineString->at(m_secondNodeIndex).interpolate( m_firstInitialCoords, ratio ); emit nodesMoved(); } } GeoDataCoordinates MergingPolylineNodesAnimation::newCoords() { return m_lineString->at(m_firstNodeIndex).interpolate( m_lineString->at(m_secondNodeIndex), 0.5 ); } qreal MergingPolylineNodesAnimation::nodesDistance() { - return distanceSphere( m_lineString->at(m_firstNodeIndex), m_lineString->at(m_secondNodeIndex) ); + return m_lineString->at(m_firstNodeIndex).sphericalDistanceTo(m_lineString->at(m_secondNodeIndex)); } } // namespace Marble #include "moc_MergingPolylineNodesAnimation.cpp" diff --git a/src/plugins/render/elevationprofilefloatitem/ElevationProfileDataSource.cpp b/src/plugins/render/elevationprofilefloatitem/ElevationProfileDataSource.cpp index 390e6d9fc..acb2aee87 100644 --- a/src/plugins/render/elevationprofilefloatitem/ElevationProfileDataSource.cpp +++ b/src/plugins/render/elevationprofilefloatitem/ElevationProfileDataSource.cpp @@ -1,251 +1,251 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011-2012 Florian Eßer // Copyright 2012 Bernhard Beschow // Copyright 2013 Roman Karlstetter // #include "ElevationProfileDataSource.h" #include "ElevationModel.h" #include "GeoDataDocument.h" #include "GeoDataLineString.h" #include "GeoDataObject.h" #include "GeoDataMultiGeometry.h" #include "GeoDataPlacemark.h" #include "GeoDataTrack.h" #include "GeoDataTreeModel.h" #include "MarbleDebug.h" #include "MarbleMath.h" #include "MarbleModel.h" #include "routing/Route.h" #include "routing/RoutingModel.h" #include namespace Marble { ElevationProfileDataSource::ElevationProfileDataSource( QObject *parent ) : QObject( parent ) { // nothing to do } QVector ElevationProfileDataSource::calculateElevationData(const GeoDataLineString &lineString) const { // TODO: Don't re-calculate the whole route if only a small part of it was changed QVector result; qreal distance = 0; //GeoDataLineString path; for ( int i = 0; i < lineString.size(); i++ ) { const qreal ele = getElevation( lineString[i] ); if ( i ) { - distance += EARTH_RADIUS * distanceSphere( lineString[i-1], lineString[i] ); + distance += EARTH_RADIUS * lineString[i-1].sphericalDistanceTo(lineString[i]); } if ( ele != invalidElevationData ) { // skip no data result.append( QPointF( distance, ele ) ); } } return result; } // end of impl of ElevationProfileDataSource ElevationProfileTrackDataSource::ElevationProfileTrackDataSource( const GeoDataTreeModel *treeModel, QObject *parent ) : ElevationProfileDataSource( parent ), m_currentSourceIndex( -1 ) { if ( treeModel ) { connect( treeModel, SIGNAL(added(GeoDataObject*)), SLOT(handleObjectAdded(GeoDataObject*)) ); connect( treeModel, SIGNAL(removed(GeoDataObject*)), SLOT(handleObjectRemoved(GeoDataObject*)) ); } } QStringList ElevationProfileTrackDataSource::sourceDescriptions() const { return m_trackChooserList; } void ElevationProfileTrackDataSource::setSourceIndex(int index) { if (m_currentSourceIndex != index) { m_currentSourceIndex = index; requestUpdate(); } } int ElevationProfileTrackDataSource::currentSourceIndex() const { return m_currentSourceIndex; } void ElevationProfileTrackDataSource::requestUpdate() { if ( m_currentSourceIndex < 0 ) { return; } if ( m_currentSourceIndex >= m_trackList.size() ) { return; } const GeoDataLineString *routePoints = m_trackList[m_currentSourceIndex]->lineString(); emit dataUpdated(*routePoints, calculateElevationData(*routePoints)); } bool ElevationProfileTrackDataSource::isDataAvailable() const { return !m_trackHash.isEmpty(); } qreal ElevationProfileTrackDataSource::getElevation(const GeoDataCoordinates &coordinates) const { return coordinates.altitude(); } void ElevationProfileTrackDataSource::handleObjectAdded(GeoDataObject *object) { const GeoDataDocument *document = dynamic_cast(object); if (!document) { return;// don't know what to do if not a document } QList trackList; for (int i = 0; isize(); ++i) { const GeoDataFeature *feature = document->child(i); const GeoDataPlacemark *placemark = dynamic_cast(feature); if (!placemark) { continue; } const GeoDataMultiGeometry *multiGeometry = dynamic_cast(placemark->geometry()); if (!multiGeometry) { continue; } for (int i = 0; isize(); i++) { const GeoDataTrack *track = dynamic_cast(multiGeometry->child(i)); if (track && track->size() > 1) { mDebug() << "new GeoDataTrack for ElevationProfile detected"; trackList.append(track); } } } if (trackList.isEmpty()) { return; } // update internal datastructures m_trackHash.insert(document->fileName(), trackList); const GeoDataTrack *selectedTrack = 0; if ( 0 <= m_currentSourceIndex && m_currentSourceIndex < m_trackList.size() ) { selectedTrack = m_trackList[m_currentSourceIndex]; } m_trackChooserList.clear(); m_trackList.clear(); QHashIterator > i(m_trackHash); while (i.hasNext()) { i.next(); mDebug() << i.key() << ": " << i.value() << endl; QFileInfo info(i.key()); QString filename = info.fileName(); QList list = i.value(); for (int i = 0; i(object); if (!topLevelDoc) { return;// don't know what to do if not a document } const QString key = topLevelDoc->fileName(); if ( !m_trackHash.contains( key ) ) { return; } const QList list = m_trackHash.value(key); const GeoDataTrack *const selectedTrack = m_currentSourceIndex == -1 ? 0 : m_trackList[m_currentSourceIndex]; for (int i = 0; iroute().path(); const QVector elevationData = calculateElevationData(routePoints); emit dataUpdated( routePoints, elevationData ); } bool ElevationProfileRouteDataSource::isDataAvailable() const { return m_routingModel && m_routingModel->rowCount() > 0; } qreal ElevationProfileRouteDataSource::getElevation(const GeoDataCoordinates &coordinates) const { const qreal lat = coordinates.latitude ( GeoDataCoordinates::Degree ); const qreal lon = coordinates.longitude( GeoDataCoordinates::Degree ); qreal ele = m_elevationModel->height( lon, lat ); return ele; } // end of impl of ElevationProfileRouteDataSource } #include "moc_ElevationProfileDataSource.cpp" diff --git a/src/plugins/render/elevationprofilefloatitem/ElevationProfilePlotAxis.cpp b/src/plugins/render/elevationprofilefloatitem/ElevationProfilePlotAxis.cpp index a5714a70b..48a40eab5 100644 --- a/src/plugins/render/elevationprofilefloatitem/ElevationProfilePlotAxis.cpp +++ b/src/plugins/render/elevationprofilefloatitem/ElevationProfilePlotAxis.cpp @@ -1,168 +1,169 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011-2012 Florian Eßer // #include "ElevationProfilePlotAxis.h" +#include "MarbleGlobal.h" #include "MarbleLocale.h" #include "MarbleMath.h" #include namespace Marble { ElevationProfilePlotAxis::ElevationProfilePlotAxis() : m_minValue( 0.0 ), m_maxValue ( 0.0 ), m_displayScale( 1.0 ), m_pixelLength ( 0 ), m_minTickCount( 2 ), m_maxTickCount( 5 ), m_unitString( QString() ) { // nothing to do... } void ElevationProfilePlotAxis::setRange(qreal minValue, qreal maxValue) { m_minValue = minValue; m_maxValue = maxValue; update(); } void ElevationProfilePlotAxis::setLength(int length) { m_pixelLength = length; update(); } void ElevationProfilePlotAxis::setTickCount( const int min, const int max ) { m_minTickCount = min; m_maxTickCount = max; } void ElevationProfilePlotAxis::update() { updateTicks(); updateScale(); } qreal ElevationProfilePlotAxis::minValue() const { return m_minValue; } qreal ElevationProfilePlotAxis::maxValue() const { return m_maxValue; } qreal ElevationProfilePlotAxis::range() const { return m_maxValue - m_minValue; } qreal ElevationProfilePlotAxis::scale() const { return m_displayScale; } QString ElevationProfilePlotAxis::unit() const { return m_unitString; } AxisTickList ElevationProfilePlotAxis::ticks() const { return m_ticks; } void ElevationProfilePlotAxis::updateTicks() { m_ticks.clear(); if( range() == 0 ) { return; } static QVector niceIntervals = QVector() << 10 << 20 << 25 << 30 << 50; const int exponent = qRound( log10( range() ) ); const qreal factor = qPow( 10, 2 - exponent ); const qreal tickRange = range() * factor; qreal stepWidth = niceIntervals.last(); qreal error = tickRange; for ( const int i: niceIntervals ) { const qreal numTicks = tickRange / i; if ( numTicks < m_minTickCount || numTicks > m_maxTickCount ) { continue; } const qreal newError = qAbs( numTicks - qRound( numTicks ) ); if ( newError < error ) { error = newError; stepWidth = i; } } stepWidth /= factor; qreal offset = 0; if ( fmod( m_minValue, stepWidth ) != 0 ) { offset = stepWidth - fmod( m_minValue, stepWidth ); } qreal val = m_minValue + offset; int pos = m_pixelLength / range() * offset; m_ticks << AxisTick( pos, val ); while( val < m_maxValue ) { val += stepWidth; pos += m_pixelLength / range() * stepWidth; if ( pos > m_pixelLength ) { break; } m_ticks << AxisTick( pos, val ); } } void ElevationProfilePlotAxis::updateScale() { MarbleLocale::MeasurementSystem measurementSystem; measurementSystem = MarbleGlobal::getInstance()->locale()->measurementSystem(); switch ( measurementSystem ) { case MarbleLocale::MetricSystem: if ( range() >= 10 * KM2METER ) { m_unitString = tr( "km" ); m_displayScale = METER2KM; } else { m_unitString = tr( "m" ); m_displayScale = 1.0; } break; case MarbleLocale::ImperialSystem: // FIXME: Do these values make sense? if ( range() >= 10 * KM2METER * MI2KM ) { m_unitString = tr( "mi" ); m_displayScale = METER2KM * KM2MI; } else { m_unitString = tr( "ft" ); m_displayScale = M2FT; } break; case MarbleLocale::NauticalSystem: m_unitString = tr("nm"); m_displayScale = METER2KM * KM2NM; break; } } } #include "moc_ElevationProfilePlotAxis.cpp" diff --git a/src/plugins/render/routing/RoutingPlugin.cpp b/src/plugins/render/routing/RoutingPlugin.cpp index fdf2fb62a..a16a42912 100644 --- a/src/plugins/render/routing/RoutingPlugin.cpp +++ b/src/plugins/render/routing/RoutingPlugin.cpp @@ -1,591 +1,591 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2010 Siddharth Srivastava // Copyright 2010 Dennis Nienhüser // #include "RoutingPlugin.h" #include "ui_RoutingPlugin.h" #include "ui_RoutingConfigDialog.h" #include "Planet.h" #include "AudioOutput.h" #include "GeoDataCoordinates.h" #include "GeoDataLookAt.h" #include "GeoPainter.h" #include "MarbleGraphicsGridLayout.h" #include "MarbleModel.h" #include "MarbleWidget.h" #include "MarbleMath.h" #include "MarbleLocale.h" #include "MarbleDirs.h" #include "PluginManager.h" #include "PositionTracking.h" #include "PositionProviderPlugin.h" #include "routing/Route.h" #include "routing/RoutingManager.h" #include "routing/RoutingModel.h" #include "routing/RouteRequest.h" #include "routing/SpeakersModel.h" #include "ViewportParams.h" #include "WidgetGraphicsItem.h" #include #include namespace Marble { namespace { int const thresholdDistance = 1000; // in meter } class RoutingPluginPrivate { public: MarbleWidget* m_marbleWidget; WidgetGraphicsItem* m_widgetItem; RoutingModel* m_routingModel; Ui::RoutingPlugin m_widget; bool m_nearNextInstruction; bool m_guidanceModeEnabled; AudioOutput* m_audio; QDialog *m_configDialog; Ui::RoutingConfigDialog m_configUi; bool m_routeCompleted; SpeakersModel* m_speakersModel; RoutingPluginPrivate( RoutingPlugin* parent ); void updateZoomButtons( int zoomValue ); void updateZoomButtons(); void updateGuidanceModeButton(); void forceRepaint(); void updateButtonVisibility(); void reverseRoute(); void toggleGuidanceMode( bool enabled ); void updateDestinationInformation(); void updateGpsButton( PositionProviderPlugin *activePlugin ); void togglePositionTracking( bool enabled ); static QString richText( const QString &source ); static QString fuzzyDistance( qreal distanceMeter ); void readSettings(); qreal nextInstructionDistance() const; qreal remainingDistance() const; private: RoutingPlugin* m_parent; }; RoutingPluginPrivate::RoutingPluginPrivate( RoutingPlugin *parent ) : m_marbleWidget( 0 ), m_widgetItem( 0 ), m_routingModel( 0 ), m_nearNextInstruction( false ), m_guidanceModeEnabled( false ), m_audio( new AudioOutput( parent ) ), m_configDialog( 0 ), m_routeCompleted( false ), m_speakersModel( 0 ), m_parent( parent ) { m_audio->setMuted( false ); m_audio->setSoundEnabled( true ); } QString RoutingPluginPrivate::richText( const QString &source ) { return QLatin1String("") + source + QLatin1String(""); } QString RoutingPluginPrivate::fuzzyDistance( qreal length ) { int precision = 0; QString distanceUnit = QLatin1String( "m" ); if ( MarbleGlobal::getInstance()->locale()->measurementSystem() != MarbleLocale::MetricSystem ) { precision = 1; distanceUnit = "mi"; length *= METER2KM; length *= KM2MI; } else if (MarbleGlobal::getInstance()->locale()->measurementSystem() == MarbleLocale::MetricSystem) { if ( length >= 1000 ) { length /= 1000; distanceUnit = "km"; precision = 1; } else if ( length >= 200 ) { length = 50 * qRound( length / 50 ); } else if ( length >= 100 ) { length = 25 * qRound( length / 25 ); } else { length = 10 * qRound( length / 10 ); } } else if (MarbleGlobal::getInstance()->locale()->measurementSystem() == MarbleLocale::NauticalSystem) { precision = 2; distanceUnit = "nm"; length *= METER2KM; length *= KM2NM; } return QString( "%1 %2" ).arg( length, 0, 'f', precision ).arg( distanceUnit ); } void RoutingPluginPrivate::updateZoomButtons( int zoomValue ) { int const minZoom = m_marbleWidget ? m_marbleWidget->minimumZoom() : 900; int const maxZoom = m_marbleWidget ? m_marbleWidget->maximumZoom() : 2400; bool const zoomInEnabled = zoomValue < maxZoom; bool const zoomOutEnabled = zoomValue > minZoom; if ( ( zoomInEnabled != m_widget.zoomInButton->isEnabled() ) || ( zoomOutEnabled != m_widget.zoomOutButton->isEnabled() ) ) { m_widget.zoomInButton->setEnabled( zoomInEnabled ); m_widget.zoomOutButton->setEnabled( zoomOutEnabled ); forceRepaint(); } } void RoutingPluginPrivate::updateGuidanceModeButton() { bool const hasRoute = m_routingModel->rowCount() > 0; m_widget.routingButton->setEnabled( hasRoute ); forceRepaint(); } void RoutingPluginPrivate::forceRepaint() { m_parent->update(); emit m_parent->repaintNeeded(); } void RoutingPluginPrivate::updateButtonVisibility() { bool const show = m_guidanceModeEnabled; bool const near = show && m_nearNextInstruction; m_widget.progressBar->setVisible( near ); m_widget.instructionIconLabel->setVisible( show ); m_widget.spacer->changeSize( show ? 10 : 0, 20 ); m_widget.instructionLabel->setVisible( show ); // m_widget.followingInstructionIconLabel->setVisible( show ); // Disabling the next instruction turn icon for now, it seems to confuse first time users. m_widget.followingInstructionIconLabel->setVisible( false ); m_widget.destinationDistanceLabel->setVisible( show ); m_widget.gpsButton->setVisible( !show ); m_widget.zoomOutButton->setVisible( !show ); m_widget.zoomInButton->setVisible( !show ); m_widgetItem->widget()->updateGeometry(); QSize const size = m_widgetItem->widget()->sizeHint(); m_widgetItem->widget()->resize( size ); m_widgetItem->setContentSize( size ); bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( smallScreen ) { qreal const pluginWidth = size.width(); int x = -10; if ( m_guidanceModeEnabled ) { int const parentWidth = m_marbleWidget->width(); x = qRound( ( parentWidth - pluginWidth ) / 2.0 ); } m_parent->setPosition( QPointF( x, m_parent->position().y() ) ); } } void RoutingPluginPrivate::updateZoomButtons() { if ( m_marbleWidget ) { updateZoomButtons( m_marbleWidget->zoom() ); } } void RoutingPluginPrivate::toggleGuidanceMode( bool enabled ) { if( !m_marbleWidget || m_guidanceModeEnabled == enabled ) { return; } m_guidanceModeEnabled = enabled; updateButtonVisibility(); if( enabled ) { QObject::connect( m_routingModel, SIGNAL(positionChanged()), m_parent, SLOT(updateDestinationInformation()) ); } else { QObject::disconnect( m_routingModel, SIGNAL(positionChanged()), m_parent, SLOT(updateDestinationInformation()) ); } if ( enabled ) { QString const text = QObject::tr( "Starting guidance mode, please wait..." ); m_widget.instructionLabel->setText( richText( "%1" ).arg( text ) ); } if ( enabled ) { RouteRequest* request = m_marbleWidget->model()->routingManager()->routeRequest(); if ( request && request->size() > 0 ) { GeoDataCoordinates source = request->source(); if ( source.isValid() ) { GeoDataLookAt view; view.setCoordinates( source ); // By happy coincidence this equals OpenStreetMap tile level 15 view.setRange( 851.807 ); m_marbleWidget->flyTo( view ); } } } m_marbleWidget->model()->routingManager()->setGuidanceModeEnabled( enabled ); if ( enabled ) { m_routeCompleted = false; } forceRepaint(); } void RoutingPluginPrivate::updateDestinationInformation() { if ( m_routingModel->route().currentSegment().isValid() ) { qreal remaining = remainingDistance(); qreal distanceLeft = nextInstructionDistance(); m_audio->update( m_routingModel->route(), distanceLeft, remaining, m_routingModel->deviatedFromRoute() ); m_nearNextInstruction = distanceLeft < thresholdDistance; QString pixmapHtml = "
"; m_widget.destinationDistanceLabel->setText( pixmapHtml + richText( fuzzyDistance( remaining ) ) ); m_widget.instructionIconLabel->setEnabled( m_nearNextInstruction ); m_widget.progressBar->setMaximum( thresholdDistance ); m_widget.progressBar->setValue( qRound( distanceLeft ) ); updateButtonVisibility(); QString pixmap = MarbleDirs::path(QStringLiteral("bitmaps/routing_step.png")); pixmapHtml = QString( "" ).arg( pixmap ); qreal planetRadius = m_marbleWidget->model()->planet()->radius(); GeoDataCoordinates const onRoute = m_routingModel->route().positionOnRoute(); GeoDataCoordinates const ego = m_routingModel->route().position(); - qreal const distanceToRoute = planetRadius * distanceSphere( ego, onRoute ); + qreal const distanceToRoute = planetRadius * ego.sphericalDistanceTo(onRoute); if ( !m_routingModel->route().currentSegment().isValid() ) { m_widget.instructionLabel->setText( richText( QObject::tr( "Calculate a route to get directions." ) ) ); m_widget.instructionIconLabel->setText( pixmapHtml ); } else if ( distanceToRoute > 300.0 ) { m_widget.instructionLabel->setText( richText( QObject::tr( "Route left." ) ) ); m_widget.instructionIconLabel->setText( pixmapHtml ); } else if ( !m_routingModel->route().currentSegment().nextRouteSegment().isValid() ) { m_widget.instructionLabel->setText( richText( QObject::tr( "Destination ahead." ) ) ); m_widget.instructionIconLabel->setText( pixmapHtml ); } else { pixmap = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().directionPixmap(); QString const instructionText = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().instructionText(); m_widget.instructionLabel->setText( richText( "%1" ).arg( instructionText ) ); pixmapHtml = QString( "


%2

" ).arg( pixmap ); m_widget.instructionIconLabel->setText( pixmapHtml.arg( richText( fuzzyDistance( distanceLeft ) ) ) ); if( remaining > 50 ) { m_routeCompleted = false; } else { if ( !m_routeCompleted ) { QString content = QObject::tr( "Arrived at destination. Calculate the way back." ); m_widget.instructionLabel->setText( richText( "%1" ).arg( content ) ); } m_routeCompleted = true; } } forceRepaint(); } } void RoutingPluginPrivate::updateGpsButton( PositionProviderPlugin *activePlugin ) { m_widget.gpsButton->setChecked( activePlugin != 0 ); forceRepaint(); } void RoutingPluginPrivate::togglePositionTracking( bool enabled ) { PositionProviderPlugin* plugin = 0; if ( enabled ) { const PluginManager* pluginManager = m_marbleWidget->model()->pluginManager(); QList plugins = pluginManager->positionProviderPlugins(); if ( plugins.size() > 0 ) { plugin = plugins.first()->newInstance(); } } m_parent->marbleModel()->positionTracking()->setPositionProviderPlugin( plugin ); } void RoutingPluginPrivate::reverseRoute() { if ( m_marbleWidget ) { m_marbleWidget->model()->routingManager()->reverseRoute(); } } void RoutingPluginPrivate::readSettings() { if ( m_configDialog ) { if ( !m_speakersModel ) { m_speakersModel = new SpeakersModel( m_parent ); } int const index = m_speakersModel->indexOf( m_audio->speaker() ); m_configUi.speakerComboBox->setModel( m_speakersModel ); m_configUi.speakerComboBox->setCurrentIndex( index ); m_configUi.voiceNavigationCheckBox->setChecked( !m_audio->isMuted() ); m_configUi.soundRadioButton->setChecked( m_audio->isSoundEnabled() ); m_configUi.speakerRadioButton->setChecked( !m_audio->isSoundEnabled() ); } } qreal RoutingPluginPrivate::nextInstructionDistance() const { GeoDataCoordinates position = m_routingModel->route().position(); GeoDataCoordinates interpolated = m_routingModel->route().positionOnRoute(); GeoDataCoordinates onRoute = m_routingModel->route().currentWaypoint(); qreal planetRadius = m_marbleWidget->model()->planet()->radius(); - qreal distance = planetRadius * ( distanceSphere( position, interpolated ) + distanceSphere( interpolated, onRoute ) ); + const qreal distance = planetRadius * (position.sphericalDistanceTo(interpolated) + interpolated.sphericalDistanceTo(onRoute)); const RouteSegment &segment = m_routingModel->route().currentSegment(); for (int i=0; iroute().currentSegment().maneuver().position(); bool foundSegment = false; qreal distance = nextInstructionDistance(); for ( int i=0; iroute().size(); ++i ) { if ( foundSegment ) { distance += m_routingModel->route().at( i ).distance(); } else { foundSegment = m_routingModel->route().at( i ).maneuver().position() == position; } } return distance; } void RoutingPlugin::writeSettings() { Q_ASSERT( d->m_configDialog ); int const index = d->m_configUi.speakerComboBox->currentIndex(); if ( index >= 0 ) { QModelIndex const idx = d->m_speakersModel->index( index ); d->m_audio->setSpeaker( d->m_speakersModel->data( idx, SpeakersModel::Path ).toString() ); if ( !d->m_speakersModel->data( idx, SpeakersModel::IsLocal ).toBool() ) { d->m_speakersModel->install( index ); } } d->m_audio->setMuted( !d->m_configUi.voiceNavigationCheckBox->isChecked() ); d->m_audio->setSoundEnabled( d->m_configUi.soundRadioButton->isChecked() ); d->readSettings(); emit settingsChanged( nameId() ); } RoutingPlugin::RoutingPlugin() : AbstractFloatItem( 0 ), d( 0 ) { } RoutingPlugin::RoutingPlugin( const MarbleModel *marbleModel ) : AbstractFloatItem( marbleModel, QPointF( -10, -10 ) ), d( new RoutingPluginPrivate( this ) ) { setEnabled( true ); //plugin is visible by default on small screen devices setVisible( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ); setPadding( 0.5 ); setBorderWidth( 1 ); setBackground( QBrush( QColor( "white" ) ) ); } RoutingPlugin::~RoutingPlugin() { delete d; } QStringList RoutingPlugin::backendTypes() const { return QStringList(QStringLiteral("routing")); } QString RoutingPlugin::name() const { return tr( "Routing" ); } QString RoutingPlugin::guiString() const { return tr( "&Routing" ); } QString RoutingPlugin::nameId() const { return QStringLiteral("routing"); } QString RoutingPlugin::version() const { return QStringLiteral("1.0"); } QString RoutingPlugin::description() const { return tr( "Routing information and navigation controls" ); } QString RoutingPlugin::copyrightYears() const { return QStringLiteral("2010"); } QVector RoutingPlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Siddharth Srivastava"), QStringLiteral("akssps011@gmail.com")) << PluginAuthor(QStringLiteral("Dennis Nienhüser"), QStringLiteral("nienhueser@kde.org")); } QIcon RoutingPlugin::icon() const { return QIcon(QStringLiteral(":/icons/routeplanning.png")); } void RoutingPlugin::initialize() { QWidget *widget = new QWidget; d->m_widget.setupUi( widget ); d->m_widgetItem = new WidgetGraphicsItem( this ); d->m_widgetItem->setWidget( widget ); PositionProviderPlugin* activePlugin = marbleModel()->positionTracking()->positionProviderPlugin(); d->updateGpsButton( activePlugin ); connect( marbleModel()->positionTracking(), SIGNAL(positionProviderPluginChanged(PositionProviderPlugin*)), this, SLOT(updateGpsButton(PositionProviderPlugin*)) ); d->m_widget.routingButton->setEnabled( false ); connect( d->m_widget.instructionLabel, SIGNAL(linkActivated(QString)), this, SLOT(reverseRoute()) ); MarbleGraphicsGridLayout *layout = new MarbleGraphicsGridLayout( 1, 1 ); layout->addItem( d->m_widgetItem, 0, 0 ); setLayout( layout ); d->updateButtonVisibility(); } bool RoutingPlugin::isInitialized() const { return d->m_widgetItem; } bool RoutingPlugin::eventFilter( QObject *object, QEvent *e ) { if ( d->m_marbleWidget || !enabled() || !visible() ) { return AbstractFloatItem::eventFilter( object, e ); } MarbleWidget *widget = dynamic_cast ( object ); if ( widget && !d->m_marbleWidget ) { d->m_marbleWidget = widget; d->m_routingModel = d->m_marbleWidget->model()->routingManager()->routingModel(); connect( d->m_widget.routingButton, SIGNAL(clicked(bool)), this, SLOT(toggleGuidanceMode(bool)) ); connect( d->m_widget.gpsButton, SIGNAL(clicked(bool)), this, SLOT(togglePositionTracking(bool)) ); connect( d->m_widget.zoomInButton, SIGNAL(clicked()), d->m_marbleWidget, SLOT(zoomIn()) ); connect( d->m_widget.zoomOutButton, SIGNAL(clicked()), d->m_marbleWidget, SLOT(zoomOut()) ); connect( d->m_marbleWidget, SIGNAL(themeChanged(QString)), this, SLOT(updateZoomButtons()) ); connect( d->m_marbleWidget, SIGNAL(zoomChanged(int)), this, SLOT(updateZoomButtons(int)) ); connect( d->m_routingModel, SIGNAL(currentRouteChanged()), this, SLOT(updateGuidanceModeButton()) ); d->updateGuidanceModeButton(); } return AbstractFloatItem::eventFilter( object, e ); } QHash RoutingPlugin::settings() const { QHash result = AbstractFloatItem::settings(); result.insert(QStringLiteral("muted"), d->m_audio->isMuted()); result.insert(QStringLiteral("sound"), d->m_audio->isSoundEnabled()); result.insert(QStringLiteral("speaker"), d->m_audio->speaker()); return result; } void RoutingPlugin::setSettings( const QHash &settings ) { AbstractFloatItem::setSettings( settings ); d->m_audio->setMuted(settings.value(QStringLiteral("muted"), false).toBool()); d->m_audio->setSoundEnabled(settings.value(QStringLiteral("sound"), true).toBool()); d->m_audio->setSpeaker(settings.value(QStringLiteral("speaker")).toString()); d->readSettings(); } QDialog *RoutingPlugin::configDialog() { if ( !d->m_configDialog ) { d->m_configDialog = new QDialog; d->m_configUi.setupUi( d->m_configDialog ); d->readSettings(); connect( d->m_configDialog, SIGNAL(accepted()), this, SLOT(writeSettings()) ); connect( d->m_configDialog, SIGNAL(rejected()), this, SLOT(readSettings()) ); connect( d->m_configUi.buttonBox->button( QDialogButtonBox::Reset ), SIGNAL(clicked()), SLOT(restoreDefaultSettings()) ); } return d->m_configDialog; } } #include "moc_RoutingPlugin.cpp" diff --git a/src/plugins/runner/local-osm-search/OsmDatabase.cpp b/src/plugins/runner/local-osm-search/OsmDatabase.cpp index da1ff8c27..fa3cd7cc8 100644 --- a/src/plugins/runner/local-osm-search/OsmDatabase.cpp +++ b/src/plugins/runner/local-osm-search/OsmDatabase.cpp @@ -1,306 +1,306 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Dennis Nienhüser // Copyright 2013 Bernhard Beschow // #include "OsmDatabase.h" #include "DatabaseQuery.h" #include "GeoDataLatLonAltBox.h" #include "MarbleDebug.h" #include "MarbleMath.h" #include "MarbleLocale.h" #include "MarbleModel.h" #include "PositionTracking.h" #include #include #include #include #include namespace Marble { namespace { class PlacemarkSmallerDistance { public: PlacemarkSmallerDistance( const GeoDataCoordinates ¤tPosition ) : m_currentPosition( currentPosition ) {} bool operator()( const OsmPlacemark &a, const OsmPlacemark &b ) const { return distanceSphere( a.longitude() * DEG2RAD, a.latitude() * DEG2RAD, m_currentPosition.longitude(), m_currentPosition.latitude() ) < distanceSphere( b.longitude() * DEG2RAD, b.latitude() * DEG2RAD, m_currentPosition.longitude(), m_currentPosition.latitude() ); } private: GeoDataCoordinates m_currentPosition; }; class PlacemarkHigherScore { public: PlacemarkHigherScore( const DatabaseQuery *currentQuery ) : m_currentQuery( currentQuery ) {} bool operator()( const OsmPlacemark &a, const OsmPlacemark &b ) const { return a.matchScore( m_currentQuery ) > b.matchScore( m_currentQuery ); } private: const DatabaseQuery *const m_currentQuery; }; } OsmDatabase::OsmDatabase( const QStringList &databaseFiles ) : m_databaseFiles( databaseFiles ) { } QVector OsmDatabase::find( const DatabaseQuery &userQuery ) { if ( m_databaseFiles.isEmpty() ) { return QVector(); } QSqlDatabase database = QSqlDatabase::addDatabase( "QSQLITE", QString( "marble/local-osm-search-%1" ).arg( reinterpret_cast( this ) ) ); QVector result; QTime timer; timer.start(); for( const QString &databaseFile: m_databaseFiles ) { database.setDatabaseName( databaseFile ); if ( !database.open() ) { qWarning() << "Failed to connect to database" << databaseFile; } QString regionRestriction; if ( !userQuery.region().isEmpty() ) { QTime regionTimer; regionTimer.start(); // Nested set model to support region hierarchies, see http://en.wikipedia.org/wiki/Nested_set_model const QString regionsQueryString = QLatin1String("SELECT lft, rgt FROM regions WHERE name LIKE '%") + userQuery.region() + QLatin1String("%';"); QSqlQuery regionsQuery( regionsQueryString, database ); if ( regionsQuery.lastError().isValid() ) { qWarning() << regionsQuery.lastError() << "in" << databaseFile << "with query" << regionsQuery.lastQuery(); } regionRestriction = " AND ("; int regionCount = 0; while ( regionsQuery.next() ) { if ( regionCount > 0 ) { regionRestriction += QLatin1String(" OR "); } regionRestriction += QLatin1String(" (regions.lft >= ") + regionsQuery.value( 0 ).toString() + QLatin1String(" AND regions.lft <= ") + regionsQuery.value( 1 ).toString() + QLatin1Char(')'); regionCount++; } regionRestriction += QLatin1Char(')'); mDebug() << Q_FUNC_INFO << "region query in" << databaseFile << "with query" << regionsQueryString << "took" << regionTimer.elapsed() << "ms for" << regionCount << "results"; if ( regionCount == 0 ) { continue; } } QString queryString; queryString = " SELECT regions.name," " places.name, places.number," " places.category, places.lon, places.lat" " FROM regions, places"; if ( userQuery.queryType() == DatabaseQuery::CategorySearch ) { queryString += QLatin1String(" WHERE regions.id = places.region"); if( userQuery.category() == OsmPlacemark::UnknownCategory ) { // search for all pois which are not street nor address queryString += QLatin1String(" AND places.category <> 0 AND places.category <> 6"); } else { // search for specific category queryString += QLatin1String(" AND places.category = %1"); queryString = queryString.arg( (qint32) userQuery.category() ); } if ( userQuery.position().isValid() && userQuery.region().isEmpty() ) { // sort by distance queryString += QLatin1String(" ORDER BY ((places.lat-%1)*(places.lat-%1)+(places.lon-%2)*(places.lon-%2))"); GeoDataCoordinates position = userQuery.position(); queryString = queryString.arg( position.latitude( GeoDataCoordinates::Degree ), 0, 'f', 8 ) .arg( position.longitude( GeoDataCoordinates::Degree ), 0, 'f', 8 ); } else { queryString += regionRestriction; } } else if ( userQuery.queryType() == DatabaseQuery::BroadSearch ) { queryString += QLatin1String(" WHERE regions.id = places.region" " AND places.name ") + wildcardQuery(userQuery.searchTerm()); } else { queryString += QLatin1String(" WHERE regions.id = places.region" " AND places.name ") + wildcardQuery(userQuery.street()); if ( !userQuery.houseNumber().isEmpty() ) { queryString += QLatin1String(" AND places.number ") + wildcardQuery(userQuery.houseNumber()); } else { queryString += QLatin1String(" AND places.number IS NULL"); } queryString += regionRestriction; } queryString += QLatin1String(" LIMIT 50;"); /** @todo: sort/filter results from several databases */ QSqlQuery query( database ); query.setForwardOnly( true ); QTime queryTimer; queryTimer.start(); if ( !query.exec( queryString ) ) { qWarning() << query.lastError() << "in" << databaseFile << "with query" << query.lastQuery(); continue; } int resultCount = 0; while ( query.next() ) { OsmPlacemark placemark; if ( userQuery.resultFormat() == DatabaseQuery::DistanceFormat ) { GeoDataCoordinates coordinates( query.value(4).toFloat(), query.value(5).toFloat(), 0.0, GeoDataCoordinates::Degree ); placemark.setAdditionalInformation( formatDistance( coordinates, userQuery.position() ) ); } else { placemark.setAdditionalInformation( query.value( 0 ).toString() ); } placemark.setName( query.value(1).toString() ); placemark.setHouseNumber( query.value(2).toString() ); placemark.setCategory( (OsmPlacemark::OsmCategory) query.value(3).toInt() ); placemark.setLongitude( query.value(4).toFloat() ); placemark.setLatitude( query.value(5).toFloat() ); result.push_back( placemark ); resultCount++; } mDebug() << Q_FUNC_INFO << "query in" << databaseFile << "with query" << queryString << "took" << queryTimer.elapsed() << "ms for" << resultCount << "results"; } mDebug() << "Offline OSM search query took" << timer.elapsed() << "ms for" << result.count() << "results."; std::sort( result.begin(), result.end() ); makeUnique( result ); if ( userQuery.position().isValid() ) { const PlacemarkSmallerDistance placemarkSmallerDistance( userQuery.position() ); std::sort( result.begin(), result.end(), placemarkSmallerDistance ); } else { const PlacemarkHigherScore placemarkHigherScore( &userQuery ); std::sort( result.begin(), result.end(), placemarkHigherScore ); } if ( result.size() > 50 ) { result.remove( 50, result.size()-50 ); } return result; } void OsmDatabase::makeUnique( QVector &placemarks ) { for ( int i=1; ilocale()->measurementSystem() == MarbleLocale::ImperialSystem ) { precision = 1; distanceUnit = "mi"; distance *= METER2KM; distance *= KM2MI; } else if (MarbleGlobal::getInstance()->locale()->measurementSystem() == MarbleLocale::MetricSystem) { if ( distance >= 1000 ) { distance /= 1000; distanceUnit = "km"; precision = 1; } else if ( distance >= 200 ) { distance = 50 * qRound( distance / 50 ); } else if ( distance >= 100 ) { distance = 25 * qRound( distance / 25 ); } else { distance = 10 * qRound( distance / 10 ); } } else if (MarbleGlobal::getInstance()->locale()->measurementSystem() == MarbleLocale::NauticalSystem) { precision = 2; distanceUnit = "nm"; distance *= METER2KM; distance *= KM2NM; } QString const fuzzyDistance = QString( "%1 %2" ).arg( distance, 0, 'f', precision ).arg( distanceUnit ); int direction = 180 + bearing( a, b ) * RAD2DEG; QString heading = QObject::tr( "north" ); if ( direction > 337 ) { heading = QObject::tr( "north" ); } else if ( direction > 292 ) { heading = QObject::tr( "north-west" ); } else if ( direction > 247 ) { heading = QObject::tr( "west" ); } else if ( direction > 202 ) { heading = QObject::tr( "south-west" ); } else if ( direction > 157 ) { heading = QObject::tr( "south" ); } else if ( direction > 112 ) { heading = QObject::tr( "south-east" ); } else if ( direction > 67 ) { heading = QObject::tr( "east" ); } else if ( direction > 22 ) { heading = QObject::tr( "north-east" ); } return fuzzyDistance + QLatin1Char(' ') + heading; } qreal OsmDatabase::bearing( const GeoDataCoordinates &a, const GeoDataCoordinates &b ) { qreal delta = b.longitude() - a.longitude(); qreal lat1 = a.latitude(); qreal lat2 = b.latitude(); return fmod( atan2( sin ( delta ) * cos ( lat2 ), cos( lat1 ) * sin( lat2 ) - sin( lat1 ) * cos( lat2 ) * cos ( delta ) ), 2 * M_PI ); } QString OsmDatabase::wildcardQuery( const QString &term ) { QString result = term; if (term.contains(QLatin1Char('*'))) { return QLatin1String(" LIKE '") + result.replace(QLatin1Char('*'), QLatin1Char('%')) + QLatin1Char('\''); } else { return QLatin1String(" = '") + result + QLatin1Char('\''); } } } diff --git a/tools/vectorosm-tilecreator/NodeReducer.cpp b/tools/vectorosm-tilecreator/NodeReducer.cpp index 54afeaa82..9e5b72d4e 100644 --- a/tools/vectorosm-tilecreator/NodeReducer.cpp +++ b/tools/vectorosm-tilecreator/NodeReducer.cpp @@ -1,220 +1,220 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Akshat Tandon // #include "GeoDataPlacemark.h" #include "GeoDataLineString.h" #include "GeoDataPolygon.h" #include "GeoDataBuilding.h" #include "GeoDataMultiGeometry.h" #include "GeoDataCoordinates.h" #include "MarbleMath.h" #include "NodeReducer.h" #include "OsmPlacemarkData.h" #include #include namespace Marble { NodeReducer::NodeReducer(GeoDataDocument* document, const TileId &tileId) : m_removedNodes(0), m_remainingNodes(0), m_zoomLevel(tileId.zoomLevel()) { const GeoSceneMercatorTileProjection tileProjection; GeoDataLatLonBox tileBoundary = tileProjection.geoCoordinates(m_zoomLevel, tileId.x(), tileId.y()); tileBoundary.scale(1.0-1e-4, 1.0-1e-4); tileBoundary.boundaries(m_tileBoundary[North], m_tileBoundary[South], m_tileBoundary[East], m_tileBoundary[West]); for (GeoDataPlacemark* placemark: document->placemarkList()) { GeoDataGeometry const * const geometry = placemark->geometry(); auto const visualCategory = placemark->visualCategory(); if (const auto prevLine = geodata_cast(geometry)) { GeoDataLineString* reducedLine = new GeoDataLineString; reduce(*prevLine, placemark->osmData(), visualCategory, reducedLine); placemark->setGeometry(reducedLine); } else if (m_zoomLevel < 17) { if (const auto prevRing = geodata_cast(geometry)) { placemark->setGeometry(reducedRing(*prevRing, placemark, visualCategory)); } else if (const auto prevPolygon = geodata_cast(geometry)) { placemark->setGeometry(reducedPolygon(*prevPolygon, placemark, visualCategory)); } else if (const auto building = geodata_cast(geometry)) { if (const auto prevRing = geodata_cast(&building->multiGeometry()->at(0))) { GeoDataLinearRing* ring = reducedRing(*prevRing, placemark, visualCategory); GeoDataBuilding* newBuilding = new GeoDataBuilding(*building); newBuilding->multiGeometry()->clear(); newBuilding->multiGeometry()->append(ring); placemark->setGeometry(newBuilding); } else if (const auto prevPolygon = geodata_cast(&building->multiGeometry()->at(0))) { GeoDataPolygon* poly = reducedPolygon(*prevPolygon, placemark, visualCategory); GeoDataBuilding* newBuilding = new GeoDataBuilding(*building); newBuilding->multiGeometry()->clear(); newBuilding->multiGeometry()->append(poly); placemark->setGeometry(newBuilding); } } } } } qint64 NodeReducer::remainingNodes() const { return m_remainingNodes; } qreal NodeReducer::epsilonFor(qreal multiplier) const { if (m_zoomLevel >= 17) { return 0.25; } else if (m_zoomLevel >= 10) { int const factor = 1 << (qAbs(m_zoomLevel-12)); return multiplier / factor; } else { int const factor = 1 << (qAbs(m_zoomLevel-10)); return multiplier * factor; } } qreal NodeReducer::perpendicularDistance(const GeoDataCoordinates &a, const GeoDataCoordinates &b, const GeoDataCoordinates &c) const { qreal ret; qreal const y0 = a.latitude(); qreal const x0 = a.longitude(); qreal const y1 = b.latitude(); qreal const x1 = b.longitude(); qreal const y2 = c.latitude(); qreal const x2 = c.longitude(); qreal const y01 = x0 - x1; qreal const x01 = y0 - y1; qreal const y10 = x1 - x0; qreal const x10 = y1 - y0; qreal const y21 = x2 - x1; qreal const x21 = y2 - y1; qreal const len = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2); qreal const t = len == 0.0 ? -1.0 : (x01 * x21 + y01 * y21) / len; if ( t < 0.0 ) { - ret = EARTH_RADIUS * distanceSphere(a, b); + ret = EARTH_RADIUS * a.sphericalDistanceTo(b); } else if ( t > 1.0 ) { - ret = EARTH_RADIUS * distanceSphere(a, c); + ret = EARTH_RADIUS * a.sphericalDistanceTo(c); } else { qreal const nom = qAbs( x21 * y10 - x10 * y21 ); qreal const den = sqrt( x21 * x21 + y21 * y21 ); ret = EARTH_RADIUS * nom / den; } return ret; } bool NodeReducer::touchesTileBorder(const GeoDataCoordinates &coordinates) const { return coordinates.latitude() >= m_tileBoundary[North] || coordinates.latitude() <= m_tileBoundary[South] || coordinates.longitude() <= m_tileBoundary[West] || coordinates.longitude() >= m_tileBoundary[East]; } qint64 NodeReducer::removedNodes() const { return m_removedNodes; } void NodeReducer::setBorderPoints(OsmPlacemarkData &osmData, const QVector &borderPoints, int length) const { int const n = borderPoints.size(); if (n == 0) { return; } if (n > length) { qDebug() << "Invalid border points for length" << length << ":" << borderPoints; return; } typedef QPair Segment; typedef QVector Segments; Segments segments; Segment currentSegment; currentSegment.first = borderPoints.first(); currentSegment.second = currentSegment.first; for (int i=1; i 1 && segments.last().second+1 == length && segments.first().first == 0) { segments.last().second = segments.first().second; segments.pop_front(); } int wraps = 0; for (auto const &segment: segments) { if (segment.first >= segment.second) { ++wraps; } if (segment.first < 0 || segment.second < 0 || segment.first+1 > length || segment.second+1 > length) { qDebug() << "Wrong border points sequence for length " << length << ":" << borderPoints << ", intermediate " << segments; return; } } if (wraps > 1) { //qDebug() << "Wrong border points sequence:" << borderPoints; return; } QString value; value.reserve(segments.size() * (2 + QString::number(length).size())); for (auto const &segment: segments) { int diff = segment.second - segment.first; diff = diff > 0 ? diff : length + diff; value = value % QStringLiteral(";") % QString::number(segment.first) % QStringLiteral("+") % QString::number(diff); } osmData.addTag(QStringLiteral("mx:bp"), value.mid(1)); } GeoDataLinearRing *NodeReducer::reducedRing(const GeoDataLinearRing& prevRing, GeoDataPlacemark* placemark, const GeoDataPlacemark::GeoDataVisualCategory& visualCategory) { GeoDataLinearRing* reducedRing = new GeoDataLinearRing; reduce(prevRing, placemark->osmData(), visualCategory, reducedRing); return reducedRing; } GeoDataPolygon *NodeReducer::reducedPolygon(const GeoDataPolygon& prevPolygon, GeoDataPlacemark* placemark, const GeoDataPlacemark::GeoDataVisualCategory& visualCategory) { GeoDataPolygon* reducedPolygon = new GeoDataPolygon; GeoDataLinearRing const * prevRing = &(prevPolygon.outerBoundary()); GeoDataLinearRing reducedRing; reduce(*prevRing, placemark->osmData().memberReference(-1), visualCategory, &reducedRing); reducedPolygon->setOuterBoundary(reducedRing); QVector const & innerBoundaries = prevPolygon.innerBoundaries(); for(int i = 0; i < innerBoundaries.size(); i++) { prevRing = &innerBoundaries[i]; GeoDataLinearRing reducedInnerRing; reduce(*prevRing, placemark->osmData().memberReference(i), visualCategory, &reducedInnerRing); reducedPolygon->appendInnerBoundary(reducedInnerRing); } return reducedPolygon; } } diff --git a/tools/vectorosm-tilecreator/PeakAnalyzer.cpp b/tools/vectorosm-tilecreator/PeakAnalyzer.cpp index fc055be40..9ce1831be 100644 --- a/tools/vectorosm-tilecreator/PeakAnalyzer.cpp +++ b/tools/vectorosm-tilecreator/PeakAnalyzer.cpp @@ -1,108 +1,108 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Dennis Nienhüser // #include "PeakAnalyzer.h" #include "GeoDataPlacemark.h" #include "MarbleMath.h" #include "OsmPlacemarkData.h" #include #include namespace Marble { PeakAnalyzer::Peaks PeakAnalyzer::peaksNear(const GeoDataPlacemark* placemark, const Peaks &peaks, double maxDistance) { // If this turns out to become a bottleneck due to quadratic runtime, use kd-tree via nanoflann from // https://github.com/jlblancoc/nanoflann to speed it up. Peaks neighbors; for (auto peak: peaks) { - if (distanceSphere(peak->coordinate(), placemark->coordinate()) < maxDistance) { + if (peak->coordinate().sphericalDistanceTo(placemark->coordinate()) < maxDistance) { neighbors << peak; } } return neighbors; } void PeakAnalyzer::dbScan(const Peaks &peaks, double maxDistance, int minPoints) { QSet visited; QMap associations; Peaks noise; PeakClusters clusters; for(auto peak: peaks) { if (visited.contains(peak)) { continue; } visited << peak; auto neighbors = peaksNear(peak, peaks, maxDistance); if (neighbors.size() < minPoints) { noise << peak; } else { PeakCluster* fit = nullptr; for (auto &cluster: clusters) { for (auto placemark: cluster) { - if (distanceSphere(peak->coordinate(), placemark->coordinate()) < maxDistance) { + if (peak->coordinate().sphericalDistanceTo(placemark->coordinate()) < maxDistance) { fit = &cluster; } } } if (!fit) { clusters << PeakCluster(); fit = &clusters.last(); } while (!neighbors.isEmpty()) { auto neighbor = neighbors.front(); neighbors.pop_front(); if (!visited.contains(neighbor)) { visited << neighbor; auto const moreNeighbors = peaksNear(neighbor, peaks, maxDistance); if (moreNeighbors.size() >= minPoints) { neighbors += moreNeighbors; } } if (associations[neighbor] == nullptr) { *fit << neighbor; associations[neighbor] = fit; } } } } for (auto &cluster: clusters) { Q_ASSERT(!cluster.isEmpty()); std::sort(cluster.begin(), cluster.end(), [](GeoDataPlacemark* a, GeoDataPlacemark* b) { return a->coordinate().altitude() > b->coordinate().altitude(); }); bool first = true; for (auto peak: cluster) { peak->osmData().addTag(QLatin1String("marbleZoomLevel"), first ? QLatin1String("11") : QLatin1String("13")); first = false; } } for (auto peak: noise) { peak->osmData().addTag(QLatin1String("marbleZoomLevel"), QLatin1String("11")); } } void PeakAnalyzer::determineZoomLevel(const QVector &placemarks) { QVector peaks; std::copy_if(placemarks.begin(), placemarks.end(), std::back_inserter(peaks), [](GeoDataPlacemark* placemark) { return placemark->visualCategory() == GeoDataPlacemark::NaturalPeak; }); double const maxDistance = 3000.0 / EARTH_RADIUS; dbScan(peaks, maxDistance, 2); } } diff --git a/tools/vectorosm-tilecreator/SpellChecker.cpp b/tools/vectorosm-tilecreator/SpellChecker.cpp index ec6fef3d0..595bd20dd 100644 --- a/tools/vectorosm-tilecreator/SpellChecker.cpp +++ b/tools/vectorosm-tilecreator/SpellChecker.cpp @@ -1,190 +1,190 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2016 Dennis Nienhüser // #include "SpellChecker.h" #include "GeoDataPlacemark.h" #include "MarbleMath.h" #include "OsmPlacemarkData.h" #include "MarbleDirs.h" #include "MarbleModel.h" #include "ParsingRunnerManager.h" #include "GeoSceneMercatorTileProjection.h" #include "TileId.h" #include #include #include namespace Marble { SpellChecker::SpellChecker(const QString &citiesFile) : m_tileLevel(10), m_tileHash(parseCities(citiesFile)), m_verbose(false) { // nothing to do } void SpellChecker::correctPlaceLabels(const QVector &placemarks) { auto places = cityPlaces(placemarks); double const maxDistance = 5000.0 / EARTH_RADIUS; int hits = 0; int validated = 0; int misses = 0; for (auto place: places) { auto const places = candidatesFor(place); bool hasMatch = false; bool isValid = false; QString const placeName = place->name(); if (!places.isEmpty()) { auto match = places.first(); if (match->name() == place->name()) { ++validated; isValid = true; } else { - if (distanceSphere(match->coordinate(), place->coordinate()) < maxDistance) { + if (match->coordinate().sphericalDistanceTo(place->coordinate()) < maxDistance) { if (levenshteinDistance(places.first()->name(), placeName) < 6) { if (m_verbose) { qDebug() << "Correcting" << placeName << "to" << match->name(); } place->setName(match->name()); place->osmData().removeTag("name"); place->osmData().addTag("name", match->name()); hasMatch = true; } } if (m_verbose && !hasMatch) { qDebug() << "No match for " << placeName << ", candidates: "; for (auto candidate: places) { - qDebug() << distanceSphere(candidate->coordinate(), place->coordinate()) * EARTH_RADIUS << " m, " + qDebug() << candidate->coordinate().sphericalDistanceTo(place->coordinate()) * EARTH_RADIUS << " m, " << "levenshtein distance " << levenshteinDistance(placeName, candidate->name()) << ":" << candidate->name(); } } } } else if (m_verbose) { qDebug() << "No match for " << placeName << " at " << place->coordinate().toString(GeoDataCoordinates::Decimal) << " and no candidates for replacement"; } hits += hasMatch ? 1 : 0; misses += (hasMatch || isValid) ? 0 : 1; } if (m_verbose) { qDebug() << "In total there are " << hits << " corrections, " << validated << " validations and " << misses << " misses"; } } void SpellChecker::setVerbose(bool verbose) { m_verbose = verbose; } QVector SpellChecker::cityPlaces(const QVector &placemarks) const { QSet categories; categories << GeoDataPlacemark::PlaceCity; categories << GeoDataPlacemark::PlaceCityCapital; categories << GeoDataPlacemark::PlaceCityNationalCapital; categories << GeoDataPlacemark::PlaceSuburb; categories << GeoDataPlacemark::PlaceHamlet; categories << GeoDataPlacemark::PlaceLocality; categories << GeoDataPlacemark::PlaceTown; categories << GeoDataPlacemark::PlaceTownCapital; categories << GeoDataPlacemark::PlaceTownNationalCapital; categories << GeoDataPlacemark::PlaceVillage; categories << GeoDataPlacemark::PlaceVillageCapital; categories << GeoDataPlacemark::PlaceVillageNationalCapital; QVector places; std::copy_if(placemarks.begin(), placemarks.end(), std::back_inserter(places), [categories] (GeoDataPlacemark* placemark) { return categories.contains(placemark->visualCategory()); }); return places; } QHash > SpellChecker::parseCities(const QString &filename) const { QHash > placeLabels; QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Cannot open " << filename << ":" << file.errorString(); return placeLabels; } while (!file.atEnd()) { QByteArray line = file.readLine(); auto const values = line.split('\t'); if (values.size() > 15) { GeoDataPlacemark* city = new GeoDataPlacemark; city->setName(values[1]); bool ok; double const lon = values[5].toDouble(&ok); if (!ok) { qDebug() << values[5] << " is no longitude"; continue; } double const lat = values[4].toDouble(&ok); if (!ok) { qDebug() << values[4] << " is no latitude"; continue; } double const ele = values[15].toDouble(); auto const coordinate = GeoDataCoordinates(lon, lat, ele, GeoDataCoordinates::Degree); city->setCoordinate(coordinate); auto const tile = TileId::fromCoordinates(coordinate, m_tileLevel); placeLabels[tile] << city; } } return placeLabels; } int SpellChecker::levenshteinDistance(const QString &a, const QString &b) { // From https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance unsigned int const len1 = a.size(), len2 = b.size(); std::vector> distance(len1 + 1, std::vector(len2 + 1)); distance[0][0] = 0; for(unsigned int i = 1; i <= len1; ++i) { distance[i][0] = i; } for(unsigned int i = 1; i <= len2; ++i) { distance[0][i] = i; } for(unsigned int i = 1; i <= len1; ++i) { for(unsigned int j = 1; j <= len2; ++j) { distance[i][j] = std::min({ distance[i - 1][j] + 1, distance[i][j - 1] + 1, distance[i - 1][j - 1] + (a[i - 1] == b[j - 1] ? 0 : 1) }); } } return distance[len1][len2]; } QVector SpellChecker::candidatesFor(GeoDataPlacemark *placemark) const { int const N = pow(2, m_tileLevel); auto const tile = TileId::fromCoordinates(placemark->coordinate(), m_tileLevel); QVector places; for (int x=qMax(0, tile.x()-1); xname(); std::sort(places.begin(), places.end(), [placeName] (GeoDataPlacemark* a, GeoDataPlacemark* b) { return levenshteinDistance(a->name(), placeName) < levenshteinDistance(b->name(), placeName); }); return places; } }