File indexing completed on 2025-04-20 03:34:32
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2014 Abhinav Gangwar <abhgang@gmail.com> 0004 // 0005 0006 // Self 0007 #include "ClickOnThat.h" 0008 0009 // Qt 0010 #include <QTime> 0011 #include <QVector> 0012 #include <QVariant> 0013 #include <QStringList> 0014 #include <QDir> 0015 0016 // Marble 0017 #include <marble/MarbleWidget.h> 0018 #include <marble/MarbleMap.h> 0019 #include <marble/MarbleModel.h> 0020 #include <marble/GeoDataTreeModel.h> 0021 #include <marble/MarbleDirs.h> 0022 #include <marble/MarblePlacemarkModel.h> 0023 0024 #include <marble/GeoDataDocument.h> 0025 #include <marble/GeoDataPlacemark.h> 0026 #include <marble/GeoDataStyle.h> 0027 #include <marble/GeoDataStyleMap.h> 0028 #include <marble/GeoDataIconStyle.h> 0029 #include <marble/GeoDataLinearRing.h> 0030 #include <marble/GeoDataPoint.h> 0031 #include <marble/GeoDataPolygon.h> 0032 #include <marble/GeoDataMultiGeometry.h> 0033 0034 namespace Marble 0035 { 0036 class ClickOnThatPrivate 0037 { 0038 public: 0039 ClickOnThatPrivate( MarbleWidget *marbleWidget ) 0040 : m_marbleWidget( marbleWidget ), 0041 m_parent( nullptr ), 0042 m_correctAnswerPlacemark( nullptr ), 0043 m_selectPinDocument( nullptr ), 0044 m_countryNames( nullptr ), 0045 m_countryBoundaries( nullptr ) 0046 { 0047 m_continentsAndOceans 0048 << QStringLiteral("Asia") << QStringLiteral("Africa") 0049 << QStringLiteral("North America") << QStringLiteral("South America") 0050 << QStringLiteral("Antarctica") << QStringLiteral("Europe") 0051 << QStringLiteral("Australia") 0052 << QStringLiteral("Arctic Ocean") << QStringLiteral("Indian Ocean") 0053 << QStringLiteral("North Atlantic Ocean") << QStringLiteral("North Pacific Ocean") 0054 << QStringLiteral("South Pacific Ocean") << QStringLiteral("South Atlantic Ocean") 0055 << QStringLiteral("Southern Ocean"); 0056 } 0057 0058 ~ClickOnThatPrivate() 0059 { 0060 delete m_selectPinDocument; 0061 } 0062 0063 MarbleWidget *m_marbleWidget; 0064 ClickOnThat *m_parent; 0065 0066 /** 0067 * Store the GeoDataPlacemark also 0068 * for the correct answer so that 0069 * we can highlight and zoom in 0070 * ( to fit in the current view port ) 0071 * to this placemark when user 0072 * chooses to view the right answer. 0073 */ 0074 GeoDataPlacemark *m_correctAnswerPlacemark; 0075 GeoDataCoordinates m_correctAnswer; 0076 0077 /** 0078 * @p m_selectPinDocument shows a pin 0079 * on map indicating whether the user 0080 * has clicked on right country 0081 */ 0082 GeoDataDocument *m_selectPinDocument; 0083 0084 /** 0085 * Document to store point placemarks which 0086 * have country names ( from file "boundaryplacemarks.cache" ) 0087 */ 0088 GeoDataDocument *m_countryNames; 0089 0090 /** 0091 * Document which have placemarks whose geometry 0092 * specifies the boundaries of a country 0093 * (from file "ne_50m_admin_0_countries.pn2" ) 0094 */ 0095 GeoDataDocument *m_countryBoundaries; 0096 0097 0098 /* 0099 * If the placemark used for posting question 0100 * represent a continent, the user needs to click exactly on 0101 * a particular country whose geometry contains this point 0102 * placemark on map ( Ideally it should be any country 0103 * within that continent ). Also, oceans are point placemark, so 0104 * user needs to click exactly on same point this placemark 0105 * represents ( Ideally it should anywhere within the ocean territory ). 0106 * So, to avoid such placemark we will use this list. 0107 */ 0108 QStringList m_continentsAndOceans; 0109 }; 0110 0111 ClickOnThat::ClickOnThat( MarbleWidget *marbleWidget ) 0112 : QObject(), 0113 d( new ClickOnThatPrivate(marbleWidget) ) 0114 { 0115 d->m_parent = this; 0116 connect( this, SIGNAL(announceHighlight(qreal,qreal,GeoDataCoordinates::Unit)), 0117 d->m_marbleWidget, SIGNAL(highlightedPlacemarksChanged(qreal,qreal,GeoDataCoordinates::Unit)) ); 0118 } 0119 0120 ClickOnThat::~ClickOnThat() 0121 { 0122 delete d; 0123 } 0124 0125 void ClickOnThat::disablePinDocument() 0126 { 0127 if ( d->m_selectPinDocument ) { 0128 d->m_selectPinDocument->setVisible( false ); 0129 d->m_marbleWidget->model()->treeModel()->updateFeature( d->m_selectPinDocument ); 0130 } 0131 } 0132 0133 void ClickOnThat::initiateGame() 0134 { 0135 /** 0136 * First remove the GeoDataDocument, which displays 0137 * country names, from map. 0138 */ 0139 if ( !d->m_countryNames ) { 0140 const GeoDataTreeModel *const treeModel = d->m_marbleWidget->model()->treeModel(); 0141 for ( int i = 0; i < treeModel->rowCount(); ++i ) { 0142 QVariant const data = treeModel->data ( treeModel->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole ); 0143 GeoDataObject *object = qvariant_cast<GeoDataObject*>( data ); 0144 Q_ASSERT_X( object, "CountryByShape::initiateGame", 0145 "failed to get valid data from treeModel for GeoDataObject" ); 0146 if (auto doc = geodata_cast<GeoDataDocument>(object)) { 0147 QFileInfo fileInfo( doc->fileName() ); 0148 if (fileInfo.fileName() == QLatin1String("boundaryplacemarks.cache")) { 0149 d->m_countryNames = doc; 0150 break; 0151 } 0152 } 0153 } 0154 } 0155 0156 if ( !d->m_countryBoundaries ) { 0157 const GeoDataTreeModel *const treeModel = d->m_marbleWidget->model()->treeModel(); 0158 for ( int i = 0; i < treeModel->rowCount(); ++i ) { 0159 QVariant const data = treeModel->data ( treeModel->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole ); 0160 GeoDataObject *object = qvariant_cast<GeoDataObject*>( data ); 0161 Q_ASSERT_X( object, "MainWindow::initiateGame", 0162 "failed to get valid data from treeModel for GeoDataObject" ); 0163 if (auto doc = geodata_cast<GeoDataDocument>(object)) { 0164 QFileInfo fileInfo( doc->fileName() ); 0165 if (fileInfo.fileName() == QLatin1String("ne_50m_admin_0_countries.pn2")) { 0166 d->m_countryBoundaries = doc; 0167 break; 0168 } 0169 } 0170 } 0171 } 0172 0173 if ( !d->m_selectPinDocument ) { 0174 d->m_selectPinDocument = new GeoDataDocument; 0175 GeoDataPlacemark *pinPlacemark = new GeoDataPlacemark; 0176 0177 GeoDataStyle::Ptr pinStyle(new GeoDataStyle); 0178 pinStyle->setId(QStringLiteral("answer")); 0179 GeoDataIconStyle iconStyle; 0180 iconStyle.setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/target.png"))); 0181 pinStyle->setIconStyle( iconStyle ); 0182 0183 GeoDataStyleMap styleMap; 0184 styleMap.setId(QStringLiteral("default-map")); 0185 styleMap.insert(QStringLiteral("normal"), QLatin1Char('#') + pinStyle->id()); 0186 0187 d->m_selectPinDocument->addStyle( pinStyle ); 0188 d->m_selectPinDocument->addStyleMap( styleMap ); 0189 0190 d->m_selectPinDocument->append( pinPlacemark ); 0191 pinPlacemark->setStyleUrl(QLatin1Char('#') + styleMap.id()); 0192 d->m_selectPinDocument->setVisible( false ); 0193 0194 // Add this document to treeModel 0195 d->m_marbleWidget->model()->treeModel()->addDocument( d->m_selectPinDocument ); 0196 } 0197 0198 d->m_marbleWidget->setHighlightEnabled( true ); 0199 d->m_marbleWidget->centerOn( 23.0, 42.0 ); 0200 d->m_marbleWidget->setDistance( 7500 ); 0201 connect( d->m_marbleWidget, SIGNAL(highlightedPlacemarksChanged(qreal,qreal,GeoDataCoordinates::Unit)), 0202 this, SLOT(determineResult(qreal,qreal,GeoDataCoordinates::Unit)) ); 0203 0204 if ( d->m_countryBoundaries && 0205 d->m_countryNames ) 0206 { 0207 d->m_countryNames->setVisible( false ); 0208 d->m_marbleWidget->model()->treeModel()->updateFeature( d->m_countryNames ); 0209 emit gameInitialized(); 0210 } 0211 } 0212 0213 void ClickOnThat::postQuestion( QObject *gameObject ) 0214 { 0215 /** 0216 * Find a random placemark 0217 */ 0218 Q_ASSERT_X( d->m_countryNames, "ClickOnThat::postQuestion", 0219 "CountryByShapePrivate::m_countryNames is NULL" ); 0220 QVector<GeoDataPlacemark*> countryPlacemarks = d->m_countryNames->placemarkList(); 0221 0222 uint randomSeed = uint(QTime::currentTime().msec()); 0223 qsrand( randomSeed ); 0224 0225 GeoDataPlacemark *placemark = nullptr; 0226 GeoDataPoint *point = nullptr; 0227 bool found = false; 0228 while( !found ) { 0229 placemark = countryPlacemarks[qrand()%countryPlacemarks.size()]; 0230 if ( !d->m_continentsAndOceans.contains(placemark->name(), Qt::CaseSensitive) ) { 0231 found = true; 0232 point = dynamic_cast<GeoDataPoint*>( placemark->geometry() ); 0233 } 0234 } 0235 if ( point ) { 0236 d->m_correctAnswerPlacemark = placemark; 0237 d->m_correctAnswer = point->coordinates(); 0238 if ( gameObject ) { 0239 QMetaObject::invokeMethod( gameObject, "clickOnThatQuestion", 0240 Q_ARG(QVariant, QVariant(placemark->name())) ); 0241 } 0242 } 0243 } 0244 0245 void ClickOnThat::updateSelectPin(bool result, const GeoDataCoordinates &clickedPoint ) 0246 { 0247 QDir dir; 0248 QString iconPath = dir.absolutePath(); 0249 if ( result ) { 0250 //iconPath = MarbleDirs::path("bitmaps/MapTackRoundHeadGreen.png"); 0251 iconPath += QLatin1String("/MapTackRoundHeadGreen.png"); 0252 } 0253 else { 0254 iconPath += QLatin1String("/MapTackRoundHeadRed.png"); 0255 } 0256 0257 GeoDataStyle::Ptr style = d->m_selectPinDocument->style(QStringLiteral("answer")); 0258 style->iconStyle().setIconPath( iconPath ); 0259 d->m_selectPinDocument->addStyle( style ); 0260 0261 QVector<GeoDataPlacemark*> placemarkList = d->m_selectPinDocument->placemarkList(); 0262 if ( placemarkList.size() > 0 ) { 0263 placemarkList[0]->setCoordinate( clickedPoint ); 0264 } 0265 0266 if ( !d->m_selectPinDocument->isVisible() ) { 0267 d->m_selectPinDocument->setVisible( true ); 0268 } 0269 d->m_marbleWidget->model()->treeModel()->updateFeature( d->m_selectPinDocument ); 0270 } 0271 0272 void ClickOnThat::determineResult( qreal lon, qreal lat, GeoDataCoordinates::Unit unit ) 0273 { 0274 GeoDataCoordinates coord( lon, lat, 0, unit ); 0275 0276 Q_ASSERT_X( d->m_countryNames, "ClickOnThat::determineResult", 0277 "CountryByShapePrivate::m_countryBoundaries is NULL" ); 0278 QVector<GeoDataFeature*>::Iterator i = d->m_countryBoundaries->begin(); 0279 QVector<GeoDataFeature*>::Iterator const end = d->m_countryBoundaries->end(); 0280 0281 bool foundStandardPoint = false; 0282 bool foundClickedPoint = false; 0283 for ( ; i != end; ++i ) { 0284 GeoDataPlacemark *country = static_cast<GeoDataPlacemark*>( *i ); 0285 0286 GeoDataPolygon *polygon = dynamic_cast<GeoDataPolygon*>( country->geometry() ); 0287 GeoDataLinearRing *linearring = dynamic_cast<GeoDataLinearRing*>( country->geometry() ); 0288 GeoDataMultiGeometry *multigeom = dynamic_cast<GeoDataMultiGeometry*>( country->geometry() ); 0289 0290 foundClickedPoint = false; 0291 foundStandardPoint = false; 0292 if ( polygon && 0293 polygon->contains( coord ) && 0294 polygon->contains(d->m_correctAnswer) ) 0295 { 0296 foundClickedPoint = true; 0297 foundStandardPoint = true; 0298 d->m_correctAnswerPlacemark = country; 0299 break; 0300 } 0301 if ( linearring && 0302 linearring->contains( coord ) && 0303 linearring->contains(d->m_correctAnswer) ) 0304 { 0305 foundClickedPoint = true; 0306 foundStandardPoint = true; 0307 d->m_correctAnswerPlacemark = country; 0308 break; 0309 } 0310 if ( multigeom ) { 0311 QVector<GeoDataGeometry*>::Iterator iter = multigeom->begin(); 0312 QVector<GeoDataGeometry*>::Iterator const end = multigeom->end(); 0313 0314 for ( ; iter != end; ++iter ) { 0315 GeoDataPolygon *poly = dynamic_cast<GeoDataPolygon*>( *iter ); 0316 if ( poly && 0317 poly->contains( coord ) ) 0318 { 0319 foundClickedPoint = true; 0320 } 0321 if ( poly && 0322 poly->contains( d->m_correctAnswer ) ) 0323 { 0324 foundStandardPoint = true; 0325 d->m_correctAnswerPlacemark = country; 0326 } 0327 if ( foundClickedPoint && foundStandardPoint ) { 0328 break; 0329 } 0330 } 0331 } 0332 if ( foundClickedPoint && foundStandardPoint ) { 0333 break; 0334 } 0335 } 0336 0337 if ( foundClickedPoint && foundStandardPoint ) { 0338 updateSelectPin( true, coord ); 0339 emit updateResult( true ); 0340 } 0341 else { 0342 updateSelectPin( false, coord ); 0343 emit updateResult( false ); 0344 } 0345 } 0346 0347 void ClickOnThat::highlightCorrectAnswer() 0348 { 0349 disconnect( d->m_marbleWidget, SIGNAL(highlightedPlacemarksChanged(qreal,qreal,GeoDataCoordinates::Unit)), 0350 this, SLOT(determineResult(qreal,qreal,GeoDataCoordinates::Unit)) ); 0351 0352 emit announceHighlight( d->m_correctAnswer.longitude(GeoDataCoordinates::Degree), 0353 d->m_correctAnswer.latitude(GeoDataCoordinates::Degree), 0354 GeoDataCoordinates::Degree ); 0355 updateSelectPin( true, d->m_correctAnswer ); 0356 0357 /** 0358 * Zoom to highlighted placemark 0359 * so that it fits the current 0360 * view port 0361 */ 0362 d->m_marbleWidget->centerOn( *(d->m_correctAnswerPlacemark), true ); 0363 0364 connect( d->m_marbleWidget, SIGNAL(highlightedPlacemarksChanged(qreal,qreal,GeoDataCoordinates::Unit)), 0365 this, SLOT(determineResult(qreal,qreal,GeoDataCoordinates::Unit)) ); 0366 } 0367 0368 0369 } // namespace Marble 0370 0371 #include "moc_ClickOnThat.cpp"