mapdata.cpp 9.05 KB
Newer Older
1
/*
2
    SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3

4
    SPDX-License-Identifier: LGPL-2.0-or-later
5
6
7
*/

#include "mapdata.h"
8
#include "levelparser_p.h"
9

10
11
12
13
14
#include "style/mapcssresult_p.h"
#include "style/mapcssstate_p.h"

#include <KOSMIndoorMap/MapCSSParser>
#include <KOSMIndoorMap/MapCSSStyle>
15

16
17
#include <osm/geomath.h>

18
19
#include <QTimeZone>

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using namespace KOSMIndoorMap;

MapLevel::MapLevel(int level)
    : m_level(level)
{
}

MapLevel::~MapLevel() = default;

bool MapLevel::operator<(const MapLevel &other) const
{
    return m_level > other.m_level;
}

bool MapLevel::operator==(const MapLevel &other) const
{
    return m_level == other.m_level;
}

bool MapLevel::hasName() const
{
    return !m_levelName.isEmpty();
}

QString MapLevel::name() const
{
    if (m_levelName.isEmpty()) {
        return QString::number(m_level / 10);
    }
    return m_levelName;
}

void MapLevel::setName(const QString &name)
{
    m_levelName = name;
}

bool MapLevel::isFullLevel() const
{
    return m_level % 10 == 0;
}

62
63
64
65
66
67
68
69
70
71
int MapLevel::fullLevelBelow() const
{
    return m_level < 0 ? (m_level - (10 + m_level % 10)) : (m_level - m_level % 10);
}

int MapLevel::fullLevelAbove() const
{
    return m_level < 0 ? (m_level - m_level % 10) : (m_level + (10 - m_level % 10));
}

72
73
74
75
76
int MapLevel::numericLevel() const
{
    return m_level;
}

77
78
79
80
81
82
83
84
85
86
87
namespace KOSMIndoorMap {
class MapDataPrivate {
public:
    OSM::DataSet m_dataSet;
    OSM::BoundingBox m_bbox;

    OSM::TagKey m_levelRefTag;
    OSM::TagKey m_nameTag;

    std::map<MapLevel, std::vector<OSM::Element>> m_levelMap;
    std::map<MapLevel, std::size_t> m_dependentElementCounts;
88
89
90

    QString m_regionCode;
    QTimeZone m_timeZone;
91
92
};
}
93

94
MapData::MapData()
95
    : d(std::make_shared<MapDataPrivate>())
96
97
{
}
98
99

MapData::MapData(const MapData&) = default;
Volker Krause's avatar
Volker Krause committed
100
MapData::MapData(MapData&&) = default;
101
102
MapData::~MapData() = default;

103
MapData& MapData::operator=(const MapData&) = default;
Volker Krause's avatar
Volker Krause committed
104
105
MapData& MapData::operator=(MapData&&) = default;

106
107
const OSM::DataSet& MapData::dataSet() const
{
108
    return d->m_dataSet;
109
110
}

111
112
bool MapData::isEmpty() const
{
113
114
115
116
117
118
    return !d || d->m_levelMap.empty();
}

bool MapData::operator==(const MapData &other) const
{
    return d == other.d;
119
120
}

Volker Krause's avatar
Volker Krause committed
121
122
OSM::DataSet& MapData::dataSet()
{
123
    return d->m_dataSet;
Volker Krause's avatar
Volker Krause committed
124
125
}

126
127
void MapData::setDataSet(OSM::DataSet &&dataSet)
{
128
    d->m_dataSet = std::move(dataSet);
Volker Krause's avatar
Volker Krause committed
129

130
131
    d->m_levelRefTag = d->m_dataSet.tagKey("level:ref");
    d->m_nameTag = d->m_dataSet.tagKey("name");
Volker Krause's avatar
Volker Krause committed
132

133
134
    d->m_levelMap.clear();
    d->m_bbox = {};
Volker Krause's avatar
Volker Krause committed
135
136

    processElements();
137
138
139
    filterLevels();
}

