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"