diff --git a/examples/cpp/squad-interpolation/squad-interpolation.cpp b/examples/cpp/squad-interpolation/squad-interpolation.cpp index d90c1836a..3cd312b8f 100644 --- a/examples/cpp/squad-interpolation/squad-interpolation.cpp +++ b/examples/cpp/squad-interpolation/squad-interpolation.cpp @@ -1,241 +1,242 @@ // // 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 #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 ) { 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/apps/marble-kde/marble.kcfg b/src/apps/marble-kde/marble.kcfg index 9f046b1ae..9da3f60aa 100644 --- a/src/apps/marble-kde/marble.kcfg +++ b/src/apps/marble-kde/marble.kcfg @@ -1,333 +1,334 @@ "../../lib/marble/MarbleGlobal.h" + "../../lib/marble/MarbleColors.h" QtCore/QDir QtCore/QLocale QFontDatabase 9.4 54.8 1050 0.0 0.0 11564500 true false false false 0 true 1 -100 100 true false true false false 0 false true QDir::homePath() QDir::homePath() false 0 QLocale::MetricSystem Marble::DMSDegree Marble::HighQuality Marble::LowQuality Marble::Native QFontDatabase::systemFont(QFontDatabase::GeneralFont) QDir::homePath() false false false false Marble::KeepAxisVertically Marble::ShowHomeLocation true false 0 100 0 999999 0 0 999999 8080 0 65535 true Marble::HttpProxy false false true true false true true true 0 Marble::Oxygen::skyBlue4 200 Marble::Oxygen::seaBlue2 200 Marble::Oxygen::aluminumGray4 200 false owncloud true true diff --git a/src/apps/marble-qt/QtMainWindow.cpp b/src/apps/marble-qt/QtMainWindow.cpp index 67570dc0b..7f6b699b5 100644 --- a/src/apps/marble-qt/QtMainWindow.cpp +++ b/src/apps/marble-qt/QtMainWindow.cpp @@ -1,1665 +1,1666 @@ // // 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-2010 Torsten Rahn // Copyright 2007 Inge Wallin // Copyright 2011-2013 Bernhard Beschow // Copyright 2012 Illya Kovalevskyy // Copyright 2012 Mohammed Nafees // #include "QtMainWindow.h" #include "MarbleDebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "EditBookmarkDialog.h" #include "BookmarkManagerDialog.h" #include "CurrentLocationWidget.h" #include "MapViewWidget.h" #include "MarbleDirs.h" #include "MarbleAboutDialog.h" #include "QtMarbleConfigDialog.h" #include "SunControlWidget.h" #include "TimeControlWidget.h" #include "MarbleLocale.h" #include "DownloadRegionDialog.h" #include "ViewportParams.h" #include "AbstractFloatItem.h" #include "MarbleModel.h" #include "MarbleClock.h" #include "HttpDownloadManager.h" #include "BookmarkManager.h" #include "NewBookmarkFolderDialog.h" #include "GeoSceneDocument.h" #include "GeoSceneHead.h" #include "GeoDataLookAt.h" #include "GeoDataCoordinates.h" #include "GeoDataDocument.h" #include "GeoDataFolder.h" #include "GeoDataPlacemark.h" #include "GeoUriParser.h" #include "routing/RoutingManager.h" #include "routing/RoutingProfilesModel.h" #include "routing/RoutingWidget.h" #include "routing/RouteRequest.h" #include "ParseRunnerPlugin.h" #include "PositionTracking.h" #include "PositionProviderPlugin.h" #include "PluginManager.h" +#include "MarbleColors.h" #include "MapThemeDownloadDialog.h" #include "MapWizard.h" #include "MarbleWidgetInputHandler.h" #include "Planet.h" #include "cloudsync/CloudSyncManager.h" #include "cloudsync/BookmarkSyncManager.h" #include "cloudsync/RouteSyncManager.h" #include "MovieCaptureDialog.h" #include "DataMigration.h" #include "TileCoordsPyramid.h" using namespace Marble; /* TRANSLATOR Marble::MainWindow */ MainWindow::MainWindow(const QString& marbleDataPath, const QVariantMap& cmdLineSettings, QWidget *parent) : QMainWindow(parent), m_controlView( nullptr ), m_savedSize( QSize(-1, -1) ), m_sunControlDialog( nullptr ), m_timeControlDialog( nullptr ), m_configDialog( nullptr ), m_downloadRegionDialog( nullptr ), m_movieCaptureDialog( nullptr ), // File Menu m_fileMenu( nullptr ), m_viewMenu( nullptr ), m_helpMenu( nullptr ), m_settingsMenu( nullptr ), m_panelMenu( nullptr ), m_viewSizeMenu( nullptr ), m_infoBoxesMenu( nullptr ), m_onlineServicesMenu( nullptr ), m_bookmarkMenu( nullptr ), m_openAction( nullptr ), m_exportMapAction( nullptr ), m_downloadAction( nullptr ), m_downloadRegionAction( nullptr ), m_printPreviewAction( nullptr ), m_printAction( nullptr ), m_workOfflineAction( nullptr ), m_quitAction( nullptr ), m_mapWizardAction( nullptr ), // Edit Menu m_copyMapAction( nullptr ), m_copyCoordinatesAction( nullptr ), m_osmEditAction( nullptr ), m_recordMovieAction( nullptr ), m_stopRecordingAction( nullptr ), // View Menu m_showCloudsAction( nullptr ),\ m_controlSunAction( nullptr ), m_controlTimeAction( nullptr ), m_reloadAction( nullptr ), // Settings Menu m_fullScreenAction( nullptr ), m_statusBarAction( nullptr ), m_configDialogAction( nullptr ), m_viewSizeActsGroup( nullptr ), // Help Menu m_whatsThisAction( nullptr ), m_aboutMarbleAction( nullptr ), m_aboutQtAction( nullptr ), m_lockFloatItemsAction( nullptr ), m_handbookAction( nullptr ), m_forumAction( nullptr ), // Status Bar m_positionLabel( nullptr ), m_distanceLabel( nullptr ), m_zoomLabel( nullptr ), m_clockLabel( nullptr ), m_downloadProgressBar( nullptr ), m_toggleTileLevelAction( nullptr ), m_angleDisplayUnitActionGroup( nullptr ), m_dmsDegreeAction( nullptr ), m_decimalDegreeAction( nullptr ), m_utmAction( nullptr ), //Bookmark Menu m_addBookmarkAction( nullptr ), m_setHomeAction( nullptr ), m_toggleBookmarkDisplayAction( nullptr ), m_manageBookmarksAction( nullptr ) { setUpdatesEnabled( false ); QString selectedPath = marbleDataPath.isEmpty() ? readMarbleDataPath() : marbleDataPath; if ( !selectedPath.isEmpty() ) MarbleDirs::setMarbleDataPath( selectedPath ); #ifdef Q_OS_WIN QPointer migration = new DataMigration(this); migration->exec(); #endif m_controlView = new ControlView( this ); setWindowIcon(QIcon(QStringLiteral(":/icons/marble.png"))); setCentralWidget( m_controlView ); // Initializing config dialog m_configDialog = new QtMarbleConfigDialog( m_controlView->marbleWidget(), m_controlView->cloudSyncManager(), this ); connect( m_configDialog, SIGNAL(settingsChanged()), this, SLOT(updateSettings()) ); connect( m_configDialog, SIGNAL(clearVolatileCacheClicked()), m_controlView->marbleWidget(), SLOT(clearVolatileTileCache()) ); connect( m_configDialog, SIGNAL(clearPersistentCacheClicked()), m_controlView->marbleModel(), SLOT(clearPersistentTileCache()) ); connect( m_configDialog, SIGNAL(syncNowClicked()), m_controlView->cloudSyncManager()->bookmarkSyncManager(), SLOT(startBookmarkSync()) ); connect(m_configDialog, SIGNAL(syncNowClicked()), m_configDialog, SLOT(disableSyncNow())); // Load bookmark file. If it does not exist, a default one will be used. m_controlView->marbleModel()->bookmarkManager()->loadFile( "bookmarks/bookmarks.kml" ); createActions(); QList const panelActions = m_controlView->setupDockWidgets( this ); createMenus( panelActions ); createStatusBar(); connect( m_controlView->marbleWidget(), SIGNAL(themeChanged(QString)), this, SLOT(updateMapEditButtonVisibility(QString)) ); connect(m_controlView->marbleModel(), SIGNAL(themeChanged(QString)), this, SLOT(updateWindowTitle())); connect( m_controlView, SIGNAL(showMapWizard()), this, SLOT(showMapWizard()) ); connect( m_controlView, SIGNAL(mapThemeDeleted()), this, SLOT(fallBackToDefaultTheme()) ); updateWindowTitle(); setUpdatesEnabled( true ); m_position = QCoreApplication::translate( "Marble", NOT_AVAILABLE ); m_distance = marbleWidget()->distanceString(); m_zoom = QString::number( marbleWidget()->tileZoomLevel() ); m_clock = QLocale().toString( m_controlView->marbleModel()->clockDateTime().addSecs( m_controlView->marbleModel()->clockTimezone() ), QLocale::ShortFormat ); QMetaObject::invokeMethod(this, "initObject", Qt::QueuedConnection, Q_ARG(QVariantMap, cmdLineSettings)); } MainWindow::~MainWindow() { delete m_movieCaptureDialog; } void MainWindow::addGeoDataFile( const QString &fileName ) { QFileInfo file( fileName ); if ( !file.exists() ) return; // delay file loading to initObject(), such that restoring view from previous session in readSettings() // doesn't interfere with focusing on these files m_commandlineFilePaths << file.absoluteFilePath(); } void MainWindow::initObject(const QVariantMap& cmdLineSettings) { QCoreApplication::processEvents (); setupStatusBar(); readSettings(cmdLineSettings); for ( const QString &path: m_commandlineFilePaths ) { m_controlView->marbleModel()->addGeoDataFile( path ); } if ( cmdLineSettings.contains( "tour" ) ) { QString const tour = cmdLineSettings.value( "tour" ).toString(); m_controlView->openTour( tour ); } m_commandlineFilePaths.clear(); } void MainWindow::createActions() { m_openAction = new QAction(QIcon(QStringLiteral(":/icons/document-open.png")), tr("&Open..."), this); m_openAction->setShortcut( tr( "Ctrl+O" ) ); m_openAction->setStatusTip( tr( "Open a file for viewing on Marble")); connect( m_openAction, SIGNAL(triggered()), this, SLOT(openFile()) ); m_downloadAction = new QAction(QIcon(QStringLiteral(":/icons/get-hot-new-stuff.png")), tr("&Download Maps..."), this); connect(m_downloadAction, SIGNAL(triggered()), this, SLOT(openMapDialog())); m_exportMapAction = new QAction(QIcon(QStringLiteral(":/icons/document-save-as.png")), tr("&Export Map..."), this); m_exportMapAction->setShortcut(tr("Ctrl+S")); m_exportMapAction->setStatusTip(tr("Save a screenshot of the map")); connect(m_exportMapAction, SIGNAL(triggered()), this, SLOT(exportMapScreenShot())); // Action: Download Region m_downloadRegionAction = new QAction( tr( "Download &Region..." ), this ); m_downloadRegionAction->setStatusTip( tr( "Download a map region in different zoom levels for offline usage" ) ); connect( m_downloadRegionAction, SIGNAL(triggered()), SLOT(showDownloadRegionDialog()) ); m_printAction = new QAction(QIcon(QStringLiteral(":/icons/document-print.png")), tr("&Print..."), this); m_printAction->setShortcut(tr("Ctrl+P")); m_printAction->setStatusTip(tr("Print a screenshot of the map")); connect(m_printAction, SIGNAL(triggered()), this, SLOT(printMapScreenShot())); m_printPreviewAction = new QAction(QIcon(QStringLiteral(":/icons/document-print-preview.png")), tr("Print Previe&w ..."), this); m_printPreviewAction->setStatusTip(tr("Print a screenshot of the map")); connect(m_printPreviewAction, SIGNAL(triggered()), m_controlView, SLOT(printPreview())); m_quitAction = new QAction(QIcon(QStringLiteral(":/icons/application-exit.png")), tr("&Quit"), this); m_quitAction->setShortcut(tr("Ctrl+Q")); m_quitAction->setStatusTip(tr("Quit the Application")); connect(m_quitAction, SIGNAL(triggered()), this, SLOT(close())); m_copyMapAction = new QAction(QIcon(QStringLiteral(":/icons/edit-copy.png")), tr("&Copy Map"), this); m_copyMapAction->setShortcut(tr("Ctrl+C")); m_copyMapAction->setStatusTip(tr("Copy a screenshot of the map")); connect(m_copyMapAction, SIGNAL(triggered()), this, SLOT(copyMap())); m_osmEditAction = new QAction(QIcon(QStringLiteral(":/icons/edit-map.png")), tr("&Edit Map"), this ); m_osmEditAction->setShortcut(tr( "Ctrl+E" ) ); m_osmEditAction->setStatusTip(tr( "Edit the current map region in an external editor" ) ); updateMapEditButtonVisibility( m_controlView->marbleWidget()->mapThemeId() ); connect( m_osmEditAction, SIGNAL(triggered()), m_controlView, SLOT(launchExternalMapEditor()) ); m_recordMovieAction = new QAction(tr("&Record Movie"), this); m_recordMovieAction->setStatusTip(tr("Records a movie of the globe")); m_recordMovieAction->setShortcut(QKeySequence("Ctrl+Shift+R")); m_recordMovieAction->setIcon(QIcon(QStringLiteral(":/icons/animator.png"))); connect(m_recordMovieAction, SIGNAL(triggered()), this, SLOT(showMovieCaptureDialog())); m_stopRecordingAction = new QAction( tr("&Stop Recording"), this ); m_stopRecordingAction->setStatusTip( tr("Stop recording a movie of the globe") ); m_stopRecordingAction->setShortcut(QKeySequence( "Ctrl+Shift+S" )); m_stopRecordingAction->setEnabled( false ); connect( m_stopRecordingAction, SIGNAL(triggered()), this, SLOT(stopRecording()) ); m_configDialogAction = new QAction(QIcon(QStringLiteral(":/icons/settings-configure.png")), tr("&Configure Marble"), this); m_configDialogAction->setStatusTip(tr("Show the configuration dialog")); connect(m_configDialogAction, SIGNAL(triggered()), this, SLOT(editSettings())); m_copyCoordinatesAction = new QAction(QIcon(QStringLiteral(":/icons/copy-coordinates.png")), tr("C&opy Coordinates"), this); m_copyCoordinatesAction->setStatusTip(tr("Copy the center coordinates as text")); connect(m_copyCoordinatesAction, SIGNAL(triggered()), this, SLOT(copyCoordinates())); m_fullScreenAction = new QAction(QIcon(QStringLiteral(":/icons/view-fullscreen.png")), tr("&Full Screen Mode"), this); m_fullScreenAction->setShortcut(tr("Ctrl+Shift+F")); m_fullScreenAction->setCheckable( true ); m_fullScreenAction->setStatusTip(tr("Full Screen Mode")); connect(m_fullScreenAction, SIGNAL(triggered(bool)), this, SLOT(showFullScreen(bool))); m_statusBarAction = new QAction( tr("&Show Status Bar"), this); m_statusBarAction->setCheckable( true ); m_statusBarAction->setStatusTip(tr("Show Status Bar")); connect(m_statusBarAction, SIGNAL(triggered(bool)), this, SLOT(showStatusBar(bool))); m_lockFloatItemsAction = new QAction(QIcon(QStringLiteral(":/icons/unlock.png")), tr("Lock Position"), this); m_lockFloatItemsAction->setCheckable( true ); m_lockFloatItemsAction->setStatusTip(tr("Lock Position of Floating Items")); connect(m_lockFloatItemsAction, SIGNAL(triggered(bool)), this, SLOT(lockPosition(bool))); m_showCloudsAction = new QAction(QIcon(QStringLiteral(":/icons/clouds.png")), tr("&Clouds"), this); m_showCloudsAction->setCheckable( true ); m_showCloudsAction->setStatusTip(tr("Show Real Time Cloud Cover")); connect(m_showCloudsAction, SIGNAL(triggered(bool)), this, SLOT(showClouds(bool))); m_workOfflineAction = new QAction(QIcon(QStringLiteral(":/icons/user-offline.png")), tr("Work Off&line"), this); m_workOfflineAction->setCheckable( true ); connect(m_workOfflineAction, SIGNAL(triggered(bool)), this, SLOT(workOffline(bool))); m_controlTimeAction = new QAction(QIcon(QStringLiteral(":/icons/clock.png")), tr("&Time Control..."), this ); m_controlTimeAction->setStatusTip( tr( "Configure Time Control " ) ); connect( m_controlTimeAction, SIGNAL(triggered()), this, SLOT(controlTime()) ); m_controlSunAction = new QAction( tr( "S&un Control..." ), this ); m_controlSunAction->setStatusTip( tr( "Configure Sun Control" ) ); connect( m_controlSunAction, SIGNAL(triggered()), this, SLOT(controlSun()) ); m_reloadAction = new QAction(QIcon(QStringLiteral(":/icons/view-refresh.png")), tr("&Redisplay"), this); m_reloadAction->setShortcut(tr("F5")); m_reloadAction->setStatusTip(tr("Reload Current Map")); connect(m_reloadAction, SIGNAL(triggered()), this, SLOT(reload())); m_handbookAction = new QAction(QIcon(QStringLiteral(":/icons/help-contents.png")), tr("Marble Virtual Globe &Handbook"), this); m_handbookAction->setShortcut(tr("F1")); m_handbookAction->setStatusTip(tr("Show the Handbook for Marble Virtual Globe")); connect(m_handbookAction, SIGNAL(triggered()), this, SLOT(handbook())); m_whatsThisAction = new QAction(QIcon(QStringLiteral(":/icons/help-whatsthis.png")), tr("What's &This"), this); m_whatsThisAction->setShortcut(tr("Shift+F1")); m_whatsThisAction->setStatusTip(tr("Show a detailed explanation of the action.")); connect(m_whatsThisAction, SIGNAL(triggered()), this, SLOT(enterWhatsThis())); m_forumAction = new QAction( tr("&Community Forum"), this); m_forumAction->setStatusTip(tr("Visit Marble's Community Forum")); connect(m_forumAction, SIGNAL(triggered()), this, SLOT(openForum())); m_aboutMarbleAction = new QAction(QIcon(QStringLiteral(":/icons/marble.png")), tr("&About Marble Virtual Globe"), this); m_aboutMarbleAction->setStatusTip(tr("Show the application's About Box")); connect(m_aboutMarbleAction, SIGNAL(triggered()), this, SLOT(aboutMarble())); m_aboutQtAction = new QAction(tr("About &Qt"), this); m_aboutQtAction->setStatusTip(tr("Show the Qt library's About box")); connect(m_aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); //Bookmark Actions m_addBookmarkAction = new QAction(QIcon(QStringLiteral(":/icons/bookmark-new.png")), tr("&Add Bookmark"), this); m_addBookmarkAction->setShortcut(tr("Ctrl+B")); m_addBookmarkAction->setStatusTip(tr("Add Bookmark")); connect( m_addBookmarkAction, SIGNAL(triggered()), this, SLOT(openEditBookmarkDialog()) ); m_setHomeAction = new QAction(QIcon(QStringLiteral(":/icons/go-home.png")), tr("&Set Home Location"), this); m_setHomeAction->setStatusTip( tr( "&Set Home Location" ) ); connect( m_setHomeAction, SIGNAL(triggered()), this, SLOT(setHome()) ); m_toggleBookmarkDisplayAction = new QAction(tr( "Show &Bookmarks" ), this); m_toggleBookmarkDisplayAction->setStatusTip( tr( "Toggle display of Bookmarks" ) ); m_toggleBookmarkDisplayAction->setCheckable( true ); connect( m_toggleBookmarkDisplayAction, SIGNAL(triggered(bool)), this, SLOT(showBookmarks(bool)) ); m_manageBookmarksAction = new QAction(QIcon(QStringLiteral(":/icons/bookmarks-organize.png")), tr("&Manage Bookmarks"), this); m_manageBookmarksAction->setStatusTip( tr( "Manage Bookmarks" ) ); connect( m_manageBookmarksAction, SIGNAL(triggered()), this, SLOT(manageBookmarks()) ); // Map Wizard action m_mapWizardAction = new QAction(QIcon(QStringLiteral(":/icons/create-new-map.png")), tr("&Create a New Map..."), this); m_mapWizardAction->setStatusTip( tr( "A wizard guides you through the creation of your own map theme." ) ); connect( m_mapWizardAction, SIGNAL(triggered()), SLOT(showMapWizard()) ); // Statusbar Actions m_toggleTileLevelAction = new QAction( tr( "Show Tile Zoom Level" ), statusBar() ); m_toggleTileLevelAction->setCheckable( true ); m_toggleTileLevelAction->setChecked( false ); connect( m_toggleTileLevelAction, SIGNAL(triggered(bool)), this, SLOT(showZoomLevel(bool)) ); m_angleDisplayUnitActionGroup = new QActionGroup( statusBar() ); m_dmsDegreeAction = new QAction( tr( "Degree (DMS)" ), statusBar() ); m_dmsDegreeAction->setCheckable( true ); m_dmsDegreeAction->setData( (int)DMSDegree ); m_angleDisplayUnitActionGroup->addAction(m_dmsDegreeAction); m_decimalDegreeAction = new QAction( tr( "Degree (Decimal)" ), statusBar() ); m_decimalDegreeAction->setCheckable( true ); m_decimalDegreeAction->setData( (int)DecimalDegree ); m_angleDisplayUnitActionGroup->addAction(m_decimalDegreeAction); m_utmAction = new QAction( tr( "Universal Transverse Mercator (UTM)" ), statusBar() ); m_utmAction->setCheckable( true ); m_utmAction->setData( (int)UTM ); m_angleDisplayUnitActionGroup->addAction(m_utmAction); connect( m_angleDisplayUnitActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeAngleDisplayUnit(QAction*)) ); // View size actions m_viewSizeActsGroup = ControlView::createViewSizeActionGroup( this ); connect( m_viewSizeActsGroup, SIGNAL(triggered(QAction*)), this, SLOT(changeViewSize(QAction*)) ); } void MainWindow::createMenus( const QList &panelActions ) { m_fileMenu = menuBar()->addMenu(tr("&File")); m_fileMenu->addAction(m_openAction); m_fileMenu->addAction(m_downloadAction); m_fileMenu->addAction( m_downloadRegionAction ); m_fileMenu->addAction( m_mapWizardAction ); m_fileMenu->addAction(m_exportMapAction); m_fileMenu->addSeparator(); m_fileMenu->addAction(m_printAction); m_fileMenu->addAction(m_printPreviewAction); m_fileMenu->addSeparator(); m_fileMenu->addAction(m_workOfflineAction); m_fileMenu->addAction(m_quitAction); m_fileMenu = menuBar()->addMenu(tr("&Edit")); m_fileMenu->addAction(m_copyMapAction); m_fileMenu->addAction(m_copyCoordinatesAction); m_fileMenu->addAction( m_osmEditAction ); m_fileMenu->addSeparator(); m_fileMenu->addAction(m_recordMovieAction); m_fileMenu->addAction(m_stopRecordingAction); m_viewMenu = menuBar()->addMenu(tr("&View")); m_infoBoxesMenu = new QMenu(tr("&Info Boxes"), this); m_onlineServicesMenu = new QMenu(tr("&Online Services"), this); createPluginsMenus(); m_bookmarkMenu = menuBar()->addMenu(tr("&Bookmarks")); createBookmarkMenu(); connect( m_bookmarkMenu, SIGNAL(aboutToShow()), this, SLOT(createBookmarkMenu()) ); m_panelMenu = new QMenu(tr("&Panels"), this); for( QAction* action: panelActions ) { m_panelMenu->addAction( action ); } m_viewSizeMenu = new QMenu(tr("&View Size"), this); m_viewSizeMenu->addActions( m_viewSizeActsGroup->actions() ); m_settingsMenu = menuBar()->addMenu(tr("&Settings")); m_settingsMenu->addMenu( m_panelMenu ); m_settingsMenu->addAction(m_statusBarAction); m_settingsMenu->addSeparator(); m_settingsMenu->addMenu( m_viewSizeMenu ); m_settingsMenu->addAction(m_fullScreenAction); m_settingsMenu->addSeparator(); m_settingsMenu->addAction(m_configDialogAction); m_helpMenu = menuBar()->addMenu(tr("&Help")); m_helpMenu->addAction(m_handbookAction); m_helpMenu->addAction(m_forumAction); m_helpMenu->addSeparator(); m_helpMenu->addAction(m_whatsThisAction); m_helpMenu->addSeparator(); m_helpMenu->addAction(m_aboutMarbleAction); m_helpMenu->addAction(m_aboutQtAction); // FIXME: Discuss if this is the best place to put this QList pluginList = m_controlView->marbleWidget()->renderPlugins(); QList::const_iterator it = pluginList.constBegin(); QList::const_iterator const listEnd = pluginList.constEnd(); for (; it != listEnd; ++it ) { connect( (*it), SIGNAL(actionGroupsChanged()), this, SLOT(createPluginMenus()) ); } } void MainWindow::createPluginsMenus() { m_onlineServicesMenu->clear(); m_infoBoxesMenu->clear(); m_viewMenu->clear(); m_viewMenu->addAction(m_reloadAction); m_viewMenu->addSeparator(); // Do not create too many menu entries on a MID // FIXME: Set up another way of switching the plugins on and off. if( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { return; } m_infoBoxesMenu->addAction(m_lockFloatItemsAction); m_infoBoxesMenu->addSeparator(); QList themeActions; QList renderPluginList = m_controlView->marbleWidget()->renderPlugins(); QList::const_iterator i = renderPluginList.constBegin(); QList::const_iterator const end = renderPluginList.constEnd(); for (; i != end; ++i ) { switch( (*i)->renderType() ) { case RenderPlugin::TopLevelRenderType: m_viewMenu->addAction( (*i)->action() ); break; case RenderPlugin::PanelRenderType: m_infoBoxesMenu->addAction( (*i)->action() ); break; case RenderPlugin::OnlineRenderType: m_onlineServicesMenu->addAction( (*i)->action() ); break; case RenderPlugin::ThemeRenderType: themeActions.append( (*i)->action() ); break; default: mDebug() << "No menu entry created for plugin with unknown render type:" << (*i)->nameId(); break; } } m_viewMenu->addMenu( m_infoBoxesMenu ); m_viewMenu->addMenu( m_onlineServicesMenu ); m_viewMenu->addActions( themeActions ); m_viewMenu->addAction( m_showCloudsAction ); m_viewMenu->addSeparator(); m_viewMenu->addAction(m_controlSunAction); m_viewMenu->addAction(m_controlTimeAction); } void MainWindow::createBookmarksListMenu( QMenu *bookmarksListMenu, const GeoDataContainer *container ) { //m_bookmarksListMenu->clear(); QVector bookmarks = container->placemarkList(); for ( const GeoDataPlacemark *placemark: bookmarks ) { QAction *bookmarkAction = new QAction( placemark->name(), this ); QVariant var; const GeoDataLookAt* lookAt = placemark->lookAt(); if ( !lookAt ) { GeoDataCoordinates coordinates = placemark->coordinate(); GeoDataLookAt coordinateToLookAt; coordinateToLookAt.setCoordinates( coordinates ); coordinateToLookAt.setRange( marbleWidget()->lookAt().range() ); var.setValue( coordinateToLookAt ); } else { var.setValue( *lookAt ); } bookmarkAction->setData( var ); bookmarksListMenu->addAction( bookmarkAction ); } } void MainWindow::createBookmarkMenu() { m_bookmarkMenu->clear(); m_bookmarkMenu->addAction( m_addBookmarkAction ); m_bookmarkMenu->addAction( m_toggleBookmarkDisplayAction ); m_toggleBookmarkDisplayAction->setChecked( m_controlView->marbleModel()->bookmarkManager()->document()->isVisible() ); m_bookmarkMenu->addAction( m_setHomeAction ); m_bookmarkMenu->addAction( m_manageBookmarksAction ); m_bookmarkMenu->addSeparator(); m_bookmarkMenu->addAction( QIcon(QStringLiteral(":/icons/go-home.png")), tr("&Home"), m_controlView->marbleWidget(), SLOT(goHome()) ); createFolderList( m_bookmarkMenu, m_controlView->marbleModel()->bookmarkManager()->document() ); } void MainWindow::createFolderList( QMenu *bookmarksListMenu, const GeoDataContainer *container ) { QVector folders = container->folderList(); if ( folders.size() == 1 && folders.first()->name() == tr("Default")) { createBookmarksListMenu( bookmarksListMenu, folders.first() ); } else { for ( const GeoDataFolder *folder: folders ) { QMenu *subMenu = bookmarksListMenu->addMenu(QIcon(QStringLiteral(":/icons/folder-bookmark.png")), folder->name()); createFolderList( subMenu, folder ); connect( subMenu, SIGNAL(triggered(QAction*)), this, SLOT(lookAtBookmark(QAction*)) ); } } createBookmarksListMenu( bookmarksListMenu, container ); connect( bookmarksListMenu, SIGNAL(triggered(QAction*)), this, SLOT(lookAtBookmark(QAction*)) ); } void MainWindow::lookAtBookmark( QAction *action) { if ( action->data().isNull() ) { return; } GeoDataLookAt temp = qvariant_cast( action->data() ) ; m_controlView->marbleWidget()->flyTo( temp ) ; mDebug() << " looking at bookmark having longitude : "<< temp.longitude(GeoDataCoordinates::Degree) << " latitude : "<< temp.latitude(GeoDataCoordinates::Degree) << " distance : " << temp.range(); } void MainWindow::manageBookmarks() { MarbleModel * const model = m_controlView->marbleModel(); QPointer dialog = new BookmarkManagerDialog( model, this ); dialog->exec(); delete dialog; } void MainWindow::setHome() { MarbleWidget *widget = m_controlView->marbleWidget(); widget->model()->setHome( widget->centerLongitude(), widget->centerLatitude(), widget->zoom() ); } void MainWindow::openEditBookmarkDialog() { MarbleWidget *widget = m_controlView->marbleWidget(); QPointer dialog = new EditBookmarkDialog( widget->model()->bookmarkManager(), widget ); dialog->setMarbleWidget( widget ); dialog->setCoordinates( widget->lookAt().coordinates() ); dialog->setRange( widget->lookAt().range() ); dialog->setReverseGeocodeName(); if ( dialog->exec() == QDialog::Accepted ) { widget->model()->bookmarkManager()->addBookmark( dialog->folder(), dialog->bookmark() ); } delete dialog; } void MainWindow::createPluginMenus() { // Remove and delete toolbars if they exist while( !m_pluginToolbars.isEmpty() ) { QToolBar* tb = m_pluginToolbars.takeFirst(); this->removeToolBar(tb); tb->deleteLater(); } // Do not create too many menu entries on a MID // FIXME: Set up another way of switching the plugins on and off. if( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { return; } //remove and delete old menus if they exist while( !m_pluginMenus.isEmpty() ) { m_viewMenu->removeAction( m_pluginMenus.takeFirst() ); } QList renderPluginList = m_controlView->marbleWidget()->renderPlugins(); QList::const_iterator i = renderPluginList.constBegin(); QList::const_iterator const end = renderPluginList.constEnd(); for (; i != end; ++i ) { // menus const QList *tmp_actionGroups = (*i)->actionGroups(); if ((*i)->enabled() && tmp_actionGroups && (*i)->nameId() != QLatin1String("annotation")) { for( QActionGroup *ag: *tmp_actionGroups ) { if( !ag->actions().isEmpty() ) { m_pluginMenus.append( m_viewMenu->addSeparator() ); } for( QAction *action: ag->actions() ) { m_viewMenu->addAction( action ); m_pluginMenus.append( action ); } } } // toolbars const QList *tmp_toolbarActionGroups = (*i)->toolbarActionGroups(); if ( (*i)->enabled() && tmp_toolbarActionGroups ) { QToolBar* toolbar = new QToolBar(this); toolbar->setObjectName(QLatin1String("plugin-toolbar-") + (*i)->nameId()); for( QActionGroup* ag: *tmp_toolbarActionGroups ) { toolbar->addActions( ag->actions() ); if ( tmp_toolbarActionGroups->last() != ag ) { toolbar->addSeparator(); } } m_pluginToolbars.append( toolbar ); this->addToolBar( toolbar ); } } // FIXME: load the menus once the method has been settled on } void MainWindow::createStatusBar() { statusBar()->showMessage(tr("Ready")); statusBar()->hide(); } void MainWindow::openMapDialog() { QPointer dialog( new MapThemeDownloadDialog( m_controlView->marbleWidget() ) ); dialog->exec(); delete dialog; } void MainWindow::exportMapScreenShot() { QString fileName = QFileDialog::getSaveFileName(this, tr("Export Map"), // krazy:exclude=qclasses QDir::homePath(), tr("Images (*.jpg *.png)")); if ( !fileName.isEmpty() ) { // Take the case into account where no file format is indicated const char * format = nullptr; if ( !fileName.endsWith(QLatin1String( "png" ), Qt::CaseInsensitive) && !fileName.endsWith(QLatin1String( "jpg" ), Qt::CaseInsensitive) ) { format = "JPG"; } QPixmap mapPixmap = m_controlView->mapScreenShot(); bool success = mapPixmap.save( fileName, format ); if ( !success ) { QMessageBox::warning(this, tr("Marble"), // krazy:exclude=qclasses tr( "An error occurred while trying to save the file.\n" ), QMessageBox::Ok); } } } void MainWindow::showFullScreen( bool isChecked ) { if ( isChecked ) { setWindowState( windowState() | Qt::WindowFullScreen ); // set } else { setWindowState( windowState() & ~Qt::WindowFullScreen ); // reset } m_fullScreenAction->setChecked( isChecked ); // Sync state with the GUI } void MainWindow::copyCoordinates() { qreal lon = m_controlView->marbleWidget()->centerLongitude(); qreal lat = m_controlView->marbleWidget()->centerLatitude(); QString positionString = GeoDataCoordinates( lon, lat, 0.0, GeoDataCoordinates::Degree ).toString(); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText( positionString ); } void MainWindow::copyMap() { QPixmap mapPixmap = m_controlView->mapScreenShot(); QClipboard *clipboard = QApplication::clipboard(); clipboard->setPixmap( mapPixmap ); } void MainWindow::showStatusBar( bool isChecked ) { if ( isChecked ) { statusBar()->show(); } else { statusBar()->hide(); } m_statusBarAction->setChecked( isChecked ); // Sync state with the GUI } void MainWindow::showClouds( bool isChecked ) { m_controlView->marbleWidget()->setShowClouds( isChecked ); m_showCloudsAction->setChecked( isChecked ); // Sync state with the GUI } void MainWindow::showBookmarks( bool show ) { m_controlView->marbleModel()->bookmarkManager()->setShowBookmarks( show ); m_toggleBookmarkDisplayAction->setChecked( show ); // Sync state with the GUI } void MainWindow::workOffline( bool offline ) { m_controlView->setWorkOffline( offline ); m_workOfflineAction->setChecked( offline ); // Sync state with the GUI } void MainWindow::lockPosition( bool isChecked ) { QList floatItemList = m_controlView->marbleWidget()->floatItems(); QList::const_iterator i = floatItemList.constBegin(); QList::const_iterator const end = floatItemList.constEnd(); for (; i != end; ++i ) { // Locking one would suffice as it affects all. // Nevertheless go through all. (*i)->setPositionLocked(isChecked); } } void MainWindow::controlSun() { if (!m_sunControlDialog) { m_sunControlDialog = new SunControlWidget( m_controlView->marbleWidget(), this ); connect( m_sunControlDialog, SIGNAL(showSun(bool)), this, SLOT (showSun(bool)) ); } m_sunControlDialog->show(); m_sunControlDialog->raise(); m_sunControlDialog->activateWindow(); } void MainWindow::controlTime() { if ( !m_timeControlDialog ) { m_timeControlDialog = new TimeControlWidget( m_controlView->marbleModel()->clock() ); } /* m_timeControlDialog is a modeless dialog so that user may adjust time and interact with main application simultaneously.*/ m_timeControlDialog->show(); m_timeControlDialog->raise(); m_timeControlDialog->activateWindow(); } void MainWindow::showSun( bool active ) { m_controlView->marbleWidget()->setShowSunShading( active ); } void MainWindow::reload() { m_controlView->marbleWidget()->reloadMap(); } void MainWindow::enterWhatsThis() { QWhatsThis::enterWhatsThisMode(); } void MainWindow::aboutMarble() { MarbleAboutDialog dlg(this); dlg.setApplicationTitle( tr( "Marble Virtual Globe %1" ).arg( ControlView::applicationVersion() ) ); dlg.exec(); } void MainWindow::handbook() { const QString code = MarbleLocale::languageCode(); QUrl handbookLocation(QLatin1String("http://docs.kde.org/stable/") + code + QLatin1String("/kdeedu/marble/index.html")); // TODO: this logic seems broken. Should that check "code.isEmpty()" instead? // and how do we konw there is a doc for the code? if ( handbookLocation.isEmpty() ) handbookLocation = QUrl("http://docs.kde.org/stable/en/kdeedu/marble/index.html"); if( !QDesktopServices::openUrl( handbookLocation ) ) qDebug() << "URL not opened"; } void MainWindow::openForum() { QUrl forumLocation("https://forum.kde.org/viewforum.php?f=217"); if( !QDesktopServices::openUrl( forumLocation ) ) { mDebug() << "Failed to open URL " << forumLocation.toString(); } } void MainWindow::showPosition( const QString& position ) { m_position = position; updateStatusBar(); } void MainWindow::showDistance( const QString& distance ) { m_distance = distance; updateStatusBar(); } void MainWindow::showZoom( int zoom ) { m_zoom = QString::number( zoom ); updateStatusBar(); } void MainWindow::showDateTime() { m_clock = QLocale().toString( m_controlView->marbleModel()->clockDateTime().addSecs( m_controlView->marbleModel()->clockTimezone() ), QLocale::ShortFormat ); updateStatusBar(); } void MainWindow::updateStatusBar() { if ( m_positionLabel ) m_positionLabel->setText(tr("Position: %1").arg(m_position)); if ( m_distanceLabel ) m_distanceLabel->setText(tr("Altitude: %1").arg(m_distance)); if ( m_zoomLabel ) m_zoomLabel->setText(tr("Zoom: %1").arg(m_zoom)); if ( m_clockLabel ) m_clockLabel->setText(tr("Time: %1").arg(m_clock)); switch ( m_configDialog->angleUnit() ) { case DMSDegree: m_dmsDegreeAction->setChecked( true ); break; case DecimalDegree: m_decimalDegreeAction->setChecked( true ); break; case UTM: m_utmAction->setChecked( true ); break; } } void MainWindow::openFile() { const PluginManager *const pluginManager = m_controlView->marbleModel()->pluginManager(); QStringList allFileExtensions; QStringList filters; for ( const ParseRunnerPlugin *plugin: pluginManager->parsingRunnerPlugins() ) { if (plugin->nameId() == QLatin1String("Cache")) continue; const QStringList fileExtensions = plugin->fileExtensions().replaceInStrings( QRegExp( "^" ), "*." ); const QString filter = plugin->fileFormatDescription() + QLatin1String(" (") + fileExtensions.join(QLatin1Char(' ')) + QLatin1Char(')'); filters << filter; allFileExtensions << fileExtensions; } allFileExtensions.sort(); // sort since file extensions are visible under Windows const QString allFileTypes = tr("All Supported Files") + QLatin1String(" (") + allFileExtensions.join(QLatin1Char(' ')) + QLatin1Char(')'); filters.sort(); filters.prepend( allFileTypes ); const QString filter = filters.join( ";;" ); QStringList fileNames = QFileDialog::getOpenFileNames( this, tr( "Open File" ), m_lastFileOpenPath, filter ); if ( !fileNames.isEmpty() ) { const QString firstFile = fileNames.first(); m_lastFileOpenPath = QFileInfo( firstFile ).absolutePath(); } for( const QString &fileName: fileNames ) { m_controlView->marbleModel()->addGeoDataFile( fileName ); } } void MainWindow::setupStatusBar() { statusBar()->setSizeGripEnabled( true ); statusBar()->setContextMenuPolicy( Qt::ActionsContextMenu ); statusBar()->addAction( m_toggleTileLevelAction ); QMenu *angleDisplayUnitMenu = new QMenu(this); angleDisplayUnitMenu->addActions( m_angleDisplayUnitActionGroup->actions() ); QAction *angleDisplayUnitMenuAction = new QAction( tr("&Angle Display Unit"), statusBar() ); angleDisplayUnitMenuAction->setMenu( angleDisplayUnitMenu ); statusBar()->addAction( angleDisplayUnitMenuAction ); setupDownloadProgressBar(); m_positionLabel = new QLabel( ); m_positionLabel->setIndent( 5 ); // UTM syntax is used in the template string, as it is longer than the lon/lat one QString templatePositionString = tr("Position: %1").arg(QLatin1String(" 00Z 000000.00 m E, 00000000.00 m N_")); int maxPositionWidth = fontMetrics().boundingRect(templatePositionString).width() + 2 * m_positionLabel->margin() + 2 * m_positionLabel->indent(); m_positionLabel->setFixedWidth( maxPositionWidth ); statusBar()->addPermanentWidget ( m_positionLabel ); m_distanceLabel = new QLabel( ); m_distanceLabel->setIndent( 5 ); QString templateDistanceString = tr("Altitude: %1").arg(QLatin1String(" 00.000,0 mu")); int maxDistanceWidth = fontMetrics().boundingRect(templateDistanceString).width() + 2 * m_distanceLabel->margin() + 2 * m_distanceLabel->indent(); m_distanceLabel->setFixedWidth( maxDistanceWidth ); statusBar()->addPermanentWidget ( m_distanceLabel ); m_zoomLabel = new QLabel( ); m_zoomLabel->setIndent( 5 ); QString templateZoomString = tr("Zoom: %1").arg(QLatin1String(" 00")); int maxZoomWidth = fontMetrics().boundingRect(templateZoomString).width() + 2 * m_zoomLabel->margin() + 2 * m_zoomLabel->indent(); m_zoomLabel->setFixedWidth( maxZoomWidth ); // Not added here, but activated by the user with the context menu m_clockLabel = new QLabel( ); m_clockLabel->setIndent( 5 ); QString templateDateTimeString = tr("Time: %1").arg(QLocale().toString(QDateTime::fromString( "01:01:1000", "dd:mm:yyyy"), QLocale::ShortFormat)); int maxDateTimeWidth = fontMetrics().boundingRect( templateDateTimeString ).width() + 2 * m_clockLabel->margin() + 2 * m_clockLabel->indent(); m_clockLabel->setFixedWidth( maxDateTimeWidth ); statusBar()->addPermanentWidget ( m_clockLabel ); connect( marbleWidget(), SIGNAL(mouseMoveGeoPosition(QString)), this, SLOT(showPosition(QString)) ); connect( marbleWidget(), SIGNAL(distanceChanged(QString)), this, SLOT(showDistance(QString)) ); connect( marbleWidget(), SIGNAL(tileLevelChanged(int)), this, SLOT(showZoom(int)) ); connect( m_controlView->marbleModel()->clock(), SIGNAL(timeChanged()), this, SLOT(showDateTime()) ); updateStatusBar(); } void MainWindow::setupDownloadProgressBar() { m_downloadProgressBar = new QProgressBar; m_downloadProgressBar->setVisible( true ); statusBar()->addPermanentWidget( m_downloadProgressBar ); HttpDownloadManager * const downloadManager = m_controlView->marbleModel()->downloadManager(); Q_ASSERT( downloadManager ); connect( downloadManager, SIGNAL(progressChanged(int,int)), SLOT(handleProgress(int,int)) ); connect( downloadManager, SIGNAL(jobRemoved()), SLOT(removeProgressItem()) ); } void MainWindow::handleProgress( int active, int queued ){ m_downloadProgressBar->setUpdatesEnabled( false ); if ( m_downloadProgressBar->value() < 0 ) { m_downloadProgressBar->setMaximum( 1 ); m_downloadProgressBar->setValue( 0 ); m_downloadProgressBar->setVisible( true ); } else { m_downloadProgressBar->setMaximum( qMax( m_downloadProgressBar->maximum(), active + queued ) ); } m_downloadProgressBar->setUpdatesEnabled( true ); } void MainWindow::removeProgressItem(){ m_downloadProgressBar->setUpdatesEnabled( false ); m_downloadProgressBar->setValue( m_downloadProgressBar->value() + 1 ); if ( m_downloadProgressBar->value() == m_downloadProgressBar->maximum() ) { m_downloadProgressBar->reset(); m_downloadProgressBar->setVisible( false ); } m_downloadProgressBar->setUpdatesEnabled( true ); } void MainWindow::closeEvent( QCloseEvent *event ) { writeSettings(); QCloseEvent newEvent; QCoreApplication::sendEvent( m_controlView, &newEvent ); if ( newEvent.isAccepted() ) { event->accept(); } else { event->ignore(); } } QString MainWindow::readMarbleDataPath() { QSettings settings; settings.beginGroup("MarbleWidget"); const auto marbleDataPath = settings.value("marbleDataPath", "").toString(); settings.endGroup(); return marbleDataPath; } void MainWindow::readSettings(const QVariantMap& overrideSettings) { QSettings settings; settings.beginGroup("MainWindow"); resize(settings.value("size", QSize(640, 480)).toSize()); move(settings.value("pos", QPoint(200, 200)).toPoint()); showFullScreen(settings.value("fullScreen", false ).toBool()); showStatusBar(settings.value("statusBar", false ).toBool()); showZoomLevel(settings.value("showZoomLevel",false).toBool()); show(); showClouds(settings.value("showClouds", true ).toBool()); workOffline(settings.value("workOffline", false ).toBool()); m_controlView->marbleWidget()->setShowAtmosphere(settings.value("showAtmosphere", true ).toBool()); m_lastFileOpenPath = settings.value("lastFileOpenDir", QDir::homePath()).toString(); showBookmarks( settings.value( "showBookmarks", true ).toBool() ); restoreState( settings.value("windowState").toByteArray() ); settings.endGroup(); setUpdatesEnabled(false); settings.beginGroup("MarbleWidget"); QString mapThemeId; const QVariantMap::ConstIterator mapThemeIdIt = overrideSettings.find(QLatin1String("mapTheme")); if ( mapThemeIdIt != overrideSettings.constEnd() ) { mapThemeId = mapThemeIdIt.value().toString(); } else { mapThemeId = settings.value("mapTheme", m_controlView->defaultMapThemeId() ).toString(); } mDebug() << Q_FUNC_INFO << "mapThemeId:" << mapThemeId; m_controlView->marbleWidget()->setMapThemeId( mapThemeId ); m_controlView->marbleWidget()->setProjection( (Projection)(settings.value("projection", Spherical ).toInt()) ); // Set home position m_controlView->marbleModel()->setHome( settings.value("homeLongitude", 9.4).toDouble(), settings.value("homeLatitude", 54.8).toDouble(), settings.value("homeZoom", 1050 ).toInt() ); // Center on/Distance const QVariantMap::ConstIterator distanceIt = overrideSettings.find(QLatin1String("distance")); const bool isDistanceOverwritten = (distanceIt != overrideSettings.constEnd()); const QVariantMap::ConstIterator lonLatIt = overrideSettings.find(QLatin1String("lonlat")); if ( lonLatIt != overrideSettings.constEnd() ) { const QVariantList lonLat = lonLatIt.value().toList(); m_controlView->marbleWidget()->centerOn( lonLat.at(0).toDouble(), lonLat.at(1).toDouble() ); } else { switch ( m_configDialog->onStartup() ) { case Marble::LastLocationVisited: m_controlView->marbleWidget()->centerOn( settings.value("quitLongitude", 0.0).toDouble(), settings.value("quitLatitude", 0.0).toDouble() ); if (! isDistanceOverwritten) { // set default radius to 1350 (Atlas theme's "sharp" radius) m_controlView->marbleWidget()->setRadius( settings.value("quitRadius", 1350).toInt() ); } break; case Marble::ShowHomeLocation: m_controlView->marbleWidget()->goHome(); break; default: break; } } if (isDistanceOverwritten) { m_controlView->marbleWidget()->setDistance(distanceIt.value().toDouble()); } // Geo URI parsing QString geoUriString = qvariant_cast( overrideSettings.value("geo-uri", "")); if ( !geoUriString.isEmpty() ) { m_controlView->openGeoUri( geoUriString ); } bool isLocked = settings.value( "lockFloatItemPositions", false ).toBool(); m_lockFloatItemsAction->setChecked( isLocked ); lockPosition(isLocked); settings.endGroup(); settings.beginGroup( "Sun" ); m_controlView->marbleWidget()->setShowSunShading( settings.value( "showSun", false ).toBool() ); m_controlView->marbleWidget()->setShowCityLights( settings.value( "showCitylights", false ).toBool() ); m_controlView->marbleWidget()->setLockToSubSolarPoint( settings.value( "lockToSubSolarPoint", false ).toBool() ); m_controlView->marbleWidget()->setSubSolarPointIconVisible( settings.value( "subSolarPointIconVisible", false ).toBool() ); settings.endGroup(); settings.beginGroup( "Time" ); if( settings.value( "systemTime", "true" ).toBool() == true ) { /* nothing to do */ } else if( settings.value( "lastSessionTime", "true" ).toBool() == true ) { m_controlView->marbleModel()->setClockDateTime( settings.value( "dateTime" ).toDateTime() ); m_controlView->marbleModel()->setClockSpeed( settings.value( "speedSlider", 1 ).toInt() ); } settings.endGroup(); setUpdatesEnabled(true); // Load previous route settings settings.beginGroup( "Routing" ); { RoutingManager *const routingManager = m_controlView->marbleModel()->routingManager(); routingManager->readSettings(); bool const startupWarning = settings.value( "showGuidanceModeStartupWarning", QVariant( true ) ).toBool(); routingManager->setShowGuidanceModeStartupWarning( startupWarning ); routingManager->setLastOpenPath( settings.value( "lastRouteOpenPath", QDir::homePath() ).toString() ); routingManager->setLastSavePath( settings.value( "lastRouteSavePath", QDir::homePath() ).toString() ); QColor tempColor; tempColor = QColor( settings.value( "routeColorStandard", Oxygen::skyBlue4.name() ).toString() ); tempColor.setAlpha( settings.value( "routeAlphaStandard", 200 ).toInt() ); routingManager->setRouteColorStandard( tempColor ); tempColor = QColor( settings.value( "routeColorHighlighted", Oxygen::skyBlue1.name() ).toString() ); tempColor.setAlpha( settings.value( "routeAlphaHighlighted", 200 ).toInt() ); routingManager->setRouteColorHighlighted( tempColor ); tempColor = QColor( settings.value( "routeColorAlternative", Oxygen::aluminumGray4.name() ).toString() ); tempColor.setAlpha( settings.value( "routeAlphaAlternative", 200 ).toInt() ); routingManager->setRouteColorAlternative( tempColor ); } settings.endGroup(); settings.beginGroup( "Routing Profile" ); if ( settings.contains( "Num" ) ) { QList profiles; int numProfiles = settings.value( "Num", 0 ).toInt(); for ( int i = 0; i < numProfiles; ++i ) { settings.beginGroup( QString( "Profile %0" ).arg(i) ); QString name = settings.value( "Name", tr( "Unnamed" ) ).toString(); RoutingProfile profile( name ); for ( const QString& pluginName: settings.childGroups() ) { settings.beginGroup( pluginName ); profile.pluginSettings().insert( pluginName, QHash() ); for ( const QString& key: settings.childKeys() ) { if (key != QLatin1String("Enabled")) { profile.pluginSettings()[ pluginName ].insert( key, settings.value( key ) ); } } settings.endGroup(); } profiles << profile; settings.endGroup(); } m_controlView->marbleModel()->routingManager()->profilesModel()->setProfiles( profiles ); } else { m_controlView->marbleModel()->routingManager()->profilesModel()->loadDefaultProfiles(); } int const profileIndex = settings.value( "currentIndex", 0 ).toInt(); if ( profileIndex >= 0 && profileIndex < m_controlView->marbleModel()->routingManager()->profilesModel()->rowCount() ) { RoutingProfile profile = m_controlView->marbleModel()->routingManager()->profilesModel()->profiles().at( profileIndex ); m_controlView->marbleModel()->routingManager()->routeRequest()->setRoutingProfile( profile ); } settings.endGroup(); settings.beginGroup( "Plugins"); PositionTracking* tracking = m_controlView->marbleModel()->positionTracking(); tracking->readSettings(); QString positionProvider = settings.value( "activePositionTrackingPlugin", QString() ).toString(); if ( !positionProvider.isEmpty() ) { const PluginManager* pluginManager = m_controlView->marbleModel()->pluginManager(); for( const PositionProviderPlugin* plugin: pluginManager->positionProviderPlugins() ) { if ( plugin->nameId() == positionProvider ) { PositionProviderPlugin* instance = plugin->newInstance(); tracking->setPositionProviderPlugin( instance ); break; } } } settings.endGroup(); settings.beginGroup( "Tracking" ); if ( settings.contains( "autoCenter" ) || settings.contains( "recenterMode" ) ) { CurrentLocationWidget* trackingWidget = m_controlView->currentLocationWidget(); Q_ASSERT( trackingWidget ); trackingWidget->setRecenterMode( settings.value( "recenterMode", 0 ).toInt() ); trackingWidget->setAutoZoom( settings.value( "autoZoom", false ).toBool() ); trackingWidget->setTrackVisible( settings.value( "trackVisible", true ).toBool() ); trackingWidget->setLastOpenPath( settings.value( "lastTrackOpenPath", QDir::homePath() ).toString() ); trackingWidget->setLastSavePath( settings.value( "lastTrackSavePath", QDir::homePath() ).toString() ); } settings.endGroup(); // The config dialog has to read settings. m_configDialog->readSettings(); settings.beginGroup( "Navigation" ); m_controlView->setExternalMapEditor( settings.value( "externalMapEditor", "" ).toString() ); settings.endGroup(); settings.beginGroup( "CloudSync" ); CloudSyncManager* cloudSyncManager = m_controlView->cloudSyncManager(); cloudSyncManager->setOwncloudCredentials( settings.value( "owncloudServer", "" ).toString(), settings.value( "owncloudUsername", "" ).toString(), settings.value( "owncloudPassword", "" ).toString() ); cloudSyncManager->setSyncEnabled( settings.value( "enableSync", false ).toBool() ); cloudSyncManager->routeSyncManager()->setRouteSyncEnabled( settings.value( "syncRoutes", true ).toBool() ); cloudSyncManager->bookmarkSyncManager()->setBookmarkSyncEnabled( settings.value( "syncBookmarks", true ).toBool() ); settings.endGroup(); } void MainWindow::writeSettings() { QSettings settings; settings.beginGroup( "MainWindow" ); settings.setValue( "size", size() ); settings.setValue( "pos", pos() ); settings.setValue( "fullScreen", m_fullScreenAction->isChecked() ); settings.setValue( "statusBar", m_statusBarAction->isChecked() ); settings.setValue( "showZoomLevel", m_toggleTileLevelAction->isChecked() ); settings.setValue( "showClouds", m_showCloudsAction->isChecked() ); settings.setValue( "workOffline", m_workOfflineAction->isChecked() ); settings.setValue( "showAtmosphere", m_controlView->marbleWidget()->showAtmosphere() ); settings.setValue( "lastFileOpenDir", m_lastFileOpenPath ); settings.setValue( "showBookmarks", m_toggleBookmarkDisplayAction->isChecked() ); settings.setValue( "windowState", saveState() ); settings.endGroup(); settings.beginGroup( "MarbleWidget" ); // Get the 'home' values from the widget and store them in the settings. qreal homeLon = 0; qreal homeLat = 0; int homeZoom = 0; m_controlView->marbleModel()->home( homeLon, homeLat, homeZoom ); QString mapTheme = m_controlView->marbleWidget()->mapThemeId(); int projection = (int)( m_controlView->marbleWidget()->projection() ); settings.setValue( "homeLongitude", homeLon ); settings.setValue( "homeLatitude", homeLat ); settings.setValue( "homeZoom", homeZoom ); settings.setValue( "mapTheme", mapTheme ); settings.setValue( "projection", projection ); // Get the 'quit' values from the widget and store them in the settings. qreal quitLon = m_controlView->marbleWidget()->centerLongitude(); qreal quitLat = m_controlView->marbleWidget()->centerLatitude(); const int quitRadius = m_controlView->marbleWidget()->radius(); settings.setValue( "quitLongitude", quitLon ); settings.setValue( "quitLatitude", quitLat ); settings.setValue( "quitRadius", quitRadius ); settings.setValue( "lockFloatItemPositions", m_lockFloatItemsAction->isChecked() ); settings.endGroup(); settings.beginGroup( "Sun" ); settings.setValue( "showSun", m_controlView->marbleWidget()->showSunShading() ); settings.setValue( "showCitylights", m_controlView->marbleWidget()->showCityLights() ); settings.setValue( "lockToSubSolarPoint", m_controlView->marbleWidget()->isLockedToSubSolarPoint() ); settings.setValue( "subSolarPointIconVisible", m_controlView->marbleWidget()->isSubSolarPointIconVisible() ); settings.endGroup(); settings.beginGroup( "Time" ); settings.setValue( "dateTime", m_controlView->marbleModel()->clockDateTime() ); settings.setValue( "speedSlider", m_controlView->marbleModel()->clockSpeed() ); settings.endGroup(); settings.beginGroup( "Routing Profile" ); QList profiles = m_controlView->marbleWidget() ->model()->routingManager()->profilesModel()->profiles(); settings.setValue( "Num", profiles.count() ); for ( int i = 0; i < profiles.count(); ++i ) { settings.beginGroup( QString( "Profile %0" ).arg(i) ); const RoutingProfile& profile = profiles.at( i ); settings.setValue( "Name", profile.name() ); for ( const QString& pluginName: settings.childGroups() ) { settings.beginGroup( pluginName ); settings.remove(QString()); //remove all keys settings.endGroup(); } for ( const QString &key: profile.pluginSettings().keys() ) { settings.beginGroup( key ); settings.setValue( "Enabled", true ); for ( const QString& settingKey: profile.pluginSettings()[ key ].keys() ) { Q_ASSERT(settingKey != QLatin1String("Enabled")); settings.setValue( settingKey, profile.pluginSettings()[ key ][ settingKey ] ); } settings.endGroup(); } settings.endGroup(); } RoutingProfile const profile = m_controlView->marbleWidget()->model()->routingManager()->routeRequest()->routingProfile(); settings.setValue( "currentIndex", profiles.indexOf( profile ) ); settings.endGroup(); settings.beginGroup( "Plugins"); QString positionProvider; PositionTracking* tracking = m_controlView->marbleModel()->positionTracking(); tracking->writeSettings(); if ( tracking->positionProviderPlugin() ) { positionProvider = tracking->positionProviderPlugin()->nameId(); } settings.setValue( "activePositionTrackingPlugin", positionProvider ); settings.endGroup(); settings.beginGroup( "Tracking" ); CurrentLocationWidget* trackingWidget = m_controlView->currentLocationWidget(); if ( trackingWidget ) { // Can be null due to lazy initialization settings.setValue( "recenterMode", trackingWidget->recenterMode() ); settings.setValue( "autoZoom", trackingWidget->autoZoom() ); settings.setValue( "trackVisible", trackingWidget->trackVisible() ); settings.setValue( "lastTrackOpenPath", trackingWidget->lastOpenPath() ); settings.setValue( "lastTrackSavePath", trackingWidget->lastSavePath() ); } settings.endGroup(); // The config dialog has to write settings. m_configDialog->writeSettings(); // Store current route settings settings.beginGroup( "Routing" ); { RoutingManager *const routingManager = m_controlView->marbleModel()->routingManager(); routingManager->writeSettings(); settings.setValue( "showGuidanceModeStartupWarning", routingManager->showGuidanceModeStartupWarning() ); settings.setValue( "lastRouteOpenPath", routingManager->lastOpenPath() ); settings.setValue( "lastRouteSavePath", routingManager->lastSavePath() ); settings.setValue( "routeColorStandard", routingManager->routeColorStandard().name() ); settings.setValue( "routeAlphaStandard", routingManager->routeColorStandard().alpha() ); settings.setValue( "routeColorHighlighted", routingManager->routeColorHighlighted().name() ); settings.setValue( "routeAlphaHighlighted", routingManager->routeColorHighlighted().alpha() ); settings.setValue( "routeColorAlternative", routingManager->routeColorAlternative().name() ); settings.setValue( "routeAlphaAlternative", routingManager->routeColorAlternative().alpha() ); } settings.endGroup(); settings.beginGroup( "Navigation"); settings.setValue( "externalMapEditor", m_controlView->externalMapEditor() ); settings.endGroup(); } void MainWindow::editSettings() { // Show the settings dialog. m_configDialog->show(); m_configDialog->raise(); m_configDialog->activateWindow(); } void MainWindow::updateSettings() { mDebug() << Q_FUNC_INFO << "Updating Settings ..."; // FIXME: Font doesn't get updated instantly. m_controlView->marbleWidget()->setDefaultFont( m_configDialog->mapFont() ); m_controlView->marbleWidget()->setMapQualityForViewContext( m_configDialog->stillQuality(), Marble::Still ); m_controlView->marbleWidget()->setMapQualityForViewContext( m_configDialog->animationQuality(), Marble::Animation ); m_controlView->marbleWidget()->setDefaultAngleUnit( m_configDialog->angleUnit() ); MarbleGlobal::getInstance()->locale()->setMeasurementSystem( m_configDialog->measurementSystem() ); m_distance = m_controlView->marbleWidget()->distanceString(); updateStatusBar(); m_controlView->marbleWidget()->setAnimationsEnabled( m_configDialog->animateTargetVoyage() ); m_controlView->marbleWidget()->inputHandler()->setInertialEarthRotationEnabled( m_configDialog->inertialEarthRotation() ); if ( !m_configDialog->externalMapEditor().isEmpty() ) { m_controlView->setExternalMapEditor( m_configDialog->externalMapEditor() ); } // Cache m_controlView->marbleModel()->setPersistentTileCacheLimit( m_configDialog->persistentTileCacheLimit() * 1024 ); m_controlView->marbleWidget()->setVolatileTileCacheLimit( m_configDialog->volatileTileCacheLimit() * 1024 ); /* m_controlView->marbleWidget()->setProxy( m_configDialog->proxyUrl(), m_configDialog->proxyPort(), m_configDialog->user(), m_configDialog->password() ); */ CloudSyncManager* cloudSyncManager = m_controlView->cloudSyncManager(); cloudSyncManager->setOwncloudCredentials( m_configDialog->owncloudServer(), m_configDialog->owncloudUsername(), m_configDialog->owncloudPassword() ); cloudSyncManager->setSyncEnabled( m_configDialog->syncEnabled() ); cloudSyncManager->routeSyncManager()->setRouteSyncEnabled( m_configDialog->syncRoutes() ); cloudSyncManager->bookmarkSyncManager()->setBookmarkSyncEnabled( m_configDialog->syncBookmarks() ); m_controlView->marbleWidget()->update(); } void MainWindow::showDownloadRegionDialog() { if ( !m_downloadRegionDialog ) { m_downloadRegionDialog = new DownloadRegionDialog( m_controlView->marbleWidget(), m_controlView ); // it might be tempting to move the connects to DownloadRegionDialog's "accepted" and // "applied" signals, be aware that the "hidden" signal might be come before the "accepted" // signal, leading to a too early disconnect. connect( m_downloadRegionDialog, SIGNAL(accepted()), SLOT(downloadRegion())); connect( m_downloadRegionDialog, SIGNAL(applied()), SLOT(downloadRegion())); } // FIXME: get allowed range from current map theme m_downloadRegionDialog->setAllowedTileLevelRange( 0, 16 ); m_downloadRegionDialog->setSelectionMethod( DownloadRegionDialog::VisibleRegionMethod ); ViewportParams const * const viewport = m_controlView->marbleWidget()->viewport(); m_downloadRegionDialog->setSpecifiedLatLonAltBox( viewport->viewLatLonAltBox() ); m_downloadRegionDialog->setVisibleLatLonAltBox( viewport->viewLatLonAltBox() ); m_downloadRegionDialog->show(); m_downloadRegionDialog->raise(); m_downloadRegionDialog->activateWindow(); } void MainWindow::downloadRegion() { Q_ASSERT( m_downloadRegionDialog ); QVector const pyramid = m_downloadRegionDialog->region(); if ( !pyramid.isEmpty() ) { m_controlView->marbleWidget()->downloadRegion( pyramid ); } } void MainWindow::printMapScreenShot() { #ifndef QT_NO_PRINTER QPrinter printer( QPrinter::HighResolution ); QPointer printDialog = new QPrintDialog( &printer, this ); m_controlView->printMapScreenShot( printDialog ); delete printDialog; #endif } void MainWindow::updateMapEditButtonVisibility( const QString &mapTheme ) { Q_ASSERT( m_osmEditAction ); QStringList osmThemes = QStringList() << "earth/openstreetmap/openstreetmap.dgml" << "earth/hikebikemap/hikebikemap.dgml" << "earth/opencyclemap/opencyclemap.dgml" << "earth/public-transport/public-transport.dgml" << "earth/openseamap/openseamap.dgml" << "earth/vectorosm/vectorosm.dgml"; m_osmEditAction->setVisible(osmThemes.contains(mapTheme)); } void MainWindow::showMovieCaptureDialog() { if (m_movieCaptureDialog == nullptr) { m_movieCaptureDialog = new MovieCaptureDialog(m_controlView->marbleWidget(), m_controlView->marbleWidget()); connect( m_movieCaptureDialog, SIGNAL(started()), this, SLOT(changeRecordingState())); } m_movieCaptureDialog->show(); } void MainWindow::stopRecording() { if ( m_movieCaptureDialog ) { m_movieCaptureDialog->stopRecording(); changeRecordingState(); } } void MainWindow::changeRecordingState() { m_recordMovieAction->setEnabled( !m_recordMovieAction->isEnabled() ); m_stopRecordingAction->setEnabled( !m_stopRecordingAction->isEnabled() ); } void MainWindow::updateWindowTitle() { GeoSceneDocument *theme = m_controlView->marbleModel()->mapTheme(); setWindowTitle(theme ? theme->head()->name() : QString()); } void MainWindow::showMapWizard() { QPointer mapWizard = new MapWizard(); QSettings settings; settings.beginGroup( "MapWizard" ); mapWizard->setWmsServers( settings.value( "wmsServers" ).toStringList() ); mapWizard->setStaticUrlServers( settings.value( "staticUrlServers" ).toStringList() ); settings.endGroup(); mapWizard->exec(); settings.beginGroup( "MapWizard" ); settings.setValue( "wmsServers", mapWizard->wmsServers() ); settings.setValue( "staticUrlServers", mapWizard->staticUrlServers() ); settings.endGroup(); mapWizard->deleteLater(); } void MainWindow::showZoomLevel(bool show) { if ( show ) { m_zoomLabel->show(); statusBar()->insertPermanentWidget( 2, m_zoomLabel ); } else { statusBar()->removeWidget( m_zoomLabel ); } // update from last modification m_toggleTileLevelAction->setChecked( show ); } void MainWindow::changeAngleDisplayUnit( QAction *action ) { m_configDialog->setAngleUnit((Marble::AngleUnit)action->data().toInt()); } void MainWindow::fallBackToDefaultTheme() { m_controlView->marbleWidget()->setMapThemeId( m_controlView->defaultMapThemeId() ); } void MainWindow::changeViewSize( QAction* action ) { if ( action->data().type() == QVariant::Size ) { if ( m_savedSize.isEmpty() ) { m_savedSize = m_controlView->size(); } m_controlView->setFixedSize( action->data().toSize() ); adjustSize(); } else { m_controlView->setMinimumSize( QSize( 0, 0 ) ); m_controlView->setMaximumSize( QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX ) ); m_controlView->resize( m_savedSize ); m_controlView->setMinimumSize( m_savedSize ); adjustSize(); m_controlView->setMinimumSize( QSize( 0, 0 ) ); m_savedSize.setHeight( -1 ); } } #include "moc_QtMainWindow.cpp" diff --git a/src/lib/marble/MarbleGlobal.h b/src/lib/marble/MarbleGlobal.h index f138baeba..4cedd138b 100644 --- a/src/lib/marble/MarbleGlobal.h +++ b/src/lib/marble/MarbleGlobal.h @@ -1,309 +1,308 @@ // // 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-2009 Torsten Rahn // Copyright 2007 Inge Wallin // #ifndef MARBLE_GLOBAL_H #define MARBLE_GLOBAL_H #include #include #include "marble_export.h" -#include "MarbleColors.h" // #define QT_STRICT_ITERATORS /* M_PI is a #define that may or may not be handled in */ #ifndef M_PI #define M_PI 3.14159265358979323846264338327950288419717 #endif namespace Marble { enum TessellationFlag { NoTessellation = 0x0, Tessellate = 0x1, RespectLatitudeCircle = 0x2, FollowGround = 0x4, PreventNodeFiltering = 0x8 }; Q_DECLARE_FLAGS(TessellationFlags, TessellationFlag) /** * @brief This enum is used to choose the projection shown in the view. */ enum Projection { Spherical, ///< Spherical projection ("Orthographic") Equirectangular, ///< Flat projection ("plate carree") Mercator, ///< Mercator projection Gnomonic, ///< Gnomonic projection Stereographic, ///< Stereographic projection LambertAzimuthal, ///< Lambert Azimuthal Equal-Area projection AzimuthalEquidistant, ///< Azimuthal Equidistant projection VerticalPerspective ///< Vertical perspective projection // NOTE: MarbleWidget::setProjection(int) relies on VerticalPerspective being the last // value above. Adjust that method if you do changes here }; /** * @brief This enum is used to choose the unit chosen to measure angles. */ enum AngleUnit { DMSDegree, ///< Degrees in DMS notation DecimalDegree, ///< Degrees in decimal notation UTM ///< UTM }; /** * @brief This enum is used to choose context in which map quality gets used. */ enum ViewContext { Still, ///< still image Animation ///< animated view (e.g. while rotating the globe) }; /** * @brief This enum is used to choose the map quality shown in the view. */ enum MapQuality { OutlineQuality, ///< Only a wire representation is drawn LowQuality, ///< Low resolution (e.g. interlaced) NormalQuality, ///< Normal quality HighQuality, ///< High quality (e.g. antialiasing for lines) PrintQuality ///< Print quality }; /** * @brief This enum is used to specify the proxy that is used. */ enum ProxyType { HttpProxy, ///< Uses an Http proxy Socks5Proxy ///< Uses a Socks5Proxy }; /** * @brief This enum is used to choose the localization of the labels. */ enum LabelPositionFlag { NoLabel = 0x0, LineStart = 0x1, LineCenter = 0x2, LineEnd = 0x4, IgnoreXMargin = 0x8, IgnoreYMargin = 0x10, FollowLine = 0x20 }; Q_DECLARE_FLAGS(LabelPositionFlags, LabelPositionFlag) /** * @brief This enum is used to choose the localization of the labels. */ enum LabelLocalization { CustomAndNative, ///< Custom and native labels Custom, ///< Shows the name in the user's language Native ///< Display the name in the official language and /// glyphs of the labeled place. }; /** * @brief This enum is used to choose how the globe behaves while dragging. */ enum DragLocation { KeepAxisVertically, ///< Keep planet axis vertically FollowMousePointer ///< Follow mouse pointer exactly }; /** * @brief This enum is used to choose how the globe behaves while dragging. */ enum OnStartup { ShowHomeLocation, ///< Show home location on startup LastLocationVisited ///< Show last location visited on quit }; enum AltitudeMode { ClampToGround, ///< Altitude always sticks to ground level RelativeToGround, ///< Altitude is always given relative to ground level Absolute, ///< Altitude is given relative to the sealevel RelativeToSeaFloor, ///< Altitude is given relative to the sea floor ClampToSeaFloor ///< Altitude always sticks to sea floor }; enum Pole { AnyPole, ///< Any pole NorthPole, ///< Only North Pole SouthPole ///< Only South Pole }; /** * @brief This enum is used to describe the type of download */ enum DownloadUsage { DownloadBulk, ///< Bulk download, for example "File/Download region" DownloadBrowse ///< Browsing mode, normal operation of Marble, like a web browser }; /** * @brief Describes possible flight mode (interpolation between source * and target camera positions) */ enum FlyToMode { Automatic, ///< A sane value is chosen automatically depending on animation settings and the action Instant, ///< Change camera position immediately (no interpolation) Linear, ///< Linear interpolation of lon, lat and distance to ground Jump ///< Linear interpolation of lon and lat, distance increases towards the middle point, then decreases }; /** * @brief Search mode: Global (worldwide) versus area (local, regional) search */ enum SearchMode { GlobalSearch, ///< Search a whole planet AreaSearch ///< Search a certain region of a planet (e.g. visible region) }; /** * @brief */ enum RenderStatus { Complete, ///< All data is there and up to date WaitingForUpdate, ///< Rendering is based on complete, but outdated data, data update was requested WaitingForData, ///< Rendering is based on no or partial data, more data was requested (e.g. pending network queries) Incomplete ///< Data is missing and some error occurred when trying to retrieve it (e.g. network failure) }; const int defaultLevelZeroColumns = 2; const int defaultLevelZeroRows = 1; // Conversion Metric / Imperial System: km vs. miles const qreal MI2KM = 1.609344; const qreal KM2MI = 1.0 / MI2KM; // Conversion Nautical / Imperial System: nm vs. km const qreal NM2KM = 1.852; const qreal KM2NM = 1.0 / NM2KM; const qreal NM2FT = 6080; // nm feet // Conversion Metric / Imperial System: meter vs. feet const qreal M2FT = 3.2808; const qreal FT2M = 1.0 / M2FT; // Conversion Metric / Imperial System: meter vs inch const qreal M2IN = 39.3701; const qreal IN2M = 1.0 / M2IN; // Interconversion between Imperial System: feet vs inch const qreal FT2IN = 12.0; // Conversion Metric / Imperial System: meter vs yard const qreal M2YD = 1.09361; const qreal YD2M = 1.0 / M2YD; // Conversion meter vs millimeter const qreal M2MM = 1000.0; const qreal MM2M = 1.0 / M2MM; // Conversion meter vs centimeter const qreal M2CM = 100.0; const qreal CM2M = 1.0 / M2CM; // Conversion degree vs. radians const qreal DEG2RAD = M_PI / 180.0; const qreal RAD2DEG = 180.0 / M_PI; // Conversion meter vs kilometer const qreal KM2METER = 1000.0; const qreal METER2KM = 1.0 / KM2METER; //Conversion hour vs minute const qreal HOUR2MIN = 60.0; const qreal MIN2HOUR = 1.0 / HOUR2MIN; //Conversion (time) minute vs second const qreal MIN2SEC = 60.0; const qreal SEC2MIN = 1.0 / MIN2SEC; //Conversion hour vs second const qreal HOUR2SEC = 3600.0; const qreal SEC2HOUR = 1.0 / HOUR2SEC; const qreal TWOPI = 2 * M_PI; // Version definitions to use with an external application (as digiKam) // String for about dialog and http user agent // FIXME: check if blanks are allowed in user agent version numbers const QString MARBLE_VERSION_STRING = QString::fromLatin1( "0.27.20 (0.28 development version)" ); // API Version id: // form : 0xMMmmpp // MM = major revision. // mm = minor revision. // pp = patch revision. #define MARBLE_VERSION 0x001b14 static const char NOT_AVAILABLE[] = QT_TRANSLATE_NOOP("Marble", "not available"); const int tileDigits = 6; // Average earth radius in m // Deprecated: Please use model()->planetRadius() instead. const qreal EARTH_RADIUS = 6378137.0; // Maximum level of base tiles const int maxBaseTileLevel = 4; // Default size (width and height) of tiles const unsigned int c_defaultTileSize = 675; class MarbleGlobalPrivate; class MarbleLocale; class MARBLE_EXPORT MarbleGlobal { public: static MarbleGlobal * getInstance(); ~MarbleGlobal(); MarbleLocale * locale() const; enum Profile { Default = 0x0, SmallScreen = 0x1, HighResolution = 0x2 }; Q_DECLARE_FLAGS( Profiles, Profile ) Profiles profiles() const; void setProfiles( Profiles profiles ); /** @deprecated Profiles are detected automatically now. This only returns profiles() anymore */ MARBLE_DEPRECATED static Profiles detectProfiles(); private: MarbleGlobal(); Q_DISABLE_COPY( MarbleGlobal ) MarbleGlobalPrivate * const d; }; } Q_DECLARE_OPERATORS_FOR_FLAGS( Marble::TessellationFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Marble::LabelPositionFlags ) Q_DECLARE_OPERATORS_FOR_FLAGS( Marble::MarbleGlobal::Profiles ) #endif diff --git a/src/lib/marble/PlanetFactory.cpp b/src/lib/marble/PlanetFactory.cpp index 7f68de1ec..b19a31c2e 100644 --- a/src/lib/marble/PlanetFactory.cpp +++ b/src/lib/marble/PlanetFactory.cpp @@ -1,266 +1,267 @@ // // 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 Henry de Valence // Copyright 2009 David Roberts // Copyright 2012 Mohammed Nafees // Copyright 2014 Dennis Nienhüser #include "PlanetFactory.h" #include "Planet.h" +#include "MarbleColors.h" #include "MarbleDebug.h" #include "MarbleGlobal.h" namespace Marble { QList PlanetFactory::planetList() { QStringList planets; planets << "mercury" << "venus" << "earth" << "mars" << "jupiter" << "saturn" << "uranus" << "neptune" << "pluto" << "sun" << "moon" << "sky"; return planets; } Planet PlanetFactory::construct( const QString &id ) { Planet planet; planet.setId( id ); // constants taken from http://aa.quae.nl/en/reken/zonpositie.html if (id == QLatin1String("mercury")) { planet.setM_0( 174.7948*DEG2RAD ); planet.setM_1( 4.09233445*DEG2RAD ); planet.setC_1( 23.4400*DEG2RAD ); planet.setC_2( 2.9818*DEG2RAD ); planet.setC_3( 0.5255*DEG2RAD ); planet.setC_4( 0.1058*DEG2RAD ); planet.setC_5( 0.0241*DEG2RAD ); planet.setC_6( 0.0055*DEG2RAD ); planet.setPi( 111.5943*DEG2RAD ); planet.setEpsilon( 0.02*DEG2RAD ); planet.setTheta_0( 13.5964*DEG2RAD ); planet.setTheta_1( 6.1385025*DEG2RAD ); planet.setRadius( 2440000.0 ); planet.setName(QStringLiteral("Mercury")); planet.setHasAtmosphere( false ); } else if (id == QLatin1String("venus")) { planet.setM_0( 50.4161*DEG2RAD ); planet.setM_1( 1.60213034*DEG2RAD ); planet.setC_1( 0.7758*DEG2RAD ); planet.setC_2( 0.0033*DEG2RAD ); planet.setC_3( 0 ); planet.setC_4( 0 ); planet.setC_5( 0 ); planet.setC_6( 0 ); planet.setPi( 73.9519*DEG2RAD ); planet.setEpsilon( 2.64*DEG2RAD ); planet.setTheta_0( 215.2995*DEG2RAD ); planet.setTheta_1( -1.4813688*DEG2RAD ); planet.setRadius( 6051800.0 ); planet.setTwilightZone(18*DEG2RAD); planet.setName(QStringLiteral("Venus")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Oxygen::sunYellow4 ); } else if (id == QLatin1String("earth")) { planet.setM_0( 357.5291*DEG2RAD ); planet.setM_1( 0.98560028*DEG2RAD ); planet.setC_1( 1.9148*DEG2RAD ); planet.setC_2( 0.0200*DEG2RAD ); planet.setC_3( 0.0003*DEG2RAD ); planet.setC_4( 0 ); planet.setC_5( 0 ); planet.setC_6( 0 ); planet.setPi( 102.9372*DEG2RAD ); planet.setEpsilon( 23.43686*DEG2RAD ); planet.setTheta_0( 280.1600*DEG2RAD ); planet.setTheta_1( 360.9856235*DEG2RAD ); planet.setRadius( 6378137.0 ); planet.setTwilightZone(18*DEG2RAD); planet.setName(QStringLiteral("Earth")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Qt::white ); } else if (id == QLatin1String("mars")) { planet.setM_0( 19.3730*DEG2RAD ); planet.setM_1( 0.52402068*DEG2RAD ); planet.setC_1( 10.6912*DEG2RAD ); planet.setC_2( 0.6228*DEG2RAD ); planet.setC_3( 0.0503*DEG2RAD ); planet.setC_4( 0.0046*DEG2RAD ); planet.setC_5( 0.0005*DEG2RAD ); planet.setC_6( 0 ); planet.setPi( 70.9812*DEG2RAD ); planet.setEpsilon( 25.19*DEG2RAD ); planet.setTheta_0( 313.4803*DEG2RAD ); planet.setTheta_1( 350.89198226*DEG2RAD ); planet.setRadius( 3397000.0 ); planet.setTwilightZone(9.0*DEG2RAD); planet.setName(QStringLiteral("Mars")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Oxygen::hotOrange2 ); } else if (id == QLatin1String("jupiter")) { planet.setM_0( 20.0202*DEG2RAD ); planet.setM_1( 0.08308529*DEG2RAD ); planet.setC_1( 5.5549*DEG2RAD ); planet.setC_2( 0.1683*DEG2RAD ); planet.setC_3( 0.0071*DEG2RAD ); planet.setC_4( 0.0003*DEG2RAD ); planet.setC_5( 0 ); planet.setC_6( 0 ); planet.setPi( 237.2074*DEG2RAD ); planet.setEpsilon( 3.12*DEG2RAD ); planet.setTheta_0( 146.0727*DEG2RAD ); planet.setTheta_1( 870.5366420*DEG2RAD ); planet.setRadius( 71492000.0 ); planet.setName(QStringLiteral("Jupiter")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Oxygen::sunYellow2 ); } else if (id == QLatin1String("saturn")) { planet.setM_0( 317.0207*DEG2RAD ); planet.setM_1( 0.03344414*DEG2RAD ); planet.setC_1( 6.3585*DEG2RAD ); planet.setC_2( 0.2204*DEG2RAD ); planet.setC_3( 0.0106*DEG2RAD ); planet.setC_4( 0.0006*DEG2RAD ); planet.setC_5( 0 ); planet.setC_6( 0 ); planet.setPi( 99.4571*DEG2RAD ); planet.setEpsilon( 26.74*DEG2RAD ); planet.setTheta_0( 174.3479*DEG2RAD ); planet.setTheta_1( 810.7939024*DEG2RAD ); planet.setRadius( 60268000.0 ); planet.setName(QStringLiteral("Saturn")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Oxygen::sunYellow2 ); } else if (id == QLatin1String("uranus")) { planet.setM_0( 141.0498*DEG2RAD ); planet.setM_1( 0.01172834*DEG2RAD ); planet.setC_1( 5.3042*DEG2RAD ); planet.setC_2( 0.1534*DEG2RAD ); planet.setC_3( 0.0062*DEG2RAD ); planet.setC_4( 0.0003*DEG2RAD ); planet.setC_5( 0 ); planet.setC_6( 0 ); planet.setPi( 5.4639*DEG2RAD ); planet.setEpsilon( 82.22*DEG2RAD ); planet.setTheta_0( 17.9705*DEG2RAD ); planet.setTheta_1( -501.1600928*DEG2RAD ); planet.setRadius( 25559000.0 ); planet.setName(QStringLiteral("Uranus")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Oxygen::seaBlue4 ); } else if (id == QLatin1String("neptune")) { planet.setM_0( 256.2250*DEG2RAD ); planet.setM_1( 0.00598103*DEG2RAD ); planet.setC_1( 1.0302*DEG2RAD ); planet.setC_2( 0.0058*DEG2RAD ); planet.setC_3( 0 ); planet.setC_4( 0 ); planet.setC_5( 0 ); planet.setC_6( 0 ); planet.setPi( 182.1957*DEG2RAD ); planet.setEpsilon( 27.84*DEG2RAD ); planet.setTheta_0( 52.3996*DEG2RAD ); planet.setTheta_1( 536.3128492*DEG2RAD ); planet.setRadius( 24766000.0 ); planet.setName(QStringLiteral("Neptune")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Oxygen::skyBlue2 ); } else if (id == QLatin1String("pluto")) { // dwarf planets ... (everybody likes pluto) planet.setM_0( 14.882*DEG2RAD ); planet.setM_1( 0.00396*DEG2RAD ); planet.setC_1( 28.3150*DEG2RAD ); planet.setC_2( 4.3408*DEG2RAD ); planet.setC_3( 0.9214*DEG2RAD ); planet.setC_4( 0.2235*DEG2RAD ); planet.setC_5( 0.0627*DEG2RAD ); planet.setC_6( 0.0174*DEG2RAD ); planet.setPi( 4.5433*DEG2RAD ); planet.setEpsilon( 57.46*DEG2RAD ); planet.setTheta_0( 56.3183*DEG2RAD ); planet.setTheta_1( -56.3623195*DEG2RAD ); planet.setRadius( 1151000.0 ); planet.setName(QStringLiteral("Pluto")); planet.setHasAtmosphere( false ); } else if (id == QLatin1String("sun")) { mDebug() << "WARNING: Creating Planet instance" << id << "with invalid orbital elements"; planet.setRadius( 695000000.0 ); //FIXME: fill in with valid data planet.setName(QStringLiteral("Sun")); planet.setHasAtmosphere( true ); planet.setAtmosphereColor( Qt::white ); } else if (id == QLatin1String("moon")) { mDebug() << "WARNING: Creating Planet instance" << id << "with invalid orbital elements"; planet.setRadius( 1738000.0 ); //FIXME: fill in with valid data planet.setName(QStringLiteral("Moon")); planet.setHasAtmosphere( false ); } else if (id == QLatin1String("sky")) { mDebug() << "WARNING: Creating Planet instance" << id << "with invalid orbital elements"; planet.setRadius( 10000000.0 ); planet.setName(QStringLiteral("Sky")); planet.setHasAtmosphere( false ); } else { mDebug() << "WARNING: Creating Planet instance" << id << "with invalid orbital elements"; planet.setRadius( 10000000.0 ); planet.setName(QStringLiteral("Unknown")); planet.setHasAtmosphere( false ); } return planet; } QString PlanetFactory::localizedName( const QString &id ) { if (id == QLatin1String("mercury")) { return QObject::tr("Mercury", "the planet"); } else if (id == QLatin1String("venus")) { return QObject::tr("Venus", "the planet"); } else if (id == QLatin1String("earth")) { return QObject::tr("Earth", "the planet"); } else if (id == QLatin1String("mars")) { return QObject::tr("Mars", "the planet"); } else if (id == QLatin1String("jupiter")) { return QObject::tr("Jupiter", "the planet"); } else if (id == QLatin1String("saturn")) { return QObject::tr("Saturn", "the planet"); } else if (id == QLatin1String("uranus")) { return QObject::tr("Uranus", "the planet"); } else if (id == QLatin1String("neptune")) { return QObject::tr("Neptune", "the planet"); // dwarf planets ... (everybody likes pluto) } else if (id == QLatin1String("pluto")) { return QObject::tr("Pluto", "the planet"); // sun, moon and sky } else if (id == QLatin1String("sun")) { return QObject::tr("Sun", "the earth's star"); } else if (id == QLatin1String("moon")) { return QObject::tr("Moon", "the earth's moon"); } else if (id == QLatin1String("sky")) { return QObject::tr("Sky"); } else if ( id.isEmpty() ) { mDebug() << "Warning: empty id"; return QObject::tr("Unknown Planet", "a planet without data"); } return id; } } diff --git a/src/lib/marble/PositionTracking.cpp b/src/lib/marble/PositionTracking.cpp index f8e35bafb..74d19f659 100644 --- a/src/lib/marble/PositionTracking.cpp +++ b/src/lib/marble/PositionTracking.cpp @@ -1,398 +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 "MarbleColors.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( nullptr ), m_positionProvider( nullptr ), 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 != nullptr ); 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 += 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 != nullptr ); 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/declarative/RouteRelationModel.cpp b/src/lib/marble/declarative/RouteRelationModel.cpp index 3b8ea4ae6..c0ddae734 100644 --- a/src/lib/marble/declarative/RouteRelationModel.cpp +++ b/src/lib/marble/declarative/RouteRelationModel.cpp @@ -1,173 +1,174 @@ // // 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 2017 Sergey Popov // #include "RouteRelationModel.h" +#include "MarbleColors.h" #include "MarbleDirs.h" #include "osm/OsmPlacemarkData.h" #include "GeoDataColorStyle.h" namespace Marble { RouteRelationModel::RouteRelationModel(QObject *parent) : QAbstractListModel(parent) { m_networks[QStringLiteral("iwn")] = tr("International walking route"); m_networks[QStringLiteral("nwn")] = tr("National walking route"); m_networks[QStringLiteral("rwn")] = tr("Regional walking route"); m_networks[QStringLiteral("lwn")] = tr("Local walking route"); m_networks[QStringLiteral("icn")] = tr("International cycling route"); m_networks[QStringLiteral("ncn")] = tr("National cycling route"); m_networks[QStringLiteral("rcn")] = tr("Regional cycling route"); m_networks[QStringLiteral("lcn")] = tr("Local cycling route"); m_networks[QStringLiteral("US:TX:FM")] = tr("Farm to Market Road", "State or county road in Texas, USA"); m_networks[QStringLiteral("regional")] = tr("Regional route"); m_networks[QStringLiteral("national")] = tr("National route"); m_networks[QStringLiteral("municipal")] = tr("Municipal route"); m_networks[QStringLiteral("territorial")] = tr("Territorial route"); m_networks[QStringLiteral("local")] = tr("Local route"); m_networks[QStringLiteral("prefectural")] = tr("Prefectural route"); m_networks[QStringLiteral("US")] = tr("United States route"); } void RouteRelationModel::setRelations(const QSet &relations) { if (!m_relations.isEmpty()) { beginRemoveRows(QModelIndex(), 0, m_relations.count() - 1); m_relations.clear(); endRemoveRows(); } if (!relations.isEmpty()) { beginInsertRows(QModelIndex(), 0, relations.count() - 1); m_relations.reserve(relations.size()); for (auto relation: relations) { if (relation->relationType() >= GeoDataRelation::RouteRoad && relation->relationType() <= GeoDataRelation::RouteSled) { m_relations << new GeoDataRelation(*relation); } } std::sort(m_relations.begin(), m_relations.end(), [](const GeoDataRelation * a, const GeoDataRelation * b) { return *a < *b; }); endInsertRows(); } } int RouteRelationModel::rowCount(const QModelIndex & parent) const { return parent.isValid() ? 0 : m_relations.count(); } QVariant RouteRelationModel::data(const QModelIndex & index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= m_relations.count()) { return QVariant(); } if (role == Qt::DisplayRole) { return m_relations.at(index.row())->name(); } else if (role == IconSource) { switch (m_relations.at(index.row())->relationType()) { case GeoDataRelation::RouteRoad: return QStringLiteral("material/directions-car.svg"); case GeoDataRelation::RouteDetour: return QStringLiteral("material/directions-car.svg"); case GeoDataRelation::RouteFerry: return QStringLiteral("material/directions-boat.svg"); case GeoDataRelation::RouteTrain: return QStringLiteral("material/directions-railway.svg"); case GeoDataRelation::RouteSubway: return QStringLiteral("material/directions-subway.svg"); case GeoDataRelation::RouteTram: return QStringLiteral("material/directions-tram.svg"); case GeoDataRelation::RouteBus: return QStringLiteral("material/directions-bus.svg"); case GeoDataRelation::RouteTrolleyBus: return QStringLiteral("material/directions-bus.svg"); case GeoDataRelation::RouteBicycle: return QStringLiteral("material/directions-bike.svg"); case GeoDataRelation::RouteMountainbike: return QStringLiteral("material/directions-bike.svg"); case GeoDataRelation::RouteFoot: return QStringLiteral("material/directions-walk.svg"); case GeoDataRelation::RouteHiking: return QStringLiteral("thenounproject/204712-hiker.svg"); case GeoDataRelation::RouteHorse: return QStringLiteral("thenounproject/78374-horse-riding.svg"); case GeoDataRelation::RouteInlineSkates: return QStringLiteral("thenounproject/101965-inline-skater.svg"); case GeoDataRelation::RouteSkiDownhill: return QStringLiteral("thenounproject/2412-skiing-downhill.svg"); case GeoDataRelation::RouteSkiNordic: return QStringLiteral("thenounproject/30231-skiing-cross-country.svg"); case GeoDataRelation::RouteSkitour: return QStringLiteral("thenounproject/29366-skitour.svg"); case GeoDataRelation::RouteSled: return QStringLiteral("thenounproject/365217-sled.svg"); case GeoDataRelation::UnknownType: return QVariant(QString()); } } else if (role == Description) { return m_relations.at(index.row())->osmData().tagValue(QStringLiteral("description")); } else if (role == Network) { auto const network = m_relations.at(index.row())->osmData().tagValue(QStringLiteral("network")); auto iter = m_networks.find(network); if (iter != m_networks.end()) { return *iter; } auto const fields = network.split(':', QString::SkipEmptyParts); for (auto const &field: fields) { auto iter = m_networks.find(field); if (iter != m_networks.end()) { return *iter; } } return network; } else if (role == RouteColor) { auto const color = m_relations.at(index.row())->osmData().tagValue(QStringLiteral("colour")); return color.isEmpty() ? QStringLiteral("white") : color; } else if (role == TextColor) { auto const colorValue = m_relations.at(index.row())->osmData().tagValue(QStringLiteral("colour")); auto const color = QColor(colorValue.isEmpty() ? QStringLiteral("white") : colorValue); return GeoDataColorStyle::contrastColor(color); } else if (role == RouteFrom) { return m_relations.at(index.row())->osmData().tagValue(QStringLiteral("from")); } else if (role == RouteTo) { return m_relations.at(index.row())->osmData().tagValue(QStringLiteral("to")); } else if (role == RouteRef) { auto const ref = m_relations.at(index.row())->osmData().tagValue(QStringLiteral("ref")); return ref.isEmpty() ? m_relations.at(index.row())->name() : ref; } else if (role == RouteVia) { auto const viaValue = m_relations.at(index.row())->osmData().tagValue(QStringLiteral("via")); auto viaList = viaValue.split(';', QString::SkipEmptyParts); for (auto &via: viaList) { via = via.trimmed(); } return viaList; } else if (role == OsmId) { return m_relations.at(index.row())->osmData().oid(); } else if (role == RouteVisible) { return m_relations.at(index.row())->isVisible(); } return QVariant(); } QHash RouteRelationModel::roleNames() const { QHash roles; roles[Qt::DisplayRole] = "display"; roles[IconSource] = "iconSource"; roles[Description] = "description"; roles[Network] = "network"; roles[RouteColor] = "routeColor"; roles[TextColor] = "textColor"; roles[RouteFrom] = "routeFrom"; roles[RouteTo] = "routeTo"; roles[RouteRef] = "routeRef"; roles[RouteVia] = "routeVia"; roles[OsmId] = "oid"; roles[RouteVisible] = "routeVisible"; return roles; } QString RouteRelationModel::svgFile(const QString &path) { #ifdef Q_OS_ANDROID return MarbleDirs::path(QStringLiteral("svg/%1").arg(path)); #else return QStringLiteral("file:///") + MarbleDirs::path(QStringLiteral("svg/%1").arg(path)); #endif } } diff --git a/src/lib/marble/geodata/data/GeoDataBalloonStyle.h b/src/lib/marble/geodata/data/GeoDataBalloonStyle.h index 88d7b20f2..183cc07ab 100644 --- a/src/lib/marble/geodata/data/GeoDataBalloonStyle.h +++ b/src/lib/marble/geodata/data/GeoDataBalloonStyle.h @@ -1,69 +1,71 @@ // // 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 Mohammed Nafees // #ifndef GEODATABALLOONSTYLE_H #define GEODATABALLOONSTYLE_H #include "MarbleGlobal.h" #include "GeoDataColorStyle.h" #include "geodata_export.h" +#include + namespace Marble { class GeoDataBalloonStylePrivate; /** */ class GEODATA_EXPORT GeoDataBalloonStyle : public GeoDataColorStyle { public: GeoDataBalloonStyle(); GeoDataBalloonStyle( const GeoDataBalloonStyle &other ); GeoDataBalloonStyle& operator=( const GeoDataBalloonStyle &other ); bool operator==( const GeoDataBalloonStyle &other ) const; bool operator!=( const GeoDataBalloonStyle &other ) const; ~GeoDataBalloonStyle() override; /** Provides type information for downcasting a GeoNode */ const char* nodeType() const override; enum DisplayMode { Default, Hide }; QColor backgroundColor() const; void setBackgroundColor( const QColor &color ); QColor textColor() const; void setTextColor( const QColor &color ); QString text() const; void setText( const QString &text ); DisplayMode displayMode() const; void setDisplayMode(DisplayMode mode ); void pack( QDataStream& stream ) const override; void unpack( QDataStream& stream ) override; private: GeoDataBalloonStylePrivate* const d; }; } #endif diff --git a/src/lib/marble/geodata/data/GeoDataListStyle.h b/src/lib/marble/geodata/data/GeoDataListStyle.h index 0e99bc59c..f3f7f38b7 100644 --- a/src/lib/marble/geodata/data/GeoDataListStyle.h +++ b/src/lib/marble/geodata/data/GeoDataListStyle.h @@ -1,98 +1,100 @@ // // 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 Mohammed Nafees // #ifndef GEODATALISTSTYLE_H #define GEODATALISTSTYLE_H -#include - #include "MarbleGlobal.h" #include "GeoDataObject.h" #include "geodata_export.h" +#include +#include + + namespace Marble { class GeoDataListStylePrivate; class GeoDataItemIcon; /** */ class GEODATA_EXPORT GeoDataListStyle : public GeoDataObject { public: GeoDataListStyle(); GeoDataListStyle( const GeoDataListStyle &other ); GeoDataListStyle& operator=( const GeoDataListStyle &other ); bool operator==( const GeoDataListStyle &other ) const; bool operator!=( const GeoDataListStyle &other ) const; ~GeoDataListStyle() override; /** Provides type information for downcasting a GeoNode */ const char* nodeType() const override; enum ListItemType { Check, RadioFolder, CheckOffOnly, CheckHideChildren }; ListItemType listItemType() const; void setListItemType(ListItemType type); QColor backgroundColor() const; void setBackgroundColor( const QColor &color ); QVector itemIconList() const; GeoDataItemIcon* child( int ); const GeoDataItemIcon* child( int ) const; int childPosition( const GeoDataItemIcon *child ) const; void append( GeoDataItemIcon *other ); void remove( int index ); int size() const; GeoDataItemIcon& at( int pos ); const GeoDataItemIcon& at( int pos ) const; GeoDataItemIcon& last(); const GeoDataItemIcon& last() const; GeoDataItemIcon& first(); const GeoDataItemIcon& first() const; QVector::Iterator begin(); QVector::Iterator end(); QVector::ConstIterator constBegin() const; QVector::ConstIterator constEnd() const; void clear(); void pack( QDataStream& stream ) const override; void unpack( QDataStream& stream ) override; private: friend class GeoDataItemIcon; GeoDataListStylePrivate* const d; }; } #endif diff --git a/src/lib/marble/routing/RouteRequest.cpp b/src/lib/marble/routing/RouteRequest.cpp index 761324af1..7ec7c4cb6 100644 --- a/src/lib/marble/routing/RouteRequest.cpp +++ b/src/lib/marble/routing/RouteRequest.cpp @@ -1,360 +1,361 @@ // // 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 "RouteRequest.h" #include "GeoDataLineString.h" #include "GeoDataPlacemark.h" #include "GeoDataData.h" #include "GeoDataExtendedData.h" +#include "MarbleColors.h" #include "MarbleDirs.h" #include #include #include namespace Marble { struct PixmapElement { int index; int size; explicit PixmapElement( int index=-1, int size=0 ); bool operator < ( const PixmapElement &other ) const; }; class RouteRequestPrivate { public: QVector m_route; QMap m_pixmapCache; RoutingProfile m_routingProfile; /** Determines a suitable index for inserting a via point */ int viaIndex( const GeoDataCoordinates &position ) const; }; PixmapElement::PixmapElement( int index_, int size_ ) : index( index_ ), size( size_ ) { // nothing to do } bool PixmapElement::operator <(const PixmapElement &other) const { return index < other.index || size < other.size; } int RouteRequestPrivate::viaIndex( const GeoDataCoordinates &position ) const { /** @todo: Works, but does not look elegant at all */ // Iterates over all ordered trip point pairs (P,Q) and finds the triple // (P,position,Q) or (P,Q,position) with minimum length qreal minLength = -1.0; int result = 0; GeoDataLineString viaFirst; GeoDataLineString viaSecond; for ( int i = 0; i < m_route.size(); ++i ) { Q_ASSERT( viaFirst.size() < 4 && viaSecond.size() < 4 ); if ( viaFirst.size() == 3 ) { viaFirst.remove( 0 ); viaFirst.remove( 0 ); } if ( viaSecond.size() == 3 ) { viaSecond.remove( 0 ); viaSecond.remove( 0 ); } if ( viaFirst.size() == 1 ) { viaFirst.append( position ); } viaFirst.append( m_route[i].coordinate() ); viaSecond.append( m_route[i].coordinate() ); if ( viaSecond.size() == 2 ) { viaSecond.append( position ); } if ( viaFirst.size() == 3 ) { qreal len = viaFirst.length( EARTH_RADIUS ); if ( minLength < 0.0 || len < minLength ) { minLength = len; result = i; } } /** @todo: Assumes that destination is the last point */ if ( viaSecond.size() == 3 && i + 1 < m_route.size() ) { qreal len = viaSecond.length( EARTH_RADIUS ); if ( minLength < 0.0 || len < minLength ) { minLength = len; result = i + 1; } } } Q_ASSERT( 0 <= result && result <= m_route.size() ); return result; } RouteRequest::RouteRequest( QObject *parent ) : QObject( parent ), d( new RouteRequestPrivate ) { // nothing to do } RouteRequest::~RouteRequest() { delete d; } int RouteRequest::size() const { return d->m_route.size(); } GeoDataCoordinates RouteRequest::source() const { GeoDataCoordinates result; if ( d->m_route.size() ) { result = d->m_route.first().coordinate(); } return result; } GeoDataCoordinates RouteRequest::destination() const { GeoDataCoordinates result; if ( d->m_route.size() ) { result = d->m_route.last().coordinate(); } return result; } GeoDataCoordinates RouteRequest::at( int position ) const { return d->m_route.at( position ).coordinate(); } QPixmap RouteRequest::pixmap(int position, int size, int margin ) const { PixmapElement const element( position, size ); if ( !d->m_pixmapCache.contains( element ) ) { // Transparent background bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; int const imageSize = size > 0 ? size : ( smallScreen ? 32 : 16 ); QImage result( imageSize, imageSize, QImage::Format_ARGB32_Premultiplied ); result.fill( qRgba( 0, 0, 0, 0 ) ); // Paint a colored circle QPainter painter( &result ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.setPen( QColor( Qt::black ) ); bool const isVisited = visited( position ); QColor const backgroundColor = isVisited ? Oxygen::aluminumGray4 : Oxygen::forestGreen4; painter.setBrush( QBrush( backgroundColor ) ); painter.setPen( Qt::black ); int const iconSize = imageSize - 2 * margin; painter.drawEllipse( margin, margin, iconSize, iconSize ); char const text = char( 'A' + position ); // Choose a suitable font size QFont font = painter.font(); int fontSize = 20; while ( fontSize-- > 0 ) { font.setPointSize( fontSize ); QFontMetrics const fontMetric( font ); if ( fontMetric.width( text ) <= iconSize && fontMetric.height( ) <= iconSize ) { break; } } Q_ASSERT( fontSize ); font.setPointSize( fontSize ); painter.setFont( font ); // Paint a character denoting the position (0=A, 1=B, 2=C, ...) painter.drawText( 0, 0, imageSize, imageSize, Qt::AlignCenter, QString( text ) ); d->m_pixmapCache.insert( element, QPixmap::fromImage( result ) ); } return d->m_pixmapCache[element]; } void RouteRequest::clear() { for ( int i=d->m_route.size()-1; i>=0; --i ) { remove( i ); } } void RouteRequest::insert( int index, const GeoDataCoordinates &coordinates, const QString &name ) { GeoDataPlacemark placemark; placemark.setCoordinate( coordinates ); placemark.setName( name ); insert(index, placemark); } void RouteRequest::insert(int index, const GeoDataPlacemark &placemark) { d->m_route.insert( index, placemark ); emit positionAdded( index ); } void RouteRequest::swap(int index1, int index2) { if (index1 < 0 || index2 < 0 || index1 > d->m_route.size()-1 || index2 > d->m_route.size()-1) { return; } qSwap(d->m_route[index1], d->m_route[index2]); emit positionChanged(index1, d->m_route[index1].coordinate()); emit positionChanged(index2, d->m_route[index2].coordinate()); } void RouteRequest::append( const GeoDataCoordinates &coordinates, const QString &name ) { GeoDataPlacemark placemark; placemark.setCoordinate( coordinates ); placemark.setName( name ); append( placemark ); } void RouteRequest::append( const GeoDataPlacemark &placemark ) { d->m_route.append( placemark ); emit positionAdded( d->m_route.size()-1 ); } void RouteRequest::remove( int index ) { if ( index >= 0 && index < d->m_route.size() ) { d->m_route.remove( index ); emit positionRemoved( index ); } } void RouteRequest::addVia( const GeoDataCoordinates &position ) { GeoDataPlacemark placemark; placemark.setCoordinate( position ); addVia(placemark); } void RouteRequest::addVia(const GeoDataPlacemark &placemark) { int index = d->viaIndex( placemark.coordinate() ); d->m_route.insert( index, placemark ); emit positionAdded( index ); } void RouteRequest::setPosition( int index, const GeoDataCoordinates &position, const QString &name ) { if ( index >= 0 && index < d->m_route.size() ) { d->m_route[index].setName( name ); if ( d->m_route[index].coordinate() != position ) { d->m_route[index].setCoordinate( position ); setVisited( index, false ); emit positionChanged( index, position ); } } } void RouteRequest::setName( int index, const QString &name ) { if ( index >= 0 && index < d->m_route.size() ) { d->m_route[index].setName( name ); } } QString RouteRequest::name( int index ) const { QString result; if ( index >= 0 && index < d->m_route.size() ) { result = d->m_route[index].name(); } return result; } void RouteRequest::setVisited( int index, bool visited ) { if ( index >= 0 && index < d->m_route.size() ) { d->m_route[index].extendedData().addValue(GeoDataData(QStringLiteral("routingVisited"), visited)); QMap::iterator iter = d->m_pixmapCache.begin(); while ( iter != d->m_pixmapCache.end() ) { if ( iter.key().index == index ) { iter = d->m_pixmapCache.erase( iter ); } else { ++iter; } } emit positionChanged( index, d->m_route[index].coordinate() ); } } bool RouteRequest::visited( int index ) const { bool visited = false; if ( index >= 0 && index < d->m_route.size() ) { if (d->m_route[index].extendedData().contains(QStringLiteral("routingVisited"))) { visited = d->m_route[index].extendedData().value(QStringLiteral("routingVisited")).value().toBool(); } } return visited; } void RouteRequest::reverse() { std::reverse(d->m_route.begin(), d->m_route.end()); int const total = d->m_route.size(); for (int i = 0; i < total; ++i) { setVisited( i, false ); } } void RouteRequest::setRoutingProfile( const RoutingProfile &profile ) { d->m_routingProfile = profile; emit routingProfileChanged(); } RoutingProfile RouteRequest::routingProfile() const { return d->m_routingProfile; } GeoDataPlacemark &RouteRequest::operator []( int index ) { return d->m_route[index]; } const GeoDataPlacemark &RouteRequest::operator [](int index) const { return d->m_route[index]; } } // namespace Marble #include "moc_RouteRequest.cpp" diff --git a/src/lib/marble/routing/RoutingInputWidget.cpp b/src/lib/marble/routing/RoutingInputWidget.cpp index 600c1692d..d53c8598f 100644 --- a/src/lib/marble/routing/RoutingInputWidget.cpp +++ b/src/lib/marble/routing/RoutingInputWidget.cpp @@ -1,504 +1,505 @@ // // 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 // Copyright 2012 Illya Kovalevskyy // #include "RoutingInputWidget.h" #include "MarblePlacemarkModel.h" #include "RouteRequest.h" #ifdef MARBLE_NO_WEBKITWIDGETS #include "NullTinyWebBrowser.h" #else #include "TinyWebBrowser.h" #endif #include "BookmarkManager.h" #include "MarbleModel.h" #include "MarbleWidget.h" #include "routing/RoutingManager.h" #include "GeoDataPlacemark.h" #include "GeoDataFolder.h" #include "GeoDataExtendedData.h" #include "GeoDataData.h" #include "PositionTracking.h" #include "ReverseGeocodingRunnerManager.h" #include "SearchRunnerManager.h" +#include "MarbleColors.h" #include "MarbleLineEdit.h" #include "GoToDialog.h" #include #include #include #include #include #include #include namespace Marble { /** * A MarbleLineEdit that swallows enter/return pressed * key events */ class RoutingInputLineEdit : public MarbleLineEdit { public: explicit RoutingInputLineEdit( QWidget *parent = nullptr ); protected: void keyPressEvent(QKeyEvent *) override; }; class RoutingInputWidgetPrivate { public: MarbleModel* m_marbleModel; RoutingInputLineEdit *m_lineEdit; QPushButton* m_removeButton; SearchRunnerManager m_placemarkRunnerManager; ReverseGeocodingRunnerManager m_reverseGeocodingRunnerManager; MarblePlacemarkModel *m_placemarkModel; RouteRequest *m_route; int m_index; QTimer m_nominatimTimer; QAction* m_bookmarkAction; QAction* m_mapInput; QAction* m_currentLocationAction; QAction* m_centerAction; QMenu *m_menu; /** Constructor */ RoutingInputWidgetPrivate( MarbleModel* model, int index, QWidget *parent ); /** Initiate reverse geocoding request to download address */ void adjustText(); void createMenu( RoutingInputWidget *parent ); QMenu* createBookmarkMenu( RoutingInputWidget *parent ); static void createBookmarkActions( QMenu* menu, GeoDataFolder* bookmarksFolder, QObject *parent ); static QPixmap addDropDownIndicator( const QPixmap &pixmap ); void updateDescription(); }; void RoutingInputWidgetPrivate::updateDescription() { GeoDataPlacemark const placemark = (*m_route)[m_index]; GeoDataExtendedData const address = placemark.extendedData(); if (address.contains(QStringLiteral("road")) && address.contains(QStringLiteral("city"))) { QString const road = address.value(QStringLiteral("road")).value().toString(); QString const city = address.value(QStringLiteral("city")).value().toString(); if (address.contains(QStringLiteral("house_number"))) { QString const houseNumber = address.value(QStringLiteral("house_number")).value().toString(); QString const name = QObject::tr("%1 %2, %3", "An address with parameters %1=house number, %2=road, %3=city"); m_lineEdit->setText( name.arg( houseNumber, road, city ) ); } else { QString const name = QObject::tr("%2, %3", "An address with parameters %1=road, %2=city"); m_lineEdit->setText( name.arg( road, city ) ); } } else if ( m_route->name( m_index ).isEmpty() ) { if ( !placemark.address().isEmpty() ) { m_lineEdit->setText( placemark.address() ); } else { m_lineEdit->setText( placemark.coordinate().toString().trimmed() ); } } else { m_lineEdit->setText( placemark.name() ); } m_lineEdit->setCursorPosition( 0 ); } RoutingInputLineEdit::RoutingInputLineEdit( QWidget *parent ) : MarbleLineEdit( parent ) { setPlaceholderText( QObject::tr( "Address or search term..." ) ); } void RoutingInputLineEdit::keyPressEvent(QKeyEvent *event) { MarbleLineEdit::keyPressEvent( event ); bool const returnPressed = event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter; if ( returnPressed ) { event->accept(); } } RoutingInputWidgetPrivate::RoutingInputWidgetPrivate( MarbleModel* model, int index, QWidget *parent ) : m_marbleModel( model ), m_lineEdit( nullptr ), m_placemarkRunnerManager( m_marbleModel ), m_reverseGeocodingRunnerManager( m_marbleModel ), m_placemarkModel( nullptr ), m_route( m_marbleModel->routingManager()->routeRequest() ), m_index( index ), m_bookmarkAction( nullptr ), m_mapInput( nullptr ), m_currentLocationAction( nullptr ), m_centerAction( nullptr ), m_menu( nullptr ) { m_lineEdit = new RoutingInputLineEdit( parent ); m_lineEdit->setDecorator( addDropDownIndicator( m_route->pixmap( m_index ) ) ); m_removeButton = new QPushButton( parent ); m_removeButton->setIcon(QIcon(QStringLiteral(":/marble/routing/icon-remove.png"))); m_removeButton->setToolTip( QObject::tr( "Remove via point" ) ); m_removeButton->setFlat( true ); m_removeButton->setMaximumWidth( 18 ); m_nominatimTimer.setInterval( 1000 ); m_nominatimTimer.setSingleShot( true ); } void RoutingInputWidgetPrivate::adjustText() { m_nominatimTimer.start(); } void RoutingInputWidgetPrivate::createMenu( RoutingInputWidget *parent ) { QMenu* result = new QMenu( parent ); m_centerAction = result->addAction( QIcon( m_route->pixmap( m_index ) ), QObject::tr( "&Center Map here" ), parent, SLOT(requestActivity()) ); result->addSeparator(); m_currentLocationAction = result->addAction( QIcon(QStringLiteral(":/icons/gps.png")), QObject::tr("Current &Location"), parent, SLOT(setCurrentLocation()) ); m_currentLocationAction->setEnabled( false ); m_mapInput = result->addAction(QIcon(QStringLiteral(":/icons/crosshairs.png")), QObject::tr("From &Map...")); m_mapInput->setCheckable( true ); QObject::connect( m_mapInput, SIGNAL(triggered(bool)), parent, SLOT(setMapInputModeEnabled(bool)) ); m_bookmarkAction = result->addAction(QIcon(QStringLiteral(":/icons/bookmarks.png")), QObject::tr("From &Bookmark")); m_bookmarkAction->setMenu( createBookmarkMenu( parent ) ); m_menu = result; } QMenu* RoutingInputWidgetPrivate::createBookmarkMenu( RoutingInputWidget *parent ) { QMenu* result = new QMenu( parent ); result->addAction(QIcon(QStringLiteral(":/icons/go-home.png")), QObject::tr("&Home"), parent, SLOT(setHomePosition())); QVector folders = m_marbleModel->bookmarkManager()->folders(); if ( folders.size() == 1 ) { createBookmarkActions( result, folders.first(), parent ); } else { QVector::const_iterator i = folders.constBegin(); QVector::const_iterator end = folders.constEnd(); for (; i != end; ++i ) { QMenu* menu = result->addMenu(QIcon(QStringLiteral(":/icons/folder-bookmark.png")), (*i)->name()); createBookmarkActions( menu, *i, parent ); } } return result; } void RoutingInputWidgetPrivate::createBookmarkActions( QMenu* menu, GeoDataFolder* bookmarksFolder, QObject *parent ) { QVector bookmarks = bookmarksFolder->placemarkList(); QVector::const_iterator i = bookmarks.constBegin(); QVector::const_iterator end = bookmarks.constEnd(); for (; i != end; ++i ) { QAction *bookmarkAction = new QAction( (*i)->name(), parent ); bookmarkAction->setData( qVariantFromValue( (*i)->coordinate() ) ); menu->addAction( bookmarkAction ); QObject::connect( menu, SIGNAL(triggered(QAction*)), parent, SLOT(setBookmarkPosition(QAction*)) ); } } QPixmap RoutingInputWidgetPrivate::addDropDownIndicator(const QPixmap &pixmap) { QPixmap result( pixmap.size() + QSize( 8, pixmap.height() ) ); result.fill( QColor( Qt::transparent ) ); QPainter painter( &result ); painter.drawPixmap( 0, 0, pixmap ); QPoint const one( pixmap.width() + 1, pixmap.height() - 8 ); QPoint const two( one.x() + 6, one.y() ); QPoint const three( one.x() + 3, one.y() + 4 ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.setPen( Qt::NoPen ); painter.setBrush( QColor( Oxygen::aluminumGray4 ) ); painter.drawConvexPolygon( QPolygon() << one << two << three ); return result; } RoutingInputWidget::RoutingInputWidget( MarbleModel* model, int index, QWidget *parent ) : QWidget( parent ), d( new RoutingInputWidgetPrivate( model, index, this ) ) { QHBoxLayout *layout = new QHBoxLayout( this ); layout->setSizeConstraint( QLayout::SetMinimumSize ); layout->setSpacing( 0 ); layout->setMargin( 0 ); layout->addWidget( d->m_lineEdit ); layout->addWidget( d->m_removeButton ); bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( smallScreen ) { connect( d->m_lineEdit, SIGNAL(decoratorButtonClicked()), this, SLOT(openTargetSelectionDialog()) ); } else { d->createMenu( this ); connect(d->m_lineEdit, SIGNAL(decoratorButtonClicked()), this, SLOT(showMenu())); } connect( d->m_removeButton, SIGNAL(clicked()), this, SLOT(requestRemoval()) ); connect( d->m_marbleModel->bookmarkManager(), SIGNAL(bookmarksChanged()), this, SLOT(reloadBookmarks()) ); connect( d->m_marbleModel->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)), this, SLOT(updateCurrentLocationButton(PositionProviderStatus)) ); connect( &d->m_placemarkRunnerManager, SIGNAL(searchResultChanged(QAbstractItemModel*)), this, SLOT(setPlacemarkModel(QAbstractItemModel*)) ); connect( &d->m_reverseGeocodingRunnerManager, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)), this, SLOT(retrieveReverseGeocodingResult(GeoDataCoordinates,GeoDataPlacemark)) ); connect( d->m_lineEdit, SIGNAL(returnPressed()), this, SLOT(findPlacemarks()) ); connect( d->m_lineEdit, SIGNAL(textEdited(QString)), this, SLOT(setInvalid()) ); connect( &d->m_placemarkRunnerManager, SIGNAL(searchFinished(QString)), this, SLOT(finishSearch()) ); connect( d->m_marbleModel->routingManager()->routeRequest(), SIGNAL(positionChanged(int,GeoDataCoordinates)), this, SLOT(updatePosition(int,GeoDataCoordinates)) ); connect( &d->m_nominatimTimer, SIGNAL(timeout()), this, SLOT(reverseGeocoding()) ); connect( this, SIGNAL(targetValidityChanged(bool)), this, SLOT(updateCenterButton(bool)) ); updateCenterButton( hasTargetPosition() ); d->adjustText(); } RoutingInputWidget::~RoutingInputWidget() { delete d; } void RoutingInputWidget::reverseGeocoding() { if ( !hasTargetPosition() ) { return; } QString const name = d->m_route->name( d->m_index ); if ( name.isEmpty() || name == tr( "Current Location" ) ) { d->m_reverseGeocodingRunnerManager.reverseGeocoding( targetPosition() ); } else { d->updateDescription(); } } void RoutingInputWidget::setPlacemarkModel( QAbstractItemModel *model ) { d->m_placemarkModel = dynamic_cast(model); } void RoutingInputWidget::setTargetPosition( const GeoDataCoordinates &position, const QString &name ) { if ( d->m_mapInput ) { d->m_mapInput->setChecked( false ); } d->m_route->setPosition( d->m_index, position, name ); if ( !name.isEmpty() ) { d->updateDescription(); } emit targetValidityChanged( true ); } bool RoutingInputWidget::hasTargetPosition() const { return targetPosition().isValid(); } GeoDataCoordinates RoutingInputWidget::targetPosition() const { if ( d->m_index < d->m_route->size() ) { return d->m_route->at( d->m_index ); } else { return GeoDataCoordinates(); } } void RoutingInputWidget::findPlacemarks() { QString text = d->m_lineEdit->text(); if ( text.isEmpty() ) { setInvalid(); } else { d->m_lineEdit->setBusy(true); d->m_placemarkRunnerManager.findPlacemarks( text ); } } MarblePlacemarkModel *RoutingInputWidget::searchResultModel() { return d->m_placemarkModel; } void RoutingInputWidget::requestActivity() { if ( hasTargetPosition() ) { emit activityRequest( this ); } } void RoutingInputWidget::requestRemoval() { emit removalRequest( this ); } bool RoutingInputWidget::hasInput() const { return !d->m_lineEdit->text().isEmpty(); } void RoutingInputWidget::setMapInputModeEnabled( bool enabled ) { emit mapInputModeEnabled( this, enabled ); } void RoutingInputWidget::finishSearch() { d->m_lineEdit->setBusy(false); emit searchFinished( this ); } void RoutingInputWidget::setInvalid() { d->m_route->setPosition( d->m_index, GeoDataCoordinates() ); emit targetValidityChanged( false ); } void RoutingInputWidget::abortMapInputRequest() { if ( d->m_mapInput ) { d->m_mapInput->setChecked( false ); } } void RoutingInputWidget::setIndex( int index ) { d->m_index = index; d->m_lineEdit->setBusy(false); d->m_lineEdit->setDecorator( d->addDropDownIndicator( d->m_route->pixmap( index ) ) ); } void RoutingInputWidget::updatePosition( int index, const GeoDataCoordinates & ) { if ( index == d->m_index ) { d->m_lineEdit->setBusy(false); emit targetValidityChanged( hasTargetPosition() ); d->adjustText(); } } void RoutingInputWidget::clear() { d->m_nominatimTimer.stop(); d->m_lineEdit->setBusy(false); d->m_route->setPosition( d->m_index, GeoDataCoordinates() ); d->m_lineEdit->clear(); emit targetValidityChanged( false ); } void RoutingInputWidget::retrieveReverseGeocodingResult( const GeoDataCoordinates &, const GeoDataPlacemark &placemark ) { (*d->m_route)[d->m_index] = placemark; d->updateDescription(); } void RoutingInputWidget::reloadBookmarks() { if ( d->m_bookmarkAction ) { d->m_bookmarkAction->setMenu( d->createBookmarkMenu( this ) ); } } void RoutingInputWidget::setHomePosition() { qreal lon( 0.0 ), lat( 0.0 ); int zoom( 0 ); d->m_marbleModel->home( lon, lat, zoom ); GeoDataCoordinates home( lon, lat, 0.0, GeoDataCoordinates::Degree ); setTargetPosition( home ); requestActivity(); } void RoutingInputWidget::updateCurrentLocationButton( PositionProviderStatus status ) { if ( d->m_currentLocationAction ) { d->m_currentLocationAction->setEnabled( status == PositionProviderStatusAvailable ); } } void RoutingInputWidget::setCurrentLocation() { setTargetPosition( d->m_marbleModel->positionTracking()->currentLocation() ); requestActivity(); } void RoutingInputWidget::updateCenterButton( bool hasPosition ) { if ( d->m_centerAction ) { d->m_centerAction->setEnabled( hasPosition ); } } void RoutingInputWidget::setBookmarkPosition( QAction* bookmark ) { if ( !bookmark->data().isNull() ) { setTargetPosition( bookmark->data().value() ); requestActivity(); } } void RoutingInputWidget::openTargetSelectionDialog() { QPointer dialog = new GoToDialog( d->m_marbleModel, this ); dialog->setWindowTitle( tr( "Choose Placemark" ) ); dialog->setShowRoutingItems( false ); dialog->setSearchEnabled( false ); if ( dialog->exec() == QDialog::Accepted ) { const GeoDataCoordinates coordinates = dialog->coordinates(); setTargetPosition( coordinates ); } delete dialog; } void RoutingInputWidget::showMenu() { d->m_menu->exec( mapToGlobal( QPoint( 0, size().height() ) ) ); } } // namespace Marble #include "moc_RoutingInputWidget.cpp" diff --git a/src/lib/marble/routing/RoutingLayer.cpp b/src/lib/marble/routing/RoutingLayer.cpp index 9a22f792a..f47ddcd90 100644 --- a/src/lib/marble/routing/RoutingLayer.cpp +++ b/src/lib/marble/routing/RoutingLayer.cpp @@ -1,817 +1,818 @@ // // 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 "RoutingLayer.h" #include "GeoDataCoordinates.h" #include "GeoDataLineString.h" #include "GeoPainter.h" +#include "MarbleColors.h" #include "MarblePlacemarkModel.h" #include "MarbleWidget.h" #include "MarbleWidgetPopupMenu.h" #include "RoutingModel.h" #include "Route.h" #include "RouteRequest.h" #include "MarbleModel.h" #include "AlternativeRoutesModel.h" #include "RoutingManager.h" #include "Maneuver.h" #include "RenderState.h" #include #include #include #include #include #include #include namespace Marble { class RoutingLayerPrivate { template struct PaintRegion { T index; QRegion region; PaintRegion( const T &index_, const QRegion ®ion_ ) : index( index_ ), region( region_ ) { // nothing to do } }; using ModelRegion = PaintRegion; using RequestRegion = PaintRegion; public: RoutingLayer *const q; QList m_instructionRegions; QList m_regions; QList m_alternativeRouteRegions; QList m_placemarks; QRegion m_routeRegion; int m_movingIndex; MarbleWidget *const m_marbleWidget; QPixmap m_targetPixmap; QPixmap m_standardRoutePoint; QPixmap m_activeRoutePoint; QRect m_dirtyRect; QPoint m_dropStopOver; QPoint m_dragStopOver; int m_dragStopOverRightIndex; RoutingModel *const m_routingModel; MarblePlacemarkModel *m_placemarkModel; QItemSelectionModel *m_selectionModel; QSize m_pixmapSize; RouteRequest *const m_routeRequest; MarbleWidgetPopupMenu *m_contextMenu; QAction *m_removeViaPointAction; int m_activeMenuIndex; AlternativeRoutesModel *const m_alternativeRoutesModel; ViewContext m_viewContext; bool m_viewportChanged; bool m_isInteractive; /** Constructor */ explicit RoutingLayerPrivate( RoutingLayer *parent, MarbleWidget *widget ); /** Update the cached drag position. Use an empty point to clear it. */ void storeDragPosition( const QPoint &position ); // The following methods are mostly only called at one place in the code, but often // Inlined to avoid the function call overhead. Having functions here is just to // keep the code clean /** Returns the same color as the given one with its alpha channel adjusted to the given value */ static inline QColor alphaAdjusted( const QColor &color, int alpha ); static QPixmap createRoutePoint(const QColor &penColor, const QColor &brushColor); /** * Returns the start or destination position if Ctrl key is among the * provided modifiers, the cached insert position otherwise */ inline int viaInsertPosition( Qt::KeyboardModifiers modifiers ) const; /** Paint icons for each placemark in the placemark model */ inline void renderPlacemarks( GeoPainter *painter ); /** Paint waypoint polygon */ inline void renderRoute( GeoPainter *painter ); /** Paint turn instruction for selected items */ inline void renderAnnotations( GeoPainter *painter ) const; /** Paint alternative routes in gray */ inline void renderAlternativeRoutes( GeoPainter *painter ); /** Paint icons for trip points etc */ inline void renderRequest( GeoPainter *painter ); /** Insert via points or emit position signal, if appropriate */ inline bool handleMouseButtonRelease( QMouseEvent *e ); /** Select route instructions points, start dragging trip points */ inline bool handleMouseButtonPress( QMouseEvent *e ); /** Dragging trip points, route polygon hovering */ inline bool handleMouseMove( QMouseEvent *e ); /** True if the given point (screen coordinates) is among the route instruction points */ inline bool isInfoPoint( const QPoint &point ); /** True if the given point (screen coordinates) is above an alternative route */ inline bool isAlternativeRoutePoint( const QPoint &point ); /** Paint the stopover indicator pixmap at the given position. Also repaints the old position */ inline void paintStopOver( QRect position ); /** Removes the stopover indicator pixmap. Also repaints its old position */ inline void clearStopOver(); }; RoutingLayerPrivate::RoutingLayerPrivate( RoutingLayer *parent, MarbleWidget *widget ) : q( parent ), m_movingIndex( -1 ), m_marbleWidget( widget ), m_targetPixmap(QStringLiteral(":/data/bitmaps/routing_pick.png")), m_standardRoutePoint(createRoutePoint(widget->model()->routingManager()->routeColorStandard(), widget->model()->routingManager()->routeColorAlternative())), m_activeRoutePoint(createRoutePoint(widget->model()->routingManager()->routeColorHighlighted(), alphaAdjusted(Oxygen::hotOrange4, 200))), m_dragStopOverRightIndex(-1), m_routingModel( widget->model()->routingManager()->routingModel() ), m_placemarkModel( nullptr ), m_selectionModel( nullptr ), m_pixmapSize( 22, 22 ), m_routeRequest( widget->model()->routingManager()->routeRequest() ), m_activeMenuIndex( -1 ), m_alternativeRoutesModel( widget->model()->routingManager()->alternativeRoutesModel() ), m_viewContext( Still ), m_viewportChanged( true ), m_isInteractive( true ) { m_contextMenu = new MarbleWidgetPopupMenu( m_marbleWidget, m_marbleWidget->model() ); m_removeViaPointAction = new QAction( QObject::tr( "&Remove this destination" ), q ); QObject::connect( m_removeViaPointAction, SIGNAL(triggered()), q, SLOT(removeViaPoint()) ); m_contextMenu->addAction( Qt::RightButton, m_removeViaPointAction ); QAction *exportAction = new QAction( QObject::tr( "&Export route..." ), q ); QObject::connect( exportAction, SIGNAL(triggered()), q, SLOT(exportRoute()) ); m_contextMenu->addAction( Qt::RightButton, exportAction ); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { m_pixmapSize = QSize( 38, 38 ); } } int RoutingLayerPrivate::viaInsertPosition( Qt::KeyboardModifiers modifiers ) const { if ( modifiers & Qt::ControlModifier ) { bool leftHand = m_routeRequest->size() / 2 >= m_dragStopOverRightIndex; if ( leftHand && m_routeRequest->size() > 2 ) { return 0; } else { return m_routeRequest->size(); } } else { return m_dragStopOverRightIndex; } } void RoutingLayerPrivate::renderPlacemarks( GeoPainter *painter ) { m_placemarks.clear(); painter->setPen( QColor( Qt::black ) ); for ( int i = 0; i < m_placemarkModel->rowCount(); ++i ) { QModelIndex index = m_placemarkModel->index( i, 0 ); QVariant data = index.data( MarblePlacemarkModel::CoordinateRole ); if ( index.isValid() && !data.isNull() ) { GeoDataCoordinates pos = data.value(); QPixmap pixmap = index.data( Qt::DecorationRole ).value(); if ( !pixmap.isNull() && m_selectionModel->isSelected( index ) ) { QIcon selected = QIcon( pixmap ); QPixmap result = selected.pixmap( m_pixmapSize, QIcon::Selected, QIcon::On ); painter->drawPixmap( pos, result ); } else { painter->drawPixmap( pos, pixmap ); } const QRegion region = painter->regionFromPixmapRect(pos, m_targetPixmap.width(), m_targetPixmap.height()); m_placemarks.push_back( ModelRegion( index, region ) ); } } } void RoutingLayerPrivate::renderAlternativeRoutes( GeoPainter *painter ) { QPen alternativeRoutePen( m_marbleWidget->model()->routingManager()->routeColorAlternative() ); alternativeRoutePen.setWidth( 5 ); painter->setPen( alternativeRoutePen ); for ( int i=0; irowCount(); ++i ) { const GeoDataDocument *route = m_alternativeRoutesModel->route(i); if ( route && route != m_alternativeRoutesModel->currentRoute() ) { const GeoDataLineString* points = AlternativeRoutesModel::waypoints( route ); if ( points ) { painter->drawPolyline( *points ); if ( m_viewportChanged && m_isInteractive && m_viewContext == Still ) { QRegion region = painter->regionFromPolyline( *points, 8 ); m_alternativeRouteRegions.push_back( RequestRegion( i, region ) ); } } } } } void RoutingLayerPrivate::renderRoute( GeoPainter *painter ) { GeoDataLineString waypoints = m_routingModel->route().path(); QPen standardRoutePen( m_marbleWidget->model()->routingManager()->routeColorStandard() ); standardRoutePen.setWidth( 5 ); if ( m_marbleWidget->model()->routingManager()->state() == RoutingManager::Downloading ) { standardRoutePen.setStyle( Qt::DotLine ); } painter->setPen( standardRoutePen ); painter->drawPolyline( waypoints ); if ( m_viewportChanged && m_viewContext == Still ) { int const offset = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ? 24 : 8; if ( m_isInteractive ) { m_routeRegion = painter->regionFromPolyline( waypoints, offset ); } } standardRoutePen.setWidth( 2 ); painter->setPen( standardRoutePen ); // Map matched position //painter->setBrush( QBrush( Oxygen::brickRed4) ); //painter->drawEllipse( m_routingModel->route().positionOnRoute(), 8, 8 ); painter->setBrush( QBrush( m_marbleWidget->model()->routingManager()->routeColorAlternative() ) ); if ( !m_dropStopOver.isNull() ) { int dx = 1 + m_pixmapSize.width() / 2; int dy = 1 + m_pixmapSize.height() / 2; QPoint center = m_dropStopOver - QPoint( dx, dy ); painter->drawPixmap( center, m_targetPixmap ); if ( !m_dragStopOver.isNull() && m_dragStopOverRightIndex >= 0 && m_dragStopOverRightIndex <= m_routeRequest->size() ) { QPoint moved = m_dropStopOver - m_dragStopOver; if ( moved.manhattanLength() > 10 ) { qreal lon( 0.0 ), lat( 0.0 ); if ( m_marbleWidget->geoCoordinates( m_dropStopOver.x(), m_dropStopOver.y(), lon, lat, GeoDataCoordinates::Radian ) ) { GeoDataCoordinates drag( lon, lat ); standardRoutePen.setStyle( Qt::DotLine ); painter->setPen( standardRoutePen ); GeoDataLineString lineString; if ( m_dragStopOverRightIndex > 0 ) { lineString << m_routeRequest->at( m_dragStopOverRightIndex-1 ); } lineString << drag; if ( m_dragStopOverRightIndex < m_routeRequest->size() ) { lineString << m_routeRequest->at( m_dragStopOverRightIndex ); } painter->drawPolyline( lineString ); standardRoutePen.setStyle( Qt::SolidLine ); painter->setPen( standardRoutePen ); } } } } if ( m_viewContext == Animation ) { return; } if( m_routingModel->rowCount() == m_routingModel->route().size() ) { m_instructionRegions.clear(); QPen activeRouteSegmentPen(m_marbleWidget->model()->routingManager()->routeColorHighlighted()); activeRouteSegmentPen.setWidth(6); if (m_marbleWidget->model()->routingManager()->state() == RoutingManager::Downloading) { activeRouteSegmentPen.setStyle(Qt::DotLine); } painter->setPen(activeRouteSegmentPen); for ( int i = 0; i < m_routingModel->rowCount(); ++i ) { QModelIndex index = m_routingModel->index( i, 0 ); GeoDataCoordinates pos = index.data( MarblePlacemarkModel::CoordinateRole ).value(); if ( m_selectionModel && m_selectionModel->selection().contains( index ) ) { const RouteSegment &segment = m_routingModel->route().at( i ); const GeoDataLineString currentRoutePoints = segment.path(); painter->drawPolyline( currentRoutePoints ); painter->drawPixmap(pos, m_activeRoutePoint); } else { painter->drawPixmap(pos, m_standardRoutePoint); } if ( m_isInteractive ) { QRegion region = painter->regionFromEllipse( pos, 12, 12 ); m_instructionRegions.push_front( ModelRegion( index, region ) ); } } } if( !m_routingModel->deviatedFromRoute() ) { GeoDataCoordinates location = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().position(); QString nextInstruction = m_routingModel->route().currentSegment().nextRouteSegment().maneuver().instructionText(); if( !nextInstruction.isEmpty() ) { painter->drawPixmap(location, m_activeRoutePoint); } } } void RoutingLayerPrivate::renderAnnotations( GeoPainter *painter ) const { if ( !m_selectionModel || m_selectionModel->selection().isEmpty() ) { // nothing to do return; } for ( int i = 0; i < m_routingModel->rowCount(); ++i ) { QModelIndex index = m_routingModel->index( i, 0 ); if ( m_selectionModel->selection().contains( index ) ) { bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; GeoDataCoordinates pos = index.data( MarblePlacemarkModel::CoordinateRole ).value(); painter->setPen( QColor( Qt::black ) ); painter->setBrush( QBrush( Oxygen::sunYellow6 ) ); painter->drawAnnotation( pos, index.data().toString(), QSize( smallScreen ? 240 : 120, 0 ), 10, 30, 5, 5 ); } } } void RoutingLayerPrivate::renderRequest( GeoPainter *painter ) { m_regions.clear(); for ( int i = 0; i < m_routeRequest->size(); ++i ) { const GeoDataCoordinates pos = m_routeRequest->at( i ); if ( pos.isValid() ) { QPixmap pixmap = m_routeRequest->pixmap( i ); painter->drawPixmap( pos, pixmap ); const QRegion region = painter->regionFromPixmapRect(pos, pixmap.width(), pixmap.height()); m_regions.push_front( RequestRegion( i, region ) ); } } } void RoutingLayerPrivate::storeDragPosition( const QPoint &pos ) { m_dragStopOver = pos; m_dragStopOverRightIndex = -1; qreal lon( 0.0 ), lat( 0.0 ); if ( m_routeRequest && !pos.isNull() && m_marbleWidget->geoCoordinates( pos.x(), pos.y(), lon, lat, GeoDataCoordinates::Radian ) ) { GeoDataCoordinates waypoint( lon, lat ); m_dragStopOverRightIndex = m_routingModel->rightNeighbor( waypoint, m_routeRequest ); } } QColor RoutingLayerPrivate::alphaAdjusted( const QColor &color, int alpha ) { QColor result( color ); result.setAlpha( alpha ); return result; } QPixmap RoutingLayerPrivate::createRoutePoint(const QColor &penColor, const QColor &brushColor) { QPen pen(penColor); pen.setWidth(2); const QBrush brush(brushColor); QPixmap routePoint(QSize(8, 8)); routePoint.fill(Qt::transparent); QPainter painter(&routePoint); painter.setRenderHint(QPainter::Antialiasing, true); painter.setPen(pen); painter.setBrush(brush); painter.drawEllipse(1, 1, 6, 6); return routePoint; } bool RoutingLayerPrivate::handleMouseButtonPress( QMouseEvent *e ) { for( const RequestRegion ®ion: m_regions ) { if ( region.region.contains( e->pos() ) ) { if ( e->button() == Qt::LeftButton ) { m_movingIndex = region.index; m_dropStopOver = QPoint(); m_dragStopOver = QPoint(); return true; } else if ( e->button() == Qt::RightButton ) { m_removeViaPointAction->setEnabled( true ); m_activeMenuIndex = region.index; m_contextMenu->showRmbMenu( e->x(), e->y() ); return true; } else return false; } } for( const ModelRegion ®ion: m_instructionRegions ) { if ( region.region.contains( e->pos() ) && m_selectionModel ) { if ( e->button() == Qt::LeftButton ) { QItemSelectionModel::SelectionFlag command = QItemSelectionModel::ClearAndSelect; if ( m_selectionModel->isSelected( region.index ) ) { command = QItemSelectionModel::Clear; } m_selectionModel->select( region.index, command ); m_dropStopOver = e->pos(); storeDragPosition( e->pos() ); // annotation and old annotation are dirty, large region emit q->repaintNeeded(); return true; } else if ( e->button() == Qt::RightButton ) { m_removeViaPointAction->setEnabled( false ); m_contextMenu->showRmbMenu( e->x(), e->y() ); return true; } else return false; } } if ( m_routeRegion.contains( e->pos() ) ) { if ( e->button() == Qt::LeftButton ) { /** @todo: Determine the neighbored via points and insert in order */ m_dropStopOver = e->pos(); storeDragPosition( e->pos() ); return true; } else if ( e->button() == Qt::RightButton ) { m_removeViaPointAction->setEnabled( false ); m_contextMenu->showRmbMenu( e->x(), e->y() ); return true; } else return false; } if ( e->button() != Qt::LeftButton ) { return false; } for( const RequestRegion ®ion: m_alternativeRouteRegions ) { if ( region.region.contains( e->pos() ) ) { m_alternativeRoutesModel->setCurrentRoute( region.index ); return true; } } for( const ModelRegion ®ion: m_placemarks ) { if ( region.region.contains( e->pos() ) ) { emit q->placemarkSelected( region.index ); return true; } } return false; } bool RoutingLayerPrivate::handleMouseButtonRelease( QMouseEvent *e ) { if ( e->button() != Qt::LeftButton ) { return false; } if ( m_movingIndex >= 0 ) { m_movingIndex = -1; clearStopOver(); m_marbleWidget->model()->routingManager()->retrieveRoute(); return true; } if ( !m_dropStopOver.isNull() && !m_dragStopOver.isNull() ) { QPoint moved = e->pos() - m_dragStopOver; if ( moved.manhattanLength() < 10 ) { return false; } qreal lon( 0.0 ), lat( 0.0 ); if ( m_dragStopOverRightIndex >= 0 && m_dragStopOverRightIndex <= m_routeRequest->size() && m_marbleWidget->geoCoordinates( m_dropStopOver.x(), m_dropStopOver.y(), lon, lat, GeoDataCoordinates::Radian ) ) { GeoDataCoordinates position( lon, lat ); m_dragStopOverRightIndex = viaInsertPosition( e->modifiers() ); m_routeRequest->insert( m_dragStopOverRightIndex, position ); clearStopOver(); m_marbleWidget->model()->routingManager()->retrieveRoute(); return true; } } return false; } bool RoutingLayerPrivate::handleMouseMove( QMouseEvent *e ) { qreal lon( 0.0 ), lat( 0.0 ); if ( m_marbleWidget->geoCoordinates( e->pos().x(), e->pos().y(), lon, lat, GeoDataCoordinates::Radian ) ) { if ( m_movingIndex >= 0 ) { GeoDataCoordinates moved( lon, lat ); m_routeRequest->setPosition( m_movingIndex, moved ); m_marbleWidget->setCursor( Qt::ArrowCursor ); } else if ( !m_dragStopOver.isNull() ) { // Repaint only that region of the map that is affected by the change m_dragStopOverRightIndex = viaInsertPosition( e->modifiers() ); QRect dirty = m_routeRegion.boundingRect(); dirty |= QRect( m_dropStopOver, m_pixmapSize ); dirty |= QRect( e->pos(), m_pixmapSize ); if ( e->buttons() & Qt::LeftButton ) { m_dropStopOver = e->pos(); } else { m_dragStopOver = QPoint(); m_dropStopOver = QPoint(); } emit q->repaintNeeded( dirty ); m_marbleWidget->setCursor( Qt::ArrowCursor ); } else if ( isInfoPoint( e->pos() ) ) { clearStopOver(); m_marbleWidget->setCursor( Qt::ArrowCursor ); } else if ( m_routeRegion.contains( e->pos() ) ) { m_dropStopOver = e->pos(); m_marbleWidget->setCursor( Qt::ArrowCursor ); } else if ( !m_dropStopOver.isNull() ) { clearStopOver(); } else if ( isAlternativeRoutePoint( e->pos() ) ) { m_marbleWidget->setCursor( Qt::ArrowCursor ); } else { return false; } // Update pixmap in the map (old and new position needs repaint) paintStopOver( QRect( e->pos(), m_pixmapSize ) ); return true; } return false; } bool RoutingLayerPrivate::isInfoPoint( const QPoint &point ) { for( const RequestRegion ®ion: m_regions ) { if ( region.region.contains( point ) ) { return true; } } for( const ModelRegion ®ion: m_instructionRegions ) { if ( region.region.contains( point ) ) { return true; } } return false; } bool RoutingLayerPrivate::isAlternativeRoutePoint( const QPoint &point ) { for( const RequestRegion ®ion: m_alternativeRouteRegions ) { if ( region.region.contains( point ) ) { return true; } } return false; } void RoutingLayerPrivate::paintStopOver( QRect dirty ) { emit q->repaintNeeded( m_dirtyRect ); int dx = 1 + m_pixmapSize.width() / 2; int dy = 1 + m_pixmapSize.height() / 2; dirty.adjust( -dx, -dy, -dx, -dy ); emit q->repaintNeeded( dirty ); m_dirtyRect = dirty; } void RoutingLayerPrivate::clearStopOver() { m_dropStopOver = QPoint(); m_dragStopOver = QPoint(); emit q->repaintNeeded( m_dirtyRect ); } RoutingLayer::RoutingLayer( MarbleWidget *widget, QWidget *parent ) : QObject( parent ), d( new RoutingLayerPrivate( this, widget ) ) { connect( widget->model()->routingManager(), SIGNAL(stateChanged(RoutingManager::State)), this, SLOT(updateRouteState()) ); connect( widget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)), this, SLOT(setViewportChanged()) ); connect(widget->model()->routingManager()->alternativeRoutesModel(), SIGNAL(currentRouteChanged(const GeoDataDocument*)), this, SLOT(setViewportChanged()) ); connect(widget->model()->routingManager()->alternativeRoutesModel(), SIGNAL(currentRouteChanged(const GeoDataDocument*)), this, SIGNAL(repaintNeeded()) ); connect( widget->model()->routingManager()->alternativeRoutesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(showAlternativeRoutes()) ); } RoutingLayer::~RoutingLayer() { delete d; } QStringList RoutingLayer::renderPosition() const { return QStringList(QStringLiteral("HOVERS_ABOVE_SURFACE")); } qreal RoutingLayer::zValue() const { return 1.0; } bool RoutingLayer::render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer *layer ) { Q_UNUSED( viewport ) Q_UNUSED( renderPos ) Q_UNUSED( layer ) painter->save(); if ( d->m_placemarkModel) { d->renderPlacemarks( painter ); } if ( d->m_alternativeRoutesModel ) { d->renderAlternativeRoutes( painter ); } d->renderRoute( painter ); if ( d->m_routeRequest) { d->renderRequest( painter ); } d->renderAnnotations( painter ); painter->restore(); if ( d->m_viewportChanged && d->m_viewContext == Still ) { d->m_viewportChanged = false; } return true; } RenderState RoutingLayer::renderState() const { return RenderState(QStringLiteral("Routing"), d->m_marbleWidget->model()->routingManager()->state() == RoutingManager::Downloading ? WaitingForUpdate : Complete); } bool RoutingLayer::eventFilter( QObject *obj, QEvent *event ) { Q_UNUSED( obj ) if ( !d->m_isInteractive ) { return false; } if ( event->type() == QEvent::MouseButtonPress ) { QMouseEvent *e = static_cast( event ); return d->handleMouseButtonPress( e ); } if ( event->type() == QEvent::MouseButtonRelease ) { QMouseEvent *e = static_cast( event ); return d->handleMouseButtonRelease( e ); } if ( event->type() == QEvent::MouseMove ) { QMouseEvent *e = static_cast( event ); return d->handleMouseMove( e ); } return false; } void RoutingLayer::setPlacemarkModel ( MarblePlacemarkModel *model ) { d->m_placemarkModel = model; setViewportChanged(); } void RoutingLayer::synchronizeWith( QItemSelectionModel *selection ) { d->m_selectionModel = selection; } void RoutingLayer::removeViaPoint() { if ( d->m_activeMenuIndex >= 0 ) { d->m_routeRequest->remove( d->m_activeMenuIndex ); d->m_activeMenuIndex = -1; emit repaintNeeded(); d->m_marbleWidget->model()->routingManager()->retrieveRoute(); } } void RoutingLayer::showAlternativeRoutes() { setViewportChanged(); emit repaintNeeded(); } void RoutingLayer::exportRoute() { QString fileName = QFileDialog::getSaveFileName( d->m_marbleWidget, tr( "Export Route" ), // krazy:exclude=qclasses QDir::homePath(), tr( "GPX and KML files (*.gpx *.kml)" ) ); if ( !fileName.isEmpty() ) { if ( fileName.endsWith( QLatin1String( ".gpx" ), Qt::CaseInsensitive ) ) { QFile gpx( fileName ); if ( gpx.open( QFile::WriteOnly) ) { d->m_routingModel->exportGpx( &gpx ); gpx.close(); } } else { d->m_marbleWidget->model()->routingManager()->saveRoute( fileName ); } } } void RoutingLayer::updateRouteState() { setViewportChanged(); emit repaintNeeded(); } void RoutingLayer::setViewportChanged() { d->m_viewportChanged = true; d->m_routeRegion = QRegion(); d->m_instructionRegions.clear(); d->m_alternativeRouteRegions.clear(); } void RoutingLayer::setViewContext( ViewContext viewContext ) { d->m_viewContext = viewContext; } void RoutingLayer::setInteractive( bool interactive ) { d->m_isInteractive = interactive; } bool RoutingLayer::isInteractive() const { return d->m_isInteractive; } QString RoutingLayer::runtimeTrace() const { return QStringLiteral("Routing Layer"); } } // namespace Marble #include "moc_RoutingLayer.cpp" diff --git a/src/lib/marble/routing/RoutingManager.cpp b/src/lib/marble/routing/RoutingManager.cpp index 03d0146c9..4df57e20a 100644 --- a/src/lib/marble/routing/RoutingManager.cpp +++ b/src/lib/marble/routing/RoutingManager.cpp @@ -1,652 +1,653 @@ // // 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 "RoutingManager.h" #include "AlternativeRoutesModel.h" #include "MarbleModel.h" #include "RouteRequest.h" #include "RoutingModel.h" #include "RoutingProfilesModel.h" #include "RoutingRunnerPlugin.h" #include "GeoWriter.h" #include "GeoDataDocument.h" #include "GeoDataExtendedData.h" #include "GeoDataData.h" #include "GeoDataFolder.h" #include "GeoDataParser.h" #include "GeoDataPlacemark.h" #include "GeoDataTreeModel.h" +#include "MarbleColors.h" #include "MarbleDirs.h" #include "MarbleDebug.h" #include "PositionTracking.h" #include "PluginManager.h" #include "PositionProviderPlugin.h" #include "Route.h" #include "RoutingRunnerManager.h" #include #include #include #include #include namespace Marble { class RoutingManagerPrivate { public: RoutingManager* q; RouteRequest m_routeRequest; RoutingModel m_routingModel; RoutingProfilesModel m_profilesModel; RoutingManager::State m_state; const PluginManager *const m_pluginManager; GeoDataTreeModel *const m_treeModel; PositionTracking *const m_positionTracking; AlternativeRoutesModel m_alternativeRoutesModel; RoutingRunnerManager m_runnerManager; bool m_haveRoute; bool m_guidanceModeEnabled; QMutex m_fileMutex; bool m_shutdownPositionTracking; bool m_guidanceModeWarning; QString m_lastOpenPath; QString m_lastSavePath; QColor m_routeColorStandard; QColor m_routeColorHighlighted; QColor m_routeColorAlternative; RoutingManagerPrivate(MarbleModel *marbleModel, RoutingManager *manager); static GeoDataFolder *createFolderFromRequest(const RouteRequest &request); static QString stateFile( const QString &name = QString( "route.kml" ) ); void saveRoute( const QString &filename ); void loadRoute( const QString &filename ); void addRoute( GeoDataDocument* route ); void routingFinished(); void setCurrentRoute(const GeoDataDocument *route); void recalculateRoute( bool deviated ); static void importPlacemark( RouteSegment &outline, QVector &segments, const GeoDataPlacemark *placemark ); }; RoutingManagerPrivate::RoutingManagerPrivate(MarbleModel *model, RoutingManager *manager) : q( manager ), m_routeRequest( manager ), m_routingModel(&m_routeRequest, model->positionTracking(), manager), m_profilesModel( model->pluginManager() ), m_state( RoutingManager::Retrieved ), m_pluginManager( model->pluginManager() ), m_treeModel( model->treeModel() ), m_positionTracking( model->positionTracking() ), m_alternativeRoutesModel(manager), m_runnerManager(model, manager), m_haveRoute( false ), m_guidanceModeEnabled( false ), m_shutdownPositionTracking( false ), m_guidanceModeWarning( true ), m_routeColorStandard( Oxygen::skyBlue4 ), m_routeColorHighlighted( Oxygen::skyBlue1 ), m_routeColorAlternative( Oxygen::aluminumGray4 ) { m_routeColorStandard.setAlpha( 200 ); m_routeColorHighlighted.setAlpha( 200 ); m_routeColorAlternative.setAlpha( 200 ); } GeoDataFolder *RoutingManagerPrivate::createFolderFromRequest(const RouteRequest &request) { GeoDataFolder* result = new GeoDataFolder; result->setName(QStringLiteral("Route Request")); for (int i = 0; i < request.size(); ++i) { GeoDataPlacemark *placemark = new GeoDataPlacemark(request[i]); result->append( placemark ); } return result; } QString RoutingManagerPrivate::stateFile( const QString &name) { QString const subdir = "routing"; 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( name ); } void RoutingManagerPrivate::saveRoute(const QString &filename) { GeoWriter writer; writer.setDocumentType( kml::kmlTag_nameSpaceOgc22 ); QMutexLocker locker( &m_fileMutex ); QFile file( filename ); if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { mDebug() << "Cannot write to " << file.fileName(); return; } GeoDataDocument container; container.setName(QStringLiteral("Route")); GeoDataFolder *request = createFolderFromRequest(m_routeRequest); if ( request ) { container.append( request ); } const GeoDataDocument *route = m_alternativeRoutesModel.currentRoute(); if ( route ) { container.append( new GeoDataDocument( *route ) ); } if ( !writer.write( &file, &container ) ) { mDebug() << "Can not write route state to " << file.fileName(); } file.close(); } void RoutingManagerPrivate::loadRoute(const QString &filename) { QFile file( filename ); if ( !file.open( QIODevice::ReadOnly ) ) { mDebug() << "Can not read route from " << file.fileName(); return; } GeoDataParser parser( GeoData_KML ); if ( !parser.read( &file ) ) { mDebug() << "Could not parse file: " << parser.errorString(); return; } GeoDocument *doc = parser.releaseDocument(); file.close(); bool loaded = false; GeoDataDocument* container = dynamic_cast( doc ); if (container && !container->isEmpty()) { GeoDataFolder* viaPoints = dynamic_cast( &container->first() ); if ( viaPoints ) { loaded = true; QVector placemarks = viaPoints->placemarkList(); for( int i=0; i viaPoints_needed; --i ) { m_routeRequest.remove( viaPoints_needed ); } } else { mDebug() << "Expected a GeoDataDocument with at least one child, didn't get one though"; } } if ( container && container->size() == 2 ) { GeoDataDocument* route = dynamic_cast(&container->last()); if ( route ) { loaded = true; m_alternativeRoutesModel.clear(); m_alternativeRoutesModel.addRoute( new GeoDataDocument(*route), AlternativeRoutesModel::Instant ); m_alternativeRoutesModel.setCurrentRoute( 0 ); m_state = RoutingManager::Retrieved; emit q->stateChanged( m_state ); emit q->routeRetrieved( route ); } else { mDebug() << "Expected a GeoDataDocument child, didn't get one though"; } } if (loaded) { delete doc; // == container } else { mDebug() << "File " << filename << " is not a valid Marble route .kml file"; if ( container ) { m_treeModel->addDocument( container ); } } } RoutingManager::RoutingManager(MarbleModel *marbleModel, QObject *parent) : QObject(parent), d(new RoutingManagerPrivate(marbleModel, this)) { connect( &d->m_runnerManager, SIGNAL(routeRetrieved(GeoDataDocument*)), this, SLOT(addRoute(GeoDataDocument*)) ); connect( &d->m_runnerManager, SIGNAL(routingFinished()), this, SLOT(routingFinished()) ); connect(&d->m_alternativeRoutesModel, SIGNAL(currentRouteChanged(const GeoDataDocument*)), this, SLOT(setCurrentRoute(const GeoDataDocument*))); connect( &d->m_routingModel, SIGNAL(deviatedFromRoute(bool)), this, SLOT(recalculateRoute(bool)) ); } RoutingManager::~RoutingManager() { delete d; } RoutingProfilesModel *RoutingManager::profilesModel() { return &d->m_profilesModel; } RoutingModel *RoutingManager::routingModel() { return &d->m_routingModel; } const RoutingModel *RoutingManager::routingModel() const { return &d->m_routingModel; } RouteRequest* RoutingManager::routeRequest() { return &d->m_routeRequest; } RoutingManager::State RoutingManager::state() const { return d->m_state; } void RoutingManager::retrieveRoute() { d->m_haveRoute = false; int realSize = 0; for ( int i = 0; i < d->m_routeRequest.size(); ++i ) { // Sort out dummy targets if ( d->m_routeRequest.at( i ).isValid() ) { ++realSize; } } d->m_alternativeRoutesModel.newRequest( &d->m_routeRequest ); if ( realSize > 1 ) { d->m_state = RoutingManager::Downloading; d->m_runnerManager.retrieveRoute( &d->m_routeRequest ); } else { d->m_routingModel.clear(); d->m_state = RoutingManager::Retrieved; } emit stateChanged( d->m_state ); } void RoutingManagerPrivate::addRoute( GeoDataDocument* route ) { if ( route ) { m_alternativeRoutesModel.addRoute( route ); } if ( !m_haveRoute ) { m_haveRoute = route != nullptr; } emit q->routeRetrieved( route ); } void RoutingManagerPrivate::routingFinished() { m_state = RoutingManager::Retrieved; emit q->stateChanged( m_state ); } void RoutingManagerPrivate::setCurrentRoute(const GeoDataDocument *document) { QVector segments; RouteSegment outline; if (document != nullptr) { const auto folders = document->folderList(); for (const auto folder : folders) { for (const auto placemark : folder->placemarkList()) { importPlacemark(outline, segments, placemark); } } for (const auto placemark : document->placemarkList()) { importPlacemark(outline, segments, placemark); } } if ( segments.isEmpty() ) { segments << outline; } // Map via points onto segments if ( m_routeRequest.size() > 1 && segments.size() > 1 ) { int index = 0; for ( int j = 0; j < m_routeRequest.size(); ++j ) { QPair minimum( -1, -1.0 ); int viaIndex = -1; for ( int i = index; i < segments.size(); ++i ) { const RouteSegment &segment = segments[i]; GeoDataCoordinates closest; const qreal distance = segment.distanceTo( m_routeRequest.at( j ), closest, closest ); if ( minimum.first < 0 || distance < minimum.second ) { minimum.first = i; minimum.second = distance; viaIndex = j; } } if ( minimum.first >= 0 ) { index = minimum.first; Maneuver viaPoint = segments[ minimum.first ].maneuver(); viaPoint.setWaypoint( m_routeRequest.at( viaIndex ), viaIndex ); segments[ minimum.first ].setManeuver( viaPoint ); } } } Route route; if ( segments.size() > 0 ) { for( const RouteSegment &segment: segments ) { route.addRouteSegment( segment ); } } m_routingModel.setRoute( route ); } void RoutingManagerPrivate::importPlacemark( RouteSegment &outline, QVector &segments, const GeoDataPlacemark *placemark ) { const GeoDataGeometry* geometry = placemark->geometry(); const GeoDataLineString* lineString = dynamic_cast( geometry ); QStringList blacklist = QStringList() << "" << "Route" << "Tessellated"; RouteSegment segment; bool isOutline = true; if ( !blacklist.contains( placemark->name() ) ) { if( lineString ) { Maneuver maneuver; maneuver.setInstructionText( placemark->name() ); maneuver.setPosition( lineString->at( 0 ) ); if (placemark->extendedData().contains(QStringLiteral("turnType"))) { QVariant turnType = placemark->extendedData().value(QStringLiteral("turnType")).value(); // The enum value is converted to/from an int in the QVariant // because only a limited set of data types can be serialized with QVariant's // toString() method (which is used to serialize / values) maneuver.setDirection( Maneuver::Direction( turnType.toInt() ) ); } if (placemark->extendedData().contains(QStringLiteral("roadName"))) { QVariant roadName = placemark->extendedData().value(QStringLiteral("roadName")).value(); maneuver.setRoadName( roadName.toString() ); } segment.setManeuver( maneuver ); isOutline = false; } } if ( lineString ) { segment.setPath( *lineString ); if ( isOutline ) { outline = segment; } else { segments.push_back( segment ); } } } AlternativeRoutesModel* RoutingManager::alternativeRoutesModel() { return &d->m_alternativeRoutesModel; } void RoutingManager::writeSettings() const { d->saveRoute( d->stateFile() ); } void RoutingManager::saveRoute( const QString &filename ) const { d->saveRoute( filename ); } void RoutingManager::loadRoute( const QString &filename ) { d->loadRoute( filename ); } RoutingProfile RoutingManager::defaultProfile( RoutingProfile::TransportType transportType ) const { RoutingProfile profile; RoutingProfilesModel::ProfileTemplate tpl = RoutingProfilesModel::CarFastestTemplate; switch ( transportType ) { case RoutingProfile::Motorcar: tpl = RoutingProfilesModel::CarFastestTemplate; profile.setName(QStringLiteral("Motorcar")); profile.setTransportType( RoutingProfile::Motorcar ); break; case RoutingProfile::Bicycle: tpl = RoutingProfilesModel::BicycleTemplate; profile.setName(QStringLiteral("Bicycle")); profile.setTransportType( RoutingProfile::Bicycle ); break; case RoutingProfile::Pedestrian: tpl = RoutingProfilesModel::PedestrianTemplate; profile.setName(QStringLiteral("Pedestrian")); profile.setTransportType( RoutingProfile::Pedestrian ); break; } for( RoutingRunnerPlugin* plugin: d->m_pluginManager->routingRunnerPlugins() ) { if ( plugin->supportsTemplate( tpl ) ) { profile.pluginSettings()[plugin->nameId()] = plugin->templateSettings( tpl ); } } return profile; } void RoutingManager::readSettings() { d->loadRoute( d->stateFile() ); } void RoutingManager::setGuidanceModeEnabled( bool enabled ) { if ( d->m_guidanceModeEnabled == enabled ) { return; } d->m_guidanceModeEnabled = enabled; if ( enabled ) { d->saveRoute( d->stateFile( "guidance.kml" ) ); if ( d->m_guidanceModeWarning ) { QString text = QLatin1String("

") + tr("Caution: Driving instructions may be incomplete or wrong.") + QLatin1Char(' ') + tr("Road construction, weather and other unforeseen variables can result in the suggested route not to be the most expedient or safest route to your destination.") + QLatin1Char(' ') + tr("Please use common sense while navigating.") + QLatin1String("

") + QLatin1String("

") + tr("The Marble development team wishes you a pleasant and safe journey.") + QLatin1String("

"); QPointer messageBox = new QMessageBox(QMessageBox::Information, tr("Guidance Mode"), text, QMessageBox::Ok); QCheckBox *showAgain = new QCheckBox( tr( "Show again" ) ); showAgain->setChecked( true ); showAgain->blockSignals( true ); // otherwise it'd close the dialog messageBox->addButton( showAgain, QMessageBox::ActionRole ); const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; messageBox->resize( 380, smallScreen ? 400 : 240 ); messageBox->exec(); if ( !messageBox.isNull() ) { d->m_guidanceModeWarning = showAgain->isChecked(); } delete messageBox; } } else { d->loadRoute( d->stateFile( "guidance.kml" ) ); } PositionProviderPlugin* positionProvider = d->m_positionTracking->positionProviderPlugin(); if ( !positionProvider && enabled ) { QList plugins = d->m_pluginManager->positionProviderPlugins(); if ( plugins.size() > 0 ) { positionProvider = plugins.first()->newInstance(); } d->m_positionTracking->setPositionProviderPlugin( positionProvider ); d->m_shutdownPositionTracking = true; } else if ( positionProvider && !enabled && d->m_shutdownPositionTracking ) { d->m_shutdownPositionTracking = false; d->m_positionTracking->setPositionProviderPlugin( nullptr ); } emit guidanceModeEnabledChanged( d->m_guidanceModeEnabled ); } void RoutingManagerPrivate::recalculateRoute( bool deviated ) { if ( m_guidanceModeEnabled && deviated ) { for ( int i=m_routeRequest.size()-3; i>=0; --i ) { if ( m_routeRequest.visited( i ) ) { m_routeRequest.remove( i ); } } if ( m_routeRequest.size() == 2 && m_routeRequest.visited( 0 ) && !m_routeRequest.visited( 1 ) ) { m_routeRequest.setPosition( 0, m_positionTracking->currentLocation(), QObject::tr( "Current Location" ) ); q->retrieveRoute(); } else if ( m_routeRequest.size() != 0 && !m_routeRequest.visited( m_routeRequest.size()-1 ) ) { m_routeRequest.insert( 0, m_positionTracking->currentLocation(), QObject::tr( "Current Location" ) ); q->retrieveRoute(); } } } void RoutingManager::reverseRoute() { d->m_routeRequest.reverse(); retrieveRoute(); } void RoutingManager::clearRoute() { d->m_routeRequest.clear(); retrieveRoute(); } void RoutingManager::setShowGuidanceModeStartupWarning( bool show ) { d->m_guidanceModeWarning = show; } bool RoutingManager::showGuidanceModeStartupWarning() const { return d->m_guidanceModeWarning; } void RoutingManager::setLastOpenPath( const QString &path ) { d->m_lastOpenPath = path; } QString RoutingManager::lastOpenPath() const { return d->m_lastOpenPath; } void RoutingManager::setLastSavePath( const QString &path ) { d->m_lastSavePath = path; } QString RoutingManager::lastSavePath() const { return d->m_lastSavePath; } void RoutingManager::setRouteColorStandard( const QColor& color ) { d->m_routeColorStandard = color; } QColor RoutingManager::routeColorStandard() const { return d->m_routeColorStandard; } void RoutingManager::setRouteColorHighlighted( const QColor& color ) { d->m_routeColorHighlighted = color; } QColor RoutingManager::routeColorHighlighted() const { return d->m_routeColorHighlighted; } void RoutingManager::setRouteColorAlternative( const QColor& color ) { d->m_routeColorAlternative = color; } QColor RoutingManager::routeColorAlternative() const { return d->m_routeColorAlternative; } bool RoutingManager::guidanceModeEnabled() const { return d->m_guidanceModeEnabled; } } // namespace Marble #include "moc_RoutingManager.cpp" diff --git a/src/plugins/render/annotate/AreaAnnotation.cpp b/src/plugins/render/annotate/AreaAnnotation.cpp index be86e82c9..1a902b061 100644 --- a/src/plugins/render/annotate/AreaAnnotation.cpp +++ b/src/plugins/render/annotate/AreaAnnotation.cpp @@ -1,1525 +1,1526 @@ // // 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 // Copyright 2013 Thibaut Gridel // Copyright 2014 Calin Cruceru // // Self #include "AreaAnnotation.h" // Qt #include #include #include #include // Marble #include "GeoDataPlacemark.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoPainter.h" #include "ViewportParams.h" #include "SceneGraphicsTypes.h" +#include "MarbleColors.h" #include "MergingPolygonNodesAnimation.h" #include "PolylineNode.h" #include "osm/OsmPlacemarkData.h" namespace Marble { const int AreaAnnotation::regularDim = 15; const int AreaAnnotation::selectedDim = 15; const int AreaAnnotation::mergedDim = 20; const int AreaAnnotation::hoveredDim = 20; const QColor AreaAnnotation::regularColor = Oxygen::aluminumGray3; const QColor AreaAnnotation::mergedColor = Oxygen::emeraldGreen6; AreaAnnotation::AreaAnnotation( GeoDataPlacemark *placemark ) : SceneGraphicsItem( placemark ), m_viewport( nullptr ), m_regionsInitialized( false ), m_busy( false ), m_hoveredNode( -1, -1 ), m_interactingObj( InteractingNothing ), m_virtualHovered( -1, -1 ) { setPaintLayers(QStringList() << "AreaAnnotation"); } AreaAnnotation::~AreaAnnotation() { delete m_animation; } void AreaAnnotation::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel) { Q_UNUSED(layer); Q_UNUSED(tileZoomLevel); m_viewport = viewport; Q_ASSERT(geodata_cast(placemark()->geometry())); painter->save(); if ( state() == SceneGraphicsItem::DrawingPolygon || !m_regionsInitialized ) { setupRegionsLists( painter ); m_regionsInitialized = true; } else { updateRegions( painter ); } if ( hasFocus() ) { drawNodes( painter ); } painter->restore(); } bool AreaAnnotation::containsPoint( const QPoint &point ) const { if ( m_busy ) { return false; } if ( state() == SceneGraphicsItem::Editing ) { return ( polygonContains( point ) && innerBoundsContain( point ) == -1 ) || outerNodeContains( point ) != -1 || innerNodeContains( point ) != QPair( -1, -1 ); } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { return polygonContains( point ) && outerNodeContains( point ) == -1 && innerNodeContains( point ) == QPair( -1, -1 ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return outerNodeContains( point ) != -1 || innerNodeContains( point ) != QPair( -1, -1 ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return ( polygonContains( point ) && innerBoundsContain( point ) == -1 ) || virtualNodeContains( point ) != QPair( -1, -1 ) || innerNodeContains( point ) != QPair( -1, -1 ) || outerNodeContains( point ) != -1; } return false; } void AreaAnnotation::dealWithItemChange( const SceneGraphicsItem *other ) { Q_UNUSED( other ); // So far we only deal with item changes when hovering nodes, so that // they do not remain hovered when changing the item we interact with. if ( state() == SceneGraphicsItem::Editing ) { if ( m_hoveredNode != QPair( -1, -1 ) ) { const int i = m_hoveredNode.first; const int j = m_hoveredNode.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } else { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } } m_hoveredNode = QPair( -1, -1 ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { if ( m_hoveredNode != QPair( -1, -1 ) ) { const int i = m_hoveredNode.first; const int j = m_hoveredNode.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } else { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } } m_hoveredNode = QPair( -1, -1 ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { m_virtualHovered = QPair( -1, -1 ); } } void AreaAnnotation::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination ) { GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing outerRing = polygon->outerBoundary(); QVector innerRings = polygon->innerBoundaries(); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } polygon->outerBoundary().clear(); polygon->innerBoundaries().clear(); const qreal deltaLat = destination.latitude() - source.latitude(); const qreal deltaLon = destination.longitude() - source.longitude(); Quaternion latRectAxis = Quaternion::fromEuler( 0, destination.longitude(), 0); Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0); Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0); Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis; qreal lonRotated, latRotated; for ( int i = 0; i < outerRing.size(); ++i ) { Quaternion qpos = outerRing.at(i).quaternion(); qpos.rotateAroundAxis(rotAxis); qpos.getSpherical( lonRotated, latRotated ); GeoDataCoordinates movedPoint( lonRotated, latRotated, 0 ); // Keeping the OsmPlacemarkData synchronized with the geometry if ( osmData ) { osmData->memberReference( -1 ).changeNodeReference( outerRing.at( i ), movedPoint ); } polygon->outerBoundary().append( movedPoint ); } for ( int i = 0; i < innerRings.size(); ++i ) { GeoDataLinearRing newRing( Tessellate ); for ( int j = 0; j < innerRings.at(i).size(); ++j ) { Quaternion qpos = innerRings.at(i).at(j).quaternion(); qpos.rotateAroundAxis(rotAxis); qpos.getSpherical( lonRotated, latRotated ); GeoDataCoordinates movedPoint( lonRotated, latRotated, 0 ); if ( osmData ) { osmData->memberReference( i ).changeNodeReference( innerRings.at( i ).at( j ), movedPoint ); } newRing.append( movedPoint ); } polygon->innerBoundaries().append( newRing ); } } void AreaAnnotation::setBusy( bool enabled ) { m_busy = enabled; if ( !enabled && m_animation && state() == SceneGraphicsItem::MergingNodes ) { // Update the PolylineNodes lists after the animation has finished its execution. const int ff = m_firstMergedNode.first; const int fs = m_firstMergedNode.second; const int sf = m_secondMergedNode.first; const int ss = m_secondMergedNode.second; if ( ff != -1 && fs == -1 && sf != -1 && ss == -1 ) { m_outerNodesList[sf].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); m_hoveredNode = QPair( -1, -1 ); // Remove the merging node flag and add the NodeIsSelected flag if either one of the // merged nodes had been selected before merging them. m_outerNodesList[sf].setFlag( PolylineNode::NodeIsMerged, false ); if ( m_outerNodesList.at(ff).isSelected() ) { m_outerNodesList[sf].setFlag( PolylineNode::NodeIsSelected ); } m_outerNodesList.removeAt( ff ); m_firstMergedNode = QPair( -1, -1 ); m_secondMergedNode = QPair( -1, -1 ); } else if ( ff != -1 && fs != -1 && sf != -1 && ss != -1 ) { m_innerNodesList[sf][ss].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); m_hoveredNode = QPair( -1, -1 ); m_innerNodesList[sf][ss].setFlag( PolylineNode::NodeIsMerged, false ); if ( m_innerNodesList.at(ff).at(fs).isSelected() ) { m_innerNodesList[sf][ss].setFlag( PolylineNode::NodeIsSelected ); } m_innerNodesList[sf].removeAt( fs ); m_firstMergedNode = QPair( -1, -1 ); m_secondMergedNode = QPair( -1, -1 ); } delete m_animation; } } bool AreaAnnotation::isBusy() const { return m_busy; } void AreaAnnotation::deselectAllNodes() { if ( state() != SceneGraphicsItem::Editing ) { return; } for ( int i = 0 ; i < m_outerNodesList.size(); ++i ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsSelected, false ); } for ( int i = 0; i < m_innerNodesList.size(); ++i ) { for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsSelected, false ); } } } void AreaAnnotation::deleteAllSelectedNodes() { if ( state() != SceneGraphicsItem::Editing ) { return; } GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing &outerRing = polygon->outerBoundary(); QVector &innerRings = polygon->innerBoundaries(); OsmPlacemarkData *osmData = nullptr; OsmPlacemarkData initialOsmData; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); initialOsmData = placemark()->osmData(); } // If it proves inefficient, try something different. GeoDataLinearRing initialOuterRing = polygon->outerBoundary(); QVector initialInnerRings = polygon->innerBoundaries(); const QVector initialOuterNodes = m_outerNodesList; const QVector< QVector > initialInnerNodes = m_innerNodesList; for ( int i = 0; i < outerRing.size(); ++i ) { if ( m_outerNodesList.at(i).isSelected() ) { if ( m_outerNodesList.size() <= 3 ) { setRequest( SceneGraphicsItem::RemovePolygonRequest ); return; } if ( osmData ) { osmData->memberReference( -1 ).removeNodeReference( initialOuterRing.at( i ) ); } m_outerNodesList.removeAt( i ); outerRing.remove( i ); --i; } } for ( int i = 0; i < innerRings.size(); ++i ) { for ( int j = 0; j < innerRings.at(i).size(); ++j ) { if ( m_innerNodesList.at(i).at(j).isSelected() ) { if ( m_innerNodesList.at(i).size() <= 3 ) { if ( osmData ) { osmData->removeMemberReference( i ); } innerRings.remove( i ); m_innerNodesList.removeAt( i ); --i; break; } if ( osmData ) { osmData->memberReference( i ).removeNodeReference( initialInnerRings.at( i ).at( j ) ); } innerRings[i].remove( j ); m_innerNodesList[i].removeAt( j ); --j; } } } if ( !isValidPolygon() ) { if ( osmData ) { placemark()->setOsmData( initialOsmData ); } polygon->outerBoundary() = initialOuterRing; polygon->innerBoundaries() = initialInnerRings; m_outerNodesList = initialOuterNodes; m_innerNodesList = initialInnerNodes; setRequest( SceneGraphicsItem::InvalidShapeWarning ); } } void AreaAnnotation::deleteClickedNode() { if ( state() != SceneGraphicsItem::Editing ) { return; } GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing &outerRing = polygon->outerBoundary(); QVector &innerRings = polygon->innerBoundaries(); OsmPlacemarkData *osmData = nullptr; OsmPlacemarkData initialOsmData; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); initialOsmData = placemark()->osmData(); } // If it proves inefficient, try something different. GeoDataLinearRing initialOuterRing = polygon->outerBoundary(); QVector initialInnerRings = polygon->innerBoundaries(); const QVector initialOuterNodes = m_outerNodesList; const QVector< QVector > initialInnerNodes = m_innerNodesList; int i = m_clickedNodeIndexes.first; int j = m_clickedNodeIndexes.second; m_hoveredNode = QPair( -1, -1 ); if ( i != -1 && j == -1 ) { if ( m_outerNodesList.size() <= 3 ) { setRequest( SceneGraphicsItem::RemovePolygonRequest ); return; } // Keep the OsmPlacemarkData synchronized with the geometry if ( osmData ) { osmData->removeNodeReference( outerRing.at( i ) ); } outerRing.remove( i ); m_outerNodesList.removeAt( i ); } else if ( i != -1 && j != -1 ) { if ( m_innerNodesList.at(i).size() <= 3 ) { if ( osmData ) { osmData->removeMemberReference( i ); } innerRings.remove( i ); m_innerNodesList.removeAt( i ); return; } if ( osmData ) { osmData->memberReference( i ).removeNodeReference( innerRings.at( i ).at( j ) ); } innerRings[i].remove( j ); m_innerNodesList[i].removeAt( j ); } if ( !isValidPolygon() ) { if ( osmData ) { placemark()->setOsmData( initialOsmData ); } polygon->outerBoundary() = initialOuterRing; polygon->innerBoundaries() = initialInnerRings; m_outerNodesList = initialOuterNodes; m_innerNodesList = initialInnerNodes; setRequest( SceneGraphicsItem::InvalidShapeWarning ); } } void AreaAnnotation::changeClickedNodeSelection() { if ( state() != SceneGraphicsItem::Editing ) { return; } const int i = m_clickedNodeIndexes.first; const int j = m_clickedNodeIndexes.second; if ( i != -1 && j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsSelected, !m_outerNodesList.at(i).isSelected() ); } else if ( i != -1 && j != -1 ) { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsSelected, !m_innerNodesList.at(i).at(j).isSelected() ); } } bool AreaAnnotation::hasNodesSelected() const { for ( int i = 0; i < m_outerNodesList.size(); ++i ) { if ( m_outerNodesList.at(i).isSelected() ) { return true; } } for ( int i = 0; i < m_innerNodesList.size(); ++i ) { for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { if ( m_innerNodesList.at(i).at(j).isSelected() ) { return true; } } } return false; } bool AreaAnnotation::clickedNodeIsSelected() const { const int i = m_clickedNodeIndexes.first; const int j = m_clickedNodeIndexes.second; return ( i != -1 && j == -1 && m_outerNodesList.at(i).isSelected() ) || ( i != -1 && j != -1 && m_innerNodesList.at(i).at(j).isSelected() ); } QPointer AreaAnnotation::animation() { return m_animation; } bool AreaAnnotation::mousePressEvent( QMouseEvent *event ) { if ( !m_viewport || m_busy ) { return false; } setRequest( SceneGraphicsItem::NoRequest ); if ( state() == SceneGraphicsItem::Editing ) { return processEditingOnPress( event ); } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { return processAddingHoleOnPress( event ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return processMergingOnPress( event ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return processAddingNodesOnPress( event ); } return false; } bool AreaAnnotation::mouseMoveEvent( QMouseEvent *event ) { if ( !m_viewport || m_busy ) { return false; } setRequest( SceneGraphicsItem::NoRequest ); if ( state() == SceneGraphicsItem::Editing ) { return processEditingOnMove( event ); } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { return processAddingHoleOnMove( event ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return processMergingOnMove( event ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return processAddingNodesOnMove( event ); } return false; } bool AreaAnnotation::mouseReleaseEvent( QMouseEvent *event ) { if ( !m_viewport || m_busy ) { return false; } setRequest( SceneGraphicsItem::NoRequest ); if ( state() == SceneGraphicsItem::Editing ) { return processEditingOnRelease( event ); } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { return processAddingHoleOnRelease( event ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return processMergingOnRelease( event ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return processAddingNodesOnRelease( event ); } return false; } void AreaAnnotation::dealWithStateChange( SceneGraphicsItem::ActionState previousState ) { // Dealing with cases when exiting a state has an effect on this item. if ( previousState == SceneGraphicsItem::Editing ) { // Make sure that when changing the state, there is no highlighted node. if ( m_hoveredNode != QPair( -1, -1 ) ) { const int i = m_hoveredNode.first; const int j = m_hoveredNode.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } else { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } } m_clickedNodeIndexes = QPair( -1, -1 ); m_hoveredNode = QPair( -1, -1 ); } else if ( previousState == SceneGraphicsItem::AddingPolygonHole ) { // Check if a polygon hole was being drawn before changing state. GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); QVector &innerBounds = polygon->innerBoundaries(); if ( innerBounds.size() && innerBounds.last().size() <= 2 ) { // If only two nodes were added, remove this inner boundary entirely. innerBounds.remove( innerBounds.size() - 1 ); m_innerNodesList.removeLast(); return; } } else if ( previousState == SceneGraphicsItem::MergingNodes ) { // If there was only a node selected for being merged and the state changed, // deselect it. const int i = m_firstMergedNode.first; const int j = m_firstMergedNode.second; if ( i != -1 && j != -1 ) { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMerged, false ); } else if ( i != -1 && j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsMerged, false ); } // Make sure that when changing the state, there is no highlighted node. if ( m_hoveredNode != QPair( -1, -1 ) ) { int i = m_hoveredNode.first; int j = m_hoveredNode.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } else { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } } m_firstMergedNode = QPair( -1, -1 ); m_hoveredNode = QPair( -1, -1 ); delete m_animation; } else if ( previousState == SceneGraphicsItem::AddingNodes ) { m_outerVirtualNodes.clear(); m_innerVirtualNodes.clear(); m_virtualHovered = QPair( -1, -1 ); m_adjustedNode = -2; } // Dealing with cases when entering a state has an effect on this item, or // initializations are needed. if ( state() == SceneGraphicsItem::Editing ) { m_interactingObj = InteractingNothing; m_clickedNodeIndexes = QPair( -1, -1 ); m_hoveredNode = QPair( -1, -1 ); } else if ( state() == SceneGraphicsItem::AddingPolygonHole ) { // Nothing to do so far when entering this state. GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); QVector &innerBounds = polygon->innerBoundaries(); m_innerNodesList.append(QVector()); innerBounds.append( GeoDataLinearRing( Tessellate ) ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { m_firstMergedNode = QPair( -1, -1 ); m_secondMergedNode = QPair( -1, -1 ); m_hoveredNode = QPair( -1, -1 ); m_animation = nullptr; } else if ( state() == SceneGraphicsItem::AddingNodes ) { m_virtualHovered = QPair( -1, -1 ); m_adjustedNode = -2; } } const char *AreaAnnotation::graphicType() const { return SceneGraphicsTypes::SceneGraphicAreaAnnotation; } bool AreaAnnotation::isValidPolygon() const { const GeoDataPolygon *poly = static_cast( placemark()->geometry() ); const QVector &innerRings = poly->innerBoundaries(); for ( const GeoDataLinearRing &innerRing: innerRings ) { for ( int i = 0; i < innerRing.size(); ++i ) { if ( !poly->outerBoundary().contains( innerRing.at(i) ) ) { return false; } } } return true; } void AreaAnnotation::setupRegionsLists( GeoPainter *painter ) { const GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); const GeoDataLinearRing &outerRing = polygon->outerBoundary(); const QVector &innerRings = polygon->innerBoundaries(); // Add the outer boundary nodes. QVector::ConstIterator itBegin = outerRing.constBegin(); QVector::ConstIterator itEnd = outerRing.constEnd(); m_outerNodesList.clear(); m_innerNodesList.clear(); m_boundariesList.clear(); for ( ; itBegin != itEnd; ++itBegin ) { const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) ); m_outerNodesList.append( newNode ); } for ( const GeoDataLinearRing &innerRing: innerRings ) { QVector::ConstIterator itBegin = innerRing.constBegin(); QVector::ConstIterator itEnd = innerRing.constEnd(); QVector innerNodes; innerNodes.reserve(innerRing.size()); for ( ; itBegin != itEnd; ++itBegin ) { const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) ); innerNodes.append( newNode ); } m_innerNodesList.append( innerNodes ); } // Add the outer boundary to the boundaries list. m_boundariesList.append( painter->regionFromPolygon( outerRing, Qt::OddEvenFill ) ); } void AreaAnnotation::updateRegions( GeoPainter *painter ) { if ( m_busy ) { return; } const GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); const GeoDataLinearRing &outerRing = polygon->outerBoundary(); const QVector &innerRings = polygon->innerBoundaries(); if ( state() == SceneGraphicsItem::AddingNodes ) { // Create and update virtual nodes lists when being in the AddingPolgonNodes state, to // avoid overhead in other states. m_outerVirtualNodes.clear(); const QRegion firstRegion( painter->regionFromEllipse( outerRing.first().interpolate( outerRing.last(), 0.5 ), hoveredDim, hoveredDim ) ); m_outerVirtualNodes.append( PolylineNode( firstRegion ) ); for ( int i = 0; i < outerRing.size() - 1; ++i ) { const QRegion newRegion( painter->regionFromEllipse( outerRing.at(i).interpolate( outerRing.at(i+1), 0.5 ), hoveredDim, hoveredDim ) ); m_outerVirtualNodes.append( PolylineNode( newRegion ) ); } m_innerVirtualNodes.clear(); m_innerVirtualNodes.reserve(innerRings.size()); for ( int i = 0; i < innerRings.size(); ++i ) { m_innerVirtualNodes.append(QVector()); const QRegion firstRegion( painter->regionFromEllipse( innerRings.at(i).first().interpolate( innerRings.at(i).last(), 0.5 ), hoveredDim, hoveredDim ) ); m_innerVirtualNodes[i].append( PolylineNode( firstRegion ) ); for ( int j = 0; j < innerRings.at(i).size() - 1; ++j ) { const QRegion newRegion( painter->regionFromEllipse( innerRings.at(i).at(j).interpolate( innerRings.at(i).at(j+1), 0.5 ), hoveredDim, hoveredDim ) ); m_innerVirtualNodes[i].append( PolylineNode( newRegion ) ); } } } // Update the boundaries list. m_boundariesList.clear(); m_boundariesList.reserve(1 + innerRings.size()); m_boundariesList.append( painter->regionFromPolygon( outerRing, Qt::OddEvenFill ) ); for ( const GeoDataLinearRing &ring: innerRings ) { m_boundariesList.append( painter->regionFromPolygon( ring, Qt::OddEvenFill ) ); } // Update the outer and inner nodes lists. Q_ASSERT( m_outerNodesList.size() == outerRing.size() ); for ( int i = 0; i < m_outerNodesList.size(); ++i ) { const QRegion newRegion = m_outerNodesList.at(i).isSelected() ? painter->regionFromEllipse( outerRing.at(i), selectedDim, selectedDim ) : painter->regionFromEllipse( outerRing.at(i), regularDim, regularDim ); m_outerNodesList[i].setRegion( newRegion ); } Q_ASSERT( m_innerNodesList.size() == innerRings.size() ); for ( int i = 0; i < m_innerNodesList.size(); ++i ) { Q_ASSERT( m_innerNodesList.at(i).size() == innerRings.at(i).size() ); for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { const QRegion newRegion = m_innerNodesList.at(i).at(j).isSelected() ? painter->regionFromEllipse( innerRings.at(i).at(j), selectedDim, selectedDim ) : painter->regionFromEllipse( innerRings.at(i).at(j), regularDim, regularDim ); m_innerNodesList[i][j].setRegion( newRegion ); } } } void AreaAnnotation::drawNodes( GeoPainter *painter ) { // These are the 'real' dimensions of the drawn nodes. The ones which have class scope are used // to generate the regions and they are a little bit larger, because, for example, it would be // a little bit too hard to select nodes. static const int d_regularDim = 10; static const int d_selectedDim = 10; static const int d_mergedDim = 20; static const int d_hoveredDim = 20; const GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); const GeoDataLinearRing &outerRing = polygon->outerBoundary(); const QVector &innerRings = polygon->innerBoundaries(); QColor glowColor = QApplication::palette().highlightedText().color(); glowColor.setAlpha(120); auto const selectedColor = QApplication::palette().highlight().color(); auto const hoveredColor = selectedColor; for ( int i = 0; i < outerRing.size(); ++i ) { // The order here is important, because a merged node can be at the same time selected. if ( m_outerNodesList.at(i).isBeingMerged() ) { painter->setBrush( mergedColor ); painter->drawEllipse( outerRing.at(i), d_mergedDim, d_mergedDim ); } else if ( m_outerNodesList.at(i).isSelected() ) { painter->setBrush( selectedColor ); painter->drawEllipse( outerRing.at(i), d_selectedDim, d_selectedDim ); if ( m_outerNodesList.at(i).isEditingHighlighted() || m_outerNodesList.at(i).isMergingHighlighted() ) { QPen defaultPen = painter->pen(); QPen newPen; newPen.setWidth( defaultPen.width() + 3 ); newPen.setColor( glowColor ); painter->setBrush( Qt::NoBrush ); painter->setPen( newPen ); painter->drawEllipse( outerRing.at(i), d_selectedDim + 2, d_selectedDim + 2 ); painter->setPen( defaultPen ); } } else { painter->setBrush( regularColor ); painter->drawEllipse( outerRing.at(i), d_regularDim, d_regularDim ); if ( m_outerNodesList.at(i).isEditingHighlighted() || m_outerNodesList.at(i).isMergingHighlighted() ) { QPen defaultPen = painter->pen(); QPen newPen; newPen.setWidth( defaultPen.width() + 3 ); newPen.setColor( glowColor ); painter->setPen( newPen ); painter->setBrush( Qt::NoBrush ); painter->drawEllipse( outerRing.at(i), d_regularDim + 2, d_regularDim + 2 ); painter->setPen( defaultPen ); } } } for ( int i = 0; i < innerRings.size(); ++i ) { for ( int j = 0; j < innerRings.at(i).size(); ++j ) { if ( m_innerNodesList.at(i).at(j).isBeingMerged() ) { painter->setBrush( mergedColor ); painter->drawEllipse( innerRings.at(i).at(j), d_mergedDim, d_mergedDim ); } else if ( m_innerNodesList.at(i).at(j).isSelected() ) { painter->setBrush( selectedColor ); painter->drawEllipse( innerRings.at(i).at(j), d_selectedDim, d_selectedDim ); if ( m_innerNodesList.at(i).at(j).isEditingHighlighted() || m_innerNodesList.at(i).at(j).isMergingHighlighted() ) { QPen defaultPen = painter->pen(); QPen newPen; newPen.setWidth( defaultPen.width() + 3 ); newPen.setColor( glowColor ); painter->setBrush( Qt::NoBrush ); painter->setPen( newPen ); painter->drawEllipse( innerRings.at(i).at(j), d_selectedDim + 2, d_selectedDim + 2 ); painter->setPen( defaultPen ); } } else { painter->setBrush( regularColor ); painter->drawEllipse( innerRings.at(i).at(j), d_regularDim, d_regularDim ); if ( m_innerNodesList.at(i).at(j).isEditingHighlighted() || m_innerNodesList.at(i).at(j).isMergingHighlighted() ) { QPen defaultPen = painter->pen(); QPen newPen; newPen.setWidth( defaultPen.width() + 3 ); newPen.setColor( glowColor ); painter->setBrush( Qt::NoBrush ); painter->setPen( newPen ); painter->drawEllipse( innerRings.at(i).at(j), d_regularDim + 2, d_regularDim + 2 ); painter->setPen( defaultPen ); } } } } if ( m_virtualHovered != QPair( -1, -1 ) ) { const int i = m_virtualHovered.first; const int j = m_virtualHovered.second; painter->setBrush( hoveredColor ); if ( i != -1 && j == -1 ) { const GeoDataCoordinates coords = i ? outerRing.at(i).interpolate( outerRing.at(i - 1), 0.5 ) : outerRing.first().interpolate( outerRing.last(), 0.5 ); painter->drawEllipse( coords, d_hoveredDim, d_hoveredDim ); } else { Q_ASSERT( i != -1 && j != -1 ); const GeoDataCoordinates coords = j ? innerRings.at(i).at(j).interpolate( innerRings.at(i).at(j - 1), 0.5 ) : innerRings.at(i).first().interpolate( innerRings.at(i).last(), 0.5 ); painter->drawEllipse( coords, d_hoveredDim, d_hoveredDim ); } } } int AreaAnnotation::outerNodeContains( const QPoint &point ) const { if ( !hasFocus() ) { return -1; } for ( int i = 0; i < m_outerNodesList.size(); ++i ) { if ( m_outerNodesList.at(i).containsPoint( point ) ) { return i; } } return -1; } QPair AreaAnnotation::innerNodeContains( const QPoint &point ) const { if ( !hasFocus() ) { return QPair( -1, -1 ); } for ( int i = 0; i < m_innerNodesList.size(); ++i ) { for ( int j = 0; j < m_innerNodesList.at(i).size(); ++j ) { if ( m_innerNodesList.at(i).at(j).containsPoint( point ) ) { return QPair( i, j ); } } } return QPair( -1, -1 ); } QPair AreaAnnotation::virtualNodeContains( const QPoint &point ) const { if ( !hasFocus() ) { return QPair( -1, -1 ); } for ( int i = 0; i < m_outerVirtualNodes.size(); ++i ) { if ( m_outerVirtualNodes.at(i).containsPoint( point ) ) { return QPair( i, -1 ); } } for ( int i = 0; i < m_innerVirtualNodes.size(); ++i ) { for ( int j = 0; j < m_innerVirtualNodes.at(i).size(); ++j ) { if ( m_innerVirtualNodes.at(i).at(j).containsPoint( point ) ) { return QPair( i, j ); } } } return QPair( -1, -1 ); } int AreaAnnotation::innerBoundsContain( const QPoint &point ) const { // There are no inner boundaries. if ( m_boundariesList.size() == 1 ) { return -1; } // Starting from 1 because on index 0 is stored the region representing the whole polygon. for ( int i = 1; i < m_boundariesList.size(); ++i ) { if ( m_boundariesList.at(i).contains( point ) ) { return i; } } return -1; } bool AreaAnnotation::polygonContains( const QPoint &point ) const { return m_boundariesList.first().contains( point ); } bool AreaAnnotation::processEditingOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton && mouseEvent->button() != Qt::RightButton ) { return false; } qreal lat, lon; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); m_movedPointCoords.set( lon, lat ); // First check if one of the nodes from outer boundary has been clicked. const int outerIndex = outerNodeContains( mouseEvent->pos() ); if ( outerIndex != -1 ) { m_clickedNodeIndexes = QPair( outerIndex, -1 ); if ( mouseEvent->button() == Qt::RightButton ) { setRequest( SceneGraphicsItem::ShowNodeRmbMenu ); } else { m_interactingObj = InteractingNode; } return true; } // Then check if one of the nodes which form an inner boundary has been clicked. const QPair innerIndexes = innerNodeContains( mouseEvent->pos() ); if ( innerIndexes.first != -1 && innerIndexes.second != -1 ) { m_clickedNodeIndexes = innerIndexes; if ( mouseEvent->button() == Qt::RightButton ) { setRequest( SceneGraphicsItem::ShowNodeRmbMenu ); } else { m_interactingObj = InteractingNode; } return true; } // If neither outer boundary nodes nor inner boundary nodes contain the event position, // then check if the interior of the polygon (excepting its 'holes') contains this point. if ( polygonContains( mouseEvent->pos() ) && innerBoundsContain( mouseEvent->pos() ) == -1 ) { if ( mouseEvent->button() == Qt::RightButton ) { setRequest( SceneGraphicsItem::ShowPolygonRmbMenu ); } else { m_interactingObj = InteractingPolygon; } return true; } return false; } bool AreaAnnotation::processEditingOnMove( QMouseEvent *mouseEvent ) { if ( !m_viewport ) { return false; } qreal lon, lat; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); const GeoDataCoordinates newCoords( lon, lat ); const qreal deltaLat = lat - m_movedPointCoords.latitude(); const qreal deltaLon = lon - m_movedPointCoords.longitude(); if ( m_interactingObj == InteractingNode ) { GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing &outerRing = polygon->outerBoundary(); QVector &innerRings = polygon->innerBoundaries(); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } const int i = m_clickedNodeIndexes.first; const int j = m_clickedNodeIndexes.second; if ( j == -1 ) { // Keeping the osmPlacemarkData synchronized with the geometry if ( osmData ) { osmData->memberReference( -1 ).changeNodeReference( outerRing.at( i ), newCoords ); } outerRing[i] = newCoords; } else { Q_ASSERT( i != -1 && j != -1 ); if ( osmData ) { osmData->memberReference( i ).changeNodeReference( innerRings.at( i ).at( j ), newCoords ); } innerRings[i].at(j) = newCoords; } return true; } else if ( m_interactingObj == InteractingPolygon ) { GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing outerRing = polygon->outerBoundary(); QVector innerRings = polygon->innerBoundaries(); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } Quaternion latRectAxis = Quaternion::fromEuler( 0, lon, 0); Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0); Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0); Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis; polygon->outerBoundary().clear(); polygon->innerBoundaries().clear(); qreal lonRotated, latRotated; for ( int i = 0; i < outerRing.size(); ++i ) { Quaternion qpos = outerRing.at(i).quaternion(); qpos.rotateAroundAxis(rotAxis); qpos.getSpherical( lonRotated, latRotated ); GeoDataCoordinates movedPoint( lonRotated, latRotated, 0 ); if ( osmData ) { osmData->memberReference( -1 ).changeNodeReference( outerRing.at( i ), movedPoint ); } polygon->outerBoundary().append( movedPoint ); } for ( int i = 0; i < innerRings.size(); ++i ) { GeoDataLinearRing newRing( Tessellate ); for ( int j = 0; j < innerRings.at(i).size(); ++j ) { Quaternion qpos = innerRings.at(i).at(j).quaternion(); qpos.rotateAroundAxis(rotAxis); qpos.getSpherical( lonRotated, latRotated ); GeoDataCoordinates movedPoint( lonRotated, latRotated, 0 ); if ( osmData ) { osmData->memberReference( i ).changeNodeReference( innerRings.at( i ).at( j ) , movedPoint ); } newRing.append( movedPoint ); } polygon->innerBoundaries().append( newRing ); } m_movedPointCoords = newCoords; return true; } else if ( m_interactingObj == InteractingNothing ) { return dealWithHovering( mouseEvent ); } return false; } bool AreaAnnotation::processEditingOnRelease( QMouseEvent *mouseEvent ) { static const int mouseMoveOffset = 1; if ( mouseEvent->button() != Qt::LeftButton ) { return false; } if ( m_interactingObj == InteractingNode ) { qreal x, y; m_viewport->screenCoordinates( m_movedPointCoords.longitude(), m_movedPointCoords.latitude(), x, y ); // The node gets selected only if it is clicked and not moved. if ( qFabs(mouseEvent->pos().x() - x) > mouseMoveOffset || qFabs(mouseEvent->pos().y() - y) > mouseMoveOffset ) { m_interactingObj = InteractingNothing; return true; } const int i = m_clickedNodeIndexes.first; const int j = m_clickedNodeIndexes.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( PolylineNode::NodeIsSelected, !m_outerNodesList[i].isSelected() ); } else { m_innerNodesList[i][j].setFlag ( PolylineNode::NodeIsSelected, !m_innerNodesList.at(i).at(j).isSelected() ); } m_interactingObj = InteractingNothing; return true; } else if ( m_interactingObj == InteractingPolygon ) { // Nothing special happens at polygon release. m_interactingObj = InteractingNothing; return true; } return false; } bool AreaAnnotation::processAddingHoleOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton ) { return false; } qreal lon, lat; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); const GeoDataCoordinates newCoords( lon, lat ); GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); QVector &innerBounds = polygon->innerBoundaries(); innerBounds.last().append( newCoords ); m_innerNodesList.last().append( PolylineNode() ); return true; } bool AreaAnnotation::processAddingHoleOnMove( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); return true; } bool AreaAnnotation::processAddingHoleOnRelease( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); return true; } bool AreaAnnotation::processMergingOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton ) { return false; } GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing initialOuterRing = polygon->outerBoundary(); OsmPlacemarkData *osmData = nullptr; OsmPlacemarkData initialOsmData; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } GeoDataLinearRing &outerRing = polygon->outerBoundary(); QVector &innerRings = polygon->innerBoundaries(); const int outerIndex = outerNodeContains( mouseEvent->pos() ); // If the selected node is an outer boundary node. if ( outerIndex != -1 ) { // If this is the first node selected to be merged. if ( m_firstMergedNode.first == -1 && m_firstMergedNode.second == -1 ) { m_firstMergedNode = QPair( outerIndex, -1 ); m_outerNodesList[outerIndex].setFlag( PolylineNode::NodeIsMerged ); // If the first selected node was an inner boundary node, raise the request for showing // warning. } else if ( m_firstMergedNode.first != -1 && m_firstMergedNode.second != -1 ) { setRequest( SceneGraphicsItem::OuterInnerMergingWarning ); m_innerNodesList[m_firstMergedNode.first][m_firstMergedNode.second].setFlag( PolylineNode::NodeIsMerged, false ); if ( m_hoveredNode.first != -1 ) { // We can be sure that the hovered node is an outer node. Q_ASSERT( m_hoveredNode.second == -1 ); m_outerNodesList[m_hoveredNode.first].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } m_hoveredNode = m_firstMergedNode = QPair( -1, -1 ); } else { Q_ASSERT( m_firstMergedNode.first != -1 && m_firstMergedNode.second == -1 ); // Clicking two times the same node results in unmarking it for merging. if ( m_firstMergedNode.first == outerIndex ) { m_outerNodesList[outerIndex].setFlag( PolylineNode::NodeIsMerged, false ); m_firstMergedNode = QPair( -1, -1 ); return true; } // If two nodes which form a triangle are merged, the whole triangle should be // destroyed. if ( outerRing.size() <= 3 ) { setRequest( SceneGraphicsItem::RemovePolygonRequest ); return true; } GeoDataCoordinates mergedNode = outerRing.at(m_firstMergedNode.first).interpolate( outerRing.at(outerIndex), 0.5 ); // Keeping the osm data synchronized with the geometry if ( osmData ) { osmData->memberReference( -1 ).changeNodeReference( outerRing.at( outerIndex ), mergedNode ); osmData->memberReference( -1 ).removeNodeReference( outerRing.at( m_firstMergedNode.first ) ); } outerRing[outerIndex] = mergedNode; outerRing.remove( m_firstMergedNode.first ); if ( !isValidPolygon() ) { if ( osmData ) { placemark()->setOsmData( initialOsmData ); } polygon->outerBoundary() = initialOuterRing; m_outerNodesList[m_firstMergedNode.first].setFlag( PolylineNode::NodeIsMerged, false ); // Remove highlight effect before showing warning if ( m_hoveredNode.first != -1 ) { m_outerNodesList[m_hoveredNode.first].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } m_hoveredNode = m_firstMergedNode = QPair( -1, -1 ); setRequest( SceneGraphicsItem::InvalidShapeWarning ); return true; } // Do not modify it here. The animation has access to the object. It will modify the polygon. polygon->outerBoundary() = initialOuterRing; m_outerNodesList[outerIndex].setFlag( PolylineNode::NodeIsMerged ); m_secondMergedNode = QPair( outerIndex, -1 ); delete m_animation; m_animation = new MergingPolygonNodesAnimation( this ); setRequest( SceneGraphicsItem::StartPolygonAnimation ); } return true; } // If the selected node is an inner boundary node. const QPair innerIndexes = innerNodeContains( mouseEvent->pos() ); if ( innerIndexes.first != -1 && innerIndexes.second != -1 ) { const int i = m_firstMergedNode.first; const int j = m_firstMergedNode.second; // If this is the first selected node. if ( i == -1 && j == -1 ) { m_firstMergedNode = innerIndexes; m_innerNodesList[innerIndexes.first][innerIndexes.second].setFlag( PolylineNode::NodeIsMerged ); // If the first selected node has been an outer boundary one, raise the request for showing warning. } else if ( i != -1 && j == -1 ) { setRequest( SceneGraphicsItem::OuterInnerMergingWarning ); m_outerNodesList[i].setFlag( PolylineNode::NodeIsMerged, false ); if ( m_hoveredNode.first != -1 ) { // We can now be sure that the highlighted node is a node from polygon's outer boundary Q_ASSERT( m_hoveredNode.second != -1 ); m_outerNodesList[m_hoveredNode.first].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } m_firstMergedNode = QPair( -1, -1 ); } else { Q_ASSERT( i != -1 && j != -1 ); if ( i != innerIndexes.first ) { setRequest( SceneGraphicsItem::InnerInnerMergingWarning ); m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMerged, false ); if ( m_hoveredNode.first != -1 && m_hoveredNode.second != -1 ) { m_innerNodesList[m_hoveredNode.first][m_hoveredNode.second].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } m_hoveredNode = m_firstMergedNode = QPair( -1, -1 ); return true; } // Clicking two times the same node results in unmarking it for merging. if ( m_firstMergedNode == innerIndexes ) { m_innerNodesList[i][j].setFlag( PolylineNode::NodeIsMerged, false ); m_firstMergedNode = QPair( -1, -1 ); return true; } // If two nodes which form an inner boundary of a polygon with a size smaller than // 3 are merged, remove the whole inner boundary. if ( innerRings.at(i).size() <= 3 ) { innerRings.remove( i ); m_innerNodesList.removeAt( i ); m_firstMergedNode = m_secondMergedNode = m_hoveredNode = QPair( -1, -1 ); return true; } m_innerNodesList[innerIndexes.first][innerIndexes.second].setFlag( PolylineNode::NodeIsMerged ); m_secondMergedNode = innerIndexes; m_animation = new MergingPolygonNodesAnimation( this ); setRequest( SceneGraphicsItem::StartPolygonAnimation ); } return true; } return false; } bool AreaAnnotation::processMergingOnMove( QMouseEvent *mouseEvent ) { return dealWithHovering( mouseEvent ); } bool AreaAnnotation::processMergingOnRelease( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); return true; } bool AreaAnnotation::processAddingNodesOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton ) { return false; } GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); GeoDataLinearRing &outerRing = polygon->outerBoundary(); QVector &innerRings = polygon->innerBoundaries(); // If a virtual node has just been clicked, add it to the polygon's outer boundary // and start 'adjusting' its position. const QPair index = virtualNodeContains( mouseEvent->pos() ); if ( index != QPair( -1, -1 ) && m_adjustedNode == -2 ) { Q_ASSERT( m_virtualHovered == index ); const int i = index.first; const int j = index.second; if ( i != -1 && j == -1 ) { GeoDataLinearRing newRing( Tessellate ); QVector newList; newList.reserve(outerRing.size()); for ( int k = i; k < i + outerRing.size(); ++k ) { newRing.append( outerRing.at(k % outerRing.size()) ); PolylineNode newNode; newNode.setFlags( m_outerNodesList.at(k % outerRing.size()).flags() ); newList.append( newNode ); } GeoDataCoordinates newCoords = newRing.first().interpolate( newRing.last(), 0.5 ); newRing.append( newCoords ); m_outerNodesList = newList; m_outerNodesList.append( PolylineNode( QRegion() ) ); polygon->outerBoundary() = newRing; m_adjustedNode = -1; } else { Q_ASSERT( i != -1 && j != -1 ); GeoDataLinearRing newRing( Tessellate ); QVector newList; newList.reserve(innerRings.at(i).size()); for ( int k = j; k < j + innerRings.at(i).size(); ++k ) { newRing.append( innerRings.at(i).at(k % innerRings.at(i).size()) ); PolylineNode newNode; newNode.setFlags( m_innerNodesList.at(i).at(k % innerRings.at(i).size()).flags() ); newList.append( newNode ); } GeoDataCoordinates newCoords = newRing.first().interpolate( newRing.last(), 0.5 ); newRing.append( newCoords ); m_innerNodesList[i] = newList; m_innerNodesList[i].append( PolylineNode( QRegion() ) ); polygon->innerBoundaries()[i] = newRing; m_adjustedNode = i; } m_virtualHovered = QPair( -1, -1 ); return true; } // If a virtual node which has been previously clicked and selected to become a // 'real node' is clicked one more time, it stops from being 'adjusted'. const int outerIndex = outerNodeContains( mouseEvent->pos() ); if ( outerIndex != -1 && m_adjustedNode != -2 ) { m_adjustedNode = -2; return true; } const QPair innerIndex = innerNodeContains( mouseEvent->pos() ); if ( innerIndex != QPair( -1, -1 ) && m_adjustedNode != -2 ) { m_adjustedNode = -2; return true; } return false; } bool AreaAnnotation::processAddingNodesOnMove( QMouseEvent *mouseEvent ) { Q_ASSERT( mouseEvent->button() == Qt::NoButton ); const QPair index = virtualNodeContains( mouseEvent->pos() ); // If we are adjusting a virtual node which has just been clicked and became real, just // change its coordinates when moving it, as we do with nodes in Editing state on move. if ( m_adjustedNode != -2 ) { // The virtual node which has just been added is always the last within // GeoDataLinearRing's container.qreal lon, lat; qreal lon, lat; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); const GeoDataCoordinates newCoords( lon, lat ); GeoDataPolygon *polygon = static_cast( placemark()->geometry() ); if ( m_adjustedNode == -1 ) { polygon->outerBoundary().last() = newCoords; } else { Q_ASSERT( m_adjustedNode >= 0 ); polygon->innerBoundaries()[m_adjustedNode].last() = newCoords; } return true; // If we are hovering a virtual node, store its index in order to be painted in drawNodes // method. } else if ( index != QPair( -1, -1 ) ) { m_virtualHovered = index; return true; } // This means that the interior of the polygon has been hovered. Let the event propagate // since there may be overlapping polygons. return false; } bool AreaAnnotation::processAddingNodesOnRelease( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); return m_adjustedNode == -2; } bool AreaAnnotation::dealWithHovering( QMouseEvent *mouseEvent ) { const PolylineNode::PolyNodeFlag flag = state() == SceneGraphicsItem::Editing ? PolylineNode::NodeIsEditingHighlighted : PolylineNode::NodeIsMergingHighlighted; const int outerIndex = outerNodeContains( mouseEvent->pos() ); if ( outerIndex != -1 ) { if ( !m_outerNodesList.at(outerIndex).isEditingHighlighted() && !m_outerNodesList.at(outerIndex).isMergingHighlighted() ) { // Deal with the case when two nodes are very close to each other. if ( m_hoveredNode != QPair( -1, -1 ) ) { const int i = m_hoveredNode.first; const int j = m_hoveredNode.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( flag, false ); } else { m_innerNodesList[i][j].setFlag( flag, false ); } } m_hoveredNode = QPair( outerIndex, -1 ); m_outerNodesList[outerIndex].setFlag( flag ); setRequest( ChangeCursorPolygonNodeHover ); } return true; } else if ( m_hoveredNode != QPair( -1, -1 ) && m_hoveredNode.second == -1 ) { m_outerNodesList[m_hoveredNode.first].setFlag( flag, false ); m_hoveredNode = QPair( -1, -1 ); return true; } const QPair innerIndex = innerNodeContains( mouseEvent->pos() ); if ( innerIndex != QPair( -1, -1 ) ) { if ( !m_innerNodesList.at(innerIndex.first).at(innerIndex.second).isEditingHighlighted() && !m_innerNodesList.at(innerIndex.first).at(innerIndex.second).isMergingHighlighted()) { // Deal with the case when two nodes are very close to each other. if ( m_hoveredNode != QPair( -1, -1 ) ) { const int i = m_hoveredNode.first; const int j = m_hoveredNode.second; if ( j == -1 ) { m_outerNodesList[i].setFlag( flag, false ); } else { m_innerNodesList[i][j].setFlag( flag, false ); } } m_hoveredNode = innerIndex; m_innerNodesList[innerIndex.first][innerIndex.second].setFlag( flag ); setRequest( ChangeCursorPolygonNodeHover ); } return true; } else if ( m_hoveredNode != QPair( -1, -1 ) && m_hoveredNode.second != -1 ) { m_innerNodesList[m_hoveredNode.first][m_hoveredNode.second].setFlag( flag, false ); m_hoveredNode = QPair( -1, -1 ); return true; } // This means that the interior of the polygon has been covered so we catch this event too. setRequest( ChangeCursorPolygonBodyHover ); return true; } } diff --git a/src/plugins/render/annotate/PolylineAnnotation.cpp b/src/plugins/render/annotate/PolylineAnnotation.cpp index 56c80f857..7eaf080af 100644 --- a/src/plugins/render/annotate/PolylineAnnotation.cpp +++ b/src/plugins/render/annotate/PolylineAnnotation.cpp @@ -1,841 +1,842 @@ // // 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 "PolylineAnnotation.h" // Qt #include #include #include // Marble #include "SceneGraphicsTypes.h" #include "GeoPainter.h" #include "PolylineNode.h" #include "GeoDataLineString.h" #include "GeoDataPlacemark.h" #include "ViewportParams.h" +#include "MarbleColors.h" #include "MergingPolylineNodesAnimation.h" #include "osm/OsmPlacemarkData.h" namespace Marble { const int PolylineAnnotation::regularDim = 15; const int PolylineAnnotation::selectedDim = 15; const int PolylineAnnotation::mergedDim = 20; const int PolylineAnnotation::hoveredDim = 20; const QColor PolylineAnnotation::regularColor = Oxygen::aluminumGray3; const QColor PolylineAnnotation::mergedColor = Oxygen::emeraldGreen6; PolylineAnnotation::PolylineAnnotation( GeoDataPlacemark *placemark ) : SceneGraphicsItem( placemark ), m_viewport( nullptr ), m_regionsInitialized( false ), m_busy( false ), m_interactingObj( InteractingNothing ), m_clickedNodeIndex( -1 ), m_hoveredNodeIndex( -1 ), m_virtualHoveredNode( -1 ) { setPaintLayers(QStringList() << "PolylineAnnotation"); } PolylineAnnotation::~PolylineAnnotation() { delete m_animation; } void PolylineAnnotation::paint(GeoPainter *painter, const ViewportParams *viewport , const QString &layer, int tileZoomLevel) { Q_UNUSED(layer); Q_UNUSED(tileZoomLevel); m_viewport = viewport; Q_ASSERT(geodata_cast(placemark()->geometry())); painter->save(); if ( state() == SceneGraphicsItem::DrawingPolyline || !m_regionsInitialized ) { setupRegionsLists( painter ); m_regionsInitialized = true; } else { updateRegions( painter ); } if ( hasFocus() ) { drawNodes( painter ); } painter->restore(); } void PolylineAnnotation::setupRegionsLists( GeoPainter *painter ) { Q_ASSERT( state() == SceneGraphicsItem::DrawingPolyline || !m_regionsInitialized ); const GeoDataLineString line = static_cast( *placemark()->geometry() ); // Add polyline nodes. QVector::ConstIterator itBegin = line.constBegin(); QVector::ConstIterator itEnd = line.constEnd(); m_nodesList.clear(); m_nodesList.reserve(line.size()); for ( ; itBegin != itEnd; ++itBegin ) { const PolylineNode newNode = PolylineNode( painter->regionFromEllipse( *itBegin, regularDim, regularDim ) ); m_nodesList.append( newNode ); } // Add region from polyline so that events on polyline's 'lines' could be caught. m_polylineRegion = painter->regionFromPolyline( line, 15 ); } void PolylineAnnotation::updateRegions( GeoPainter *painter ) { if ( m_busy ) { return; } const GeoDataLineString line = static_cast( *placemark()->geometry() ); if ( state() == SceneGraphicsItem::AddingNodes ) { // Create and update virtual nodes lists when being in the AddingPolgonNodes state, to // avoid overhead in other states. m_virtualNodesList.clear(); for ( int i = 0; i < line.size() - 1; ++i ) { const QRegion newRegion( painter->regionFromEllipse( line.at(i).interpolate( line.at(i+1), 0.5 ), hoveredDim, hoveredDim ) ); m_virtualNodesList.append( PolylineNode( newRegion ) ); } } // Update the polyline region; m_polylineRegion = painter->regionFromPolyline( line, 15 ); // Update the node lists. for ( int i = 0; i < m_nodesList.size(); ++i ) { const QRegion newRegion = m_nodesList.at(i).isSelected() ? painter->regionFromEllipse( line.at(i), selectedDim, selectedDim ) : painter->regionFromEllipse( line.at(i), regularDim, regularDim ); m_nodesList[i].setRegion( newRegion ); } } void PolylineAnnotation::drawNodes( GeoPainter *painter ) { // These are the 'real' dimensions of the drawn nodes. The ones which have class scope are used // to generate the regions and they are a little bit larger, because, for example, it would be // a little bit too hard to select nodes. static const int d_regularDim = 10; static const int d_selectedDim = 10; static const int d_mergedDim = 20; static const int d_hoveredDim = 20; const GeoDataLineString line = static_cast( *placemark()->geometry() ); QColor glowColor = QApplication::palette().highlightedText().color(); glowColor.setAlpha(120); auto const selectedColor = QApplication::palette().highlight().color(); auto const hoveredColor = selectedColor; for ( int i = 0; i < line.size(); ++i ) { // The order here is important, because a merged node can be at the same time selected. if ( m_nodesList.at(i).isBeingMerged() ) { painter->setBrush( mergedColor ); painter->drawEllipse( line.at(i), d_mergedDim, d_mergedDim ); } else if ( m_nodesList.at(i).isSelected() ) { painter->setBrush( selectedColor ); painter->drawEllipse( line.at(i), d_selectedDim, d_selectedDim ); if ( m_nodesList.at(i).isEditingHighlighted() || m_nodesList.at(i).isMergingHighlighted() ) { QPen defaultPen = painter->pen(); QPen newPen; newPen.setWidth( defaultPen.width() + 3 ); newPen.setColor( glowColor ); painter->setBrush( Qt::NoBrush ); painter->setPen( newPen ); painter->drawEllipse( line.at(i), d_selectedDim + 2, d_selectedDim + 2 ); painter->setPen( defaultPen ); } } else { painter->setBrush( regularColor ); painter->drawEllipse( line.at(i), d_regularDim, d_regularDim ); if ( m_nodesList.at(i).isEditingHighlighted() || m_nodesList.at(i).isMergingHighlighted() ) { QPen defaultPen = painter->pen(); QPen newPen; newPen.setWidth( defaultPen.width() + 3 ); newPen.setColor( glowColor ); painter->setPen( newPen ); painter->setBrush( Qt::NoBrush ); painter->drawEllipse( line.at(i), d_regularDim + 2, d_regularDim + 2 ); painter->setPen( defaultPen ); } } } if ( m_virtualHoveredNode != -1 ) { painter->setBrush( hoveredColor ); GeoDataCoordinates newCoords; if ( m_virtualHoveredNode + 1 ) { newCoords = line.at( m_virtualHoveredNode + 1 ).interpolate( line.at( m_virtualHoveredNode ), 0.5 ); } else { newCoords = line.first().interpolate( line.last(), 0.5 ); } painter->drawEllipse( newCoords, d_hoveredDim, d_hoveredDim ); } } bool PolylineAnnotation::containsPoint( const QPoint &point ) const { if ( state() == SceneGraphicsItem::Editing ) { return nodeContains( point ) != -1 || polylineContains( point ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return nodeContains( point ) != -1; } else if ( state() == SceneGraphicsItem::AddingNodes ) { return virtualNodeContains( point ) != -1 || nodeContains( point ) != -1 || polylineContains( point ); } return false; } int PolylineAnnotation::nodeContains( const QPoint &point ) const { if ( !hasFocus() ) { return -1; } for ( int i = 0; i < m_nodesList.size(); ++i ) { if ( m_nodesList.at(i).containsPoint( point ) ) { return i; } } return -1; } int PolylineAnnotation::virtualNodeContains( const QPoint &point ) const { if ( !hasFocus() ) { return -1; } for ( int i = 0; i < m_virtualNodesList.size(); ++i ) { if ( m_virtualNodesList.at(i).containsPoint( point ) ) return i; } return -1; } bool PolylineAnnotation::polylineContains( const QPoint &point ) const { return m_polylineRegion.contains( point ); } void PolylineAnnotation::dealWithItemChange( const SceneGraphicsItem *other ) { Q_UNUSED( other ); // So far we only deal with item changes when hovering nodes, so that // they do not remain hovered when changing the item we interact with. if ( state() == SceneGraphicsItem::Editing ) { if ( m_hoveredNodeIndex != -1 && m_hoveredNodeIndex < static_cast( placemark()->geometry() )->size() ) { m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } m_hoveredNodeIndex = -1; } else if ( state() == SceneGraphicsItem::MergingNodes ) { if ( m_hoveredNodeIndex != -1 ) { m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); } m_hoveredNodeIndex = -1; } else if ( state() == SceneGraphicsItem::AddingNodes ) { m_virtualHoveredNode = -1; } } void PolylineAnnotation::move( const GeoDataCoordinates &source, const GeoDataCoordinates &destination ) { GeoDataLineString *lineString = static_cast( placemark()->geometry() ); GeoDataLineString oldLineString = *lineString; OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } lineString->clear(); const qreal deltaLat = destination.latitude() - source.latitude(); const qreal deltaLon = destination.longitude() - source.longitude(); Quaternion latRectAxis = Quaternion::fromEuler( 0, destination.longitude(), 0); Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0); Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0); Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis; qreal lonRotated, latRotated; for ( int i = 0; i < oldLineString.size(); ++i ) { Quaternion qpos = oldLineString.at(i).quaternion(); qpos.rotateAroundAxis(rotAxis); qpos.getSpherical( lonRotated, latRotated ); GeoDataCoordinates movedPoint( lonRotated, latRotated, 0 ); if ( osmData ) { osmData->changeNodeReference( oldLineString.at( i ), movedPoint ); } lineString->append( movedPoint ); } } void PolylineAnnotation::setBusy( bool enabled ) { m_busy = enabled; if ( !enabled && m_animation && state() == SceneGraphicsItem::MergingNodes ) { if ( m_firstMergedNode != -1 && m_secondMergedNode != -1 ) { // Update the PolylineNodes lists after the animation has finished its execution. m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsMergingHighlighted, false ); m_hoveredNodeIndex = -1; // Remove the merging node flag and add the NodeIsSelected flag if either one of the // merged nodes had been selected before merging them. m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsMerged, false ); if ( m_nodesList[m_firstMergedNode].isSelected() ) { m_nodesList[m_secondMergedNode].setFlag( PolylineNode::NodeIsSelected ); } m_nodesList.removeAt( m_firstMergedNode ); m_firstMergedNode = -1; m_secondMergedNode = -1; } delete m_animation; } } bool PolylineAnnotation::isBusy() const { return m_busy; } void PolylineAnnotation::deselectAllNodes() { if ( state() != SceneGraphicsItem::Editing ) { return; } for ( int i = 0 ; i < m_nodesList.size(); ++i ) { m_nodesList[i].setFlag( PolylineNode::NodeIsSelected, false ); } } void PolylineAnnotation::deleteAllSelectedNodes() { if ( state() != SceneGraphicsItem::Editing ) { return; } GeoDataLineString *line = static_cast( placemark()->geometry() ); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } for ( int i = 0; i < line->size(); ++i ) { if ( m_nodesList.at(i).isSelected() ) { if ( m_nodesList.size() <= 2 ) { setRequest( SceneGraphicsItem::RemovePolylineRequest ); return; } if ( osmData ) { osmData->removeNodeReference( line->at( i ) ); } m_nodesList.removeAt( i ); line->remove( i ); --i; } } } void PolylineAnnotation::deleteClickedNode() { if ( state() != SceneGraphicsItem::Editing ) { return; } GeoDataLineString *line = static_cast( placemark()->geometry() ); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } if ( m_nodesList.size() <= 2 ) { setRequest( SceneGraphicsItem::RemovePolylineRequest ); return; } if ( osmData ) { osmData->removeMemberReference( m_clickedNodeIndex ); } m_nodesList.removeAt( m_clickedNodeIndex ); line->remove( m_clickedNodeIndex ); } void PolylineAnnotation::changeClickedNodeSelection() { if ( state() != SceneGraphicsItem::Editing ) { return; } m_nodesList[m_clickedNodeIndex].setFlag( PolylineNode::NodeIsSelected, !m_nodesList[m_clickedNodeIndex].isSelected() ); } bool PolylineAnnotation::hasNodesSelected() const { for ( int i = 0; i < m_nodesList.size(); ++i ) { if ( m_nodesList.at(i).isSelected() ) { return true; } } return false; } bool PolylineAnnotation::clickedNodeIsSelected() const { return m_nodesList[m_clickedNodeIndex].isSelected(); } QPointer PolylineAnnotation::animation() { return m_animation; } bool PolylineAnnotation::mousePressEvent( QMouseEvent *event ) { if ( !m_viewport || m_busy ) { return false; } setRequest( SceneGraphicsItem::NoRequest ); if ( state() == SceneGraphicsItem::Editing ) { return processEditingOnPress( event ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return processMergingOnPress( event ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return processAddingNodesOnPress( event ); } return false; } bool PolylineAnnotation::mouseMoveEvent( QMouseEvent *event ) { if ( !m_viewport || m_busy ) { return false; } setRequest( SceneGraphicsItem::NoRequest ); if ( state() == SceneGraphicsItem::Editing ) { return processEditingOnMove( event ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return processMergingOnMove( event ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return processAddingNodesOnMove( event ); } return false; } bool PolylineAnnotation::mouseReleaseEvent( QMouseEvent *event ) { if ( !m_viewport || m_busy ) { return false; } setRequest( SceneGraphicsItem::NoRequest ); if ( state() == SceneGraphicsItem::Editing ) { return processEditingOnRelease( event ); } else if ( state() == SceneGraphicsItem::MergingNodes ) { return processMergingOnRelease( event ); } else if ( state() == SceneGraphicsItem::AddingNodes ) { return processAddingNodesOnRelease( event ); } return false; } void PolylineAnnotation::dealWithStateChange( SceneGraphicsItem::ActionState previousState ) { // Dealing with cases when exiting a state has an effect on this item. if ( previousState == SceneGraphicsItem::DrawingPolyline ) { // nothing so far } else if ( previousState == SceneGraphicsItem::Editing ) { // Make sure that when changing the state, there is no highlighted node. if ( m_hoveredNodeIndex != -1 ) { m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } m_clickedNodeIndex = -1; m_hoveredNodeIndex = -1; } else if ( previousState == SceneGraphicsItem::MergingNodes ) { // If there was only a node selected for being merged and the state changed, // deselect it. if ( m_firstMergedNode != -1 ) { m_nodesList[m_firstMergedNode].setFlag( PolylineNode::NodeIsMerged, false ); } // Make sure that when changing the state, there is no highlighted node. if ( m_hoveredNodeIndex != -1 ) { if ( m_hoveredNodeIndex != -1 ) { m_nodesList[m_hoveredNodeIndex].setFlag( PolylineNode::NodeIsEditingHighlighted, false ); } } m_hoveredNodeIndex = -1; delete m_animation; } else if ( previousState == SceneGraphicsItem::AddingNodes ) { m_virtualNodesList.clear(); m_virtualHoveredNode = -1; m_adjustedNode = -1; } // Dealing with cases when entering a state has an effect on this item, or // initializations are needed. if ( state() == SceneGraphicsItem::Editing ) { m_interactingObj = InteractingNothing; m_clickedNodeIndex = -1; m_hoveredNodeIndex = -1; } else if ( state() == SceneGraphicsItem::MergingNodes ) { m_firstMergedNode = -1; m_secondMergedNode = -1; m_hoveredNodeIndex = -1; m_animation = nullptr; } else if ( state() == SceneGraphicsItem::AddingNodes ) { m_virtualHoveredNode = -1; m_adjustedNode = -1; } } bool PolylineAnnotation::processEditingOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton && mouseEvent->button() != Qt::RightButton ) { return false; } qreal lat, lon; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); m_movedPointCoords.set( lon, lat ); // First check if one of the nodes has been clicked. m_clickedNodeIndex = nodeContains( mouseEvent->pos() ); if ( m_clickedNodeIndex != -1 ) { if ( mouseEvent->button() == Qt::RightButton ) { setRequest( SceneGraphicsItem::ShowNodeRmbMenu ); } else { Q_ASSERT( mouseEvent->button() == Qt::LeftButton ); m_interactingObj = InteractingNode; } return true; } // Then check if the 'interior' of the polyline has been clicked (by interior // I mean its lines excepting its nodes). if ( polylineContains( mouseEvent->pos() ) ) { if ( mouseEvent->button() == Qt::RightButton ) { setRequest( SceneGraphicsItem::ShowPolylineRmbMenu ); } else { Q_ASSERT( mouseEvent->button() == Qt::LeftButton ); m_interactingObj = InteractingPolyline; } return true; } return false; } bool PolylineAnnotation::processEditingOnMove( QMouseEvent *mouseEvent ) { if ( !m_viewport ) { return false; } qreal lon, lat; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); const GeoDataCoordinates newCoords( lon, lat ); if ( m_interactingObj == InteractingNode ) { GeoDataLineString *line = static_cast( placemark()->geometry() ); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } // Keeping the OsmPlacemarkData synchronized with the geometry if ( osmData ) { osmData->changeNodeReference( line->at( m_clickedNodeIndex ), newCoords ); } line->at(m_clickedNodeIndex) = newCoords; return true; } else if ( m_interactingObj == InteractingPolyline ) { GeoDataLineString *lineString = static_cast( placemark()->geometry() ); OsmPlacemarkData *osmData = nullptr; if ( placemark()->hasOsmData() ) { osmData = &placemark()->osmData(); } const GeoDataLineString oldLineString = *lineString; lineString->clear(); const qreal deltaLat = lat - m_movedPointCoords.latitude(); const qreal deltaLon = lon - m_movedPointCoords.longitude(); Quaternion latRectAxis = Quaternion::fromEuler( 0, lon, 0); Quaternion latAxis = Quaternion::fromEuler( -deltaLat, 0, 0); Quaternion lonAxis = Quaternion::fromEuler(0, deltaLon, 0); Quaternion rotAxis = latRectAxis * latAxis * latRectAxis.inverse() * lonAxis; qreal lonRotated, latRotated; for ( int i = 0; i < oldLineString.size(); ++i ) { Quaternion qpos = oldLineString.at(i).quaternion(); qpos.rotateAroundAxis(rotAxis); qpos.getSpherical( lonRotated, latRotated ); GeoDataCoordinates movedPoint( lonRotated, latRotated, 0 ); if ( osmData ) { osmData->changeNodeReference( oldLineString.at( i ), movedPoint ); } lineString->append( movedPoint ); } m_movedPointCoords = newCoords; return true; } return dealWithHovering( mouseEvent ); } bool PolylineAnnotation::processEditingOnRelease( QMouseEvent *mouseEvent ) { static const int mouseMoveOffset = 1; if ( mouseEvent->button() != Qt::LeftButton ) { return false; } if ( m_interactingObj == InteractingNode ) { qreal x, y; m_viewport->screenCoordinates( m_movedPointCoords.longitude(), m_movedPointCoords.latitude(), x, y ); // The node gets selected only if it is clicked and not moved. if ( qFabs(mouseEvent->pos().x() - x) > mouseMoveOffset || qFabs(mouseEvent->pos().y() - y) > mouseMoveOffset ) { m_interactingObj = InteractingNothing; return true; } m_nodesList[m_clickedNodeIndex].setFlag( PolylineNode::NodeIsSelected, !m_nodesList.at(m_clickedNodeIndex).isSelected() ); m_interactingObj = InteractingNothing; return true; } else if ( m_interactingObj == InteractingPolyline ) { // Nothing special happens at polyline release. m_interactingObj = InteractingNothing; return true; } return false; } bool PolylineAnnotation::processMergingOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton ) { return false; } GeoDataLineString line = static_cast( *placemark()->geometry() ); const int index = nodeContains( mouseEvent->pos() ); if ( index == -1 ) { return false; } // If this is the first node selected to be merged. if ( m_firstMergedNode == -1 ) { m_firstMergedNode = index; m_nodesList[index].setFlag( PolylineNode::NodeIsMerged ); } else { Q_ASSERT( m_firstMergedNode != -1 ); // Clicking two times the same node results in unmarking it for merging. if ( m_firstMergedNode == index ) { m_nodesList[index].setFlag( PolylineNode::NodeIsMerged, false ); m_firstMergedNode = -1; return true; } // If these two nodes are the last ones remained as part of the polyline, remove // the whole polyline. if ( line.size() <= 2 ) { setRequest( SceneGraphicsItem::RemovePolylineRequest ); return true; } m_nodesList[index].setFlag( PolylineNode::NodeIsMerged ); m_secondMergedNode = index; delete m_animation; m_animation = new MergingPolylineNodesAnimation( this ); setRequest( SceneGraphicsItem::StartPolylineAnimation ); } return true; } bool PolylineAnnotation::processMergingOnMove( QMouseEvent *mouseEvent ) { return dealWithHovering( mouseEvent ); } bool PolylineAnnotation::processMergingOnRelease( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); return true; } bool PolylineAnnotation::processAddingNodesOnPress( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != Qt::LeftButton ) { return false; } GeoDataLineString *line = static_cast( placemark()->geometry() ); // If a virtual node has just been clicked, add it to the polyline and start 'adjusting' // its position. const int virtualIndex = virtualNodeContains( mouseEvent->pos() ); if ( virtualIndex != -1 && m_adjustedNode == -1 ) { Q_ASSERT( m_virtualHoveredNode == virtualIndex ); line->insert( virtualIndex + 1, line->at( virtualIndex ).interpolate( line->at( virtualIndex + 1 ), 0.5 ) ); m_nodesList.insert( virtualIndex + 1, PolylineNode() ); m_adjustedNode = virtualIndex + 1; m_virtualHoveredNode = -1; return true; } // If a virtual node which has been previously clicked and selected to become a // 'real node' is clicked one more time, it stops from being 'adjusted'. const int realIndex = nodeContains( mouseEvent->pos() ); if ( realIndex != -1 && m_adjustedNode != -1 ) { m_adjustedNode = -1; return true; } return false; } bool PolylineAnnotation::processAddingNodesOnMove( QMouseEvent *mouseEvent ) { Q_ASSERT( mouseEvent->button() == Qt::NoButton ); const int index = virtualNodeContains( mouseEvent->pos() ); // If we are adjusting a virtual node which has just been clicked and became real, just // change its coordinates when moving it, as we do with nodes in Editing state on move. if ( m_adjustedNode != -1 ) { // The virtual node which has just been added is always the last within // GeoDataLinearRing's container.qreal lon, lat; qreal lon, lat; m_viewport->geoCoordinates( mouseEvent->pos().x(), mouseEvent->pos().y(), lon, lat, GeoDataCoordinates::Radian ); const GeoDataCoordinates newCoords( lon, lat ); GeoDataLineString *line = static_cast( placemark()->geometry() ); line->at(m_adjustedNode) = newCoords; return true; // If we are hovering a virtual node, store its index in order to be painted in drawNodes // method. } else if ( index != -1 ) { m_virtualHoveredNode = index; return true; } return false; } bool PolylineAnnotation::processAddingNodesOnRelease( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); return m_adjustedNode == -1; } bool PolylineAnnotation::dealWithHovering( QMouseEvent *mouseEvent ) { const PolylineNode::PolyNodeFlag flag = state() == SceneGraphicsItem::Editing ? PolylineNode::NodeIsEditingHighlighted : PolylineNode::NodeIsMergingHighlighted; const int index = nodeContains( mouseEvent->pos() ); if ( index != -1 ) { if ( !m_nodesList.at(index).isEditingHighlighted() && !m_nodesList.at(index).isMergingHighlighted() ) { // Deal with the case when two nodes are very close to each other. if ( m_hoveredNodeIndex != -1 ) { m_nodesList[m_hoveredNodeIndex].setFlag( flag, false ); } m_hoveredNodeIndex = index; m_nodesList[index].setFlag( flag ); setRequest( ChangeCursorPolylineNodeHover ); } return true; } else if ( m_hoveredNodeIndex != -1 ) { m_nodesList[m_hoveredNodeIndex].setFlag( flag, false ); m_hoveredNodeIndex = -1; return true; } // This means that the interior of the polyline has been hovered so we catch this event too. setRequest( ChangeCursorPolylineLineHover ); return true; } const char *PolylineAnnotation::graphicType() const { return SceneGraphicsTypes::SceneGraphicPolylineAnnotation; } } diff --git a/src/plugins/render/aprs/AprsObject.cpp b/src/plugins/render/aprs/AprsObject.cpp index 64bc0b077..dda178b63 100644 --- a/src/plugins/render/aprs/AprsObject.cpp +++ b/src/plugins/render/aprs/AprsObject.cpp @@ -1,160 +1,161 @@ // // 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 Wes Hardaker // #include "AprsObject.h" #include +#include "MarbleColors.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "GeoDataLineString.h" #include "GeoPainter.h" #include "GeoAprsCoordinates.h" using namespace Marble; AprsObject::AprsObject( const GeoAprsCoordinates &at, const QString &name ) : m_myName( name ), m_seenFrom( at.seenFrom() ), m_havePixmap ( false ), m_pixmapFilename( ), m_pixmap( nullptr ) { m_history.push_back( at ); } AprsObject::~AprsObject() { delete m_pixmap; } GeoAprsCoordinates AprsObject::location() const { return m_history.last(); } void AprsObject::setLocation( const GeoAprsCoordinates &location ) { // Not ideal but it's unlikely they'll jump to the *exact* same spot again if ( !m_history.contains( location ) ) { m_history.push_back( location ); mDebug() << " moved: " << m_myName.toLocal8Bit().data(); } else { int index = m_history.indexOf( location ); QTime now; m_history[index].setTimestamp( now ); m_history[index].addSeenFrom( location.seenFrom() ); } m_seenFrom = ( m_seenFrom | location.seenFrom() ); } void AprsObject::setPixmapId( QString &pixmap ) { QString pixmapFilename = MarbleDirs::path( pixmap ); if ( QFile( pixmapFilename ).exists() ) { m_havePixmap = true; m_pixmapFilename = pixmapFilename; // We can't load the pixmap here since it's used in a different thread } else { m_havePixmap = false; } } QColor AprsObject::calculatePaintColor( int from, const QTime &time, int fadeTime ) { QColor color; if ( from & GeoAprsCoordinates::Directly ) { color = Oxygen::emeraldGreen4; // oxygen green if direct } else if ( (from & ( GeoAprsCoordinates::FromTCPIP | GeoAprsCoordinates::FromTTY ) ) == ( GeoAprsCoordinates::FromTCPIP | GeoAprsCoordinates::FromTTY ) ) { color = Oxygen::burgundyPurple4; // oxygen purple if both } else if ( from & GeoAprsCoordinates::FromTCPIP ) { color = Oxygen::brickRed4; // oxygen red if net } else if ( from & GeoAprsCoordinates::FromTTY ) { color = Oxygen::seaBlue4; // oxygen blue if TNC TTY relay } else if ( from & ( GeoAprsCoordinates::FromFile ) ) { color = Oxygen::sunYellow3; // oxygen yellow if file only } else { mDebug() << "**************************************** unknown from: " << from; color = Oxygen::aluminumGray5; // shouldn't happen but a user // could mess up I suppose we // should at least draw it in // something. } if ( fadeTime > 0 && time.elapsed() > fadeTime ) { // 5 min ( 600000 ms ) color.setAlpha( 160 ); } return color; } void AprsObject::render( GeoPainter *painter, ViewportParams *viewport, int fadeTime, int hideTime ) { Q_UNUSED( viewport ); if ( hideTime > 0 && m_history.last().timestamp().elapsed() > hideTime ) return; QColor baseColor = calculatePaintColor( m_seenFrom, m_history.last().timestamp(), fadeTime ); if ( m_history.count() > 1 ) { QList::iterator spot = m_history.begin(); QList::iterator endSpot = m_history.end(); GeoDataLineString lineString; lineString.setTessellate( true ); lineString << *spot; // *spot exists because m_history.count() > 1 for( ++spot; spot != endSpot; ++spot ) { if ( hideTime > 0 && ( *spot ).timestamp().elapsed() > hideTime ) break; lineString << *spot; // draw the new circle in whatever is appropriate for that point const QColor penColor = calculatePaintColor( spot->seenFrom(), spot->timestamp(), fadeTime ); painter->setPen( penColor ); painter->drawRect( *spot, 5, 5 ); } // draw the line in the base color painter->setPen( baseColor ); painter->drawPolyline( lineString ); } // Always draw the symbol then the text last so it's on top if ( m_havePixmap ) { if ( ! m_pixmap ) m_pixmap = new QPixmap ( m_pixmapFilename ); if ( m_pixmap && ! m_pixmap->isNull() ) painter->drawPixmap( m_history.last(), *m_pixmap ); else painter->drawRect( m_history.last(), 6, 6 ); } else painter->drawRect( m_history.last(), 6, 6 ); painter->setPen( baseColor ); painter->drawText( m_history.last(), m_myName ); } diff --git a/src/plugins/render/earthquake/EarthquakeItem.cpp b/src/plugins/render/earthquake/EarthquakeItem.cpp index 952ad4236..ce4a863ae 100644 --- a/src/plugins/render/earthquake/EarthquakeItem.cpp +++ b/src/plugins/render/earthquake/EarthquakeItem.cpp @@ -1,138 +1,140 @@ // // 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 Utku Aydin // #include "EarthquakeItem.h" + +#include "MarbleColors.h" #include "ViewportParams.h" #include #include #include #include namespace Marble { // That's the font we will use to paint. const QFont EarthquakeItem::s_font = QFont( QStringLiteral( "Sans Serif" ), 8, QFont::Bold ); EarthquakeItem::EarthquakeItem( QObject *parent ) : AbstractDataPluginItem( parent ), m_magnitude( 0.0 ), m_depth( 0.0 ) { // The size of an item without a text is 0 setSize( QSize( 0, 0 ) ); setCacheMode( ItemCoordinateCache ); } EarthquakeItem::~EarthquakeItem() { // nothing to do } bool EarthquakeItem::initialized() const { return m_magnitude > 0.0; } bool EarthquakeItem::operator<( const AbstractDataPluginItem *other ) const { // Larger magnitude first const EarthquakeItem* item = dynamic_cast( other ); return item ? magnitude() > item->magnitude() : false; } double EarthquakeItem::magnitude() const { return m_magnitude; } void EarthquakeItem::setMagnitude( double magnitude ) { m_magnitude = magnitude; setSize( QSize( m_magnitude * 10, m_magnitude * 10 ) ); updateTooltip(); } void EarthquakeItem::paint( QPainter *painter ) { // Save the old painter state. painter->save(); // Draw the arch into the given rect. qreal width = magnitude() * 10; qreal height = magnitude() * 10; // Draws the circle with circles' center as rectangle's top-left corner. QRect arcRect( 0, 0, width, height ); QColor color = Oxygen::brickRed4; if ( magnitude() < 5.0 ) { color = Oxygen::sunYellow6; } else if ( magnitude() < 6.0 ) { color = Oxygen::hotOrange4; } painter->setPen( QPen( Qt::NoPen ) ); QBrush brush( color ); brush.setColor( color ); painter->setBrush( brush ); painter->drawEllipse( arcRect ); // Draws the seismograph QSvgRenderer renderer(QStringLiteral(":/seismograph.svg")); renderer.render( painter, QRectF( 0.0, 0.0, width, height ) ); // Draws magnitude of the earthquake QFontMetrics metrics( s_font ); const QString magnitudeText = QLocale::system().toString(m_magnitude); QRect magnitudeRect = metrics.boundingRect( magnitudeText ); painter->setBrush( QBrush() ); painter->setPen( QPen() ); painter->setFont( s_font ); painter->drawText( QPoint( (arcRect.width() - magnitudeRect.width()) / 2, (arcRect.height() - magnitudeRect.height()) / 2 + metrics.ascent() ), magnitudeText ); // Restore the old painter state. painter->restore(); } void EarthquakeItem::setDateTime( const QDateTime &dateTime ) { m_dateTime = dateTime; updateTooltip(); } QDateTime EarthquakeItem::dateTime() const { return m_dateTime; } double EarthquakeItem::depth() const { return m_depth; } void EarthquakeItem::setDepth( double depth ) { m_depth = depth; updateTooltip(); } void EarthquakeItem::updateTooltip() { QLocale locale = QLocale::system(); QString html = QLatin1String(""); if ( m_dateTime.isValid() ) { html += QLatin1String(""); } html += QLatin1String("
") + tr("Date:") + QLatin1String("") + locale.toString(m_dateTime, QLocale::ShortFormat) + QLatin1String("
") + tr("Magnitude:") + QLatin1String("") + locale.toString(m_magnitude) + QLatin1String("
") + tr("Depth:") + QLatin1String("") + locale.toString(m_depth) + QLatin1String(" km
"); setToolTip( html ); } } #include "moc_EarthquakeItem.cpp" diff --git a/src/plugins/render/eclipses/EclipsesPlugin.cpp b/src/plugins/render/eclipses/EclipsesPlugin.cpp index 4bfbb8f77..780801032 100644 --- a/src/plugins/render/eclipses/EclipsesPlugin.cpp +++ b/src/plugins/render/eclipses/EclipsesPlugin.cpp @@ -1,510 +1,511 @@ // // 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 Rene Kuettner // #include "EclipsesPlugin.h" #include "MarbleWidget.h" +#include "MarbleColors.h" #include "MarbleDebug.h" #include "MarbleModel.h" #include "MarbleClock.h" #include "ViewportParams.h" #include "GeoPainter.h" #include "EclipsesModel.h" #include "EclipsesItem.h" #include "EclipsesBrowserDialog.h" #include "ui_EclipsesConfigDialog.h" #include "ui_EclipsesReminderDialog.h" #include #include namespace Marble { EclipsesPlugin::EclipsesPlugin() : RenderPlugin( nullptr ), m_isInitialized( false ), m_marbleWidget( nullptr ), m_model( nullptr ), m_eclipsesActionGroup( nullptr ), m_eclipsesMenuAction( nullptr ), m_eclipsesListMenu( nullptr ), m_menuYear( 0 ), m_configDialog( nullptr ), m_configWidget( nullptr ), m_browserDialog( nullptr ), m_reminderDialog( nullptr ), m_reminderWidget( nullptr ) { } EclipsesPlugin::EclipsesPlugin( const MarbleModel *marbleModel ) : RenderPlugin( marbleModel ), m_isInitialized( false ), m_marbleWidget( nullptr ), m_model( nullptr ), m_eclipsesActionGroup( nullptr ), m_eclipsesMenuAction( nullptr ), m_eclipsesListMenu( nullptr ), m_menuYear( 0 ), m_configDialog( nullptr ), m_configWidget( nullptr ), m_browserDialog( nullptr ), m_reminderDialog( nullptr ), m_reminderWidget( nullptr ) { connect( this, SIGNAL(settingsChanged(QString)), SLOT(updateSettings()) ); } EclipsesPlugin::~EclipsesPlugin() { if( m_isInitialized ) { delete m_model; delete m_eclipsesActionGroup; delete m_eclipsesListMenu; delete m_configDialog; delete m_configWidget; delete m_browserDialog; delete m_reminderDialog; delete m_reminderWidget; } } QStringList EclipsesPlugin::backendTypes() const { return QStringList(QStringLiteral("eclipses")); } QString EclipsesPlugin::renderPolicy() const { return QStringLiteral("ALWAYS"); } QStringList EclipsesPlugin::renderPosition() const { return QStringList(QStringLiteral("ORBIT")); } QString EclipsesPlugin::name() const { return tr( "Eclipses" ); } QString EclipsesPlugin::nameId() const { return QStringLiteral("eclipses"); } QString EclipsesPlugin::guiString() const { return tr( "E&clipses" ); } QString EclipsesPlugin::version() const { return QStringLiteral("1.0"); } QString EclipsesPlugin::description() const { return tr( "This plugin visualizes solar eclipses." ); } QString EclipsesPlugin::copyrightYears() const { return QStringLiteral("2013"); } QVector EclipsesPlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Rene Kuettner"), QStringLiteral("rene@bitkanal.net")) << PluginAuthor(QStringLiteral("Gerhard Holtkamp"), QString()); } QIcon EclipsesPlugin::icon() const { return QIcon(QStringLiteral(":res/eclipses.png")); } RenderPlugin::RenderType EclipsesPlugin::renderType() const { return RenderPlugin::ThemeRenderType; //return UnknownRenderType; } QList* EclipsesPlugin::actionGroups() const { return const_cast*>( &m_actionGroups ); } QDialog* EclipsesPlugin::configDialog() { Q_ASSERT( m_isInitialized ); return m_configDialog; } void EclipsesPlugin::initialize() { if( isInitialized() ) { return; } // initialize dialogs delete m_configDialog; m_configDialog = new QDialog(); delete m_configWidget; m_configWidget = new Ui::EclipsesConfigDialog(); m_configWidget->setupUi( m_configDialog ); connect( m_configDialog, SIGNAL(accepted()), this, SLOT(writeSettings()) ); connect( m_configDialog, SIGNAL(rejected()), this, SLOT(readSettings()) ); connect( m_configWidget->buttonBox->button( QDialogButtonBox::Reset ), SIGNAL(clicked()), this, SLOT(readSettings()) ); connect( m_configWidget->buttonBox->button( QDialogButtonBox::Apply ), SIGNAL(clicked()), this, SLOT(writeSettings()) ); connect( m_configWidget->buttonBox->button( QDialogButtonBox::Apply ), SIGNAL(clicked()), this, SLOT(updateEclipses()) ); m_browserDialog = new EclipsesBrowserDialog( marbleModel() ); connect( m_browserDialog, SIGNAL(buttonShowClicked(int,int)), this, SLOT(showEclipse(int,int)) ); connect( m_browserDialog, SIGNAL(buttonSettingsClicked()), m_configDialog, SLOT(show()) ); delete m_reminderDialog; m_reminderDialog = new QDialog(); delete m_reminderWidget; m_reminderWidget = new Ui::EclipsesReminderDialog(); m_reminderWidget->setupUi( m_reminderDialog ); // initialize menu entries m_eclipsesActionGroup = new QActionGroup( this ); m_actionGroups.append( m_eclipsesActionGroup ); m_eclipsesListMenu = new QMenu(); m_eclipsesActionGroup->addAction( m_eclipsesListMenu->menuAction() ); connect( m_eclipsesListMenu, SIGNAL(triggered(QAction*)), this, SLOT(showEclipseFromMenu(QAction*)) ); m_eclipsesMenuAction = new QAction( tr("Browse Ecli&pses..."), m_eclipsesActionGroup ); m_eclipsesMenuAction->setIcon(QIcon(QStringLiteral(":res/eclipses.png"))); m_eclipsesActionGroup->addAction( m_eclipsesMenuAction ); connect( m_eclipsesMenuAction, SIGNAL(triggered()), m_browserDialog, SLOT(show()) ); // initialize eclipses model m_model = new EclipsesModel( marbleModel() ); connect( marbleModel()->clock(), SIGNAL(timeChanged()), this, SLOT(updateEclipses()) ); m_isInitialized = true; readSettings(); updateEclipses(); updateMenuItemState(); updateSettings(); } bool EclipsesPlugin::isInitialized() const { return m_isInitialized; } bool EclipsesPlugin::eventFilter( QObject *object, QEvent *e ) { // delayed initialization of pointer to marble widget MarbleWidget *widget = dynamic_cast (object); if ( widget && m_marbleWidget != widget ) { connect( widget, SIGNAL(themeChanged(QString)), this, SLOT(updateMenuItemState()) ); m_marbleWidget = widget; } return RenderPlugin::eventFilter(object, e); } bool EclipsesPlugin::render( GeoPainter *painter, ViewportParams *viewport, const QString &renderPos, GeoSceneLayer *layer ) { Q_UNUSED( viewport ); Q_UNUSED( renderPos ); Q_UNUSED( layer ); if (marbleModel()->planetId() == QLatin1String("earth")) { for( EclipsesItem *item: m_model->items() ) { if( item->takesPlaceAt( marbleModel()->clock()->dateTime() ) ) { return renderItem( painter, item ); } } } return true; } bool EclipsesPlugin::renderItem( GeoPainter *painter, EclipsesItem *item ) const { int phase = item->phase(); // Draw full penumbra shadow cone if( m_configWidget->checkBoxShowFullPenumbra->isChecked() ) { painter->setPen( Oxygen::aluminumGray1 ); QColor sunBoundingBrush ( Oxygen::aluminumGray6 ); sunBoundingBrush.setAlpha( 48 ); painter->setBrush( sunBoundingBrush ); painter->drawPolygon( item->shadowConePenumbra() ); } // Draw 60% penumbra shadow cone if( m_configWidget->checkBoxShow60MagPenumbra->isChecked() ) { painter->setPen( Oxygen::aluminumGray2 ); QColor penumbraBrush ( Oxygen::aluminumGray6 ); penumbraBrush.setAlpha( 96 ); painter->setBrush( penumbraBrush ); painter->drawPolygon( item->shadowCone60MagPenumbra() ); } // Draw southern boundary of the penumbra if( m_configWidget->checkBoxShowSouthernPenumbra->isChecked() ) { QColor southernBoundaryColor(Oxygen::brickRed1); southernBoundaryColor.setAlpha(128); QPen southernBoundary(southernBoundaryColor); southernBoundary.setWidth(3); painter->setPen( southernBoundary ); painter->drawPolyline( item->southernPenumbra() ); painter->setPen( Oxygen::brickRed5 ); painter->drawPolyline( item->southernPenumbra() ); } // Draw northern boundary of the penumbra if( m_configWidget->checkBoxShowNorthernPenumbra->isChecked() ) { QColor northernBoundaryColor(Oxygen::brickRed1); northernBoundaryColor.setAlpha(128); QPen northernBoundary(northernBoundaryColor); northernBoundary.setWidth(3); painter->setPen( northernBoundary ); painter->drawPolyline( item->northernPenumbra() ); painter->setPen( Oxygen::brickRed5 ); painter->drawPolyline( item->northernPenumbra() ); } // Draw Sunrise / Sunset Boundaries if( m_configWidget->checkBoxShowSunBoundaries->isChecked() ) { painter->setPen( Oxygen::hotOrange6 ); const QList boundaries = item->sunBoundaries(); QList::const_iterator i = boundaries.constBegin(); QColor sunBoundingBrush ( Oxygen::hotOrange5 ); sunBoundingBrush.setAlpha( 64 ); painter->setBrush( sunBoundingBrush ); for( ; i != boundaries.constEnd(); ++i ) { painter->drawPolygon( *i ); } } // total or annular eclipse if( m_configWidget->checkBoxShowUmbra->isChecked() && phase > 3 ) { painter->setPen( Oxygen::aluminumGray4 ); QColor sunBoundingBrush ( Oxygen::aluminumGray6 ); sunBoundingBrush.setAlpha( 128 ); painter->setBrush( sunBoundingBrush ); painter->drawPolygon( item->umbra() ); // draw shadow cone painter->setPen( Qt::black ); QColor shadowConeBrush ( Oxygen::aluminumGray6 ); shadowConeBrush.setAlpha( 128 ); painter->setBrush( shadowConeBrush ); painter->drawPolygon( item->shadowConeUmbra() ); } // plot central line if( m_configWidget->checkBoxShowCentralLine->isChecked() && phase > 3 ) { painter->setPen( Qt::black ); painter->drawPolyline( item->centralLine() ); } // mark point of maximum eclipse if( m_configWidget->checkBoxShowMaximum->isChecked() ) { painter->setPen( Qt::white ); QColor sunBoundingBrush ( Qt::white ); sunBoundingBrush.setAlpha( 128 ); painter->setBrush( sunBoundingBrush ); painter->drawEllipse( item->maxLocation(), 15, 15 ); painter->setPen( Oxygen::brickRed4 ); painter->drawText( item->maxLocation(), tr( "Maximum of Eclipse" ) ); } return true; } QHash EclipsesPlugin::settings() const { return RenderPlugin::settings(); } void EclipsesPlugin::setSettings( const QHash &settings ) { RenderPlugin::setSettings( settings ); m_settings = settings; emit settingsChanged( nameId() ); } void EclipsesPlugin::readSettings() { m_configWidget->checkBoxEnableLunarEclipses->setChecked( m_settings.value(QStringLiteral("enableLunarEclipses"), false).toBool()); m_configWidget->checkBoxShowMaximum->setChecked( m_settings.value(QStringLiteral("showMaximum"), true).toBool()); m_configWidget->checkBoxShowUmbra->setChecked( m_settings.value(QStringLiteral("showUmbra"), true).toBool()); m_configWidget->checkBoxShowSouthernPenumbra->setChecked( m_settings.value(QStringLiteral("showSouthernPenumbra"), true).toBool()); m_configWidget->checkBoxShowNorthernPenumbra->setChecked( m_settings.value(QStringLiteral("showNorthernPenumbra"), true).toBool()); m_configWidget->checkBoxShowCentralLine->setChecked( m_settings.value(QStringLiteral("showCentralLine"), true).toBool()); m_configWidget->checkBoxShowFullPenumbra->setChecked( m_settings.value(QStringLiteral("showFullPenumbra"), true).toBool()); m_configWidget->checkBoxShow60MagPenumbra->setChecked( m_settings.value(QStringLiteral("show60MagPenumbra"), false).toBool()); m_configWidget->checkBoxShowSunBoundaries->setChecked( m_settings.value(QStringLiteral("showSunBoundaries"), true).toBool()); } void EclipsesPlugin::writeSettings() { m_settings.insert(QStringLiteral("enableLunarEclipses"), m_configWidget->checkBoxEnableLunarEclipses->isChecked() ); m_settings.insert(QStringLiteral("showMaximum"), m_configWidget->checkBoxShowMaximum->isChecked() ); m_settings.insert(QStringLiteral("showUmbra"), m_configWidget->checkBoxShowUmbra->isChecked() ); m_settings.insert(QStringLiteral("showSouthernPenumbra"), m_configWidget->checkBoxShowSouthernPenumbra->isChecked() ); m_settings.insert(QStringLiteral("showNorthernPenumbra"), m_configWidget->checkBoxShowNorthernPenumbra->isChecked() ); m_settings.insert(QStringLiteral("showCentralLine"), m_configWidget->checkBoxShowCentralLine->isChecked() ); m_settings.insert(QStringLiteral("showFullPenumbra"), m_configWidget->checkBoxShowFullPenumbra->isChecked() ); m_settings.insert(QStringLiteral("show60MagPenumbra"), m_configWidget->checkBoxShow60MagPenumbra->isChecked() ); m_settings.insert(QStringLiteral("showSunBoundaries"), m_configWidget->checkBoxShowSunBoundaries->isChecked() ); emit settingsChanged( nameId() ); } void EclipsesPlugin::updateSettings() { if (!isInitialized()) { return; } m_browserDialog->setWithLunarEclipses( m_settings.value(QStringLiteral("enableLunarEclipses")).toBool()); if( m_model->withLunarEclipses() != m_settings.value(QStringLiteral("enableLunarEclipses")).toBool()) { updateEclipses(); } } void EclipsesPlugin::updateEclipses() { // mDebug() << "Updating eclipses...."; const int year = marbleModel()->clock()->dateTime().date().year(); const bool lun = m_settings.value(QStringLiteral("enableLunarEclipses")).toBool(); if( ( m_menuYear != year ) || ( m_model->withLunarEclipses() != lun ) ) { // remove old menus for( QAction *action: m_eclipsesListMenu->actions() ) { m_eclipsesListMenu->removeAction( action ); delete action; } // update year and create menus for this year's eclipse events if( m_model->year() != year ) { m_model->setYear( year ); } m_menuYear = year; // enable/disable lunar eclipses if necessary if( m_model->withLunarEclipses() != lun ) { m_model->setWithLunarEclipses( lun ); } m_eclipsesListMenu->setTitle( tr("Eclipses in %1").arg( year ) ); for( EclipsesItem *item: m_model->items() ) { QAction *action = m_eclipsesListMenu->addAction( item->dateMaximum().date().toString() ); action->setData( QVariant( 1000 * item->dateMaximum().date().year() + item->index() ) ); action->setIcon( item->icon() ); } emit actionGroupsChanged(); } } void EclipsesPlugin::updateMenuItemState() { if( !isInitialized() ) { return; } // eclipses are only supported for earth based observers at the moment // so we disable the menu items for other celestial bodies const bool active = (marbleModel()->planetId() == QLatin1String("earth")); m_eclipsesListMenu->setEnabled( active ); m_eclipsesMenuAction->setEnabled( active ); } void EclipsesPlugin::showEclipse( int year, int index ) { if( m_model->year() != year ) { m_model->setYear( year ); } EclipsesItem *item = m_model->eclipseWithIndex( index ); Q_ASSERT( item ); if( item ) { m_marbleWidget->model()->clock()->setDateTime( item->dateMaximum() ); m_marbleWidget->centerOn( item->maxLocation() ); } } void EclipsesPlugin::showEclipseFromMenu( QAction *action ) { Q_ASSERT( action->data().isValid() ); int year = action->data().toInt() / 1000; int index = action->data().toInt() - 1000 * year; showEclipse( year, index ); } } // namespace Marble #include "moc_EclipsesPlugin.cpp" diff --git a/src/plugins/render/elevationprofilefloatitem/ElevationProfileFloatItem.cpp b/src/plugins/render/elevationprofilefloatitem/ElevationProfileFloatItem.cpp index 44191c229..154264e67 100644 --- a/src/plugins/render/elevationprofilefloatitem/ElevationProfileFloatItem.cpp +++ b/src/plugins/render/elevationprofilefloatitem/ElevationProfileFloatItem.cpp @@ -1,728 +1,729 @@ // // 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 "ElevationProfileFloatItem.h" #include "ElevationProfileContextMenu.h" #include "ui_ElevationProfileConfigWidget.h" #include "MarbleModel.h" #include "MarbleWidget.h" #include "GeoDataPlacemark.h" #include "GeoDataTreeModel.h" #include "ViewportParams.h" +#include "MarbleColors.h" #include "MarbleDirs.h" #include "ElevationModel.h" #include "MarbleGraphicsGridLayout.h" #include "MarbleDebug.h" #include "routing/RoutingManager.h" #include "routing/RoutingModel.h" #include #include #include #include #include #include namespace Marble { ElevationProfileFloatItem::ElevationProfileFloatItem( const MarbleModel *marbleModel ) : AbstractFloatItem( marbleModel, QPointF( 220, 10.5 ), QSizeF( 0.0, 50.0 ) ), m_activeDataSource(nullptr), m_routeDataSource( marbleModel ? marbleModel->routingManager()->routingModel() : nullptr, marbleModel ? marbleModel->elevationModel() : nullptr, this ), m_trackDataSource( marbleModel ? marbleModel->treeModel() : nullptr, this ), m_configDialog( nullptr ), ui_configWidget( nullptr ), m_leftGraphMargin( 0 ), m_eleGraphWidth( 0 ), m_viewportWidth( 0 ), m_shrinkFactorY( 1.2 ), m_fontHeight( 10 ), m_markerPlacemark( new GeoDataPlacemark ), m_documentIndex( -1 ), m_cursorPositionX( 0 ), m_isInitialized( false ), m_contextMenu( nullptr ), m_marbleWidget( nullptr ), m_firstVisiblePoint( 0 ), m_lastVisiblePoint( 0 ), m_zoomToViewport( false ) { setVisible( false ); bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( smallScreen ) { setPosition( QPointF( 10.5, 10.5 ) ); } bool const highRes = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::HighResolution; m_eleGraphHeight = highRes ? 100 : 50; /// TODO make configurable setPadding( 1 ); m_markerDocument.setDocumentRole( UnknownDocument ); m_markerDocument.setName(QStringLiteral("Elevation Profile")); m_markerPlacemark->setName(QStringLiteral("Elevation Marker")); m_markerPlacemark->setVisible( false ); m_markerDocument.append( m_markerPlacemark ); m_contextMenu = new ElevationProfileContextMenu(this); connect( &m_trackDataSource, SIGNAL(sourceCountChanged()), m_contextMenu, SLOT(updateContextMenuEntries()) ); connect( &m_routeDataSource, SIGNAL(sourceCountChanged()), m_contextMenu, SLOT(updateContextMenuEntries()) ); } ElevationProfileFloatItem::~ElevationProfileFloatItem() { } QStringList ElevationProfileFloatItem::backendTypes() const { return QStringList(QStringLiteral("elevationprofile")); } qreal ElevationProfileFloatItem::zValue() const { return 3.0; } QString ElevationProfileFloatItem::name() const { return tr("Elevation Profile"); } QString ElevationProfileFloatItem::guiString() const { return tr("&Elevation Profile"); } QString ElevationProfileFloatItem::nameId() const { return QStringLiteral("elevationprofile"); } QString ElevationProfileFloatItem::version() const { return QStringLiteral("1.2"); // TODO: increase to 1.3 ? } QString ElevationProfileFloatItem::description() const { return tr( "A float item that shows the elevation profile of the current route." ); } QString ElevationProfileFloatItem::copyrightYears() const { return QStringLiteral("2011, 2012, 2013"); } QVector ElevationProfileFloatItem::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Florian Eßer"),QStringLiteral("f.esser@rwth-aachen.de")) << PluginAuthor(QStringLiteral("Bernhard Beschow"), QStringLiteral("bbeschow@cs.tu-berlin.de")) << PluginAuthor(QStringLiteral("Roman Karlstetter"), QStringLiteral("roman.karlstetter@googlemail.com")); } QIcon ElevationProfileFloatItem::icon () const { return QIcon(QStringLiteral(":/icons/elevationprofile.png")); } void ElevationProfileFloatItem::initialize () { connect( marbleModel()->elevationModel(), SIGNAL(updateAvailable()), &m_routeDataSource, SLOT(requestUpdate()) ); connect( marbleModel()->routingManager()->routingModel(), SIGNAL(currentRouteChanged()), &m_routeDataSource, SLOT(requestUpdate()) ); connect( this, SIGNAL(dataUpdated()), SLOT(forceRepaint()) ); switchDataSource(&m_routeDataSource); m_fontHeight = QFontMetricsF( font() ).ascent() + 1; m_leftGraphMargin = QFontMetricsF( font() ).width( "0000 m" ); /// TODO make this dynamic according to actual need m_isInitialized = true; } bool ElevationProfileFloatItem::isInitialized () const { return m_isInitialized; } void ElevationProfileFloatItem::setProjection( const ViewportParams *viewport ) { if ( !( viewport->width() == m_viewportWidth && m_isInitialized ) ) { bool const highRes = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::HighResolution; int const widthRatio = highRes ? 2 : 3; setContentSize( QSizeF( viewport->width() / widthRatio, m_eleGraphHeight + m_fontHeight * 2.5 ) ); m_eleGraphWidth = contentSize().width() - m_leftGraphMargin; m_axisX.setLength( m_eleGraphWidth ); m_axisY.setLength( m_eleGraphHeight ); m_axisX.setTickCount( 3, m_eleGraphWidth / ( m_leftGraphMargin * 1.5 ) ); m_axisY.setTickCount( 2, m_eleGraphHeight / m_fontHeight ); m_viewportWidth = viewport->width(); bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( !m_isInitialized && !smallScreen ) { setPosition( QPointF( (viewport->width() - contentSize().width()) / 2 , 10.5 ) ); } } update(); AbstractFloatItem::setProjection( viewport ); } void ElevationProfileFloatItem::paintContent( QPainter *painter ) { // do not try to draw if not initialized if(!isInitialized()) { return; } painter->save(); painter->setRenderHint( QPainter::Antialiasing, true ); painter->setFont( font() ); if ( ! ( m_activeDataSource->isDataAvailable() && m_eleData.size() > 0 ) ) { painter->setPen( QColor( Qt::black ) ); QString text = tr( "Create a route or load a track from file to view its elevation profile." ); painter->drawText( contentRect().toRect(), Qt::TextWordWrap | Qt::AlignCenter, text ); painter->restore(); return; } if ( m_zoomToViewport && ( m_lastVisiblePoint - m_firstVisiblePoint < 5 ) ) { painter->setPen( QColor( Qt::black ) ); QString text = tr( "Not enough points in the current viewport.\nTry to disable 'Zoom to viewport'." ); painter->drawText( contentRect().toRect(), Qt::TextWordWrap | Qt::AlignCenter, text ); painter->restore(); return; } QString intervalStr; int lastStringEnds; // draw viewport bounds if ( ! m_zoomToViewport && ( m_firstVisiblePoint > 0 || m_lastVisiblePoint < m_eleData.size() - 1 ) ) { QColor color( Qt::black ); color.setAlpha( 64 ); QRect rect; rect.setLeft( m_leftGraphMargin + m_eleData.value( m_firstVisiblePoint ).x() * m_eleGraphWidth / m_axisX.range() ); rect.setTop( 0 ); rect.setWidth( ( m_eleData.value( m_lastVisiblePoint ).x() - m_eleData.value( m_firstVisiblePoint ).x() ) * m_eleGraphWidth / m_axisX.range() ); rect.setHeight( m_eleGraphHeight ); painter->fillRect( rect, color ); } // draw X and Y axis painter->setPen( Oxygen::aluminumGray4 ); painter->drawLine( m_leftGraphMargin, m_eleGraphHeight, contentSize().width(), m_eleGraphHeight ); painter->drawLine( m_leftGraphMargin, m_eleGraphHeight, m_leftGraphMargin, 0 ); // draw Y grid and labels painter->setPen( QColor( Qt::black ) ); QPen dashedPen( Qt::DashLine ); dashedPen.setColor( Oxygen::aluminumGray4 ); QRect labelRect( 0, 0, m_leftGraphMargin - 1, m_fontHeight + 2 ); lastStringEnds = m_eleGraphHeight + m_fontHeight; // painter->drawText(m_leftGraphMargin + 1, m_fontHeight, QLatin1Char('[') + m_axisY.unit() + QLatin1Char(']')); for ( const AxisTick &tick: m_axisY.ticks() ) { const int posY = m_eleGraphHeight - tick.position; painter->setPen( dashedPen ); painter->drawLine( m_leftGraphMargin, posY, contentSize().width(), posY ); labelRect.moveCenter( QPoint( labelRect.center().x(), posY ) ); if ( labelRect.top() < 0 ) { // don't cut off uppermost label labelRect.moveTop( 0 ); } if ( labelRect.bottom() >= lastStringEnds ) { // Don't print overlapping labels continue; } lastStringEnds = labelRect.top(); painter->setPen( QColor( Qt::black ) ); intervalStr.setNum( tick.value * m_axisY.scale() ); painter->drawText( labelRect, Qt::AlignRight, intervalStr ); } // draw X grid and labels painter->setPen( QColor( Qt::black ) ); labelRect.moveTop( m_eleGraphHeight + 1 ); lastStringEnds = 0; for ( const AxisTick &tick: m_axisX.ticks() ) { const int posX = m_leftGraphMargin + tick.position; painter->setPen( dashedPen ); painter->drawLine( posX, 0, posX, m_eleGraphHeight ); intervalStr.setNum( tick.value * m_axisX.scale() ); if ( tick.position == m_axisX.ticks().last().position ) { intervalStr += QLatin1Char(' ') + m_axisX.unit(); } labelRect.setWidth( QFontMetricsF( font() ).width( intervalStr ) * 1.5 ); labelRect.moveCenter( QPoint( posX, labelRect.center().y() ) ); if ( labelRect.right() > m_leftGraphMargin + m_eleGraphWidth ) { // don't cut off rightmost label labelRect.moveRight( m_leftGraphMargin + m_eleGraphWidth ); } if ( labelRect.left() <= lastStringEnds ) { // Don't print overlapping labels continue; } lastStringEnds = labelRect.right(); painter->setPen( QColor( Qt::black ) ); painter->drawText( labelRect, Qt::AlignCenter, intervalStr ); } // display elevation gain/loss data painter->setPen( QColor( Qt::black ) ); intervalStr = tr( "Difference: %1 %2" ) .arg( QString::number( m_gain - m_loss, 'f', 0 ) ) .arg( m_axisY.unit() ); intervalStr += QString::fromUtf8( " (↗ %1 %3 ↘ %2 %3)" ) .arg( QString::number( m_gain, 'f', 0 ) ) .arg( QString::number( m_loss, 'f', 0 ) ) .arg( m_axisY.unit() ); painter->drawText( contentRect().toRect(), Qt::AlignBottom | Qt::AlignCenter, intervalStr ); // draw elevation profile painter->setPen( QColor( Qt::black ) ); bool const highRes = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::HighResolution; QPen pen = painter->pen(); pen.setWidth( highRes ? 2 : 1 ); painter->setPen( pen ); QLinearGradient fillGradient( 0, 0, 0, m_eleGraphHeight ); QColor startColor = Oxygen::forestGreen4; QColor endColor = Oxygen::hotOrange4; startColor.setAlpha( 200 ); endColor.setAlpha( 32 ); fillGradient.setColorAt( 0.0, startColor ); fillGradient.setColorAt( 1.0, endColor ); QBrush brush = QBrush( fillGradient ); painter->setBrush( brush ); QPoint oldPos; oldPos.setX( m_leftGraphMargin ); oldPos.setY( ( m_axisY.minValue() - m_axisY.minValue() ) * m_eleGraphHeight / ( m_axisY.range() / m_shrinkFactorY ) ); oldPos.setY( m_eleGraphHeight - oldPos.y() ); QPainterPath path; path.moveTo( oldPos.x(), m_eleGraphHeight ); path.lineTo( oldPos.x(), oldPos.y() ); const int start = m_zoomToViewport ? m_firstVisiblePoint : 0; const int end = m_zoomToViewport ? m_lastVisiblePoint : m_eleData.size() - 1; for ( int i = start; i <= end; ++i ) { QPoint newPos; if ( i == start ) { // make sure the plot always starts at the y-axis newPos.setX( 0 ); } else { newPos.setX( ( m_eleData.value(i).x() - m_axisX.minValue() ) * m_eleGraphWidth / m_axisX.range() ); } newPos.rx() += m_leftGraphMargin; if ( newPos.x() != oldPos.x() || newPos.y() != oldPos.y() ) { newPos.setY( ( m_eleData.value(i).y() - m_axisY.minValue() ) * m_eleGraphHeight / ( m_axisY.range() * m_shrinkFactorY ) ); newPos.setY( m_eleGraphHeight - newPos.y() ); path.lineTo( newPos.x(), newPos.y() ); oldPos = newPos; } } path.lineTo( oldPos.x(), m_eleGraphHeight ); // fill painter->setPen( QPen( Qt::NoPen ) ); painter->drawPath( path ); // contour // "remove" the first and last path element first, they are only used to fill down to the bottom painter->setBrush( QBrush( Qt::NoBrush ) ); path.setElementPositionAt( 0, path.elementAt( 1 ).x, path.elementAt( 1 ).y ); path.setElementPositionAt( path.elementCount()-1, path.elementAt( path.elementCount()-2 ).x, path.elementAt( path.elementCount()-2 ).y ); painter->setPen( pen ); painter->drawPath( path ); pen.setWidth( 1 ); painter->setPen( pen ); // draw interactive cursor const GeoDataCoordinates currentPoint = m_markerPlacemark->coordinate(); if ( currentPoint.isValid() ) { painter->setPen( QColor( Qt::white ) ); painter->drawLine( m_leftGraphMargin + m_cursorPositionX, 0, m_leftGraphMargin + m_cursorPositionX, m_eleGraphHeight ); qreal xpos = m_axisX.minValue() + ( m_cursorPositionX / m_eleGraphWidth ) * m_axisX.range(); qreal ypos = m_eleGraphHeight - ( ( currentPoint.altitude() - m_axisY.minValue() ) / ( qMax( 1.0, m_axisY.range() ) * m_shrinkFactorY ) ) * m_eleGraphHeight; painter->drawLine( m_leftGraphMargin + m_cursorPositionX - 5, ypos, m_leftGraphMargin + m_cursorPositionX + 5, ypos ); intervalStr.setNum( xpos * m_axisX.scale(), 'f', 2 ); intervalStr += QLatin1Char(' ') + m_axisX.unit(); int currentStringBegin = m_leftGraphMargin + m_cursorPositionX - QFontMetricsF( font() ).width( intervalStr ) / 2; painter->drawText( currentStringBegin, contentSize().height() - 1.5 * m_fontHeight, intervalStr ); intervalStr.setNum( currentPoint.altitude(), 'f', 1 ); intervalStr += QLatin1Char(' ') + m_axisY.unit(); if ( m_cursorPositionX + QFontMetricsF( font() ).width( intervalStr ) + m_leftGraphMargin < m_eleGraphWidth ) { currentStringBegin = ( m_leftGraphMargin + m_cursorPositionX + 5 + 2 ); } else { currentStringBegin = m_leftGraphMargin + m_cursorPositionX - 5 - QFontMetricsF( font() ).width( intervalStr ) * 1.5; } // Make sure the text still fits into the window while ( ypos < m_fontHeight ) { ypos++; } painter->drawText( currentStringBegin, ypos + m_fontHeight / 2, intervalStr ); } painter->restore(); } QDialog *ElevationProfileFloatItem::configDialog() //FIXME TODO Make a config dialog? /// TODO what is this comment? { if ( !m_configDialog ) { // Initializing configuration dialog m_configDialog = new QDialog(); ui_configWidget = new Ui::ElevationProfileConfigWidget; ui_configWidget->setupUi( m_configDialog ); readSettings(); connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), SLOT(writeSettings()) ); connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), SLOT(readSettings()) ); QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply ); connect( applyButton, SIGNAL(clicked()), this, SLOT(writeSettings()) ); } return m_configDialog; } void ElevationProfileFloatItem::contextMenuEvent( QWidget *w, QContextMenuEvent *e ) { Q_ASSERT( m_contextMenu ); m_contextMenu->getMenu()->exec( w->mapToGlobal( e->pos() ) ); } bool ElevationProfileFloatItem::eventFilter( QObject *object, QEvent *e ) { if ( !enabled() || !visible() ) { return false; } MarbleWidget *widget = dynamic_cast( object ); if ( !widget ) { return AbstractFloatItem::eventFilter(object,e); } if ( widget && !m_marbleWidget ) { m_marbleWidget = widget; connect( this, SIGNAL(dataUpdated()), this, SLOT(updateVisiblePoints()) ); connect( m_marbleWidget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)), this, SLOT(updateVisiblePoints()) ); connect( this, SIGNAL(settingsChanged(QString)), this, SLOT(updateVisiblePoints()) ); } if ( e->type() == QEvent::MouseButtonDblClick || e->type() == QEvent::MouseMove ) { GeoDataTreeModel *const treeModel = const_cast( marbleModel() )->treeModel(); QMouseEvent *event = static_cast( e ); QRectF plotRect = QRectF ( m_leftGraphMargin, 0, m_eleGraphWidth, contentSize().height() ); plotRect.translate( positivePosition() ); plotRect.translate( padding(), padding() ); // for antialiasing: increase size by 1 px to each side plotRect.translate(-1, -1); plotRect.setSize(plotRect.size() + QSize(2, 2) ); const bool cursorAboveFloatItem = plotRect.contains(event->pos()); if ( cursorAboveFloatItem ) { const int start = m_zoomToViewport ? m_firstVisiblePoint : 0; const int end = m_zoomToViewport ? m_lastVisiblePoint : m_eleData.size(); // Double click triggers recentering the map at the specified position if ( e->type() == QEvent::MouseButtonDblClick ) { const QPointF mousePosition = event->pos() - plotRect.topLeft(); const int xPos = mousePosition.x(); for ( int i = start; i < end; ++i) { const int plotPos = ( m_eleData.value(i).x() - m_axisX.minValue() ) * m_eleGraphWidth / m_axisX.range(); if ( plotPos >= xPos ) { widget->centerOn( m_points[i], true ); break; } } return true; } if ( e->type() == QEvent::MouseMove && !(event->buttons() & Qt::LeftButton) ) { // Cross hair cursor when moving above the float item // and mark the position on the graph widget->setCursor(QCursor(Qt::CrossCursor)); if ( m_cursorPositionX != event->pos().x() - plotRect.left() ) { m_cursorPositionX = event->pos().x() - plotRect.left(); const qreal xpos = m_axisX.minValue() + ( m_cursorPositionX / m_eleGraphWidth ) * m_axisX.range(); GeoDataCoordinates currentPoint; // invalid coordinates for ( int i = start; i < end; ++i) { if ( m_eleData.value(i).x() >= xpos ) { currentPoint = m_points[i]; currentPoint.setAltitude( m_eleData.value(i).y() ); break; } } m_markerPlacemark->setCoordinate( currentPoint ); if ( m_documentIndex < 0 ) { m_documentIndex = treeModel->addDocument( &m_markerDocument ); } emit repaintNeeded(); } return true; } } else { if ( m_documentIndex >= 0 ) { m_markerPlacemark->setCoordinate( GeoDataCoordinates() ); // set to invalid treeModel->removeDocument( &m_markerDocument ); m_documentIndex = -1; emit repaintNeeded(); } } } return AbstractFloatItem::eventFilter(object,e); } void ElevationProfileFloatItem::handleDataUpdate(const GeoDataLineString &points, const QVector &eleData) { m_eleData = eleData; m_points = points; calculateStatistics( m_eleData ); if ( m_eleData.length() >= 2 ) { m_axisX.setRange( m_eleData.first().x(), m_eleData.last().x() ); m_axisY.setRange( qMin( m_minElevation, qreal( 0.0 ) ), m_maxElevation ); } emit dataUpdated(); } void ElevationProfileFloatItem::updateVisiblePoints() { if ( ! m_activeDataSource->isDataAvailable() || m_points.size() < 2 ) { return; } // find the longest visible route section on screen QList > routeSegments; QList currentRouteSegment; for ( int i = 0; i < m_eleData.count(); i++ ) { qreal lon = m_points[i].longitude(GeoDataCoordinates::Degree); qreal lat = m_points[i].latitude (GeoDataCoordinates::Degree); qreal x = 0; qreal y = 0; if ( m_marbleWidget->screenCoordinates(lon, lat, x, y) ) { // on screen --> add point to list currentRouteSegment.append(i); } else { // off screen --> start new list if ( !currentRouteSegment.isEmpty() ) { routeSegments.append( currentRouteSegment ); currentRouteSegment.clear(); } } } routeSegments.append( currentRouteSegment ); // in case the route ends on screen int maxLenght = 0; for ( const QList ¤tRouteSegment: routeSegments ) { if ( currentRouteSegment.size() > maxLenght ) { maxLenght = currentRouteSegment.size() ; m_firstVisiblePoint = currentRouteSegment.first(); m_lastVisiblePoint = currentRouteSegment.last(); } } if ( m_firstVisiblePoint < 0 ) { m_firstVisiblePoint = 0; } if ( m_lastVisiblePoint < 0 || m_lastVisiblePoint >= m_eleData.count() ) { m_lastVisiblePoint = m_eleData.count() - 1; } // include setting range to statistics and test for m_zoomToViewport in calculateStatistics(); if ( m_zoomToViewport ) { calculateStatistics( m_eleData ); m_axisX.setRange( m_eleData.value( m_firstVisiblePoint ).x(), m_eleData.value( m_lastVisiblePoint ).x() ); m_axisY.setRange( m_minElevation, m_maxElevation ); } return; } void ElevationProfileFloatItem::calculateStatistics(const QVector &eleData) { // This basically calculates the important peaks of the moving average filtered elevation and // calculates the elevation data based on this points. // This is done by always placing the averaging window in a way that it starts or ends at an // original data point. This should ensure that all minima/maxima of the moving average // filtered data are covered. const qreal averageDistance = 200.0; m_maxElevation = 0.0; m_minElevation = invalidElevationData; m_gain = 0.0; m_loss = 0.0; const int start = m_zoomToViewport ? m_firstVisiblePoint : 0; const int end = m_zoomToViewport ? m_lastVisiblePoint + 1 : eleData.size(); if( start < end ) { qreal lastX = eleData.value( start ).x(); qreal lastY = eleData.value( start ).y(); qreal nextX = eleData.value( start + 1 ).x(); qreal nextY = eleData.value( start + 1 ).y(); m_maxElevation = qMax( lastY, nextY ); m_minElevation = qMin( lastY, nextY ); int averageStart = start; if(lastX + averageDistance < eleData.value( start + 2 ).x()) ++averageStart; for ( int index = start + 2; index <= end; ++index ) { qreal indexX = index < end ? eleData.value( index ).x() : eleData.value( end - 1 ).x() + averageDistance; qreal indexY = eleData.value( qMin( index, end - 1 ) ).y(); m_maxElevation = qMax( m_maxElevation, indexY ); m_minElevation = qMin( m_minElevation, indexY ); // Low-pass filtering (moving average) of the elevation profile to calculate gain and loss values // not always the best method, see for example // http://www.ikg.uni-hannover.de/fileadmin/ikg/staff/thesis/finished/documents/StudArb_Schulze.pdf // (German), chapter 4.2 // Average over the part ending with the previous point. // Do complete recalculation to avoid accumulation of floating point artifacts. nextY = 0; qreal averageX = nextX - averageDistance; for( int averageIndex = averageStart; averageIndex < index; ++averageIndex ) { qreal nextAverageX = eleData.value( averageIndex ).x(); qreal ratio = ( nextAverageX - averageX ) / averageDistance; // Weighting of original data based on covered distance nextY += eleData.value( qMax( averageIndex - 1, 0 ) ).y() * ratio; averageX = nextAverageX; } while( averageStart < index ) { // This handles the part ending with the previous point on the first iteration and the parts starting with averageStart afterwards if ( nextY > lastY ) { m_gain += nextY - lastY; } else { m_loss += lastY - nextY; } // Here we split the data into parts that average over the same data points // As soon as the end of the averaging window reaches the current point we reached the end of the current part lastX = nextX; lastY = nextY; nextX = eleData.value( averageStart ).x() + averageDistance; if( nextX >= indexX ) { break; } // We don't need to recalculate the average completely, just remove the reached point qreal ratio = (nextX - lastX) / averageDistance; nextY += ( eleData.value( index - 1 ).y() - eleData.value( qMax( averageStart - 1, 0 ) ).y() ) * ratio; ++averageStart; } // This is for the next part already, the end of the averaging window is at the following point nextX = indexX; } // Also include the last point nextY = eleData.value( end - 1 ).y(); if ( nextY > lastY ) { m_gain += nextY - lastY; } else { m_loss += lastY - nextY; } } } void ElevationProfileFloatItem::forceRepaint() { // We add one pixel as antialiasing could result into painting on these pixels to. QRectF floatItemRect = QRectF( positivePosition() - QPoint( 1, 1 ), size() + QSize( 2, 2 ) ); update(); emit repaintNeeded( floatItemRect.toRect() ); } void ElevationProfileFloatItem::readSettings() { if ( !m_configDialog ) return; if ( m_zoomToViewport ) { ui_configWidget->m_zoomToViewportCheckBox->setCheckState( Qt::Checked ); } else { ui_configWidget->m_zoomToViewportCheckBox->setCheckState( Qt::Unchecked ); } } void ElevationProfileFloatItem::writeSettings() { if ( ui_configWidget->m_zoomToViewportCheckBox->checkState() == Qt::Checked ) { m_zoomToViewport = true; } else { m_zoomToViewport = false; } emit settingsChanged( nameId() ); } void ElevationProfileFloatItem::toggleZoomToViewport() { m_zoomToViewport = ! m_zoomToViewport; calculateStatistics( m_eleData ); if ( ! m_zoomToViewport ) { m_axisX.setRange( m_eleData.first().x(), m_eleData.last().x() ); m_axisY.setRange( qMin( m_minElevation, qreal( 0.0 ) ), m_maxElevation ); } readSettings(); emit settingsChanged( nameId() ); } void ElevationProfileFloatItem::switchToRouteDataSource() { switchDataSource(&m_routeDataSource); } void ElevationProfileFloatItem::switchToTrackDataSource(int index) { m_trackDataSource.setSourceIndex(index); switchDataSource(&m_trackDataSource); } void ElevationProfileFloatItem::switchDataSource(ElevationProfileDataSource* source) { if (m_activeDataSource) { disconnect(m_activeDataSource, SIGNAL(dataUpdated(GeoDataLineString,QVector)),nullptr,nullptr); } m_activeDataSource = source; connect(m_activeDataSource, SIGNAL(dataUpdated(GeoDataLineString,QVector)), this, SLOT(handleDataUpdate(GeoDataLineString,QVector))); m_activeDataSource->requestUpdate(); } } #include "moc_ElevationProfileFloatItem.cpp" diff --git a/src/plugins/render/mapscale/MapScaleFloatItem.cpp b/src/plugins/render/mapscale/MapScaleFloatItem.cpp index ab8b390a7..07aaf3e2c 100644 --- a/src/plugins/render/mapscale/MapScaleFloatItem.cpp +++ b/src/plugins/render/mapscale/MapScaleFloatItem.cpp @@ -1,457 +1,458 @@ // // 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 2012 Illya Kovalevskyy // #include "MapScaleFloatItem.h" #include #include #include #include #include #include #include #include #include "ui_MapScaleConfigWidget.h" +#include "MarbleColors.h" #include "MarbleDebug.h" #include "MarbleGlobal.h" #include "projections/AbstractProjection.h" #include "MarbleLocale.h" #include "MarbleModel.h" #include "ViewportParams.h" #include "GeoDataLatLonAltBox.h" namespace Marble { MapScaleFloatItem::MapScaleFloatItem( const MarbleModel *marbleModel ) : AbstractFloatItem( marbleModel, QPointF( 10.5, -10.5 ), QSizeF( 0.0, 40.0 ) ), m_configDialog(nullptr), m_radius(0), m_target(QString()), m_leftBarMargin(0), m_rightBarMargin(0), m_scaleBarWidth(0), m_viewportWidth(0), m_scaleBarHeight(5), m_scaleBarDistance(0.0), m_bestDivisor(0), m_pixelInterval(0), m_valueInterval(0), m_scaleInitDone( false ), m_showRatioScale( false ), m_contextMenu( nullptr ), m_minimized(false), m_widthScaleFactor(2) { m_minimizeAction = new QAction(tr("Minimize"), this); m_minimizeAction->setCheckable(true); m_minimizeAction->setChecked(m_minimized); connect(m_minimizeAction, SIGNAL(triggered()), this, SLOT(toggleMinimized())); } MapScaleFloatItem::~MapScaleFloatItem() { } QStringList MapScaleFloatItem::backendTypes() const { return QStringList(QStringLiteral("mapscale")); } QString MapScaleFloatItem::name() const { return tr("Scale Bar"); } QString MapScaleFloatItem::guiString() const { return tr("&Scale Bar"); } QString MapScaleFloatItem::nameId() const { return QStringLiteral("scalebar"); } QString MapScaleFloatItem::version() const { return QStringLiteral("1.1"); } QString MapScaleFloatItem::description() const { return tr("This is a float item that provides a map scale."); } QString MapScaleFloatItem::copyrightYears() const { return QStringLiteral("2008, 2010, 2012"); } QVector MapScaleFloatItem::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org"), tr("Original Developer")) << PluginAuthor(QStringLiteral("Khanh-Nhan Nguyen"), QStringLiteral("khanh.nhan@wpi.edu")) << PluginAuthor(QStringLiteral("Illya Kovalevskyy"), QStringLiteral("illya.kovalevskyy@gmail.com")); } QIcon MapScaleFloatItem::icon () const { return QIcon(QStringLiteral(":/icons/scalebar.png")); } void MapScaleFloatItem::initialize () { } bool MapScaleFloatItem::isInitialized () const { return true; } void MapScaleFloatItem::setProjection( const ViewportParams *viewport ) { int viewportWidth = viewport->width(); QString target = marbleModel()->planetId(); if ( !( m_radius == viewport->radius() && viewportWidth == m_viewportWidth && m_target == target && m_scaleInitDone ) ) { int fontHeight = QFontMetrics( font() ).ascent(); if (m_showRatioScale) { setContentSize( QSizeF( viewport->width() / m_widthScaleFactor, fontHeight + 3 + m_scaleBarHeight + fontHeight + 7 ) ); } else { setContentSize( QSizeF( viewport->width() / m_widthScaleFactor, fontHeight + 3 + m_scaleBarHeight ) ); } m_leftBarMargin = QFontMetrics( font() ).boundingRect( "0" ).width() / 2; m_rightBarMargin = QFontMetrics( font() ).boundingRect( "0000" ).width() / 2; m_scaleBarWidth = contentSize().width() - m_leftBarMargin - m_rightBarMargin; m_viewportWidth = viewport->width(); m_radius = viewport->radius(); m_scaleInitDone = true; m_pixel2Length = marbleModel()->planetRadius() / (qreal)(viewport->radius()); if ( viewport->currentProjection()->surfaceType() == AbstractProjection::Cylindrical ) { qreal centerLatitude = viewport->viewLatLonAltBox().center().latitude(); // For flat maps we calculate the length of the 90 deg section of the // central latitude circle. For flat maps this distance matches // the pixel based radius propertyy. m_pixel2Length *= M_PI / 2 * cos( centerLatitude ); } m_scaleBarDistance = (qreal)(m_scaleBarWidth) * m_pixel2Length; const MarbleLocale::MeasurementSystem measurementSystem = MarbleGlobal::getInstance()->locale()->measurementSystem(); if ( measurementSystem != MarbleLocale::MetricSystem ) { m_scaleBarDistance *= KM2MI; } else if (measurementSystem == MarbleLocale::NauticalSystem) { m_scaleBarDistance *= KM2NM; } calcScaleBar(); update(); } AbstractFloatItem::setProjection( viewport ); } void MapScaleFloatItem::paintContent( QPainter *painter ) { painter->save(); painter->setRenderHint( QPainter::Antialiasing, true ); int fontHeight = QFontMetrics( font() ).ascent(); //calculate scale ratio qreal displayMMPerPixel = 1.0 * painter->device()->widthMM() / painter->device()->width(); qreal ratio = m_pixel2Length / (displayMMPerPixel * MM2M); //round ratio to 3 most significant digits, assume that ratio >= 1, otherwise it may display "1 : 0" //i made this assumption because as the primary use case we do not need to zoom in that much qreal power = 1; int iRatio = (int)(ratio + 0.5); //round ratio to the nearest integer while (iRatio >= 1000) { iRatio /= 10; power *= 10; } iRatio *= power; m_ratioString.setNum(iRatio); m_ratioString = QLatin1String("1 : ") + m_ratioString; painter->setPen( QColor( Qt::darkGray ) ); painter->setBrush( QColor( Qt::darkGray ) ); painter->drawRect( m_leftBarMargin, fontHeight + 3, m_scaleBarWidth, m_scaleBarHeight ); painter->setPen( QColor( Qt::black ) ); painter->setBrush( QColor( Qt::white ) ); painter->drawRect( m_leftBarMargin, fontHeight + 3, m_bestDivisor * m_pixelInterval, m_scaleBarHeight ); painter->setPen( QColor( Oxygen::aluminumGray4 ) ); painter->drawLine( m_leftBarMargin + 1, fontHeight + 2 + m_scaleBarHeight, m_leftBarMargin + m_bestDivisor * m_pixelInterval - 1, fontHeight + 2 + m_scaleBarHeight ); painter->setPen( QColor( Qt::black ) ); painter->setBrush( QColor( Qt::black ) ); QString intervalStr; int lastStringEnds = 0; int currentStringBegin = 0; for ( int j = 0; j <= m_bestDivisor; j += 2 ) { if ( j < m_bestDivisor ) { painter->drawRect( m_leftBarMargin + j * m_pixelInterval, fontHeight + 3, m_pixelInterval - 1, m_scaleBarHeight ); painter->setPen( QColor( Oxygen::aluminumGray5 ) ); painter->drawLine( m_leftBarMargin + j * m_pixelInterval + 1, fontHeight + 4, m_leftBarMargin + (j + 1) * m_pixelInterval - 1, fontHeight + 4 ); painter->setPen( QColor( Qt::black ) ); } MarbleLocale::MeasurementSystem distanceUnit; distanceUnit = MarbleGlobal::getInstance()->locale()->measurementSystem(); QString unit = tr("km"); switch ( distanceUnit ) { case MarbleLocale::MetricSystem: if ( m_bestDivisor * m_valueInterval > 10000 ) { unit = tr("km"); intervalStr.setNum( j * m_valueInterval / 1000 ); } else { unit = tr("m"); intervalStr.setNum( j * m_valueInterval ); } break; case MarbleLocale::ImperialSystem: unit = tr("mi"); intervalStr.setNum( j * m_valueInterval / 1000 ); if ( m_bestDivisor * m_valueInterval > 3800 ) { intervalStr.setNum( j * m_valueInterval / 1000 ); } else { intervalStr.setNum( qreal(j * m_valueInterval ) / 1000.0, 'f', 2 ); } break; case MarbleLocale::NauticalSystem: unit = tr("nm"); intervalStr.setNum( j * m_valueInterval / 1000 ); if ( m_bestDivisor * m_valueInterval > 3800 ) { intervalStr.setNum( j * m_valueInterval / 1000 ); } else { intervalStr.setNum( qreal(j * m_valueInterval ) / 1000.0, 'f', 2 ); } break; } painter->setFont( font() ); if ( j == 0 ) { const QString text = QLatin1String("0 ") + unit; painter->drawText(0, fontHeight, text); lastStringEnds = QFontMetrics(font()).width(text); continue; } if( j == m_bestDivisor ) { currentStringBegin = ( j * m_pixelInterval - QFontMetrics( font() ).boundingRect( intervalStr ).width() ); } else { currentStringBegin = ( j * m_pixelInterval - QFontMetrics( font() ).width( intervalStr ) / 2 ); } if ( lastStringEnds < currentStringBegin ) { painter->drawText( currentStringBegin, fontHeight, intervalStr ); lastStringEnds = currentStringBegin + QFontMetrics( font() ).width( intervalStr ); } } int leftRatioIndent = m_leftBarMargin + (m_scaleBarWidth - QFontMetrics( font() ).width(m_ratioString) ) / 2; painter->drawText( leftRatioIndent, fontHeight + 3 + m_scaleBarHeight + fontHeight + 5, m_ratioString ); painter->restore(); } void MapScaleFloatItem::calcScaleBar() { qreal magnitude = 1; // First we calculate the exact length of the whole area that is possibly // available to the scalebar in kilometers int magValue = (int)( m_scaleBarDistance ); // We calculate the two most significant digits of the km-scalebar-length // and store them in magValue. while ( magValue >= 100 ) { magValue /= 10; magnitude *= 10; } m_bestDivisor = 4; int bestMagValue = 1; for ( int i = 0; i < magValue; i++ ) { // We try to find the lowest divisor between 4 and 8 that // divides magValue without remainder. for ( int j = 4; j < 9; j++ ) { if ( ( magValue - i ) % j == 0 ) { // We store the very first result we find and store // m_bestDivisor and bestMagValue as a final result. m_bestDivisor = j; bestMagValue = magValue - i; // Stop all for loops and end search i = magValue; j = 9; } } // If magValue doesn't divide through values between 4 and 8 // (e.g. because it's a prime number) try again with magValue // decreased by i. } m_pixelInterval = (int)( m_scaleBarWidth * (qreal)( bestMagValue ) / (qreal)( magValue ) / m_bestDivisor ); m_valueInterval = (int)( bestMagValue * magnitude / m_bestDivisor ); } QDialog *MapScaleFloatItem::configDialog() { if ( !m_configDialog ) { // Initializing configuration dialog m_configDialog = new QDialog(); ui_configWidget = new Ui::MapScaleConfigWidget; ui_configWidget->setupUi( m_configDialog ); readSettings(); connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), SLOT(writeSettings()) ); connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), SLOT(readSettings()) ); QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply ); connect( applyButton, SIGNAL(clicked()) , this, SLOT(writeSettings()) ); } return m_configDialog; } void MapScaleFloatItem::contextMenuEvent( QWidget *w, QContextMenuEvent *e ) { if ( !m_contextMenu ) { m_contextMenu = contextMenu(); for( QAction *action: m_contextMenu->actions() ) { if ( action->text() == tr( "&Configure..." ) ) { m_contextMenu->removeAction( action ); break; } } QAction *toggleAction = m_contextMenu->addAction( tr("&Ratio Scale"), this, SLOT(toggleRatioScaleVisibility()) ); toggleAction->setCheckable( true ); toggleAction->setChecked( m_showRatioScale ); m_contextMenu->addAction(m_minimizeAction); } Q_ASSERT( m_contextMenu ); m_contextMenu->exec( w->mapToGlobal( e->pos() ) ); } void MapScaleFloatItem::toolTipEvent( QHelpEvent *e ) { QToolTip::showText( e->globalPos(), m_ratioString ); } void MapScaleFloatItem::readSettings() { if ( !m_configDialog ) return; if ( m_showRatioScale ) { ui_configWidget->m_showRatioScaleCheckBox->setCheckState( Qt::Checked ); } else { ui_configWidget->m_showRatioScaleCheckBox->setCheckState( Qt::Unchecked ); } ui_configWidget->m_minimizeCheckBox->setChecked(m_minimized); } void MapScaleFloatItem::writeSettings() { if ( ui_configWidget->m_showRatioScaleCheckBox->checkState() == Qt::Checked ) { m_showRatioScale = true; } else { m_showRatioScale = false; } if (m_minimized != ui_configWidget->m_minimizeCheckBox->isChecked()) { toggleMinimized(); } emit settingsChanged( nameId() ); } void MapScaleFloatItem::toggleRatioScaleVisibility() { m_showRatioScale = !m_showRatioScale; readSettings(); emit settingsChanged( nameId() ); } void MapScaleFloatItem::toggleMinimized() { m_minimized = !m_minimized; ui_configWidget->m_minimizeCheckBox->setChecked(m_minimized); m_minimizeAction->setChecked(m_minimized); readSettings(); emit settingsChanged( nameId() ); if (m_minimized == true) { m_widthScaleFactor = 4; } else { m_widthScaleFactor = 2; } } } #include "moc_MapScaleFloatItem.cpp" diff --git a/src/plugins/render/measure/MeasureToolPlugin.cpp b/src/plugins/render/measure/MeasureToolPlugin.cpp index 5364b2bdc..9d0b91f53 100644 --- a/src/plugins/render/measure/MeasureToolPlugin.cpp +++ b/src/plugins/render/measure/MeasureToolPlugin.cpp @@ -1,705 +1,706 @@ // // 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 2007-2008 Carlos Licea // Copyright 2011 Michael Henning // Copyright 2011 Valery Kharitonov // Copyright 2012 Mohammed Nafees // #include "MeasureToolPlugin.h" #include "MeasureConfigDialog.h" #include "GeoPainter.h" #include "GeoDataLinearRing.h" +#include "MarbleColors.h" #include "MarbleDebug.h" #include "MarbleWidgetPopupMenu.h" #include "MarbleModel.h" #include "MarbleLocale.h" #include "ViewportParams.h" #include "Planet.h" #include #include #include #include namespace Marble { MeasureToolPlugin::MeasureToolPlugin( const MarbleModel *marbleModel ) : RenderPlugin( marbleModel ), m_measureLineString( GeoDataLineString( Tessellate ) ), #ifdef Q_OS_MACX m_font_regular( QFont( QStringLiteral( "Sans Serif" ), 10, 50, false ) ), #else m_font_regular( QFont( QStringLiteral( "Sans Serif" ), 8, 50, false ) ), #endif m_fontascent(-1), m_pen( Qt::red ), m_addMeasurePointAction( nullptr ), m_removeLastMeasurePointAction( nullptr ), m_removeMeasurePointsAction( nullptr ), m_separator( nullptr ), m_marbleWidget( nullptr ), m_configDialog( nullptr ), m_showDistanceLabel( true ), m_showBearingLabel( true ), m_showBearingChangeLabel( true ), m_showPolygonArea(false), m_showCircularArea(true), m_showRadius(true), m_showPerimeter(true), m_showCircumference(true), m_totalDistance(0.0), m_polygonArea(0.0), m_circularArea(0.0), m_radius(0.0), m_perimeter(0.0), m_circumference(0.0), m_paintMode(Polygon) { m_pen.setWidthF( 2.0 ); } QStringList MeasureToolPlugin::backendTypes() const { return QStringList(QStringLiteral("measuretool")); } QString MeasureToolPlugin::renderPolicy() const { return QStringLiteral("ALWAYS"); } QStringList MeasureToolPlugin::renderPosition() const { return QStringList(QStringLiteral("ATMOSPHERE")); } QString MeasureToolPlugin::name() const { return tr( "Measure Tool" ); } QString MeasureToolPlugin::guiString() const { return tr( "&Measure Tool" ); } QString MeasureToolPlugin::nameId() const { return QStringLiteral("measure-tool"); } QString MeasureToolPlugin::version() const { return QStringLiteral("1.0"); } QString MeasureToolPlugin::description() const { return tr( "Measure distances between two or more points." ); } QString MeasureToolPlugin::copyrightYears() const { return QStringLiteral("2006-2008, 2011"); } QVector MeasureToolPlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Dennis Nienhüser"), QStringLiteral("nienhueser@kde.org")) << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org")) << PluginAuthor(QStringLiteral("Inge Wallin"), QStringLiteral("ingwa@kde.org")) << PluginAuthor(QStringLiteral("Carlos Licea"), QStringLiteral("carlos.licea@kdemail.net")) << PluginAuthor(QStringLiteral("Michael Henning"), QStringLiteral("mikehenning@eclipse.net")) << PluginAuthor(QStringLiteral("Valery Kharitonov"), QStringLiteral("kharvd@gmail.com")) << PluginAuthor(QStringLiteral("Mohammed Nafees"), QStringLiteral("nafees.technocool@gmail.com")) << PluginAuthor(QStringLiteral("Illya Kovalevskyy"), QStringLiteral("illya.kovalevskyy@gmail.com")); } QIcon MeasureToolPlugin::icon () const { return QIcon(QStringLiteral(":/icons/measure.png")); } void MeasureToolPlugin::initialize () { m_fontascent = QFontMetrics( m_font_regular ).ascent(); } bool MeasureToolPlugin::isInitialized () const { return m_fontascent >= 0; } QDialog *MeasureToolPlugin::configDialog() { if ( !m_configDialog ) { m_configDialog = new MeasureConfigDialog(m_configDialog); connect( m_configDialog, SIGNAL(accepted()), SLOT(writeSettings()) ); connect( m_configDialog, SIGNAL(applied()), this, SLOT(writeSettings()) ); } m_configDialog->setShowDistanceLabels( m_showDistanceLabel ); m_configDialog->setShowBearingLabel( m_showBearingLabel ); m_configDialog->setShowBearingLabelChange( m_showBearingChangeLabel ); m_configDialog->setShowPolygonArea( m_showPolygonArea ); m_configDialog->setShowCircularArea( m_showCircularArea ); m_configDialog->setShowRadius( m_showRadius ); m_configDialog->setShowPerimeter( m_showPerimeter ); m_configDialog->setShowCircumference( m_showCircumference ); m_configDialog->setPaintMode( m_paintMode ); return m_configDialog; } QHash MeasureToolPlugin::settings() const { QHash settings = RenderPlugin::settings(); settings.insert(QStringLiteral("showDistanceLabel"), m_showDistanceLabel); settings.insert(QStringLiteral("showBearingLabel"), m_showBearingLabel); settings.insert(QStringLiteral("showBearingChangeLabel"), m_showBearingChangeLabel); settings.insert(QStringLiteral("showPolygonArea"), m_showPolygonArea); settings.insert(QStringLiteral("showCircularArea"), m_showCircularArea); settings.insert(QStringLiteral("showRadius"), m_showRadius); settings.insert(QStringLiteral("showPerimeter"), m_showPerimeter); settings.insert(QStringLiteral("showCircumference"), m_showCircumference); settings.insert(QStringLiteral("paintMode"), (int)m_paintMode); return settings; } void MeasureToolPlugin::setSettings( const QHash &settings ) { RenderPlugin::setSettings( settings ); m_showDistanceLabel = settings.value(QStringLiteral("showDistanceLabel"), true).toBool(); m_showBearingLabel = settings.value(QStringLiteral("showBearingLabel"), true).toBool(); m_showBearingChangeLabel = settings.value(QStringLiteral("showBearingChangeLabel"), true).toBool(); m_showPolygonArea = settings.value(QStringLiteral("showPolygonArea"), false).toBool(); m_showCircularArea = settings.value(QStringLiteral("showCircularArea"), true).toBool(); m_showRadius = settings.value(QStringLiteral("showRadius"), true).toBool(); m_showPerimeter = settings.value(QStringLiteral("showPerimeter"), true).toBool(); m_showCircumference = settings.value(QStringLiteral("showCircumference"), true).toBool(); m_paintMode = (PaintMode)settings.value(QStringLiteral("paintMode"), 0).toInt(); } void MeasureToolPlugin::writeSettings() { m_showDistanceLabel = m_configDialog->showDistanceLabels(); m_showBearingLabel = m_configDialog->showBearingLabel(); m_showBearingChangeLabel = m_configDialog->showBearingLabelChange(); m_showPolygonArea = m_configDialog->showPolygonArea(); m_showCircularArea = m_configDialog->showCircularArea(); m_showRadius = m_configDialog->showRadius(); m_showPerimeter = m_configDialog->showPerimeter(); m_showCircumference = m_configDialog->showCircumference(); m_paintMode = (PaintMode)m_configDialog->paintMode(); if (m_paintMode == Circular) { if (m_measureLineString.size() < 2) { m_addMeasurePointAction->setEnabled(true); } else { m_addMeasurePointAction->setEnabled(false); while (m_measureLineString.size() > 2) m_measureLineString.remove(m_measureLineString.size()-1); } } else { m_addMeasurePointAction->setEnabled(true); } emit settingsChanged( nameId() ); emit repaintNeeded(); } bool MeasureToolPlugin::render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer * layer ) { Q_UNUSED(renderPos) Q_UNUSED(layer) m_latLonAltBox = viewport->viewLatLonAltBox(); // No way to paint anything if the list is empty. if ( m_measureLineString.isEmpty() ) return true; painter->save(); // Prepare for painting the measure line string and paint it. painter->setPen( m_pen ); if ( m_showDistanceLabel || m_showBearingLabel || m_showBearingChangeLabel ) { drawSegments( painter ); } else { painter->drawPolyline( m_measureLineString ); } // Paint the nodes of the paths. drawMeasurePoints( painter ); m_totalDistance = m_measureLineString.length( marbleModel()->planet()->radius() ); if ( m_measureLineString.size() > 1 ) drawInfobox(painter); painter->restore(); return true; } void MeasureToolPlugin::drawSegments( GeoPainter* painter ) { for ( int segmentIndex = 0; segmentIndex < m_measureLineString.size() - 1; ++segmentIndex ) { GeoDataLineString segment( Tessellate ); segment << m_measureLineString[segmentIndex] ; segment << m_measureLineString[segmentIndex + 1]; QPen shadowPen( Oxygen::aluminumGray5 ); shadowPen.setWidthF(4.0); painter->setPen( shadowPen ); painter->drawPolyline( segment ); QString infoString; if ( (m_paintMode == Polygon && m_showDistanceLabel) || (m_paintMode == Circular && m_showRadius) ) { const qreal segmentLength = segment.length( marbleModel()->planet()->radius() ); m_radius = segmentLength; infoString = meterToPreferredUnit(segmentLength); } if ( m_showBearingLabel && m_paintMode != Circular ) { GeoDataCoordinates coordinates = segment.first(); qreal bearing = coordinates.bearing( segment.last(), GeoDataCoordinates::Degree ); if ( bearing < 0 ) { bearing += 360; } QString bearingString = QString::fromUtf8( "%1°" ).arg( bearing, 0, 'f', 2 ); if ( !infoString.isEmpty() ) { infoString += QLatin1Char('\n'); } infoString.append( bearingString ); } if ( m_showBearingChangeLabel && segmentIndex != 0 ) { GeoDataCoordinates currentCoordinates = m_measureLineString[segmentIndex]; qreal currentBearing = currentCoordinates.bearing(m_measureLineString[segmentIndex+1]); qreal previousBearing = currentCoordinates.bearing( m_measureLineString[segmentIndex-1]); GeoDataLinearRing ring; painter->setPen( Qt::NoPen ); painter->setBrush( QBrush ( QColor ( 127, 127, 127, 127 ) ) ); if (currentBearing < previousBearing) currentBearing += 2 * M_PI; ring << currentCoordinates; qreal angleLength = qAbs(m_latLonAltBox.north() - m_latLonAltBox.south()) / 20; qreal iterBearing = previousBearing; while ( iterBearing < currentBearing ) { ring << currentCoordinates.moveByBearing( iterBearing, angleLength ); iterBearing += 0.1; } ring << currentCoordinates.moveByBearing( currentBearing, angleLength ); painter->drawPolygon( ring ); qreal currentBearingChange = (currentBearing - previousBearing) * RAD2DEG; if (currentBearingChange < 0) currentBearingChange += 360; QString bearingChangedString = QString::fromUtf8( "%1°" ).arg( currentBearingChange, 0, 'f', 2 ); painter->setPen( Qt::black ); GeoDataCoordinates textPosition = ring.latLonAltBox().center(); qreal deltaEast = ring.latLonAltBox().east() - currentCoordinates.longitude(); qreal deltaWest = currentCoordinates.longitude() - ring.latLonAltBox().west(); if (deltaEast > deltaWest) { textPosition.setLongitude(currentCoordinates.longitude() + deltaEast / 2); } else { textPosition.setLongitude(currentCoordinates.longitude() - deltaWest); } painter->drawText(textPosition, bearingChangedString ); } // Drawing ellipse around 1st point towards the 2nd if ( m_paintMode == Circular ) { GeoDataCoordinates currentCoordinates = m_measureLineString[segmentIndex]; GeoDataLinearRing ring; // planetRadius - planet radius // d - distance between points // S - area of the painted circle qreal planetRadius = marbleModel()->planet()->radius(); qreal d = m_measureLineString.length(1); m_circularArea = 2 * M_PI * planetRadius * planetRadius * (1 - qCos(d)); qreal iterBearing = 0; while ( iterBearing < 2 * M_PI ) { ring << currentCoordinates.moveByBearing(iterBearing, d); iterBearing += 0.1; } painter->setPen( Qt::NoPen ); painter->setBrush( QBrush ( QColor ( 127, 127, 127, 127 ) ) ); painter->drawPolygon(ring); if ( m_showCircularArea ) { painter->setPen(Qt::white); GeoDataCoordinates textPosition = ring.latLonAltBox().center(); QString areaText = tr("Area:\n%1").arg(meterToPreferredUnit(m_circularArea, true)); QFontMetrics fontMetrics = painter->fontMetrics(); QRect boundingRect = fontMetrics.boundingRect(QRect(), Qt::AlignCenter, areaText); painter->drawText(textPosition, areaText, -boundingRect.width()/2, -boundingRect.height()*1.5, boundingRect.width(), boundingRect.height(), QTextOption(Qt::AlignCenter)); } if ( m_showCircumference ) { painter->setPen(Qt::white); GeoDataCoordinates textPosition = ring.latLonAltBox().center(); m_circumference = 2 * M_PI * planetRadius * qSin(d); QString circumferenceText = tr("Circumference:\n%1").arg(meterToPreferredUnit(m_circumference)); QFontMetrics fontMetrics = painter->fontMetrics(); QRect boundingRect = fontMetrics.boundingRect(QRect(),Qt::AlignCenter, circumferenceText); painter->drawText(textPosition, circumferenceText, -boundingRect.width()/2, boundingRect.height(), boundingRect.width(), boundingRect.height(), QTextOption(Qt::AlignCenter)); } } if ( !infoString.isEmpty() ) { QPen linePen; // have three alternating colors for the segments switch ( segmentIndex % 3 ) { case 0: linePen.setColor( Oxygen::brickRed4 ); break; case 1: linePen.setColor( Oxygen::forestGreen4 ); break; case 2: linePen.setColor( Oxygen::skyBlue4 ); break; } linePen.setWidthF(2.0); painter->setPen( linePen ); painter->drawPolyline( segment, infoString, LineCenter ); } } if (m_paintMode == Polygon && m_measureLineString.size() > 2) { GeoDataLinearRing measureRing(m_measureLineString); if (m_showPolygonArea || m_showPerimeter) { painter->setPen( Qt::NoPen ); painter->setBrush( QBrush ( QColor ( 127, 127, 127, 127 ) ) ); painter->drawPolygon(measureRing); QPen shadowPen( Oxygen::aluminumGray5 ); shadowPen.setStyle(Qt::DashLine); shadowPen.setWidthF(3.0); painter->setPen( shadowPen ); painter->drawPolyline(GeoDataLineString( Tessellate ) << m_measureLineString.first() << m_measureLineString.last()); } if (m_showPolygonArea) { qreal theta1 = 0.0; qreal n = m_measureLineString.size(); for (int segmentIndex = 1; segmentIndex < m_measureLineString.size()-1; segmentIndex++) { GeoDataCoordinates current = m_measureLineString[segmentIndex]; qreal prevBearing = current.bearing(m_measureLineString[segmentIndex-1]); qreal nextBearing = current.bearing(m_measureLineString[segmentIndex+1]); if (nextBearing < prevBearing) nextBearing += 2 * M_PI; qreal angle = nextBearing - prevBearing; theta1 += angle; } // Traversing first vertex GeoDataCoordinates current = m_measureLineString[0]; qreal prevBearing = current.bearing(m_measureLineString[n-1]); qreal nextBearing = current.bearing(m_measureLineString[1]); if (nextBearing < prevBearing) nextBearing += 2 * M_PI; qreal angle = nextBearing - prevBearing; theta1 += angle; // And the last one current = m_measureLineString[n-1]; prevBearing = current.bearing(m_measureLineString[n-2]); nextBearing = current.bearing(m_measureLineString[0]); if (nextBearing < prevBearing) nextBearing += 2 * M_PI; angle = nextBearing - prevBearing; theta1 += angle; qreal theta2 = 2 * M_PI * n - theta1; // theta = smaller of theta1 and theta2 qreal theta = (theta1 < theta2) ? theta1 : theta2; qreal planetRadius = marbleModel()->planet()->radius(); qreal S = qAbs((theta - (n-2) * M_PI) * planetRadius * planetRadius); m_polygonArea = S; painter->setPen(Qt::white); GeoDataCoordinates textPosition = measureRing.latLonAltBox().center(); QString areaText = tr("Area:\n%1").arg(meterToPreferredUnit(S, true)); QFontMetrics fontMetrics = painter->fontMetrics(); QRect boundingRect = fontMetrics.boundingRect(QRect(), Qt::AlignCenter, areaText); painter->drawText(textPosition, areaText, -boundingRect.width()/2, -(boundingRect.height()+fontMetrics.height()*0.25), boundingRect.width(), boundingRect.height(), QTextOption(Qt::AlignCenter)); } if (m_showPerimeter) { painter->setPen(Qt::white); GeoDataCoordinates textPosition = measureRing.latLonAltBox().center(); qreal P = measureRing.length(marbleModel()->planet()->radius()); m_perimeter = P; QString perimeterText = tr("Perimeter:\n%1").arg(meterToPreferredUnit(P)); QFontMetrics fontMetrics = painter->fontMetrics(); QRect boundingRect = fontMetrics.boundingRect(QRect(),Qt::AlignCenter, perimeterText); painter->drawText(textPosition, perimeterText, -boundingRect.width()/2, 0, boundingRect.width(), boundingRect.height(), QTextOption(Qt::AlignCenter)); } } } QString MeasureToolPlugin::meterToPreferredUnit(qreal meters, bool isSquare) { MarbleLocale *locale = MarbleGlobal::getInstance()->locale(); const MarbleLocale::MeasurementSystem measurementSystem = locale->measurementSystem(); MarbleLocale::MeasureUnit unit; qreal convertedMeters; if (isSquare) meters = qSqrt(meters); locale->meterToTargetUnit(meters, measurementSystem, convertedMeters, unit); QString unitString = locale->unitAbbreviation(unit); if (isSquare) { qreal k = convertedMeters/meters; convertedMeters *= k; convertedMeters *= meters; unitString.append(QChar(0xB2)); } return QString("%L1 %2").arg(convertedMeters, 8, 'f', 1, QLatin1Char(' ')) .arg(unitString); } void MeasureToolPlugin::drawMeasurePoints( GeoPainter *painter ) { // Paint the marks. GeoDataLineString::const_iterator itpoint = m_measureLineString.constBegin(); GeoDataLineString::const_iterator const endpoint = m_measureLineString.constEnd(); if (m_mark.isNull()) { m_mark = QPixmap(QStringLiteral(":/mark.png")); } for (; itpoint != endpoint; ++itpoint ) { painter->drawPixmap( *itpoint, m_mark ); } } void MeasureToolPlugin::drawInfobox( GeoPainter *painter ) const { QString boxContent; if (m_paintMode == Polygon) { boxContent += QLatin1String("") + tr("Polygon Ruler") + QLatin1String(":
\n"); } else /* Circular */ { boxContent += QLatin1String("") + tr("Circle Ruler") + QLatin1String(":
\n"); } if (m_paintMode == Polygon) { boxContent += tr("Total Distance: %1
\n").arg( meterToPreferredUnit(m_totalDistance) ); if (m_showPolygonArea) boxContent += tr("Area: %1
\n").arg( meterToPreferredUnit(m_polygonArea, true) ); if (m_showPerimeter) boxContent += tr("Perimeter: %1
\n").arg( meterToPreferredUnit(m_perimeter) ); } else /* Circular */ { if (m_showRadius) boxContent += tr("Radius: %1
\n").arg( meterToPreferredUnit(m_radius) ); if (m_showCircumference) boxContent += tr("Circumference: %1
\n").arg( meterToPreferredUnit(m_circumference) ); if (m_showCircularArea) boxContent += tr("Area: %1
\n").arg( meterToPreferredUnit(m_circularArea, true) ); } painter->setPen( QColor( Qt::black ) ); painter->setBrush( QColor( 192, 192, 192, 192 ) ); QTextDocument doc; doc.setHtml(boxContent); doc.setDefaultFont(m_font_regular); doc.adjustSize(); QSizeF pageSize = doc.size(); painter->drawRect( 10, 105, 10 + pageSize.width(), pageSize.height() ); QTransform transform; transform.translate(15, 110); painter->setTransform(transform); doc.drawContents(painter); painter->setTransform(QTransform()); } void MeasureToolPlugin::addMeasurePoint( qreal lon, qreal lat ) { m_measureLineString << GeoDataCoordinates( lon, lat ); emit numberOfMeasurePointsChanged( m_measureLineString.size() ); } void MeasureToolPlugin::removeLastMeasurePoint() { if (!m_measureLineString.isEmpty()) m_measureLineString.remove( m_measureLineString.size() - 1 ); emit numberOfMeasurePointsChanged( m_measureLineString.size() ); } void MeasureToolPlugin::removeMeasurePoints() { m_measureLineString.clear(); emit numberOfMeasurePointsChanged( m_measureLineString.size() ); } void MeasureToolPlugin::addContextItems() { MarbleWidgetPopupMenu *menu = m_marbleWidget->popupMenu(); // Connect the inputHandler and the measure tool to the popup menu m_addMeasurePointAction = new QAction(QIcon(QStringLiteral(":/icons/measure.png")), tr("Add &Measure Point"), this); m_removeLastMeasurePointAction = new QAction( tr( "Remove &Last Measure Point" ), this ); m_removeLastMeasurePointAction->setEnabled( false ); m_removeMeasurePointsAction = new QAction( tr( "&Remove Measure Points" ), this ); m_removeMeasurePointsAction->setEnabled( false ); m_separator = new QAction( this ); m_separator->setSeparator( true ); bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if ( !smallScreen ) { menu->addAction( Qt::RightButton, m_addMeasurePointAction ); menu->addAction( Qt::RightButton, m_removeLastMeasurePointAction ); menu->addAction( Qt::RightButton, m_removeMeasurePointsAction ); menu->addAction( Qt::RightButton, m_separator ); } connect( m_addMeasurePointAction, SIGNAL(triggered()), SLOT(addMeasurePointEvent()) ); connect( m_removeLastMeasurePointAction, SIGNAL(triggered()), SLOT(removeLastMeasurePoint()) ); connect( m_removeMeasurePointsAction, SIGNAL(triggered()), SLOT(removeMeasurePoints()) ); connect( this, SIGNAL(numberOfMeasurePointsChanged(int)), SLOT(setNumberOfMeasurePoints(int)) ); } void MeasureToolPlugin::removeContextItems() { delete m_addMeasurePointAction; delete m_removeLastMeasurePointAction; delete m_removeMeasurePointsAction; delete m_separator; } void MeasureToolPlugin::addMeasurePointEvent() { QPoint p = m_marbleWidget->popupMenu()->mousePosition(); qreal lat; qreal lon; m_marbleWidget->geoCoordinates( p.x(), p.y(), lon, lat, GeoDataCoordinates::Radian ); addMeasurePoint( lon, lat ); } void MeasureToolPlugin::setNumberOfMeasurePoints( int newNumber ) { const bool enableMeasureActions = ( newNumber > 0 ); m_removeMeasurePointsAction->setEnabled(enableMeasureActions); m_removeLastMeasurePointAction->setEnabled(enableMeasureActions); if (m_paintMode == Circular) { if (newNumber >= 2) { m_addMeasurePointAction->setEnabled(false); } else { m_addMeasurePointAction->setEnabled(true); } } } bool MeasureToolPlugin::eventFilter( QObject *object, QEvent *e ) { if ( m_marbleWidget && !enabled() ) { m_marbleWidget = nullptr; removeContextItems(); m_measureLineString.clear(); } if ( m_marbleWidget || !enabled() || !visible() ) { return RenderPlugin::eventFilter( object, e ); } MarbleWidget *widget = qobject_cast( object ); if ( widget ) { m_marbleWidget = widget; addContextItems(); } return RenderPlugin::eventFilter( object, e ); } } #include "moc_MeasureToolPlugin.cpp" diff --git a/src/plugins/render/positionmarker/PositionMarker.cpp b/src/plugins/render/positionmarker/PositionMarker.cpp index cd8251955..9c6816dac 100644 --- a/src/plugins/render/positionmarker/PositionMarker.cpp +++ b/src/plugins/render/positionmarker/PositionMarker.cpp @@ -1,480 +1,481 @@ // // 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 // Copyright 2010 Daniel Marth // #include "PositionMarker.h" #include "MarbleDebug.h" #include #include #include #include #include #include #include #include "ui_PositionMarkerConfigWidget.h" +#include "MarbleColors.h" #include "MarbleModel.h" #include "MarbleDirs.h" #include "GeoPainter.h" #include "PositionTracking.h" #include "ViewportParams.h" #include "Planet.h" #include "GeoDataAccuracy.h" namespace Marble { const int PositionMarker::sm_defaultSizeStep = 2; const float PositionMarker::sm_resizeSteps[] = { 0.25, 0.5, 1.0, 2.0, 4.0 }; const int PositionMarker::sm_numResizeSteps = sizeof( sm_resizeSteps ) / sizeof( sm_resizeSteps[0] ); PositionMarker::PositionMarker( const MarbleModel *marbleModel ) : RenderPlugin( marbleModel ), m_marbleModel( marbleModel ), m_isInitialized( false ), m_useCustomCursor( false ), m_defaultCursorPath(MarbleDirs::path(QStringLiteral("svg/track_turtle.svg"))), m_lastBoundingBox(), ui_configWidget( nullptr ), m_configDialog( nullptr ), m_cursorPath( m_defaultCursorPath ), m_cursorSize( 1.0 ), m_accuracyColor( Oxygen::brickRed4 ), m_trailColor( 0, 0, 255 ), m_heading( 0.0 ), m_showTrail ( false ) { const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; m_accuracyColor.setAlpha( smallScreen ? 80 : 40 ); } PositionMarker::~PositionMarker () { delete ui_configWidget; delete m_configDialog; } QStringList PositionMarker::renderPosition() const { return QStringList(QStringLiteral("HOVERS_ABOVE_SURFACE")); } QString PositionMarker::renderPolicy() const { return QStringLiteral("ALWAYS"); } QStringList PositionMarker::backendTypes() const { return QStringList(QStringLiteral("positionmarker")); } QString PositionMarker::name() const { return tr( "Position Marker" ); } QString PositionMarker::guiString() const { return tr( "&Position Marker" ); } QString PositionMarker::nameId() const { return QStringLiteral("positionMarker"); } QString PositionMarker::version() const { return QStringLiteral("1.0"); } QString PositionMarker::description() const { return tr( "draws a marker at the current position" ); } QString PositionMarker::copyrightYears() const { return QStringLiteral("2009, 2010"); } QVector PositionMarker::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Andrew Manson"), QStringLiteral("g.real.ate@gmail.com")) << PluginAuthor(QStringLiteral("Eckhart Woerner"), QStringLiteral("ewoerner@kde.org")) << PluginAuthor(QStringLiteral("Thibaut Gridel"), QStringLiteral("tgridel@free.fr")) << PluginAuthor(QStringLiteral("Daniel Marth"), QStringLiteral("danielmarth@gmx.at")); } QIcon PositionMarker::icon() const { return QIcon(QStringLiteral(":/icons/positionmarker.png")); } QDialog *PositionMarker::configDialog() { if ( !m_configDialog ) { // Initializing configuration dialog m_configDialog = new QDialog(); ui_configWidget = new Ui::PositionMarkerConfigWidget; ui_configWidget->setupUi( m_configDialog ); ui_configWidget->m_resizeSlider->setMaximum( sm_numResizeSteps - 1 ); readSettings(); connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), SLOT(writeSettings()) ); connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), SLOT(readSettings()) ); connect( ui_configWidget->m_buttonBox->button( QDialogButtonBox::RestoreDefaults ), SIGNAL(clicked()), SLOT(restoreDefaultSettings()) ); QPushButton *applyButton = ui_configWidget->m_buttonBox->button( QDialogButtonBox::Apply ); connect( applyButton, SIGNAL(clicked()), SLOT(writeSettings()) ); connect( ui_configWidget->m_fileChooserButton, SIGNAL(clicked()), SLOT(chooseCustomCursor()) ); connect( ui_configWidget->m_resizeSlider, SIGNAL(valueChanged(int)), SLOT(resizeCursor(int)) ); connect( ui_configWidget->m_acColorChooserButton, SIGNAL(clicked()), SLOT(chooseColor()) ); connect( ui_configWidget->m_trailColorChooserButton, SIGNAL(clicked()), SLOT(chooseColor()) ); } return m_configDialog; } void PositionMarker::initialize() { if ( marbleModel() ) { connect( marbleModel()->positionTracking(), SIGNAL(gpsLocation(GeoDataCoordinates,qreal)), this, SLOT(setPosition(GeoDataCoordinates)) ); connect( marbleModel()->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)), this, SIGNAL(repaintNeeded()) ); m_isInitialized = true; } loadDefaultCursor(); } bool PositionMarker::isInitialized() const { return m_isInitialized; } bool PositionMarker::render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer * layer ) { Q_UNUSED( renderPos ) Q_UNUSED( layer ) bool const gpsActive = marbleModel()->positionTracking()->positionProviderPlugin() != nullptr; bool const positionAvailable = marbleModel()->positionTracking()->status() == PositionProviderStatusAvailable; bool const positionValid = m_currentPosition.isValid(); if ( gpsActive && positionAvailable && positionValid ) { m_lastBoundingBox = viewport->viewLatLonAltBox(); qreal screenPositionX, screenPositionY; if (!viewport->screenCoordinates( m_currentPosition, screenPositionX, screenPositionY )){ return true; } const GeoDataCoordinates top( m_currentPosition.longitude(), m_currentPosition.latitude()+0.1 ); qreal screenTopX, screenTopY; if (!viewport->screenCoordinates( top, screenTopX, screenTopY )){ return true; } qreal const correction = -90.0 + RAD2DEG * atan2( screenPositionY -screenTopY, screenPositionX - screenTopX ); const qreal rotation = m_heading + correction; if ( m_useCustomCursor ) { QTransform transform; transform.rotate( rotation ); bool const highQuality = painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality; Qt::TransformationMode const mode = highQuality ? Qt::SmoothTransformation : Qt::FastTransformation; m_customCursorTransformed = m_customCursor.transformed( transform, mode ); } else { // Calculate the scaled arrow shape const QPointF baseX( m_cursorSize, 0.0 ); const QPointF baseY( 0.0, m_cursorSize ); const QPointF relativeLeft = - ( baseX * 9 ) + ( baseY * 9 ); const QPointF relativeRight = ( baseX * 9 ) + ( baseY * 9 ); const QPointF relativeTip = - ( baseY * 19.0 ); m_arrow = QPolygonF() << QPointF( 0.0, 0.0 ) << relativeLeft << relativeTip << relativeRight; // Rotate the shape according to the current direction and move it to the screen center QMatrix transformation; transformation.translate( screenPositionX, screenPositionY ); transformation.rotate( rotation ); m_arrow = m_arrow * transformation; m_dirtyRegion = QRegion(); m_dirtyRegion += ( m_arrow.boundingRect().toRect() ); m_dirtyRegion += ( m_previousArrow.boundingRect().toRect() ); } painter->save(); GeoDataAccuracy accuracy = marbleModel()->positionTracking()->accuracy(); if ( accuracy.horizontal > 0 && accuracy.horizontal < 1000 ) { // Paint a circle indicating the position accuracy painter->setPen( Qt::transparent ); qreal planetRadius = m_marbleModel->planet()->radius(); int width = qRound( accuracy.horizontal * viewport->radius() / planetRadius ); if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) { int arrowSize = qMax( m_arrow.boundingRect().width(), m_arrow.boundingRect().height() ); width = qMax( width, arrowSize + 10 ); } painter->setBrush( m_accuracyColor ); painter->drawEllipse( m_currentPosition, width, width ); } // Draw trail if requested. if( m_showTrail ) { painter->save(); // Use selected color to draw trail. painter->setBrush( m_trailColor ); painter->setPen( m_trailColor ); // we don't draw m_trail[0] which is current position for( int i = 1; i < m_trail.size(); ++i ) { // Get screen coordinates from coordinates on the map. qreal trailPointX, trailPointY; viewport->screenCoordinates( m_trail[i], trailPointX, trailPointY ); const int size = ( sm_numTrailPoints - i ) * 3; QRectF trailRect; trailRect.setX( trailPointX - size / 2.0 ); trailRect.setY( trailPointY - size / 2.0 ); trailRect.setWidth( size ); trailRect.setHeight( size ); const qreal opacity = 1.0 - 0.15 * ( i - 1 ); painter->setOpacity( opacity ); painter->drawEllipse( trailRect ); } painter->restore(); } if( m_useCustomCursor) { painter->drawPixmap( m_currentPosition, m_customCursorTransformed ); } else { painter->setPen( Qt::black ); painter->setBrush( Qt::white ); painter->drawPolygon( m_arrow ); } painter->restore(); m_previousArrow = m_arrow; } return true; } QHash PositionMarker::settings() const { QHash settings = RenderPlugin::settings(); settings.insert(QStringLiteral("useCustomCursor"), m_useCustomCursor); settings.insert(QStringLiteral("cursorPath"), m_cursorPath); settings.insert(QStringLiteral("cursorSize"), m_cursorSize); settings.insert(QStringLiteral("acColor"), m_accuracyColor); settings.insert(QStringLiteral("trailColor"), m_trailColor); settings.insert(QStringLiteral("showTrail"), m_showTrail); return settings; } void PositionMarker::setSettings( const QHash &settings ) { RenderPlugin::setSettings( settings ); const bool smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; QColor defaultColor = Oxygen::brickRed4; defaultColor.setAlpha( smallScreen ? 80 : 40 ); m_useCustomCursor = settings.value(QStringLiteral("useCustomCursor"), false).toBool(); m_cursorPath = settings.value(QStringLiteral("cursorPath"), m_defaultCursorPath).toString(); m_cursorSize = settings.value(QStringLiteral("cursorSize"), 1.0).toFloat(); loadCustomCursor( m_cursorPath, m_useCustomCursor ); m_accuracyColor = settings.value(QStringLiteral("acColor"), defaultColor).value(); m_trailColor = settings.value(QStringLiteral("trailColor"), QColor(0, 0, 255)).value(); m_showTrail = settings.value(QStringLiteral("showTrail"), false).toBool(); readSettings(); } void PositionMarker::readSettings() { if ( !m_configDialog ) { return; } if( m_useCustomCursor ) ui_configWidget->m_customCursor->click(); else ui_configWidget->m_originalCursor->click(); bool found = false; float cursorSize = m_cursorSize; for( int i = 0; i < sm_numResizeSteps && !found; i++ ) { if( sm_resizeSteps[i] == cursorSize ) { ui_configWidget->m_resizeSlider->setValue( i ); found = true; } } if( !found ) { ui_configWidget->m_resizeSlider->setValue( sm_defaultSizeStep ); cursorSize = sm_resizeSteps[sm_defaultSizeStep]; } ui_configWidget->m_sizeLabel->setText( tr( "Cursor Size: %1" ).arg( cursorSize ) ); QPalette palette = ui_configWidget->m_acColorChooserButton->palette(); palette.setColor( QPalette::Button, m_accuracyColor ); ui_configWidget->m_acColorChooserButton->setPalette( palette ); palette = ui_configWidget->m_trailColorChooserButton->palette(); palette.setColor( QPalette::Button, m_trailColor ); ui_configWidget->m_trailColorChooserButton->setPalette( palette ); ui_configWidget->m_trailCheckBox->setChecked( m_showTrail ); } void PositionMarker::writeSettings() { if ( !m_configDialog ) { return; } m_useCustomCursor = ui_configWidget->m_customCursor->isChecked(); m_cursorPath = m_cursorPath; m_cursorSize = sm_resizeSteps[ui_configWidget->m_resizeSlider->value()]; m_accuracyColor = m_accuracyColor; m_trailColor = m_trailColor; m_showTrail = ui_configWidget->m_trailCheckBox->isChecked(); emit settingsChanged( nameId() ); } void PositionMarker::setPosition( const GeoDataCoordinates &position ) { m_previousPosition = m_currentPosition; m_currentPosition = position; m_heading = marbleModel()->positionTracking()->direction(); // Update the trail m_trail.push_front( m_currentPosition ); for( int i = sm_numTrailPoints + 1; i< m_trail.size(); ++i ) { m_trail.pop_back(); } if ( m_lastBoundingBox.contains( m_currentPosition ) ) { emit repaintNeeded( m_dirtyRegion ); } } void PositionMarker::chooseCustomCursor() { QString filename = QFileDialog::getOpenFileName( nullptr, tr( "Choose Custom Cursor" ) ); if( !filename.isEmpty() ) loadCustomCursor( filename, true ); } void PositionMarker::loadCustomCursor( const QString& filename, bool useCursor ) { m_customCursor = QPixmap( filename ).scaled( 22 * m_cursorSize, 22 * m_cursorSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); if( !m_customCursor.isNull() ) { if( m_configDialog ) { if( useCursor ) ui_configWidget->m_customCursor->click(); ui_configWidget->m_fileChooserButton->setIconSize( QSize( m_customCursor.width(), m_customCursor.height() ) ); ui_configWidget->m_fileChooserButton->setIcon( QIcon( m_customCursor ) ); } m_cursorPath = filename; } else { mDebug() << "Unable to load custom cursor from " << filename << ". " << "The default cursor will be used instead"; if ( m_configDialog ) ui_configWidget->m_fileChooserButton->setIcon( QIcon( m_defaultCursor ) ); m_customCursor = m_defaultCursor; m_cursorPath = m_defaultCursorPath; } } void PositionMarker::loadDefaultCursor() { m_defaultCursor = QPixmap( m_defaultCursorPath ).scaled( 22 * m_cursorSize, 22 * m_cursorSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); } void PositionMarker::chooseColor() { QColor initialColor; if( sender() == ui_configWidget->m_acColorChooserButton ) { initialColor = m_accuracyColor; } else if( sender() == ui_configWidget->m_trailColorChooserButton ) { initialColor = m_trailColor; } QColor color = QColorDialog::getColor( initialColor, nullptr, tr( "Please choose a color" ), QColorDialog::ShowAlphaChannel ); if( color.isValid() ) { QPalette palette; if( sender() == ui_configWidget->m_acColorChooserButton ) { m_accuracyColor = color; palette = ui_configWidget->m_acColorChooserButton->palette(); palette.setColor( QPalette::Button, m_accuracyColor ); ui_configWidget->m_acColorChooserButton->setPalette( palette ); } else if( sender() == ui_configWidget->m_trailColorChooserButton ) { m_trailColor = color; palette = ui_configWidget->m_trailColorChooserButton->palette(); palette.setColor( QPalette::Button, m_trailColor ); ui_configWidget->m_trailColorChooserButton->setPalette( palette ); } } } void PositionMarker::resizeCursor( int step ) { m_cursorSize = sm_resizeSteps[step]; float newSize = 22 * m_cursorSize; m_customCursor = QPixmap( m_cursorPath ).scaled( newSize, newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ); ui_configWidget->m_sizeLabel->setText( tr( "Cursor Size: %1" ).arg( m_cursorSize ) ); if( !m_customCursor.isNull() ) { ui_configWidget->m_fileChooserButton->setIconSize( QSize( m_customCursor.width(), m_customCursor.height() ) ); ui_configWidget->m_fileChooserButton->setIcon( QIcon( m_customCursor ) ); } loadDefaultCursor(); } qreal PositionMarker::zValue() const { return 1.0; } } #include "moc_PositionMarker.cpp" diff --git a/src/plugins/render/satellites/SatellitesModel.cpp b/src/plugins/render/satellites/SatellitesModel.cpp index 51e2d80e5..8daac829d 100644 --- a/src/plugins/render/satellites/SatellitesModel.cpp +++ b/src/plugins/render/satellites/SatellitesModel.cpp @@ -1,272 +1,273 @@ // // 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 // #include "SatellitesModel.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "SatellitesMSCItem.h" #include "SatellitesTLEItem.h" #include "MarbleClock.h" +#include "MarbleColors.h" #include "GeoDataPlacemark.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataLabelStyle.h" #include "GeoDataLineStyle.h" #include #include #include namespace Marble { SatellitesModel::SatellitesModel( GeoDataTreeModel *treeModel, const MarbleClock *clock ) : TrackerPluginModel( treeModel ), m_clock( clock ), m_currentColorIndex( 0 ) { setupColors(); connect(m_clock, SIGNAL(timeChanged()), this, SLOT(update())); } void SatellitesModel::setupColors() { m_colorList.push_back( Oxygen::brickRed4 ); m_colorList.push_back( Oxygen::raspberryPink4 ); m_colorList.push_back( Oxygen::burgundyPurple4 ); m_colorList.push_back( Oxygen::grapeViolet4 ); m_colorList.push_back( Oxygen::skyBlue4 ); m_colorList.push_back( Oxygen::seaBlue4 ); m_colorList.push_back( Oxygen::emeraldGreen4 ); m_colorList.push_back( Oxygen::forestGreen4 ); m_colorList.push_back( Oxygen::sunYellow4 ); m_colorList.push_back( Oxygen::hotOrange4 ); m_colorList.push_back( Oxygen::aluminumGray4 ); m_colorList.push_back( Oxygen::woodBrown4 ); } QColor SatellitesModel::nextColor() { if (m_colorList.isEmpty()) { return Oxygen::brickRed4; } if (m_currentColorIndex < m_colorList.size()) { m_currentColorIndex++; return m_colorList[m_currentColorIndex-1]; } else { m_currentColorIndex = 1; return m_colorList[0]; } return Oxygen::brickRed4; } void SatellitesModel::loadSettings( const QHash &settings ) { QStringList idList = settings[QStringLiteral("idList")].toStringList(); m_enabledIds = idList; updateVisibility(); } void SatellitesModel::setPlanet( const QString &planetId ) { if( m_lcPlanet != planetId ) { mDebug() << "Planet changed from" << m_lcPlanet << "to" << planetId; m_lcPlanet = planetId; updateVisibility(); } } void SatellitesModel::updateVisibility() { beginUpdateItems(); for( TrackerPluginItem *obj: items() ) { SatellitesMSCItem *oItem = dynamic_cast(obj); if( oItem != nullptr ) { bool enabled = ( ( oItem->relatedBody().toLower() == m_lcPlanet ) && ( m_enabledIds.contains( oItem->id() ) ) ); oItem->setEnabled( enabled ); if( enabled ) { oItem->update(); } } SatellitesTLEItem *eItem = dynamic_cast(obj); if( eItem != nullptr ) { // TLE satellites are always earth satellites bool enabled = (m_lcPlanet == QLatin1String("earth")); eItem->setEnabled( enabled ); if( enabled ) { eItem->update(); } } } endUpdateItems(); } void SatellitesModel::parseFile( const QString &id, const QByteArray &data ) { // catalog files are comma separated while tle files // are not allowed to contain commas, so we use this // to distinguish between those two if( data.contains( ',' ) ) { parseCatalog( id, data ); } else { parseTLE( id, data ); } emit fileParsed( id ); } void SatellitesModel::parseCatalog( const QString &id, const QByteArray &data ) { // For details see: // http://techbase.kde.org/Projects/Marble/SatelliteCatalogFormat mDebug() << "Reading satellite catalog from:" << id; QTextStream ts(data); int index = 1; beginUpdateItems(); QString line = ts.readLine(); for( ; !line.isNull(); line = ts.readLine() ) { if (line.trimmed().startsWith(QLatin1Char('#'))) { continue; } QStringList elms = line.split(", "); // check for '<' instead of '==' in order to allow fields to be added // to catalogs later without breaking the code if( elms.size() < 14 ) { mDebug() << "Skipping line:" << elms << "(" << line << ")"; continue; } QString name( elms[0] ); QString category( elms[1] ); QString body( elms[2] ); QByteArray body8Bit = body.toLocal8Bit(); char *cbody = const_cast( body8Bit.constData() ); mDebug() << "Loading" << category << name; PlanetarySats *planSat = new PlanetarySats(); planSat->setPlanet( cbody ); planSat->setStateVector( elms[7].toFloat() - 2400000.5, elms[8].toFloat(), elms[9].toFloat(), elms[10].toFloat(), elms[11].toFloat(), elms[12].toFloat(), elms[13].toFloat() ); planSat->stateToKepler(); QDateTime missionStart, missionEnd; if( elms[3].toUInt() > 0 ) { missionStart = QDateTime::fromTime_t( elms[3].toUInt() ); } if( elms[4].toUInt() > 0 ) { missionEnd = QDateTime::fromTime_t( elms[4].toUInt() ); } SatellitesMSCItem *item = new SatellitesMSCItem( name, category, body, id, missionStart, missionEnd, index++, planSat, m_clock ); GeoDataStyle::Ptr style(new GeoDataStyle( *item->placemark()->style() )); style->lineStyle().setPenStyle( Qt::SolidLine ); style->lineStyle().setColor( nextColor() ); style->labelStyle().setGlow( true ); // use special icon for moons if (category == QLatin1String("Moons")) { style->iconStyle().setIconPath(QStringLiteral(":/icons/moon.png")); } else { style->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/satellite.png"))); } item->placemark()->setStyle( style ); item->placemark()->setVisible( ( body.toLower() == m_lcPlanet ) ); addItem( item ); } endUpdateItems(); } void SatellitesModel::parseTLE( const QString &id, const QByteArray &data ) { mDebug() << "Reading satellite TLE data from:" << id; QList tleLines = data.split( '\n' ); // File format: One line of description, two lines of TLE, last line is empty if ( tleLines.size() % 3 != 1 ) { mDebug() << "Malformated satellite data file"; } beginUpdateItems(); //FIXME: terrible hack because twoline2rv uses sscanf setlocale( LC_NUMERIC, "C" ); double startmfe, stopmfe, deltamin; elsetrec satrec; int i = 0; while ( i < tleLines.size() - 1 ) { QString satelliteName = QString( tleLines.at( i++ ) ).trimmed(); char line1[130]; char line2[130]; if( tleLines.at( i ).size() >= 79 || tleLines.at( i+1 ).size() >= 79 ) { mDebug() << "Invalid TLE data!"; return; } qstrcpy( line1, tleLines.at( i++ ).constData() ); qstrcpy( line2, tleLines.at( i++ ).constData() ); twoline2rv( line1, line2, 'c', 'd', 'i', wgs84, startmfe, stopmfe, deltamin, satrec ); if ( satrec.error != 0 ) { mDebug() << "Error: " << satrec.error; return; } SatellitesTLEItem *item = new SatellitesTLEItem( satelliteName, satrec, m_clock ); GeoDataStyle::Ptr style(new GeoDataStyle( *item->placemark()->style() )); style->lineStyle().setPenStyle( Qt::SolidLine ); style->lineStyle().setColor( nextColor() ); style->labelStyle().setGlow( true ); style->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/satellite.png"))); item->placemark()->setStyle( style ); addItem( item ); } //Reset to environment setlocale( LC_NUMERIC, "" ); endUpdateItems(); } } // namespace Marble #include "moc_SatellitesModel.cpp" diff --git a/src/plugins/render/stars/StarsPlugin.cpp b/src/plugins/render/stars/StarsPlugin.cpp index 61b0f3a66..ba1d2e349 100644 --- a/src/plugins/render/stars/StarsPlugin.cpp +++ b/src/plugins/render/stars/StarsPlugin.cpp @@ -1,1488 +1,1489 @@ // // 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 2011-2013 Bernhard Beschow // #include "StarsPlugin.h" #include "ui_StarsConfigWidget.h" #include #include #include #include #include #include #include #include #include "MarbleClock.h" +#include "MarbleColors.h" #include "MarbleDebug.h" #include "MarbleDirs.h" #include "MarbleModel.h" #include "MarbleWidget.h" #include "AbstractFloatItem.h" #include "GeoPainter.h" #include "Planet.h" #include "PlanetFactory.h" #include "SunLocator.h" #include "ViewportParams.h" #include "src/lib/astro/solarsystem.h" namespace Marble { StarsPlugin::StarsPlugin( const MarbleModel *marbleModel ) : RenderPlugin( marbleModel ), m_nameIndex( 0 ), m_configDialog( nullptr ), ui_configWidget( nullptr ), m_renderStars( true ), m_renderConstellationLines( true ), m_renderConstellationLabels( true ), m_renderDsos( true ), m_renderDsoLabels( true ), m_renderSun( true ), m_renderMoon( true ), m_renderEcliptic( true ), m_renderCelestialEquator( true ), m_renderCelestialPole( true ), m_starsLoaded( false ), m_starPixmapsCreated( false ), m_constellationsLoaded( false ), m_dsosLoaded( false ), m_zoomSunMoon( true ), m_viewSolarSystemLabel( true ), m_magnitudeLimit( 100 ), m_zoomCoefficient( 4 ), m_constellationBrush( Marble::Oxygen::aluminumGray5 ), m_constellationLabelBrush( Marble::Oxygen::aluminumGray5 ), m_dsoLabelBrush( Marble::Oxygen::aluminumGray5 ), m_eclipticBrush( Marble::Oxygen::aluminumGray5 ), m_celestialEquatorBrush( Marble::Oxygen::aluminumGray5 ), m_celestialPoleBrush( Marble::Oxygen::aluminumGray5 ), m_contextMenu(nullptr), m_constellationsAction(nullptr), m_sunMoonAction(nullptr), m_planetsAction(nullptr), m_dsoAction(nullptr), m_doRender( false ) { bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen; if (smallScreen) m_magnitudeLimit = 5; prepareNames(); } StarsPlugin::~StarsPlugin() { delete m_contextMenu; } QStringList StarsPlugin::backendTypes() const { return QStringList(QStringLiteral("stars")); } QString StarsPlugin::renderPolicy() const { return QStringLiteral("SPECIFIED_ALWAYS"); } QStringList StarsPlugin::renderPosition() const { return QStringList(QStringLiteral("STARS")); } RenderPlugin::RenderType StarsPlugin::renderType() const { return RenderPlugin::ThemeRenderType; } QString StarsPlugin::name() const { return tr( "Stars" ); } QString StarsPlugin::guiString() const { return tr( "&Stars" ); } QString StarsPlugin::nameId() const { return QStringLiteral("stars"); } QString StarsPlugin::version() const { return QStringLiteral("1.2"); } QString StarsPlugin::description() const { return tr( "A plugin that shows the Starry Sky and the Sun." ); } QString StarsPlugin::copyrightYears() const { return QStringLiteral("2008-2012"); } QVector StarsPlugin::pluginAuthors() const { return QVector() << PluginAuthor(QStringLiteral("Torsten Rahn"), QStringLiteral("tackat@kde.org")) << PluginAuthor(QStringLiteral("Rene Kuettner"), QStringLiteral("rene@bitkanal.net")) << PluginAuthor(QStringLiteral("Timothy Lanzi"), QStringLiteral("trlanzi@gmail.com")); } QIcon StarsPlugin::icon() const { return QIcon(QStringLiteral(":/icons/stars.png")); } void StarsPlugin::initialize() { } bool StarsPlugin::isInitialized() const { return true; } QDialog *StarsPlugin::configDialog() { if (!m_configDialog) { // Initializing configuration dialog m_configDialog = new QDialog; ui_configWidget = new Ui::StarsConfigWidget; ui_configWidget->setupUi( m_configDialog ); readSettings(); connect( ui_configWidget->m_buttonBox, SIGNAL(accepted()), SLOT(writeSettings()) ); connect( ui_configWidget->m_buttonBox, SIGNAL(rejected()), SLOT(readSettings()) ); connect( ui_configWidget->m_constellationColorButton, SIGNAL(clicked()), this, SLOT(constellationGetColor()) ); connect( ui_configWidget->m_constellationLabelColorButton, SIGNAL(clicked()), this, SLOT(constellationLabelGetColor()) ); connect( ui_configWidget->m_dsoLabelColorButton, SIGNAL(clicked()), this, SLOT(dsoLabelGetColor()) ); connect( ui_configWidget->m_eclipticColorButton, SIGNAL(clicked()), this, SLOT(eclipticGetColor()) ); connect( ui_configWidget->m_celestialEquatorColorButton, SIGNAL(clicked()), this, SLOT(celestialEquatorGetColor()) ); connect( ui_configWidget->m_celestialPoleColorButton, SIGNAL(clicked()), this, SLOT(celestialPoleGetColor()) ); } return m_configDialog; } QHash StarsPlugin::settings() const { QHash settings = RenderPlugin::settings(); settings.insert(QStringLiteral("nameIndex"), m_nameIndex); settings.insert(QStringLiteral("renderStars"), m_renderStars); settings.insert(QStringLiteral("renderConstellationLines"), m_renderConstellationLines); settings.insert(QStringLiteral("renderConstellationLabels"), m_renderConstellationLabels); settings.insert(QStringLiteral("renderDsos"), m_renderDsos); settings.insert(QStringLiteral("renderDsoLabels"), m_renderDsoLabels); settings.insert(QStringLiteral("renderSun"), m_renderSun); settings.insert(QStringLiteral("renderMoon"), m_renderMoon); QStringList planetState; for (const QString &key: m_renderPlanet.keys()) planetState += key + QLatin1Char(':') + QString::number((int)m_renderPlanet[key]); settings.insert(QStringLiteral("renderPlanet"), planetState.join(QLatin1Char('|'))); settings.insert(QStringLiteral("renderEcliptic"), m_renderEcliptic); settings.insert(QStringLiteral("renderCelestialEquator"), m_renderCelestialEquator); settings.insert(QStringLiteral("renderCelestialPole"), m_renderCelestialPole); settings.insert(QStringLiteral("zoomSunMoon"), m_zoomSunMoon); settings.insert(QStringLiteral("viewSolarSystemLabel"), m_viewSolarSystemLabel); settings.insert(QStringLiteral("magnitudeLimit"), m_magnitudeLimit); settings.insert(QStringLiteral("constellationBrush"), m_constellationBrush.color().rgb()); settings.insert(QStringLiteral("constellationLabelBrush"), m_constellationLabelBrush.color().rgb()); settings.insert(QStringLiteral("dsoLabelBrush"), m_dsoLabelBrush.color().rgb()); settings.insert(QStringLiteral("eclipticBrush"), m_eclipticBrush.color().rgb()); settings.insert(QStringLiteral("celestialEaquatorBrush"), m_celestialEquatorBrush.color().rgb()); settings.insert(QStringLiteral("celestialPoleBrush"), m_celestialPoleBrush.color().rgb()); return settings; } void StarsPlugin::setSettings( const QHash &settings ) { RenderPlugin::setSettings( settings ); m_nameIndex = readSetting(settings, QStringLiteral("nameIndex"), 0); m_renderStars = readSetting(settings, QStringLiteral("renderStars"), true); m_renderConstellationLines = readSetting(settings, QStringLiteral("renderConstellationLines"), true); m_renderConstellationLabels = readSetting(settings, QStringLiteral("renderConstellationLabels"), true); m_renderDsos = readSetting(settings, QStringLiteral("renderDsos"), true); m_renderDsoLabels = readSetting(settings, QStringLiteral("renderDsoLabels"), true); m_renderSun = readSetting(settings, QStringLiteral("renderSun"), true); m_renderMoon = readSetting(settings, QStringLiteral("renderMoon"), true); m_renderPlanet.clear(); const QString renderPlanet = readSetting(settings, QStringLiteral("renderPlanet"), QString()); const QStringList renderStates = renderPlanet.split(QLatin1Char('|')); for(const QString &state: renderStates) { const QStringList stateList = state.split(QLatin1Char(':')); if (stateList.size() == 2) m_renderPlanet[stateList[0]] = (bool)stateList[1].toInt(); } m_renderEcliptic = readSetting(settings, QStringLiteral("renderEcliptic"), true); m_renderCelestialEquator = readSetting(settings, QStringLiteral("renderCelestialEquator"), true); m_renderCelestialPole = readSetting(settings, QStringLiteral("renderCelestialPole"), true); m_zoomSunMoon = readSetting(settings, QStringLiteral("zoomSunMoon"), true); m_viewSolarSystemLabel = readSetting(settings, QStringLiteral("viewSolarSystemLabel"), true); m_magnitudeLimit = readSetting(settings, QStringLiteral("magnitudeLimit"), 100); QColor const defaultColor = Marble::Oxygen::aluminumGray5; m_constellationBrush = QColor(readSetting(settings, QStringLiteral("constellationBrush"), defaultColor.rgb())); m_constellationLabelBrush = QColor(readSetting(settings, QStringLiteral("constellationLabelBrush"), defaultColor.rgb())); m_dsoLabelBrush = QColor(readSetting(settings, QStringLiteral("dsoLabelBrush"), defaultColor.rgb())); m_eclipticBrush = QColor(readSetting(settings, QStringLiteral("eclipticBrush"), defaultColor.rgb())); m_celestialEquatorBrush = QColor(readSetting(settings, QStringLiteral("celestialEquatorBrush"), defaultColor.rgb())); m_celestialPoleBrush = QColor(readSetting(settings, QStringLiteral("celestialPoleBrush"), defaultColor.rgb())); } QPixmap StarsPlugin::starPixmap(qreal mag, int colorId) const { if ( mag < -1 ) { return m_pixN1Stars.at(colorId); } else if ( mag < 0 ) { return m_pixP0Stars.at(colorId); } else if ( mag < 1 ) { return m_pixP1Stars.at(colorId); } else if ( mag < 2 ) { return m_pixP2Stars.at(colorId); } else if ( mag < 3 ) { return m_pixP3Stars.at(colorId); } else if ( mag < 4 ) { return m_pixP4Stars.at(colorId); } else if ( mag < 5 ) { return m_pixP5Stars.at(colorId); } else if ( mag < 6 ) { return m_pixP6Stars.at(colorId); } else { return m_pixP7Stars.at(colorId); } return QPixmap(); } void StarsPlugin::prepareNames() { QFile names(MarbleDirs::path(QStringLiteral("stars/names.csv"))); if ( !names.open( QIODevice::ReadOnly ) ) { return; } QTextStream in( &names ); while ( !in.atEnd() ) { QString line = in.readLine(); const QStringList list = line.split(QLatin1Char(';')); if ( list.size() == 3 ) { m_nativeHash[ list.at( 0 ) ] = QCoreApplication::translate( "StarNames", list.at( 1 ).toUtf8().constData() ); m_abbrHash[ list.at( 0 ) ] = list.at( 2 ); } } names.close(); } QString StarsPlugin::assembledConstellation(const QString &name) { switch (m_nameIndex) { case 0: return name; case 1: return m_nativeHash[name]; case 2: return m_abbrHash[name]; default: return name; } } void StarsPlugin::readSettings() { if ( !m_configDialog ) { return; } ui_configWidget->constellationNamesComboBox->setCurrentIndex(m_nameIndex); Qt::CheckState const constellationLineState = m_renderConstellationLines ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewConstellationLinesCheckbox->setCheckState( constellationLineState ); Qt::CheckState const constellationLabelState = m_renderConstellationLabels ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewConstellationLabelsCheckbox->setCheckState( constellationLabelState ); Qt::CheckState const dsoState = m_renderDsos ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewDsosCheckbox->setCheckState( dsoState ); Qt::CheckState const dsoLabelState = m_renderDsoLabels ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewDsoLabelCheckbox->setCheckState( dsoLabelState ); Qt::CheckState const sunState = m_renderSun ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 0 )->setCheckState( sunState ); Qt::CheckState const moonState = m_renderMoon ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 1 )->setCheckState( moonState ); Qt::CheckState const mercuryState = m_renderPlanet["mercury"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 2 )->setCheckState(mercuryState); Qt::CheckState const venusState = m_renderPlanet["venus"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 3 )->setCheckState(venusState); Qt::CheckState const marsState = m_renderPlanet["mars"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 5 )->setCheckState(marsState); Qt::CheckState const jupiterState = m_renderPlanet["jupiter"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 6 )->setCheckState(jupiterState); Qt::CheckState const saturnState = m_renderPlanet["saturn"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 7 )->setCheckState(saturnState); Qt::CheckState const uranusState = m_renderPlanet["uranus"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 8 )->setCheckState(uranusState); Qt::CheckState const neptuneState = m_renderPlanet["neptune"] ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_solarSystemListWidget->item( 9 )->setCheckState(neptuneState); Qt::CheckState const eclipticState = m_renderEcliptic ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewEclipticCheckbox->setCheckState( eclipticState ); Qt::CheckState const celestialEquatorState = m_renderCelestialEquator ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewCelestialEquatorCheckbox->setCheckState( celestialEquatorState ); Qt::CheckState const celestialPoleState = m_renderCelestialPole ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewCelestialPoleCheckbox->setCheckState( celestialPoleState ); Qt::CheckState const zoomSunMoonState = m_zoomSunMoon ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_zoomSunMoonCheckbox->setCheckState( zoomSunMoonState ); Qt::CheckState const viewSolarSystemLabelState = m_viewSolarSystemLabel ? Qt::Checked : Qt::Unchecked; ui_configWidget->m_viewSolarSystemLabelCheckbox->setCheckState( viewSolarSystemLabelState ); int magState = m_magnitudeLimit; if ( magState < ui_configWidget->m_magnitudeSlider->minimum() ) { magState = ui_configWidget->m_magnitudeSlider->minimum(); } else if ( magState > ui_configWidget->m_magnitudeSlider->maximum() ) { magState = ui_configWidget->m_magnitudeSlider->maximum(); } ui_configWidget->m_magnitudeSlider->setValue(magState); QPalette constellationPalette; constellationPalette.setColor( QPalette::Button, m_constellationBrush.color() ); ui_configWidget->m_constellationColorButton->setPalette( constellationPalette ); QPalette constellationLabelPalette; constellationLabelPalette.setColor( QPalette::Button, m_constellationLabelBrush.color() ); ui_configWidget->m_constellationLabelColorButton->setPalette( constellationLabelPalette ); QPalette dsoLabelPalette; dsoLabelPalette.setColor( QPalette::Button, m_dsoLabelBrush.color() ); ui_configWidget->m_dsoLabelColorButton->setPalette( dsoLabelPalette ); QPalette eclipticPalette; eclipticPalette.setColor( QPalette::Button, m_eclipticBrush.color() ); ui_configWidget->m_eclipticColorButton->setPalette( eclipticPalette ); QPalette celestialEquatorPalette; celestialEquatorPalette.setColor( QPalette::Button, m_celestialEquatorBrush.color() ); ui_configWidget->m_celestialEquatorColorButton->setPalette( celestialEquatorPalette ); QPalette celestialPolePalette; celestialPolePalette.setColor( QPalette::Button, m_celestialPoleBrush.color() ); ui_configWidget->m_celestialPoleColorButton->setPalette( celestialPolePalette ); } void StarsPlugin::writeSettings() { m_nameIndex = ui_configWidget->constellationNamesComboBox->currentIndex(); m_renderConstellationLines = ui_configWidget->m_viewConstellationLinesCheckbox->checkState() == Qt::Checked; m_renderConstellationLabels = ui_configWidget->m_viewConstellationLabelsCheckbox->checkState() == Qt::Checked; m_renderDsos = ui_configWidget->m_viewDsosCheckbox->checkState() == Qt::Checked; m_renderDsoLabels = ui_configWidget->m_viewDsoLabelCheckbox->checkState() == Qt::Checked; m_renderSun = ui_configWidget->m_solarSystemListWidget->item( 0 )->checkState() == Qt::Checked; m_renderMoon = ui_configWidget->m_solarSystemListWidget->item( 1 )->checkState() == Qt::Checked; m_renderPlanet["mercury"] = ui_configWidget->m_solarSystemListWidget->item( 2 )->checkState() == Qt::Checked; m_renderPlanet["venus"] = ui_configWidget->m_solarSystemListWidget->item( 3 )->checkState() == Qt::Checked; m_renderPlanet["mars"] = ui_configWidget->m_solarSystemListWidget->item( 5 )->checkState() == Qt::Checked; m_renderPlanet["jupiter"] = ui_configWidget->m_solarSystemListWidget->item( 6 )->checkState() == Qt::Checked; m_renderPlanet["saturn"] = ui_configWidget->m_solarSystemListWidget->item( 7 )->checkState() == Qt::Checked; m_renderPlanet["uranus"] = ui_configWidget->m_solarSystemListWidget->item( 8 )->checkState() == Qt::Checked; m_renderPlanet["neptune"] = ui_configWidget->m_solarSystemListWidget->item( 9 )->checkState() == Qt::Checked; m_renderEcliptic = ui_configWidget->m_viewEclipticCheckbox->checkState() == Qt::Checked; m_renderCelestialEquator = ui_configWidget->m_viewCelestialEquatorCheckbox->checkState() == Qt::Checked; m_renderCelestialPole = ui_configWidget->m_viewCelestialPoleCheckbox->checkState() == Qt::Checked; m_zoomSunMoon = ui_configWidget->m_zoomSunMoonCheckbox->checkState() == Qt::Checked; m_viewSolarSystemLabel = ui_configWidget->m_viewSolarSystemLabelCheckbox->checkState() == Qt::Checked; m_magnitudeLimit = ui_configWidget->m_magnitudeSlider->value(); m_constellationBrush = QBrush( ui_configWidget->m_constellationColorButton->palette().color( QPalette::Button) ); m_constellationLabelBrush = QBrush( ui_configWidget->m_constellationLabelColorButton->palette().color( QPalette::Button) ); m_dsoLabelBrush = QBrush( ui_configWidget->m_dsoLabelColorButton->palette().color( QPalette::Button) ); m_eclipticBrush = QBrush( ui_configWidget->m_eclipticColorButton->palette().color( QPalette::Button) ); m_celestialEquatorBrush = QBrush( ui_configWidget->m_celestialEquatorColorButton->palette().color( QPalette::Button) ); m_celestialPoleBrush = QBrush( ui_configWidget->m_celestialPoleColorButton->palette().color( QPalette::Button) ); emit settingsChanged( nameId() ); } void StarsPlugin::constellationGetColor() { const QColor c = QColorDialog::getColor( m_constellationBrush.color(), nullptr, tr("Please choose the color for the constellation lines.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->m_constellationColorButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->m_constellationColorButton->setPalette( palette ); } } void StarsPlugin::constellationLabelGetColor() { const QColor c = QColorDialog::getColor( m_constellationLabelBrush.color(), nullptr, tr("Please choose the color for the constellation labels.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->m_constellationLabelColorButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->m_constellationLabelColorButton->setPalette( palette ); } } void StarsPlugin::dsoLabelGetColor() { const QColor c = QColorDialog::getColor( m_dsoLabelBrush.color(), nullptr, tr("Please choose the color for the dso labels.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->m_dsoLabelColorButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->m_dsoLabelColorButton->setPalette( palette ); } } void StarsPlugin::eclipticGetColor() { const QColor c = QColorDialog::getColor( m_eclipticBrush.color(), nullptr, tr("Please choose the color for the ecliptic.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->m_eclipticColorButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->m_eclipticColorButton->setPalette( palette ); } } void StarsPlugin::celestialEquatorGetColor() { const QColor c = QColorDialog::getColor( m_celestialEquatorBrush.color(), nullptr, tr("Please choose the color for the celestial equator.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->m_celestialEquatorColorButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->m_celestialEquatorColorButton->setPalette( palette ); } } void StarsPlugin::celestialPoleGetColor() { const QColor c = QColorDialog::getColor( m_celestialPoleBrush.color(), nullptr, tr("Please choose the color for the celestial equator.") ); if ( c.isValid() ) { QPalette palette = ui_configWidget->m_celestialPoleColorButton->palette(); palette.setColor( QPalette::Button, c ); ui_configWidget->m_celestialPoleColorButton->setPalette( palette ); } } void StarsPlugin::loadStars() { //mDebug() << Q_FUNC_INFO; // Load star data m_stars.clear(); QFile starFile(MarbleDirs::path(QStringLiteral("stars/stars.dat"))); starFile.open( QIODevice::ReadOnly ); QDataStream in( &starFile ); // Read and check the header quint32 magic; in >> magic; if ( magic != 0x73746172 ) { return; } // Read the version qint32 version; in >> version; if ( version > 004 ) { mDebug() << "stars.dat: file too new."; return; } if ( version == 003 ) { mDebug() << "stars.dat: file version no longer supported."; return; } int maxid = 0; int id = 0; int starIndex = 0; double ra; double de; double mag; int colorId = 2; mDebug() << "Star Catalog Version " << version; while ( !in.atEnd() ) { if ( version >= 2 ) { in >> id; } if ( id > maxid ) { maxid = id; } in >> ra; in >> de; in >> mag; if ( version >= 4 ) { in >> colorId; } StarPoint star( id, ( qreal )( ra ), ( qreal )( de ), ( qreal )( mag ), colorId ); // Create entry in stars database m_stars << star; // Create key,value pair in idHash table to map from star id to // index in star database vector m_idHash[id] = starIndex; // Increment Index for use in hash ++starIndex; } // load the Sun pixmap // TODO: adjust pixmap size according to distance m_pixmapSun.load(MarbleDirs::path(QStringLiteral("svg/sun.png"))); m_pixmapMoon.load(MarbleDirs::path(QStringLiteral("svg/moon.png"))); m_starsLoaded = true; } void StarsPlugin::createStarPixmaps() { // Load star pixmaps QVector pixBigStars; pixBigStars.clear(); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_blue.png")))); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_bluewhite.png")))); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_white.png")))); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_yellow.png")))); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_orange.png")))); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_red.png")))); pixBigStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_0_garnetred.png")))); QVector pixSmallStars; pixSmallStars.clear(); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_blue.png")))); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_bluewhite.png")))); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_white.png")))); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_yellow.png")))); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_orange.png")))); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_red.png")))); pixSmallStars.append(QPixmap(MarbleDirs::path(QStringLiteral("bitmaps/stars/star_3_garnetred.png")))); // Pre-Scale Star Pixmaps m_pixN1Stars.clear(); for ( int p=0; p < pixBigStars.size(); ++p) { int width = 1.0*pixBigStars.at(p).width(); m_pixN1Stars.append(pixBigStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP0Stars.clear(); for ( int p=0; p < pixBigStars.size(); ++p) { int width = 0.90*pixBigStars.at(p).width(); m_pixP0Stars.append(pixBigStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP1Stars.clear(); for ( int p=0; p < pixBigStars.size(); ++p) { int width = 0.80*pixBigStars.at(p).width(); m_pixP1Stars.append(pixBigStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP2Stars.clear(); for ( int p=0; p < pixBigStars.size(); ++p) { int width = 0.70*pixBigStars.at(p).width(); m_pixP2Stars.append(pixBigStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP3Stars.clear(); for ( int p=0; p < pixSmallStars.size(); ++p) { int width = 14; m_pixP3Stars.append(pixSmallStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP4Stars.clear(); for ( int p=0; p < pixSmallStars.size(); ++p) { int width = 10; m_pixP4Stars.append(pixSmallStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP5Stars.clear(); for ( int p=0; p < pixSmallStars.size(); ++p) { int width = 6; m_pixP5Stars.append(pixSmallStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP6Stars.clear(); for ( int p=0; p < pixSmallStars.size(); ++p) { int width = 4; m_pixP6Stars.append(pixSmallStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_pixP7Stars.clear(); for ( int p=0; p < pixSmallStars.size(); ++p) { int width = 1; m_pixP7Stars.append(pixSmallStars.at(p).scaledToWidth(width,Qt::SmoothTransformation)); } m_starPixmapsCreated = true; } void StarsPlugin::loadConstellations() { // Load star data m_constellations.clear(); QFile constellationFile(MarbleDirs::path(QStringLiteral("stars/constellations.dat"))); constellationFile.open( QIODevice::ReadOnly ); QTextStream in( &constellationFile ); QString line; QString indexList; while ( !in.atEnd() ) { line = in.readLine(); // Check for null line at end of file if ( line.isNull() ) { continue; } // Ignore Comment lines in header and // between constellation entries if (line.startsWith(QLatin1Char('#'))) { continue; } indexList = in.readLine(); // Make sure we have a valid label and indexList if ( indexList.isNull() ) { break; } Constellation constellation( this, line, indexList ); m_constellations << constellation; } m_constellationsLoaded = true; } void StarsPlugin::loadDsos() { // Load star data m_dsos.clear(); QFile dsoFile(MarbleDirs::path(QStringLiteral("stars/dso.dat"))); dsoFile.open( QIODevice::ReadOnly ); QTextStream in( &dsoFile ); QString line; while ( !in.atEnd() ) { line = in.readLine(); // Check for null line at end of file if ( line.isNull() ) { continue; } // Ignore Comment lines in header and // between dso entries if (line.startsWith(QLatin1Char('#'))) { continue; } QStringList entries = line.split( QLatin1Char( ',' ) ); QString id = entries.at( 0 ); double raH = entries.at( 1 ).toDouble(); double raM = entries.at( 2 ).toDouble(); double raS = entries.at( 3 ).toDouble(); double decD = entries.at( 4 ).toDouble(); double decM = entries.at( 5 ).toDouble(); double decS = entries.at( 6 ).toDouble(); double raRad = ( raH+raM/60.0+raS/3600.0 )*15.0*M_PI/180.0; double decRad; if ( decD >= 0.0 ) { decRad = ( decD+decM/60.0+decS/3600.0 )*M_PI/180.0; } else { decRad = ( decD-decM/60.0-decS/3600.0 )*M_PI/180.0; } DsoPoint dso( id, ( qreal )( raRad ), ( qreal )( decRad ) ); // Create entry in stars database m_dsos << dso; } m_dsoImage.load(MarbleDirs::path(QStringLiteral("stars/deepsky.png"))); m_dsosLoaded = true; } bool StarsPlugin::render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer * layer ) { Q_UNUSED( renderPos ) Q_UNUSED( layer ) QString planetId = marbleModel()->planetId(); const bool doRender = !viewport->mapCoversViewport() && ( (viewport->projection() == Spherical || viewport->projection() == VerticalPerspective) && planetId == QLatin1String("earth")); // So far displaying stars is only supported on earth. if ( doRender != m_doRender ) { if ( doRender ) { connect( marbleModel()->clock(), SIGNAL(timeChanged()), this, SLOT(requestRepaint()) ); } else { disconnect( marbleModel()->clock(), SIGNAL(timeChanged()), this, SLOT(requestRepaint()) ); } m_doRender = doRender; } painter->save(); SolarSystem sys; QDateTime dateTime = marbleModel()->clock()->dateTime(); sys.setCurrentMJD( dateTime.date().year(), dateTime.date().month(), dateTime.date().day(), dateTime.time().hour(), dateTime.time().minute(), (double)dateTime.time().second()); QString const pname = planetId.at(0).toUpper() + planetId.right(planetId.size() - 1); QByteArray name = pname.toLatin1(); sys.setCentralBody( name.data() ); Vec3 skyVector = sys.getPlanetocentric (0.0, 0.0); qreal skyRotationAngle = -atan2(skyVector[1], skyVector[0]); const qreal centerLon = viewport->centerLongitude(); const qreal centerLat = viewport->centerLatitude(); const qreal skyRadius = 0.6 * sqrt( ( qreal )viewport->width() * viewport->width() + viewport->height() * viewport->height() ); if ( doRender ) { if (!m_starPixmapsCreated) { createStarPixmaps(); m_starPixmapsCreated = true; } // Delayed initialization: // Load the star database only if the sky is actually being painted... if ( !m_starsLoaded ) { loadStars(); m_starsLoaded = true; } if ( !m_constellationsLoaded ) { loadConstellations(); m_constellationsLoaded = true; } if ( !m_dsosLoaded ) { loadDsos(); m_dsosLoaded = true; } const qreal earthRadius = viewport->radius(); // List of Pens used to draw the sky QPen polesPen( m_celestialPoleBrush, 2, Qt::SolidLine ); QPen constellationPenSolid( m_constellationBrush, 1, Qt::SolidLine ); QPen constellationPenDash( m_constellationBrush, 1, Qt::DashLine ); QPen constellationLabelPen( m_constellationLabelBrush, 1, Qt::SolidLine ); QPen eclipticPen( m_eclipticBrush, 1, Qt::DotLine ); QPen equatorPen( m_celestialEquatorBrush, 1, Qt::DotLine ); QPen dsoLabelPen (m_dsoLabelBrush, 1, Qt::SolidLine); const Quaternion skyAxis = Quaternion::fromEuler( -centerLat , centerLon + skyRotationAngle, 0.0 ); matrix skyAxisMatrix; skyAxis.inverse().toMatrix( skyAxisMatrix ); if ( m_renderCelestialPole ) { polesPen.setWidth( 2 ); painter->setPen( polesPen ); Quaternion qpos1; qpos1 = Quaternion::fromSpherical( 0, 90 * DEG2RAD ); qpos1.rotateAroundAxis( skyAxisMatrix ); if ( qpos1.v[Q_Z] < 0 ) { const int x1 = ( int )( viewport->width() / 2 + skyRadius * qpos1.v[Q_X] ); const int y1 = ( int )( viewport->height() / 2 - skyRadius * qpos1.v[Q_Y] ); painter->drawLine( x1, y1, x1+10, y1 ); painter->drawLine( x1+5, y1-5, x1+5, y1+5 ); painter->drawText( x1+8, y1+12, "NP" ); } Quaternion qpos2; qpos2 = Quaternion::fromSpherical( 0, -90 * DEG2RAD ); qpos2.rotateAroundAxis( skyAxisMatrix ); if ( qpos2.v[Q_Z] < 0 ) { const int x1 = ( int )( viewport->width() / 2 + skyRadius * qpos2.v[Q_X] ); const int y1 = ( int )( viewport->height() / 2 - skyRadius * qpos2.v[Q_Y] ); painter->drawLine( x1, y1, x1+10, y1 ); painter->drawLine( x1+5, y1-5, x1+5, y1+5 ); painter->drawText( x1+8, y1+12, "SP" ); } } if( m_renderEcliptic ) { const Quaternion eclipticAxis = Quaternion::fromEuler( 0.0, 0.0, -marbleModel()->planet()->epsilon() ); matrix eclipticAxisMatrix; (eclipticAxis * skyAxis).inverse().toMatrix( eclipticAxisMatrix ); painter->setPen(eclipticPen); int previousX = -1; int previousY = -1; for ( int i = 0; i <= 36; ++i) { Quaternion qpos; qpos = Quaternion::fromSpherical( i * 10 * DEG2RAD, 0 ); qpos.rotateAroundAxis( eclipticAxisMatrix ); int x = ( int )( viewport->width() / 2 + skyRadius * qpos.v[Q_X] ); int y = ( int )( viewport->height() / 2 - skyRadius * qpos.v[Q_Y] ); if ( qpos.v[Q_Z] < 0 && previousX >= 0 ) painter->drawLine(previousX, previousY, x, y); previousX = x; previousY = y; } } if( m_renderCelestialEquator ) { painter->setPen(equatorPen); int previousX = -1; int previousY = -1; for ( int i = 0; i <= 36; ++i) { Quaternion qpos; qpos = Quaternion::fromSpherical( i * 10 * DEG2RAD, 0 ); qpos.rotateAroundAxis( skyAxisMatrix ); int x = ( int )( viewport->width() / 2 + skyRadius * qpos.v[Q_X] ); int y = ( int )( viewport->height() / 2 - skyRadius * qpos.v[Q_Y] ); if ( qpos.v[Q_Z] < 0 && previousX > 0 ) painter->drawLine(previousX, previousY, x, y); previousX = x; previousY = y; } } if ( m_renderDsos ) { painter->setPen(dsoLabelPen); // Render Deep Space Objects for ( int d = 0; d < m_dsos.size(); ++d ) { Quaternion qpos = m_dsos.at( d ).quaternion(); qpos.rotateAroundAxis( skyAxisMatrix ); if ( qpos.v[Q_Z] > 0 ) { continue; } qreal earthCenteredX = qpos.v[Q_X] * skyRadius; qreal earthCenteredY = qpos.v[Q_Y] * skyRadius; // Don't draw high placemarks (e.g. satellites) that aren't visible. if ( qpos.v[Q_Z] < 0 && ( ( earthCenteredX * earthCenteredX + earthCenteredY * earthCenteredY ) < earthRadius * earthRadius ) ) { continue; } // Let (x, y) be the position on the screen of the placemark.. const int x = ( int )( viewport->width() / 2 + skyRadius * qpos.v[Q_X] ); const int y = ( int )( viewport->height() / 2 - skyRadius * qpos.v[Q_Y] ); // Skip placemarks that are outside the screen area if ( x < 0 || x >= viewport->width() || y < 0 || y >= viewport->height() ) { continue; } // Hard Code DSO Size for now qreal size = 20; // Center Image on x,y location painter->drawImage( QRectF( x-size/2, y-size/2, size, size ),m_dsoImage ); if (m_renderDsoLabels) { painter->drawText( x+8, y+12, m_dsos.at( d ).id() ); } } } if ( m_renderConstellationLines || m_renderConstellationLabels ) { // Render Constellations for ( int c = 0; c < m_constellations.size(); ++c ) { int xMean = 0; int yMean = 0; int endptCount = 0; painter->setPen( constellationPenSolid ); for ( int s = 0; s < ( m_constellations.at( c ).size() - 1 ); ++s ) { int starId1 = m_constellations.at( c ).at( s ); int starId2 = m_constellations.at( c ).at( s + 1 ); if ( starId1 == -1 || starId2 == -1 ) { // starId == -1 means we don't draw this segment continue; } else if ( starId1 == -2 || starId2 == -2 ) { painter->setPen( constellationPenDash ); } else if ( starId1 == -3 || starId2 == -3 ) { painter->setPen( constellationPenSolid ); } int idx1 = m_idHash.value( starId1,-1 ); int idx2 = m_idHash.value( starId2,-1 ); if ( idx1 < 0 ) { mDebug() << "unknown star, " << starId1 << ", in constellation " << m_constellations.at( c ).name(); continue; } if ( idx2 < 0 ) { mDebug() << "unknown star, " << starId1 << ", in constellation " << m_constellations.at( c ).name(); continue; } // Fetch quaternion from star s in constellation c Quaternion q1 = m_stars.at( idx1 ).quaternion(); // Fetch quaternion from star s+1 in constellation c Quaternion q2 = m_stars.at( idx2 ).quaternion(); q1.rotateAroundAxis( skyAxisMatrix ); q2.rotateAroundAxis( skyAxisMatrix ); if ( q1.v[Q_Z] > 0 || q2.v[Q_Z] > 0 ) { continue; } // Let (x, y) be the position on the screen of the placemark.. int x1 = ( int )( viewport->width() / 2 + skyRadius * q1.v[Q_X] ); int y1 = ( int )( viewport->height() / 2 - skyRadius * q1.v[Q_Y] ); int x2 = ( int )( viewport->width() / 2 + skyRadius * q2.v[Q_X] ); int y2 = ( int )( viewport->height() / 2 - skyRadius * q2.v[Q_Y] ); xMean = xMean + x1 + x2; yMean = yMean + y1 + y2; endptCount = endptCount + 2; if ( m_renderConstellationLines ) { painter->drawLine( x1, y1, x2, y2 ); } } // Skip constellation labels that are outside the screen area if ( endptCount > 0 ) { xMean = xMean / endptCount; yMean = yMean / endptCount; } if ( endptCount < 1 || xMean < 0 || xMean >= viewport->width() || yMean < 0 || yMean >= viewport->height() ) continue; painter->setPen( constellationLabelPen ); if ( m_renderConstellationLabels ) { painter->drawText( xMean, yMean, m_constellations.at( c ).name() ); } } } // Render Stars for ( int s = 0; s < m_stars.size(); ++s ) { Quaternion qpos = m_stars.at(s).quaternion(); qpos.rotateAroundAxis( skyAxisMatrix ); if ( qpos.v[Q_Z] > 0 ) { continue; } qreal earthCenteredX = qpos.v[Q_X] * skyRadius; qreal earthCenteredY = qpos.v[Q_Y] * skyRadius; // Don't draw high placemarks (e.g. satellites) that aren't visible. if ( qpos.v[Q_Z] < 0 && ( ( earthCenteredX * earthCenteredX + earthCenteredY * earthCenteredY ) < earthRadius * earthRadius ) ) { continue; } // Let (x, y) be the position on the screen of the placemark.. const int x = ( int )( viewport->width() / 2 + skyRadius * qpos.v[Q_X] ); const int y = ( int )( viewport->height() / 2 - skyRadius * qpos.v[Q_Y] ); // Skip placemarks that are outside the screen area if ( x < 0 || x >= viewport->width() || y < 0 || y >= viewport->height() ) continue; // Show star if it is brighter than magnitude threshold if ( m_stars.at(s).magnitude() < m_magnitudeLimit ) { // colorId is used to select which pixmap in vector to display int colorId = m_stars.at(s).colorId(); QPixmap s_pixmap = starPixmap(m_stars.at(s).magnitude(), colorId); int sizeX = s_pixmap.width(); int sizeY = s_pixmap.height(); painter->drawPixmap( x-sizeX/2, y-sizeY/2 ,s_pixmap ); } } if ( m_renderSun ) { // sun double ra = 0.0; double decl = 0.0; sys.getSun( ra, decl ); ra = 15.0 * sys.DmsDegF( ra ); decl = sys.DmsDegF( decl ); Quaternion qpos = Quaternion::fromSpherical( ra * DEG2RAD, decl * DEG2RAD ); qpos.rotateAroundAxis( skyAxisMatrix ); if ( qpos.v[Q_Z] <= 0 ) { QPixmap glow(MarbleDirs::path(QStringLiteral("svg/glow.png"))); qreal deltaX = glow.width() / 2.; qreal deltaY = glow.height() / 2.; int x = (int)(viewport->width() / 2 + skyRadius * qpos.v[Q_X]); int y = (int)(viewport->height() / 2 - skyRadius * qpos.v[Q_Y]); bool glowDrawn = false; if (!(x < -glow.width() || x >= viewport->width() || y < -glow.height() || y >= viewport->height())) { painter->drawPixmap( x - deltaX, y - deltaY, glow ); glowDrawn = true; } if (glowDrawn) { double diameter = 0.0, mag = 0.0; sys.getPhysSun(diameter, mag); const int coefficient = m_zoomSunMoon ? m_zoomCoefficient : 1; const qreal size = skyRadius * qSin(diameter) * coefficient; const qreal factor = size/m_pixmapSun.width(); QPixmap sun = m_pixmapSun.transformed(QTransform().scale(factor, factor), Qt::SmoothTransformation); deltaX = sun.width() / 2.; deltaY = sun.height() / 2.; x = (int)(viewport->width() / 2 + skyRadius * qpos.v[Q_X]); y = (int)(viewport->height() / 2 - skyRadius * qpos.v[Q_Y]); painter->drawPixmap( x - deltaX, y - deltaY, sun ); } // It's labels' time! if (m_viewSolarSystemLabel) painter->drawText(x+deltaX*1.5, y+deltaY*1.5, tr("Sun")); } } if ( m_renderMoon && marbleModel()->planetId() == QLatin1String("earth")) { // moon double ra=0.0; double decl=0.0; sys.getMoon(ra, decl); ra = 15.0 * sys.DmsDegF(ra); decl = sys.DmsDegF(decl); Quaternion qpos = Quaternion::fromSpherical( ra * DEG2RAD, decl * DEG2RAD ); qpos.rotateAroundAxis( skyAxisMatrix ); if ( qpos.v[Q_Z] <= 0 ) { // If zoom Sun and Moon is enabled size is multiplied by zoomCoefficient. const int coefficient = m_zoomSunMoon ? m_zoomCoefficient : 1; QPixmap moon = m_pixmapMoon.copy(); const qreal size = skyRadius * qSin(sys.getDiamMoon()) * coefficient; qreal deltaX = size / 2.; qreal deltaY = size / 2.; const int x = (int)(viewport->width() / 2 + skyRadius * qpos.v[Q_X]); const int y = (int)(viewport->height() / 2 - skyRadius * qpos.v[Q_Y]); if (!(x < -size || x >= viewport->width() || y < -size || y >= viewport->height())) { // Moon phases double phase = 0.0, ildisk = 0.0, amag = 0.0; sys.getLunarPhase(phase, ildisk, amag); QPainterPath path; QRectF fullMoonRect = moon.rect(); if (ildisk < 0.05) { // small enough, so it's not visible path.addEllipse(fullMoonRect); } else if (ildisk < 0.95) { // makes sense to do smth QRectF halfEllipseRect; qreal ellipseWidth = 2 * qAbs(ildisk-0.5) * moon.width(); halfEllipseRect.setX((fullMoonRect.width() - ellipseWidth) * 0.5); halfEllipseRect.setWidth(ellipseWidth); halfEllipseRect.setHeight(moon.height()); if (phase < 0.5) { if (ildisk < 0.5) { path.moveTo(fullMoonRect.width()/2, moon.height()); path.arcTo(fullMoonRect, -90, -180); path.arcTo(halfEllipseRect, 90, -180); } else { path.moveTo(fullMoonRect.width()/2, 0); path.arcTo(fullMoonRect, 90, 180); path.arcTo(halfEllipseRect, -90, -180); } } else { if (ildisk < 0.5) { path.moveTo(fullMoonRect.width()/2, moon.height()); path.arcTo(fullMoonRect, -90, 180); path.arcTo(halfEllipseRect, 90, 180); } else { path.moveTo(fullMoonRect.width()/2, 0); path.arcTo(fullMoonRect, 90, -180); path.arcTo(halfEllipseRect, -90, 180); } } path.closeSubpath(); } QPainter overlay; overlay.begin(&moon); overlay.setPen(Qt::NoPen); overlay.setBrush(QBrush(QColor(0, 0, 0, 180))); overlay.setRenderHint(QPainter::Antialiasing, true); overlay.drawPath(path); overlay.end(); qreal angle = marbleModel()->planet()->epsilon() * qCos(ra * DEG2RAD) * RAD2DEG; if (viewport->polarity() < 0) angle += 180; QTransform form; const qreal factor = size / moon.size().width(); moon = moon.transformed(form.rotate(angle).scale(factor, factor), Qt::SmoothTransformation); painter->drawPixmap( x - deltaX, y - deltaY, moon ); // It's labels' time! if (m_viewSolarSystemLabel) painter->drawText(x+deltaX, y+deltaY, PlanetFactory::localizedName("moon")); } } } for(const QString &planet: m_renderPlanet.keys()) { if (m_renderPlanet[planet]) renderPlanet(planet, painter, sys, viewport, skyRadius, skyAxisMatrix); } } painter->restore(); return true; } void StarsPlugin::renderPlanet(const QString &planetId, GeoPainter *painter, SolarSystem &sys, ViewportParams *viewport, qreal skyRadius, matrix &skyAxisMatrix) const { double ra(.0), decl(.0), diam(.0), mag(.0), phase(.0); int color=0; // venus, mars, jupiter, uranus, neptune, saturn if (planetId == QLatin1String("venus")) { sys.getVenus(ra, decl); sys.getPhysVenus(diam, mag, phase); color = 2; } else if (planetId == QLatin1String("mars")) { sys.getMars(ra, decl); sys.getPhysMars(diam, mag, phase); color = 5; } else if (planetId == QLatin1String("jupiter")) { sys.getJupiter(ra, decl); sys.getPhysJupiter(diam, mag, phase); color = 2; } else if (planetId == QLatin1String("mercury")) { sys.getMercury(ra, decl); sys.getPhysMercury(diam, mag, phase); color = 3; } else if (planetId == QLatin1String("saturn")) { sys.getSaturn(ra, decl); sys.getPhysSaturn(diam, mag, phase); color = 3; } else if (planetId == QLatin1String("uranus")) { sys.getUranus(ra, decl); sys.getPhysUranus(diam, mag, phase); color = 0; } else if (planetId == QLatin1String("neptune")) { sys.getNeptune(ra, decl); sys.getPhysNeptune(diam, mag, phase); color = 0; } else { return; } ra = 15.0 * sys.DmsDegF(ra); decl = sys.DmsDegF(decl); Quaternion qpos = Quaternion::fromSpherical( ra * DEG2RAD, decl * DEG2RAD ); qpos.rotateAroundAxis( skyAxisMatrix ); if ( qpos.v[Q_Z] <= 0 ) { QPixmap planetPixmap = starPixmap(mag, color); qreal deltaX = planetPixmap.width() / 2.; qreal deltaY = planetPixmap.height() / 2.; const int x = (int)(viewport->width() / 2 + skyRadius * qpos.v[Q_X]); const int y = (int)(viewport->height() / 2 - skyRadius * qpos.v[Q_Y]); if (!(x < 0 || x >= viewport->width() || y < 0 || y >= viewport->height())) { painter->drawPixmap( x - deltaX, y - deltaY, planetPixmap ); } // It's labels' time! if (m_viewSolarSystemLabel) painter->drawText(x+deltaX, y+deltaY, PlanetFactory::localizedName(planetId)); } } void StarsPlugin::requestRepaint() { emit repaintNeeded( QRegion() ); } void StarsPlugin::toggleSunMoon(bool on) { m_renderSun = on; m_renderMoon = on; if (on) { m_viewSolarSystemLabel = true; } const Qt::CheckState state = on ? Qt::Checked : Qt::Unchecked; if ( m_configDialog ) { ui_configWidget->m_solarSystemListWidget->item( 0 )->setCheckState( state ); ui_configWidget->m_solarSystemListWidget->item( 1 )->setCheckState( state ); ui_configWidget->m_viewSolarSystemLabelCheckbox->setChecked(m_viewSolarSystemLabel); } emit settingsChanged( nameId() ); requestRepaint(); } void StarsPlugin::toggleDsos(bool on) { m_renderDsos = on; // only enable labels if set to true if (on) { m_renderDsoLabels = true; } const Qt::CheckState state = on ? Qt::Checked : Qt::Unchecked; if ( m_configDialog ) { ui_configWidget->m_viewDsosCheckbox->setChecked(state); ui_configWidget->m_viewDsoLabelCheckbox->setChecked(state); } emit settingsChanged( nameId() ); requestRepaint(); } void StarsPlugin::toggleConstellations(bool on) { m_renderConstellationLines = on; m_renderConstellationLabels = on; const Qt::CheckState state = on ? Qt::Checked : Qt::Unchecked; if ( m_configDialog ) { ui_configWidget->m_viewConstellationLinesCheckbox->setChecked( state ); ui_configWidget->m_viewConstellationLabelsCheckbox->setChecked( state ); } emit settingsChanged( nameId() ); requestRepaint(); } void StarsPlugin::togglePlanets(bool on) { m_renderPlanet["venus"] = on; m_renderPlanet["mars"] = on; m_renderPlanet["jupiter"] = on; m_renderPlanet["mercury"] = on; m_renderPlanet["saturn"] = on; m_renderPlanet["uranus"] = on; m_renderPlanet["neptune"] = on; const Qt::CheckState state = on ? Qt::Checked : Qt::Unchecked; if ( m_configDialog ) { // Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune ui_configWidget->m_solarSystemListWidget->item(2)->setCheckState(state); ui_configWidget->m_solarSystemListWidget->item(3)->setCheckState(state); ui_configWidget->m_solarSystemListWidget->item(5)->setCheckState(state); ui_configWidget->m_solarSystemListWidget->item(6)->setCheckState(state); ui_configWidget->m_solarSystemListWidget->item(7)->setCheckState(state); ui_configWidget->m_solarSystemListWidget->item(8)->setCheckState(state); ui_configWidget->m_solarSystemListWidget->item(9)->setCheckState(state); } emit settingsChanged( nameId() ); requestRepaint(); } void StarsPlugin::executeConfigDialog() { QDialog *dialog = configDialog(); Q_ASSERT( dialog ); dialog->exec(); } bool StarsPlugin::eventFilter( QObject *object, QEvent *e ) { if ( !enabled() || !visible() ) { return false; } if( e->type() == QEvent::ContextMenu ) { MarbleWidget *widget = dynamic_cast( object ); QContextMenuEvent *menuEvent = dynamic_cast ( e ); if( widget && menuEvent ) { qreal mouseLon, mouseLat; const bool aboveMap = widget->geoCoordinates( menuEvent->x(), menuEvent->y(), mouseLon, mouseLat, GeoDataCoordinates::Radian ); if ( aboveMap ) { return false; } for ( AbstractFloatItem *floatItem: widget->floatItems() ) { if ( floatItem->enabled() && floatItem->visible() && floatItem->contains( menuEvent->pos() ) ) { return false; } } if (!m_contextMenu) { m_contextMenu = new QMenu; m_constellationsAction = m_contextMenu->addAction(tr("Show &Constellations"), this, SLOT(toggleConstellations(bool))); m_constellationsAction->setCheckable(true); m_sunMoonAction = m_contextMenu->addAction(tr("Show &Sun and Moon"), this, SLOT(toggleSunMoon(bool))); m_sunMoonAction->setCheckable(true); m_planetsAction = m_contextMenu->addAction(tr("Show &Planets"), this, SLOT(togglePlanets(bool))); m_planetsAction->setCheckable(true); m_dsoAction = m_contextMenu->addAction(tr("Show &Deep Sky Objects"), this, SLOT(toggleDsos(bool)) ); m_dsoAction->setCheckable(true); m_contextMenu->addSeparator(); m_contextMenu->addAction(tr("&Configure..."), this, SLOT(executeConfigDialog())); } // update action states m_constellationsAction->setChecked(m_renderConstellationLines || m_renderConstellationLabels); m_sunMoonAction->setChecked(m_renderSun || m_renderMoon); m_dsoAction->setChecked(m_renderDsos); const bool isAnyPlanetRendered = m_renderPlanet["venus"] || m_renderPlanet["mars"] || m_renderPlanet["jupiter"] || m_renderPlanet["mercury"] || m_renderPlanet["saturn"] || m_renderPlanet["uranus"] || m_renderPlanet["neptune"]; m_planetsAction->setChecked(isAnyPlanetRendered); m_contextMenu->exec(widget->mapToGlobal(menuEvent->pos())); return true; } return false; } else { return RenderPlugin::eventFilter( object, e ); } } } #include "moc_StarsPlugin.cpp" diff --git a/src/plugins/runner/gpx/handlers/GPXgpxTagHandler.cpp b/src/plugins/runner/gpx/handlers/GPXgpxTagHandler.cpp index 40a09a308..b279ac196 100644 --- a/src/plugins/runner/gpx/handlers/GPXgpxTagHandler.cpp +++ b/src/plugins/runner/gpx/handlers/GPXgpxTagHandler.cpp @@ -1,102 +1,103 @@ /* Copyright (C) 2007 Nikolas Zimmermann This file is part of the KDE project This library is free software you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License aint with this library see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "GPXgpxTagHandler.h" #include "MarbleDebug.h" #include "GPXElementDictionary.h" #include "GeoDataParser.h" #include "GeoDataDocument.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataLabelStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataStyleMap.h" #include "GeoDataHotSpot.h" +#include "MarbleColors.h" #include "MarbleDirs.h" #include "MarbleGlobal.h" namespace Marble { namespace gpx { GPX_DEFINE_TAG_HANDLER(gpx) GeoNode* GPXgpxTagHandler::parse(GeoParser& parser) const { GeoDataDocument* doc = geoDataDoc( parser ); GeoDataStyle::Ptr style(new GeoDataStyle); GeoDataLineStyle lineStyle; QColor transparentRed = Oxygen::brickRed6; 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()); doc->addStyleMap(styleMap); doc->addStyle(style); // create a style for routes GeoDataStyle::Ptr routestyle(new GeoDataStyle); GeoDataLineStyle routeLineStyle; QColor skyBlue = Oxygen::skyBlue6; skyBlue.setAlpha( 200 ); routeLineStyle.setColor( skyBlue ); routeLineStyle.setWidth( 5 ); routestyle->setLineStyle(routeLineStyle); routestyle->setId(QStringLiteral("route")); GeoDataStyleMap routeStyleMap; routeStyleMap.setId(QStringLiteral("map-route")); routeStyleMap.insert(QStringLiteral("normal"), QLatin1Char('#') + routestyle->id()); doc->addStyleMap(routeStyleMap); doc->addStyle(routestyle); // create a default style for waypoint icons GeoDataStyle::Ptr waypointStyle(new GeoDataStyle); waypointStyle->setId(QStringLiteral("waypoint")); GeoDataIconStyle iconStyle; iconStyle.setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/flag.png"))); iconStyle.setHotSpot(QPointF(0.12,0.03), GeoDataHotSpot::Fraction, GeoDataHotSpot::Fraction); waypointStyle->setIconStyle(iconStyle); GeoDataLabelStyle waypointLabelStyle; waypointLabelStyle.setAlignment(GeoDataLabelStyle::Corner); waypointStyle->setLabelStyle(waypointLabelStyle); GeoDataStyleMap waypointStyleMap; waypointStyleMap.setId(QStringLiteral("map-waypoint")); waypointStyleMap.insert(QStringLiteral("normal"), QLatin1Char('#') + waypointStyle->id()); doc->addStyleMap(waypointStyleMap); doc->addStyle(waypointStyle); return doc; } } } diff --git a/src/plugins/runner/openlocation-code-search/OpenLocationCodeSearchRunner.cpp b/src/plugins/runner/openlocation-code-search/OpenLocationCodeSearchRunner.cpp index a29e8c4ad..85d0e2d51 100644 --- a/src/plugins/runner/openlocation-code-search/OpenLocationCodeSearchRunner.cpp +++ b/src/plugins/runner/openlocation-code-search/OpenLocationCodeSearchRunner.cpp @@ -1,200 +1,202 @@ // // 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 2015 Constantin Mihalache // //self #include "OpenLocationCodeSearchRunner.h" + #include "GeoDataPlacemark.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoDataStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataPolyStyle.h" //Qt +#include #include namespace Marble { OpenLocationCodeSearchRunner::OpenLocationCodeSearchRunner( QObject *parent ): SearchRunner( parent ) { // initialize the charIndex map QString const acceptedChars = "23456789CFGHJMPQRVWX"; for( int index = 0; index < acceptedChars.size(); index++ ) { charIndex[ acceptedChars[index] ] = index; } } void OpenLocationCodeSearchRunner::search( const QString &searchTerm, const GeoDataLatLonBox &preferred ) { Q_UNUSED( preferred ); QVector result; if( isValidOLC( searchTerm.toUpper() ) ) { GeoDataLatLonBox boundingBox = decodeOLC( searchTerm.toUpper() ); if( !boundingBox.isEmpty() ) { GeoDataPlacemark *placemark = new GeoDataPlacemark( searchTerm ); GeoDataPolygon *geometry = new GeoDataPolygon( polygonFromLatLonBox( boundingBox ) ); placemark->setGeometry( geometry ); GeoDataStyle::Ptr style = GeoDataStyle::Ptr(new GeoDataStyle()); GeoDataLineStyle lineStyle; GeoDataPolyStyle polyStyle; lineStyle.setColor( QColor( Qt::GlobalColor::red ) ); lineStyle.setWidth( 2 ); polyStyle.setFill( false ); style->setLineStyle( lineStyle ); style->setPolyStyle( polyStyle ); placemark->setStyle( style ); result.append( placemark ); } } emit searchFinished( result ); } GeoDataPolygon OpenLocationCodeSearchRunner::polygonFromLatLonBox( const GeoDataLatLonBox& boundingBox ) const { if( boundingBox.isEmpty() ) { return GeoDataPolygon(); } GeoDataPolygon poly; GeoDataLinearRing outerBoundry; // north-west corner outerBoundry.append( GeoDataCoordinates( boundingBox.west(), boundingBox.north(), GeoDataCoordinates::Unit::Degree ) ); // north-east corner outerBoundry.append( GeoDataCoordinates( boundingBox.east(), boundingBox.north(), GeoDataCoordinates::Unit::Degree ) ); // south-east corner outerBoundry.append( GeoDataCoordinates( boundingBox.east(), boundingBox.south(), GeoDataCoordinates::Unit::Degree ) ); // south-west corner outerBoundry.append( GeoDataCoordinates( boundingBox.west(), boundingBox.south(), GeoDataCoordinates::Unit::Degree ) ); poly.setOuterBoundary( outerBoundry ); return poly; } GeoDataLatLonBox OpenLocationCodeSearchRunner::decodeOLC( const QString &olc ) const { if( !isValidOLC( olc ) ) { return GeoDataLatLonBox(); } // remove padding QString decoded = olc; decoded = decoded.remove( QRegExp("[0+]") ); qreal southLatitude = 0; qreal westLongitude = 0; int digit = 0; qreal latitudeResolution = 400; qreal longitudeResolution = 400; while( digit < decoded.size() ) { if( digit < 10 ) { latitudeResolution /= 20; longitudeResolution /= 20; southLatitude += latitudeResolution * charIndex[ decoded[digit] ]; westLongitude += longitudeResolution * charIndex[ decoded[digit+1] ]; digit += 2; } else { latitudeResolution /= 5; longitudeResolution /= 4; southLatitude += latitudeResolution *( charIndex[ decoded[digit] ] / 4 ); westLongitude += longitudeResolution *( charIndex[ decoded[digit] ] % 4 ); digit += 1; } } return GeoDataLatLonBox( southLatitude - 90 + latitudeResolution, southLatitude - 90, westLongitude - 180 + longitudeResolution, westLongitude - 180, GeoDataCoordinates::Unit::Degree ); } bool OpenLocationCodeSearchRunner::isValidOLC( const QString& olc ) const { // It must have only one SEPARATOR located at an even index in // the string. QChar const separator(QLatin1Char('+')); int separatorPos = olc.indexOf(separator); if( separatorPos == -1 || separatorPos != olc.lastIndexOf(separator) || separatorPos % 2 != 0 ) { return false; } int const separatorPosition = 8; // It must be a full open location code. if( separatorPos != separatorPosition ) { return false; } // Test the first two characters as only some characters of the // ACCEPTED_CHARS are allowed. // // First latitude character can only take one of the first 9 values. int index0 = charIndex.value( olc[0], -1 ); if( index0 == -1 || index0 > 8 ) { return false; } // First longitude character can only take one of the first 18 values. int index1 = charIndex.value( olc[1], -1 ); if( index1 == -1 || index1 > 17) { return false; } // Test the characters before the SEPARATOR. QChar const suffixPadding(QLatin1Char('0')); bool paddingBegun = false; for( int index = 0; index < separatorPos; index++ ) { if( paddingBegun ) { // Once padding has begun, there should be only padding. if( olc[index] != suffixPadding ) { return false; } continue; } if( charIndex.contains( olc[index] ) ) { continue; } if( olc[index] == suffixPadding ) { paddingBegun = true; // Padding can start only at an even index. if( index % 2 != 0 ) { return false; } continue; } return false; } // Test the characters after the SEPARATOR. if( olc.size() > separatorPos +1 ) { if( paddingBegun ) { return false; } // Only one character after the SEPARATOR is not allowed. if( olc.size() == separatorPos + 2 ) { return false; } for( int index = separatorPos + 1; index < olc.size(); index++ ) { if( !charIndex.contains( olc[index] ) ) { return false; } } } return true; } } diff --git a/src/plugins/runner/osm/OsmParser.cpp b/src/plugins/runner/osm/OsmParser.cpp index 3a8f427a1..7bf5f9106 100644 --- a/src/plugins/runner/osm/OsmParser.cpp +++ b/src/plugins/runner/osm/OsmParser.cpp @@ -1,253 +1,254 @@ // // 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 Thibaut Gridel // Copyright 2011 Konstantin Oblaukhov // Copyright 2014 Bernhard Beschow // Copyright 2015 Dennis Nienhüser // #include "OsmParser.h" #include "OsmElementDictionary.h" #include "osm/OsmObjectManager.h" #include "GeoDataDocument.h" #include "GeoDataPoint.h" #include "GeoDataTypes.h" #include "GeoDataStyle.h" #include "GeoDataPolyStyle.h" #include #include "o5mreader.h" +#include #include #include #include #include namespace Marble { GeoDataDocument *OsmParser::parse(const QString &filename, QString &error) { QFileInfo const fileInfo(filename); if (!fileInfo.exists() || !fileInfo.isReadable()) { error = QString("Cannot read file %1").arg(filename); return nullptr; } if (fileInfo.completeSuffix() == QLatin1String("o5m")) { return parseO5m(filename, error); } else { return parseXml(filename, error); } } GeoDataDocument* OsmParser::parseO5m(const QString &filename, QString &error) { O5mreader* reader; O5mreaderDataset data; O5mreaderIterateRet outerState, innerState; char *key, *value; // share string data on the heap at least for this file QSet stringPool; OsmNodes nodes; OsmWays ways; OsmRelations relations; QHash relationTypes; relationTypes[O5MREADER_DS_NODE] = QStringLiteral("node"); relationTypes[O5MREADER_DS_WAY] = QStringLiteral("way"); relationTypes[O5MREADER_DS_REL] = QStringLiteral("relation"); auto file = fopen(filename.toStdString().c_str(), "rb"); o5mreader_open(&reader, file); while( (outerState = o5mreader_iterateDataSet(reader, &data)) == O5MREADER_ITERATE_RET_NEXT) { switch (data.type) { case O5MREADER_DS_NODE: { OsmNode& node = nodes[data.id]; node.osmData().setId(data.id); node.setCoordinates(GeoDataCoordinates(data.lon*1.0e-7, data.lat*1.0e-7, 0.0, GeoDataCoordinates::Degree)); while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) { const QString keyString = *stringPool.insert(QString::fromUtf8(key)); const QString valueString = *stringPool.insert(QString::fromUtf8(value)); node.osmData().addTag(keyString, valueString); } } break; case O5MREADER_DS_WAY: { OsmWay &way = ways[data.id]; way.osmData().setId(data.id); uint64_t nodeId; while ((innerState = o5mreader_iterateNds(reader, &nodeId)) == O5MREADER_ITERATE_RET_NEXT) { way.addReference(nodeId); } while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) { const QString keyString = *stringPool.insert(QString::fromUtf8(key)); const QString valueString = *stringPool.insert(QString::fromUtf8(value)); way.osmData().addTag(keyString, valueString); } } break; case O5MREADER_DS_REL: { OsmRelation &relation = relations[data.id]; relation.osmData().setId(data.id); char *role; uint8_t type; uint64_t refId; while ((innerState = o5mreader_iterateRefs(reader, &refId, &type, &role)) == O5MREADER_ITERATE_RET_NEXT) { const QString roleString = *stringPool.insert(QString::fromUtf8(role)); relation.addMember(refId, roleString, relationTypes[type]); } while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) { const QString keyString = *stringPool.insert(QString::fromUtf8(key)); const QString valueString = *stringPool.insert(QString::fromUtf8(value)); relation.osmData().addTag(keyString, valueString); } } break; } } fclose(file); error = reader->errMsg; o5mreader_close(reader); return createDocument(nodes, ways, relations); } GeoDataDocument* OsmParser::parseXml(const QString &filename, QString &error) { QXmlStreamReader parser; QFile file; QBuffer buffer; QFileInfo fileInfo(filename); if (fileInfo.completeSuffix() == QLatin1String("osm.zip")) { MarbleZipReader zipReader(filename); if (zipReader.fileInfoList().size() != 1) { int const fileNumber = zipReader.fileInfoList().size(); error = QStringLiteral("Unexpected number of files (%1) in %2").arg(fileNumber).arg(filename); return nullptr; } QByteArray const data = zipReader.fileData(zipReader.fileInfoList().first().filePath); buffer.setData(data); buffer.open(QBuffer::ReadOnly); parser.setDevice(&buffer); } else { file.setFileName(filename); if (!file.open(QFile::ReadOnly)) { error = QStringLiteral("Cannot open file %1").arg(filename); return nullptr; } parser.setDevice(&file); } OsmPlacemarkData* osmData(nullptr); QString parentTag; qint64 parentId(0); // share string data on the heap at least for this file QSet stringPool; OsmNodes m_nodes; OsmWays m_ways; OsmRelations m_relations; while (!parser.atEnd()) { parser.readNext(); if (!parser.isStartElement()) { continue; } QStringRef const tagName = parser.name(); if (tagName == osm::osmTag_node || tagName == osm::osmTag_way || tagName == osm::osmTag_relation) { parentTag = parser.name().toString(); parentId = parser.attributes().value(QLatin1String("id")).toLongLong(); if (tagName == osm::osmTag_node) { m_nodes[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes()); m_nodes[parentId].parseCoordinates(parser.attributes()); osmData = &m_nodes[parentId].osmData(); } else if (tagName == osm::osmTag_way) { m_ways[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes()); osmData = &m_ways[parentId].osmData(); } else { Q_ASSERT(tagName == osm::osmTag_relation); m_relations[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes()); osmData = &m_relations[parentId].osmData(); } } else if (osmData && tagName == osm::osmTag_tag) { const QXmlStreamAttributes &attributes = parser.attributes(); const QString keyString = *stringPool.insert(attributes.value(QLatin1String("k")).toString()); const QString valueString = *stringPool.insert(attributes.value(QLatin1String("v")).toString()); osmData->addTag(keyString, valueString); } else if (tagName == osm::osmTag_nd && parentTag == osm::osmTag_way) { m_ways[parentId].addReference(parser.attributes().value(QLatin1String("ref")).toLongLong()); } else if (tagName == osm::osmTag_member && parentTag == osm::osmTag_relation) { m_relations[parentId].parseMember(parser.attributes()); } // other tags like osm, bounds ignored } if (parser.hasError()) { error = parser.errorString(); return nullptr; } return createDocument(m_nodes, m_ways, m_relations); } GeoDataDocument *OsmParser::createDocument(OsmNodes &nodes, OsmWays &ways, OsmRelations &relations) { GeoDataDocument* document = new GeoDataDocument; GeoDataPolyStyle backgroundPolyStyle; backgroundPolyStyle.setFill( true ); backgroundPolyStyle.setOutline( false ); backgroundPolyStyle.setColor(QStringLiteral("#f1eee8")); GeoDataStyle::Ptr backgroundStyle(new GeoDataStyle); backgroundStyle->setPolyStyle( backgroundPolyStyle ); backgroundStyle->setId(QStringLiteral("background")); document->addStyle( backgroundStyle ); QSet usedNodes, usedWays; for(auto const &relation: relations) { relation.createMultipolygon(document, ways, nodes, usedNodes, usedWays); } for(auto id: usedWays) { ways.remove(id); } QHash placemarks; for (auto iter=ways.constBegin(), end=ways.constEnd(); iter != end; ++iter) { auto placemark = iter.value().create(nodes, usedNodes); if (placemark) { document->append(placemark); placemarks[placemark->osmData().oid()] = placemark; } } for(auto id: usedNodes) { if (nodes[id].osmData().isEmpty()) { nodes.remove(id); } } for(auto const &node: nodes) { auto placemark = node.create(); if (placemark) { document->append(placemark); placemarks[placemark->osmData().oid()] = placemark; } } for(auto const &relation: relations) { relation.createRelation(document, placemarks); } return document; } } diff --git a/tools/osm-addresses/OsmParser.cpp b/tools/osm-addresses/OsmParser.cpp index 05a7cb4a2..986b1f989 100644 --- a/tools/osm-addresses/OsmParser.cpp +++ b/tools/osm-addresses/OsmParser.cpp @@ -1,760 +1,761 @@ // // 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 "OsmParser.h" #include "OsmRegionTree.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataLinearRing.h" #include "GeoDataLineString.h" #include "GeoDataPolygon.h" #include "GeoDataDocument.h" #include "GeoDataPlacemark.h" #include "GeoDataMultiGeometry.h" #include "GeoDataStyle.h" #include "GeoDataStyleMap.h" #include "GeoDataLineStyle.h" #include "geodata/writer/GeoDataDocumentWriter.h" #include #include #include +#include #include #include namespace Marble { using namespace Oxygen; namespace { struct GrahamScanHelper { Coordinate coordinate; qreal direction; GrahamScanHelper( const Coordinate &coordinate_=Coordinate(), qreal direction_=0.0 ) : coordinate( coordinate_ ), direction( direction_ ) { // nothing to do } double turnDirection( const GrahamScanHelper &two, const GrahamScanHelper &three ) { return ( two.coordinate.lat - coordinate.lat ) * ( three.coordinate.lon - coordinate.lon ) - ( two.coordinate.lon - coordinate.lon ) * ( three.coordinate.lat - coordinate.lat ); } static bool directionLessThan( const GrahamScanHelper &one, const GrahamScanHelper &two ) { return one.direction < two.direction; } }; } bool moreImportantAdminArea( const OsmRegion &a, const OsmRegion& b ) { return a.adminLevel() < b.adminLevel(); } OsmParser::OsmParser( QObject *parent ) : QObject( parent ), m_convexHull( nullptr ) { m_categoryMap["tourism/camp_site"] = OsmPlacemark::AccomodationCamping; m_categoryMap["tourism/hostel"] = OsmPlacemark::AccomodationHostel; m_categoryMap["tourism/hotel"] = OsmPlacemark::AccomodationHotel; m_categoryMap["tourism/motel"] = OsmPlacemark::AccomodationMotel; //m_categoryMap["/"] = OsmPlacemark::AccomodationYouthHostel; m_categoryMap["amenity/library"] = OsmPlacemark::AmenityLibrary; m_categoryMap["amenity/college"] = OsmPlacemark::EducationCollege; m_categoryMap["amenity/school"] = OsmPlacemark::EducationSchool; m_categoryMap["amenity/university"] = OsmPlacemark::EducationUniversity; m_categoryMap["amenity/bar"] = OsmPlacemark::FoodBar; m_categoryMap["amenity/biergarten"] = OsmPlacemark::FoodBiergarten; m_categoryMap["amenity/cafe"] = OsmPlacemark::FoodCafe; m_categoryMap["amenity/fast_food"] = OsmPlacemark::FoodFastFood; m_categoryMap["amenity/pub"] = OsmPlacemark::FoodPub; m_categoryMap["amenity/restaurant"] = OsmPlacemark::FoodRestaurant; m_categoryMap["amenity/doctor"] = OsmPlacemark::HealthDoctors; m_categoryMap["amenity/doctors"] = OsmPlacemark::HealthDoctors; m_categoryMap["amenity/hospital"] = OsmPlacemark::HealthHospital; m_categoryMap["amenity/pharmacy"] = OsmPlacemark::HealthPharmacy; m_categoryMap["amenity/atm"] = OsmPlacemark::MoneyAtm; m_categoryMap["amenity/bank"] = OsmPlacemark::MoneyBank; m_categoryMap["shop/beverages"] = OsmPlacemark::ShoppingBeverages; m_categoryMap["shop/hifi"] = OsmPlacemark::ShoppingHifi; m_categoryMap["shop/supermarket"] = OsmPlacemark::ShoppingSupermarket; m_categoryMap["tourism/attraction"] = OsmPlacemark::TouristAttraction; m_categoryMap["tourism/castle"] = OsmPlacemark::TouristCastle; m_categoryMap["amenity/cinema"] = OsmPlacemark::TouristCinema; m_categoryMap["tourism/monument"] = OsmPlacemark::TouristMonument; m_categoryMap["tourism/museum"] = OsmPlacemark::TouristMuseum; m_categoryMap["historic/ruins"] = OsmPlacemark::TouristRuin; m_categoryMap["amenity/theatre"] = OsmPlacemark::TouristTheatre; m_categoryMap["tourism/theme_park"] = OsmPlacemark::TouristThemePark; m_categoryMap["tourism/viewpoint"] = OsmPlacemark::TouristViewPoint; m_categoryMap["tourism/zoo"] = OsmPlacemark::TouristZoo; m_categoryMap["aeroway/aerodrome"] = OsmPlacemark::TransportAirport; m_categoryMap["aeroway/terminal"] = OsmPlacemark::TransportAirportTerminal; m_categoryMap["amenity/bus_station"] = OsmPlacemark::TransportBusStation; m_categoryMap["highway/bus_stop"] = OsmPlacemark::TransportBusStop; m_categoryMap["highway/speed_camera"] = OsmPlacemark::TransportSpeedCamera; m_categoryMap["amenity/car_sharing"] = OsmPlacemark::TransportCarShare; m_categoryMap["amenity/car_rental"] = OsmPlacemark::TransportRentalCar; m_categoryMap["amenity/bicycle_rental"] = OsmPlacemark::TransportRentalBicycle; m_categoryMap["amenity/fuel"] = OsmPlacemark::TransportFuel; m_categoryMap["amenity/parking"] = OsmPlacemark::TransportParking; m_categoryMap["amenity/taxi"] = OsmPlacemark::TransportTaxiRank; m_categoryMap["railway/station"] = OsmPlacemark::TransportTrainStation; m_categoryMap["railway/tram_stop"] = OsmPlacemark::TransportTramStop; } void OsmParser::addWriter( Writer* writer ) { m_writers.push_back( writer ); } Node::operator OsmPlacemark() const { OsmPlacemark placemark; placemark.setCategory( category ); placemark.setName( name.trimmed() ); placemark.setHouseNumber( houseNumber.trimmed() ); placemark.setLongitude( lon ); placemark.setLatitude( lat ); return placemark; } Node::operator Coordinate() const { Coordinate coordinate; coordinate.lon = lon; coordinate.lat = lat; return coordinate; } Way::operator OsmPlacemark() const { OsmPlacemark placemark; placemark.setCategory( category ); placemark.setName( name.trimmed() ); placemark.setHouseNumber( houseNumber.trimmed() ); return placemark; } void Way::setPosition( const QHash &database, OsmPlacemark &placemark ) const { if ( !nodes.isEmpty() ) { if ( nodes.first() == nodes.last() && database.contains( nodes.first() ) ) { GeoDataLinearRing ring; for( int id: nodes ) { if ( database.contains( id ) ) { const Coordinate &node = database[id]; GeoDataCoordinates coordinates( node.lon, node.lat, 0.0, GeoDataCoordinates::Degree ); ring << coordinates; } else { qDebug() << "Missing node " << id << " in database"; } } if ( !ring.isEmpty() ) { GeoDataCoordinates center = ring.latLonAltBox().center(); placemark.setLongitude( center.longitude( GeoDataCoordinates::Degree ) ); placemark.setLatitude( center.latitude( GeoDataCoordinates::Degree ) ); } } else { int id = nodes.at( nodes.size() / 2 ); if ( database.contains( id ) ) { const Coordinate &node = database[id]; placemark.setLongitude( node.lon ); placemark.setLatitude( node.lat ); } } } } void Way::setRegion( const QHash &database, const OsmRegionTree & tree, QList & osmOsmRegions, OsmPlacemark &placemark ) const { if ( !city.isEmpty() ) { for( const OsmOsmRegion & region: osmOsmRegions ) { if ( region.region.name() == city ) { placemark.setRegionId( region.region.identifier() ); return; } } for( const Node & node: database ) { if ( node.category >= OsmPlacemark::PlacesRegion && node.category <= OsmPlacemark::PlacesIsland && node.name == city ) { qDebug() << "Creating a new implicit region from " << node.name << " at " << node.lon << "," << node.lat; OsmOsmRegion region; region.region.setName( city ); region.region.setLongitude( node.lon ); region.region.setLatitude( node.lat ); placemark.setRegionId( region.region.identifier() ); osmOsmRegions.push_back( region ); return; } } qDebug() << "Unable to locate city " << city << ", setting it up without coordinates"; OsmOsmRegion region; region.region.setName( city ); placemark.setRegionId( region.region.identifier() ); osmOsmRegions.push_back( region ); return; } GeoDataCoordinates position( placemark.longitude(), placemark.latitude(), 0.0, GeoDataCoordinates::Degree ); placemark.setRegionId( tree.smallestRegionId( position ) ); } void OsmParser::read( const QFileInfo &content, const QString &areaName ) { QTime timer; timer.start(); m_nodes.clear(); m_ways.clear(); m_relations.clear(); m_placemarks.clear(); m_osmOsmRegions.clear(); int pass = 0; bool needAnotherPass = false; do { qWarning() << "Step 1." << pass << ": Parsing input file " << content.fileName(); parse( content, pass++, needAnotherPass ); } while ( needAnotherPass ); qWarning() << "Step 2: " << m_coordinates.size() << "coordinates." << "Now extracting regions from" << m_relations.size() << "relations"; QHash::iterator itpoint = m_relations.begin(); QHash::iterator const endpoint = m_relations.end(); for(; itpoint != endpoint; ++itpoint ) { if ( itpoint.value().isAdministrativeBoundary /*&& relation.isMultipolygon*/ ) { importMultipolygon( itpoint.value() ); if ( !itpoint.value().relations.isEmpty() ) { qDebug() << "Ignoring relations inside the relation " << itpoint.value().name; } } } m_relations.clear(); for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) { OsmOsmRegion &osmOsmRegion = m_osmOsmRegions[i]; GeoDataCoordinates center = osmOsmRegion.region.geometry().latLonAltBox().center(); osmOsmRegion.region.setLongitude( center.longitude( GeoDataCoordinates::Degree ) ); osmOsmRegion.region.setLatitude( center.latitude( GeoDataCoordinates::Degree ) ); } qWarning() << "Step 3: Creating region hierarchies from" << m_osmOsmRegions.size() << "administrative boundaries"; QMultiMap sortedRegions; for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) { sortedRegions.insert( m_osmOsmRegions[i].region.adminLevel(), i ); } for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) { GeoDataLinearRing const & ring = m_osmOsmRegions[i].region.geometry().outerBoundary(); OsmOsmRegion* parent = nullptr; qDebug() << "Examining admin region " << i << " of " << m_osmOsmRegions.count(); for ( int level=m_osmOsmRegions[i].region.adminLevel()-1; level >= 0 && parent == nullptr; --level ) { QList candidates = sortedRegions.values( level ); qDebug() << "Examining " << candidates.count() << "admin regions on level" << level; for( int j: candidates ) { GeoDataLinearRing const & outer = m_osmOsmRegions[j].region.geometry().outerBoundary(); if ( contains( outer, ring ) ) { if ( parent == nullptr || contains( parent->region.geometry().outerBoundary(), outer ) ) { qDebug() << "Parent found: " << m_osmOsmRegions[i].region.name() << ", level " << m_osmOsmRegions[i].region.adminLevel() << "is a child of " << m_osmOsmRegions[j].region.name() << ", level " << m_osmOsmRegions[j].region.adminLevel(); parent = &m_osmOsmRegions[j]; break; } } } } m_osmOsmRegions[i].parent = parent; } for ( int i = 0; i < m_osmOsmRegions.size(); ++i ) { int const parent = m_osmOsmRegions[i].parent ? m_osmOsmRegions[i].parent->region.identifier() : 0; m_osmOsmRegions[i].region.setParentIdentifier( parent ); } OsmRegion mainArea; mainArea.setIdentifier( 0 ); mainArea.setName( areaName ); mainArea.setAdminLevel( 1 ); QPair minLon( -180.0, 180.0 ), minLat( -90.0, 90.0 ); for( const Coordinate & node: m_coordinates ) { minLon.first = qMin( node.lon, minLon.first ); minLon.second = qMax( node.lon, minLon.second ); minLat.first = qMin( node.lat, minLat.first ); minLat.second = qMax( node.lat, minLat.second ); } GeoDataLatLonBox center( minLat.second, minLat.first, minLon.second, minLon.first ); mainArea.setLongitude( center.center().longitude( GeoDataCoordinates::Degree ) ); mainArea.setLatitude( center.center().latitude( GeoDataCoordinates::Degree ) ); QList regions; for( const OsmOsmRegion & region: m_osmOsmRegions ) { regions << region.region; } std::sort( regions.begin(), regions.end(), moreImportantAdminArea ); OsmRegionTree regionTree( mainArea ); regionTree.append( regions ); Q_ASSERT( regions.isEmpty() ); int left = 0; regionTree.traverse( left ); qWarning() << "Step 4: Creating placemarks from" << m_nodes.size() << "nodes"; for( const Node & node: m_nodes ) { if ( node.save ) { OsmPlacemark placemark = node; GeoDataCoordinates position( node.lon, node.lat, 0.0, GeoDataCoordinates::Degree ); placemark.setRegionId( regionTree.smallestRegionId( position ) ); if ( !node.name.isEmpty() ) { placemark.setHouseNumber( QString() ); m_placemarks.push_back( placemark ); } if ( !node.street.isEmpty() && node.name != node.street ) { placemark.setCategory( OsmPlacemark::Address ); placemark.setName( node.street.trimmed() ); placemark.setHouseNumber( node.houseNumber.trimmed() ); m_placemarks.push_back( placemark ); } } } qWarning() << "Step 5: Creating placemarks from" << m_ways.size() << "ways"; QMultiMap waysByName; for ( const Way & way: m_ways ) { if ( way.save ) { if ( !way.name.isEmpty() && !way.nodes.isEmpty() ) { waysByName.insert( way.name, way ); } if ( !way.street.isEmpty() && way.name != way.street && !way.nodes.isEmpty() ) { waysByName.insert( way.street, way ); } } else { ++m_statistic.uselessWays; } } QSet keys = QSet::fromList( waysByName.keys() ); for( const QString & key: keys ) { QList > merged = merge( waysByName.values( key ) ); for( const QList & ways: merged ) { Q_ASSERT( !ways.isEmpty() ); OsmPlacemark placemark = ways.first(); ways.first().setPosition( m_coordinates, placemark ); ways.first().setRegion( m_nodes, regionTree, m_osmOsmRegions, placemark ); if ( placemark.category() != OsmPlacemark::Address && !ways.first().name.isEmpty() ) { placemark.setHouseNumber( QString() ); m_placemarks.push_back( placemark ); } if ( !ways.first().isBuilding || !ways.first().houseNumber.isEmpty() ) { placemark.setCategory( OsmPlacemark::Address ); QString name = ways.first().street.isEmpty() ? ways.first().name : ways.first().street; if ( !name.isEmpty() ) { placemark.setName( name.trimmed() ); placemark.setHouseNumber( ways.first().houseNumber.trimmed() ); m_placemarks.push_back( placemark ); } } } } m_convexHull = convexHull(); m_coordinates.clear(); m_nodes.clear(); m_ways.clear(); Q_ASSERT( regions.isEmpty() ); for( const OsmOsmRegion & region: m_osmOsmRegions ) { regions << region.region; } std::sort( regions.begin(), regions.end(), moreImportantAdminArea ); regionTree = OsmRegionTree( mainArea ); regionTree.append( regions ); Q_ASSERT( regions.isEmpty() ); left = 0; regionTree.traverse( left ); regions = regionTree; qWarning() << "Step 6: " << m_statistic.mergedWays << " ways merged," << m_statistic.uselessWays << "useless ways." << "Now serializing" << regions.size() << "regions"; for( const OsmRegion & region: regions ) { for( Writer * writer: m_writers ) { writer->addOsmRegion( region ); } } qWarning() << "Step 7: Serializing" << m_placemarks.size() << "placemarks"; for( const OsmPlacemark & placemark: m_placemarks ) { for( Writer * writer: m_writers ) { Q_ASSERT( !placemark.name().isEmpty() ); writer->addOsmPlacemark( placemark ); } } qWarning() << "Step 8: There is no step 8. Done after " << timer.elapsed() / 1000 << "s."; //writeOutlineKml( areaName ); } QList< QList > OsmParser::merge( const QList &ways ) const { QList mergers; for( const Way & way: ways ) { mergers << WayMerger( way ); } bool moved = false; do { moved = false; for( int i = 0; i < mergers.size(); ++i ) { for ( int j = i + 1; j < mergers.size(); ++j ) { if ( mergers[i].compatible( mergers[j] ) ) { mergers[i].merge( mergers[j] ); moved = true; mergers.removeAt( j ); } } } } while ( moved ); QList< QList > result; for( const WayMerger & merger: mergers ) { result << merger.ways; } m_statistic.mergedWays += ( ways.size() - result.size() ); return result; } void OsmParser::importMultipolygon( const Relation &relation ) { /** @todo: import nodes? What are they used for? */ typedef QPair RelationPair; QVector outer; QVector inner; for( const RelationPair & pair: relation.ways ) { if ( pair.second == Outer ) { importWay( outer, pair.first ); } else if ( pair.second == Inner ) { importWay( inner, pair.first ); } else { qDebug() << "Ignoring way " << pair.first << " with unknown relation role."; } } for( const GeoDataLineString & string: outer ) { if ( string.isEmpty() || !( string.first() == string.last() ) ) { qDebug() << "Ignoring open polygon in relation " << relation.name << ". Check data."; continue; } GeoDataPolygon polygon; polygon.setOuterBoundary(GeoDataLinearRing(string)); Q_ASSERT( polygon.outerBoundary().size() > 0 ); for( const GeoDataLineString & hole: inner ) { if ( contains( polygon.outerBoundary(), hole ) ) { polygon.appendInnerBoundary(GeoDataLinearRing(hole)); } } OsmOsmRegion region; region.region.setName( relation.name ); region.region.setGeometry( polygon ); region.region.setAdminLevel( relation.adminLevel ); qDebug() << "Adding administrative region " << relation.name; m_osmOsmRegions.push_back( region ); } } void OsmParser::importWay( QVector &ways, int id ) { if ( !m_ways.contains( id ) ) { qDebug() << "Skipping unknown way " << id << ". Check data."; return; } GeoDataLineString way; for( int node: m_ways[id].nodes ) { if ( !m_coordinates.contains( node ) ) { qDebug() << "Skipping unknown node " << node << ". Check data."; } else { const Coordinate &nd = m_coordinates[node]; GeoDataCoordinates coordinates( nd.lon, nd.lat, 0.0, GeoDataCoordinates::Degree ); way << coordinates; } } QList remove; do { remove.clear(); for ( int i = 0; i < ways.size(); ++i ) { const GeoDataLineString &existing = ways[i]; if ( existing.first() == way.first() ) { way = reverse( way ) << existing; remove.push_front( i ); } else if ( existing.last() == way.first() ) { GeoDataLineString copy = existing; way = copy << way; remove.push_front( i ); } else if ( existing.first() == way.last() ) { way << existing; remove.push_front( i ); } else if ( existing.last() == way.last() ) { way << reverse( existing ); remove.push_front( i ); } } for( int key: remove ) { ways.remove( key ); } } while ( !remove.isEmpty() ); ways.push_back( way ); } GeoDataLineString OsmParser::reverse( const GeoDataLineString & string ) { GeoDataLineString result; for ( int i = string.size() - 1; i >= 0; --i ) { result << string[i]; } return result; } bool OsmParser::shouldSave( ElementType /*type*/, const QString &key, const QString &value ) { using Dictionary = QList; static QHash interestingTags; if ( interestingTags.isEmpty() ) { Dictionary highways; highways << "primary" << "secondary" << "tertiary"; highways << "residential" << "unclassified" << "road"; highways << "living_street" << "service" << "track"; highways << "bus_stop" << "platform" << "speed_camera"; interestingTags["highway"] = highways; Dictionary aeroways; aeroways << "aerodrome" << "terminal"; interestingTags["aeroway"] = aeroways; interestingTags["aerialway"] = Dictionary() << "station"; Dictionary leisures; leisures << "sports_centre" << "stadium" << "pitch"; leisures << "park" << "dance"; interestingTags["leisure"] = leisures; Dictionary amenities; amenities << "restaurant" << "food_court" << "fast_food"; amenities << "pub" << "bar" << "cafe"; amenities << "biergarten" << "kindergarten" << "school"; amenities << "college" << "university" << "library"; amenities << "ferry_terminal" << "bus_station" << "car_rental"; amenities << "car_sharing" << "fuel" << "parking"; amenities << "bank" << "pharmacy" << "hospital"; amenities << "cinema" << "nightclub" << "theatre"; amenities << "taxi" << "bicycle_rental" << "atm"; interestingTags["amenity"] = amenities; Dictionary shops; shops << "beverages" << "supermarket" << "hifi"; interestingTags["shop"] = shops; Dictionary tourism; tourism << "attraction" << "camp_site" << "caravan_site"; tourism << "chalet" << "chalet" << "hostel"; tourism << "hotel" << "motel" << "museum"; tourism << "theme_park" << "viewpoint" << "zoo"; interestingTags["tourism"] = tourism; Dictionary historic; historic << "castle" << "fort" << "monument"; historic << "ruins"; interestingTags["historic"] = historic; Dictionary railway; railway << "station" << "tram_stop"; interestingTags["railway"] = railway; Dictionary places; places << "region" << "county" << "city"; places << "town" << "village" << "hamlet"; places << "isolated_dwelling" << "suburb" << "locality"; places << "island"; interestingTags["place"] = places; } return interestingTags.contains( key ) && interestingTags[key].contains( value ); } void OsmParser::setCategory( Element &element, const QString &key, const QString &value ) { QString const term = key + QLatin1Char('/') + value; if ( m_categoryMap.contains( term ) ) { if ( element.category != OsmPlacemark::UnknownCategory ) { qDebug() << "Overwriting category " << element.category << " with " << m_categoryMap[term] << " for " << element.name; } element.category = m_categoryMap[term]; } } // From http://en.wikipedia.org/wiki/Graham_scan#Pseudocode GeoDataLinearRing* OsmParser::convexHull() const { Q_ASSERT(m_coordinates.size()>2); QList coordinates = m_coordinates.values(); QVector points; points.reserve( coordinates.size()+1 ); Coordinate start = coordinates.first(); int startPos = 0; for ( int i=0; i2 ); qSwap( points[1], points[startPos] ); Q_ASSERT( start.lat != 360.0 ); for ( int i=0; i 0 ); Q_ASSERT( m <= n ); Q_ASSERT( i >= 0 ); Q_ASSERT( i <= n ); } ++m; qSwap( points[m], points[i] ); } GeoDataLinearRing* ring = new GeoDataLinearRing; for ( int i=1; i<=m; ++i ) { ring->append(GeoDataCoordinates(points[i].coordinate.lon, points[i].coordinate.lat, 0.0, GeoDataCoordinates::Degree)); } return ring; } QColor OsmParser::randomColor() const { QVector colors = QVector() << aluminumGray4 << brickRed4; colors << woodBrown4 << forestGreen4 << hotOrange4; colors << seaBlue2 << skyBlue4 << sunYellow6; return colors.at( qrand() % colors.size() ); } void OsmParser::writeKml( const QString &area, const QString &version, const QString &date, const QString &transport, const QString &payload, const QString &filename ) const { GeoDataDocument* document = new GeoDataDocument; //for( const OsmOsmRegion & region: m_osmOsmRegions ) { GeoDataPlacemark* placemark = new GeoDataPlacemark; placemark->setName( area ); if ( !version.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "version", version ) ); } if ( !date.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "date", date ) ); } if ( !transport.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "transport", transport ) ); } if ( !payload.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "payload", payload ) ); } GeoDataStyle::Ptr style(new GeoDataStyle); GeoDataLineStyle lineStyle; QColor color = randomColor(); color.setAlpha( 200 ); lineStyle.setColor( color ); lineStyle.setWidth( 4 ); style->setLineStyle( lineStyle ); style->setId(color.name().replace(QLatin1Char('#'), QLatin1Char('f'))); GeoDataStyleMap styleMap; styleMap.setId(color.name().replace(QLatin1Char('#'), QLatin1Char('f'))); styleMap.insert("normal", QLatin1Char('#') + style->id()); document->addStyle( style ); placemark->setStyleUrl(QLatin1Char('#') + styleMap.id()); //placemark->setGeometry( new GeoDataLinearRing( region.region.geometry().outerBoundary() ) ); GeoDataMultiGeometry *geometry = new GeoDataMultiGeometry; geometry->append( m_convexHull ); placemark->setGeometry( geometry ); document->append( placemark ); document->addStyleMap( styleMap ); // } if (!GeoDataDocumentWriter::write(filename, *document)) { qCritical() << "Can not write to " << filename; } } Coordinate::Coordinate(float lon_, float lat_) : lon(lon_), lat(lat_) { // nothing to do } } #include "moc_OsmParser.cpp" diff --git a/tools/poly2kml/main.cpp b/tools/poly2kml/main.cpp index b897dcf49..0a6296390 100644 --- a/tools/poly2kml/main.cpp +++ b/tools/poly2kml/main.cpp @@ -1,193 +1,194 @@ // // 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 #include #include #include #include #include #include "geodata/data/GeoDataLineString.h" #include "geodata/data/GeoDataLinearRing.h" #include "geodata/data/GeoDataDocument.h" #include "geodata/data/GeoDataPlacemark.h" #include "geodata/data/GeoDataMultiGeometry.h" #include "geodata/data/GeoDataStyle.h" #include "geodata/data/GeoDataStyleMap.h" #include "geodata/data/GeoDataLineStyle.h" #include "geodata/data/GeoDataData.h" #include "geodata/data/GeoDataExtendedData.h" #include "geodata/writer/GeoWriter.h" #include "geodata/handlers/kml/KmlElementDictionary.h" +#include "MarbleColors.h" using namespace Marble; int usage() { qDebug() << "Usage: [options] poly2kml input.poly output.kml"; qDebug() << "\tOptions store additional metadata in output.kml:"; qDebug() << "\t--version aVersion"; qDebug() << "\t--name aName"; qDebug() << "\t--date aDate"; qDebug() << "\t--payload aFilename"; return 1; } QColor randomColor() { QVector colors = QVector() << Oxygen::aluminumGray4 << Oxygen::brickRed4; colors << Oxygen::hotOrange4 << Oxygen::forestGreen4 << Oxygen::hotOrange4; colors << Oxygen::seaBlue2 << Oxygen::skyBlue4 << Oxygen::sunYellow6; return colors.at( qrand() % colors.size() ); } void parseBoundingBox( const QFileInfo &file, const QString &name, const QString &version, const QString &date, const QString &transport, const QString &payload, GeoDataDocument* document ) { GeoDataPlacemark* placemark = new GeoDataPlacemark; GeoDataMultiGeometry *geometry = new GeoDataMultiGeometry; QFile input( file.absoluteFilePath() ); QString country = "Unknown"; if ( input.open( QFile::ReadOnly ) ) { QTextStream stream( &input ); country = stream.readLine(); float lat( 0.0 ), lon( 0.0 ); GeoDataLinearRing *box = new GeoDataLinearRing; while ( !stream.atEnd() ) { bool inside = true; QString line = stream.readLine().trimmed(); QStringList entries = line.split( QLatin1Char( ' ' ), QString::SkipEmptyParts ); if ( entries.size() == 1 ) { if (entries.first() == QLatin1String("END") && inside) { inside = false; if (!box->isEmpty()) { geometry->append(box); box = new GeoDataLinearRing; } } else if (entries.first() == QLatin1String("END") && !inside) { qDebug() << "END not expected here"; } else if ( entries.first().startsWith( QLatin1String( "!" ) ) ) { qDebug() << "Warning: Negative polygons not supported, skipping"; } else { //int number = entries.first().toInt(); inside = true; } } else if ( entries.size() == 2 ) { lon = entries.first().toDouble(); lat = entries.last().toDouble(); GeoDataCoordinates point( lon, lat, 0.0, GeoDataCoordinates::Degree ); *box << point; } else { qDebug() << "Warning: Ignoring line in" << file.absoluteFilePath() << "with" << entries.size() << "fields:" << line; } } } GeoDataStyle::Ptr style(new GeoDataStyle); GeoDataLineStyle lineStyle; QColor color = randomColor(); color.setAlpha( 200 ); lineStyle.setColor( color ); lineStyle.setWidth( 4 ); style->setLineStyle(lineStyle); style->setId("border"); GeoDataStyleMap styleMap; styleMap.setId("map-border"); styleMap.insert("normal", QLatin1Char('#') + style->id()); document->addStyleMap(styleMap); document->addStyle(style); placemark->setStyleUrl(QLatin1Char('#') + styleMap.id()); placemark->setName( name ); if ( !version.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "version", version ) ); } if ( !date.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "date", date ) ); } if ( !transport.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "transport", transport ) ); } if ( !payload.isEmpty() ) { placemark->extendedData().addValue( GeoDataData( "payload", payload ) ); } placemark->setGeometry(geometry); document->append(placemark); } int save( GeoDataDocument* document, const QFileInfo &filename ) { GeoWriter writer; writer.setDocumentType( kml::kmlTag_nameSpaceOgc22 ); QFile file( filename.absoluteFilePath() ); if ( !file.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) { qDebug() << "Cannot write to " << file.fileName(); return usage(); } if ( !writer.write( &file, document ) ) { qDebug() << "Can not write to " << file.fileName(); } file.close(); return 0; } int main( int argc, char* argv[] ) { QCoreApplication app( argc, argv ); if ( argc < 3 ) { usage(); return 0; } QString inputFile = argv[argc-2]; QString outputFile = argv[argc-1]; QString name; QString version; QString date; QString transport; QString payload; for ( int i=1; i