Volker Krause's avatar
Volker Krause committed
140
141
OSM::BoundingBox MapData::boundingBox() const
{
142
    return d->m_bbox;
Volker Krause's avatar
Volker Krause committed
143
144
}

145
146
void MapData::setBoundingBox(OSM::BoundingBox bbox)
{
147
    d->m_bbox = bbox;
148
149
}

150
151
const std::map<MapLevel, std::vector<OSM::Element>>& MapData::levelMap() const
{
152
    return d->m_levelMap;
153
154
}

Volker Krause's avatar
Volker Krause committed
155
void MapData::processElements()
156
{
157
158
159
160
161
162
163
    const auto levelTag = d->m_dataSet.tagKey("level");
    const auto repeatOnTag = d->m_dataSet.tagKey("repeat_on");
    const auto buildingLevelsTag = d->m_dataSet.tagKey("building:levels");
    const auto buildingMinLevelTag = d->m_dataSet.tagKey("building:min_level");
    const auto buildingLevelsUndergroundTag = d->m_dataSet.tagKey("building:levels:underground");
    const auto maxLevelTag = d->m_dataSet.tagKey("max_level");
    const auto minLevelTag = d->m_dataSet.tagKey("min_level");
164
    const auto countryTag = d->m_dataSet.tagKey("addr:country");
165
166
167
168
169
170

    MapCSSParser p;
    auto filter = p.parse(QStringLiteral(":/org.kde.kosmindoormap/assets/css/input-filter.mapcss"));
    if (p.hasError()) {
        qWarning() << p.errorMessage();
    }
171
    filter.compile(d->m_dataSet);
172
173
    MapCSSResult filterResult;

174
    OSM::for_each(d->m_dataSet, [&](auto e) {
175
176
177
178
179
        // discard everything here that is tag-less (and thus likely part of a higher-level geometry)
        if (!e.hasTags()) {
            return;
        }

180
181
182
183
184
185
186
187
        // attempt to detect the country we are in
        if (d->m_regionCode.isEmpty()) {
            const auto countryCode = e.tagValue(countryTag);
            if (countryCode.size() == 2 && std::isupper(countryCode[0]) && std::isupper(countryCode[1])) {
                d->m_regionCode = QString::fromUtf8(countryCode);
            }
        }

188
        // apply the input filter, anything that explicitly got opacity 0 will be discarded
189
        bool isDependentElement = false;
190
191
192
193
194
195
196
197
        MapCSSState filterState;
        filterState.element = e;
        filter.evaluate(filterState, filterResult);
        if (auto prop = filterResult.declaration(MapCSSDeclaration::Opacity)) {
            if (prop->doubleValue() == 0.0) {
                qDebug() << "input filter dropped" << e.url();
                return;
            }
198
199
200
201
202
203
            // anything that doesn't work on its own is a "dependent element"
            // we discard levels only containing dependent elements, but we retain all of them if the
            // level contains an element we are sure about that we can display it
            if (prop->doubleValue() < 1.0) {
                isDependentElement = true;
            }
204
205
        }

Volker Krause's avatar
Volker Krause committed
206
        // bbox computation
207
208
        e.recomputeBoundingBox(d->m_dataSet);
        d->m_bbox = OSM::unite(e.boundingBox(), d->m_bbox);
Volker Krause's avatar
Volker Krause committed
209

210
        // multi-level building element
Volker Krause's avatar
Volker Krause committed
211
212
        // we handle this first, before level=, as level is often used instead
        // of building:min_level in combination with building:level
213
        const auto buildingLevels = e.tagValue(buildingLevelsTag, maxLevelTag).toInt();
214
        if (buildingLevels > 0) {
215
216
            const auto startLevel = e.tagValue(buildingMinLevelTag, levelTag, minLevelTag).toInt();
            qDebug() << startLevel << buildingLevels << e.url();
217
218
219
220
221
222
223
224
            for (auto i = startLevel; i < buildingLevels; ++i) {
                addElement(i * 10, e, true);
            }
        }
        const auto undergroundLevels = e.tagValue(buildingLevelsUndergroundTag).toUInt();
        for (auto i = undergroundLevels; i > 0; --i) {
            addElement(-i * 10, e, true);
        }
Volker Krause's avatar
Volker Krause committed
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
        if (buildingLevels > 0 || undergroundLevels > 0) {
            return;
        }

        // element with explicit level specified
        auto level = e.tagValue(levelTag);
        if (level.isEmpty()) {
            level = e.tagValue(repeatOnTag);
        }
        if (!level.isEmpty()) { // level-less -> outdoor
            LevelParser::parse(std::move(level), e, [this, isDependentElement](int level, OSM::Element e) {
                addElement(level, e, isDependentElement);
            });
            return;
        }
240

241
        // no level information available
242
        d->m_levelMap[MapLevel{}].push_back(e);
Volker Krause's avatar
Volker Krause committed
243
        if (isDependentElement) {
244
            d->m_dependentElementCounts[MapLevel{}]++;
245
246
247
248
        }
    });
}

