diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3435792 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/osmparser"] + path = lib/osmparser + url = https://github.com/Lauchmelder23/OSMParser diff --git a/include/multipolygon.hpp b/include/multipolygon.hpp index 654e543..c86a836 100644 --- a/include/multipolygon.hpp +++ b/include/multipolygon.hpp @@ -10,7 +10,7 @@ struct SDL_Renderer; class Multipolygon { public: - Multipolygon(const std::shared_ptr& relation, int width, int height, osmp::Bounds bounds); + Multipolygon(const osmp::Relation& relation, int width, int height, const osmp::Bounds& bounds); void SetColor(int r, int g, int b); void Draw(SDL_Renderer* renderer); diff --git a/lib/osmparser b/lib/osmparser new file mode 160000 index 0000000..3d3abac --- /dev/null +++ b/lib/osmparser @@ -0,0 +1 @@ +Subproject commit 3d3abacee8242c08b350ca068dee82b95a102917 diff --git a/lib/osmparser/CMakeLists.txt b/lib/osmparser/CMakeLists.txt deleted file mode 100644 index e8bf4ac..0000000 --- a/lib/osmparser/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.10) - -find_package(tinyxml2 CONFIG REQUIRED) - -file(GLOB_RECURSE CPP_FILES - "src/*.cpp" -) - -file(GLOB_RECURSE HPP_FILES - "include/*.hpp" -) - -get_target_property(TINYXML2_INCLUDE_DIR tinyxml2::tinyxml2 INTERFACE_INCLUDE_DIRECTORIES) - -add_library(osmp STATIC - ${CPP_FILES} -) - -target_include_directories(osmp PUBLIC - include - ${TINYXML2_INCLUDE_DIR} -) - -target_link_libraries(osmp PRIVATE - tinyxml2::tinyxml2 -) \ No newline at end of file diff --git a/lib/osmparser/include/osmimember.hpp b/lib/osmparser/include/osmimember.hpp deleted file mode 100644 index b426f72..0000000 --- a/lib/osmparser/include/osmimember.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include - -namespace osmp -{ - class Object; - - class IMember - { - public: - enum class Type { - NODE, WAY, RELATION - }; - - public: - IMember(const IMember& other) = delete; - virtual ~IMember() {} - - IMember::Type GetType() const; - - const std::vector& GetTags() const; - size_t GetTagsSize() const; - const Tag& GetTag(size_t index) const; - std::string GetTag(const std::string& key) const; - - protected: - IMember(const tinyxml2::XMLElement* element, Object* parent, IMember::Type type); - - protected: - IMember::Type type; - Object* parent; - - std::vector tags; - // std::map tags; - - public: - uint64_t id; - std::string user; - unsigned int uid; - bool visible; - std::string version; - unsigned int changeset; - std::string timestamp; - }; -} \ No newline at end of file diff --git a/lib/osmparser/include/osmnode.hpp b/lib/osmparser/include/osmnode.hpp deleted file mode 100644 index 76d7627..0000000 --- a/lib/osmparser/include/osmnode.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include - -#include "util.hpp" -#include -#include - -namespace osmp -{ - class Object; - - class Node : public IMember - { - public: - Node(const tinyxml2::XMLElement* xml, Object* parent); - - public: - double lat, lon; - }; -} \ No newline at end of file diff --git a/lib/osmparser/include/osmobject.hpp b/lib/osmparser/include/osmobject.hpp deleted file mode 100644 index 9870fcd..0000000 --- a/lib/osmparser/include/osmobject.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once -#include -#include -#include -#include - -#include - -namespace osmp -{ - class Node; - class Way; - class Relation; - - class Object - { - public: - Object(const std::string& file); - ~Object(); - - std::vector> GetNodes() const; - size_t GetNodesSize() const; - std::shared_ptr GetNode(uint64_t id) const; - - std::vector> GetWays() const; - size_t GetWaysSize() const; - std::shared_ptr GetWay(uint64_t id) const; - - std::vector> GetRelations() const; - size_t GetRelationsSize() const; - std::shared_ptr GetRelation(uint64_t id) const; - - public: - const std::string version; - const std::string generator; - - Bounds bounds; - - private: - std::map> nodes; - std::map> ways; - std::map> relations; - }; -} \ No newline at end of file diff --git a/lib/osmparser/include/osmp.hpp b/lib/osmparser/include/osmp.hpp deleted file mode 100644 index a0ea337..0000000 --- a/lib/osmparser/include/osmp.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include \ No newline at end of file diff --git a/lib/osmparser/include/osmrelation.hpp b/lib/osmparser/include/osmrelation.hpp deleted file mode 100644 index c0272a2..0000000 --- a/lib/osmparser/include/osmrelation.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once -#include -#include - -#include -#include -#include - -namespace osmp -{ - class Object; - - class Relation : public IMember - { - public: - typedef struct sMember { - std::shared_ptr member; - std::string role; - } Member; - - public: - Relation(const tinyxml2::XMLElement* xml, Object* parent); - - std::string GetRelationType(); - - const std::vector& GetNodes() const; - size_t GetNodesSize() const; - const Member& GetNode(size_t index) const; - - const std::vector& GetWays() const; - size_t GetWaysSize() const; - const Member& GetWay(size_t index) const; - - bool HasNullMembers() const { return hasNullMembers; } - - private: - std::string relationType; - bool hasNullMembers; - - std::vector nodes; - std::vector ways; - }; -} \ No newline at end of file diff --git a/lib/osmparser/include/osmtag.hpp b/lib/osmparser/include/osmtag.hpp deleted file mode 100644 index a4e1f22..0000000 --- a/lib/osmparser/include/osmtag.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include - -namespace osmp -{ - enum class TagKey { - NONE, - AERIALWAY, AEROWAY, AMENITY, BARRIER, BOUNDARY, - BUILDING, CRAFT, EMERGENCY, GEOLOGICAL, HEALTHCARE, - HIGHWAY, HISTORIC, LANDUSE, LEISURE, MANMADE, MILITARY, - NATURAL, OFFICE, PLACE, POWER, PUBLIC_TRANSPORT, - RAILWAY, ROUTE, SHOP, SPORT, TELECOM, TOURISM, WATER, WATERWAY - }; - - /* - enum class TagValue { - NONE, - - BUILDING_APARTMENTS, BUILDING_BUNGALOW, BUILDING_CABIN, BUILDING_DETACHED, BUILDING_DORMITORY, BUILDING_FARM, BUILDING_GER, - BUILDING_HOTEL, BUILDING_HOUSE, BUILDING_HOUSEBOAT, BUILDING_RESIDENTIAL, BUILDING_SEMIDETACHED_HOUSE, BUILDING_STATIC_CARAVAN, - BUILDING_TERRACE, BUILDING_COMMERCIAL, BUILDING_INDUSTRIAL, BUILDING_KIOSK, BUILDING_OFFICE, BUILDING_RETAIL, BUILDING_SUPERMARKET, - BUILDING_WAREHOUSE, BUILDING_CATHEDRAL, BUILDING_CHAPEL, BUILDING_CHURCH, BUILDING_MONASTERY, BUILDING_MOSQUE, BUILDING_PRESBYTERY, - BUILDING_RELIGIOUS, BUILDING_SHRINE, BUILDING_SYNAGOGUE, BUILDING_TEMPLE, BUILDING_BAKEHOUSE, BUILDING_CIVIC, BUILDING_FIRE_STATION, - BUILDING_GOVERNMENT, BUILDING_HOSPITAL, BUILDING_PUBLIC, BUILDING_TOILETS, BUILDING_TRAIN_STATION, BUILDING_TRANSPORTATION, - BUILDING_KINDERGARTEN, BUILDING_SCHOOL, BUILDING_UNIVERSITY - - }; - */ - - typedef struct sTag - { - std::string k; // TODO: Should/could be an enum - std::string v; - } Tag; -} diff --git a/lib/osmparser/include/osmway.hpp b/lib/osmparser/include/osmway.hpp deleted file mode 100644 index 2813075..0000000 --- a/lib/osmparser/include/osmway.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include -#include - -#include -#include -#include - -namespace osmp -{ - class Object; - class Node; - - class Way : public IMember - { - public: - Way(const tinyxml2::XMLElement* way_elem, Object* parent); - - const std::vector>& GetNodes() const; - size_t GetNodesSize() const; - const std::shared_ptr& GetNode(size_t index) const; - - public: - bool area, closed; // Closed := Startpoint = endpoint, Area := Closed AND certain conditions are not met - - private: - std::vector> nodes; - }; -} \ No newline at end of file diff --git a/lib/osmparser/include/util.hpp b/lib/osmparser/include/util.hpp deleted file mode 100644 index 403d8e4..0000000 --- a/lib/osmparser/include/util.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -namespace tinyxml2 -{ - class XMLElement; -} - -namespace osmp -{ - typedef struct sBounds - { - double minlat, minlon, maxlat, maxlon; - } Bounds; - - std::string GetSafeAttributeString(const tinyxml2::XMLElement* elem, const std::string& name); - double GetSafeAttributeFloat(const tinyxml2::XMLElement* elem, const std::string& name); - uint64_t GetSafeAttributeUint64(const tinyxml2::XMLElement* elem, const std::string& name); - bool GetSafeAttributeBool(const tinyxml2::XMLElement* elem, const std::string& name); -} \ No newline at end of file diff --git a/lib/osmparser/src/osmimember.cpp b/lib/osmparser/src/osmimember.cpp deleted file mode 100644 index e038607..0000000 --- a/lib/osmparser/src/osmimember.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include -#include - -namespace xml = tinyxml2; - -namespace osmp -{ - IMember::IMember(const xml::XMLElement* element, Object* parent, IMember::Type type) : - type(type), parent(parent) - { - // Get Attribute - id = GetSafeAttributeUint64(element, "id"); - user = GetSafeAttributeString(element, "user"); - uid = GetSafeAttributeUint64(element, "uid"); - visible = GetSafeAttributeBool(element, "visible"); - version = GetSafeAttributeString(element, "version"); - changeset = GetSafeAttributeUint64(element, "changeset"); - timestamp = GetSafeAttributeString(element, "timestamp"); - - const xml::XMLElement* tag_element = element->FirstChildElement("tag"); - while (tag_element != nullptr) - { - tags.push_back({ - GetSafeAttributeString(tag_element, "k"), - GetSafeAttributeString(tag_element, "v"), - }); - - tag_element = tag_element->NextSiblingElement("tag"); - } - } - - IMember::Type IMember::GetType() const - { - return type; - } - - const std::vector& IMember::GetTags() const - { - return tags; - } - - size_t IMember::GetTagsSize() const - { - return tags.size(); - } - - const Tag& IMember::GetTag(size_t index) const - { - return tags[index]; - } - - std::string IMember::GetTag(const std::string& key) const - { - for (const Tag& tag : tags) - { - if (tag.k == key) - { - return tag.v; - } - } - - return ""; - } -} diff --git a/lib/osmparser/src/osmnode.cpp b/lib/osmparser/src/osmnode.cpp deleted file mode 100644 index 41a0efa..0000000 --- a/lib/osmparser/src/osmnode.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include - -#include - -namespace xml = tinyxml2; - -namespace osmp -{ - Node::Node(const tinyxml2::XMLElement* node_elem, Object* parent) : - IMember(node_elem, parent, IMember::Type::NODE) - { - // Get Attribute - lat = GetSafeAttributeFloat(node_elem, "lat"); - lon = GetSafeAttributeFloat(node_elem, "lon"); - } -} \ No newline at end of file diff --git a/lib/osmparser/src/osmobject.cpp b/lib/osmparser/src/osmobject.cpp deleted file mode 100644 index fca53bb..0000000 --- a/lib/osmparser/src/osmobject.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include - -namespace xml = tinyxml2; - -namespace osmp -{ - Object::Object(const std::string& file) : - bounds({ 0.0f, 0.0f, 0.0f, 0.0f }) - { - xml::XMLDocument doc; - xml::XMLError result = doc.LoadFile(file.c_str()); - if (result != xml::XML_SUCCESS) - { - std::cerr << "Error: " << result << std::endl; - return; - } - - xml::XMLElement* root = doc.FirstChildElement(); - - // Get bounds - xml::XMLElement* bounds_elem = root->FirstChildElement("bounds"); - bounds = { - GetSafeAttributeFloat(bounds_elem, "minlat"), - GetSafeAttributeFloat(bounds_elem, "minlon"), - GetSafeAttributeFloat(bounds_elem, "maxlat"), - GetSafeAttributeFloat(bounds_elem, "maxlon") - }; - - // Get nodes - xml::XMLElement* node_elem = root->FirstChildElement("node"); - while (node_elem != nullptr) - { - std::shared_ptr new_node = std::make_shared(node_elem, this); - nodes.insert(std::make_pair(new_node->id, new_node)); - - node_elem = node_elem->NextSiblingElement("node"); - } - - // Get ways - xml::XMLElement* way_elem = root->FirstChildElement("way"); - while (way_elem != nullptr) - { - std::shared_ptr new_way = std::make_shared(way_elem, this); - ways.insert(std::make_pair(new_way->id, new_way)); - - way_elem = way_elem->NextSiblingElement("way"); - } - - // Get relations - xml::XMLElement* relation_elem = root->FirstChildElement("relation"); - while (relation_elem != nullptr) - { - std::shared_ptr new_way = std::make_shared(relation_elem, this); - relations.insert(std::make_pair(new_way->id, new_way)); - - relation_elem = relation_elem->NextSiblingElement("relation"); - } - } - - Object::~Object() - { - - } - - std::vector> Object::GetNodes() const - { - std::vector> vecNodes; - for (std::map>::const_iterator it = nodes.begin(); it != nodes.end(); it++) - vecNodes.push_back(it->second); - - return vecNodes; - } - - size_t Object::GetNodesSize() const - { - return nodes.size(); - } - - std::shared_ptr Object::GetNode(uint64_t id) const - { - std::map>::const_iterator node = nodes.find(id); - if (node != nodes.end()) - return node->second; - - return nullptr; - } - - std::vector> Object::GetWays() const - { - std::vector> vecWays; - for (std::map>::const_iterator it = ways.begin(); it != ways.end(); it++) - vecWays.push_back(it->second); - - return vecWays; - } - - size_t Object::GetWaysSize() const - { - return ways.size(); - } - - std::shared_ptr Object::GetWay(uint64_t id) const - { - std::map>::const_iterator way = ways.find(id); - if (way != ways.end()) - return way->second; - - return nullptr; - } - - std::vector> Object::GetRelations() const - { - std::vector> vecRelations; - for (std::map>::const_iterator it = relations.begin(); it != relations.end(); it++) - vecRelations.push_back(it->second); - - return vecRelations; - } - - size_t Object::GetRelationsSize() const - { - return relations.size(); - } - - std::shared_ptr Object::GetRelation(uint64_t id) const - { - std::map>::const_iterator relation = relations.find(id); - if (relation != relations.end()) - return relation->second; - - return nullptr; - } -} \ No newline at end of file diff --git a/lib/osmparser/src/osmrelation.cpp b/lib/osmparser/src/osmrelation.cpp deleted file mode 100644 index 87320e7..0000000 --- a/lib/osmparser/src/osmrelation.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include - -#include - -#include -#include -#include -#include - -namespace xml = tinyxml2; - -namespace osmp -{ - Relation::Relation(const xml::XMLElement* xml, Object* parent) : - IMember(xml, parent, IMember::Type::RELATION), hasNullMembers(false) - { - const xml::XMLElement* member_element = xml->FirstChildElement("member"); - while (member_element != nullptr) - { - std::string memberType = GetSafeAttributeString(member_element, "type"); - uint64_t ref = GetSafeAttributeUint64(member_element, "ref"); - std::string role = GetSafeAttributeString(member_element, "role"); - - std::shared_ptr member = nullptr; - if (memberType == "node") { - member = parent->GetNode(ref); - nodes.push_back({ member, role }); - } - else if (memberType == "way") { - member = parent->GetWay(ref); - if (member == nullptr) { - hasNullMembers = true; - } - ways.push_back({ member, role }); - } - - member_element = member_element->NextSiblingElement("member"); - } - } - - std::string Relation::GetRelationType() - { - return GetTag("type"); - } - - const std::vector& Relation::GetNodes() const - { - return nodes; - } - - size_t Relation::GetNodesSize() const - { - return nodes.size(); - } - - const Relation::Member& Relation::GetNode(size_t index) const - { - return nodes[index]; - } - - const std::vector& Relation::GetWays() const - { - return ways; - } - - size_t Relation::GetWaysSize() const - { - return ways.size(); - } - - const Relation::Member& Relation::GetWay(size_t index) const - { - return ways[index]; - } -} \ No newline at end of file diff --git a/lib/osmparser/src/osmway.cpp b/lib/osmparser/src/osmway.cpp deleted file mode 100644 index 524d9f6..0000000 --- a/lib/osmparser/src/osmway.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include - -#include -#include -#include - -namespace xml = tinyxml2; - -namespace osmp -{ - Way::Way(const tinyxml2::XMLElement* way_elem, Object* parent) : - IMember(way_elem, parent, IMember::Type::WAY) - { - area = GetSafeAttributeBool(way_elem, "area"); - closed = false; - - const xml::XMLElement* nd_elem = way_elem->FirstChildElement("nd"); - while (nd_elem != nullptr) - { - nodes.push_back( - parent->GetNode(GetSafeAttributeUint64(nd_elem, "ref")) - ); - - nd_elem = nd_elem->NextSiblingElement("nd"); - } - - if (nodes.front() == nodes.back()) - { - closed = true; - - if (!area && GetTag("barrier") == "" && GetTag("highway") == "") // this code sucks, it can be done better - area = true; - } - } - - const std::vector>& Way::GetNodes() const - { - return nodes; - } - - size_t Way::GetNodesSize() const - { - return nodes.size(); - } - - const std::shared_ptr& Way::GetNode(size_t index) const - { - return nodes[index]; - } -} \ No newline at end of file diff --git a/lib/osmparser/src/util.cpp b/lib/osmparser/src/util.cpp deleted file mode 100644 index 8490796..0000000 --- a/lib/osmparser/src/util.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include - -#include -#include - -namespace xml = tinyxml2; - -namespace osmp -{ -#define FAILED(err) (err != xml::XML_SUCCESS) - - std::string GetSafeAttributeString(const tinyxml2::XMLElement* elem, const std::string& name) - { - const char* buffer; - - xml::XMLError result = elem->QueryStringAttribute(name.c_str(), &buffer); - if (FAILED(result)) - return ""; - - std::string returnStr(buffer); - return returnStr; - } - - double GetSafeAttributeFloat(const tinyxml2::XMLElement* elem, const std::string& name) - { - double returnVal = 0.0f; - - xml::XMLError result = elem->QueryDoubleAttribute(name.c_str(), &returnVal); - - return returnVal; - } - - uint64_t GetSafeAttributeUint64(const tinyxml2::XMLElement* elem, const std::string& name) - { - uint64_t returnVal = 0; - - xml::XMLError result = elem->QueryUnsigned64Attribute(name.c_str(), &returnVal); - return returnVal; - } - - bool GetSafeAttributeBool(const tinyxml2::XMLElement* elem, const std::string& name) - { - bool returnVal = false; - - xml::XMLError result = elem->QueryBoolAttribute(name.c_str(), &returnVal); - - return returnVal; - } -} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1dba678..4d6c1b9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,14 +44,14 @@ int main(int argc, char** argv) int windowWidth = windowHeight * aspectRatio; // Fetch all the ways - std::vector> ways = obj->GetWays(); + osmp::Ways ways = obj->GetWays(); // Turn them into renderable ways by mapping the global coordinates to screen coordinates (do this smarter in the future pls) std::vector buildings; std::vector highways; - for (std::shared_ptr way : ways) + for (osmp::Way way : ways) { - const std::vector>& nodes = way->GetNodes(); + const osmp::Nodes& nodes = way->GetNodes(); std::string highwayVal = way->GetTag("highway"); std::string railwayVal = way->GetTag("railway"); if (way->area) @@ -119,9 +119,9 @@ int main(int argc, char** argv) } // Fetch all relations - std::vector> relations = obj->GetRelations(); + osmp::Relations relations = obj->GetRelations(); std::vector multipolygons; - for (const std::shared_ptr& relation : relations) + for (const osmp::Relation& relation : relations) { if (relation->GetRelationType() == "multipolygon" && !relation->HasNullMembers()) { @@ -174,14 +174,14 @@ int main(int argc, char** argv) SDL_SetRenderDrawColor(renderer, 240, 240, 250, 255); SDL_RenderClear(renderer); - for (Multipolygon& multipolygon : multipolygons) { + for (Multipolygon& multipolygon : multipolygons) { multipolygon.Draw(renderer); - } + } - //for (Area& area : buildings) - //{ - // filledPolygonRGBA(renderer, area.x, area.y, area.length, area.r, area.g, area.b, 255); - //} + for (Area& area : buildings) + { + filledPolygonRGBA(renderer, area.x, area.y, area.length, area.r, area.g, area.b, 255); + } for (Highway& highway : highways) { diff --git a/src/multipolygon.cpp b/src/multipolygon.cpp index a6becce..52196c4 100644 --- a/src/multipolygon.cpp +++ b/src/multipolygon.cpp @@ -1,6 +1,7 @@ #include "..\include\multipolygon.hpp" #include +#include #include #include #include @@ -12,265 +13,167 @@ #include #define BREAKIF(x) if(relation->id == x) __debugbreak() +#define INDEXOF(x, y, n) (y * n + x) struct TriangulationData { std::vector vertices, holes; std::vector segments; }; +struct Ring { + osmp::Nodes nodes; + bool inner; + int index; + bool hole = false; +}; + +struct RingGroup { + std::vector rings; +}; + // Map values from one interval [A, B] to another [a, b] inline double Map(double A, double B, double a, double b, double x) { return (x - A) * (b - a) / (B - A) + a; } -Multipolygon::Multipolygon(const std::shared_ptr& relation, int width, int height, osmp::Bounds bounds) : +// TODO: Implement better algorithm +[[nodiscard]] bool Intersect(double p1_x, double p1_y, double p2_x, double p2_y, double q1_x, double q1_y, double q2_x, double q2_y); +[[nodiscard]] bool Intersect(const osmp::Node& p1, const osmp::Node& p2, const osmp::Node& q1, const osmp::Node& q2); +[[nodiscard]] bool SelfIntersecting(const Ring& ring); + +[[nodiscard]] bool BuildRing(Ring& ring, osmp::MemberWays& unassigned, int ringCount); +[[nodiscard]] bool AssignRings(std::vector& rings, const osmp::MemberWays& members); + +void FindAllContainedRings(const std::vector& containmentMatrix, int container, int numRings, std::vector& buffer); +void FindAllContainedRingsThatArentContainedByUnusedRings(const std::vector& containmentMatrix, int container, int numRings, const std::vector& unusedRings, std::vector& buffer); +[[nodiscard]] int FindUncontainedRing(const std::vector& containmentMatrix, int rings, const std::vector& unusedRings); +[[nodiscard]] bool PointInsideRing(const Ring& ring, const osmp::Node& point); +[[nodiscard]] bool IsRingContained(const Ring& r1, const Ring& r2); +[[nodiscard]] bool GroupRings(std::vector& ringGroup, std::vector& rings); + +Multipolygon::Multipolygon(const osmp::Relation& relation, int width, int height, const osmp::Bounds& bounds) : r(255), g(0), b(255), visible(true), rendering(RenderType::FILL), id(relation->id) { if (relation->HasNullMembers()) return; - // BREAKIF(7344428); - // BREAKIF(6427823); + const osmp::MemberWays& members = relation->GetWays(); - std::vector data; + /* Implement https://wiki.openstreetmap.org/wiki/Relation:multipolygon/Algorithm */ - std::vector ways = relation->GetWays(); - std::vector> nodes; - int run = 1; - - bool lastWasInner = false; - bool hasSeenOuter = false; - std::vector> outerWays; - std::vector> innerWays; - // Pre processing - for (osmp::Relation::Member member : ways) { - std::shared_ptr way = std::dynamic_pointer_cast(member.member); - if (member.role == "inner") - { - if (!hasSeenOuter) // TODO: Find better way to sort things - continue; - - if (innerWays.empty() || !lastWasInner) - innerWays.push_back({}); - - innerWays.back().push_back(member); - lastWasInner = true; - } - else - { - hasSeenOuter = true; - if (outerWays.empty() || lastWasInner) - outerWays.push_back({}); - - outerWays.back().push_back(member); - lastWasInner = false; - } + std::vector rings; + if (!AssignRings(rings, members)) + { + std::cerr << "Assigning rings has failed for multipolygon " << id << std::endl; } - if (outerWays.empty()) // There must always be an outer ring, anything else makes no sense - return; + std::vector ringGroups; + GroupRings(ringGroups, rings); - auto jt = outerWays.begin(); - bool currentIsInner = false; - while (!outerWays.empty() || !innerWays.empty()) + char* triSwitches = "zpNBQ"; + for (const RingGroup& ringGroup : ringGroups) { - std::vector member = *jt; - auto it = member.begin(); - while (!member.empty()) + TriangulationData td; + + bool valid = true; + for (const Ring& ring : ringGroup.rings) { - if (it == member.end()) - it = member.begin(); - std::shared_ptr way = std::dynamic_pointer_cast(it->member); - - // Several possible scenarios: - // Closed way - // Outer edge - // Append all nodes to the triangulation data - // Inner edge - // Append all nodes to the triangulation data - // Calculate average of nodes to get coordinates of the hole - // - // Open way - // Read next way until way is closed. This MUST happen, if the way remains open the OSM data is faulty and should be discarded - // Continue with Closed way algorithm - - bool inner = (it->role == "inner"); - std::vector> wayNodes = way->GetNodes(); - - if (run == 1) { - nodes.insert(nodes.begin(), wayNodes.begin(), wayNodes.end()); - } - else { - if (nodes.back() == wayNodes.front()) { - nodes.insert(nodes.end(), wayNodes.begin() + 1, wayNodes.end()); - } - else if (nodes.back() == wayNodes.back()) { - nodes.insert(nodes.end(), wayNodes.rbegin() + 1, wayNodes.rend()); - } - else if (nodes.front() == wayNodes.back()) { - nodes.insert(nodes.begin(), wayNodes.begin(), wayNodes.end() - 1); - } - else if (nodes.front() == wayNodes.front()) { - nodes.insert(nodes.begin(), wayNodes.rbegin(), wayNodes.rend() - 1); - } - else { - it++; - continue; - } - } - - it = member.erase(it); - - run++; - - if (!(way->closed)) { - if (nodes.size() > 1 && nodes.front() == nodes.back()) - { - // nodes.pop_back(); - } - else - { - continue; - } - } - - nodes.pop_back(); - - if (!inner || data.empty()) - { - data.push_back({}); - } - TriangulationData& td = data.back(); - - // Push all vertices to data std::vector vertices; - std::map duplicates; - int n = td.vertices.size() / 2; - for (const std::shared_ptr& node : nodes) { + for (const osmp::Node& node : ring.nodes) { double x = Map(bounds.minlon, bounds.maxlon, 0, width, node->lon); double y = height - Map(bounds.minlat, bounds.maxlat, 0, height, node->lat); - auto xit = std::find(td.vertices.begin(), td.vertices.end(), x); - auto yit = std::find(td.vertices.begin(), td.vertices.end(), y); - if (std::distance(xit, yit) == 1) { - duplicates.insert(std::make_pair(n, std::distance(td.vertices.begin(), xit) / 2)); - } - else { - vertices.push_back(x); - vertices.push_back(y); - } - n++; + vertices.push_back(x); + vertices.push_back(y); } - if (inner) - { - // Calculate data of hole by using the average position of all inner vertices (that should work right?, probably not...) - REAL holeX = 0.0f; - REAL holeY = 0.0f;; + int segment = td.vertices.size() / 2; + for (int i = 0; i < vertices.size() / 2; i += 1) { + td.segments.push_back(segment + i); + td.segments.push_back(segment + i + 1); + } + td.segments.back() = td.vertices.size() / 2; + + td.vertices.insert(td.vertices.end(), vertices.begin(), vertices.end()); + + if (ring.hole) { + double holeX = 0.0f; + double holeY = 0.0f; for (int i = 0; i < vertices.size(); i += 2) { holeX += vertices[i]; holeY += vertices[i + 1]; } - holeX /= (vertices.size() / 2); - holeY /= (vertices.size() / 2); + + holeX /= vertices.size() / 2; + holeY /= vertices.size() / 2; td.holes.push_back(holeX); td.holes.push_back(holeY); } + } - // Get segments - int segNum = td.vertices.size() / 2; - for (int i = 0; i < vertices.size(); i += 2) { - auto dit = duplicates.find(segNum); - if (dit != duplicates.end()) - { - td.segments.push_back(dit->second); - } - else - { - td.segments.push_back(segNum++); - } + // TODO: Find better way to check for duplicates + for (int i = 0; i < td.vertices.size(); i += 2) { + for (int j = 0; j < td.vertices.size(); j += 2) { + if (i == j) continue; - dit = duplicates.find(segNum); - if (dit != duplicates.end()) + if (td.vertices[i] == td.vertices[j] && td.vertices[i + 1] == td.vertices[j + 1]) { - td.segments.push_back(dit->second); - } - else - { - td.segments.push_back(segNum); + valid = false; + break; } } - td.segments.back() = td.vertices.size() / 2; - - td.vertices.insert(td.vertices.end(), vertices.begin(), vertices.end()); - nodes.clear(); - run = 1; } - - if (currentIsInner) { - innerWays.erase(innerWays.begin()); - jt = outerWays.begin(); - } - else { - outerWays.erase(outerWays.begin()); - jt = innerWays.begin(); - } - - currentIsInner = !currentIsInner; - } - - char* triswitches = "zpNBQ"; - for (TriangulationData& td : data) - { - triangulateio in; - - in.numberofpoints = td.vertices.size() / 2; - in.pointlist = td.vertices.data(); - in.pointmarkerlist = NULL; - in.numberofpointattributes = 0; - in.numberofpointattributes = NULL; + if (valid) + { + triangulateio in; - in.numberofholes = td.holes.size() / 2; - in.holelist = td.holes.data(); + in.numberofpoints = td.vertices.size() / 2; + in.pointlist = td.vertices.data(); + in.pointmarkerlist = NULL; - in.numberofsegments = td.segments.size() / 2; - in.segmentlist = td.segments.data(); - in.segmentmarkerlist = NULL; + in.numberofpointattributes = 0; + in.numberofpointattributes = NULL; - in.numberofregions = 0; - in.regionlist = NULL; + in.numberofholes = td.holes.size() / 2; + in.holelist = td.holes.data(); - triangulateio out; - out.pointlist = NULL; - out.pointmarkerlist = NULL; - out.trianglelist = NULL; - out.segmentlist = NULL; - out.segmentmarkerlist = NULL; + in.numberofsegments = td.segments.size() / 2; + in.segmentlist = td.segments.data(); + in.segmentmarkerlist = NULL; - triangulate(triswitches, &in, &out, NULL); + in.numberofregions = 0; + in.regionlist = NULL; - // TODO: memory leak go brrrr - polygons.push_back({}); - for (int i = 0; i < in.numberofpoints * 2; i += 2) { - polygons.back().vertices.push_back({ in.pointlist[i], in.pointlist[i + 1] }); - // polygons.back().vertices.push_back(in.pointlist[i + 1]); + triangulateio out; + out.pointlist = NULL; + out.pointmarkerlist = NULL; + out.trianglelist = NULL; + out.segmentlist = NULL; + out.segmentmarkerlist = NULL; + + triangulate(triSwitches, &in, &out, NULL); + + polygons.push_back({}); + for (int i = 0; i < in.numberofpoints * 2; i += 2) { + polygons.back().vertices.push_back({ in.pointlist[i], in.pointlist[i + 1] }); + // polygons.back().vertices.push_back(in.pointlist[i + 1]); + } + for (int i = 0; i < out.numberoftriangles * 3; i++) { + polygons.back().indices.push_back(out.trianglelist[i]); + } + for (int i = 0; i < in.numberofsegments * 2; i++) { + polygons.back().segments.push_back(in.segmentlist[i]); + } + + trifree(out.trianglelist); + trifree(out.segmentlist); } - for (int i = 0; i < out.numberoftriangles * 3; i++) { - polygons.back().indices.push_back(out.trianglelist[i]); - } - for (int i = 0; i < in.numberofsegments * 2; i++) { - polygons.back().segments.push_back(in.segmentlist[i]); - } - - trifree(out.trianglelist); - trifree(out.segmentlist); } - // TODO: Make a color map std::string tag = ""; @@ -659,8 +562,16 @@ void Multipolygon::Draw(SDL_Renderer* renderer) if (!visible) return; - // if (id != 6427823) - // return; + //for (const Polygon& polygon : polygons) { + // for (auto it = polygon.vertices.begin(); it != polygon.vertices.end() - 1; it++) { + // thickLineRGBA(renderer, + // it->x, it->y, + // (it+1)->x, (it+1)->y, + // 2, + // r, g, b, 255 + // ); + // } + //} for (const Polygon& polygon : polygons) { switch(rendering) @@ -713,3 +624,306 @@ void Multipolygon::Draw(SDL_Renderer* renderer) } } } + +bool Intersect(double p0_x, double p0_y, double p1_x, double p1_y, double p2_x, double p2_y, double p3_x, double p3_y) +{ + if ((p0_x == p2_x && p0_y == p2_y) || + (p0_x == p3_x && p0_y == p3_y) || + (p1_x == p2_x && p1_y == p2_y) || + (p1_x == p3_x && p1_y == p3_y)) + return false; + + float s1_x, s1_y, s2_x, s2_y; + s1_x = p1_x - p0_x; s1_y = p1_y - p0_y; + s2_x = p3_x - p2_x; s2_y = p3_y - p2_y; + + float s, t; + s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y); + t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y); + + if (s >= 0 && s <= 1 && t >= 0 && t <= 1) + { + // Collision detected + return 1; + } + + return 0; // No collision +} + +bool Intersect(const osmp::Node& p0, const osmp::Node& p1, const osmp::Node& p2, const osmp::Node& p3) +{ + return Intersect(p0->lon, p0->lat, p1->lon, p1->lat, p2->lon, p2->lat, p3->lon, p3->lat); +} +bool SelfIntersecting(const Ring& ring) +{ + struct Segment { + osmp::Node p1, p2; + }; + + // Get all segments + std::vector segments; + for (auto it = ring.nodes.begin(); it != ring.nodes.end(); it++) + { + if (it == ring.nodes.end() - 1) + { + segments.push_back({ + *it, ring.nodes.front() + }); + } + else + { + segments.push_back({ + *it, *(it + 1) + }); + } + } + + // Check for self intersection (O(n^2)...) + for (auto it = segments.begin(); it != segments.end(); it++) + { + for (auto jt = segments.begin(); jt != segments.end(); jt++) + { + if (it == jt) continue; + + if (Intersect(it->p1, it->p2, jt->p1, jt->p2)) + return true; + } + } + + return false; +} + +bool BuildRing(Ring& ring, osmp::MemberWays& unassigned, int ringCount) +{ + const osmp::MemberWays original = unassigned; + + // RA-2 + int attempts = 0; + ring = Ring{ unassigned[attempts].way->GetNodes(), unassigned[attempts].role == "inner", ringCount }; + unassigned.erase(unassigned.begin() + attempts); + +RA3: + // RA-3 + if (ring.nodes.front() == ring.nodes.back()) + { + if (SelfIntersecting(ring)) + { + unassigned = original; + attempts += 1; + if (unassigned.size() == attempts) + return false; + + ring = Ring{ unassigned[attempts].way->GetNodes(), unassigned[attempts].role == "inner", ringCount }; + goto RA3; + } + else + { + ring.nodes.pop_back(); + return true; + } + } + else // RA-4 + { + osmp::Node lastNode = ring.nodes.back(); + for (auto it = unassigned.begin(); it != unassigned.end(); it++) + { + if (it->way->GetNodes().front() == lastNode) + { + ring.nodes.insert(ring.nodes.end(), it->way->GetNodes().begin() + 1, it->way->GetNodes().end()); + unassigned.erase(it); + goto RA3; + } + else if (it->way->GetNodes().back() == lastNode) + { + ring.nodes.insert(ring.nodes.end(), it->way->GetNodes().rbegin() + 1, it->way->GetNodes().rend()); + unassigned.erase(it); + goto RA3; + } + } + + // No ring found + unassigned = original; + return false; + } +} + +bool AssignRings(std::vector& rings, const osmp::MemberWays& members) +{ + // Ring assignment + osmp::MemberWays unassigned = members; + int ringCount = 0; + while (!unassigned.empty()) + { + rings.push_back({}); + if (!BuildRing(rings.back(), unassigned, ringCount) || rings.size() > members.size()) + return false; + + ringCount++; + } + + return true; +} + +bool cmp(const osmp::Node& a, const osmp::Node& b) +{ + return (a->lon < b->lon); +} + +void FindAllContainedRings(const std::vector& containmentMatrix, int container, int numRings, std::vector& buffer) +{ + buffer.clear(); + for (int j = 0; j < numRings; j++) { + if (containmentMatrix[INDEXOF(container, j, numRings)]) + buffer.push_back(j); + } +} + +void FindAllContainedRingsThatArentContainedByUnusedRings(const std::vector& containmentMatrix, int container, int numRings, const std::vector& unusedRings, std::vector& buffer) +{ + FindAllContainedRings(containmentMatrix, container, numRings, buffer); + + for (auto j = buffer.begin(); j != buffer.end(); ) { + bool foundRing = false; + for (const Ring& ring : unusedRings) { + if (containmentMatrix[INDEXOF(ring.index, *j, numRings)]) { + foundRing = true; + break; + } + } + + if(foundRing) + j = buffer.erase(j); + else + ++j; + } +} + +bool Compare(const Ring& ring, int index) { + return (ring.index == index); +} + +int FindUncontainedRing(const std::vector& containmentMatrix, int rings, const std::vector& unusedRings) +{ + for (int j = 0; j < rings; j++) { + if (std::find_if(unusedRings.begin(), unusedRings.end(), [j](const Ring& ring) { return (ring.index == j); }) == unusedRings.end()) + continue; + + bool isContained = false; + for (int i = 0; i < rings; i++) { + if (containmentMatrix[INDEXOF(i, j, rings)]) + { + isContained = true; + break; + } + } + + if (!isContained) + return j; + } + + return -1; +} + +bool PointInsideRing(const Ring& ring, const osmp::Node& point) +{ + const osmp::Node& rightestNode = *(std::max_element(ring.nodes.begin(), ring.nodes.end(), cmp)); + + int intersections = 0; + for (auto it = ring.nodes.begin(); it != ring.nodes.end(); it++) + { + const osmp::Node& jt = ((it == ring.nodes.end() - 1) ? ring.nodes.front() : *(it + 1)); + intersections += Intersect((*it)->GetLon(), (*it)->GetLat(), + jt->GetLon(), jt->GetLat(), + point->GetLon(), point->GetLat(), + rightestNode->GetLon() + 1.0f, point->GetLat() + ); + } + + return (intersections % 2 != 0); +} + +bool IsRingContained(const Ring& r1, const Ring& r2) +{ + // Test if any line segments are intersecting + // I don't think this is needed actually, the rings shouldn't overlap so testing if a node is inside is enough! + // Gonna leave this here tho in case we *do* need to see if a ring is completely contained + //for (auto it = r1.nodes.begin(); it != r1.nodes.end(); it++) + //{ + // for (auto jt = r2.nodes.begin(); jt != r2.nodes.end(); jt++) + // { + // osmp::Node n1 = ((it == r1.nodes.end() - 1) ? r1.nodes.front() : *(it + 1)); + // osmp::Node n2 = ((jt == r2.nodes.end() - 1) ? r2.nodes.front() : *(jt + 1)); + + // if (Intersect(*it, n1, *jt, n2)) + // return false; + // } + //} + + if (PointInsideRing(r1, r2.nodes.front())) + return true; + + return false; +} + +bool GroupRings(std::vector& ringGroups, std::vector& rings) +{ + const std::vector original = rings; + + //RG-1 + int ringNum = rings.size(); + std::vector containmentMatrix(ringNum * ringNum); + + for (int i = 0; i < ringNum; i++) + { + for (int j = 0; j < ringNum; j++) + { + if (i == j) { + containmentMatrix[INDEXOF(i, j, ringNum)] = false; + continue; + } + + containmentMatrix[INDEXOF(i, j, ringNum)] = IsRingContained(rings[i], rings[j]); + } + } + + // RG-2 / RG-3 + while (!rings.empty()) // TODO: Make this time out + { + int uncontainedRing = FindUncontainedRing(containmentMatrix, ringNum, rings); + if (uncontainedRing == -1) { + std::cerr << "Failed to find uncontained ring in step RG-2" << std::endl; + return false; + } + auto it = std::find_if(rings.begin(), rings.end(), [uncontainedRing](const Ring& ring) { return (ring.index == uncontainedRing); }); + if (it == rings.end()) + { + std::cerr << "Uncontained Ring is out of range" << std::endl; + return false; + } + + ringGroups.push_back({ {*it} }); + rings.erase(it); + + + // RG-4 + std::vector containedRings; + FindAllContainedRingsThatArentContainedByUnusedRings(containmentMatrix, uncontainedRing, ringNum, rings, containedRings); + + for (auto it = rings.begin(); it != rings.end(); ) { + if (std::find(containedRings.begin(), containedRings.end(), it->index) != containedRings.end()) { + ringGroups.back().rings.push_back(*it); + ringGroups.back().rings.back().hole = true; + it = rings.erase(it); + } + else { + it++; + } + } + + // TODO: RG-5 / RG-6 will be left out for now as they're optional. + // At least RG-6 should be implemented because not doing so might crash Triangle + + } + + return true; +}