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 
0007 // Self
0008 #include "CountryByShape.h"
0009 
0010 // Qt
0011 #include <QVector>
0012 #include <QTime>
0013 #include <QVariant>
0014 #include <QVariantList>
0015 #include <QFileInfo>
0016 
0017 // Marble
0018 #include <marble/MarbleWidget.h>
0019 #include <marble/MarbleModel.h>
0020 #include <marble/GeoDataTreeModel.h>
0021 #include <marble/MarblePlacemarkModel.h>
0022 
0023 #include <marble/GeoDataDocument.h>
0024 #include <marble/GeoDataPlacemark.h>
0025 #include <marble/GeoDataGeometry.h>
0026 #include <marble/GeoDataPolygon.h>
0027 #include <marble/GeoDataLinearRing.h>
0028 #include <marble/GeoDataMultiGeometry.h>
0029 #include <marble/GeoDataPoint.h>
0030 #include <marble/GeoDataLatLonAltBox.h>
0031 
0032 namespace Marble
0033 {
0034 class CountryByShapePrivate
0035 {
0036 public:
0037     CountryByShapePrivate( MarbleWidget *marbleWidget )
0038     : m_parent( nullptr ),
0039       m_marbleWidget( marbleWidget ),
0040       m_countryNames( nullptr ),
0041       m_countryBoundaries( nullptr )
0042     {
0043         m_continentsAndOceans
0044             << QStringLiteral("Asia") << QStringLiteral("Africa")
0045             << QStringLiteral("North America") << QStringLiteral("South America")
0046             << QStringLiteral("Antarctica") << QStringLiteral("Europe")
0047             << QStringLiteral("Australia")
0048             << QStringLiteral("Arctic Ocean") << QStringLiteral("Indian Ocean")
0049             << QStringLiteral("North Atlantic Ocean") << QStringLiteral("North Pacific Ocean")
0050             << QStringLiteral("South Pacific Ocean") << QStringLiteral("South Atlantic Ocean")
0051             << QStringLiteral("Southern Ocean");
0052     }
0053 
0054     CountryByShape *m_parent;
0055     MarbleWidget *m_marbleWidget;
0056 
0057     /**
0058      * Document to store point placemarks which
0059      * have country names ( from file "boundaryplacemarks.cache" )
0060      */
0061     GeoDataDocument *m_countryNames;
0062 
0063     /**
0064      * Document which have placemarks whose geometry
0065      * specifies the boundaries of a country
0066      * (from file "ne_50m_admin_0_countries.pn2" )
0067      */
0068     GeoDataDocument *m_countryBoundaries;
0069 
0070     /**
0071      * If the placemark, we select from boundaryplacemarks.cache,
0072      * is a continent, the game will highlight a country,
0073      * on map, which contains this point placemark in its geometry 
0074      * instead of highlighting the whole continent.
0075      * Also, oceans are point placemark and we don't have
0076      * any geometry, provided for oceans , that we can highlight.
0077      * So, to avoid placemarks which represent continent or ocean
0078      * we will use this list.
0079      */
0080     QStringList m_continentsAndOceans;
0081 };
0082 
0083 CountryByShape::CountryByShape( MarbleWidget *marbleWidget )
0084     : QObject(),
0085       d( new CountryByShapePrivate(marbleWidget) )
0086 {
0087     d->m_parent = this;
0088     connect( this, SIGNAL(announceHighlight(qreal,qreal,GeoDataCoordinates::Unit)),
0089              d->m_marbleWidget, SIGNAL(highlightedPlacemarksChanged(qreal,qreal,GeoDataCoordinates::Unit)) );
0090 }
0091 
0092 CountryByShape::~CountryByShape()
0093 {
0094     delete d;
0095 }
0096 
0097 void CountryByShape::initiateGame()
0098 {
0099     if ( !d->m_countryNames ) {
0100         const GeoDataTreeModel *const treeModel = d->m_marbleWidget->model()->treeModel();
0101         for ( int i = 0; i < treeModel->rowCount(); ++i ) {
0102             QVariant const data = treeModel->data ( treeModel->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole );
0103             GeoDataObject *object = qvariant_cast<GeoDataObject*>( data );
0104             Q_ASSERT_X( object, "CountryByShape::initiateGame",
0105                         "failed to get valid data from treeModel for GeoDataObject" );
0106             if (auto doc = geodata_cast<GeoDataDocument>(object)) {
0107                 QFileInfo fileInfo( doc->fileName() );
0108                 if (fileInfo.fileName() == QLatin1String("boundaryplacemarks.cache")) {
0109                     d->m_countryNames = doc;
0110                     break;
0111                 }
0112             }
0113         }
0114     }
0115 
0116     if ( !d->m_countryBoundaries ) {
0117         const GeoDataTreeModel *const treeModel = d->m_marbleWidget->model()->treeModel();
0118         for ( int i = 0; i < treeModel->rowCount(); ++i ) {
0119             QVariant const data = treeModel->data ( treeModel->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole );
0120             GeoDataObject *object = qvariant_cast<GeoDataObject*>( data );
0121             Q_ASSERT_X( object, "MainWindow::initiateGame",
0122                         "failed to get valid data from treeModel for GeoDataObject" );
0123             if (auto doc = geodata_cast<GeoDataDocument>(object)) {
0124                 QFileInfo fileInfo( doc->fileName() );
0125                 if (fileInfo.fileName() == QLatin1String("ne_50m_admin_0_countries.pn2")) {
0126                     d->m_countryBoundaries = doc;
0127                     break;
0128                 }
0129             }
0130         }
0131     }
0132 
0133     d->m_marbleWidget->setHighlightEnabled( true );
0134 
0135     if ( d->m_countryBoundaries &&
0136          d->m_countryNames )
0137     {
0138         d->m_countryNames->setVisible( false );
0139         d->m_marbleWidget->model()->treeModel()->updateFeature( d->m_countryNames );
0140         emit gameInitialized();
0141     }
0142 }
0143 
0144 void CountryByShape::postQuestion( QObject *gameObject )
0145 {
0146     //Find a random placemark
0147 
0148     Q_ASSERT_X( d->m_countryNames, "CountryByShape::postQuestion",
0149                 "CountryByShapePrivate::m_countryNames is NULL" );
0150 
0151     QVector<GeoDataPlacemark*> countryPlacemarks = d->m_countryNames->placemarkList();
0152 
0153     uint randomSeed = uint(QTime::currentTime().msec());
0154     qsrand( randomSeed );
0155 
0156     bool found = false;
0157     GeoDataPlacemark *placemark =nullptr;
0158     GeoDataPoint *point = nullptr;
0159     GeoDataCoordinates coord;
0160     GeoDataLatLonAltBox box;
0161     QVariantList answerOptions;
0162     while ( !found ) {
0163         int randomIndex = qrand()%countryPlacemarks.size();
0164         placemark = countryPlacemarks[randomIndex];
0165         point = dynamic_cast<GeoDataPoint*>( placemark->geometry() );
0166         coord = point->coordinates();
0167 
0168         if ( point ) {
0169             /**
0170              * Find the country geometry and fetch corresponding
0171              * GeoDataLatLonAltBox to zoom in to that country so that
0172              * it fills the viewport.
0173              */
0174 
0175             Q_ASSERT_X( d->m_countryBoundaries, "CountryByShape::postQuestion",
0176                         "CountryByShapePrivate::m_countryBoundaries is NULL" );
0177 
0178             QVector<GeoDataFeature*>::Iterator i = d->m_countryBoundaries->begin();
0179             QVector<GeoDataFeature*>::Iterator const end = d->m_countryBoundaries->end();
0180             for ( ; i != end; ++i ) {
0181                 GeoDataPlacemark *country = static_cast<GeoDataPlacemark*>( *i );
0182 
0183                 GeoDataPolygon *polygon = dynamic_cast<GeoDataPolygon*>( country->geometry() );
0184                 GeoDataLinearRing *linearring = dynamic_cast<GeoDataLinearRing*>( country->geometry() );
0185                 GeoDataMultiGeometry *multigeom = dynamic_cast<GeoDataMultiGeometry*>( country->geometry() );
0186 
0187                 if ( polygon &&
0188                     polygon->contains( coord ) &&
0189                     !d->m_continentsAndOceans.contains(country->name(), Qt::CaseSensitive) )
0190                 {
0191                     box = polygon->latLonAltBox();
0192                     found = true;
0193                     break;
0194                 }
0195                 if ( linearring &&
0196                     linearring->contains( coord ) &&
0197                     !d->m_continentsAndOceans.contains(country->name(), Qt::CaseSensitive) )
0198                 {
0199                     box = linearring->latLonAltBox();
0200                     found = true;
0201                     break;
0202                 }
0203                 if ( multigeom ) {
0204                     QVector<GeoDataGeometry*>::Iterator iter = multigeom->begin();
0205                     QVector<GeoDataGeometry*>::Iterator const end = multigeom->end();
0206 
0207                     for ( ; iter != end; ++iter ) {
0208                         GeoDataPolygon *poly  = dynamic_cast<GeoDataPolygon*>( *iter );
0209                         if ( poly &&
0210                             poly->contains( coord ) &&
0211                             !d->m_continentsAndOceans.contains(country->name(), Qt::CaseSensitive) )
0212                         {
0213                             box = poly->latLonAltBox();
0214                             found = true;
0215                             break;
0216                         }
0217                     }
0218                 }
0219                 if ( found ) {
0220                     break;
0221                 }
0222             }
0223         }
0224     }
0225     d->m_marbleWidget->setHighlightEnabled( true );
0226     emit announceHighlight( coord.longitude(GeoDataCoordinates::Degree),
0227                             coord.latitude(GeoDataCoordinates::Degree),
0228                             GeoDataCoordinates::Degree );
0229 
0230     /**
0231      * Now disable the highlight feature so that
0232      * the user click doesn't disturb the highlight
0233      * we did to ask question.
0234      */ 
0235     d->m_marbleWidget->setHighlightEnabled( false );
0236 
0237     d->m_marbleWidget->centerOn( box, true );
0238 
0239     answerOptions << placemark->name()
0240     << countryPlacemarks[qrand()%countryPlacemarks.size()]->name()
0241     << countryPlacemarks[qrand()%countryPlacemarks.size()]->name()
0242     << countryPlacemarks[qrand()%countryPlacemarks.size()]->name();
0243 
0244     // Randomize options in list answerOptions
0245     for ( int i = 0; i < answerOptions.size(); ++i ) {
0246         QVariant option = answerOptions.takeAt( qrand()%answerOptions.size() );
0247         answerOptions.append( option );
0248     }
0249 
0250     if ( gameObject ) {
0251         QMetaObject::invokeMethod( gameObject, "countryByShapeQuestion",
0252                                    Q_ARG(QVariant, QVariant(answerOptions)),
0253                                    Q_ARG(QVariant, QVariant(placemark->name())) );
0254     }
0255 }
0256 
0257 }   // namespace Marble
0258 
0259 #include "moc_CountryByShape.cpp"