Compare commits

...

34 Commits

Author SHA1 Message Date
Patrick Niklaus
ea91790ae8 Bump version and adjust changelog 2018-04-25 15:36:48 +00:00
Patrick Niklaus
bde65bd117 Sort unpacked paths by sharing 2018-04-25 15:24:58 +00:00
Patrick Niklaus
5d10e0408a More parameter tuning 2018-04-25 15:24:58 +00:00
Patrick Niklaus
872e055fe9 Adjust to PR comments 2018-04-25 15:24:58 +00:00
Patrick Niklaus
42866310d2 Parameter tuning for different route lengths 2018-04-25 15:24:58 +00:00
Patrick Niklaus
0c12bd784c Use duration and not weight for sharing 2018-04-25 15:24:58 +00:00
Patrick Niklaus
dc07382099 Chose alternatives parameters based on coordinate distance 2018-04-25 15:24:58 +00:00
Patrick Niklaus
bf2fc5accf Replace step function with contious scaling 2018-04-25 15:24:58 +00:00
Patrick Niklaus
8e8ac128c6 Refactor parameters to be selected at runtime 2018-04-25 15:24:57 +00:00
Patrick Niklaus
3c57c046fd Compute sharing weighted by edge weight
This ensures that small insignificat turns don't affect the overall
sharing metric too much.
2018-04-25 15:24:57 +00:00
Patrick Niklaus
c9be11eb39 Fix inserting edges of shortest path for sharing check
This caused very sub-optimal routes to be selected.
2018-04-25 15:24:57 +00:00
karenzshea
f217fa9cff update changelog for 5.17.2 2018-04-20 14:58:24 +02:00
karenzshea
b265686480 5.17.2 2018-04-20 14:56:23 +02:00
karenzshea
bba1e05967 5.17.1 2018-04-20 12:16:35 +02:00
Duane Gearhart
2e38d3c170 Do not combine a segregated edge with a roundabout (#5040)
* Do not combine a segregated edge with a roundabout, add test
2018-04-20 11:42:23 +02:00
karenzshea
020c0d1c11 5.17.0 2018-04-19 12:03:55 +02:00
Patrick Niklaus
1bc18d6275 Bump OSRM verison to 5.17 RC5 2018-04-17 15:56:42 +00:00
Huyen Chau Nguyen
4334810c71 add changelog entry 2018-04-16 09:40:49 +00:00
Huyen Chau Nguyen
a307371c27 adjust tests to changes 2018-04-16 09:40:49 +00:00
Huyen Chau Nguyen
5351a258a9 refactor bike profile 2018-04-16 09:40:49 +00:00
Huyen Chau Nguyen
adadb45f02 make primaries and secondaries more dangerous
only apply oneway safety considerations on primaries

re-adjust primary and secondary safeties
2018-04-16 09:40:49 +00:00
Patrick Niklaus
adee18468c Bump RC to 4 2018-04-13 13:23:35 +00:00
Patrick Niklaus
9a9abf4c11 Remove double log printing 2018-04-13 10:14:23 +00:00
karenzshea
331eeca4c1 empty list of shmem regions if none found 2018-04-13 10:12:20 +00:00
karenzshea
f8d6e4750a log err instead of throwing when no shmem regions found 2018-04-13 10:12:05 +00:00
Michael Krasnyk
cf505386f9
Bump version to 5.17.0-rc.3 2018-04-12 10:12:28 +02:00
Michael Krasnyk
e346e9ae07
Avoid using signed integers for edge IDs 2018-04-12 10:11:52 +02:00
Michael Krasnyk
ad37fcce2d
Bump version to 5.17.0-rc.2 2018-04-10 21:17:33 +02:00
Patrick Niklaus
0e19f07c3c
Use byte based tar size encoding above 8GB 2018-04-10 21:14:29 +02:00
Patrick Niklaus
59c0795c7f
Add test case for writing and reading huge tar file
This test case is disabled by default because it needs too much storage,
but serves as documentation for the future.
2018-04-10 21:14:27 +02:00
Michael Krasnyk
3653e51f67
Use base-256 encoding for files larger 68G
Reference:
http://lists.busybox.net/pipermail/busybox/2011-May/075596.html
2018-04-10 21:14:25 +02:00
Michael Krasnyk
8c24507f8f
Use 12 octal digits in mtar_raw_header_t::size 2018-04-10 21:14:23 +02:00
Michael Krasnyk
3e444777e0
Fix mtar file size truncation to 4G 2018-04-10 21:14:20 +02:00
Patrick Niklaus
670cd8813c Bump OSRM version 2018-04-09 13:13:44 +00:00
16 changed files with 697 additions and 305 deletions

View File

@ -15,6 +15,7 @@ branches:
- master
# enable building tags
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
- "5.17"
cache:
yarn: true

View File

@ -1,4 +1,14 @@
# UNRELEASED
# 5.17.3
- Changes from 5.17.2:
- Bugfixes:
- FIXED: Increased probability of returning alternatives and quality of the ones returned for MLD. [#5048](https://github.com/Project-OSRM/osrm-backend/issues/5048)
# 5.17.2
- Changes from 5.17.0:
- Bugfixes:
- FIXED: Do not combine a segregated edge with a roundabout [#5039](https://github.com/Project-OSRM/osrm-backend/issues/5039)
# 5.17.0
- Changes from 5.16.0:
- Bugfixes:
- FIXED: deduplication of route steps when waypoints are used [#4909](https://github.com/Project-OSRM/osrm-backend/issues/4909)
@ -6,9 +16,11 @@
- FIXED: Remove the last short annotation segment in `trimShortSegments` [#4946](https://github.com/Project-OSRM/osrm-backend/pull/4946)
- FIXED: Properly calculate annotations for speeds, durations and distances when waypoints are used with mapmatching [#4949](https://github.com/Project-OSRM/osrm-backend/pull/4949)
- FIXED: Don't apply unimplemented SH and PH conditions in OpeningHours and add inversed date ranges [#4992](https://github.com/Project-OSRM/osrm-backend/issues/4992)
- FIXED: integer overflow in `DynamicGraph::Renumber` [#5021](https://github.com/Project-OSRM/osrm-backend/pull/5021)
- Profile:
- CHANGED: Handle oneways in get_forward_backward_by_key [#4929](https://github.com/Project-OSRM/osrm-backend/pull/4929)
- FIXED: Do not route against oneway road if there is a cycleway in the wrong direction; also review bike profile [#4943](https://github.com/Project-OSRM/osrm-backend/issues/4943)
- CHANGED: Make cyclability weighting of the bike profile prefer safer routes more strongly [#5015](https://github.com/Project-OSRM/osrm-backend/issues/5015)
- Guidance:
- CHANGED: Don't use obviousness for links bifurcations [#4929](https://github.com/Project-OSRM/osrm-backend/pull/4929)
- FIXED: Adjust Straight direction modifiers of side roads in driveway handler [#4929](https://github.com/Project-OSRM/osrm-backend/pull/4929)

View File

@ -11,11 +11,11 @@ Feature: Bicycle - Adds penalties to unsafe roads
Then routability should be
| highway | cycleway | forw | backw | forw_rate | backw_rate |
| motorway | | | | | |
| primary | | 15 km/h | 15 km/h | 2.9 | 2.9 |
| secondary | | 15 km/h | 15 km/h | 3.1 | 3.1 |
| primary | | 15 km/h | 15 km/h | 2.1 | 2.1 |
| secondary | | 15 km/h | 15 km/h | 2.7 | 2.7 |
| tertiary | | 15 km/h | 15 km/h | 3.3 | 3.3 |
| primary_link | | 15 km/h | 15 km/h | 2.9 | 2.9 |
| secondary_link | | 15 km/h | 15 km/h | 3.1 | 3.1 |
| primary_link | | 15 km/h | 15 km/h | 2.1 | 2.1 |
| secondary_link | | 15 km/h | 15 km/h | 2.7 | 2.7 |
| tertiary_link | | 15 km/h | 15 km/h | 3.3 | 3.3 |
| residential | | 15 km/h | 15 km/h | 4.2 | 4.2 |
| cycleway | | 15 km/h | 15 km/h | 4.2 | 4.2 |
@ -51,49 +51,49 @@ Feature: Bicycle - Adds penalties to unsafe roads
Then routability should be
| highway | cycleway:right | cycleway:left | forw | backw | forw_rate | backw_rate |
| motorway | track | | 15 km/h | | 4.2 | |
| primary | track | | 15 km/h | 15 km/h | 4.2 | 2.9 |
| secondary | track | | 15 km/h | 15 km/h | 4.2 | 3.1 |
| primary | track | | 15 km/h | 15 km/h | 4.2 | 2.1 |
| secondary | track | | 15 km/h | 15 km/h | 4.2 | 2.7 |
| tertiary | track | | 15 km/h | 15 km/h | 4.2 | 3.3 |
| primary_link | track | | 15 km/h | 15 km/h | 4.2 | 2.9 |
| secondary_link | track | | 15 km/h | 15 km/h | 4.2 | 3.1 |
| primary_link | track | | 15 km/h | 15 km/h | 4.2 | 2.1 |
| secondary_link | track | | 15 km/h | 15 km/h | 4.2 | 2.7 |
| tertiary_link | track | | 15 km/h | 15 km/h | 4.2 | 3.3 |
| residential | track | | 15 km/h | 15 km/h | 4.2 | 4.2 |
| cycleway | track | | 15 km/h | 15 km/h | 4.2 | 4.2 |
| footway | track | | 15 km/h | 4 km/h +-1 | 4.2 | 1.1 |
| motorway | | track | 15 km/h | | 4.2 | |
| primary | | track | 15 km/h | 15 km/h | 2.9 | 4.2 |
| secondary | | track | 15 km/h | 15 km/h | 3.1 | 4.2 |
| primary | | track | 15 km/h | 15 km/h | 2.1 | 4.2 |
| secondary | | track | 15 km/h | 15 km/h | 2.7 | 4.2 |
| tertiary | | track | 15 km/h | 15 km/h | 3.3 | 4.2 |
| primary_link | | track | 15 km/h | 15 km/h | 2.9 | 4.2 |
| secondary_link | | track | 15 km/h | 15 km/h | 3.1 | 4.2 |
| primary_link | | track | 15 km/h | 15 km/h | 2.1 | 4.2 |
| secondary_link | | track | 15 km/h | 15 km/h | 2.7 | 4.2 |
| tertiary_link | | track | 15 km/h | 15 km/h | 3.3 | 4.2 |
| residential | | track | 15 km/h | 15 km/h | 4.2 | 4.2 |
| cycleway | | track | 15 km/h | 15 km/h | 4.2 | 4.2 |
| footway | | track | 4 km/h +-1 | 15 km/h | 1.1 | 4.2 |
| motorway | lane | | 15 km/h | | 4.2 | |
| primary | lane | | 15 km/h | 15 km/h | 4.2 | 2.9 |
| secondary | lane | | 15 km/h | 15 km/h | 4.2 | 3.1 |
| primary | lane | | 15 km/h | 15 km/h | 4.2 | 2.1 |
| secondary | lane | | 15 km/h | 15 km/h | 4.2 | 2.7 |
| tertiary | lane | | 15 km/h | 15 km/h | 4.2 | 3.3 |
| primary_link | lane | | 15 km/h | 15 km/h | 4.2 | 2.9 |
| secondary_link | lane | | 15 km/h | 15 km/h | 4.2 | 3.1 |
| primary_link | lane | | 15 km/h | 15 km/h | 4.2 | 2.1 |
| secondary_link | lane | | 15 km/h | 15 km/h | 4.2 | 2.7 |
| tertiary_link | lane | | 15 km/h | 15 km/h | 4.2 | 3.3 |
| residential | lane | | 15 km/h +-1 | 15 km/h +-1 | 4.2 | 4.2 |
| cycleway | lane | | 15 km/h | 15 km/h | 4.2 | 4.2 |
| footway | lane | | 15 km/h | 4 km/h +-1 | 4.2 | 1.1 |
| motorway | | lane | 15 km/h | | 4.2 | |
| primary | | lane | 15 km/h | 15 km/h | 2.9 | 4.2 |
| secondary | | lane | 15 km/h +-1 | 15 km/h +-1 | 3.1 | 4.2 |
| primary | | lane | 15 km/h | 15 km/h | 2.1 | 4.2 |
| secondary | | lane | 15 km/h +-1 | 15 km/h +-1 | 2.7 | 4.2 |
| tertiary | | lane | 15 km/h | 15 km/h | 3.3 | 4.2 |
| primary_link | | lane | 15 km/h | 15 km/h | 2.9 | 4.2 |
| secondary_link | | lane | 15 km/h | 15 km/h | 3.1 | 4.2 |
| primary_link | | lane | 15 km/h | 15 km/h | 2.1 | 4.2 |
| secondary_link | | lane | 15 km/h | 15 km/h | 2.7 | 4.2 |
| tertiary_link | | lane | 15 km/h | 15 km/h | 3.3 | 4.2 |
| residential | | lane | 15 km/h | 15 km/h | 4.2 | 4.2 |
| cycleway | | lane | 15 km/h | 15 km/h | 4.2 | 4.2 |
| footway | | lane | 4 km/h +-1 | 15 km/h | 1.1 | 4.2 |
| motorway | shared_lane | | 15 km/h | | 4.2 | |
| primary | shared_lane | | 15 km/h | 15 km/h | 4.2 | 2.9 |
| primary | shared_lane | | 15 km/h | 15 km/h | 4.2 | 2.1 |
| motorway | | shared_lane | 15 km/h | | 4.2 | |
| primary | | shared_lane | 15 km/h | 15 km/h | 2.9 | 4.2 |
| primary | | shared_lane | 15 km/h | 15 km/h | 2.1 | 4.2 |
Scenario: Bike - Don't apply penalties for all kind of cycleways

View File

@ -41,3 +41,62 @@ Feature: Fixed bugs, kept to check for regressions
When I route I should get
| from | to | route |
| 1 | 2 | bcd,bcd |
#############################
# This test models the OSM map at the location for
# https://github.com/Project-OSRM/osrm-backend/issues/5039
#############################
Scenario: Mixed Entry and Exit and segregated
Given the profile file "car" initialized with
"""
profile.properties.left_hand_driving = true
"""
Given the node locations
| node | lon | lat |
| a | 171.12889297029 | -42.58425289548 |
| b | 171.1299357 | -42.5849295 |
| c | 171.1295427 | -42.5849385 |
| d | 171.1297356 | -42.5852029 |
| e | 171.1296909 | -42.5851986 |
| f | 171.1295097 | -42.585007 |
| g | 171.1298225 | -42.5851928 |
| h | 171.1300262 | -42.5859122 |
| i | 171.1292651 | -42.584698 |
| j | 171.1297209 | -42.5848569 |
| k | 171.1297188 | -42.5854056 |
| l | 171.1298326 | -42.5857266 |
| m | 171.1298871 | -42.5848922 |
| n | 171.1296505 | -42.585189 |
| o | 171.1295206 | -42.5850862 |
| p | 171.1296106 | -42.5848862 |
| q | 171.1299784 | -42.5850191 |
| r | 171.1298867 | -42.5851671 |
| s | 171.1306955 | -42.5845812 |
| t | 171.129525 | -42.584807 |
| u | 171.1299705 | -42.584984 |
| v | 171.1299067 | -42.5849073 |
| w | 171.1302061 | -42.5848173 |
| x | 171.12975 | -42.5855753 |
| y | 171.129969 | -42.585079 |
| 1 | 171.131511926651| -42.584306746421966 |
| 2 | 171.128743886947| -42.58414875714669 |
And the ways
| nodes | highway | maxspeed | name | ref | surface | junction | oneway |
| ws | primary | 100 | Taramakau Highway | SH 6 | asphalt | | |
| kxlh | trunk | | Otira Highway | SH 73 | | | |
| ai | primary | 100 | Kumara Junction Highway | SH 6 | asphalt | | |
| qyrgdenof | primary | 100 | Kumara Junction | | | roundabout | yes |
| ke | trunk | | Otira Highway | SH 73 | | | yes |
| itj | primary | 100 | Kumara Junction Highway | SH 6 | | | yes |
| gk | trunk | | Otira Highway | SH 73 | | | yes |
| fi | primary | 100 | Kumara Junction Highway | SH 6 | | | yes |
| wq | primary | 100 | Taramakau Highway | SH 6 | | | yes |
| vw | primary | 100 | Taramakau Highway | SH 6 | | | yes |
| vbuq | primary | 100 | Kumara Junction | | | roundabout | yes |
| jmv | primary | 100 | Kumara Junction | | | roundabout | yes |
| fcpj | primary | 100 | Kumara Junction | | | roundabout | yes |
When I route I should get
| waypoints | route | turns |
| 1,2 | Taramakau Highway,Kumara Junction Highway,Kumara Junction Highway,Kumara Junction Highway | depart,Kumara Junction-exit-2,exit rotary slight left,arrive |

View File

@ -117,6 +117,19 @@ template <typename Data> struct SharedMonitor
#endif
static void remove() { bi::shared_memory_object::remove(Data::name); }
static bool exists()
{
try
{
bi::shared_memory_object shmem_open =
bi::shared_memory_object(bi::open_only, Data::name, bi::read_only);
}
catch (const bi::interprocess_exception &exception)
{
return false;
}
return true;
}
private:
#if USE_BOOST_INTERPROCESS_CONDITION

View File

@ -29,10 +29,8 @@ checkMTarError(int error_code, const boost::filesystem::path &filepath, const st
case MTAR_ESUCCESS:
return;
case MTAR_EFAILURE:
throw util::RuntimeError(filepath.string() + " : " + name,
ErrorCode::FileIOError,
SOURCE_REF,
std::strerror(errno));
throw util::RuntimeError(
filepath.string() + " : " + name, ErrorCode::FileIOError, SOURCE_REF);
case MTAR_EOPENFAIL:
throw util::RuntimeError(filepath.string() + " : " + name,
ErrorCode::FileOpenError,

View File

@ -2,6 +2,8 @@
#define DYNAMICGRAPH_HPP
#include "util/deallocating_vector.hpp"
#include "util/exception.hpp"
#include "util/exception_utils.hpp"
#include "util/integer_range.hpp"
#include "util/permutation.hpp"
#include "util/typedefs.hpp"
@ -411,7 +413,7 @@ template <typename EdgeDataT> class DynamicGraph
util::inplacePermutation(node_array.begin(), node_array.end(), old_to_new_node);
// Build up edge permutation
auto new_edge_index = 0;
EdgeID new_edge_index = 0;
std::vector<EdgeID> old_to_new_edge(edge_list.size(), SPECIAL_EDGEID);
for (auto node : util::irange<NodeID>(0, number_of_nodes))
{
@ -419,6 +421,11 @@ template <typename EdgeDataT> class DynamicGraph
// move all filled edges
for (auto edge : GetAdjacentEdgeRange(node))
{
if (new_edge_index == std::numeric_limits<EdgeID>::max())
{
throw util::exception("There are too many edges, OSRM only supports 2^32" +
SOURCE_REF);
}
edge_list[edge].target = old_to_new_node[edge_list[edge].target];
BOOST_ASSERT(edge_list[edge].target != SPECIAL_NODEID);
old_to_new_edge[edge] = new_edge_index++;

View File

@ -1,6 +1,6 @@
{
"name": "osrm",
"version": "5.17.0-latest.1",
"version": "5.17.3",
"private": false,
"description": "The Open Source Routing Machine is a high performance routing engine written in C++14 designed to run on OpenStreetMap data.",
"dependencies": {

View File

@ -109,11 +109,11 @@ function setup()
-- reduce the driving speed by 30% for unsafe roads
-- only used for cyclability metric
unsafe_highway_list = {
primary = 0.7,
secondary = 0.75,
primary = 0.5,
secondary = 0.65,
tertiary = 0.8,
primary_link = 0.7,
secondary_link = 0.75,
primary_link = 0.5,
secondary_link = 0.65,
tertiary_link = 0.8,
},
@ -250,204 +250,237 @@ end
function handle_bicycle_tags(profile,way,result,data)
-- initial routability check, filters out buildings, boundaries, etc
local route = way:get_value_by_key("route")
local man_made = way:get_value_by_key("man_made")
local railway = way:get_value_by_key("railway")
local amenity = way:get_value_by_key("amenity")
local public_transport = way:get_value_by_key("public_transport")
local bridge = way:get_value_by_key("bridge")
data.route = way:get_value_by_key("route")
data.man_made = way:get_value_by_key("man_made")
data.railway = way:get_value_by_key("railway")
data.amenity = way:get_value_by_key("amenity")
data.public_transport = way:get_value_by_key("public_transport")
data.bridge = way:get_value_by_key("bridge")
if (not data.highway or data.highway == '') and
(not route or route == '') and
(not profile.use_public_transport or not railway or railway=='') and
(not amenity or amenity=='') and
(not man_made or man_made=='') and
(not public_transport or public_transport=='') and
(not bridge or bridge=='')
(not data.route or data.route == '') and
(not profile.use_public_transport or not data.railway or data.railway=='') and
(not data.amenity or data.amenity=='') and
(not data.man_made or data.man_made=='') and
(not data.public_transport or data.public_transport=='') and
(not data.bridge or data.bridge=='')
then
return false
end
-- access
local access = find_access_tag(way, profile.access_tags_hierarchy)
if access and profile.access_tag_blacklist[access] then
data.access = find_access_tag(way, profile.access_tags_hierarchy)
if data.access and profile.access_tag_blacklist[data.access] then
return false
end
-- other tags
local junction = way:get_value_by_key("junction")
local maxspeed = parse_maxspeed(way:get_value_by_key ( "maxspeed") )
local maxspeed_forward = parse_maxspeed(way:get_value_by_key( "maxspeed:forward"))
local maxspeed_backward = parse_maxspeed(way:get_value_by_key( "maxspeed:backward"))
local barrier = way:get_value_by_key("barrier")
local oneway = way:get_value_by_key("oneway")
local oneway_bicycle = way:get_value_by_key("oneway:bicycle")
local cycleway = way:get_value_by_key("cycleway")
local cycleway_left = way:get_value_by_key("cycleway:left")
local cycleway_right = way:get_value_by_key("cycleway:right")
local duration = way:get_value_by_key("duration")
local service = way:get_value_by_key("service")
local foot = way:get_value_by_key("foot")
local foot_forward = way:get_value_by_key("foot:forward")
local foot_backward = way:get_value_by_key("foot:backward")
local bicycle = way:get_value_by_key("bicycle")
data.junction = way:get_value_by_key("junction")
data.maxspeed = parse_maxspeed(way:get_value_by_key ( "maxspeed") )
data.maxspeed_forward = parse_maxspeed(way:get_value_by_key( "maxspeed:forward"))
data.maxspeed_backward = parse_maxspeed(way:get_value_by_key( "maxspeed:backward"))
data.barrier = way:get_value_by_key("barrier")
data.oneway = way:get_value_by_key("oneway")
data.oneway_bicycle = way:get_value_by_key("oneway:bicycle")
data.cycleway = way:get_value_by_key("cycleway")
data.cycleway_left = way:get_value_by_key("cycleway:left")
data.cycleway_right = way:get_value_by_key("cycleway:right")
data.duration = way:get_value_by_key("duration")
data.service = way:get_value_by_key("service")
data.foot = way:get_value_by_key("foot")
data.foot_forward = way:get_value_by_key("foot:forward")
data.foot_backward = way:get_value_by_key("foot:backward")
data.bicycle = way:get_value_by_key("bicycle")
speed_handler(profile,way,result,data)
oneway_handler(profile,way,result,data)
cycleway_handler(profile,way,result,data)
bike_push_handler(profile,way,result,data)
local way_type_allows_pushing = false
-- maxspeed
limit( result, data.maxspeed, data.maxspeed_forward, data.maxspeed_backward )
-- not routable if no speed assigned
-- this avoid assertions in debug builds
if result.forward_speed <= 0 and result.duration <= 0 then
result.forward_mode = mode.inaccessible
end
if result.backward_speed <= 0 and result.duration <= 0 then
result.backward_mode = mode.inaccessible
end
safety_handler(profile,way,result,data)
end
function speed_handler(profile,way,result,data)
data.way_type_allows_pushing = false
-- speed
local bridge_speed = profile.bridge_speeds[bridge]
local bridge_speed = profile.bridge_speeds[data.bridge]
if (bridge_speed and bridge_speed > 0) then
data.highway = bridge
if duration and durationIsValid(duration) then
result.duration = math.max( parseDuration(duration), 1 )
data.highway = data.bridge
if data.duration and durationIsValid(data.duration) then
result.duration = math.max( parseDuration(data.duration), 1 )
end
result.forward_speed = bridge_speed
result.backward_speed = bridge_speed
way_type_allows_pushing = true
elseif profile.route_speeds[route] then
data.way_type_allows_pushing = true
elseif profile.route_speeds[data.route] then
-- ferries (doesn't cover routes tagged using relations)
result.forward_mode = mode.ferry
result.backward_mode = mode.ferry
if duration and durationIsValid(duration) then
result.duration = math.max( 1, parseDuration(duration) )
if data.duration and durationIsValid(data.duration) then
result.duration = math.max( 1, parseDuration(data.duration) )
else
result.forward_speed = profile.route_speeds[route]
result.backward_speed = profile.route_speeds[route]
result.forward_speed = profile.route_speeds[data.route]
result.backward_speed = profile.route_speeds[data.route]
end
-- railway platforms (old tagging scheme)
elseif railway and profile.platform_speeds[railway] then
result.forward_speed = profile.platform_speeds[railway]
result.backward_speed = profile.platform_speeds[railway]
way_type_allows_pushing = true
elseif data.railway and profile.platform_speeds[data.railway] then
result.forward_speed = profile.platform_speeds[data.railway]
result.backward_speed = profile.platform_speeds[data.railway]
data.way_type_allows_pushing = true
-- public_transport platforms (new tagging platform)
elseif public_transport and profile.platform_speeds[public_transport] then
result.forward_speed = profile.platform_speeds[public_transport]
result.backward_speed = profile.platform_speeds[public_transport]
way_type_allows_pushing = true
elseif data.public_transport and profile.platform_speeds[data.public_transport] then
result.forward_speed = profile.platform_speeds[data.public_transport]
result.backward_speed = profile.platform_speeds[data.public_transport]
data.way_type_allows_pushing = true
-- railways
elseif profile.use_public_transport and railway and profile.railway_speeds[railway] and profile.access_tag_whitelist[access] then
elseif profile.use_public_transport and data.railway and profile.railway_speeds[data.railway] and profile.access_tag_whitelist[data.access] then
result.forward_mode = mode.train
result.backward_mode = mode.train
result.forward_speed = profile.railway_speeds[railway]
result.backward_speed = profile.railway_speeds[railway]
elseif amenity and profile.amenity_speeds[amenity] then
result.forward_speed = profile.railway_speeds[data.railway]
result.backward_speed = profile.railway_speeds[data.railway]
elseif data.amenity and profile.amenity_speeds[data.amenity] then
-- parking areas
result.forward_speed = profile.amenity_speeds[amenity]
result.backward_speed = profile.amenity_speeds[amenity]
way_type_allows_pushing = true
result.forward_speed = profile.amenity_speeds[data.amenity]
result.backward_speed = profile.amenity_speeds[data.amenity]
data.way_type_allows_pushing = true
elseif profile.bicycle_speeds[data.highway] then
-- regular ways
result.forward_speed = profile.bicycle_speeds[data.highway]
result.backward_speed = profile.bicycle_speeds[data.highway]
way_type_allows_pushing = true
elseif access and profile.access_tag_whitelist[access] then
data.way_type_allows_pushing = true
elseif data.access and profile.access_tag_whitelist[data.access] then
-- unknown way, but valid access tag
result.forward_speed = profile.default_speed
result.backward_speed = profile.default_speed
way_type_allows_pushing = true
data.way_type_allows_pushing = true
end
end
function oneway_handler(profile,way,result,data)
-- oneway
local implied_oneway = junction == "roundabout" or junction == "circular" or data.highway == "motorway"
local reverse = false
data.implied_oneway = data.junction == "roundabout" or data.junction == "circular" or data.highway == "motorway"
data.reverse = false
if oneway_bicycle == "yes" or oneway_bicycle == "1" or oneway_bicycle == "true" then
if data.oneway_bicycle == "yes" or data.oneway_bicycle == "1" or data.oneway_bicycle == "true" then
result.backward_mode = mode.inaccessible
elseif oneway_bicycle == "no" or oneway_bicycle == "0" or oneway_bicycle == "false" then
elseif data.oneway_bicycle == "no" or data.oneway_bicycle == "0" or data.oneway_bicycle == "false" then
-- prevent other cases
elseif oneway_bicycle == "-1" then
elseif data.oneway_bicycle == "-1" then
result.forward_mode = mode.inaccessible
reverse = true
elseif oneway == "yes" or oneway == "1" or oneway == "true" then
data.reverse = true
elseif data.oneway == "yes" or data.oneway == "1" or data.oneway == "true" then
result.backward_mode = mode.inaccessible
elseif oneway == "no" or oneway == "0" or oneway == "false" then
elseif data.oneway == "no" or data.oneway == "0" or data.oneway == "false" then
-- prevent other cases
elseif oneway == "-1" then
elseif data.oneway == "-1" then
result.forward_mode = mode.inaccessible
reverse = true
elseif implied_oneway then
data.reverse = true
elseif data.implied_oneway then
result.backward_mode = mode.inaccessible
end
end
function cycleway_handler(profile,way,result,data)
-- cycleway
local has_cycleway_forward = false
local has_cycleway_backward = false
local is_oneway = result.forward_mode ~= mode.inaccessible and result.backward_mode ~= mode.inaccessible and not implied_oneway
data.has_cycleway_forward = false
data.has_cycleway_backward = false
data.is_twoway = result.forward_mode ~= mode.inaccessible and result.backward_mode ~= mode.inaccessible and not data.implied_oneway
-- cycleways on normal roads
if is_oneway then
if cycleway and profile.cycleway_tags[cycleway] then
has_cycleway_backward = true
has_cycleway_forward = true
if data.is_twoway then
if data.cycleway and profile.cycleway_tags[data.cycleway] then
data.has_cycleway_backward = true
data.has_cycleway_forward = true
end
if (cycleway_right and profile.cycleway_tags[cycleway_right]) or (cycleway_left and profile.opposite_cycleway_tags[cycleway_left]) then
has_cycleway_forward = true
if (data.cycleway_right and profile.cycleway_tags[data.cycleway_right]) or (data.cycleway_left and profile.opposite_cycleway_tags[data.cycleway_left]) then
data.has_cycleway_forward = true
end
if (cycleway_left and profile.cycleway_tags[cycleway_left]) or (cycleway_right and profile.opposite_cycleway_tags[cycleway_right]) then
has_cycleway_backward = true
if (data.cycleway_left and profile.cycleway_tags[data.cycleway_left]) or (data.cycleway_right and profile.opposite_cycleway_tags[data.cycleway_right]) then
data.has_cycleway_backward = true
end
else
local has_twoway_cycleway = (cycleway and profile.opposite_cycleway_tags[cycleway]) or (cycleway_right and profile.opposite_cycleway_tags[cycleway_right]) or (cycleway_left and profile.opposite_cycleway_tags[cycleway_left])
local has_opposite_cycleway = (cycleway_left and profile.opposite_cycleway_tags[cycleway_left]) or (cycleway_right and profile.opposite_cycleway_tags[cycleway_right])
local has_oneway_cycleway = (cycleway and profile.cycleway_tags[cycleway]) or (cycleway_right and profile.cycleway_tags[cycleway_right]) or (cycleway_left and profile.cycleway_tags[cycleway_left])
local has_twoway_cycleway = (data.cycleway and profile.opposite_cycleway_tags[data.cycleway]) or (data.cycleway_right and profile.opposite_cycleway_tags[data.cycleway_right]) or (data.cycleway_left and profile.opposite_cycleway_tags[data.cycleway_left])
local has_opposite_cycleway = (data.cycleway_left and profile.opposite_cycleway_tags[data.cycleway_left]) or (data.cycleway_right and profile.opposite_cycleway_tags[data.cycleway_right])
local has_oneway_cycleway = (data.cycleway and profile.cycleway_tags[data.cycleway]) or (data.cycleway_right and profile.cycleway_tags[data.cycleway_right]) or (data.cycleway_left and profile.cycleway_tags[data.cycleway_left])
-- set cycleway even though it is an one-way if opposite is tagged
if has_twoway_cycleway then
has_cycleway_backward = true
has_cycleway_forward = true
data.has_cycleway_backward = true
data.has_cycleway_forward = true
elseif has_opposite_cycleway then
if not reverse then
has_cycleway_backward = true
if not data.reverse then
data.has_cycleway_backward = true
else
has_cycleway_forward = true
data.has_cycleway_forward = true
end
elseif has_oneway_cycleway then
if not reverse then
has_cycleway_forward = true
if not data.reverse then
data.has_cycleway_forward = true
else
has_cycleway_backward = true
data.has_cycleway_backward = true
end
end
end
if has_cycleway_backward then
if data.has_cycleway_backward then
result.backward_mode = mode.cycling
result.backward_speed = profile.bicycle_speeds["cycleway"]
end
if has_cycleway_forward then
if data.has_cycleway_forward then
result.forward_mode = mode.cycling
result.forward_speed = profile.bicycle_speeds["cycleway"]
end
end
function bike_push_handler(profile,way,result,data)
-- pushing bikes - if no other mode found
if result.forward_mode == mode.inaccessible or result.backward_mode == mode.inaccessible or
result.forward_speed == -1 or result.backward_speed == -1 then
if foot ~= 'no' then
if data.foot ~= 'no' then
local push_forward_speed = nil
local push_backward_speed = nil
if profile.pedestrian_speeds[data.highway] then
push_forward_speed = profile.pedestrian_speeds[data.highway]
push_backward_speed = profile.pedestrian_speeds[data.highway]
elseif man_made and profile.man_made_speeds[man_made] then
push_forward_speed = profile.man_made_speeds[man_made]
push_backward_speed = profile.man_made_speeds[man_made]
elseif data.man_made and profile.man_made_speeds[data.man_made] then
push_forward_speed = profile.man_made_speeds[data.man_made]
push_backward_speed = profile.man_made_speeds[data.man_made]
else
if foot == 'yes' then
if data.foot == 'yes' then
push_forward_speed = profile.walking_speed
if not implied_oneway then
if not data.implied_oneway then
push_backward_speed = profile.walking_speed
end
elseif foot_forward == 'yes' then
elseif data.foot_forward == 'yes' then
push_forward_speed = profile.walking_speed
elseif foot_backward == 'yes' then
elseif data.foot_backward == 'yes' then
push_backward_speed = profile.walking_speed
elseif way_type_allows_pushing then
elseif data.way_type_allows_pushing then
push_forward_speed = profile.walking_speed
if not implied_oneway then
if not data.implied_oneway then
push_backward_speed = profile.walking_speed
end
end
@ -466,38 +499,32 @@ function handle_bicycle_tags(profile,way,result,data)
end
-- dismount
if bicycle == "dismount" then
if data.bicycle == "dismount" then
result.forward_mode = mode.pushing_bike
result.backward_mode = mode.pushing_bike
result.forward_speed = profile.walking_speed
result.backward_speed = profile.walking_speed
end
end
-- maxspeed
limit( result, maxspeed, maxspeed_forward, maxspeed_backward )
-- not routable if no speed assigned
-- this avoid assertions in debug builds
if result.forward_speed <= 0 and result.duration <= 0 then
result.forward_mode = mode.inaccessible
end
if result.backward_speed <= 0 and result.duration <= 0 then
result.backward_mode = mode.inaccessible
end
function safety_handler(profile,way,result,data)
-- convert duration into cyclability
if profile.properties.weight_name == 'cyclability' then
local safety_penalty = profile.unsafe_highway_list[data.highway] or 1.
local is_unsafe = safety_penalty < 1
local forward_is_unsafe = is_unsafe and not has_cycleway_forward
local backward_is_unsafe = is_unsafe and not has_cycleway_backward
local is_undesireable = data.highway == "service" and profile.service_penalties[service]
-- primaries that are one ways are probably huge primaries where the lanes need to be separated
if is_unsafe and data.highway == 'primary' and not data.is_twoway then
safety_penalty = safety_penalty * 0.5
end
if is_unsafe and data.highway == 'secondary' and not data.is_twoway then
safety_penalty = safety_penalty * 0.6
end
local forward_is_unsafe = is_unsafe and not data.has_cycleway_forward
local backward_is_unsafe = is_unsafe and not data.has_cycleway_backward
local is_undesireable = data.highway == "service" and profile.service_penalties[data.service]
local forward_penalty = 1.
local backward_penalty = 1.
if forward_is_unsafe then
@ -508,8 +535,8 @@ function handle_bicycle_tags(profile,way,result,data)
end
if is_undesireable then
forward_penalty = math.min(forward_penalty, profile.service_penalties[service])
backward_penalty = math.min(backward_penalty, profile.service_penalties[service])
forward_penalty = math.min(forward_penalty, profile.service_penalties[data.service])
backward_penalty = math.min(backward_penalty, profile.service_penalties[data.service])
end
if result.forward_speed > 0 then
@ -523,8 +550,26 @@ function handle_bicycle_tags(profile,way,result,data)
if result.duration > 0 then
result.weight = result.duration / forward_penalty
end
if data.highway == "bicycle" then
safety_bonus = safety_bonus + 0.2
if result.forward_speed > 0 then
-- convert from km/h to m/s
result.forward_rate = result.forward_speed / 3.6 * safety_bonus
end
if result.backward_speed > 0 then
-- convert from km/h to m/s
result.backward_rate = result.backward_speed / 3.6 * safety_bonus
end
if result.duration > 0 then
result.weight = result.duration / safety_bonus
end
end
end
end
function process_way(profile, way, result)
-- the initial filtering of ways based on presence of tags
-- affects processing times significantly, because all ways
@ -542,6 +587,39 @@ function process_way(profile, way, result)
local data = {
-- prefetch tags
highway = way:get_value_by_key('highway'),
route = nil,
man_made = nil,
railway = nil,
amenity = nil,
public_transport = nil,
bridge = nil,
access = nil,
junction = nil,
maxspeed = nil,
maxspeed_forward = nil,
maxspeed_backward = nil,
barrier = nil,
oneway = nil,
oneway_bicycle = nil,
cycleway = nil,
cycleway_left = nil,
cycleway_right = nil,
duration = nil,
service = nil,
foot = nil,
foot_forward = nil,
foot_backward = nil,
bicycle = nil,
way_type_allows_pushing = false,
has_cycleway_forward = false,
has_cycleway_backward = false,
is_twoway = true,
reverse = false,
implied_oneway = false
}
local handlers = Sequence {

43
scripts/osm2cucumber.js Normal file
View File

@ -0,0 +1,43 @@
/*********************************
* Takes an XML `.osm` file and converts it into a cucumber scenario definition like
* Given the node locations
* | node | lon | lat |
* .....
* Given the ways
* | nodes | tag1 | tag2 | tag3 |
* .....
*
* Note that cucumber tests are limited to 26 nodes (labelled a-z), so
* you'll need to use pretty small OSM extracts to get this to work.
*****************************************/
var fs = require('fs');
var parseString = require('xml2js').parseString;
var data = fs.readFileSync('filename.osm', 'utf8');
const items = parseString(data, (err, result) => {
var idmap = {};
console.log('Given the node locations');
console.log(' | node | lon | lat |');
result.osm.node.filter(n => !n.$.action || n.$.action !== 'delete').forEach(i => {
var code = String.fromCharCode(97 + Object.keys(idmap).length)
idmap[i.$.id] = code;
console.log(` | ${code} | ${i.$.lon} | ${i.$.lat} |`);
});
var allkeys = {};
var waytags = {};
result.osm.way.filter(n => !n.$.action || n.$.action !== 'delete').forEach(w => {
if (!waytags[w.$.id]) waytags[w.$.id] = {};
w.tag.forEach(t => { allkeys[t.$.k] = t.$.v; waytags[w.$.id][t.$.k] = t.$.v; });
});
console.log('And the ways');
console.log(` | nodes | ${Object.keys(allkeys).join(' | ')} |`);
result.osm.way.filter(n => !n.$.action || n.$.action !== 'delete').forEach(w => {
console.log(` | ${w.nd.map(n => idmap[n.$.ref]).join('')} | ${Object.keys(allkeys).map(k => waytags[w.$.id][k] || '').join(' | ')} |`);
});
});

View File

@ -618,9 +618,10 @@ RouteSteps collapseSegregatedTurnInstructions(RouteSteps steps)
TransferLanesStrategy());
++next_step;
}
// else if the current step is segregated and the next step is not then combine with turn
// adjustment
else if (curr_step->is_segregated && !next_step->is_segregated)
// else if the current step is segregated and the next step is not segregated
// and the next step is not a roundabout then combine with turn adjustment
else if (curr_step->is_segregated && !next_step->is_segregated &&
!hasRoundaboutType(next_step->maneuver.instruction))
{
// Determine if u-turn
if (bearingsAreReversed(

View File

@ -34,22 +34,27 @@ using Facade = DataFacade<Algorithm>;
namespace
{
// Alternative paths candidate via nodes are taken from overlapping search spaces.
// Overlapping by a third guarantees us taking candidate nodes "from the middle".
const constexpr auto kSearchSpaceOverlapFactor = 1.33;
// Unpack n-times more candidate paths to run high-quality checks on.
// Unpacking paths yields higher chance to find good alternatives but is also expensive.
const constexpr auto kAlternativesToUnpackFactor = 2.0;
// Alternative paths length requirement (stretch).
// At most 25% longer then the shortest path.
const constexpr auto kAtMostLongerBy = 0.25;
// Alternative paths similarity requirement (sharing).
// At least 15% different than the shortest path.
const constexpr auto kAtLeastDifferentBy = 0.85;
// Alternative paths are still reasonable around the via node candidate (local optimality).
// At least optimal around 10% sub-paths around the via node candidate.
const /*constexpr*/ auto kAtLeastOptimalAroundViaBy = 0.10;
// gcc 7.1 ICE ^
struct Parameters
{
// Alternative paths candidate via nodes are taken from overlapping search spaces.
// Overlapping by a third guarantees us taking candidate nodes "from the middle".
double kSearchSpaceOverlapFactor = 1.33;
// Unpack n-times more candidate paths to run high-quality checks on.
// Unpacking paths yields higher chance to find good alternatives but is also expensive.
unsigned kAlternativesToUnpackFactor = 2;
// Alternative paths length requirement (stretch).
// At most 25% longer then the shortest path.
double kAtMostLongerBy = 0.25;
// Alternative paths similarity requirement (sharing).
// At least 25% different than the shortest path.
double kAtMostSameBy = 0.75;
// Alternative paths are still reasonable around the via node candidate (local optimality).
// At least optimal around 10% sub-paths around the via node candidate.
double kAtLeastOptimalAroundViaBy = 0.1;
// Alternative paths similarity requirement (sharing) based on calles.
// At least 15% different than the shortest path.
double kCellsAtMostSameBy = 0.95;
};
// Represents a via middle node where forward (from s) and backward (from t)
// search spaces overlap and the weight a path (made up of s,via and via,t) has.
@ -71,11 +76,104 @@ struct WeightedViaNodePackedPath
// its total weight and the via node used to construct the path.
struct WeightedViaNodeUnpackedPath
{
double sharing;
WeightedViaNode via;
UnpackedNodes nodes;
UnpackedEdges edges;
};
// Scale the maximum allowed weight increase based on its magnitude:
// - Shortest path 10 minutes, alternative 13 minutes => Factor of 0.30 ok
// - Shortest path 10 hours, alternative 13 hours => Factor of 0.30 unreasonable
double getLongerByFactorBasedOnDuration(const EdgeWeight duration)
{
BOOST_ASSERT(duration != INVALID_EDGE_WEIGHT);
// We only have generic weights here and no durations without unpacking.
// We also have restricted way penalties which are huge and will screw scaling here.
//
// Users can pass us generic weights not based on durations; we can't do anything about
// it here other than either generating too many or no alternatives in these cases.
//
// We scale the weights with a step function based on some rough guestimates, so that
// they match tens of minutes, in the low hours, tens of hours, etc.
// Computed using a least-squares curve fitiing on the following values:
//
// def func(xs, a, b, c, d):
// return a + b/(xs-d) + c/(xs-d)**3
//
// xs = np.array([5 * 60, 10 * 60, 30 * 60, 60 * 60, 3 * 60 * 60, 10 * 60 * 60])
// ys = np.array([1.0, 0.75, 0.5, 0.4, 0.3, 0.2])
//
// xs_interp = np.arange(5*60, 10*60*60, 5*60)
// ys_interp = np.interp(xs_interp, xs, ys)
//
// params, _ = scipy.optimize.curve_fit(func, xs_interp, ys_interp)
//
// The hyperbolic shape was chosen because it interpolated well between
// the given datapoints and drops off for large durations.
const constexpr auto a = 1.91578463e-01;
const constexpr auto b = 1.35118442e+03;
const constexpr auto c = 2.45437877e+09;
const constexpr auto d = -2.07944571e+03;
if (duration < EdgeWeight(5 * 60))
{
return 1.0;
}
else if (duration > EdgeWeight(10 * 60 * 60))
{
return 0.20;
}
// Bigger than 10 minutes but smaller than 10 hours
BOOST_ASSERT(duration >= 5 * 60 && duration <= 10 * 60 * 60);
return a + b / (duration - d) + c / std::pow(duration - d, 3);
}
Parameters parametersFromRequest(const PhantomNodes &phantom_node_pair)
{
Parameters parameters;
const auto distance = util::coordinate_calculation::haversineDistance(
phantom_node_pair.source_phantom.location, phantom_node_pair.target_phantom.location);
// 10km
if (distance < 10000.)
{
parameters.kAlternativesToUnpackFactor = 10.0;
parameters.kCellsAtMostSameBy = 1.0;
parameters.kAtLeastOptimalAroundViaBy = 0.2;
parameters.kAtMostSameBy = 0.50;
}
// 20km
else if (distance < 20000.)
{
parameters.kAlternativesToUnpackFactor = 8.0;
parameters.kCellsAtMostSameBy = 1.0;
parameters.kAtLeastOptimalAroundViaBy = 0.2;
parameters.kAtMostSameBy = 0.60;
}
// 50km
else if (distance < 50000.)
{
parameters.kAlternativesToUnpackFactor = 6.0;
parameters.kCellsAtMostSameBy = 0.95;
parameters.kAtMostSameBy = 0.65;
}
// 100km
else if (distance < 100000.)
{
parameters.kAlternativesToUnpackFactor = 4.0;
parameters.kCellsAtMostSameBy = 0.95;
parameters.kAtMostSameBy = 0.70;
}
return parameters;
}
// Filters candidates which are on not unique.
// Returns an iterator to the uniquified range's new end.
// Note: mutates the range in-place invalidating iterators.
@ -110,49 +208,13 @@ RandIt filterViaCandidatesByRoadImportance(RandIt first, RandIt last, const Faca
return last;
}
// Scale the maximum allowed weight increase based on its magnitude:
// - Shortest path 10 minutes, alternative 13 minutes => Factor of 0.30 ok
// - Shortest path 10 hours, alternative 13 hours => Factor of 0.30 unreasonable
double scaledAtMostLongerByFactorBasedOnDuration(EdgeWeight duration)
{
BOOST_ASSERT(duration != INVALID_EDGE_WEIGHT);
// We only have generic weights here and no durations without unpacking.
// We also have restricted way penalties which are huge and will screw scaling here.
//
// Users can pass us generic weights not based on durations; we can't do anything about
// it here other than either generating too many or no alternatives in these cases.
//
// We scale the weights with a step function based on some rough guestimates, so that
// they match tens of minutes, in the low hours, tens of hours, etc.
// Todo: instead of a piecewise constant function should this be a continuous function?
// At the moment there are "hard" jump edge cases when crossing the thresholds.
auto scaledAtMostLongerBy = kAtMostLongerBy;
const constexpr auto minutes = 60.;
const constexpr auto hours = 60. * minutes;
if (duration < EdgeWeight(10 * minutes))
scaledAtMostLongerBy *= 1.20;
else if (duration < EdgeWeight(30 * minutes))
scaledAtMostLongerBy *= 1.00;
else if (duration < EdgeWeight(1 * hours))
scaledAtMostLongerBy *= 0.90;
else if (duration < EdgeWeight(3 * hours))
scaledAtMostLongerBy *= 0.70;
else if (duration > EdgeWeight(10 * hours))
scaledAtMostLongerBy *= 0.50;
return scaledAtMostLongerBy;
}
// Filters candidates with much higher weight than the primary route. Mutates range in-place.
// Returns an iterator to the filtered range's new end.
template <typename RandIt>
RandIt
filterViaCandidatesByStretch(RandIt first, RandIt last, EdgeWeight weight, double weight_multiplier)
RandIt filterViaCandidatesByStretch(RandIt first,
RandIt last,
const EdgeWeight weight,
const Parameters &parameters)
{
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
util::static_assert_iter_value<RandIt, WeightedViaNode>();
@ -160,9 +222,7 @@ filterViaCandidatesByStretch(RandIt first, RandIt last, EdgeWeight weight, doubl
// Assumes weight roughly corresponds to duration-ish. If this is not the case e.g.
// because users are setting weight to be distance in the profiles, then we might
// either generate more candidates than we have to or not enough. But is okay.
const auto scaled_at_most_longer_by =
scaledAtMostLongerByFactorBasedOnDuration(weight / weight_multiplier);
const auto stretch_weight_limit = (1. + scaled_at_most_longer_by) * weight;
const auto stretch_weight_limit = (1. + parameters.kAtMostLongerBy) * weight;
const auto over_weight_limit = [=](const auto via) {
return via.weight > stretch_weight_limit;
@ -198,8 +258,15 @@ filterViaCandidatesByViaNotOnPath(const WeightedViaNodePackedPath &path, RandIt
// Filters packed paths with similar cells between each other. Mutates range in-place.
// Returns an iterator to the filtered range's new end.
template <typename RandIt>
RandIt filterPackedPathsByCellSharing(RandIt first, RandIt last, const Partition &partition)
RandIt filterPackedPathsByCellSharing(RandIt first,
RandIt last,
const Partition &partition,
const Parameters &parameters)
{
// In this case we don't need to calculate sharing, because it would not filter anything
if (parameters.kCellsAtMostSameBy >= 1.0)
return last;
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
util::static_assert_iter_value<RandIt, WeightedViaNodePackedPath>();
@ -226,14 +293,13 @@ RandIt filterPackedPathsByCellSharing(RandIt first, RandIt last, const Partition
return last;
std::unordered_set<CellID> cells;
cells.reserve(size * (shortest_path.path.size() + 1) * (1. + kAtMostLongerBy));
cells.reserve(size * (shortest_path.path.size() + 1) * (1 + parameters.kAtMostLongerBy));
cells.insert(get_cell(std::get<0>(shortest_path.path.front())));
for (const auto &edge : shortest_path.path)
cells.insert(get_cell(std::get<1>(edge)));
const auto over_sharing_limit = [&](const auto &packed) {
if (packed.path.empty())
{ // don't remove routes with single-node (empty) path
return false;
@ -253,7 +319,7 @@ RandIt filterPackedPathsByCellSharing(RandIt first, RandIt last, const Partition
const auto sharing = 1. - difference;
if (sharing > kAtLeastDifferentBy)
if (sharing > parameters.kCellsAtMostSameBy)
{
return true;
}
@ -277,7 +343,8 @@ RandIt filterPackedPathsByLocalOptimality(const WeightedViaNodePackedPath &path,
const Heap &forward_heap,
const Heap &reverse_heap,
RandIt first,
RandIt last)
RandIt last,
const Parameters &parameters)
{
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
util::static_assert_iter_value<RandIt, WeightedViaNodePackedPath>();
@ -376,7 +443,7 @@ RandIt filterPackedPathsByLocalOptimality(const WeightedViaNodePackedPath &path,
const auto detour_length = forward_heap.GetKey(via) - forward_heap.GetKey(a) +
reverse_heap.GetKey(via) - reverse_heap.GetKey(b);
return plateaux_length < kAtLeastOptimalAroundViaBy * detour_length;
return plateaux_length < parameters.kAtLeastOptimalAroundViaBy * detour_length;
};
return std::remove_if(first, last, is_not_locally_optimal);
@ -384,7 +451,11 @@ RandIt filterPackedPathsByLocalOptimality(const WeightedViaNodePackedPath &path,
// Filters unpacked paths compared to all other paths. Mutates range in-place.
// Returns an iterator to the filtered range's new end.
template <typename RandIt> RandIt filterUnpackedPathsBySharing(RandIt first, RandIt last)
template <typename RandIt>
RandIt filterUnpackedPathsBySharing(RandIt first,
RandIt last,
const Facade &facade,
const Parameters &parameters)
{
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
util::static_assert_iter_value<RandIt, WeightedViaNodeUnpackedPath>();
@ -400,27 +471,35 @@ template <typename RandIt> RandIt filterUnpackedPathsBySharing(RandIt first, Ran
return last;
std::unordered_set<EdgeID> edges;
edges.reserve(size * shortest_path.edges.size() * (1. + kAtMostLongerBy));
edges.reserve(size * shortest_path.edges.size() * (1.25));
edges.insert(begin(shortest_path.edges), begin(shortest_path.edges));
const auto over_sharing_limit = [&](const auto &unpacked) {
edges.insert(begin(shortest_path.edges), end(shortest_path.edges));
const auto over_sharing_limit = [&](auto &unpacked) {
if (unpacked.edges.empty())
{ // don't remove routes with single-node (empty) path
return false;
}
const auto not_seen = [&](const EdgeID edge) { return edges.count(edge) < 1; };
const auto different = std::count_if(begin(unpacked.edges), end(unpacked.edges), not_seen);
EdgeWeight total_duration = 0;
const auto add_if_seen = [&](const EdgeWeight duration, const EdgeID edge) {
auto edge_duration = facade.GetEdgeData(edge).duration;
total_duration += edge_duration;
if (edges.count(edge) > 0)
{
return duration + edge_duration;
}
return duration;
};
const auto difference = different / static_cast<double>(unpacked.edges.size());
BOOST_ASSERT(difference >= 0.);
BOOST_ASSERT(difference <= 1.);
const auto shared_weight =
std::accumulate(begin(unpacked.edges), end(unpacked.edges), EdgeWeight{0}, add_if_seen);
const auto sharing = 1. - difference;
unpacked.sharing = shared_weight / static_cast<double>(total_duration);
BOOST_ASSERT(unpacked.sharing >= 0.);
BOOST_ASSERT(unpacked.sharing <= 1.);
if (sharing > kAtLeastDifferentBy)
if (unpacked.sharing > parameters.kAtMostSameBy)
{
return true;
}
@ -431,14 +510,19 @@ template <typename RandIt> RandIt filterUnpackedPathsBySharing(RandIt first, Ran
}
};
return std::remove_if(first + 1, last, over_sharing_limit);
auto end = std::remove_if(first + 1, last, over_sharing_limit);
std::sort(
first + 1, end, [](const auto &lhs, const auto &rhs) { return lhs.sharing < rhs.sharing; });
return end;
}
// Filters annotated routes by stretch based on duration. Mutates range in-place.
// Returns an iterator to the filtered range's new end.
template <typename RandIt>
RandIt
filterAnnotatedRoutesByStretch(RandIt first, RandIt last, const InternalRouteResult &shortest_route)
RandIt filterAnnotatedRoutesByStretch(RandIt first,
RandIt last,
const InternalRouteResult &shortest_route,
const Parameters &parameters)
{
util::static_assert_iter_category<RandIt, std::random_access_iterator_tag>();
util::static_assert_iter_value<RandIt, InternalRouteResult>();
@ -446,9 +530,7 @@ filterAnnotatedRoutesByStretch(RandIt first, RandIt last, const InternalRouteRes
BOOST_ASSERT(shortest_route.is_valid());
const auto shortest_route_duration = shortest_route.duration();
const auto scaled_at_most_longer_by =
scaledAtMostLongerByFactorBasedOnDuration(shortest_route_duration);
const auto stretch_duration_limit = (1. + scaled_at_most_longer_by) * shortest_route_duration;
const auto stretch_duration_limit = (1. + parameters.kAtMostLongerBy) * shortest_route_duration;
const auto over_duration_limit = [=](const auto &route) {
return route.duration() > stretch_duration_limit;
@ -559,6 +641,7 @@ void unpackPackedPaths(InputIt first,
}
WeightedViaNodeUnpackedPath unpacked_path{
0.0,
WeightedViaNode{packed_path_via, packed_path_weight},
std::move(unpacked_nodes),
std::move(unpacked_edges)};
@ -573,7 +656,8 @@ void unpackPackedPaths(InputIt first,
inline std::vector<WeightedViaNode>
makeCandidateVias(SearchEngineData<Algorithm> &search_engine_data,
const Facade &facade,
const PhantomNodes &phantom_node_pair)
const PhantomNodes &phantom_node_pair,
const Parameters &parameters)
{
Heap &forward_heap = *search_engine_data.forward_heap_1;
Heap &reverse_heap = *search_engine_data.reverse_heap_1;
@ -605,7 +689,7 @@ makeCandidateVias(SearchEngineData<Algorithm> &search_engine_data,
while (forward_heap.Size() + reverse_heap.Size() > 0)
{
if (shortest_path_weight != INVALID_EDGE_WEIGHT)
overlap_weight = shortest_path_weight * kSearchSpaceOverlapFactor;
overlap_weight = shortest_path_weight * parameters.kSearchSpaceOverlapFactor;
// Termination criteria - when we have a shortest path this will guarantee for our overlap.
const bool keep_going = forward_heap_min + reverse_heap_min < overlap_weight;
@ -670,7 +754,7 @@ makeCandidateVias(SearchEngineData<Algorithm> &search_engine_data,
return candidate_vias;
}
} // anon. ns
} // namespace
// Alternative Routes for MLD.
//
@ -691,9 +775,11 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
const PhantomNodes &phantom_node_pair,
unsigned number_of_alternatives)
{
Parameters parameters = parametersFromRequest(phantom_node_pair);
const auto max_number_of_alternatives = number_of_alternatives;
const auto max_number_of_alternatives_to_unpack =
kAlternativesToUnpackFactor * max_number_of_alternatives;
parameters.kAlternativesToUnpackFactor * max_number_of_alternatives;
BOOST_ASSERT(max_number_of_alternatives > 0);
BOOST_ASSERT(max_number_of_alternatives_to_unpack >= max_number_of_alternatives);
@ -707,7 +793,8 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
Heap &reverse_heap = *search_engine_data.reverse_heap_1;
// Do forward and backward search, save search space overlap as via candidates.
auto candidate_vias = makeCandidateVias(search_engine_data, facade, phantom_node_pair);
auto candidate_vias =
makeCandidateVias(search_engine_data, facade, phantom_node_pair, parameters);
const auto by_weight = [](const auto &lhs, const auto &rhs) { return lhs.weight < rhs.weight; };
auto shortest_path_via_it =
@ -730,6 +817,9 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
NodeID shortest_path_via = shortest_path_via_it->node;
EdgeWeight shortest_path_weight = shortest_path_via_it->weight;
const double duration_estimation = shortest_path_weight / facade.GetWeightMultiplier();
parameters.kAtMostLongerBy = getLongerByFactorBasedOnDuration(duration_estimation);
// Filters via candidate nodes with heuristics
// Note: filter pipeline below only makes range smaller; no need to erase items
@ -738,8 +828,7 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
it = filterViaCandidatesByUniqueNodeIds(begin(candidate_vias), it);
it = filterViaCandidatesByRoadImportance(begin(candidate_vias), it, facade);
it = filterViaCandidatesByStretch(
begin(candidate_vias), it, shortest_path_weight, facade.GetWeightMultiplier());
it = filterViaCandidatesByStretch(begin(candidate_vias), it, shortest_path_weight, parameters);
// Pre-rank by weight; sharing filtering below then discards by similarity.
std::sort(begin(candidate_vias), it, [](const auto lhs, const auto rhs) {
@ -783,10 +872,10 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
forward_heap, // paths for s, via
reverse_heap, // paths for via, t
begin(weighted_packed_paths) + 1,
alternative_paths_last);
alternative_paths_last,
parameters);
alternative_paths_last = filterPackedPathsByCellSharing(
begin(weighted_packed_paths), alternative_paths_last, partition);
begin(weighted_packed_paths), alternative_paths_last, partition, parameters);
BOOST_ASSERT(weighted_packed_paths.size() >= 1);
@ -816,7 +905,8 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
auto unpacked_paths_last = end(unpacked_paths);
unpacked_paths_last = filterUnpackedPathsBySharing(begin(unpacked_paths), end(unpacked_paths));
unpacked_paths_last = filterUnpackedPathsBySharing(
begin(unpacked_paths), end(unpacked_paths), facade, parameters);
const auto unpacked_paths_first = begin(unpacked_paths);
const auto number_of_unpacked_paths =
@ -850,7 +940,9 @@ InternalManyRoutesResult alternativePathSearch(SearchEngineData<Algorithm> &sear
if (routes.size() > 1)
{
routes_last = filterAnnotatedRoutesByStretch(routes_first + 1, routes_last, *routes_first);
parameters.kAtMostLongerBy = getLongerByFactorBasedOnDuration(routes_first->duration());
routes_last = filterAnnotatedRoutesByStretch(
routes_first + 1, routes_last, *routes_first, parameters);
routes.erase(routes_last, end(routes));
}

View File

@ -27,12 +27,15 @@ void deleteRegion(const storage::SharedRegionRegister::ShmKey key)
void listRegions()
{
osrm::util::Log() << "name\tshm key\ttimestamp\tsize";
if (!storage::SharedMonitor<storage::SharedRegionRegister>::exists())
{
return;
}
storage::SharedMonitor<storage::SharedRegionRegister> monitor;
std::vector<std::string> names;
const auto &shared_register = monitor.data();
shared_register.List(std::back_inserter(names));
osrm::util::Log() << "name\tshm key\ttimestamp\tsize";
for (const auto &name : names)
{
auto id = shared_register.Find(name);
@ -105,8 +108,7 @@ bool generateDataStoreOptions(const int argc,
boost::program_options::value<bool>(&list_datasets)
->default_value(false)
->implicit_value(true),
"Name of the dataset to load into memory. This allows having multiple datasets in memory "
"at the same time.") //
"List all OSRM datasets currently in memory") //
("only-metric",
boost::program_options::value<bool>(&only_metric)
->default_value(false)

View File

@ -41,7 +41,7 @@ typedef struct {
} mtar_raw_header_t;
static unsigned round_up(unsigned n, unsigned incr) {
static mtar_size_t round_up(mtar_size_t n, unsigned incr) {
return n + (incr - n % incr) % incr;
}
@ -60,14 +60,14 @@ static unsigned checksum(const mtar_raw_header_t* rh) {
}
static int tread(mtar_t *tar, void *data, unsigned size) {
static int tread(mtar_t *tar, void *data, mtar_size_t size) {
int err = tar->read(tar, data, size);
tar->pos += size;
return err;
}
static int twrite(mtar_t *tar, const void *data, unsigned size) {
static int twrite(mtar_t *tar, const void *data, mtar_size_t size) {
int err = tar->write(tar, data, size);
tar->pos += size;
return err;
@ -89,6 +89,7 @@ static int write_null_bytes(mtar_t *tar, int n) {
static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
unsigned chksum1, chksum2;
mtar_size_t filesize;
/* If the checksum starts with a null byte we assume the record is NULL */
if (*rh->checksum == '\0') {
@ -105,24 +106,68 @@ static int raw_to_header(mtar_header_t *h, const mtar_raw_header_t *rh) {
/* Load raw header into header */
sscanf(rh->mode, "%o", &h->mode);
sscanf(rh->owner, "%o", &h->owner);
sscanf(rh->size, "%o", &h->size);
sscanf(rh->mtime, "%o", &h->mtime);
h->type = rh->type;
strcpy(h->name, rh->name);
strcpy(h->linkname, rh->linkname);
/* Load size field */
if ((rh->size[0] & 0x80) == 0) {
#ifdef _MSC_VER
sscanf(rh->size, "%12llo", &h->size);
#else
sscanf(rh->size, "%12lo", &h->size);
#endif
} else {
h->size = (rh->size[0] & 0x7f) | (rh->size[0] & 0x40 ? 0x80 : 0);
uint8_t *p8 = (uint8_t *)&rh->size + 1;
while (p8 != (uint8_t *)&rh->size + sizeof(rh->size)) {
if (h->size >= ((mtar_size_t)1 << (sizeof(h->size) - 1) * 8)) {
return MTAR_EFAILURE;
}
h->size = ((mtar_size_t)h->size << 8) + *p8++;
}
}
return MTAR_ESUCCESS;
}
static int header_to_raw(mtar_raw_header_t *rh, const mtar_header_t *h) {
unsigned chksum;
mtar_size_t filesize = h->size;
/* Load header into raw header */
memset(rh, 0, sizeof(*rh));
/* Store size in ASCII octal digits or base-256 formats */
if (sizeof(mtar_size_t) <= 4 || filesize <= (mtar_size_t)077777777777LL) {
#ifdef _MSC_VER
sprintf(rh->size, "%llo", h->size);
#else
sprintf(rh->size, "%lo", h->size);
#endif
} else if (sizeof(filesize) < sizeof(rh->size)) {
/* GNU tar uses "base-256 encoding" for very large numbers.
* Encoding is binary, with highest bit always set as a marker
* and sign in next-highest bit:
* 80 00 .. 00 - zero
* bf ff .. ff - largest positive number
* ff ff .. ff - minus 1
* c0 00 .. 00 - smallest negative number
*/
uint8_t *p8 = (uint8_t *)&rh->size + sizeof(rh->size);
do {
*--p8 = (uint8_t)filesize;
filesize >>= 8;
} while (p8 != (uint8_t *)&rh->size);
*p8 |= 0x80;
} else {
return MTAR_EFAILURE;
}
sprintf(rh->mode, "%o", h->mode);
sprintf(rh->owner, "%o", h->owner);
sprintf(rh->size, "%o", h->size);
sprintf(rh->mtime, "%o", h->mtime);
rh->type = h->type ? h->type : MTAR_TREG;
strcpy(rh->name, h->name);
@ -153,17 +198,17 @@ const char* mtar_strerror(int err) {
}
static int file_write(mtar_t *tar, const void *data, unsigned size) {
unsigned res = fwrite(data, 1, size, tar->stream);
static int file_write(mtar_t *tar, const void *data, mtar_size_t size) {
mtar_size_t res = fwrite(data, 1, size, tar->stream);
return (res == size) ? MTAR_ESUCCESS : MTAR_EWRITEFAIL;
}
static int file_read(mtar_t *tar, void *data, unsigned size) {
unsigned res = fread(data, 1, size, tar->stream);
static int file_read(mtar_t *tar, void *data, mtar_size_t size) {
mtar_size_t res = fread(data, 1, size, tar->stream);
return (res == size) ? MTAR_ESUCCESS : MTAR_EREADFAIL;
}
static int file_seek(mtar_t *tar, unsigned offset) {
static int file_seek(mtar_t *tar, mtar_size_t offset) {
int res = fseek(tar->stream, offset, SEEK_SET);
return (res == 0) ? MTAR_ESUCCESS : MTAR_ESEEKFAIL;
}
@ -213,7 +258,7 @@ int mtar_close(mtar_t *tar) {
}
int mtar_seek(mtar_t *tar, unsigned pos) {
int mtar_seek(mtar_t *tar, mtar_size_t pos) {
int err = tar->seek(tar, pos);
tar->pos = pos;
return err;
@ -228,7 +273,8 @@ int mtar_rewind(mtar_t *tar) {
int mtar_next(mtar_t *tar) {
int err, n;
mtar_size_t n;
int err;
mtar_header_t h;
/* Load header */
err = mtar_read_header(tar, &h);
@ -287,7 +333,7 @@ int mtar_read_header(mtar_t *tar, mtar_header_t *h) {
}
int mtar_read_data(mtar_t *tar, void *ptr, unsigned size) {
int mtar_read_data(mtar_t *tar, void *ptr, mtar_size_t size) {
int err;
/* If we have no remaining data then this is the first read, we get the size,
* set the remaining data and seek to the beginning of the data */
@ -329,7 +375,7 @@ int mtar_write_header(mtar_t *tar, const mtar_header_t *h) {
}
int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size) {
int mtar_write_file_header(mtar_t *tar, const char *name, mtar_size_t size) {
mtar_header_t h;
/* Build header */
memset(&h, 0, sizeof(h));
@ -354,7 +400,7 @@ int mtar_write_dir_header(mtar_t *tar, const char *name) {
}
int mtar_write_data(mtar_t *tar, const void *data, unsigned size) {
int mtar_write_data(mtar_t *tar, const void *data, mtar_size_t size) {
int err;
/* Write data */
err = twrite(tar, data, size);

View File

@ -10,9 +10,12 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define MTAR_VERSION "0.1.0"
typedef size_t mtar_size_t;
enum {
MTAR_ESUCCESS = 0,
MTAR_EFAILURE = -1,
@ -38,7 +41,7 @@ enum {
typedef struct {
unsigned mode;
unsigned owner;
unsigned size;
mtar_size_t size;
unsigned mtime;
unsigned type;
char name[100];
@ -49,14 +52,14 @@ typedef struct {
typedef struct mtar_t mtar_t;
struct mtar_t {
int (*read)(mtar_t *tar, void *data, unsigned size);
int (*write)(mtar_t *tar, const void *data, unsigned size);
int (*seek)(mtar_t *tar, unsigned pos);
int (*read)(mtar_t *tar, void *data, mtar_size_t size);
int (*write)(mtar_t *tar, const void *data, mtar_size_t size);
int (*seek)(mtar_t *tar, mtar_size_t pos);
int (*close)(mtar_t *tar);
void *stream;
unsigned pos;
unsigned remaining_data;
unsigned last_header;
mtar_size_t pos;
mtar_size_t remaining_data;
mtar_size_t last_header;
};
@ -65,17 +68,17 @@ const char* mtar_strerror(int err);
int mtar_open(mtar_t *tar, const char *filename, const char *mode);
int mtar_close(mtar_t *tar);
int mtar_seek(mtar_t *tar, unsigned pos);
int mtar_seek(mtar_t *tar, mtar_size_t pos);
int mtar_rewind(mtar_t *tar);
int mtar_next(mtar_t *tar);
int mtar_find(mtar_t *tar, const char *name, mtar_header_t *h);
int mtar_read_header(mtar_t *tar, mtar_header_t *h);
int mtar_read_data(mtar_t *tar, void *ptr, unsigned size);
int mtar_read_data(mtar_t *tar, void *ptr, mtar_size_t size);
int mtar_write_header(mtar_t *tar, const mtar_header_t *h);
int mtar_write_file_header(mtar_t *tar, const char *name, unsigned size);
int mtar_write_file_header(mtar_t *tar, const char *name, mtar_size_t size);
int mtar_write_dir_header(mtar_t *tar, const char *name);
int mtar_write_data(mtar_t *tar, const void *data, unsigned size);
int mtar_write_data(mtar_t *tar, const void *data, mtar_size_t size);
int mtar_finalize(mtar_t *tar);

View File

@ -3,6 +3,8 @@
#include "../common/range_tools.hpp"
#include "../common/temporary_file.hpp"
#include <boost/function_output_iterator.hpp>
#include <boost/iterator/function_input_iterator.hpp>
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_SUITE(tar)
@ -187,4 +189,39 @@ BOOST_AUTO_TEST_CASE(continue_write_tar_file)
CHECK_EQUAL_COLLECTIONS(result_64bit_vector, vector_64bit);
}
// Boost test only supports disabling was only introduced in 1.59
#if BOOST_VERSION >= 105900
// This test case is disabled by default because it needs 10 GiB of storage
// Enable with ./storage-tests --run_test=tar/write_huge_tar_file
BOOST_AUTO_TEST_CASE(write_huge_tar_file, *boost::unit_test::disabled())
{
TemporaryFile tmp{TEST_DATA_DIR "/tar_huge_write_test.tar"};
std::uint64_t reference_checksum = 0;
{
storage::tar::FileWriter writer(tmp.path, storage::tar::FileWriter::GenerateFingerprint);
std::uint64_t value = 0;
const std::function<std::uint64_t()> encode_function = [&]() -> std::uint64_t {
reference_checksum += value;
return value++;
};
std::uint64_t num_elements = (10ULL * 1024ULL * 1024ULL * 1024ULL) / sizeof(std::uint64_t);
writer.WriteStreaming<std::uint64_t>(
"huge_data",
boost::make_function_input_iterator(encode_function, boost::infinite()),
num_elements);
}
std::uint64_t checksum = 0;
{
storage::tar::FileReader reader(tmp.path, storage::tar::FileReader::VerifyFingerprint);
reader.ReadStreaming<std::uint64_t>(
"huge_data",
boost::make_function_output_iterator([&](const auto &value) { checksum += value; }));
}
BOOST_CHECK_EQUAL(checksum, reference_checksum);
}
#endif
BOOST_AUTO_TEST_SUITE_END()