4.OpenStreetMap Data Model
- 2020 年 2 月 18 日
- 筆記
OpenStreetMap是一個開源項目,旨在為用戶免費創建生成世界各的地圖。

OpenStreetMap數據以OSM XML文件(.osm文件)的形式出現。
<?xml version="1.0" encoding="UTF-8"?> <osm version="0.6" generator="CGImap 0.0.2"> <bounds minlat="54.0889580" minlon="12.2487570" maxlat="54.0913900" maxlon="12.2524800"/> <node id="298884269" lat="54.0901746" lon="12.2482632" user="SvenHRO" uid="46882" visible="true" version="1" changeset="676636" timestamp="2008-09-21T21:37:45Z"/> <node id="261728686" lat="54.0906309" lon="12.2441924" user="PikoWinter" uid="36744" visible="true" version="1" changeset="323878" timestamp="2008-05-03T13:39:23Z"/> <node id="1831881213" version="1" changeset="12370172" lat="54.0900666" lon="12.2539381" user="lafkor" uid="75625" visible="true" timestamp="2012-07-20T09:43:19Z"> <tag k="name" v="Neu Broderstorf"/> <tag k="traffic_sign" v="city_limit"/> </node> ... <node id="298884272" lat="54.0901447" lon="12.2516513" user="SvenHRO" uid="46882" visible="true" version="1" changeset="676636" timestamp="2008-09-21T21:37:45Z"/> <way id="26659127" user="Masch" uid="55988" visible="true" version="5" changeset="4142606" timestamp="2010-03-16T11:47:08Z"> <nd ref="292403538"/> <nd ref="298884289"/> ... <nd ref="261728686"/> <tag k="highway" v="unclassified"/> <tag k="name" v="Pastower Straße"/> </way> <relation id="56688" user="kmvar" uid="56190" visible="true" version="28" changeset="6947637" timestamp="2011-01-12T14:23:49Z"> <member type="node" ref="294942404" role=""/> ... <member type="node" ref="364933006" role=""/> <member type="way" ref="4579143" role=""/> ... <member type="node" ref="249673494" role=""/> <tag k="name" v="Küstenbus Linie 123"/> <tag k="network" v="VVW"/> <tag k="operator" v="Regionalverkehr Küste"/> <tag k="ref" v="123"/> <tag k="route" v="bus"/> <tag k="type" v="route"/> </relation> ... </osm>
XML文件包含幾個元素類型,這些元素類型對您要編寫的程式碼很重要:節點、方式和關係。
節點
節點是OpenStreetMap數據模型中最基本的元素之一。每個節點指示一個具有標識符id、緯度lat和經度lon的點。node元素中還有其他與此項目無關的XML屬性,例如將節點添加到數據集中時的用戶id和時間戳。另外,一個節點可以有多個標籤來提供附加資訊。
道路
道路是表示地圖中某個要素的有序節點列表。這個特徵可以是道路,公園的邊界,或者地圖上的其他特徵。每種道路至少有一個標記,該標記表示關於該道路的一些資訊,並且每種道路還屬於至少一個關係,如下所述。
關係
關係是記錄其他數據元素之間關係的數據結構。來自OpenStreetMap wiki的示例包括:
- 一種路線關係,列出形成主要公路、自行車道或公共汽車路線的方式。
- 描述有孔區域的多多邊形,該區域的外邊界和內邊界由兩種方式給出。
IO2D項目
https://github.com/cpp-io2d/P0267_RefImpl/tree/master/P0267_RefImpl/Samples/maps
程式碼結構
該項目的啟動程式碼可在此處找到。 在倉庫中,應該看到以下五個目錄:
- cmake
該目錄包含一些CMakeLists.txt文件,這些文件是項目查找必要的庫所必需的。無需為此項目使用此目錄。
- instructions
該目錄包含一組練習的標記指令文件。同樣,不需要在這裡直接使用文件,但是在處理項目時,每組指令將顯示在終端工作區中,以進行適當的練習。
- src
該項目的源程式碼包含在此處
- test
此目錄包含使用Google Test框架實現的各種練習的單元測試。在開發程式碼時,查看此目錄中的相關測試以查看預期的答案和相應的程式碼可能會有所幫助。如果程式碼未通過測試,則控制台將告訴哪個文件包含失敗的測試。
- thirdparty
此目錄包含此項目已包含的第三方庫。無需直接使用此程式碼
src

