Marble

GeoGraphicsScene.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2011 Konstantin Oblaukhov <[email protected]>
4 //
5 
6 #include "GeoGraphicsScene.h"
7 
8 #include "GeoDataFeature.h"
9 #include "GeoDataLatLonAltBox.h"
10 #include "GeoDataStyle.h"
11 #include "GeoDataStyleMap.h"
12 #include "GeoDataPlacemark.h"
13 #include "GeoDataDocument.h"
14 #include "GeoGraphicsItem.h"
15 #include "TileId.h"
16 #include "TileCoordsPyramid.h"
17 #include "MarbleDebug.h"
18 
19 #include <QMap>
20 #include <QRect>
21 
22 namespace Marble
23 {
24 
25 class GeoGraphicsScenePrivate
26 {
27 public:
28  GeoGraphicsScene *q;
29  explicit GeoGraphicsScenePrivate(GeoGraphicsScene *parent) :
30  q(parent)
31  {
32  }
33 
34  ~GeoGraphicsScenePrivate()
35  {
36  q->clear();
37  }
38 
39  typedef QHash<const GeoDataFeature*,GeoGraphicsItem*> FeatureItemMap;
40  QHash<TileId, FeatureItemMap> m_tiledItems;
41  QMultiHash<const GeoDataFeature*, TileId> m_features; // multi hash because multi track and multi geometry insert multiple items
42 
43  // Stores the items which have been clicked;
44  QList<GeoGraphicsItem*> m_selectedItems;
45 
46  static GeoDataStyle::Ptr highlightStyle(const GeoDataDocument *document, const GeoDataStyleMap &styleMap);
47 
48  void selectItem( GeoGraphicsItem *item );
49  static void applyHighlightStyle(GeoGraphicsItem *item, const GeoDataStyle::Ptr &style);
50 };
51 
52 GeoDataStyle::Ptr GeoGraphicsScenePrivate::highlightStyle( const GeoDataDocument *document,
53  const GeoDataStyleMap &styleMap )
54 {
55  // @todo Consider QUrl parsing when external styles are suppported
56  QString highlightStyleId = styleMap.value(QStringLiteral("highlight"));
57  highlightStyleId.remove(QLatin1Char('#'));
58  if ( !highlightStyleId.isEmpty() ) {
59  GeoDataStyle::Ptr highlightStyle(new GeoDataStyle( *document->style(highlightStyleId) ));
60  return highlightStyle;
61  }
62  else {
63  return GeoDataStyle::Ptr();
64  }
65 }
66 
67 void GeoGraphicsScenePrivate::selectItem( GeoGraphicsItem* item )
68 {
69  m_selectedItems.append( item );
70 }
71 
72 void GeoGraphicsScenePrivate::applyHighlightStyle(GeoGraphicsItem* item, const GeoDataStyle::Ptr &highlightStyle )
73 {
74  item->setHighlightStyle( highlightStyle );
75  item->setHighlighted( true );
76 }
77 
79  QObject( parent ),
80  d( new GeoGraphicsScenePrivate(this) )
81 {
82 
83 }
84 
85 GeoGraphicsScene::~GeoGraphicsScene()
86 {
87  delete d;
88 }
89 
91 {
92  if ( box.west() > box.east() ) {
93  // Handle boxes crossing the IDL by splitting it into two separate boxes
94  GeoDataLatLonBox left;
95  left.setWest( -M_PI );
96  left.setEast( box.east() );
97  left.setNorth( box.north() );
98  left.setSouth( box.south() );
99 
100  GeoDataLatLonBox right;
101  right.setWest( box.west() );
102  right.setEast( M_PI );
103  right.setNorth( box.north() );
104  right.setSouth( box.south() );
105 
106  return items(left, zoomLevel) + items(right, zoomLevel);
107  }
108 
110  QRect rect;
111  qreal north, south, east, west;
112  box.boundaries( north, south, east, west );
113  TileId key;
114 
115  key = TileId::fromCoordinates( GeoDataCoordinates(west, north, 0), zoomLevel );
116  rect.setLeft( key.x() );
117  rect.setTop( key.y() );
118 
119  key = TileId::fromCoordinates( GeoDataCoordinates(east, south, 0), zoomLevel );
120  rect.setRight( key.x() );
121  rect.setBottom( key.y() );
122 
123  TileCoordsPyramid pyramid( 0, zoomLevel );
124  pyramid.setBottomLevelCoords( rect );
125 
126  for ( int level = pyramid.topLevel(); level <= pyramid.bottomLevel(); ++level ) {
127  QRect const coords = pyramid.coords( level );
128  int x1, y1, x2, y2;
129  coords.getCoords( &x1, &y1, &x2, &y2 );
130  for ( int x = x1; x <= x2; ++x ) {
131  bool const isBorderX = x == x1 || x == x2;
132  for ( int y = y1; y <= y2; ++y ) {
133  bool const isBorder = isBorderX || y == y1 || y == y2;
134  const TileId tileId = TileId( 0, level, x, y );
135  for(GeoGraphicsItem *object: d->m_tiledItems.value( tileId )) {
136  if (object->minZoomLevel() <= zoomLevel && object->visible()) {
137  if (!isBorder || object->latLonAltBox().intersects(box)) {
138  result.push_back(object);
139  }
140  }
141  }
142  }
143  }
144  }
145 
146  return result;
147 }
148 
150 {
151  return d->m_selectedItems;
152 }
153 
154 void GeoGraphicsScene::resetStyle()
155 {
156  for (auto const & items: d->m_tiledItems) {
157  for (auto item: items) {
158  item->resetStyle();
159  }
160  }
161  emit repaintNeeded();
162 }
163 
165 {
166  /**
167  * First set the items, which were selected previously, to
168  * use normal style
169  */
170  for ( GeoGraphicsItem *item: d->m_selectedItems ) {
171  item->setHighlighted( false );
172  }
173 
174  // Also clear the list to store the new selected items
175  d->m_selectedItems.clear();
176 
177  /**
178  * Process the placemark. which were under mouse
179  * while clicking, and update corresponding graphics
180  * items to use highlight style
181  */
182  for( const GeoDataPlacemark *placemark: selectedPlacemarks ) {
183  for (auto tileIter = d->m_features.find(placemark); tileIter != d->m_features.end() && tileIter.key() == placemark; ++tileIter) {
184  auto const & clickedItemsList = d->m_tiledItems.values(*tileIter);
185  for (auto const & clickedItems: clickedItemsList) { //iterate through FeatureItemMap clickedItems (QHash)
186  for (auto iter = clickedItems.find(placemark); iter != clickedItems.end(); ++iter) {
187  if ( iter.key() == placemark ) {
188  const GeoDataObject *parent = placemark->parent();
189  if ( parent ) {
190  auto item = *iter;
191  if (const GeoDataDocument *doc = geodata_cast<GeoDataDocument>(parent)) {
192  QString styleUrl = placemark->styleUrl();
193  styleUrl.remove(QLatin1Char('#'));
194  if ( !styleUrl.isEmpty() ) {
195  GeoDataStyleMap const &styleMap = doc->styleMap( styleUrl );
196  GeoDataStyle::Ptr style = d->highlightStyle( doc, styleMap );
197  if ( style ) {
198  d->selectItem( item );
199  d->applyHighlightStyle( item, style );
200  }
201  }
202 
203  /**
204  * If a placemark is using an inline style instead of a shared
205  * style ( e.g in case when theme file specifies the colorMap
206  * attribute ) then highlight it if any of the style maps have a
207  * highlight styleId
208  */
209  else {
210  for ( const GeoDataStyleMap &styleMap: doc->styleMaps() ) {
211  GeoDataStyle::Ptr style = d->highlightStyle( doc, styleMap );
212  if ( style ) {
213  d->selectItem( item );
214  d->applyHighlightStyle( item, style );
215  break;
216  }
217  }
218  }
219  }
220  }
221  }
222  }
223  }
224  }
225  }
226  emit repaintNeeded();
227 }
228 
230 {
231  for (auto tileIter = d->m_features.find(feature), end = d->m_features.end(); tileIter != end && tileIter.key() == feature;) {
232  auto & tileList = d->m_tiledItems[*tileIter];
233  auto iter = tileList.find(feature);
234  if (iter != tileList.end()) {
235  auto item = iter.value();
236  tileIter = d->m_features.erase(tileIter);
237  tileList.erase(iter);
238  delete item;
239  } else {
240  ++tileIter;
241  }
242  }
243 }
244 
246 {
247  for(auto const &list: d->m_tiledItems.values()) {
248  qDeleteAll(list.values());
249  }
250  d->m_tiledItems.clear();
251  d->m_features.clear();
252 }
253 
254 void GeoGraphicsScene::addItem( GeoGraphicsItem* item )
255 {
256  // Select zoom level so that the object fit in single tile
257  int zoomLevel;
258  qreal north, south, east, west;
259  item->latLonAltBox().boundaries( north, south, east, west );
260  for(zoomLevel = item->minZoomLevel(); zoomLevel >= 0; zoomLevel--)
261  {
262  if( TileId::fromCoordinates( GeoDataCoordinates(west, north, 0), zoomLevel ) ==
263  TileId::fromCoordinates( GeoDataCoordinates(east, south, 0), zoomLevel ) )
264  break;
265  }
266 
267  const TileId key = TileId::fromCoordinates( GeoDataCoordinates(west, north, 0), zoomLevel ); // same as GeoDataCoordinates(east, south, 0), see above
268 
269  auto & tileList = d->m_tiledItems[key];
270  auto feature = item->feature();
271  tileList.insertMulti(feature, item);
272  d->m_features.insert(feature, key );
273 }
274 
275 }
276 
277 #include "moc_GeoGraphicsScene.cpp"
A 3d point representation.
qreal east(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the eastern boundary of the bounding box.
void push_back(const T &value)
qreal north(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the northern boundary of the bounding box.
void addItem(GeoGraphicsItem *item)
Add an item to the GeoGraphicsScene Adds the item item to the GeoGraphicsScene.
void setRight(int x)
A base class for all geodata features.
void setBottom(int y)
qreal west(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the western boundary of the bounding box.
A base class for all geodata objects.
Definition: GeoDataObject.h:43
void getCoords(int *x1, int *y1, int *x2, int *y2) const const
void applyHighlight(const QVector< GeoDataPlacemark * > &)
bool isEmpty() const const
void removeItem(const GeoDataFeature *feature)
Remove all concerned items from the GeoGraphicsScene Removes all items which are associated with obje...
A container for Features, Styles and in the future Schemas.
A class that defines a 2D bounding box for geographic data.
a class to map different styles to one style
GeoGraphicsScene(QObject *parent=nullptr)
Creates a new instance of GeoGraphicsScene.
Binds a QML item to a specific geodetic location in screen coordinates.
a class representing a point of interest on the map
QList< GeoGraphicsItem * > selectedItems() const
Get the list of items which belong to a placemark that has been clicked.
QString & remove(int position, int n)
qreal south(GeoDataCoordinates::Unit unit=GeoDataCoordinates::Radian) const
Get the southern boundary of the bounding box.
void setTop(int y)
void clear()
Remove all items from the GeoGraphicsScene.
void setLeft(int x)
QObject * parent() const const
QList< GeoGraphicsItem * > items(const GeoDataLatLonBox &box, int maxZoomLevel) const
Get the list of items in the specified Box.
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Wed Oct 4 2023 04:09:41 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.