123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739 |
- //==============================================================================
- //
- // GPXOSM - the gpx openstreetmap class
- //
- // Copyright (C) 2019 Dick van Oudheusden
- //
- // This library is free software; you can redistribute it and/or
- // modify it under the terms of the GNU Lesser General Public
- // License as published by the Free Software Foundation; either
- // version 3 of the License, or (at your option) any later version.
- //
- // This library 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
- // Lesser General Public License for more details.
- //
- // You should have received a copy of the GNU Lesser General Public
- // License along with this library; if not, write to the Free
- // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- //
- //==============================================================================
- #include <string>
- #include <cstring>
- #include <fstream>
- #include <sstream>
- #include <vector>
- #include <ctime>
- #include <iomanip>
- #include <limits>
- #include <algorithm>
- #include "Arguments.h"
- #include "XMLParser.h"
- #include "Algorithm.h"
- namespace gpxtools
- {
- // --Track --------------------------------------------------------
- class Track
- {
- public:
- struct Point
- {
- Point(std::string lat, std::string lon)
- {
- _lat = lat;
- _lon = lon;
- }
- Point()
- {
- clear();
- }
- void clear()
- {
- _lat.clear();
- _lon.clear();
- }
- std::string _lat;
- std::string _lon;
- };
- typedef std::vector<Point> Points;
- enum JoinResult { ERROR, UPDATED, OK };
- Track() :
- _name(),
- _type(),
- _copyright(),
- _way(),
- _relation(),
- _relations()
- {
- }
- ~Track()
- {
- }
- void setName(const std::string name) { _name = name; }
- void setType(const std::string type) { _type = type; }
- void setCopyright(const std::string copyright)
- {
- if (!copyright.empty()) _copyright = copyright;
- }
- void startRelation()
- {
- _relation._number.clear();
- _relation._points.clear();
- _relation._startPoint.clear();
- _relation._startIndex = std::string::npos;
- _relation._endPoint.clear();
- _relation._endIndex = std::string::npos;
- }
- void endRelation(const std::string &number)
- {
- _relation._number = number;
- _relations.push_back(_relation);
- }
- void startWay()
- {
- _way.clear();
- }
- void endWay()
- {
- if (join(_relation._points, _way) != ERROR)
- {
- _relation._points.insert(_relation._points.end(), _way.begin(), _way.end());
- }
- }
- void addPoint(const Point &point)
- {
- _way.push_back(point);
- }
- // Join the relations
- bool join()
- {
- bool ok = align();
- if (!ok)
- {
- report();
- }
- else
- {
- reduce();
- }
- return (ok);
- }
- void write(std::ostream &str) const
- {
- str << "<?xml version='1.0'?>" << std::endl;
- str << "<gpx version='1.1'>" << std::endl;
- if (!_copyright.empty())
- {
- str << " <metadata>" << std::endl;
- str << " <copyright author='" << _copyright << "' />" << std::endl;
- str << " </metadata>" << std::endl;
- }
- str << " <trk>" << std::endl;
- if (!_name.empty())
- {
- str << " <name>" << _name << "</name>" << std::endl;
- }
- if (!_type.empty())
- {
- str << " <type>" << _type << "</type>" << std::endl;
- }
- str << " <trkseg>" << std::endl;
- for (auto relation = _relations.begin(); relation != _relations.end(); ++relation)
- {
- for (auto point = relation->_points.begin(); point != relation->_points.end(); ++point)
- {
- str << " <trkpt lat='" << point->_lat << "' lon='" << point->_lon << "' />" << std::endl;
- }
- }
- str << " </trkseg>" << std::endl;
- str << " </trk>" << std::endl;
- str << "</gpx>" << std::endl;
- }
- private:
- struct Relation
- {
- std::string _number;
- Points _points;
- Point _startPoint;
- size_t _startIndex;
- Point _endPoint;
- size_t _endIndex;
- };
- // Align the relations
- bool align()
- {
- bool updated = false;
- bool error = false;
- // Join the relations
- do
- {
- updated = false;
- error = false;
- auto previous = _relations.begin();
- auto next = (previous != _relations.end() ? previous + 1 : _relations.end());
- for (; next != _relations.end(); ++previous, ++next)
- {
- switch(join(*previous, *next))
- {
- case ERROR : error = true; break;
- case UPDATED: updated = true; break;
- case OK : break;
- }
- }
- }
- while (updated);
- return (!error);
- }
- // Report unjoined relations
- void report()
- {
- auto previous = _relations.begin();
- auto next = (previous != _relations.end() ? previous + 1 : _relations.end());
- for (; next != _relations.end(); ++previous, ++next)
- {
- if (join(previous->_points, next->_points) == ERROR)
- {
- std::cerr << "Unable to join relations " << previous->_number << " and " << next->_number << ", use join point" << std::endl;
- }
- }
- }
- // Reduce the tracks with indices
- void reduce()
- {
- for (auto relation = _relations.begin(); relation != _relations.end(); ++relation)
- {
- size_t startIndex = relation->_startIndex;
- size_t endIndex = relation->_endIndex;
- if ((startIndex != std::string::npos) || (endIndex != std::string::npos))
- {
- if ((startIndex != std::string::npos) &&
- (endIndex != std::string::npos) &&
- (startIndex > endIndex))
- {
- std::reverse(relation->_points.begin(), relation->_points.end());
- startIndex = relation->_points.size() - startIndex;
- endIndex = relation->_points.size() - endIndex;
- }
- auto start = (startIndex != std::string::npos) ?
- relation->_points.begin() + long(startIndex) : relation->_points.begin();
- auto end = (endIndex != std::string::npos) ?
- relation->_points.begin() + long(endIndex) : relation->_points.end();
- relation->_points = Points(start, end);
- }
- }
- }
- static double calcDistance(const Point &first, const Point &second)
- {
- try
- {
- return gpx::calcDistance(std::stod(first._lat), std::stod(first._lon), std::stod(second._lat), std::stod(second._lon));
- }
- catch (...)
- {
- return std::numeric_limits<double>::max();
- }
- }
- static double calcDistance(const Point &point, const Points &points, size_t &index)
- {
- double minDistance = std::numeric_limits<double>::max();
- for (auto iter = points.begin(); iter != points.end(); ++iter)
- {
- double distance = calcDistance(*iter, point);
- if (distance < minDistance)
- {
- minDistance = distance;
- index = iter - points.begin();
- }
- }
- return minDistance;
- }
- // Join the ways
- JoinResult join(Points &previous, Points &next)
- {
- JoinResult result = OK;
- if (previous.empty())
- {
- // Okee, first track
- }
- else if (calcDistance(previous.back(), next.front()) < 10.0)
- {
- // Okee, inline
- }
- else if (calcDistance(previous.back(), next.back()) < 10.0)
- {
- std::reverse(next.begin(), next.end());
- result = UPDATED;
- }
- else if (calcDistance(previous.front(), next.front()) < 10.0)
- {
- std::reverse(previous.begin(), previous.end());
- result = UPDATED;
- }
- else if (calcDistance(previous.front(), next.back()) < 10.0)
- {
- std::reverse(previous.begin(), previous.end());
- std::reverse(next.begin(), next.end());
- result = UPDATED;
- }
- else
- {
- result = ERROR;
- }
- return result;
- }
- // Join relations
- JoinResult join(Relation &previous, Relation &next)
- {
- JoinResult result = OK;
- std::size_t index = std::string::npos;
- if (calcDistance(previous._points.back(), next._points.front()) < 10.0)
- {
- // Okee, inline
- }
- else if (calcDistance(previous._points.back(), next._points.back()) < 10.0)
- {
- std::reverse(next._points.begin(), next._points.end());
- result = UPDATED;
- }
- else if (calcDistance(previous._points.front(), next._points.front()) < 10.0)
- {
- std::reverse(previous._points.begin(), previous._points.end());
- result = UPDATED;
- }
- else if (calcDistance(previous._points.front(), next._points.back()) < 10.0)
- {
- std::reverse(previous._points.begin(), previous._points.end());
- std::reverse(next._points.begin(), next._points.end());
- result = UPDATED;
- }
- else if (calcDistance(previous._points.back(), next._points, index) < 10.0)
- {
- next._startPoint = previous._points.back();
- next._startIndex = index;
- }
- else if (calcDistance(previous._points.front(), next._points, index) < 10.0)
- {
- std::reverse(previous._points.begin(), previous._points.end());
- next._startPoint = previous._points.back();
- next._startIndex = index;
- result = UPDATED;
- }
- else if (calcDistance(next._points.front(), previous._points, index) < 10.0)
- {
- previous._endPoint = next._points.front();
- previous._endIndex = index;
- }
- else if (calcDistance(next._points.back(), previous._points, index) < 10.0)
- {
- std::reverse(next._points.begin(), next._points.end());
- previous._endPoint = next._points.front();
- previous._endIndex = index;
- result = UPDATED;
- }
- else
- {
- result = ERROR;
- }
- return result;
- }
- private:
- std::string _name;
- std::string _type;
- std::string _copyright;
- Points _way;
- Relation _relation;
- std::vector<Relation> _relations;
- };
- // -- Way ---------------------------------------------------------
- class Way : public XMLParserHandler
- {
- public:
- Way(const std::string &number, Track &track) :
- _track(track),
- _number(number),
- _xmlParser(this),
- _ref(nullptr)
- {
- }
- bool download()
- {
- bool ok = false;
- std::string cmd = "wget -q -O - http://www.openstreetmap.org/api/0.6/way/" + _number + "/full";
- FILE *stream = popen(cmd.c_str(), "r");
- if (stream != nullptr)
- {
- char buffer[4096];
- ok = true;
- while (ok && (fgets(buffer, sizeof(buffer), stream) != nullptr))
- {
- //std::cout << buffer << std::endl;
- ok = _xmlParser.parse(buffer, false);
- }
- if (ok)
- _xmlParser.parse("", true);
- pclose(stream);
- }
- else
- {
- std::cerr << "Unable to start: " << cmd << std::endl;
- }
- return ok;
- }
- private:
- std::string fetch(const Attributes &attributes, const std::string &key)
- {
- auto attribute = attributes.find(key);
- return (attribute != attributes.end() ? attribute->second : "");
- }
- // Interface XMLParserHandler
- virtual void startElement(const std::string &path, const std::string &, const Attributes &attributes)
- {
- if (path == "/osm")
- {
- _ref = new Ref();
- }
- if (path == "/osm/node")
- {
- std::string lat = fetch(attributes, "lat");
- std::string lon = fetch(attributes, "lon");
- std::string id = fetch(attributes, "id");
- if (!lat.empty() && !lon.empty() && !id.empty())
- {
- //std::cout << "Point:" << lat << ',' << lon << std::endl;
- (*_ref)[id] = Track::Point(lat, lon);
- }
- }
- if (path == "/osm/way/nd")
- {
- std::string ref = fetch(attributes, "ref");
- if (!ref.empty() && _ref->find(ref) != _ref->end())
- {
- auto point = _ref->find(ref);
- if (point != _ref->end())
- {
- _track.addPoint(point->second);
- }
- }
- }
- }
- virtual void endElement(const std::string &path, const std::string &)
- {
- if (path == "/osm")
- {
- delete _ref; _ref = nullptr;
- }
- }
- virtual void text(const std::string &, const std::string &) { }
- private:
- Track &_track;
- std::string _number;
- XMLParser _xmlParser;
- typedef std::map<std::string, Track::Point> Ref;
- Ref *_ref;
- Way();
- };
- // -- Relation ----------------------------------------------------
- class Relation : public XMLParserHandler
- {
- public:
- Relation(Track &track) :
- _track(track),
- _xmlParser(this)
- {
- }
- bool download(const std::string &number)
- {
- bool ok = false;
- std::string cmd = "wget -q -O - http://www.openstreetmap.org/api/0.6/relation/" + number;
- FILE *stream = popen(cmd.c_str(), "r");
- if (stream != nullptr)
- {
- char buffer[4096];
- ok = true;
- while (ok && (fgets(buffer, sizeof(buffer), stream) != nullptr))
- {
- ok = _xmlParser.parse(buffer, false);
- }
- if (ok)
- _xmlParser.parse("", true);
- pclose(stream);
- }
- else
- {
- std::cerr << "Unable to start: " << cmd << std::endl;
- }
- _track.startRelation();
- for (auto way = _ways.begin(); way != _ways.end(); ++way)
- {
- _track.startWay();
- (*way)->download();
- _track.endWay();
- }
- _track.endRelation(number);
- return ok;
- }
- private:
- std::string fetch(const Attributes &attributes, const std::string &key)
- {
- auto attribute = attributes.find(key);
- return (attribute != attributes.end() ? attribute->second : "");
- }
- // Interface XMLParserHandler
- virtual void startElement(const std::string &path, const std::string &, const Attributes &attributes)
- {
- if (path == "/osm")
- {
- _track.setCopyright(fetch(attributes, "copyright"));
- }
- if (path == "/osm/relation/member")
- {
- if (fetch(attributes, "type") == "way")
- {
- std::string way = fetch(attributes, "ref");
- //std::cout << "Way:" << way << std::endl;
- try
- {
- std::stol(way);
- _ways.push_back(new Way(way, _track));
- }
- catch(...)
- {
- std::cerr << "Invalid way: " << way << " in relation ignored." << std::endl;
- }
- }
- }
- }
- virtual void endElement(const std::string &, const std::string &) { }
- virtual void text(const std::string &, const std::string &) { }
- private:
- Track &_track;
- XMLParser _xmlParser;
- std::vector<Way*> _ways;
- Relation();
- };
- // -- GPXOSM ------------------------------------------------------
- class GPXOSM
- {
- public:
- GPXOSM() :
- _arguments("gpxosm [OPTION].. [RELATION]..\nDownload and combine OSM relations to a GPX-file.\n", "gpxosm v0.1",
- "Generate a GPX-file on standard output based on a sequence on Openstreetmap relations."),
- _name (_arguments, true, 'n', "name", "NAME", "set the name of the GPX-track", ""),
- _type (_arguments, true, 't', "type", "TYPE", "set the type of the GPX-track", "")
- {
- }
- ~GPXOSM()
- {
- }
- bool processArguments(int argc, char *argv[])
- {
- std::vector<std::string> relations;
- if (!_arguments.parse(argc,argv, relations))
- {
- return false;
- }
- else
- {
- gpxtools::Track track;
- track.setName(_name.value());
- track.setType(_type.value());
- download(relations, track);
- if (track.join())
- {
- track.write(std::cout);
- }
- }
- return true;
- }
- private:
- void download(const std::vector<std::string> &relations, Track &track)
- {
- for (auto number = relations.begin(); number != relations.end(); ++number)
- {
- try
- {
- std::stol(*number);
- Relation relation(track);
- relation.download(*number);
- }
- catch(...)
- {
- std::cerr << "Invalid relation: " << *number << " ignored." << std::endl;
- }
- }
- }
- private:
- arg::Arguments _arguments;
- arg::Argument _name;
- arg::Argument _type;
- };
- }
- int main(int argc, char *argv[])
- {
- gpxtools::GPXOSM gpxOSM;
- if (gpxOSM.processArguments(argc, argv))
- {
- return 0;
- }
- return 1;
- }
- #if 0
- <?xml version="1.0" encoding="UTF-8"?>
- <osm version="0.6" generator="CGImap 0.6.1 (1898 thorn-03.openstreetmap.org)" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
- <relation id="9327" visible="true" version="846" changeset="65105226" timestamp="2018-12-02T21:02:37Z" user="SomeoneElse" uid="61942">
- <member type="way" ref="26730670" role=""/>
- <member type="way" ref="26730671" role=""/>
- <member type="way" ref="91649821" role=""/>
- <member type="way" ref="529697769" role=""/>
- <tag k="name" v="National Byway (Yorkshire)"/>
- <tag k="network" v="rcn"/>
- <tag k="operator" v="National Byway"/>
- <tag k="ref" v="NB"/>
- <tag k="route" v="bicycle"/>
- <tag k="type" v="route"/>
- </relation>
- </osm>
- <?xml version="1.0" encoding="UTF-8"?>
- <osm version="0.6" generator="CGImap 0.6.1 (1909 thorn-01.openstreetmap.org)" copyright="OpenStreetMap and contributors" attribution="http://www.openstreetmap.org/copyright" license="http://opendatacommons.org/licenses/odbl/1-0/">
- <node id="42881242" visible="true" version="6" changeset="34691835" timestamp="2015-10-17T11:28:08Z" user="vindert" uid="1683540" lat="51.5174500" lon="4.2908800">
- <tag k="expected_rwn_route_relations" v="3"/>
- <tag k="rwn_ref" v="94"/>
- </node>
- <node id="42881332" visible="true" version="3" changeset="3451199" timestamp="2009-12-25T22:27:26Z" user="AND_fixbot" uid="211771" lat="51.5174900" lon="4.2909200"/>
- <way id="231747375" visible="true" version="4" changeset="27197750" timestamp="2014-12-03T11:29:43Z" user="It's so funny_mechanical" uid="2394881">
- <nd ref="42881332"/>
- <tag k="highway" v="tertiary"/>
- <tag k="name" v="Ligneweg"/>
- </way>
- </osm>
- #endif
|