main.cpp控制程式的流程,完成四個主要任務:
- OSM數據被讀入程式。
- 創建一個RouteModel對象,將OSM數據存儲在可用的數據結構中。
- RoutePlanner對象是使用RouteModel創建的。此計劃器最終將對模型數據執行A*搜索,並將搜索結果存儲在RouteModel中。
- RouteModel數據是使用IO2D庫呈現的。
model.h and model.cpp
這些文件來自IO2D示例程式碼。它們用於定義讀取和存儲OSM數據的數據結構和方法。OSM數據存儲在一個模型類中,該模型類包含節點、方法、道路和其他OSM對象的嵌套結構
RouteModel類
Model當前程式碼中存在的類並不包含執行A *搜索所需的所有數據或方法,因此我們將用一個RouteModel類擴展該類。
class RouteModel : public Model { public: //RouteModel 的 Node 也是繼承的 class Node : public Model::Node { public: // Add public Node variables and methods here. Node(){ std::cout<<"RouteModel Init"<<"n"; } //因為有init 一個pointer parent_model, 因此一定要寫成 initial list 的方式 Node(int idx, RouteModel * search_model, Model::Node node) : Model::Node(node), parent_model(search_model), index(idx) { std::cout<<"RouteModel Init"<<"n"; } private: // Add private Node variables and methods here. int index; RouteModel * parent_model = nullptr; }; // Add public RouteModel variables and methods here. RouteModel(const std::vector<std::byte> &xml); //這個 path 存結果 std::vector<Node> path; // This variable will eventually store the path that is found by the A* search. //Task2:Add a public "getter" method SNodes. This method should return a reference to the vector of Nodes stored as m_Nodes. std::vector<Node>& SNodes(){ return m_Nodes; } private: // Add private RouteModel variables and methods here. //Taks1:Add a private vector of Node objects named m_Nodes. This will store all of the nodes from the Open Street Map data. //m_Nodes 存所有open street map 的 data, 之後給Astar 用 std::vector<Node> m_Nodes; };
Node 類
Model::Node當前程式碼中存在的類不包含執行A *搜索所需的所有數據。為了執行搜索,最理想的是每個節點至少包含以下資訊:
- 節點的g值。
- 節點的h值。
- 指示是否已訪問該節點的布爾值。
- 指向節點父節點的指針。
#include <limits> #include <cmath> #include <unordered_map> #include "model.h" #include <iostream> //因為Model 裡面只有一個Node 的資訊, 要能做A*, 你需要所有的Node, //因此我們繼承Model 來造一個RouteModel 準備收集所有要用的Node 給A* /* Task: In route_model.h: Add a private vector of Node objects named m_Nodes. This will store all of the nodes from the Open Street Map data. Add a public "getter" method SNodes. This method should return a reference to the vector of Nodes stored as m_Nodes. */ class RouteModel : public Model { public: //RouteModel 的 Node 也是繼承來的 class Node : public Model::Node { /* The Node Class The Model::Node class that exists in the current code doesn't contain all the data that would be needed to perfom an A* search. In order to perform a search, it would be ideal for each node to contain at least the following information: 1.The g-value for the node. 2.The h-value for the node. 3.A boolean to indicate if the node has been visited. 4.A pointer to the node's parent. In this exercise, you will fill out the RouteModel::Node class in route_model.h, which will extend the Model::Node class so that the data above, along with a few other useful variables, can be included with each node. Note that the RouteModel::Node class already has the following private variables: 1.An int index. 2.A pointer to a RouteModel object named parent_model. This variable is important, as it allows each node to access data stored in the parent model that the node belongs to. To complete this exercise: Add the following public variables to the RouteModel::Node class: 1.A Node pointer parent, which is initialized to a nullptr. 2.A float h_value, which is initialized to the maximum possible: std::numeric_limits<float>::max(). 3.A float g_value, which is initialized to 0.0. 4.A bool visited, which is initialized to false. 5.A vector of Node pointers named neighbors. Pass testing: [==========] Running 2 tests from 1 test case. [----------] Global test environment set-up. [----------] 2 tests from RouteModelTest [ RUN ] RouteModelTest.RouteModelData [ OK ] RouteModelTest.RouteModelData (67 ms) [ RUN ] RouteModelTest.RouteModelNode RouteModel Init RouteModel Init [ OK ] RouteModelTest.RouteModelNode (61 ms) [----------] 2 tests from RouteModelTest (128 ms total) [----------] Global test environment tear-down [==========] 2 tests from 1 test case ran. (128 ms total) [ PASSED ] 2 tests. */ public: //Add Node * parent = nullptr; float h_value = std::numeric_limits<float>::max(); float g_value = 0.0; bool visited = false; std::vector<Node*> neighbors; // Add public Node variables and methods here. Node(){ std::cout<<"RouteModel Init"<<"n"; } //因為有init 一個pointer parent_model, 因此一定要寫成 initial list 的方式 Node(int idx, RouteModel * search_model, Model::Node node) : Model::Node(node), parent_model(search_model), index(idx) { std::cout<<"RouteModel Init"<<"n"; } private: // Add private Node variables and methods here. //An int index. int index; //A pointer to a RouteModel object named parent_model. This variable is important, //as it allows each node to access data stored in the parent model that the node belongs to. RouteModel * parent_model = nullptr; }; // Add public RouteModel variables and methods here. RouteModel(const std::vector<std::byte> &xml); //這個 path 最後會存著結果 std::vector<Node> path; // This variable will eventually store the path that is found by the A* search. //Task2:Add a public "getter" method SNodes. This method should return a reference to the vector of Nodes stored as m_Nodes. std::vector<Node>& SNodes(){ return m_Nodes; } private: // Add private RouteModel variables and methods here. //Taks1:Add a private vector of Node objects named m_Nodes. This will store all of the nodes from the Open Street Map data. //這個m_Nodes 會拿來存所有open street map 的 data, 之後給Astar 用 std::vector<Node> m_Nodes; };
this 指針
IncrementDistance()方法隱式引用當前Car實例的distance屬性:
// The Car class class Car { public: // Method to print data. void PrintCarData() { cout << "The distance that the " << color << " car " << number << " has traveled is: " << distance << "n"; } // Method to increment the distance travelled. void IncrementDistance() { distance++; } // Class/object attributes string color; int distance = 0; int number; };
可以通過使用this指向當前類實例的指針在C ++中使其明確。使用this有時會有助於增加透明度,以更加複雜的程式碼:
// The Car class class Car { public: // Method to print data. void PrintCarData() { cout << "The distance that the " << this->color << " car " << this->number << " has traveled is: " << this->distance << "n"; } // Method to increment the distance travelled. void IncrementDistance() { this->distance++; } // Class/object attributes string color; int distance = 0; int number; };
創建RouteModel節點
RouteModel類,並且已經完成了RouteModel::Node嵌套類,可以創建RouteModel節點。 當RouteModel調用構造函數,它調用Model與開放街道地圖數據的構造。發生這種情況時,Model:Node將創建對象的集合。但是,為了執行A *搜索,將需要使用RouteModel::Node對象。 修改RouteModel構造函數。構造函數將使用Model:Node對象向量創建新RouteModel::Node對象。RouteModel::Node然後,這些對象將存儲在的m_Nodes向量中
- 在RouteModel構造函數中route_model.cpp,編寫一個帶計數器的for循環,以循環遍歷由Model::Node給出的s 的向量this->Nodes()。
- 對於Model循環中的每個節點,使用RouteModel::Node構造函數創建一個新節點,並將新節點推到的後面m_Nodes。
- 使用RouteModel::Node接受三個參數的構造函數:
Node(int idx, RouteModel * search_model, Model::Node node)