Marble

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

KDE's Doxygen guidelines are available online.