From 5770f683fe15bf71b6fd6707414df64336f07cde Mon Sep 17 00:00:00 2001 From: VladiMihaylenko Date: Wed, 6 Mar 2019 18:55:03 +0300 Subject: [PATCH] Track generator bindings. --- track_generator/CMakeLists.txt | 2 + .../pytrack_generator/CMakeLists.txt | 25 +++ .../pytrack_generator/bindings.cpp | 152 ++++++++++++++++++ .../pytrack_generator_test.py | 58 +++++++ 4 files changed, 237 insertions(+) create mode 100644 track_generator/pytrack_generator/CMakeLists.txt create mode 100644 track_generator/pytrack_generator/bindings.cpp create mode 100644 track_generator/pytrack_generator/pytrack_generator_test.py diff --git a/track_generator/CMakeLists.txt b/track_generator/CMakeLists.txt index 0ab473bd137..9139c92ba9d 100644 --- a/track_generator/CMakeLists.txt +++ b/track_generator/CMakeLists.txt @@ -36,5 +36,7 @@ omim_link_libraries( ${LIBZ} ) +omim_add_pybindings_subdirectory(pytrack_generator) + link_qt5_core(${PROJECT_NAME}) link_qt5_network(${PROJECT_NAME}) diff --git a/track_generator/pytrack_generator/CMakeLists.txt b/track_generator/pytrack_generator/CMakeLists.txt new file mode 100644 index 00000000000..5eafe0621a2 --- /dev/null +++ b/track_generator/pytrack_generator/CMakeLists.txt @@ -0,0 +1,25 @@ +project(pytrack_generator) + +set( + SRC + bindings.cpp +) + +include_directories(${CMAKE_BINARY_DIR}) + +omim_add_library(${PROJECT_NAME} MODULE ${SRC}) + +omim_link_libraries( + ${PROJECT_NAME} + ${PYTHON_LIBRARIES} + ${Boost_LIBRARIES} + routing_quality + routing + routing_common + coding + geometry + base + stats_client +) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") diff --git a/track_generator/pytrack_generator/bindings.cpp b/track_generator/pytrack_generator/bindings.cpp new file mode 100644 index 00000000000..d256942d62c --- /dev/null +++ b/track_generator/pytrack_generator/bindings.cpp @@ -0,0 +1,152 @@ +#include "routing/route.hpp" +#include "routing/routing_callbacks.hpp" +#include "routing/routing_quality/utils.hpp" + +#include "platform/platform.hpp" + +#include "geometry/latlon.hpp" + +#include +#include +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wreorder" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#endif + +#include "pyhelpers/module_version.hpp" +#include "pyhelpers/vector_list_conversion.hpp" + +#include +#include +#include + +using namespace std; + +namespace +{ +class RouteNotFoundException : public exception +{ +public: + RouteNotFoundException(string const & msg) : m_msg(msg) {} + + virtual ~RouteNotFoundException() noexcept = default; + + char const * what() const noexcept override { return m_msg.c_str(); } + +private: + string m_msg; +}; + +PyObject * kRouteNotFoundException = nullptr; + +PyObject * CreateExceptionClass(char const * name) +{ + using namespace boost::python; + string const scopeName = extract(scope().attr("__name__")); + string const qualifiedName = scopeName + "." + name; + PyObject * ex = PyErr_NewException(qualifiedName.c_str(), PyExc_Exception, nullptr); + CHECK(ex, ()); + scope().attr(name) = handle<>(borrowed(ex)); + return ex; +} + +template +void Translate(PyObject * object, Exception const & e) +{ + PyErr_SetString(object, e.what()); +} + +struct Params +{ + Params(string const & data, string const & userResources) : m_dataPath(data), m_userResourcesPath(userResources) + { + if (m_dataPath.empty()) + throw runtime_error("data_path parameter not specified"); + + if (m_userResourcesPath.empty()) + throw runtime_error("user_resources_path parameter not specified"); + } + + string DebugPrint() const + { + ostringstream ss; + ss << "Params(data path: " << m_dataPath << ", user resources path: " << m_userResourcesPath << ")"; + return ss.str(); + } + + string m_dataPath; + string m_userResourcesPath; +}; + +using Track = vector; + +Track GetTrackFrom(routing::Route const & route) +{ + CHECK(route.IsValid(), ()); + auto const & segments = route.GetRouteSegments(); + Track res; + res.reserve(segments.size()); + for (auto const & s : segments) + res.emplace_back(MercatorBounds::ToLatLon(s.GetJunction().GetPoint())); + + return res; +} + +struct Generator +{ + explicit Generator(Params const & params) : m_params(params) + { + Platform & pl = GetPlatform(); + pl.SetWritableDirForTests(m_params.m_dataPath); + pl.SetResourceDir(m_params.m_userResourcesPath); + } + + Track Generate(boost::python::object const & iterable) const + { + using namespace routing_quality; + + Track const coordinates = python_list_to_std_vector(iterable); + auto result = GetRoute(FromLatLon(coordinates), routing::VehicleType::Pedestrian); + if (result.m_code != routing::RouterResultCode::NoError) + throw RouteNotFoundException("Can't build route"); + + return GetTrackFrom(result.m_route); + } + + Params m_params; +}; +} // namespace + +using namespace boost::python; + +BOOST_PYTHON_MODULE(pytrack_generator) +{ + scope().attr("__version__") = PYBINDINGS_VERSION; + register_exception_translator([](auto const & e) { Translate(PyExc_RuntimeError, e); }); + + kRouteNotFoundException = CreateExceptionClass("RouteNotFoundException"); + register_exception_translator([](auto const & e) { Translate(kRouteNotFoundException, e); }); + + class_("Params", init()) + .def("__str__", &Params::DebugPrint) + .def_readonly("data_path", &Params::m_dataPath) + .def_readonly("user_resources_path", &Params::m_userResourcesPath); + + class_("LatLon", init()) + .def("__str__", &ms::DebugPrint) + .def_readonly("lat", &ms::LatLon::lat) + .def_readonly("lon", &ms::LatLon::lon); + + class_>("LatLonList") + .def(vector_indexing_suite>()); + + class_("Generator", init()) + .def("generate", &Generator::Generate) + .def_readonly("params", &Generator::m_params); +} diff --git a/track_generator/pytrack_generator/pytrack_generator_test.py b/track_generator/pytrack_generator/pytrack_generator_test.py new file mode 100644 index 00000000000..5841196471a --- /dev/null +++ b/track_generator/pytrack_generator/pytrack_generator_test.py @@ -0,0 +1,58 @@ +import argparse +import importlib.util +import sys + +def _usage(): + print("pytrack_generator_tests.py \ + --module_path path/to/pytrack_generator.so \ + --data_path path/to/omim/data \ + --user_resource_path path/to/omim/data") + +def _main(): + parser = argparse.ArgumentParser() + parser.add_argument( + '--module_path', + type=str + ) + parser.add_argument( + '--data_path', + type=str + ) + + parser.add_argument( + '--user_resources_path', + type=str, + ) + + args = parser.parse_args(sys.argv[1:]) + if not args.module_path or not args.data_path or not args.user_resources_path: + _usage() + sys.exit(2) + + spec = importlib.util.spec_from_file_location("pytrack_generator", args.module_path) + ge = importlib.util.module_from_spec(spec) + spec.loader.exec_module(ge) + + params = ge.Params(args.data_path, args.user_resources_path) + generator = ge.Generator(params) + + points = ge.LatLonList() + points.append(ge.LatLon(55.796993, 37.537640)) + points.append(ge.LatLon(55.798087, 37.539002)) + + result = generator.generate(points) + assert len(result) > len(points) + + try: + invalid_points = ge.LatLonList() + invalid_points.append(ge.LatLon(20, 20)) + invalid_points.append(ge.LatLon(20, 20)) + generator.generate(invalid_points) + except ge.RouteNotFoundException as ex: + print(ex) + return + + assert False + +if __name__ == '__main__': + _main()