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 #include "squad-interpolation.h"
0007 
0008 #include <marble/MarbleGlobal.h>
0009 #include <marble/GeoDataLatLonAltBox.h>
0010 #include <marble/GeoDataDocument.h>
0011 #include <marble/GeoDataPlacemark.h>
0012 #include <marble/GeoDataTreeModel.h>
0013 #include <marble/MarblePlacemarkModel.h>
0014 #include <marble/GeoDataLinearRing.h>
0015 #include <marble/MarbleColors.h>
0016 #include <marble/MarbleMath.h>
0017 #include <marble/ViewportParams.h>
0018 
0019 #include <QTimer>
0020 
0021 namespace Marble
0022 {
0023 
0024 MyPaintLayer::MyPaintLayer ( MarbleWidget *widget ) :
0025     m_widget ( widget ),
0026     m_fraction ( 0.0 ),
0027     m_delta( 0.02 ),
0028     m_index ( 0 )
0029 {
0030     GeoDataCoordinates::Unit const degree = GeoDataCoordinates::Degree;
0031     m_cities << GeoDataCoordinates(  7.64573, 45.04981, 0.0, degree ); // Torino
0032     m_cities << GeoDataCoordinates(  8.33439, 49.01673, 0.0, degree ); // Karlsruhe
0033     m_cities << GeoDataCoordinates( 14.41637, 50.09329, 0.0, degree ); // Praha
0034     m_cities << GeoDataCoordinates( 15.97254, 45.80268, 0.0, degree ); // Zagred
0035     addInterpolatedPoint();
0036 }
0037 
0038 QStringList MyPaintLayer::renderPosition() const
0039 {
0040     return QStringList(QStringLiteral("USER_TOOLS"));
0041 }
0042 
0043 bool MyPaintLayer::render ( GeoPainter *painter, ViewportParams *viewport, const QString &, GeoSceneLayer * )
0044 {
0045     if ( m_index < 20 ) {
0046         // Gray dotted line connects all current cities
0047         QPen grayPen = Marble::Oxygen::aluminumGray4;
0048         grayPen.setWidth ( 3 );
0049         grayPen.setStyle ( Qt::DotLine );
0050         painter->setPen ( grayPen );
0051         painter->drawPolyline ( m_cities );
0052     }
0053 
0054     // Blue circle around each city
0055     painter->setBrush ( QBrush ( QColor ( Marble::Oxygen::skyBlue4 ) ) );
0056     painter->setPen ( QColor ( Marble::Oxygen::aluminumGray4 ) );
0057     for ( int i = 0; i < m_cities.size(); ++i ) {
0058         painter->drawEllipse ( m_cities[i], 32, 32 );
0059     }
0060 
0061     if (m_index < 10) {
0062         // Show how squad interpolation works internally
0063         Q_ASSERT( m_cities.size() == 4 );
0064         painter->setBrush ( QBrush ( QColor ( Marble::Oxygen::grapeViolet4 ) ) );
0065         painter->setPen ( QColor ( Marble::Oxygen::aluminumGray4 ) );
0066         GeoDataCoordinates a2 = basePoint( m_cities[0], m_cities[1], m_cities[2] );
0067         painter->drawEllipse ( a2, 8, 8 );
0068         qreal x, y;
0069         if ( viewport->screenCoordinates ( a2, x, y ) ) {
0070             painter->drawText(x+5, y, QStringLiteral("A"));
0071         }
0072         GeoDataCoordinates b1 = basePoint( m_cities[1], m_cities[2], m_cities[3] );
0073         painter->drawEllipse ( b1, 8, 8 );
0074         if ( viewport->screenCoordinates ( b1, x, y ) ) {
0075             painter->drawText(x+5, y, QStringLiteral("B"));
0076         }
0077 
0078         QPen grapePen = Marble::Oxygen::grapeViolet4;
0079         grapePen.setWidth ( 2 );
0080         painter->setPen ( grapePen );
0081         GeoDataLineString string;
0082         string << m_cities[0] << a2 << b1 << m_cities[3];
0083         painter->drawPolyline ( string );
0084 
0085         GeoDataCoordinates i1 = m_cities[1].interpolate( m_cities[2], m_fraction-m_delta );
0086         GeoDataCoordinates i2 = a2.interpolate( b1, m_fraction-m_delta );
0087         QPen raspberryPen = Marble::Oxygen::burgundyPurple4;
0088         raspberryPen.setWidth ( 2 );
0089         painter->setPen ( raspberryPen );
0090         GeoDataLineString inter;
0091         inter << i1 << i2;
0092         painter->drawPolyline ( inter );
0093     }
0094 
0095     // Green linestring shows interpolation path
0096     QPen greenPen = Marble::Oxygen::forestGreen4;
0097     greenPen.setWidth ( 3 );
0098     painter->setPen ( greenPen );
0099     painter->drawPolyline ( m_interpolated, QStringLiteral("Squad\nInterpolation"), LineEnd );
0100 
0101     // Increasing city indices with some transparency effect for readability
0102     QFont font = painter->font();
0103     font.setBold( true );
0104     painter->setFont( font );
0105     QColor blue = QColor ( Marble::Oxygen::skyBlue4 );
0106     blue.setAlpha( 150 );
0107     painter->setBrush ( QBrush ( blue ) );
0108     int const h = painter->fontMetrics().height();
0109     for ( int i = 0; i < m_cities.size(); ++i ) {
0110         qreal x, y;
0111         QString const text = QString::number ( m_index + i );
0112         int const w = painter->fontMetrics().width( text );
0113         painter->setPen ( Qt::NoPen );
0114         painter->drawEllipse ( m_cities[i], 1.5*w, 1.5*h );
0115         painter->setPen ( QColor ( Marble::Oxygen::aluminumGray4 ) );
0116         if ( viewport->screenCoordinates ( m_cities[i], x, y ) ) {
0117             painter->drawText ( x-w/2, y+h/3, text );
0118         }
0119     }
0120 
0121     return true;
0122 }
0123 
0124 GeoDataLatLonBox MyPaintLayer::center() const
0125 {
0126     GeoDataLinearRing ring;
0127     foreach( const GeoDataCoordinates &city, m_cities ) {
0128         ring << city;
0129     }
0130     return ring.latLonAltBox();
0131 }
0132 
0133 void MyPaintLayer::addRandomCity ( double minDistance, double maxDistance )
0134 {
0135     minDistance *= KM2METER;
0136     maxDistance *= KM2METER;
0137     GeoDataTreeModel* tree = m_widget->model()->treeModel();
0138     if ( !tree || tree->rowCount() < 6 || m_cities.isEmpty() ) {
0139         return;
0140     }
0141 
0142     // Traverse Marble's internal city database and add a random one
0143     // which is in the requested distance range to the last one
0144     for ( int i = 0; i < tree->rowCount(); ++i ) {
0145         QVariant const data = tree->data ( tree->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole );
0146         GeoDataObject *object = qvariant_cast<GeoDataObject*> ( data );
0147         Q_ASSERT ( object );
0148         if (const auto document = geodata_cast<GeoDataDocument>(object)) {
0149             if (document->name() == QLatin1String("cityplacemarks")) {
0150                 QVector<GeoDataPlacemark*> placemarks = document->placemarkList();
0151                 for ( int i = qrand() % placemarks.size(); i < placemarks.size(); ++i ) {
0152                     const double distance = EARTH_RADIUS * m_cities.last().sphericalDistanceTo(placemarks[i]->coordinate());
0153                     if ( distance >= minDistance && distance <= maxDistance ) {
0154                         m_cities << placemarks[i]->coordinate();
0155                         return;
0156                     }
0157                 }
0158             }
0159         }
0160     }
0161 
0162     addRandomCity();
0163 }
0164 
0165 GeoDataCoordinates MyPaintLayer::basePoint( const GeoDataCoordinates &c1, const GeoDataCoordinates &c2, const GeoDataCoordinates &c3 )
0166 {
0167     Quaternion const a = (c2.quaternion().inverse() * c3.quaternion()).log();
0168     Quaternion const b = (c2.quaternion().inverse() * c1.quaternion()).log();
0169     Quaternion const c = c2.quaternion() * ((a+b)*-0.25).exp();
0170     qreal lon, lat;
0171     c.getSpherical( lon, lat );
0172     return GeoDataCoordinates( lon, lat );
0173 }
0174 
0175 void MyPaintLayer::addInterpolatedPoint()
0176 {
0177     while ( m_interpolated.size() > 2.0/m_delta ) {
0178         m_interpolated.remove ( 0 );
0179     }
0180 
0181     m_delta = m_index < 20 ? 0.01 : 0.04;
0182     Q_ASSERT ( m_cities.size() == 4 );
0183     // Interpolate for the current city
0184     m_interpolated << m_cities[1].interpolate ( m_cities[0], m_cities[2], m_cities[3], m_fraction );
0185     m_fraction += m_delta;
0186 
0187     // If current city is done, move one forward
0188     if ( m_fraction > 1.0 ) {
0189         m_fraction = 0.0;
0190         m_cities.remove ( 0 );
0191         addRandomCity();
0192         ++m_index;
0193     }
0194 
0195     // Repaint map, recenter if out of view
0196     bool hidden;
0197     qreal x; qreal y;
0198     if ( m_widget->viewport()->screenCoordinates ( m_interpolated.last(), x, y, hidden ) ) {
0199         m_widget->update();
0200     } else {
0201         m_widget->centerOn ( center() );
0202     }
0203 
0204     int const timeout = qBound( 0, 150 - 50 * m_index, 150 );
0205     QTimer::singleShot ( timeout, this, SLOT (addInterpolatedPoint()) );
0206 }
0207 
0208 }
0209 
0210 int main ( int argc, char** argv )
0211 {
0212     using namespace Marble;
0213     QApplication app ( argc, argv );
0214     MarbleWidget *mapWidget = new MarbleWidget;
0215     mapWidget->setWindowTitle(QStringLiteral("Marble - Squad Interpolation"));
0216 
0217     // Create and register our paint layer
0218     MyPaintLayer* layer = new MyPaintLayer ( mapWidget );
0219     mapWidget->addLayer ( layer );
0220     mapWidget->centerOn ( layer->center() );
0221 
0222     // Finish widget creation.
0223     mapWidget->setMapThemeId(QStringLiteral("earth/plain/plain.dgml"));
0224     mapWidget->setShowCities( false );
0225     mapWidget->setShowCrosshairs( false );
0226     mapWidget->setShowOtherPlaces( false );
0227     mapWidget->setShowPlaces( false );
0228     mapWidget->setShowTerrain( false );
0229     mapWidget->show();
0230 
0231     return app.exec();
0232 }
0233 
0234 #include "moc_squad-interpolation.cpp"