From 4ba8ccfcce50c1d1c6e91df34b85ecc98a116ec2 Mon Sep 17 00:00:00 2001 From: Moritz Kobitzsch Date: Tue, 25 Oct 2016 03:19:10 +0200 Subject: [PATCH] add a geojson debugger that allows creating features for easy inspection --- docs/developing.md | 61 ++++++ include/extractor/geojson_debug_policies.hpp | 42 ++++ include/util/geojson_debug_logger.hpp | 185 ++++++++++++++++++ include/util/geojson_debug_policies.hpp | 58 ++++++ include/util/geojson_debug_policy_toolkit.hpp | 112 +++++++++++ src/extractor/geojson_debug_policies.cpp | 68 +++++++ src/util/geojson_debug_policies.cpp | 71 +++++++ 7 files changed, 597 insertions(+) create mode 100644 docs/developing.md create mode 100644 include/extractor/geojson_debug_policies.hpp create mode 100644 include/util/geojson_debug_logger.hpp create mode 100644 include/util/geojson_debug_policies.hpp create mode 100644 include/util/geojson_debug_policy_toolkit.hpp create mode 100644 src/extractor/geojson_debug_policies.cpp create mode 100644 src/util/geojson_debug_policies.cpp diff --git a/docs/developing.md b/docs/developing.md new file mode 100644 index 000000000..f2ac4c656 --- /dev/null +++ b/docs/developing.md @@ -0,0 +1,61 @@ +# Developing / Debugging guidance code + +When changing guidance code, it is easy to introduce problems somewhere in the network. +To get a better feeling of how your changes impact the OSRM experience, we offer ways of generating geojson output to inspect (e.g. with Mapbox Studio). +When you do changes, make sure to inspect a few areas for the impact of the changes. + +## How to use GeoJson-Debugging + +This is a short guide to describe usage of our GeoJson debug logging mechanism. It is synchronized to guarantee thread-safe logging. + +## Outputting into a single file +To use it, the inclusion of `geojson_debug_logger.hpp` `geojson_debug_policies.hpp` from the `util` directory is required. + +Geojson debugging requires a few simple steps to output data into a feature collection. + +- Create a Scoped Guard that lives through the process and provide it with all required datastructures (it needs to span the lifetime of all your logging efforts) +- At the location of the output, simply call Write with your own parameters. + +A guard (ScopedGeojsonLoggerGuard) requires a logging policy. Per default we provide a way of printing out node-ids as coordinates. + +The initialisation to do so looks like this: +`util::ScopedGeojsonLoggerGuard geojson_guard( "debug.geojson", data-for-conversion);` + +The field `data-for-conversion` can be an arbitrary long set of features and needs to match the parameters used for constructing our policy (in this case `util::NodeIdVectorToLineString`). + +The policy itself offers a `operator()` accepting a `vector` of `NodeID`. + +For outputting data into our file (debug.geojson), we simply need to call the matching logging routine of the guard: `util::ScioedGeojsonLoggerGuard::Write(list_of_node_ids);` +(or `guard.Write(list_of_node_ids)` if you created an instance). + +### Possible Scopeguard Location +Think of the scopeguard as you would do of any reference. If you wan't to access to logging during a call, the guard object must be alive and valid. + +As an example: a good location to create the a scopeguard to log decisions in the edge-based-graph-factory would be right before we run it ([here](https://github.com/Project-OSRM/osrm-backend/blob/a933b5d94943bf3edaf42c84a614a99650d23cba/src/extractor/extractor.cpp#L497)). If you put `util::ScopedGeojsonLoggerGuard geojson_guard( "debug.geojson", node_coordinate_vector);` at that location, you can then print `util::ScopedGeojsonLoggerGuard::Write(list_of_node_ids);` anywhere within the `edge-based-graph-factory`. + +This location would enable call for all guidance related pre-processing which is called in the edge-based-graph-factory. +Logging any turn-handler decisions, for example, would now be possible. + +## Limitations +GeoJson debugging requires a single GeoJsonGuard (ScopedGeojsonLoggerGuard) for each desired output file. +For each set of template parameters, only the most recent guard will actually produce output. + +`util::ScopedGeojsonLoggerGuard geojson_guard( "debug.geojson", data-for-conversion);` + +`util::ScopedGeojsonLoggerGuard geojson_guard( "debug-2.geojson", data-for-conversion);` + +Will not provide a way to write into two files, but only `debug-2` will actually contain features. + +We cannot nest-these calls. + +If we want to use the same policy for multiple files, we need to use different template parameters both for the logger and the guard. + +`util::ScopedGeojsonLoggerGuard geojson_guard( "debug.geojson", data-for-conversion);` + +`util::ScopedGeojsonLoggerGuard geojson_guard( "debug-2.geojson", data-for-conversion);` + +as well as, + +`util::ScopedGeojsonLoggerGuardr::Write(list_of_node_ids);` + +`util::ScopedGeojsonLoggerGuardr::Write(list_of_node_ids);` diff --git a/include/extractor/geojson_debug_policies.hpp b/include/extractor/geojson_debug_policies.hpp new file mode 100644 index 000000000..8a58914d3 --- /dev/null +++ b/include/extractor/geojson_debug_policies.hpp @@ -0,0 +1,42 @@ +#ifndef OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES +#define OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES + +#include + +#include "extractor/query_node.hpp" +#include "util/coordinate.hpp" +#include "util/json_container.hpp" +#include "util/node_based_graph.hpp" +#include "util/typedefs.hpp" + +#include "extractor/guidance/coordinate_extractor.hpp" +#include "extractor/guidance/intersection.hpp" +#include "extractor/guidance/toolkit.hpp" + +namespace osrm +{ +namespace extractor +{ +// generate a visualisation of an intersection, printing the coordinates used for angle calculation +struct IntersectionPrinter +{ + IntersectionPrinter(const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_coordinates, + const extractor::guidance::CoordinateExtractor &coordinate_extractor); + + // renders the used coordinate locations for all entries/as well as the resulting + // intersection-classification + util::json::Array operator()(const NodeID intersection_node, + const extractor::guidance::Intersection &intersection, + const boost::optional &node_style = {}, + const boost::optional &way_style = {}) const; + + const util::NodeBasedDynamicGraph &node_based_graph; + const std::vector &node_coordinates; + const extractor::guidance::CoordinateExtractor &coordinate_extractor; +}; + +} /* namespace extractor */ +} /* namespace osrm */ + +#endif /* OSRM_EXTRACTOR_GEOJSON_DEBUG_POLICIES */ diff --git a/include/util/geojson_debug_logger.hpp b/include/util/geojson_debug_logger.hpp new file mode 100644 index 000000000..353a4342e --- /dev/null +++ b/include/util/geojson_debug_logger.hpp @@ -0,0 +1,185 @@ +#ifndef OSRM_GEOJSON_DEBUG_LOGGER_HPP +#define OSRM_GEOJSON_DEBUG_LOGGER_HPP + +#include +#include +#include + +#include "util/json_container.hpp" +#include "util/json_renderer.hpp" +#include "util/simple_logger.hpp" + +namespace osrm +{ +namespace util +{ + +// in case we want to do scenario-based logging, we can specify a dedicated logging scenario to be +// able to use multiple files for the same converter +enum class LoggingScenario +{ + eDefault = 0 +}; + +// forward declaration to become friends +template class ScopedGeojsonLoggerGuard; + +// a geojson logger requires a conversion policy to transfer arbitrary input into viable geojson. +// features of the same kind are stored in the same geojson file +template +class GeojsonLogger +{ + // become friends with the guard + friend class ScopedGeojsonLoggerGuard; + + // having these private enforces the guard to be used to initialise/close/write + private: + // cannot lock, is tooling for locked function + static void output(bool first, const util::json::Object &object) + { + if (!first) + ofs << ",\n\t\t"; + // objects are simply forwarded + util::json::render(ofs, object); + } + + // cannot lock, is tooling for locked function + static void output(bool first, const util::json::Array &array) + { + for (const auto object : array.values) + { + if (!first) + ofs << ",\n\t\t"; + + util::json::render(ofs, object.get()); + + first = false; + } + } + + // writes a single feature into the Geojson file + template static bool Write(Args &&... args) + { + // make sure to syncronize logging output, our writing should be sequential + std::lock_guard guard(lock); + + // if there is no logfile, we cannot write + if (!ofs.is_open() || (nullptr == policy)) + { + // this can only happend between two guards when concurrent writing occurs + return false; + } + + // use our policy to convert the arguments into geojson, this can be done in parallel + const auto json_object = (*policy)(std::forward(args)...); + + // different features are separated by `,` + // since we are not building a full json collection of features, we have to do some of the + // bookeeping ourselves. This prevens us from doing one huge json object, if we are + // processing larger results + output(first, json_object); + first = false; + + return static_cast(ofs); + } + + // Opening a logger, we initialize a geojson feature collection + // to be later filled with additional data + static bool Open(const std::string &logfile) + { + // if a file is open, close it off. When this function returns, there should be an open + // logfile. However, there is a brief period between closing and openen where we could miss + // out on log output. Such a sad life + if (ofs.is_open()) + { + util::SimpleLogger().Write(logWARNING) + << "Overwriting " << logfile + << ". Is this desired behaviour? If this message occurs more than once rethink the " + "location of your Logger Guard."; + Close(); + } + + // make sure to syncronize logging output, cannot be locked earlier, since Close also locks + // and we don't want deadlocks + std::lock_guard guard(lock); + ofs.open(logfile, std::ios::binary); + + // set up a feature collection + ofs << "{\n\t\"type\": \"FeatureCollection\",\n\t\"features\": [\n\t"; + // remember whether we need to output a colon + first = true; + + return static_cast(ofs); + } + + // finalising touches on the GeoJson + static bool Close() + { + // make sure to syncronize logging output + std::lock_guard guard(lock); + + // finishe the geojson feature collection and close it all off + if (ofs.is_open()) + { + ofs << "\n\t]\n}"; + ofs.close(); + } + + return static_cast(ofs); + } + + static void SetPolicy(geojson_conversion_policy *new_policy) { policy = new_policy; } + + private: + static bool first; + static std::mutex lock; + static std::ofstream ofs; + + static geojson_conversion_policy *policy; +}; + +// make sure to do opening and closing of our guard +template +class ScopedGeojsonLoggerGuard +{ + public: + template + ScopedGeojsonLoggerGuard(const std::string &logfile, Args &&... args) + : policy(std::forward(args)...) + { + GeojsonLogger::Open(logfile); + GeojsonLogger::SetPolicy(&policy); + } + + ~ScopedGeojsonLoggerGuard() + { + GeojsonLogger::Close(); + GeojsonLogger::SetPolicy(nullptr); + } + + template static bool Write(Args &&... args) + { + return GeojsonLogger::Write( + std::forward(args)...); + } + + private: + geojson_conversion_policy policy; +}; + +template +bool GeojsonLogger::first; + +template +std::mutex GeojsonLogger::lock; + +template +std::ofstream GeojsonLogger::ofs; + +template +geojson_conversion_policy *GeojsonLogger::policy; + +} // namespace util +} // namespace osrm + +#endif /* OSRM_GEOJSON_DEBUG_LOGGER_HPP */ diff --git a/include/util/geojson_debug_policies.hpp b/include/util/geojson_debug_policies.hpp new file mode 100644 index 000000000..aed748a41 --- /dev/null +++ b/include/util/geojson_debug_policies.hpp @@ -0,0 +1,58 @@ +#ifndef OSRM_GEOJSON_DEBUG_POLICIES +#define OSRM_GEOJSON_DEBUG_POLICIES + +#include + +#include "extractor/query_node.hpp" +#include "util/coordinate.hpp" +#include "util/json_container.hpp" +#include "util/node_based_graph.hpp" +#include "util/typedefs.hpp" + +#include + +namespace osrm +{ +namespace util +{ + +struct NodeIdVectorToLineString +{ + NodeIdVectorToLineString(const std::vector &node_coordinates); + + // converts a vector of node ids into a linestring geojson feature + util::json::Object operator()(const std::vector &node_ids, + const boost::optional &properties = {}) const; + + const std::vector &node_coordinates; +}; + +struct CoordinateVectorToLineString +{ + // converts a vector of node ids into a linestring geojson feature + util::json::Object operator()(const std::vector &coordinates, + const boost::optional &properties = {}) const; +}; + +struct NodeIdVectorToMultiPoint +{ + NodeIdVectorToMultiPoint(const std::vector &node_coordinates); + + // converts a vector of node ids into a linestring geojson feature + util::json::Object operator()(const std::vector &node_ids, + const boost::optional &properties = {}) const; + + const std::vector &node_coordinates; +}; + +struct CoordinateVectorToMultiPoint +{ + // converts a vector of node ids into a linestring geojson feature + util::json::Object operator()(const std::vector &coordinates, + const boost::optional &properties = {}) const; +}; + +} /* namespace util */ +} /* namespace osrm */ + +#endif /* OSRM_GEOJSON_DEBUG_POLICIES */ diff --git a/include/util/geojson_debug_policy_toolkit.hpp b/include/util/geojson_debug_policy_toolkit.hpp new file mode 100644 index 000000000..1afcc6fb3 --- /dev/null +++ b/include/util/geojson_debug_policy_toolkit.hpp @@ -0,0 +1,112 @@ +#ifndef OSRM_GEOJSON_DEBUG_POLICY_TOOLKIT_HPP +#define OSRM_GEOJSON_DEBUG_POLICY_TOOLKIT_HPP + +#include "util/json_container.hpp" + +#include + +#include + +namespace osrm +{ +namespace util +{ + +enum GeojsonStyleSize +{ + tiny, + small, + medium, + large, + extra_large, + num_styles +}; + +enum GeojsonStyleColors +{ + red, + purple, + blue, + green, + yellow, + cyan, + brown, + pink, + num_colors +}; + +const constexpr char *geojson_debug_predifined_colors[GeojsonStyleColors::num_colors] = { + "#FF4848", "#800080", "#5757FF", "#1FCB4A", "#FFE920", "#29AFD6", "#B05F3C", "#FE67EB"}; + +const constexpr double geojson_predefined_sizes[GeojsonStyleSize::num_styles] = { + 2.0, 3.5, 5.0, 6.5, 8}; + +inline util::json::Object makeStyle(const GeojsonStyleSize size_type, + const GeojsonStyleColors predefined_color) +{ + util::json::Object style; + // style everything, since we don't know the feature type + style.values["stroke"] = geojson_debug_predifined_colors[predefined_color]; + style.values["circle-color"] = geojson_debug_predifined_colors[predefined_color]; + style.values["line-width"] = geojson_predefined_sizes[size_type]; + style.values["circle-radius"] = geojson_predefined_sizes[size_type]; + return style; +} + +struct CoordinateToJsonArray +{ + util::json::Array operator()(const util::Coordinate coordinate) + { + util::json::Array json_coordinate; + json_coordinate.values.push_back(static_cast(toFloating(coordinate.lon))); + json_coordinate.values.push_back(static_cast(toFloating(coordinate.lat))); + return json_coordinate; + } +}; + +struct NodeIdToCoordinate +{ + NodeIdToCoordinate(const std::vector &node_coordinates) + : node_coordinates(node_coordinates) + { + } + + const std::vector &node_coordinates; + + util::json::Array operator()(const NodeID nid) + { + auto coordinate = node_coordinates[nid]; + CoordinateToJsonArray converter; + return converter(coordinate); + } +}; + +inline util::json::Object makeFeature(std::string type, + util::json::Array coordinates, + const boost::optional &properties = {}) +{ + util::json::Object result; + result.values["type"] = "Feature"; + result.values["properties"] = properties ? *properties : util::json::Object(); + util::json::Object geometry; + geometry.values["type"] = std::move(type); + geometry.values["properties"] = util::json::Object(); + geometry.values["coordinates"] = std::move(coordinates); + result.values["geometry"] = geometry; + + return result; +} + +inline util::json::Array makeJsonArray(const std::vector &input_coordinates) +{ + util::json::Array coordinates; + std::transform(input_coordinates.begin(), + input_coordinates.end(), + std::back_inserter(coordinates.values), + CoordinateToJsonArray()); + return coordinates; +} +} // namespace util +} // namespace osrm + +#endif /* OSRM_GEOJSON_DEBUG_POLICY_TOOLKIT_HPP */ diff --git a/src/extractor/geojson_debug_policies.cpp b/src/extractor/geojson_debug_policies.cpp new file mode 100644 index 000000000..22b172ef0 --- /dev/null +++ b/src/extractor/geojson_debug_policies.cpp @@ -0,0 +1,68 @@ +#include "extractor/geojson_debug_policies.hpp" +#include "util/coordinate.hpp" +#include "util/geojson_debug_policy_toolkit.hpp" + +#include + +namespace osrm +{ +namespace extractor +{ + +IntersectionPrinter::IntersectionPrinter( + const util::NodeBasedDynamicGraph &node_based_graph, + const std::vector &node_coordinates, + const extractor::guidance::CoordinateExtractor &coordinate_extractor) + : node_based_graph(node_based_graph), node_coordinates(node_coordinates), + coordinate_extractor(coordinate_extractor){}; + +util::json::Array IntersectionPrinter:: +operator()(const NodeID intersection_node, + const extractor::guidance::Intersection &intersection, + const boost::optional &node_style, + const boost::optional &way_style) const +{ + // request the number of lanes. This process needs to be in sync with what happens over at + // intersection_generator + const auto intersection_lanes = + extractor::guidance::getLaneCountAtIntersection(intersection_node, node_based_graph); + + std::vector coordinates; + coordinates.reserve(intersection.size()); + coordinates.push_back(node_coordinates[intersection_node]); + + const auto road_to_coordinate = [&](const extractor::guidance::ConnectedRoad &connected_road) { + const constexpr auto FORWARD = false; + const auto to_node = node_based_graph.GetTarget(connected_road.turn.eid); + return coordinate_extractor.GetCoordinateAlongRoad( + intersection_node, connected_road.turn.eid, FORWARD, to_node, intersection_lanes); + }; + + std::transform(intersection.begin(), + intersection.end(), + std::back_inserter(coordinates), + road_to_coordinate); + + util::json::Array features; + features.values.push_back( + util::makeFeature("MultiPoint", makeJsonArray(coordinates), node_style)); + + if (coordinates.size() > 1) + { + std::vector line_coordinates(2); + line_coordinates[0] = coordinates.front(); + const auto coordinate_to_line = [&](const util::Coordinate coordinate) { + line_coordinates[1] = coordinate; + return util::makeFeature("LineString", makeJsonArray(line_coordinates), way_style); + }; + + std::transform(std::next(coordinates.begin()), + coordinates.end(), + std::back_inserter(features.values), + coordinate_to_line); + } + return features; +} + +} /* namespace extractor */ +} /* namespace osrm */ diff --git a/src/util/geojson_debug_policies.cpp b/src/util/geojson_debug_policies.cpp new file mode 100644 index 000000000..ae173dff8 --- /dev/null +++ b/src/util/geojson_debug_policies.cpp @@ -0,0 +1,71 @@ +#include "util/geojson_debug_policies.hpp" +#include "util/coordinate.hpp" +#include "util/geojson_debug_policy_toolkit.hpp" + +#include + +namespace osrm +{ +namespace util +{ + +//---------------------------------------------------------------- +NodeIdVectorToLineString::NodeIdVectorToLineString( + const std::vector &node_coordinates) + : node_coordinates(node_coordinates) +{ +} + +// converts a vector of node ids into a linestring geojson feature +util::json::Object NodeIdVectorToLineString:: +operator()(const std::vector &node_ids, + const boost::optional &properties) const +{ + util::json::Array coordinates; + std::transform(node_ids.begin(), + node_ids.end(), + std::back_inserter(coordinates.values), + NodeIdToCoordinate(node_coordinates)); + + return makeFeature("LineString", std::move(coordinates), properties); +} + +//---------------------------------------------------------------- +NodeIdVectorToMultiPoint::NodeIdVectorToMultiPoint( + const std::vector &node_coordinates) + : node_coordinates(node_coordinates) +{ +} +util::json::Object NodeIdVectorToMultiPoint:: +operator()(const std::vector &node_ids, + const boost::optional &properties) const +{ + util::json::Array coordinates; + std::transform(node_ids.begin(), + node_ids.end(), + std::back_inserter(coordinates.values), + NodeIdToCoordinate(node_coordinates)); + + return makeFeature("MultiPoint", std::move(coordinates), properties); +} + +//---------------------------------------------------------------- +util::json::Object CoordinateVectorToMultiPoint:: +operator()(const std::vector &input_coordinates, + const boost::optional &properties) const +{ + const auto coordinates = makeJsonArray(input_coordinates); + return makeFeature("MultiPoint", std::move(coordinates), properties); +} + +//---------------------------------------------------------------- +util::json::Object CoordinateVectorToLineString:: +operator()(const std::vector &input_coordinates, + const boost::optional &properties) const +{ + const auto coordinates = makeJsonArray(input_coordinates); + return makeFeature("LineString", std::move(coordinates), properties); +} + +} /* namespace util */ +} /* namespace osrm */