File indexing completed on 2024-12-08 09:32:24

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2014 Dennis Nienhüser <nienhueser@kde.org>
0004 //
0005 
0006 /**
0007   * Does an animated camera flight using a given route loaded from
0008   * a .kml file and writes an .avi (DIVX) video of it.
0009   */
0010 
0011 #include <marble/MarbleWidget.h>
0012 #include <marble/MarbleMath.h>
0013 #include <marble/GeoDataCoordinates.h>
0014 #include <marble/GeoDataLineString.h>
0015 #include <marble/RenderPlugin.h>
0016 #include <marble/MarbleModel.h>
0017 #include <marble/Route.h>
0018 #include <marble/RoutingManager.h>
0019 #include <marble/RoutingModel.h>
0020 #include <marble/TourPlayback.h>
0021 #include <marble/GeoDataTour.h>
0022 #include <marble/GeoDataPlaylist.h>
0023 #include <marble/GeoDataFlyTo.h>
0024 #include <marble/GeoDataLookAt.h>
0025 
0026 #include <opencv2/highgui/highgui.hpp>
0027 #include <opencv2/imgproc/imgproc.hpp>
0028 
0029 #include <QApplication>
0030 #include <QThread>
0031 #include <QDebug>
0032 
0033 #include <cstdio>
0034 
0035 using namespace Marble;
0036 using namespace cv;
0037 
0038 class Waiter: private QThread { public: using QThread::msleep; };
0039 
0040 namespace {
0041 // Some stuff you might want to change
0042 
0043 // The map theme in use
0044 QString const mapTheme = "earth/openstreetmap/openstreetmap.dgml";
0045 
0046 // Enabled plugins. Everything else will be disabled
0047 QStringList const features = QStringList() << "stars" << "atmosphere";
0048 
0049 // Frames per second
0050 int const fps = 30;
0051 
0052 // Target video file name
0053 std::string const videoFile = "marble-tour-preview.avi";
0054 
0055 // Video resolution
0056 Size frameSize( 1280, 720 );
0057 
0058 // Camera velocity in km/h
0059 double const velocity = 200.0;
0060 }
0061 
0062 GeoDataTour* createTour( const Route &route )
0063 {
0064     GeoDataTour* tour = new GeoDataTour;
0065     tour->setPlaylist( new GeoDataPlaylist );
0066     GeoDataLineString path = route.path();
0067     if ( path.size() < 1 ) {
0068         return tour;
0069     }
0070 
0071     // Extract tour points at about all 500 meters
0072     GeoDataCoordinates last = path.at( 0 );
0073     for ( int i=1; i<path.size(); ++i ) {
0074         GeoDataCoordinates coordinates = path.at( i );
0075         double const distance = EARTH_RADIUS * last.sphericalDistanceTo( coordinates );
0076         if ( i > 1 && distance < 500 ) {
0077             // Ignore waypoints that are quite close
0078             continue;
0079         }
0080         last = coordinates;
0081 
0082         // Create a point in the tour from the given route point
0083         GeoDataLookAt* lookat = new GeoDataLookAt;
0084         coordinates.setAltitude( 800 );
0085         lookat->setCoordinates( coordinates );
0086         lookat->setRange( 800 );
0087         GeoDataFlyTo* flyto = new GeoDataFlyTo;
0088         double const duration = qBound( 0.2, distance / velocity / 3.6, 10.0 );
0089         flyto->setDuration( duration );
0090         flyto->setView( lookat );
0091         flyto->setFlyToMode( GeoDataFlyTo::Smooth );
0092         tour->playlist()->addPrimitive( flyto );
0093     }
0094 
0095     return tour;
0096 }
0097 
0098 void animatedFlight( MarbleWidget *mapWidget, GeoDataTour* tour )
0099 {
0100     mapWidget->resize( frameSize.width, frameSize.height );
0101     TourPlayback* playback = new TourPlayback;
0102     playback->setMarbleWidget( mapWidget );
0103     playback->setTour( tour );
0104     QObject::connect( playback, SIGNAL(centerOn(GeoDataCoordinates)),
0105                       mapWidget, SLOT(centerOn(GeoDataCoordinates)) );
0106 
0107     double const shift = 1.0 / fps;
0108     double const duration = playback->duration();
0109 
0110     VideoWriter videoWriter( videoFile, cv::VideoWriter::fourcc('D','I','V','X'), fps, frameSize );
0111     Mat buffer;
0112     buffer.create(frameSize, CV_8UC3);
0113     for ( double position = 0.0; position <= duration; position += shift ) {
0114         printf("[%i%% done]\r", cvRound( (100.0*position)/duration ) );
0115         fflush(stdout);
0116 
0117         playback->seek( position );
0118         QImage screenshot = QPixmap::grabWidget( mapWidget ).toImage().convertToFormat( QImage::Format_RGB888 );
0119         Mat converter( frameSize, CV_8UC3 );
0120         converter.data = screenshot.bits();
0121         cvtColor( converter, buffer, COLOR_RGB2BGR );
0122         videoWriter.write( buffer );
0123     }
0124 
0125     for ( int i=0; i<fps; ++i ) {
0126         videoWriter.write( buffer ); // one second stand-still at end
0127     }
0128     printf("Wrote %s\n", videoFile.c_str());
0129 }
0130 
0131 int main(int argc, char** argv)
0132 {
0133     QApplication app(argc,argv);
0134     if (app.arguments().size() < 2) {
0135         qDebug() << "Usage: " << app.applicationName() << " /path/to/route.kml";
0136         qDebug() << "You can create a suitable route.kml file with Marble.";
0137         return 0;
0138     }
0139 
0140     MarbleWidget *mapWidget = new MarbleWidget;
0141     mapWidget->setMapThemeId(mapTheme);
0142     foreach( RenderPlugin* plugin, mapWidget->renderPlugins() ) {
0143         if ( !features.contains( plugin->nameId() ) ) {
0144             plugin->setEnabled( false );
0145         }
0146     }
0147 
0148     mapWidget->model()->routingManager()->loadRoute(argv[1]);
0149     Route const route = mapWidget->model()->routingManager()->routingModel()->route();
0150     if ( route.size() == 0 ) {
0151         qDebug() << "Failed to open route " << argv[1];
0152         return 1;
0153     }
0154     GeoDataCoordinates start = route.path().at( 0 );
0155     start.setLongitude( start.longitude() + 1e-6 );
0156     mapWidget->centerOn( start );
0157     mapWidget->setDistance( 0.8 );
0158     animatedFlight( mapWidget, createTour( route ) );
0159     return 0;
0160 }