OsmParser.cpp 9.33 KB
Newer Older
1
2
3
4
5
6
7
//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
8
// Copyright 2011      Thibaut Gridel <tgridel@free.fr>
9
10
// Copyright 2011      Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
// Copyright 2014      Bernhard Beschow <bbeschow@cs.tu-berlin.de>
11
// Copyright 2015      Dennis Nienhüser <nienhueser@kde.org>
12
//
13
14
15

#include "OsmParser.h"
#include "OsmElementDictionary.h"
16
#include "osm/OsmObjectManager.h"
17
#include "GeoDataDocument.h"
Dennis Nienhüser's avatar
Dennis Nienhüser committed
18
#include "GeoDataPoint.h"
19
#include "GeoDataStyle.h"
20
#include "GeoDataPolyStyle.h"
21
#include <MarbleZipReader.h>
22
#include "o5mreader.h"
23

24
#include <QColor>
25
#include <QFile>
26
27
#include <QFileInfo>
#include <QBuffer>
28
#include <QSet>
29

30
namespace Marble {
31

32
GeoDataDocument *OsmParser::parse(const QString &filename, QString &error)
33
34
{
    QFileInfo const fileInfo(filename);
35
36
    if (!fileInfo.exists() || !fileInfo.isReadable()) {
        error = QString("Cannot read file %1").arg(filename);
37
        return nullptr;
38
39
    }

40
    if (fileInfo.suffix() == QLatin1String("o5m")) {
41
        return parseO5m(filename, error);
42
    } else {
43
        return parseXml(filename, error);
44
45
46
    }
}

47
GeoDataDocument* OsmParser::parseO5m(const QString &filename, QString &error)
48
49
50
51
52
{
    O5mreader* reader;
    O5mreaderDataset data;
    O5mreaderIterateRet outerState, innerState;
    char *key, *value;
53
54
    // share string data on the heap at least for this file
    QSet<QString> stringPool;
55
56
57
58
59

    OsmNodes nodes;
    OsmWays ways;
    OsmRelations relations;
    QHash<uint8_t, QString> relationTypes;
60
61
62
    relationTypes[O5MREADER_DS_NODE] = QStringLiteral("node");
    relationTypes[O5MREADER_DS_WAY] = QStringLiteral("way");
    relationTypes[O5MREADER_DS_REL] = QStringLiteral("relation");
63
64
65
66
67
68
69

    auto file = fopen(filename.toStdString().c_str(), "rb");
    o5mreader_open(&reader, file);

    while( (outerState = o5mreader_iterateDataSet(reader, &data)) == O5MREADER_ITERATE_RET_NEXT) {
        switch (data.type) {
        case O5MREADER_DS_NODE:
70
71
72
73
74
        {
            OsmNode& node = nodes[data.id];
            node.osmData().setId(data.id);
            node.setCoordinates(GeoDataCoordinates(data.lon*1.0e-7, data.lat*1.0e-7,
                                                   0.0, GeoDataCoordinates::Degree));
75
            while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) {
76
77
78
                const QString keyString = *stringPool.insert(QString::fromUtf8(key));
                const QString valueString = *stringPool.insert(QString::fromUtf8(value));
                node.osmData().addTag(keyString, valueString);
79
            }
80
        }
81
82
            break;
        case O5MREADER_DS_WAY:
83
84
85
        {
            OsmWay &way = ways[data.id];
            way.osmData().setId(data.id);
86
87
            uint64_t nodeId;
            while ((innerState = o5mreader_iterateNds(reader, &nodeId)) == O5MREADER_ITERATE_RET_NEXT) {
88
                way.addReference(nodeId);
89
90
            }
            while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) {
91
92
93
                const QString keyString = *stringPool.insert(QString::fromUtf8(key));
                const QString valueString = *stringPool.insert(QString::fromUtf8(value));
                way.osmData().addTag(keyString, valueString);
94
            }
95
        }
96
97
            break;
        case O5MREADER_DS_REL:
98
99
100
        {
            OsmRelation &relation = relations[data.id];
            relation.osmData().setId(data.id);
101
102
103
104
            char *role;
            uint8_t type;
            uint64_t refId;
            while ((innerState = o5mreader_iterateRefs(reader, &refId, &type, &role)) == O5MREADER_ITERATE_RET_NEXT) {
105
106
                const QString roleString = *stringPool.insert(QString::fromUtf8(role));
                relation.addMember(refId, roleString, relationTypes[type]);
107
108
            }
            while ((innerState = o5mreader_iterateTags(reader, &key, &value)) == O5MREADER_ITERATE_RET_NEXT) {
109
110
111
                const QString keyString = *stringPool.insert(QString::fromUtf8(key));
                const QString valueString = *stringPool.insert(QString::fromUtf8(value));
                relation.osmData().addTag(keyString, valueString);
112
            }
113
        }
114
115
116
117
118
119
120
            break;
        }
    }

    fclose(file);
    error = reader->errMsg;
    o5mreader_close(reader);
121
    return createDocument(nodes, ways, relations);
122
123
}

