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"