From bac6703f8e0d089aa58b7fefee8bba7204e4e492 Mon Sep 17 00:00:00 2001 From: Lauren Budorick Date: Fri, 29 May 2015 17:28:29 -0700 Subject: [PATCH] Implement raster source feature to read data from third-party sources, to be used in lua profiles. * Adds a data structure, RasterSource, to store parsed + queryable data * Adds bindings for that and relevant data structures as well as source_function and segment_function * Adds relevant unit tests and cucumber tests * Bring-your-own-data feature --- CMakeLists.txt | 5 +- appveyor-build.bat | 6 +- data_structures/raster_source.cpp | 178 +++++++++++++++++++ data_structures/raster_source.hpp | 175 ++++++++++++++++++ extractor/extraction_containers.cpp | 21 ++- extractor/extraction_containers.hpp | 6 +- extractor/extractor.cpp | 17 +- extractor/lat | 0 extractor/scripting_environment.cpp | 26 ++- extractor/source_coordinate.lat | 0 features/raster/extract.feature | 18 ++ features/raster/weights.feature | 78 ++++++++ features/step_definitions/data.rb | 6 + features/support/env.rb | 3 +- profiles/rasterbot-interp.lua | 46 +++++ profiles/rasterbot.lua | 46 +++++ test/rastersource.asc | 5 + unit_tests/data_structures/raster_source.cpp | 110 ++++++++++++ unit_tests/fixtures/raster_data.asc | 10 ++ 19 files changed, 744 insertions(+), 12 deletions(-) create mode 100644 data_structures/raster_source.cpp create mode 100644 data_structures/raster_source.hpp create mode 100644 extractor/lat create mode 100644 extractor/source_coordinate.lat create mode 100644 features/raster/extract.feature create mode 100644 features/raster/weights.feature create mode 100644 profiles/rasterbot-interp.lua create mode 100644 profiles/rasterbot.lua create mode 100644 test/rastersource.asc create mode 100644 unit_tests/data_structures/raster_source.cpp create mode 100644 unit_tests/fixtures/raster_data.asc diff --git a/CMakeLists.txt b/CMakeLists.txt index 990ce884c..e0b8f1016 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,10 +51,11 @@ configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/util/git_sha.cpp ) file(GLOB ExtractorGlob extractor/*.cpp) -file(GLOB ImporterGlob data_structures/import_edge.cpp data_structures/external_memory_node.cpp) +file(GLOB ImporterGlob data_structures/import_edge.cpp data_structures/external_memory_node.cpp data_structures/raster_source.cpp) add_library(IMPORT OBJECT ${ImporterGlob}) add_library(LOGGER OBJECT util/simple_logger.cpp) add_library(PHANTOMNODE OBJECT data_structures/phantom_node.cpp) +add_library(RASTERSOURCE OBJECT data_structures/raster_source.cpp) add_library(EXCEPTION OBJECT util/osrm_exception.cpp) add_library(MERCATOR OBJECT util/mercator.cpp) add_library(ANGLE OBJECT util/compute_angle.cpp) @@ -102,7 +103,7 @@ add_executable(osrm-routed routed.cpp ${ServerGlob} $) add_executable(osrm-datastore datastore.cpp $ $ $ $ $ $) # Unit tests -add_executable(datastructure-tests EXCLUDE_FROM_ALL unit_tests/datastructure_tests.cpp ${DataStructureTestsGlob} $ $ $ $ $ $ $ $) +add_executable(datastructure-tests EXCLUDE_FROM_ALL unit_tests/datastructure_tests.cpp ${DataStructureTestsGlob} $ $ $ $ $ $ $ $ $) add_executable(algorithm-tests EXCLUDE_FROM_ALL unit_tests/algorithm_tests.cpp ${AlgorithmTestsGlob} $ $ $ $ $ $) # Benchmarks diff --git a/appveyor-build.bat b/appveyor-build.bat index 9e49f1ae8..cffa5261a 100644 --- a/appveyor-build.bat +++ b/appveyor-build.bat @@ -65,12 +65,14 @@ IF %ERRORLEVEL% NEQ 0 GOTO ERROR SET PATH=c:\projects\osrm\osrm-deps\libs\bin;%PATH% +CD .. ECHO running datastructure-tests.exe ... -datastructure-tests.exe +%Configuration%\datastructure-tests.exe IF %ERRORLEVEL% NEQ 0 GOTO ERROR ECHO running algorithm-tests.exe ... -algorithm-tests.exe +%Configuration%\algorithm-tests.exe IF %ERRORLEVEL% NEQ 0 GOTO ERROR +cd %Configuration% IF NOT "%APPVEYOR_REPO_BRANCH%"=="develop" GOTO DONE ECHO ========= CREATING PACKAGES ========== diff --git a/data_structures/raster_source.cpp b/data_structures/raster_source.cpp new file mode 100644 index 000000000..f935b9d87 --- /dev/null +++ b/data_structures/raster_source.cpp @@ -0,0 +1,178 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "raster_source.hpp" + +#include "../util/simple_logger.hpp" +#include "../util/timing_util.hpp" + +#include + +#include + +RasterSource::RasterSource(RasterGrid _raster_data, + std::size_t _width, + std::size_t _height, + int _xmin, + int _xmax, + int _ymin, + int _ymax) + : xstep(calcSize(_xmin, _xmax, _width)), ystep(calcSize(_ymin, _ymax, _height)), + raster_data(_raster_data), width(_width), height(_height), xmin(_xmin), xmax(_xmax), + ymin(_ymin), ymax(_ymax) +{ + BOOST_ASSERT(xstep != 0); + BOOST_ASSERT(ystep != 0); +} + +float RasterSource::calcSize(int min, int max, std::size_t count) const +{ + BOOST_ASSERT(count > 0); + return (max - min) / (static_cast(count) - 1); +} + +// Query raster source for nearest data point +RasterDatum RasterSource::getRasterData(const int lon, const int lat) const +{ + if (lon < xmin || lon > xmax || lat < ymin || lat > ymax) + { + return {}; + } + + const std::size_t xth = static_cast(round((lon - xmin) / xstep)); + const std::size_t yth = static_cast(round((ymax - lat) / ystep)); + + return {raster_data(xth, yth)}; +} + +// Query raster source using bilinear interpolation +RasterDatum RasterSource::getRasterInterpolate(const int lon, const int lat) const +{ + if (lon < xmin || lon > xmax || lat < ymin || lat > ymax) + { + return {}; + } + + const auto xthP = (lon - xmin) / xstep; + const auto ythP = (ymax - lat) / ystep; + + const std::size_t top = static_cast(fmax(floor(ythP), 0)); + const std::size_t bottom = static_cast(fmin(ceil(ythP), height - 1)); + const std::size_t left = static_cast(fmax(floor(xthP), 0)); + const std::size_t right = static_cast(fmin(ceil(xthP), width - 1)); + + // Calculate distances from corners for bilinear interpolation + const float fromLeft = (lon - left * xstep + xmin) / xstep; + const float fromTop = (ymax - top * ystep - lat) / ystep; + const float fromRight = 1 - fromLeft; + const float fromBottom = 1 - fromTop; + + return {static_cast(raster_data(left, top) * (fromRight * fromBottom) + + raster_data(right, top) * (fromLeft * fromBottom) + + raster_data(left, bottom) * (fromRight * fromTop) + + raster_data(right, bottom) * (fromLeft * fromTop))}; +} + +// Load raster source into memory +int SourceContainer::loadRasterSource(const std::string &path_string, + double xmin, + double xmax, + double ymin, + double ymax, + std::size_t nrows, + std::size_t ncols) +{ + const auto _xmin = static_cast(xmin * COORDINATE_PRECISION); + const auto _xmax = static_cast(xmax * COORDINATE_PRECISION); + const auto _ymin = static_cast(ymin * COORDINATE_PRECISION); + const auto _ymax = static_cast(ymax * COORDINATE_PRECISION); + + const auto itr = LoadedSourcePaths.find(path_string); + if (itr != LoadedSourcePaths.end()) + { + SimpleLogger().Write() << "[source loader] Already loaded source '" << path_string + << "' at source_id " << itr->second; + return itr->second; + } + + int source_id = static_cast(LoadedSources.size()); + + SimpleLogger().Write() << "[source loader] Loading from " << path_string << " ... "; + TIMER_START(loading_source); + + boost::filesystem::path filepath(path_string); + if (!boost::filesystem::exists(filepath)) + { + throw osrm::exception("error reading: no such path"); + } + + RasterGrid rasterData{filepath, ncols, nrows}; + + RasterSource source{std::move(rasterData), ncols, nrows, _xmin, _xmax, _ymin, _ymax}; + TIMER_STOP(loading_source); + LoadedSourcePaths.emplace(path_string, source_id); + LoadedSources.push_back(std::move(source)); + + SimpleLogger().Write() << "[source loader] ok, after " << TIMER_SEC(loading_source) << "s"; + + return source_id; +} + +// External function for looking up nearest data point from a specified source +RasterDatum SourceContainer::getRasterDataFromSource(unsigned int source_id, int lon, int lat) +{ + if (LoadedSources.size() < source_id + 1) + { + throw osrm::exception("error reading: no such loaded source"); + } + + BOOST_ASSERT(lat < (90 * COORDINATE_PRECISION)); + BOOST_ASSERT(lat > (-90 * COORDINATE_PRECISION)); + BOOST_ASSERT(lon < (180 * COORDINATE_PRECISION)); + BOOST_ASSERT(lon > (-180 * COORDINATE_PRECISION)); + + const auto &found = LoadedSources[source_id]; + return found.getRasterData(lon, lat); +} + +// External function for looking up interpolated data from a specified source +RasterDatum +SourceContainer::getRasterInterpolateFromSource(unsigned int source_id, int lon, int lat) +{ + if (LoadedSources.size() < source_id + 1) + { + throw osrm::exception("error reading: no such loaded source"); + } + + BOOST_ASSERT(lat < (90 * COORDINATE_PRECISION)); + BOOST_ASSERT(lat > (-90 * COORDINATE_PRECISION)); + BOOST_ASSERT(lon < (180 * COORDINATE_PRECISION)); + BOOST_ASSERT(lon > (-180 * COORDINATE_PRECISION)); + + const auto &found = LoadedSources[source_id]; + return found.getRasterInterpolate(lon, lat); +} diff --git a/data_structures/raster_source.hpp b/data_structures/raster_source.hpp new file mode 100644 index 000000000..86c859639 --- /dev/null +++ b/data_structures/raster_source.hpp @@ -0,0 +1,175 @@ +/* + +Copyright (c) 2015, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#ifndef RASTER_SOURCE_HPP +#define RASTER_SOURCE_HPP + +#include "../util/osrm_exception.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + \brief Small wrapper around raster source queries to optionally provide results + gracefully, depending on source bounds +*/ +struct RasterDatum +{ + static std::int32_t get_invalid() { return std::numeric_limits::max(); } + + std::int32_t datum = get_invalid(); + + RasterDatum() = default; + + RasterDatum(std::int32_t _datum) : datum(_datum) {} +}; + +class RasterGrid +{ + public: + RasterGrid(const boost::filesystem::path &filepath, std::size_t _xdim, std::size_t _ydim) + { + xdim = _xdim; + ydim = _ydim; + _data.reserve(ydim * xdim); + + boost::filesystem::ifstream stream(filepath); + if (!stream) + { + throw osrm::exception("Unable to open raster file."); + } + + stream.seekg(0, std::ios_base::end); + std::string buffer; + buffer.resize(static_cast(stream.tellg())); + + stream.seekg(0, std::ios_base::beg); + + BOOST_ASSERT(buffer.size() > 1); + stream.read(&buffer[0], static_cast(buffer.size())); + + boost::algorithm::trim(buffer); + + auto itr = buffer.begin(); + auto end = buffer.end(); + + bool r = false; + try + { + r = boost::spirit::qi::parse( + itr, end, +boost::spirit::qi::int_ % +boost::spirit::qi::space, _data); + } + catch (std::exception const &ex) + { + throw osrm::exception( + std::string("Failed to read from raster source with exception: ") + ex.what()); + } + + if (!r || itr != end) + { + throw osrm::exception("Failed to parse raster source correctly."); + } + } + + RasterGrid(const RasterGrid &) = default; + RasterGrid &operator=(const RasterGrid &) = default; + + RasterGrid(RasterGrid &&) = default; + RasterGrid &operator=(RasterGrid &&) = default; + + std::int32_t operator()(std::size_t x, std::size_t y) { return _data[y * xdim + x]; } + std::int32_t operator()(std::size_t x, std::size_t y) const { return _data[(y)*xdim + (x)]; } + + private: + std::vector _data; + std::size_t xdim, ydim; +}; + +/** + \brief Stores raster source data in memory and provides lookup functions. +*/ +class RasterSource +{ + private: + const float xstep; + const float ystep; + + float calcSize(int min, int max, std::size_t count) const; + + public: + RasterGrid raster_data; + + const std::size_t width; + const std::size_t height; + const int xmin; + const int xmax; + const int ymin; + const int ymax; + + RasterDatum getRasterData(const int lon, const int lat) const; + + RasterDatum getRasterInterpolate(const int lon, const int lat) const; + + RasterSource(RasterGrid _raster_data, + std::size_t width, + std::size_t height, + int _xmin, + int _xmax, + int _ymin, + int _ymax); +}; + +class SourceContainer +{ + public: + SourceContainer() = default; + + int loadRasterSource(const std::string &path_string, + double xmin, + double xmax, + double ymin, + double ymax, + std::size_t nrows, + std::size_t ncols); + + RasterDatum getRasterDataFromSource(unsigned int source_id, int lon, int lat); + + RasterDatum getRasterInterpolateFromSource(unsigned int source_id, int lon, int lat); + + private: + std::vector LoadedSources; + std::unordered_map LoadedSourcePaths; +}; + +#endif /* RASTER_SOURCE_HPP */ diff --git a/extractor/extraction_containers.cpp b/extractor/extraction_containers.cpp index 071ea97e6..e5344de35 100644 --- a/extractor/extraction_containers.cpp +++ b/extractor/extraction_containers.cpp @@ -36,10 +36,14 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "../util/simple_logger.hpp" #include "../util/timing_util.hpp" #include "../util/fingerprint.hpp" +#include "../util/lua_util.hpp" #include #include #include +#include + +#include #include @@ -76,7 +80,8 @@ ExtractionContainers::~ExtractionContainers() */ void ExtractionContainers::PrepareData(const std::string &output_file_name, const std::string &restrictions_file_name, - const std::string &name_file_name) + const std::string &name_file_name, + lua_State *segment_state) { try { @@ -87,7 +92,7 @@ void ExtractionContainers::PrepareData(const std::string &output_file_name, PrepareNodes(); WriteNodes(file_out_stream); - PrepareEdges(); + PrepareEdges(segment_state); WriteEdges(file_out_stream); file_out_stream.close(); @@ -168,7 +173,7 @@ void ExtractionContainers::PrepareNodes() std::cout << "ok, after " << TIMER_SEC(sorting_nodes) << "s" << std::endl; } -void ExtractionContainers::PrepareEdges() +void ExtractionContainers::PrepareEdges(lua_State *segment_state) { // Sort edges by start. std::cout << "[extractor] Sorting edges by start ... " << std::flush; @@ -264,6 +269,16 @@ void ExtractionContainers::PrepareEdges() edge_iterator->source_coordinate.lat, edge_iterator->source_coordinate.lon, node_iterator->lat, node_iterator->lon); + if (lua_function_exists(segment_state, "segment_function")) + { + luabind::call_function( + segment_state, "segment_function", + boost::cref(edge_iterator->source_coordinate), + boost::cref(*node_iterator), + distance, + boost::ref(edge_iterator->weight_data)); + } + const double weight = [distance](const InternalExtractorEdge::WeightData& data) { switch (data.type) { diff --git a/extractor/extraction_containers.hpp b/extractor/extraction_containers.hpp index ef71d059f..45285e03e 100644 --- a/extractor/extraction_containers.hpp +++ b/extractor/extraction_containers.hpp @@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "internal_extractor_edge.hpp" #include "first_and_last_segment_of_way.hpp" +#include "scripting_environment.hpp" #include "../data_structures/external_memory_node.hpp" #include "../data_structures/restriction.hpp" @@ -53,7 +54,7 @@ class ExtractionContainers #endif void PrepareNodes(); void PrepareRestrictions(); - void PrepareEdges(); + void PrepareEdges(lua_State *segment_state); void WriteNodes(std::ofstream& file_out_stream) const; void WriteRestrictions(const std::string& restrictions_file_name) const; @@ -81,7 +82,8 @@ class ExtractionContainers void PrepareData(const std::string &output_file_name, const std::string &restrictions_file_name, - const std::string &names_file_name); + const std::string &names_file_name, + lua_State *segment_state); }; #endif /* EXTRACTION_CONTAINERS_HPP */ diff --git a/extractor/extractor.cpp b/extractor/extractor.cpp index c1a0a2127..e988aa92b 100644 --- a/extractor/extractor.cpp +++ b/extractor/extractor.cpp @@ -34,10 +34,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "restriction_parser.hpp" #include "scripting_environment.hpp" +#include "../data_structures/raster_source.hpp" #include "../util/git_sha.hpp" #include "../util/make_unique.hpp" #include "../util/simple_logger.hpp" #include "../util/timing_util.hpp" +#include "../util/lua_util.hpp" #include "../typedefs.h" @@ -116,6 +118,17 @@ int extractor::run() SimpleLogger().Write() << "Parsing in progress.."; TIMER_START(parsing); + lua_State *segment_state = scripting_environment.get_lua_state(); + + if (lua_function_exists(segment_state, "source_function")) + { + // bind a single instance of SourceContainer class to relevant lua state + SourceContainer sources; + luabind::globals(segment_state)["sources"] = sources; + + luabind::call_function(segment_state, "source_function"); + } + std::string generator = header.get("generator"); if (generator.empty()) { @@ -238,7 +251,9 @@ int extractor::run() extraction_containers.PrepareData(config.output_file_name, config.restriction_file_name, - config.names_file_name); + config.names_file_name, + segment_state); + TIMER_STOP(extracting); SimpleLogger().Write() << "extraction finished after " << TIMER_SEC(extracting) << "s"; SimpleLogger().Write() << "To prepare the data for routing, run: " diff --git a/extractor/lat b/extractor/lat new file mode 100644 index 000000000..e69de29bb diff --git a/extractor/scripting_environment.cpp b/extractor/scripting_environment.cpp index e98f688c6..de96b1193 100644 --- a/extractor/scripting_environment.cpp +++ b/extractor/scripting_environment.cpp @@ -30,13 +30,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "extraction_helper_functions.hpp" #include "extraction_node.hpp" #include "extraction_way.hpp" +#include "internal_extractor_edge.hpp" #include "../data_structures/external_memory_node.hpp" +#include "../data_structures/raster_source.hpp" #include "../util/lua_util.hpp" #include "../util/osrm_exception.hpp" #include "../util/simple_logger.hpp" #include "../typedefs.h" #include +#include #include @@ -80,6 +83,13 @@ void ScriptingEnvironment::init_lua_state(lua_State *lua_state) luabind::def("print", LUA_print), luabind::def("durationIsValid", durationIsValid), luabind::def("parseDuration", parseDuration), + luabind::class_("sources") + .def(luabind::constructor<>()) + .def("load", &SourceContainer::loadRasterSource) + .def("query", &SourceContainer::getRasterDataFromSource) + .def("interpolate", &SourceContainer::getRasterInterpolateFromSource), + luabind::class_("constants") + .enum_("enums")[luabind::value("precision", COORDINATE_PRECISION)], luabind::class_>("vector") .def("Add", static_cast::*)(const std::string &)>( @@ -121,7 +131,21 @@ void ScriptingEnvironment::init_lua_state(lua_State *lua_state) luabind::class_("Way") .def("get_value_by_key", &osmium::Way::get_value_by_key) .def("get_value_by_key", &get_value_by_key) - .def("id", &osmium::Way::id) + .def("id", &osmium::Way::id), + luabind::class_("EdgeSource") + .property("source_coordinate", &InternalExtractorEdge::source_coordinate) + .property("weight_data", &InternalExtractorEdge::weight_data), + luabind::class_("WeightData") + .def_readwrite("speed", &InternalExtractorEdge::WeightData::speed), + luabind::class_("EdgeTarget") + .property("lat", &ExternalMemoryNode::lat) + .property("lon", &ExternalMemoryNode::lon), + luabind::class_("Coordinate") + .property("lat", &FixedPointCoordinate::lat) + .property("lon", &FixedPointCoordinate::lon), + luabind::class_("RasterDatum") + .property("datum", &RasterDatum::datum) + .def("invalid_data", &RasterDatum::get_invalid) ]; if (0 != luaL_dofile(lua_state, file_name.c_str())) diff --git a/extractor/source_coordinate.lat b/extractor/source_coordinate.lat new file mode 100644 index 000000000..e69de29bb diff --git a/features/raster/extract.feature b/features/raster/extract.feature new file mode 100644 index 000000000..9ca0635d8 --- /dev/null +++ b/features/raster/extract.feature @@ -0,0 +1,18 @@ +@raster @extract +Feature: osrm-extract with a profile containing raster source +# expansions: +# {osm_base} => path to current input file +# {profile} => path to current profile script + + Scenario: osrm-extract on a valid profile + Given the profile "rasterbot" + And the node map + | a | b | + And the ways + | nodes | + | ab | + And the data has been saved to disk + When I run "osrm-extract {osm_base}.osm -p {profile}" + Then stderr should be empty + And stdout should contain "source loader" + And it should exit with code 0 diff --git a/features/raster/weights.feature b/features/raster/weights.feature new file mode 100644 index 000000000..1d521d076 --- /dev/null +++ b/features/raster/weights.feature @@ -0,0 +1,78 @@ +@routing @speed @raster +Feature: Raster - weights + + Background: Use specific speeds + Given the node locations + | node | lat | lon | + | a | 0.1 | 0.1 | + | b | .05 | 0.1 | + | c | 0.0 | 0.1 | + | d | .05 | .03 | + | e | .05 | .066 | + | f | .075 | .066 | + And the ways + | nodes | highway | + | ab | primary | + | ad | primary | + | bc | primary | + | dc | primary | + | de | primary | + | eb | primary | + | df | primary | + | fb | primary | + And the raster source + """ + 0 0 0 0 + 0 0 0 250 + 0 0 250 500 + 0 0 0 250 + 0 0 0 0 + """ + + Scenario: Weighting not based on raster sources + Given the profile "testbot" + When I run "osrm-extract {osm_base}.osm -p {profile}" + And I run "osrm-prepare {osm_base}.osm" + And I route I should get + | from | to | route | speed | + | a | b | ab | 36 km/h | + | a | c | ab,bc | 36 km/h | + | b | c | bc | 36 km/h | + | a | d | ad | 36 km/h | + | d | c | dc | 36 km/h | + + Scenario: Weighting based on raster sources + Given the profile "rasterbot" + When I run "osrm-extract {osm_base}.osm -p {profile}" + Then stdout should contain "evaluating segment" + And I run "osrm-prepare {osm_base}.osm" + And I route I should get + | from | to | route | speed | + | a | b | ab | 8 km/h | + | a | c | ad,dc | 15 km/h | + | b | c | bc | 8 km/h | + | a | d | ad | 15 km/h | + | d | c | dc | 15 km/h | + | d | e | de | 10 km/h | + | e | b | eb | 10 km/h | + | d | f | df | 15 km/h | + | f | b | fb | 7 km/h | + | d | b | de,eb | 10 km/h | + + Scenario: Weighting based on raster sources + Given the profile "rasterbot-interp" + When I run "osrm-extract {osm_base}.osm -p {profile}" + Then stdout should contain "evaluating segment" + And I run "osrm-prepare {osm_base}.osm" + And I route I should get + | from | to | route | speed | + | a | b | ab | 8 km/h | + | a | c | ad,dc | 15 km/h | + | b | c | bc | 8 km/h | + | a | d | ad | 15 km/h | + | d | c | dc | 15 km/h | + | d | e | de | 10 km/h | + | e | b | eb | 10 km/h | + | d | f | df | 15 km/h | + | f | b | fb | 7 km/h | + | d | b | de,eb | 10 km/h | diff --git a/features/step_definitions/data.rb b/features/step_definitions/data.rb index 524fd322c..ff074d24f 100644 --- a/features/step_definitions/data.rb +++ b/features/step_definitions/data.rb @@ -134,6 +134,12 @@ Given /^the input file ([^"]*)$/ do |file| @osm_str = File.read file end +Given /^the raster source$/ do |data| + Dir.chdir TEST_FOLDER do + File.open("rastersource.asc", "w") {|f| f.write(data)} + end +end + Given /^the data has been saved to disk$/ do begin write_input_data diff --git a/features/support/env.rb b/features/support/env.rb index ea74959ed..cb707f9e1 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -14,6 +14,7 @@ DEFAULT_SPEEDPROFILE = 'bicycle' WAY_SPACING = 100 DEFAULT_GRID_SIZE = 100 #meters PROFILES_PATH = File.join ROOT_FOLDER, 'profiles' +FIXTURES_PATH = File.join ROOT_FOLDER, 'unit_tests/fixtures' BIN_PATH = File.join ROOT_FOLDER, 'build' DEFAULT_INPUT_FORMAT = 'osm' DEFAULT_ORIGIN = [1,1] @@ -71,7 +72,7 @@ def verify_osrm_is_not_running end def verify_existance_of_binaries - ["osrm-extract", "osrm-prepare", "osrm-routed"].each do |bin| + ["osrm-extract", "osrm-prepare", "osrm-routed"].each do |bin| unless File.exists? "#{BIN_PATH}/#{bin}#{EXE}" raise "*** #{BIN_PATH}/#{bin}#{EXE} is missing. Build failed?" end diff --git a/profiles/rasterbot-interp.lua b/profiles/rasterbot-interp.lua new file mode 100644 index 000000000..42c98b6f4 --- /dev/null +++ b/profiles/rasterbot-interp.lua @@ -0,0 +1,46 @@ +-- Rasterbot profile + +-- Minimalist node_ and way_functions in order to test source_ and segment_functions + +function node_function (node, result) +end + +function way_function (way, result) + local highway = way:get_value_by_key("highway") + local name = way:get_value_by_key("name") + + if name then + result.name = name + end + + result.forward_speed = 15 + result.backward_speed = 15 +end + +function source_function () + raster_source = sources:load( + "../test/rastersource.asc", + 0, -- lon_min + 0.1, -- lon_max + 0, -- lat_min + 0.1, -- lat_max + 5, -- nrows + 4 -- ncols + ) +end + +function segment_function (source, target, distance, weight) + local sourceData = sources:interpolate(raster_source, source.lon, source.lat) + local targetData = sources:interpolate(raster_source, target.lon, target.lat) + print ("evaluating segment: " .. sourceData.datum .. " " .. targetData.datum) + local invalid = sourceData.invalid_data() + + if sourceData.datum ~= invalid and targetData.datum ~= invalid then + local slope = math.abs(sourceData.datum - targetData.datum) / distance + print (" slope: " .. slope) + print (" was speed: " .. weight.speed) + + weight.speed = weight.speed * (1 - (slope * 5)) + print (" new speed: " .. weight.speed) + end +end diff --git a/profiles/rasterbot.lua b/profiles/rasterbot.lua new file mode 100644 index 000000000..847384ddf --- /dev/null +++ b/profiles/rasterbot.lua @@ -0,0 +1,46 @@ +-- Rasterbot profile + +-- Minimalist node_ and way_functions in order to test source_ and segment_functions + +function node_function (node, result) +end + +function way_function (way, result) + local highway = way:get_value_by_key("highway") + local name = way:get_value_by_key("name") + + if name then + result.name = name + end + + result.forward_speed = 15 + result.backward_speed = 15 +end + +function source_function () + raster_source = sources:load( + "../test/rastersource.asc", + 0, -- lon_min + 0.1, -- lon_max + 0, -- lat_min + 0.1, -- lat_max + 5, -- nrows + 4 -- ncols + ) +end + +function segment_function (source, target, distance, weight) + local sourceData = sources:query(raster_source, source.lon, source.lat) + local targetData = sources:query(raster_source, target.lon, target.lat) + print ("evaluating segment: " .. sourceData.datum .. " " .. targetData.datum) + local invalid = sourceData.invalid_data() + + if sourceData.datum ~= invalid and targetData.datum ~= invalid then + local slope = math.abs(sourceData.datum - targetData.datum) / distance + print (" slope: " .. slope) + print (" was speed: " .. weight.speed) + + weight.speed = weight.speed * (1 - (slope * 5)) + print (" new speed: " .. weight.speed) + end +end diff --git a/test/rastersource.asc b/test/rastersource.asc new file mode 100644 index 000000000..b72c427ff --- /dev/null +++ b/test/rastersource.asc @@ -0,0 +1,5 @@ +0 0 0 0 +0 0 0 250 +0 0 250 500 +0 0 0 250 +0 0 0 0 \ No newline at end of file diff --git a/unit_tests/data_structures/raster_source.cpp b/unit_tests/data_structures/raster_source.cpp new file mode 100644 index 000000000..691cba108 --- /dev/null +++ b/unit_tests/data_structures/raster_source.cpp @@ -0,0 +1,110 @@ +/* + +Copyright (c) 2014, Project OSRM contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include "../../data_structures/raster_source.hpp" +#include "../../typedefs.h" +#include "../../util/osrm_exception.hpp" + +#include + +#include +#include + +BOOST_AUTO_TEST_SUITE(raster_source) + +int normalize(double coord) { return static_cast(coord * COORDINATE_PRECISION); } + +#define CHECK_QUERY(source_id, lon, lat, expected) \ + BOOST_CHECK_EQUAL( \ + sources.getRasterDataFromSource(source_id, normalize(lon), normalize(lat)).datum, \ + expected) + +#define CHECK_INTERPOLATE(source_id, lon, lat, expected) \ + BOOST_CHECK_EQUAL( \ + sources.getRasterInterpolateFromSource(source_id, normalize(lon), normalize(lat)).datum, \ + expected) + +BOOST_AUTO_TEST_CASE(raster_test) +{ + SourceContainer sources; + int source_id = sources.loadRasterSource("../unit_tests/fixtures/raster_data.asc", 0, 0.09, 0, + 0.09, 10, 10); + BOOST_CHECK_EQUAL(source_id, 0); + + // Expected nearest-neighbor queries + // EDGES + CHECK_QUERY(0, 0.00, 0.00, 10); + CHECK_QUERY(0, 0.00, 0.09, 10); + CHECK_QUERY(0, 0.09, 0.00, 40); + CHECK_QUERY(0, 0.09, 0.09, 100); + CHECK_QUERY(0, 0.09, 0.07, 140); + // OUT OF BOUNDS + CHECK_QUERY(0, -0.1, 0.07, RasterDatum::get_invalid()); + CHECK_QUERY(0, -0.1, -3.0, RasterDatum::get_invalid()); + CHECK_QUERY(0, 0.3, 23.0, RasterDatum::get_invalid()); + // ARBITRARY - AT DATA + CHECK_QUERY(0, 0.06, 0.06, 100); + CHECK_QUERY(0, 0.08, 0.05, 160); + CHECK_QUERY(0, 0.01, 0.05, 20); + // ARBITRARY - BETWEEN DATA + CHECK_QUERY(0, 0.054, 0.023, 40); + CHECK_QUERY(0, 0.056, 0.028, 80); + CHECK_QUERY(0, 0.05, 0.028, 60); + + // Expected bilinear interpolation queries + // EDGES - same as above + CHECK_INTERPOLATE(0, 0.00, 0.00, 10); + CHECK_INTERPOLATE(0, 0.00, 0.09, 10); + CHECK_INTERPOLATE(0, 0.09, 0.00, 40); + CHECK_INTERPOLATE(0, 0.09, 0.09, 100); + CHECK_INTERPOLATE(0, 0.09, 0.07, 140); + // OUT OF BOUNDS - same as above + CHECK_INTERPOLATE(0, -0.1, 0.07, RasterDatum::get_invalid()); + CHECK_INTERPOLATE(0, -0.1, -3.0, RasterDatum::get_invalid()); + CHECK_INTERPOLATE(0, 0.3, 23.0, RasterDatum::get_invalid()); + // ARBITRARY - AT DATA - same as above + CHECK_INTERPOLATE(0, 0.06, 0.06, 100); + CHECK_INTERPOLATE(0, 0.08, 0.05, 160); + CHECK_INTERPOLATE(0, 0.01, 0.05, 20); + // ARBITRARY - BETWEEN DATA + CHECK_INTERPOLATE(0, 0.054, 0.023, 54); + CHECK_INTERPOLATE(0, 0.056, 0.028, 68); + CHECK_INTERPOLATE(0, 0.05, 0.028, 56); + + int source_already_loaded_id = sources.loadRasterSource( + "../unit_tests/fixtures/raster_data.asc", 0, 0.09, 0, 0.09, 10, 10); + + BOOST_CHECK_EQUAL(source_already_loaded_id, 0); + BOOST_CHECK_THROW(sources.getRasterDataFromSource(1, normalize(0.02), normalize(0.02)), + osrm::exception); + + BOOST_CHECK_THROW( + sources.loadRasterSource("../unit_tests/fixtures/nonexistent.asc", 0, 0.1, 0, 0.1, 7, 7), + osrm::exception); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/unit_tests/fixtures/raster_data.asc b/unit_tests/fixtures/raster_data.asc new file mode 100644 index 000000000..4abd9bb3d --- /dev/null +++ b/unit_tests/fixtures/raster_data.asc @@ -0,0 +1,10 @@ +10 10 10 10 10 20 40 60 80 100 +10 10 10 10 20 40 60 80 100 120 +10 10 10 20 40 60 80 100 120 140 +10 10 20 40 60 80 100 120 140 160 +10 20 40 60 80 100 120 140 160 180 +10 10 20 40 60 80 100 120 140 160 +10 10 10 20 40 60 80 100 120 140 +10 10 10 10 20 40 60 80 100 80 +10 10 10 10 10 20 40 60 80 60 +10 10 10 10 10 10 20 40 60 40