File indexing completed on 2025-01-05 03:59:16

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