File indexing completed on 2024-04-21 03:48:25

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"