124
GeoDataDocument* OsmParser::parseXml(const QString &filename, QString &error)
125
{
126
127
128
129
    QXmlStreamReader parser;
    QFile file;
    QBuffer buffer;
    QFileInfo fileInfo(filename);
130
    if (fileInfo.completeSuffix() == QLatin1String("osm.zip")) {
131
132
133
        MarbleZipReader zipReader(filename);
        if (zipReader.fileInfoList().size() != 1) {
            int const fileNumber = zipReader.fileInfoList().size();
134
            error = QStringLiteral("Unexpected number of files (%1) in %2").arg(fileNumber).arg(filename);
135
136
137
138
139
140
141
142
143
            return nullptr;
        }
        QByteArray const data = zipReader.fileData(zipReader.fileInfoList().first().filePath);
        buffer.setData(data);
        buffer.open(QBuffer::ReadOnly);
        parser.setDevice(&buffer);
    } else {
        file.setFileName(filename);
        if (!file.open(QFile::ReadOnly)) {
144
            error = QStringLiteral("Cannot open file %1").arg(filename);
145
146
147
            return nullptr;
        }
        parser.setDevice(&file);
148
149
    }

150
    OsmPlacemarkData* osmData(nullptr);
151
152
    QString parentTag;
    qint64 parentId(0);
153
154
    // share string data on the heap at least for this file
    QSet<QString> stringPool;
155
156
157
158
159
160
161
162
163
164
165
166
167
168

    OsmNodes m_nodes;
    OsmWays m_ways;
    OsmRelations m_relations;

    while (!parser.atEnd()) {
        parser.readNext();
        if (!parser.isStartElement()) {
            continue;
        }

        QStringRef const tagName = parser.name();
        if (tagName == osm::osmTag_node || tagName == osm::osmTag_way || tagName == osm::osmTag_relation) {
            parentTag = parser.name().toString();
169
            parentId = parser.attributes().value(QLatin1String("id")).toLongLong();
170
171
172
173
174
175
176
177
178
179
180
181
182

            if (tagName == osm::osmTag_node) {
                m_nodes[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes());
                m_nodes[parentId].parseCoordinates(parser.attributes());
                osmData = &m_nodes[parentId].osmData();
            } else if (tagName == osm::osmTag_way) {
                m_ways[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes());
                osmData = &m_ways[parentId].osmData();
            } else {
                Q_ASSERT(tagName == osm::osmTag_relation);
                m_relations[parentId].osmData() = OsmPlacemarkData::fromParserAttributes(parser.attributes());
                osmData = &m_relations[parentId].osmData();
            }
Dennis Nienhüser's avatar
Dennis Nienhüser committed
183
        } else if (osmData && tagName == osm::osmTag_tag) {
184
185
186
187
            const QXmlStreamAttributes &attributes = parser.attributes();
            const QString keyString = *stringPool.insert(attributes.value(QLatin1String("k")).toString());
            const QString valueString = *stringPool.insert(attributes.value(QLatin1String("v")).toString());
            osmData->addTag(keyString, valueString);
188
        } else if (tagName == osm::osmTag_nd && parentTag == osm::osmTag_way) {
189
            m_ways[parentId].addReference(parser.attributes().value(QLatin1String("ref")).toLongLong());
190
191
192
193
194
195
196
197
198
199
        } else if (tagName == osm::osmTag_member && parentTag == osm::osmTag_relation) {
            m_relations[parentId].parseMember(parser.attributes());
        } // other tags like osm, bounds ignored
    }

    if (parser.hasError()) {
        error = parser.errorString();
        return nullptr;
    }

200
    return createDocument(m_nodes, m_ways, m_relations);
201
202
}

203
GeoDataDocument *OsmParser::createDocument(OsmNodes &nodes, OsmWays &ways, OsmRelations &relations)
204
205
206
207
208
{
    GeoDataDocument* document = new GeoDataDocument;
    GeoDataPolyStyle backgroundPolyStyle;
    backgroundPolyStyle.setFill( true );
    backgroundPolyStyle.setOutline( false );
209
    backgroundPolyStyle.setColor(QStringLiteral("#f1eee8"));
210
211
    GeoDataStyle::Ptr backgroundStyle(new GeoDataStyle);
    backgroundStyle->setPolyStyle( backgroundPolyStyle );
212
    backgroundStyle->setId(QStringLiteral("background"));
213
214
    document->addStyle( backgroundStyle );

215
    QSet<qint64> usedNodes, usedWays;
216
    for(auto const &relation: relations) {
217
        relation.createMultipolygon(document, ways, nodes, usedNodes, usedWays);
218
    }
219
    for(auto id: usedWays) {
220
        ways.remove(id);
221
222
    }

223
    QHash<qint64, GeoDataPlacemark*> placemarks;
224
225
226
227
    for (auto iter=ways.constBegin(), end=ways.constEnd(); iter != end; ++iter) {
        auto placemark = iter.value().create(nodes, usedNodes);
        if (placemark) {
            document->append(placemark);
228
            placemarks[placemark->osmData().oid()] = placemark;
229
        }
230
231
    }

232
    for(auto id: usedNodes) {
233
234
235
        if (nodes[id].osmData().isEmpty()) {
            nodes.remove(id);
        }
236
237
    }

238
    for(auto const &node: nodes) {
239
240
241
242
243
        auto placemark = node.create();
        if (placemark) {
            document->append(placemark);
            placemarks[placemark->osmData().oid()] = placemark;
        }
244
    }
245

246
    for(auto const &relation: relations) {
247
        relation.createRelation(document, placemarks);
248
249
    }

250
251
252
    return document;
}

253
}