1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2015 Stanciu Marius-Valeriu <stanciumarius94@gmail.com>
4 //
5
6 // Self
7 #include "OsmTagEditorWidget_p.h"
8 #include "OsmTagEditorWidget.h"
9
10 // Marble
11 #include "GeoDataLineString.h"
12 #include "GeoDataPolygon.h"
13 #include "GeoDataPlacemark.h"
14 #include "GeoDataStyle.h"
15 #include "GeoDataIconStyle.h"
16 #include "OsmPlacemarkData.h"
17 #include "GeoDataExtendedData.h"
18 #include "GeoDataData.h"
19 #include "GeoDataGeometry.h"
20 #include "GeoDataPoint.h"
21 #include "StyleBuilder.h"
22
23 // Qt
24 #include <QTreeWidget>
25 #include <QObject>
26
27 namespace Marble
28 {
29
30 const QString OsmTagEditorWidgetPrivate::m_customTagAdderText = QObject::tr( "Add custom tag..." );
OsmTagEditorWidgetPrivate()31 OsmTagEditorWidgetPrivate::OsmTagEditorWidgetPrivate()
32 {
33 // nothing to do
34 }
35
~OsmTagEditorWidgetPrivate()36 OsmTagEditorWidgetPrivate::~OsmTagEditorWidgetPrivate()
37 {
38 // nothing to do
39 }
40
populateCurrentTagsList()41 void OsmTagEditorWidgetPrivate::populateCurrentTagsList()
42 {
43 // Name tag
44 if ( !m_placemark->name().isEmpty() ) {
45 QStringList itemText;
46
47 // "name" is a standard OSM tag, don't translate
48 itemText<< "name" << m_placemark->name();
49 QTreeWidgetItem *nameTag = new QTreeWidgetItem( itemText );
50 nameTag->setDisabled( true );
51 m_currentTagsList->addTopLevelItem( nameTag );
52 }
53
54 // Multipolygon type tag
55 if (geodata_cast<GeoDataPolygon>(m_placemark->geometry())) {
56 QStringList itemText;
57 // "type" is a standard OSM tag, don't translate
58 itemText<< "type" << "multipolygon";
59 QTreeWidgetItem *typeTag = new QTreeWidgetItem( itemText );
60 typeTag->setDisabled( true );
61 m_currentTagsList->addTopLevelItem( typeTag );
62 }
63
64 // Other tags
65 if( m_placemark->hasOsmData() ) {
66 const OsmPlacemarkData& osmData = m_placemark->osmData();
67 QHash< QString, QString>::const_iterator it = osmData.tagsBegin();
68 QHash< QString, QString>::const_iterator end = osmData.tagsEnd();
69 for ( ; it != end; ++it ) {
70 QTreeWidgetItem *tagItem = tagWidgetItem(OsmTag(it.key(), it.value()));
71 m_currentTagsList->addTopLevelItem( tagItem );
72 }
73 }
74
75 // Custom tag adder item
76 QTreeWidgetItem *adderItem = new QTreeWidgetItem();
77 adderItem->setText( 0, m_customTagAdderText );
78 adderItem->setForeground( 0, Qt::gray );
79 adderItem->setIcon(0, QIcon(QStringLiteral(":marble/list-add.png")));
80 adderItem->setFlags( adderItem->flags() | Qt::ItemIsEditable );
81 m_currentTagsList->addTopLevelItem( adderItem );
82 m_currentTagsList->resizeColumnToContents( 0 );
83 m_currentTagsList->resizeColumnToContents( 1 );
84
85
86 }
87
populatePresetTagsList()88 void OsmTagEditorWidgetPrivate::populatePresetTagsList()
89 {
90 QList<OsmTag> tags = recommendedTags();
91 for (const OsmTag &tag: tags) {
92 QTreeWidgetItem *tagItem = tagWidgetItem( tag );
93 m_recommendedTagsList->addTopLevelItem( tagItem );
94 }
95 }
96
tagWidgetItem(const OsmTag & tag)97 QTreeWidgetItem *OsmTagEditorWidgetPrivate::tagWidgetItem(const OsmTag &tag)
98 {
99 QStringList itemText;
100
101 itemText << tag.first;
102 itemText << (tag.second.isEmpty() ? QLatin1Char('<') + QObject::tr("value") + QLatin1Char('>') : tag.second);
103
104 QTreeWidgetItem *tagItem = new QTreeWidgetItem( itemText );
105
106 return tagItem;
107 }
108
recommendedTags() const109 QList<OsmTagEditorWidgetPrivate::OsmTag> OsmTagEditorWidgetPrivate::recommendedTags() const
110 {
111 static const QVector<OsmTag> additionalOsmTags = createAdditionalOsmTags();
112
113 QList<OsmTag> recommendedTags;
114
115 QStringList filter = generateTagFilter();
116
117 auto const osmTagMapping = StyleBuilder::osmTagMapping();
118 for (auto iter=osmTagMapping.begin(), end=osmTagMapping.end() ; iter != end; ++iter) {
119 if ( filter.contains( iter.key().first ) ) {
120 recommendedTags += iter.key();
121 }
122 }
123
124 for (const auto& additionalOsmTag: additionalOsmTags) {
125 if (filter.contains(additionalOsmTag.first)) {
126 recommendedTags += additionalOsmTag;
127 }
128 }
129
130 return recommendedTags;
131 }
132
133
generateTagFilter() const134 QStringList OsmTagEditorWidgetPrivate::generateTagFilter() const
135 {
136 // TO DO: implement more dynamic criteria for the filter
137 // based on https://taginfo.openstreetmap.org/ and https://wiki.openstreetmap.org/wiki/
138
139 // Contains all keys that should pass through the filter ( eg. { "amenity", "landuse", etc.. } )
140 QStringList filter;
141
142 QStringList tags, tagsAux;
143 OsmPlacemarkData osmData;
144 if ( m_placemark->hasOsmData() ) {
145 osmData = m_placemark->osmData();
146 }
147 else {
148 osmData = OsmPlacemarkData();
149 }
150
151 // Patterns in order of usefulness
152
153
154 // If the placemark is a node, and it doesn't already have any node-specific tags, recommend all node-specific tags
155 tags = QStringList() << "amenity=*" << "shop=*" << "transport=*" << "tourism=*" << "historic=*" << "power=*" << "barrier=*";
156 if (geodata_cast<GeoDataPoint>(m_placemark->geometry()) && !containsAny(osmData, tags)) {
157 addPattern( filter, osmData, tags );
158 }
159
160
161
162 // If the placemark is a way, and it doesn't already have any way-specific tags, recommend all way-specific tags
163 tags = QStringList() << "highway=*" << "waterway=*" << "railway=*";
164 if (geodata_cast<GeoDataLineString>(m_placemark->geometry()) && !containsAny(osmData, tags)) {
165 addPattern( filter, osmData, tags );
166 }
167
168
169
170 // If the placemark is a polygon, and it doesn't already have any polygon-specific tags, recommend all polygon-specific tags
171 tags = QStringList() << "landuse=*" << "leisure=*";
172 if (geodata_cast<GeoDataPolygon>(m_placemark->geometry()) && !containsAny(osmData, tags)) {
173 addPattern( filter, osmData, tags );
174 }
175
176
177
178 // If the placemark is a relation, recommend type=*
179 tags = QStringList() << "type=*";
180 if (m_placemark->extendedData().value(QStringLiteral("osmRelation")).value().toString() == QLatin1String("yes")) {
181 addPattern( filter, osmData, tags );
182 }
183
184
185
186 // If the placemark has type=route, recommend route=*, network=*, ref=*, operator=*
187 tags = QStringList() << "type=route";
188 tagsAux = QStringList() << "route=*" << "network=*" << "ref=*" << "operator=*";
189 if (containsAny(osmData, tags)) {
190 addPattern( filter, osmData, tagsAux );
191 }
192
193
194
195 // If the placemark has type=route_master, recommend route_master=*,
196 tags = QStringList() << "type=route_master";
197 tagsAux = QStringList() << "route_master=*";
198 if (containsAny(osmData, tags)) {
199 addPattern( filter, osmData, tagsAux );
200 }
201
202
203
204 // If the placemark has type=public_transport, recommend public_transport=*,
205 tags = QStringList() << "type=public_transport";
206 tagsAux = QStringList() << "public_transport=*";
207 if (containsAny(osmData, tags)) {
208 addPattern( filter, osmData, tagsAux );
209 }
210
211
212
213 // If the placemark has type=waterway, recommend waterway=*,
214 tags = QStringList() << "type=waterway";
215 tagsAux = QStringList() << "waterway=*";
216 if (containsAny(osmData, tags)) {
217 addPattern( filter, osmData, tagsAux );
218 }
219
220
221
222 // If the placemark has type=enforcement, recommend enforcement=*,
223 tags = QStringList() << "type=enforcement";
224 tagsAux = QStringList() << "enforcement=*";
225 if (containsAny(osmData, tags)) {
226 addPattern( filter, osmData, tagsAux );
227 }
228
229
230
231 // If the placemark has amenity=place_of_worship, recommend religion=*
232 tags = QStringList() << "amenity=place_of_worship";
233 tagsAux = QStringList() << "religion=*";
234 if (containsAny(osmData, tags)) {
235 addPattern( filter, osmData, tagsAux );
236 }
237
238
239
240 // If the placemark has amenity=toilets, recommend drinking_water=*, indoor=*
241 tags = QStringList() << "amenity=toilets";
242 tagsAux = QStringList() << "drinking_water=*" << "indoor=*";
243 if (containsAny(osmData, tags)) {
244 addPattern( filter, osmData, tagsAux );
245 }
246
247
248
249 // If the placemark has tourism=hostel, tourism=hotel or tourism=motel, recommend rooms=*, beds=*, wheelchair=*
250 tags = QStringList() << "tourism=hotel" << "tourism=hostel" << "tourism=motel";
251 tagsAux = QStringList() << "rooms=*" << "beds=*" << "wheelchair=*";
252 if (containsAny(osmData, tags)) {
253 addPattern( filter, osmData, tagsAux );
254 }
255
256
257
258 // If the placemark has tourism=*, shop=*, amenity=*, recommend website=*, email=*, fee=*
259 tags = QStringList() << "tourism=*" << "shop=*" << "amenity=*";
260 tagsAux = QStringList() << "website=*" << "email=*" << "fee=*";
261 if (containsAny(osmData, tags)) {
262 addPattern( filter, osmData, tagsAux );
263 }
264
265
266
267 // If the placemark has amenity=* shop=*, recommend building=*
268 tags = QStringList() << "amenity=*" << "shop=*";
269 tagsAux = QStringList() << "building=*";
270 if (containsAny(osmData, tags)) {
271 addPattern( filter, osmData, tagsAux );
272 }
273
274
275
276 // If the placemark has highway=*, recommend "lanes=*", "maxspeed=*", "oneway=*", "service=*", "bridge=*", "tunnel=*"
277 tags = QStringList() << "highway=*";
278 tagsAux = QStringList() << "lanes=*" << "maxspeed=*" << "maxheight=*" << "maxweight=*" << "abutters=*" << "oneway=*" << "service=*" << "bridge=*" << "tunnel=*";
279 if (geodata_cast<GeoDataLineString>(m_placemark->geometry()) && containsAny(osmData, tags)) {
280 addPattern( filter, osmData, tagsAux );
281 }
282
283
284
285 // If the placemark is a polygon, recommend "surface=*"
286 tags = QStringList() << "surface=*";
287 if (geodata_cast<GeoDataPolygon>(m_placemark->geometry())) {
288 addPattern( filter, osmData, tags );
289 }
290
291
292
293 // Always recommend these:
294 tags = QStringList() << "addr:street=*" << "addr:housenumber=*" << "addr:postcode=*" << "addr:country=*" << "access=*";
295 addPattern( filter, osmData, tags );
296
297
298 return filter;
299 }
300
containsAny(const OsmPlacemarkData & osmData,const QStringList & tags)301 bool OsmTagEditorWidgetPrivate::containsAny(const OsmPlacemarkData &osmData, const QStringList &tags)
302 {
303 for ( const QString &tag: tags ) {
304 const QStringList tagSplit = tag.split(QLatin1Char('='));
305
306 // Only "key=value" mappings should be checked
307 Q_ASSERT( tagSplit.size() == 2 );
308
309 QString key = tagSplit.at( 0 );
310 QString value = tagSplit.at( 1 );
311
312 if (value == QLatin1String("*") && osmData.containsTagKey(key)) {
313 return true;
314 }
315 else if (value != QLatin1String("*") && osmData.containsTag(key, value)) {
316 return true;
317 }
318 }
319 return false;
320 }
321
addPattern(QStringList & filter,const OsmPlacemarkData & osmData,const QStringList & tags)322 void OsmTagEditorWidgetPrivate::addPattern(QStringList &filter, const OsmPlacemarkData &osmData, const QStringList &tags)
323 {
324 for ( const QString &tag: tags ) {
325 const QStringList tagSplit = tag.split(QLatin1Char('='));
326 QString key = tagSplit.at( 0 );
327 if ( !osmData.containsTagKey( key ) ) {
328 filter << key;
329 }
330 }
331 }
332
createAdditionalOsmTags()333 QVector<OsmTagEditorWidgetPrivate::OsmTag> OsmTagEditorWidgetPrivate::createAdditionalOsmTags()
334 {
335 const QVector<OsmTag> additionalOsmTags = QVector<OsmTag>()
336
337 // Recommended for nodes
338 << OsmTag("power", "pole")
339 << OsmTag("power", "generator")
340 << OsmTag("barrier", "fence")
341 << OsmTag("barrier", "wall")
342 << OsmTag("barrier", "gate")
343
344 // Recommended for ways
345 << OsmTag("lanes", "")
346 << OsmTag("maxspeed", "")
347 << OsmTag("maxheight", "")
348 << OsmTag("maxweight", "")
349 << OsmTag("oneway", "yes")
350 << OsmTag("service", "driveway")
351 << OsmTag("service", "parking_aisle")
352 << OsmTag("service", "alley")
353 << OsmTag("tunnel", "yes")
354 << OsmTag("abutters", "commercial")
355 << OsmTag("abutters", "industrial")
356 << OsmTag("abutters", "mixed")
357 << OsmTag("abutters", "residential")
358
359 // Recommended for areas
360 << OsmTag("surface", "unpaved")
361 << OsmTag("surface", "paved")
362 << OsmTag("surface", "gravel")
363 << OsmTag("surface", "dirt")
364 << OsmTag("surface", "grass")
365
366 // Relations
367 << OsmTag("type", "route")
368 << OsmTag("type", "route_master")
369 << OsmTag("type", "public_transport")
370 << OsmTag("type", "destination_sign")
371 << OsmTag("type", "waterway")
372 << OsmTag("type", "enforcement")
373
374 // Relations: route
375 << OsmTag("route", "road")
376 << OsmTag("route", "bicycle")
377 << OsmTag("route", "foot")
378 << OsmTag("route", "hiking")
379 << OsmTag("route", "bus")
380 << OsmTag("route", "trolleybus")
381 << OsmTag("route", "ferry")
382 << OsmTag("route", "detour")
383 << OsmTag("route", "train")
384 << OsmTag("route", "tram")
385 << OsmTag("route", "mtb")
386 << OsmTag("route", "horse")
387 << OsmTag("route", "ski")
388 << OsmTag("roundtrip", "yes")
389 << OsmTag("network", "")
390 << OsmTag("ref", "")
391 << OsmTag("operator", "")
392
393 // Relations: route_master
394 << OsmTag("route_master", "train")
395 << OsmTag("route_master", "subway")
396 << OsmTag("route_master", "monorail")
397 << OsmTag("route_master", "tram")
398 << OsmTag("route_master", "bus")
399 << OsmTag("route_master", "trolleybus")
400 << OsmTag("route_master", "ferry")
401 << OsmTag("route_master", "bicycle")
402
403 // Relations: public_transport
404 << OsmTag("public_transport", "stop_area")
405 << OsmTag("public_transport", "stop_area_group")
406
407 // Relations: waterway
408 << OsmTag("waterway", "river")
409 << OsmTag("waterway", "stream")
410 << OsmTag("waterway", "canal")
411 << OsmTag("waterway", "drain")
412 << OsmTag("waterway", "ditch")
413
414 // Relations: enforcement
415 << OsmTag("enforcement", "maxheight")
416 << OsmTag("enforcement", "maxweight")
417 << OsmTag("enforcement", "maxspeed")
418 << OsmTag("enforcement", "mindistance")
419 << OsmTag("enforcement", "traffic_signals")
420 << OsmTag("enforcement", "check")
421 << OsmTag("enforcement", "access")
422 << OsmTag("enforcement", "toll")
423
424 // Others
425 << OsmTag("height", "")
426 << OsmTag("rooms", "")
427 << OsmTag("beds", "")
428 << OsmTag("wheelchair", "")
429 << OsmTag("website", "")
430 << OsmTag("email", "")
431 << OsmTag("fee", "")
432 << OsmTag("destination", "")
433 << OsmTag("indoor", "yes")
434
435 // Recommended for all
436 << OsmTag("addr:street", "")
437 << OsmTag("addr:housenumber", "")
438 << OsmTag("addr:postcode", "")
439 << OsmTag("addr:country", "")
440 << OsmTag("access", "private")
441 << OsmTag("access", "permissive");
442
443 return additionalOsmTags;
444 }
445
446 }
447