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"