diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..31cd063 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +- CLI tools +- Windows and MacOS wheels +- Table api +- Trip api +- Nearest api + +## [0.0.2] - 2020-03-08 +### Changed +- Now path parameter is optional, and you can use either a valid path or + use_shared_memory=True to initialize a PyOSRM object + +### Added +- path or use_shared_memory validation on PyOSRM object initialization + +## [0.0.1] - 2020-03-03 +### Added +- PyOSRM class with working route method +- RouterResult class +- Status enum +- Working examples on readme and tests +- test data unprocessed (raw) and preprocessed for CH and MLD diff --git a/README.md b/README.md index ccec5a8..7434863 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,19 @@ python setup.py build_ext --inplace ``` ## Usage -It is most likely you will need to install osrm-backend first to pre-process the data, since this package does not provide the cli tools to do it yet. Follow the instructions in the [project wiki](https://github.com/Project-OSRM/osrm-backend/wiki/Running-OSRM#quickstart) to pre-process the data using the desired algorithm (CH or MLD). +If you installed pyosrm using pip, you don't need to have osrm-backend installed, but it is most likely you will it first to pre-process the data, since this package does not provide the cli tools to do it yet. Follow the instructions in the [project wiki](https://github.com/Project-OSRM/osrm-backend/wiki/Running-OSRM#quickstart) to pre-process the data using the desired algorithm (CH or MLD). To create a PyOSRM object, you need to pass the path to the pre-processed data, and the algorithm (default 'CH' or 'MLD'). ``` import pyosrm router = posrm.PyOSRM("tests/data/ch/monaco-latest.osrm") ``` +For large datasets, it may be required [a lot of RAM](https://github.com/Project-OSRM/osrm-backend/wiki/Disk-and-Memory-Requirements) to run osrm. For this reason, if you have more than one python process instanciating a PyOSRM object, it is recommended to use shared memory instead. +``` +import pyosrm +router = posrm.PyOSRM(use_shared_memory=True) +``` +Refer to the [documentation](https://github.com/Project-OSRM/osrm-backend/wiki/Configuring-and-using-Shared-Memory) for more information about using shared memory with osrm. ### Route To use the Route API, you just need to pass a list of coordinate pairs in format [lon, lat]. The easiest way to get the result is by using the RouteResult.json method, which formats the data in a easily serializable dictionary like the original API [result object](http://project-osrm.org/docs/v5.22.0/api/?language=cURL#result-objects). ``` diff --git a/setup.py b/setup.py index b18c304..37be23f 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ setup( name='pyosrm', - version='0.0.1', + version='0.0.2', license='MIT', description='Cython wrapper of osrm-backend to be used in Python', long_description=long_description, diff --git a/src/boost.pxd b/src/boost.pxd index 39dcb9f..7703abc 100644 --- a/src/boost.pxd +++ b/src/boost.pxd @@ -1,9 +1,9 @@ from libcpp.vector cimport vector cdef extern from "filesystem.hpp" namespace "boost::filesystem": - cdef cppclass path: - pass + cdef cppclass path: + pass cdef extern from "optional.hpp" namespace "boost::optional_ns": - cdef cppclass optional[T]: - pass + cdef cppclass optional[T]: + pass diff --git a/src/osrm.pxd b/src/osrm.pxd index 11fe569..3c3f84a 100644 --- a/src/osrm.pxd +++ b/src/osrm.pxd @@ -5,133 +5,133 @@ from libcpp.unordered_map cimport unordered_map from boost cimport path, optional cdef extern from "storage/storage_config.hpp" namespace "osrm::storage": - cdef cppclass IOConfig: - IOConfig (vector[path] required_input_files_, - vector[path] optional_input_files_, - vector[path] output_files_) except + - bool IsValid() - path GetPath(string& fileName) - path base_path - - cdef cppclass StorageConfig(IOConfig): - StorageConfig(path& base) - StorageConfig(char* base) + cdef cppclass IOConfig: + IOConfig (vector[path] required_input_files_, + vector[path] optional_input_files_, + vector[path] output_files_) except + + bool IsValid() + path GetPath(string& fileName) + path base_path + + cdef cppclass StorageConfig(IOConfig): + StorageConfig(path& base) + StorageConfig(char* base) cdef extern from "engine_config.hpp" namespace "osrm": - ctypedef enum Algorithm: - CH "osrm::EngineConfig::Algorithm::CH" - MLD "osrm::EngineConfig::Algorithm::MLD" - - struct EngineConfig: - bool IsValid() - StorageConfig storage_config - int max_locations_trip - int max_locations_viaroute - int max_locations_distance_table - int max_locations_map_matching - double max_radius_map_matching - int max_results_nearest - int max_alternatives - bool use_shared_memory - path memory_file - bool use_mmap - Algorithm algorithm - string verbosity - string dataset_name + ctypedef enum Algorithm: + CH "osrm::EngineConfig::Algorithm::CH" + MLD "osrm::EngineConfig::Algorithm::MLD" + + struct EngineConfig: + bool IsValid() + StorageConfig storage_config + int max_locations_trip + int max_locations_viaroute + int max_locations_distance_table + int max_locations_map_matching + double max_radius_map_matching + int max_results_nearest + int max_alternatives + bool use_shared_memory + path memory_file + bool use_mmap + Algorithm algorithm + string verbosity + string dataset_name cdef extern from "status.hpp" namespace "osrm::engine": - ctypedef enum Status: - Ok "osrm::engine::Status::Ok" - Error "osrm::engine::Status::Error" + ctypedef enum Status: + Ok "osrm::engine::Status::Ok" + Error "osrm::engine::Status::Error" cdef extern from "bearing.hpp" namespace "osrm": - struct Bearing: - short bearing - short range - bool IsValid() + struct Bearing: + short bearing + short range + bool IsValid() cdef extern from "approach.hpp" namespace "osrm": - cdef cppclass Approach: - pass + cdef cppclass Approach: + pass cdef extern from "engine/hint.hpp" namespace "osrm::engine": - struct Hint: - pass + struct Hint: + pass cdef extern from "util/coordinate.hpp" namespace "osrm::util": - cdef cppclass FixedLongitude: - pass - cdef cppclass FixedLatitude: - pass - cdef cppclass FloatLongitude: - # pass - FloatLongitude() - FloatLongitude(double) - double __value - cdef cppclass FloatLatitude: - # pass - FloatLatitude() - FloatLatitude(double) - double __value - cdef cppclass Coordinate: - FixedLongitude lon - FixedLatitude lat - Coordinate() - Coordinate(FixedLongitude lon_, FixedLatitude lat_) - Coordinate(FloatLongitude lon_, FloatLatitude lat_) - bool IsValid() + cdef cppclass FixedLongitude: + pass + cdef cppclass FixedLatitude: + pass + cdef cppclass FloatLongitude: + # pass + FloatLongitude() + FloatLongitude(double) + double __value + cdef cppclass FloatLatitude: + # pass + FloatLatitude() + FloatLatitude(double) + double __value + cdef cppclass Coordinate: + FixedLongitude lon + FixedLatitude lat + Coordinate() + Coordinate(FixedLongitude lon_, FixedLatitude lat_) + Coordinate(FloatLongitude lon_, FloatLatitude lat_) + bool IsValid() cdef extern from "engine/api/base_parameters.hpp" namespace "osrm::engine::api": - cdef cppclass SnappingType: - pass - # Default "osrm::engine::api::BaseParameters::SnappingType::Default" - # Any "osrm::engine::api::BaseParameters::SnappingType::Any" - - cdef cppclass OutputFormatType: - pass - # JSON "osrm::engine::api::BaseParameters::OutputFormatType::JSON" - # FLATBUFFERS "osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS" - - cdef cppclass BaseParameters: - BaseParameters(vector[Coordinate] coordinates_, vector[optional[Hint]] hints_, - vector[optional[double]] radiuses_, vector[optional[Bearing]] bearings_, - vector[optional[Approach]] approaches_, bool generate_hints_, - vector[string] exclude, SnappingType snapping_) - vector[Coordinate] coordinates - vector[optional[Hint]] hints - vector[optional[double]] radiuses - vector[optional[Bearing]] bearings - vector[optional[Approach]] approaches - vector[string] exclude - optional[OutputFormatType] format - bool generate_hints - bool skip_waypoints - SnappingType snapping + cdef cppclass SnappingType: + pass + # Default "osrm::engine::api::BaseParameters::SnappingType::Default" + # Any "osrm::engine::api::BaseParameters::SnappingType::Any" + + cdef cppclass OutputFormatType: + pass + # JSON "osrm::engine::api::BaseParameters::OutputFormatType::JSON" + # FLATBUFFERS "osrm::engine::api::BaseParameters::OutputFormatType::FLATBUFFERS" + + cdef cppclass BaseParameters: + BaseParameters(vector[Coordinate] coordinates_, vector[optional[Hint]] hints_, + vector[optional[double]] radiuses_, vector[optional[Bearing]] bearings_, + vector[optional[Approach]] approaches_, bool generate_hints_, + vector[string] exclude, SnappingType snapping_) + vector[Coordinate] coordinates + vector[optional[Hint]] hints + vector[optional[double]] radiuses + vector[optional[Bearing]] bearings + vector[optional[Approach]] approaches + vector[string] exclude + optional[OutputFormatType] format + bool generate_hints + bool skip_waypoints + SnappingType snapping cdef extern from "route_parameters.hpp" namespace "osrm": - cdef cppclass RouteParameters(BaseParameters): - pass + cdef cppclass RouteParameters(BaseParameters): + pass cdef extern from "util/json_container.hpp" namespace "osrm::util::json": - cdef cppclass Value: - T get[T]() - cdef cppclass _JsonObject "osrm::util::json::Object": - unordered_map[string, Value] values - _JsonObject() - struct _Number "osrm::util::json::Number": - double value - struct _Array "osrm::util::json::Array": - vector[Value] values - struct _String "osrm::util::json::String": - string value + cdef cppclass Value: + T get[T]() + cdef cppclass _JsonObject "osrm::util::json::Object": + unordered_map[string, Value] values + _JsonObject() + struct _Number "osrm::util::json::Number": + double value + struct _Array "osrm::util::json::Array": + vector[Value] values + struct _String "osrm::util::json::String": + string value cdef extern from "engine/api/base_result.hpp" namespace "osrm::engine::api": - cdef cppclass ResultT: - ResultT(_JsonObject value) - T get[T]() + cdef cppclass ResultT: + ResultT(_JsonObject value) + T get[T]() cdef extern from "osrm.hpp" namespace "osrm": - cdef cppclass OSRM: - OSRM() except + - OSRM(EngineConfig &config) except + - Status Route(RouteParameters ¶meters, ResultT &result) + cdef cppclass OSRM: + OSRM() except + + OSRM(EngineConfig &config) except + + Status Route(RouteParameters ¶meters, ResultT &result) diff --git a/src/pyosrm/core.pyx b/src/pyosrm/core.pyx index b593c05..351fee9 100644 --- a/src/pyosrm/core.pyx +++ b/src/pyosrm/core.pyx @@ -1,18 +1,23 @@ cimport osrm from enum import Enum +import os cdef class PyOSRM: cdef: osrm.OSRM* _thisptr - def __cinit__(self, path, algorithm="CH", use_shared_memory=False): - encoded_path = path.encode("UTF-8") + def __cinit__(self, path="", algorithm="CH", use_shared_memory=False): cdef osrm.EngineConfig engine_config - cdef char* path_c = encoded_path - cdef osrm.StorageConfig *store_config = new osrm.StorageConfig(path_c) - engine_config.storage_config = store_config[0] + cdef char* path_c + cdef osrm.StorageConfig *store_config + if os.path.exists(path): + encoded_path = path.encode("UTF-8") + path_c = encoded_path + store_config = new osrm.StorageConfig(path_c) + engine_config.storage_config = store_config[0] + elif not use_shared_memory: + raise ValueError("You need either a valid path or use_shared_memory True") engine_config.use_shared_memory = use_shared_memory - if algorithm=="CH": engine_config.algorithm = osrm.Algorithm.CH elif algorithm=="MLD": diff --git a/tests/conftest.py b/tests/conftest.py index 84c31ee..871c75f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,8 @@ import pyosrm @pytest.fixture(params=[{"algorithm": 'CH', "path": "tests/data/ch/monaco-latest.osrm"}, - {"algorithm": 'MLD', "path": "tests/data/mld/monaco-latest.osrm"}]) + {"algorithm": 'MLD', "path": "tests/data/mld/monaco-latest.osrm"}], + scope='session') def initialized_router_instance(request): router = pyosrm.PyOSRM(request.param["path"], algorithm=request.param["algorithm"]) return router diff --git a/tests/func/test_route.py b/tests/func/test_route.py index 92a22ca..e3a9f16 100644 --- a/tests/func/test_route.py +++ b/tests/func/test_route.py @@ -3,11 +3,11 @@ valid_coords = ([[7.419758, 43.731142], [7.419505, 43.736825]], ) -@pytest.fixture(params=valid_coords) +@pytest.fixture(params=valid_coords, scope='class') def valid_route_result(request, initialized_router_instance): return initialized_router_instance.route(request.param) -@pytest.fixture() +@pytest.fixture(scope='class') def valid_result_dict(valid_route_result): return valid_route_result.json() @@ -81,3 +81,17 @@ def test_location_in_waypoint_dicts(self, valid_result_dict): def test_code_in_result_dict(self, valid_result_dict): assert "code" in valid_result_dict and valid_result_dict["code"] == "Ok" + +class TestRouteExceptions: + + def test_invalid_path_initialization(self): + with pytest.raises(ValueError): + pyosrm.PyOSRM("") + + def test_no_parameters_initialization(self): + with pytest.raises(ValueError): + pyosrm.PyOSRM() + + def test_invalid_algorithm_parameter(self): + with pytest.raises(ValueError): + pyosrm.PyOSRM("/", algorithm="dijkstra")