File indexing completed on 2024-04-28 11:30:55

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
0004 //
0005 
0006 #include "GeoGraphicsScene.h"
0007 
0008 #include "GeoDataFeature.h"
0009 #include "GeoDataLatLonAltBox.h"
0010 #include "GeoDataStyle.h"
0011 #include "GeoDataStyleMap.h"
0012 #include "GeoDataPlacemark.h"
0013 #include "GeoDataDocument.h"
0014 #include "GeoGraphicsItem.h"
0015 #include "TileId.h"
0016 #include "TileCoordsPyramid.h"
0017 #include "MarbleDebug.h"
0018 
0019 #include <QMap>
0020 #include <QRect>
0021 
0022 namespace Marble
0023 {
0024 
0025 class GeoGraphicsScenePrivate
0026 {
0027 public:
0028     GeoGraphicsScene *q;
0029     explicit GeoGraphicsScenePrivate(GeoGraphicsScene *parent) :
0030         q(parent)
0031     {
0032     }
0033 
0034     ~GeoGraphicsScenePrivate()
0035     {
0036         q->clear();
0037     }
0038 
0039     typedef QHash<const GeoDataFeature*,GeoGraphicsItem*> FeatureItemMap;
0040     QHash<TileId, FeatureItemMap> m_tiledItems;
0041     QMultiHash<const GeoDataFeature*, TileId> m_features; // multi hash because multi track and multi geometry insert multiple items
0042 
0043     // Stores the items which have been clicked;
0044     QList<GeoGraphicsItem*> m_selectedItems;
0045 
0046     static GeoDataStyle::Ptr highlightStyle(const GeoDataDocument *document, const GeoDataStyleMap &styleMap);
0047 
0048     void selectItem( GeoGraphicsItem *item );
0049     static void applyHighlightStyle(GeoGraphicsItem *item, const GeoDataStyle::Ptr &style);
0050 };
0051 
0052 GeoDataStyle::Ptr GeoGraphicsScenePrivate::highlightStyle( const GeoDataDocument *document,
0053                                                        const GeoDataStyleMap &styleMap )
0054 {
0055     // @todo Consider QUrl parsing when external styles are suppported
0056     QString highlightStyleId = styleMap.value(QStringLiteral("highlight"));
0057     highlightStyleId.remove(QLatin1Char('#'));
0058     if ( !highlightStyleId.isEmpty() ) {
0059         GeoDataStyle::Ptr highlightStyle(new GeoDataStyle( *document->style(highlightStyleId) ));
0060         return highlightStyle;
0061     }
0062     else {
0063         return GeoDataStyle::Ptr();
0064     }
0065 }
0066 
0067 void GeoGraphicsScenePrivate::selectItem( GeoGraphicsItem* item )
0068 {
0069     m_selectedItems.append( item );
0070 }
0071 
0072 void GeoGraphicsScenePrivate::applyHighlightStyle(GeoGraphicsItem* item, const GeoDataStyle::Ptr &highlightStyle )
0073 {
0074     item->setHighlightStyle( highlightStyle );
0075     item->setHighlighted( true );
0076 }
0077 
0078 GeoGraphicsScene::GeoGraphicsScene( QObject* parent ):
0079     QObject( parent ),
0080     d( new GeoGraphicsScenePrivate(this) )
0081 {
0082 
0083 }
0084 
0085 GeoGraphicsScene::~GeoGraphicsScene()
0086 {
0087     delete d;
0088 }
0089 
0090 QList< GeoGraphicsItem* > GeoGraphicsScene::items( const GeoDataLatLonBox &box, int zoomLevel ) const
0091 {
0092     if ( box.west() > box.east() ) {
0093         // Handle boxes crossing the IDL by splitting it into two separate boxes
0094         GeoDataLatLonBox left;
0095         left.setWest( -M_PI );
0096         left.setEast( box.east() );
0097         left.setNorth( box.north() );
0098         left.setSouth( box.south() );
0099 
0100         GeoDataLatLonBox right;
0101         right.setWest( box.west() );
0102         right.setEast( M_PI );
0103         right.setNorth( box.north() );
0104         right.setSouth( box.south() );
0105 
0106         return items(left, zoomLevel) + items(right, zoomLevel);
0107     }
0108 
0109     QList< GeoGraphicsItem* > result;
0110     QRect rect;
0111     qreal north, south, east, west;
0112     box.boundaries( north, south, east, west );
0113     TileId key;
0114 
0115     key = TileId::fromCoordinates( GeoDataCoordinates(west, north, 0), zoomLevel );
0116     rect.setLeft( key.x() );
0117     rect.setTop( key.y() );
0118 
0119     key = TileId::fromCoordinates( GeoDataCoordinates(east, south, 0), zoomLevel );
0120     rect.setRight( key.x() );
0121     rect.setBottom( key.y() );
0122     
0123     TileCoordsPyramid pyramid( 0, zoomLevel );
0124     pyramid.setBottomLevelCoords( rect );
0125 
0126     for ( int level = pyramid.topLevel(); level <= pyramid.bottomLevel(); ++level ) {
0127         QRect const coords = pyramid.coords( level );
0128         int x1, y1, x2, y2;
0129         coords.getCoords( &x1, &y1, &x2, &y2 );
0130         for ( int x = x1; x <= x2; ++x ) {
0131             bool const isBorderX = x == x1 || x == x2;
0132             for ( int y = y1; y <= y2; ++y ) {
0133                 bool const isBorder = isBorderX || y == y1 || y == y2;
0134                 const TileId tileId = TileId( 0, level, x, y );
0135                 for(GeoGraphicsItem *object: d->m_tiledItems.value( tileId )) {
0136                     if (object->minZoomLevel() <= zoomLevel && object->visible()) {
0137                         if (!isBorder || object->latLonAltBox().intersects(box)) {
0138                             result.push_back(object);
0139                         }
0140                     }
0141                 }
0142             }
0143         }
0144     }
0145 
0146     return result;
0147 }
0148 
0149 QList< GeoGraphicsItem* > GeoGraphicsScene::selectedItems() const
0150 {
0151     return d->m_selectedItems;
0152 }
0153 
0154 void GeoGraphicsScene::resetStyle()
0155 {
0156     for (auto const & items: d->m_tiledItems) {
0157         for (auto item: items) {
0158             item->resetStyle();
0159         }
0160     }
0161     emit repaintNeeded();
0162 }
0163 
0164 void GeoGraphicsScene::applyHighlight( const QVector< GeoDataPlacemark* > &selectedPlacemarks )
0165 {
0166     /**
0167      * First set the items, which were selected previously, to
0168      * use normal style
0169      */
0170     for ( GeoGraphicsItem *item: d->m_selectedItems ) {
0171         item->setHighlighted( false );
0172     }
0173 
0174     // Also clear the list to store the new selected items
0175     d->m_selectedItems.clear();
0176 
0177     /**
0178      * Process the placemark. which were under mouse
0179      * while clicking, and update corresponding graphics
0180      * items to use highlight style
0181      */
0182     for( const GeoDataPlacemark *placemark: selectedPlacemarks ) {
0183         for (auto tileIter = d->m_features.find(placemark); tileIter != d->m_features.end() && tileIter.key() == placemark; ++tileIter) {
0184             auto const & clickedItemsList = d->m_tiledItems.values(*tileIter);
0185             for (auto const & clickedItems: clickedItemsList) { //iterate through FeatureItemMap clickedItems (QHash)
0186                 for (auto iter = clickedItems.find(placemark); iter != clickedItems.end(); ++iter) {
0187                     if ( iter.key() == placemark ) {
0188                 const GeoDataObject *parent = placemark->parent();
0189                 if ( parent ) {
0190                     auto item = *iter;
0191                     if (const GeoDataDocument *doc = geodata_cast<GeoDataDocument>(parent)) {
0192                         QString styleUrl = placemark->styleUrl();
0193                         styleUrl.remove(QLatin1Char('#'));
0194                         if ( !styleUrl.isEmpty() ) {
0195                             GeoDataStyleMap const &styleMap = doc->styleMap( styleUrl );
0196                             GeoDataStyle::Ptr style = d->highlightStyle( doc, styleMap );
0197                             if ( style ) {
0198                                 d->selectItem( item );
0199                                 d->applyHighlightStyle( item, style );
0200                             }
0201                         }
0202 
0203                         /**
0204                             * If a placemark is using an inline style instead of a shared
0205                             * style ( e.g in case when theme file specifies the colorMap
0206                             * attribute ) then highlight it if any of the style maps have a
0207                             * highlight styleId
0208                             */
0209                         else {
0210                             for ( const GeoDataStyleMap &styleMap: doc->styleMaps() ) {
0211                                 GeoDataStyle::Ptr style = d->highlightStyle( doc, styleMap );
0212                                 if ( style ) {
0213                                     d->selectItem( item );
0214                                     d->applyHighlightStyle( item, style );
0215                                     break;
0216                                 }
0217                             }
0218                         }
0219                             }
0220                         }
0221                     }
0222                 }
0223             }
0224         }
0225     }
0226     emit repaintNeeded();
0227 }
0228 
0229 void GeoGraphicsScene::removeItem( const GeoDataFeature* feature )
0230 {
0231     for (auto tileIter = d->m_features.find(feature), end = d->m_features.end(); tileIter != end && tileIter.key() == feature;) {
0232         auto & tileList = d->m_tiledItems[*tileIter];
0233         auto iter = tileList.find(feature);
0234         if (iter != tileList.end()) {
0235             auto item = iter.value();
0236             tileIter = d->m_features.erase(tileIter);
0237             tileList.erase(iter);
0238             delete item;
0239         } else {
0240             ++tileIter;
0241         }
0242     }
0243 }
0244 
0245 void GeoGraphicsScene::clear()
0246 {
0247     for(auto const &list: d->m_tiledItems.values()) {
0248         qDeleteAll(list.values());
0249     }
0250     d->m_tiledItems.clear();
0251     d->m_features.clear();
0252 }
0253 
0254 void GeoGraphicsScene::addItem( GeoGraphicsItem* item )
0255 {
0256     // Select zoom level so that the object fit in single tile
0257     int zoomLevel;
0258     qreal north, south, east, west;
0259     item->latLonAltBox().boundaries( north, south, east, west );
0260     for(zoomLevel = item->minZoomLevel(); zoomLevel >= 0; zoomLevel--)
0261     {
0262         if( TileId::fromCoordinates( GeoDataCoordinates(west, north, 0), zoomLevel ) ==
0263             TileId::fromCoordinates( GeoDataCoordinates(east, south, 0), zoomLevel ) )
0264             break;
0265     }
0266 
0267     const TileId key = TileId::fromCoordinates( GeoDataCoordinates(west, north, 0), zoomLevel ); // same as GeoDataCoordinates(east, south, 0), see above
0268 
0269     auto & tileList = d->m_tiledItems[key];
0270     auto feature = item->feature();
0271     tileList.insertMulti(feature, item);
0272     d->m_features.insert(feature, key );
0273 }
0274 
0275 }
0276 
0277 #include "moc_GeoGraphicsScene.cpp"