File indexing completed on 2024-12-01 12:27:09
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 }