249
void MapData::addElement(int level, OSM::Element e, bool isDependentElement)
250
251
{
    MapLevel l(level);
252
253
    auto it = d->m_levelMap.find(l);
    if (it == d->m_levelMap.end()) {
254
        l.setName(levelName(e));
255
        d->m_levelMap[l] = {e};
256
257
258
259
260
261
262
    } else {
        if (!(*it).first.hasName()) {
            // name does not influence op< behavior, so modifying the key here is safe
            const_cast<MapLevel&>((*it).first).setName(levelName(e));
        }
        (*it).second.push_back(e);
    }
263
    if (isDependentElement) {
264
        d->m_dependentElementCounts[l]++;
265
    }
266
267
}

268
static bool isPlausibleLevelName(const QByteArray &s)
269
{
270
    return !s.isEmpty() && !s.contains(';');
271
272
}

273
274
QString MapData::levelName(OSM::Element e)
{
275
    const auto n = e.tagValue(d->m_levelRefTag);
276
    if (isPlausibleLevelName(n)) {
277
        return QString::fromUtf8(n);
278
279
280
281
    }

    if (e.type() == OSM::Type::Relation) {
        const auto isLevelRel = std::all_of(e.relation()->members.begin(), e.relation()->members.end(), [](const auto &mem) {
282
            return std::strcmp(mem.role().name(), "shell") == 0 || std::strcmp(mem.role().name(), "buildingpart") == 0;
283
284
        });
        if (isLevelRel) {
285
            const auto n = e.tagValue(d->m_nameTag);
286
            if (isPlausibleLevelName(n)) {
287
                return QString::fromUtf8(n);
288
            }
289
290
291
292
        }
    }

    return {};
293
294
295
296
}

void MapData::filterLevels()
{
297
    // remove all levels that don't contain something we are sure would make a meaningful output
298
299
300
    for (auto it = d->m_levelMap.begin(); it != d->m_levelMap.end();) {
        if (d->m_dependentElementCounts[(*it).first] == (*it).second.size()) {
            it = d->m_levelMap.erase(it);
301
302
303
304
        } else {
            ++it;
        }
    }
305
    d->m_dependentElementCounts.clear();
306
}
307
308
309
310
311
312
313
314
315
316
317

QPointF MapData::center() const
{
    return QPointF(d->m_bbox.center().lonF(), d->m_bbox.center().latF());
}

float MapData::radius() const
{
    return std::max(OSM::distance(d->m_bbox.center(), d->m_bbox.min), OSM::distance(d->m_bbox.center(), d->m_bbox.max));
}

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
QString MapData::regionCode() const
{
    return d->m_regionCode;
}

void MapData::setRegionCode(const QString &regionCode)
{
    d->m_regionCode = regionCode;
}

QTimeZone MapData::timeZone() const
{
    return d->m_timeZone;
}

void MapData::setTimeZone(const QTimeZone &tz)
{
    d->m_timeZone = tz;
}

338
339
340
341
342
QString MapData::timeZoneId() const
{
    return QString::fromUtf8(d->m_timeZone.id());
}

343
#include "moc_mapdata.cpp"