Commit be009fab authored by Volker Krause's avatar Volker Krause
Browse files

Add the OSM parsing code subset from KPublicTransport we need here

Not ideal, but the easiest way forward for now, while all this is still
evolving.
parent 357abb65
add_subdirectory(cli)
if (TARGET Qt5::Network AND NOT CMAKE_CROSSCOMPILING AND OSM_PLANET_DIR AND OsmTools_FOUND)
add_subdirectory(osm)
add_subdirectory(knowledgedb-generator)
endif()
add_subdirectory(vdv/certs)
......
add_library(KOSM STATIC
datatypes.cpp
element.cpp
geomath.cpp
xmlparser.cpp
)
target_include_directories(KOSM PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>")
target_link_libraries(KOSM PUBLIC Qt5::Core)
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "datatypes.h"
using namespace OSM;
void DataSet::addNode(Node &&node)
{
const auto it = std::lower_bound(nodes.begin(), nodes.end(), node);
if (it != nodes.end() && (*it).id == node.id) {
// do we need to merge something here?
return;
}
nodes.insert(it, std::move(node));
}
void DataSet::addWay(Way &&way)
{
const auto it = std::lower_bound(ways.begin(), ways.end(), way);
if (it != ways.end() && (*it).id == way.id) {
// already there?
return;
}
ways.insert(it, std::move(way));
}
void DataSet::addRelation(Relation &&rel)
{
const auto it = std::lower_bound(relations.begin(), relations.end(), rel);
if (it != relations.end() && (*it).id == rel.id) {
// do we need to merge something here?
return;
}
relations.insert(it, std::move(rel));
}
QString OSM::Node::url() const
{
return QStringLiteral("https://openstreetmap.org/node/") + QString::number(id);
}
bool OSM::Way::isClosed() const
{
return nodes.size() >= 2 && nodes.front() == nodes.back();
}
QString OSM::Way::url() const
{
return QStringLiteral("https://openstreetmap.org/way/") + QString::number(id);
}
QString OSM::Relation::url() const
{
return QStringLiteral("https://openstreetmap.org/relation/") + QString::number(id);
}
QDebug operator<<(QDebug debug, OSM::Coordinate coord)
{
QDebugStateSaver saver(debug);
debug.nospace() << '(' << coord.latF() << ',' << coord.lonF() << ')';
return debug;
}
QDebug operator<<(QDebug debug, OSM::BoundingBox bbox)
{
QDebugStateSaver saver(debug);
debug.nospace() << '[' << bbox.min.latF() << ',' << bbox.min.lonF() << '|' << bbox.max.latF() << ',' << bbox.max.lonF() << ']';
return debug;
}
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef OSM_DATATYPES_H
#define OSM_DATATYPES_H
#include <QDebug>
#include <QString>
#include <cstdint>
#include <vector>
namespace OSM {
/** OSM element identifier. */
typedef int64_t Id;
/** Coordinate, stored as 1e7 * degree to avoid floating point precision issues,
* and offset to unsigned values to make the z-order curve work.
* Can be in an invalid state with coordinates out of range, see isValid().
* @see https://en.wikipedia.org/wiki/Z-order_curve for the z-order curve stuff
*/
class Coordinate {
public:
Coordinate() = default;
explicit constexpr Coordinate(double lat, double lon)
: latitude((lat + 90.0) * 10'000'000)
, longitude((lon + 180.0) * 10'000'000)
{}
explicit constexpr Coordinate(uint32_t lat, uint32_t lon)
: latitude(lat)
, longitude(lon)
{}
/** Create a coordinate from a z-order curve index. */
explicit constexpr Coordinate(uint64_t z)
: latitude(0)
, longitude(0)
{
for (int i = 0; i < 32; ++i) {
latitude += (z & (1ull << (i * 2))) >> i;
longitude += (z & (1ull << (1 + i * 2))) >> (i + 1);
}
}
constexpr inline bool isValid() const
{
return latitude != std::numeric_limits<uint32_t>::max() && longitude != std::numeric_limits<uint32_t>::max();
}
/** Z-order curve value for this coordinate. */
constexpr inline uint64_t z() const
{
uint64_t z = 0;
for (int i = 0; i < 32; ++i) {
z += ((uint64_t)latitude & (1 << i)) << i;
z += ((uint64_t)longitude & (1 << i)) << (i + 1);
}
return z;
}
constexpr inline double latF() const
{
return (latitude / 10'000'000.0) - 90.0;
}
constexpr inline double lonF() const
{
return (longitude / 10'000'000.0) - 180.0;
}
uint32_t latitude = std::numeric_limits<uint32_t>::max();
uint32_t longitude = std::numeric_limits<uint32_t>::max();
};
/** Bounding box, ie. a pair of coordinates. */
class BoundingBox {
public:
constexpr BoundingBox() = default;
constexpr inline BoundingBox(Coordinate c1, Coordinate c2)
: min(c1)
, max(c2)
{}
constexpr inline bool isValid() const
{
return min.isValid() && max.isValid();
}
constexpr inline uint32_t width() const
{
return max.longitude - min.longitude;
}
constexpr inline uint32_t height() const
{
return max.latitude - min.latitude;
}
constexpr inline Coordinate center() const
{
return Coordinate(min.latitude + height() / 2, min.longitude + width() / 2);
}
Coordinate min;
Coordinate max;
};
constexpr inline BoundingBox unite(BoundingBox bbox1, BoundingBox bbox2)
{
if (!bbox1.isValid()) {
return bbox2;
}
if (!bbox2.isValid()) {
return bbox1;
}
BoundingBox ret;
ret.min.latitude = std::min(bbox1.min.latitude, bbox2.min.latitude);
ret.min.longitude = std::min(bbox1.min.longitude, bbox2.min.longitude);
ret.max.latitude = std::max(bbox1.max.latitude, bbox2.max.latitude);
ret.max.longitude = std::max(bbox1.max.longitude, bbox2.max.longitude);
return ret;
}
constexpr inline bool intersects(BoundingBox bbox1, BoundingBox bbox2)
{
return !(bbox2.min.latitude > bbox1.max.latitude || bbox2.max.latitude < bbox1.min.latitude
|| bbox2.min.longitude > bbox1.max.longitude || bbox2.max.longitude < bbox1.min.longitude);
}
constexpr inline bool contains(BoundingBox bbox, Coordinate coord)
{
return bbox.min.latitude <= coord.latitude && bbox.max.latitude >= coord.latitude
&& bbox.min.longitude <= coord.longitude && bbox.max.longitude >= coord.longitude;
}
constexpr inline uint32_t latitudeDistance(BoundingBox bbox1, BoundingBox bbox2)
{
return bbox1.max.latitude < bbox2.min.latitude ? bbox2.min.latitude - bbox1.max.latitude : bbox1.min.latitude - bbox2.max.latitude;
}
constexpr inline uint32_t longitudeDifference(BoundingBox bbox1, BoundingBox bbox2)
{
return bbox1.max.longitude < bbox2.min.longitude ? bbox2.min.longitude - bbox1.max.longitude : bbox1.min.longitude - bbox2.max.longitude;
}
/** An OSM element tag. */
class Tag {
public:
inline bool operator<(const Tag &other) const { return key < other.key; }
QString key;
QString value;
};
/** An OSM node. */
class Node {
public:
constexpr inline bool operator<(const Node &other) const { return id < other.id; }
QString url() const;
Id id;
Coordinate coordinate;
std::vector<Tag> tags;
};
/** An OSM way. */
class Way {
public:
constexpr inline bool operator<(const Way &other) const { return id < other.id; }
bool isClosed() const;
QString url() const;
Id id;
mutable BoundingBox bbox;
std::vector<Id> nodes;
std::vector<Tag> tags;
};
/** Element type. */
enum class Type : uint8_t {
Null,
Node,
Way,
Relation
};
/** A member in a relation. */
// TODO this has 7 byte padding, can we make this more efficient?
class Member {
public:
Id id;
QString role;
Type type;
};
/** An OSM relation. */
class Relation {
public:
constexpr inline bool operator<(const Relation &other) const { return id < other.id; }
QString url() const;
Id id;
mutable BoundingBox bbox;
std::vector<Member> members;
std::vector<Tag> tags;
};
/** A set of nodes, ways and relations. */
class DataSet {
public:
void addNode(Node &&node);
void addWay(Way &&way);
void addRelation(Relation &&rel);
std::vector<Node> nodes;
std::vector<Way> ways;
std::vector<Relation> relations;
};
/** Returns the tag value for @p key of @p elem. */
template <typename Elem>
inline QString tagValue(const Elem& elem, const QLatin1String &key)
{
const auto it = std::lower_bound(elem.tags.begin(), elem.tags.end(), key, [](const auto &lhs, const auto &rhs) { return lhs.key < rhs; });
if (it != elem.tags.end() && (*it).key == key) {
return (*it).value;
}
return {};
}
/** Inserts a new tag, or replaces an existing one with the same key. */
template <typename Elem>
inline void setTag(Elem &elem, Tag &&tag)
{
const auto it = std::lower_bound(elem.tags.begin(), elem.tags.end(), tag);
if (it == elem.tags.end() || (*it).key != tag.key) {
elem.tags.insert(it, std::move(tag));
} else {
(*it) = std::move(tag);
}
}
/** Inserts a new tag, or updates an existing one. */
template <typename Elem>
inline void setTagValue(Elem &elem, const QString &key, const QString &value)
{
Tag tag{ key, value };
setTag(elem, std::move(tag));
}
template <typename Elem>
inline bool operator<(const Elem &elem, Id id)
{
return elem.id < id;
}
}
QDebug operator<<(QDebug debug, OSM::Coordinate coord);
QDebug operator<<(QDebug debug, OSM::BoundingBox bbox);
#endif // OSM_DATATYPES_H
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "element.h"
using namespace OSM;
Coordinate Element::center() const
{
switch (type()) {
case Type::Null:
return {};
case Type::Node:
return node()->coordinate;
case Type::Way:
return way()->bbox.center();
case Type::Relation:
return relation()->bbox.center();
}
return {};
}
BoundingBox Element::boundingBox() const
{
switch (type()) {
case Type::Null:
return {};
case Type::Node:
return BoundingBox(node()->coordinate, node()->coordinate);
case Type::Way:
return way()->bbox;
case Type::Relation:
return relation()->bbox;
}
return {};
}
QString Element::tagValue(const QLatin1String &key) const
{
switch (type()) {
case Type::Null:
return {};
case Type::Node:
return OSM::tagValue(*node(), key);
case Type::Way:
return OSM::tagValue(*way(), key);
case Type::Relation:
return OSM::tagValue(*relation(), key);
}
return {};
}
QString OSM::Element::tagValue(const char *key) const
{
return tagValue(QLatin1String(key));
}
QString Element::url() const
{
switch (type()) {
case Type::Null:
return {};
case Type::Node:
return node()->url();
case Type::Way:
return way()->url();
case Type::Relation:
return relation()->url();
}
return {};
}
template <typename Iter>
static void appendNodesFromWay(const DataSet &dataSet, std::vector<const Node*> &nodes, const Iter& nodeBegin, const Iter &nodeEnd)
{
nodes.reserve(nodes.size() + std::distance(nodeBegin, nodeEnd));
for (auto it = nodeBegin; it != nodeEnd; ++it) {
const auto nodeIt = std::lower_bound(dataSet.nodes.begin(), dataSet.nodes.end(), (*it));
if (nodeIt == dataSet.nodes.end() || (*nodeIt).id != (*it)) {
continue;
}
nodes.push_back(&(*nodeIt));
}
}
static OSM::Id appendNextPath(const DataSet &dataSet, std::vector<const Node*> &nodes, OSM::Id startNode, std::vector<const Way*> &ways)
{
if (ways.empty()) {
return {};
}
for (auto it = std::next(ways.begin()); it != ways.end(); ++it) {
assert(!(*it)->nodes.empty()); // ensured in the caller
if ((*it)->nodes.front() == startNode) {
appendNodesFromWay(dataSet, nodes, (*it)->nodes.begin(), (*it)->nodes.end());
const auto lastNodeId = (*it)->nodes.back();
ways.erase(it);
return lastNodeId;
}
// path segments can also be backwards
if ((*it)->nodes.back() == startNode) {
appendNodesFromWay(dataSet, nodes, (*it)->nodes.rbegin(), (*it)->nodes.rend());
const auto lastNodeId = (*it)->nodes.front();
ways.erase(it);
return lastNodeId;
}
}
return {};
}
std::vector<const Node*> Element::outerPath(const DataSet &dataSet) const
{
switch (type()) {
case Type::Null:
return {};
case Type::Node:
return {node()};
case Type::Way:
{
std::vector<const Node*> nodes;
appendNodesFromWay(dataSet, nodes, way()->nodes.begin(), way()->nodes.end());
return nodes;
}
case Type::Relation:
{
if (tagValue("type") != QLatin1String("multipolygon")) {
return {};
}
// collect the relevant ways
std::vector<const Way*> ways;
for (const auto &member : relation()->members) {
if (member.role != QLatin1String("outer")) {
continue;
}
const auto it = std::lower_bound(dataSet.ways.begin(), dataSet.ways.end(), member.id);
if (it != dataSet.ways.end() && (*it).id == member.id && !(*it).nodes.empty()) {
ways.push_back(&(*it));
}
}
// stitch them together (there is no well-defined order)
std::vector<const Node*> nodes;
for (auto it = ways.begin(); it != ways.end();) {
assert(!(*it)->nodes.empty()); // ensured above
appendNodesFromWay(dataSet, nodes, (*it)->nodes.begin(), (*it)->nodes.end());
const auto startNode = (*it)->nodes.front();
auto lastNode = (*it)->nodes.back();
do {
lastNode = appendNextPath(dataSet, nodes, lastNode, ways);
} while (lastNode && lastNode != startNode);
it = ways.erase(it);
}
return nodes;
}
}
return {};
}
void Element::recomputeBoundingBox(const DataSet &dataSet)
{
switch (type()) {
case Type::Null:
case Type::Node:
break;
case Type::Way:
way()->bbox = std::accumulate(way()->nodes.begin(), way()->nodes.end(), OSM::BoundingBox(), [&dataSet](auto bbox, auto nodeId) {
const auto nodeIt = std::lower_bound(dataSet.nodes.begin(), dataSet.nodes.end(), nodeId);
if (nodeIt == dataSet.nodes.end() || (*nodeIt).id != nodeId) {
return bbox;
}
return OSM::unite(bbox, {(*nodeIt).coordinate, (*nodeIt).coordinate});
});
break;
case Type::Relation:
relation()->bbox = {};
for_each_member(dataSet, *relation(), [this, &dataSet](auto mem) {
mem.recomputeBoundingBox(dataSet);
relation()->bbox = OSM::unite(relation()->bbox, mem.boundingBox());
});
break;
}
}
/*
Copyright (C) 2020 Volker Krause <vkrause@kde.org>
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef OSM_ELEMENT_H
#define OSM_ELEMENT_H
#include "datatypes.h"