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