Closed abrensch closed 5 years ago
-> enhancement
Hi Arndt, did you make any progress on this issue?
No progress and not currently on my agenda.So looking for contributors...
Until anything better, Brouter could provide ( if interface allows it ) to Nav application the ETA value = Now + distance / ETA_flatspeed + Filtered_ascend / ETA_climbspeed. ( Like this online bike route planner does http://bikeroutetoaster.com/BRTWebUI/ )
ETA_flatspeed + ETA_climbspeed could be optional global profile parameters with implicit default values e.g. 20 km/h and 500 m / h.
Not perfect, but no ETA can be perfect.
I don't know if this subject is still topical, but I use this simple calculation to estimate my travel times, and when I'm alone with always the same bike it's surprisingly precise. I've just added "Plain_ascent/ETA_climbspeed", where Plain_ascent is positive or negative. To do things right, there should be 2 different values for ETA: City or road. For me, it's like 26 km/h on road, and 20 km/h in cities, including traffic, lights etc...
It is not easy for the BRouter to distinguish city or road scenarios. And it may be not wise to overcomplicate the rough estimation. The road/city scenario, physical health, the route length, the "hillyness" and mood, all affect the ETA.
I often use as 1-value-fits-all the value 15 km/h for medium-long bike travelling with baggage(*). It is statistically well justified by my own real cases, including short/medium breaks for rest, sightseeing, refreshment, meals or technical reasons.
The same hard value 15 km/h is used by some reasonable routers like was e.g. GPSmid(**). 20+ km/h is rather for short or sport cases with no breaks. ETA should be IMHO rather about estimation when you get there, and not when you get there by this current ( or short term average ) speed.
(*) medium-long distance 60-160 km routes with "hillyness" 600-1200m/100 km of filtered ascend. (**) a java based OSM navigation I was used to on my Nokia/Symbian.
Ok for the road/city scenario, that's right that it's too complicated. And if you give the possibility to change the ETA_flatspeed parameter, you can manually change it when you're in city or road. But on road for medium-long bike travelling, I find it really relevant to have this calculation with flatspeed and climbspeed. Of course physical health, mood, etc... will change the values, but it's up to the biker to change this values if necessary. And I don't think it will overcomplicate, if you have default values for people who doesn't really care for this estimation.
Sure, both ways, with flat + climb speed or without them, there are 2 of possible approaches. But that is all on @abrensch .
1.4.9 now supports different "cost-models", with a new "kinematic-model" used for car routing.
This kinematic model does support travel time. However, that's not suited for bike routing. So volunteers welcome to proceed on that issue.
I'm having a similar issue (and currently trying to look into it). Given a route computed by brouter, I'd like to compute an estimated travel time. For hiking, I know there are some functions which can be used to compute a good approximation given the elevations, such as https://en.wikipedia.org/wiki/Tobler%27s_hiking_function. Do you know anything similar which could be used for bikes?
I've recently found this that could interess you : http://www.velomath.fr/simulation.php Once you've found your parameters (Cx, f and power), what I did with a few tracks of different trips, its quite precise. You just export the gpx from brouter and import it on the site.
@tomdereub oh, that's really nice! Actually, in the mean time I also came across https://linuxfr.org/users/jben/journaux/rv-un-moteur-de-recherche-d-itineraire-velo-en-utilisant-les-donnees-d-osm which seems to be using the same kind of model, except this one is doing the routing as well. Routing is not as good as what brouter
offers and the software is mostly unmaintained but it might be worth extracting the time computation code to run it on brouter
GPX output. Then, this would only need finding a rather generic set of coefficients (like one for "average", one for "sporty" and one for "slow" cyclists for instance, this could even be made part of brouter
profile files).
Yes, I knew rv too. The difference I see is the speed limit in curve, that velomath is taking into account and not rv.
So maybe the "kinematic model" I use for the car-eco profile could be ported to bikes? Because that does things like curve-speed-limit, but that's just some hard-coded heuristics adapted to cars. And I guess the model of velomath assumes constant power? Because for a "bike-eco" I would also assume low power for downhill sections, in order to get some sort of time/energy balance.
I recently made some changes to get the time estimates fom brouters kinematic model into Locus, however, there's not yet a release containing that patch.
Some analysis and notes from myself as 2 cents contribution:
The force needed by a bike:
F = F_roll + F_climb + F_drag
F_roll = m . g . C_roll (*), m is total bike+person mass g is gravitational acceleration constant cca 9.8 m/s/s
C_roll is typically 0.005 for trekking bike and good asphalt.
(*) More exactly, should be multiplied by cos(alfa), but for such cases where it matters,, the rolling term is negligible to the climbing term.
F_ climb = m . g . sin(Alfa), Alfa is a climbing angle. Obviously, for descents, the climbing term is negative and is subtracted from needed power.
F_drag = 0.5 . c_z . air density . crossection_area. v^2 = C_drag . v^2 c_z is aerodynamic drag coefficient, v is speed
So F = m . g . C_roll + m . g . sin(Alfa) + C_drag . v^2
As power P = F . v, P = [ m .g . ( C_roll + sin(Alfa))] . v + C_drag . v^3
C_drag = [ P - [ m .g . ( C_roll + sin(Alfa))] . v ] / v^3
Typical human long term sustained power output P_sust = 150W.
Let suppose this power is needed for the sustained flat speed v_flat 25 Km/h, i.e 6.94 m/s, for the consistence of units.
Let suppose 100kg total mass for a biker with a bike and luggage.
If we put the above values to the equation, we get
C_drag = [150 - 100 . 9.8 . 0.005 . 6.94] /6.94^3
C_drag = [150 - 4.9 . 6.94] /6.94^3 C_drag = 0.347
So we have all the below parameters set
P_sust = [ m .g . ( C_roll + sin(Alfa))] . v + C_drag . v^3
resp. simplified
P_sust = C1(Alfa) . v + C_drag . v^3
From this we can get simplified approximation v=f(alfa,power).
Any difference between needed force and provided force (P/v) Is source of acceleration/deceleration.
In my simulations in Excel file, I have realized the default downhill cutoff slope 1.5% needs zero power for high travel speed 17 km/h, 66W for the nominal 25 km/h, nominal power 150W for 30 km/h.
Spending biker energy on speed > 25 km/h becomes energy wasting for considered bike eco mode. 10% higher speed means 30% higher power spent on the air drag.
I have considered in my simulations for descents progressive ceasing of biker power from nominal sustained power 150W at nominal flat speed 25 km/h to 0W at 35 km/h.
The biker power is limited and trekking is done also on not paved surfaces. Therefore I consider as important for bike kinematic model to expose C_roll parameter in the way context of the profile to be computed there e.g from tags smoothness, surface, highway.
As it can be 0.005 for nominal fine asphalt, or (I guess) easily 0.1 for rough gravel tracks or rooty wood paths.
i think the bike curve speed limit is seldom an issue but at high speed, and has minimal impact on ETA.
OTOH subjective rationale speed limit plays a significant role for narrow tracks/paths a/o for difficult surfaces.
E.g. nobody will make me to ride 40+ km/h downhill on loose gravel track even without a curve, worse if turning.
P.S.: note that for very long distance trips, sustained power will be lower, 75-100W, so does sustained flat speed.
I have some JS code which could be usable for brouter-web
to compute ETA with bike profiles. Thanks @poutnikl for the detailed notes on your experimentations!
var route = {…} // Response from BRouter-server API
const EARTH_RADIUS = 6378137; // in meters
// Gravitational constant, g
const GRAVITY = 9.81; // in meters per second^(-2)
// Total mass of bike + biker + luggages, m
const MASS = 90; // in kilograms
// Speed when we start braking and never go beyond
const MAX_SPEED = 45 * 3600 / 1000; // in meters per second
// Equivalent surface for wind, S * C_x, F = -1/2 * S * C_x * v^2 = - S_C_x * v^2
const S_C_x = 0.5 * 0.45; // in meters^2
// The resistance of the road, F = - m * g * C_r
const C_r = 0.01
// Biker power
const BIKER_POWER = 100; // in W
function distance(latLng1, latLng2) {
const lat1 = (latLng1[0] * Math.PI) / 180;
const lng1 = (latLng1[1] * Math.PI) / 180;
const lat2 = (latLng2[0] * Math.PI) / 180;
const lng2 = (latLng2[1] * Math.PI) / 180;
const a = (
(Math.sin((lat2 - lat1) / 2.0) ** 2)
+ (Math.cos(lat1) * Math.cos(lat2) * (Math.sin((lng2 - lng1) / 2.0) ** 2))
);
const c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return EARTH_RADIUS * c;
}
var properties = route.features[0].properties;
var geometry = route.features[0].geometry;
segments = [];
let currentSegmentPropertiesIndex = 1; // Skip the headers line
for (let i = 1; i < geometry.coordinates.length; i++) {
let currentCoordinates = geometry.coordinates[i];
let beginningElevation = geometry.coordinates[i - 1][2];
let endElevation = currentCoordinates[2];
let tags = {
'way': properties.messages[currentSegmentPropertiesIndex][9],
'node': properties.messages[currentSegmentPropertiesIndex][10]
};
let deltaElevation = endElevation - beginningElevation;
let length = distance(
[geometry.coordinates[i - 1][1], geometry.coordinates[i - 1][0]],
[currentCoordinates[1], currentCoordinates[0]]
);
let alpha = Math.atan2(deltaElevation, length);
let speed;
if (tags['way'].search('bicycle=dismount') !== -1) {
// Use Tobler's hiking function for walking sections
speed = 6 * Math.exp(-3.5 * Math.abs(deltaElevation / length + 0.05)) * 1000 / 3600;
} else {
var cubic = new algebra.Equation(
algebra.parse(`${BIKER_POWER} - (${MASS} * ${GRAVITY} * (${C_r} + (${Math.sin(alpha)}))) * v - ${S_C_x} * v^3`),
0
);
speed = cubic.solveFor("v").filter((item) => item > 0)[0];
}
let duration = length / Math.min(speed, MAX_SPEED);
segments.push({
deltaElevation,
duration,
length,
tags,
});
let currentSegmentLongitude = properties.messages[currentSegmentPropertiesIndex][0];
let currentSegmentLatitude = properties.messages[currentSegmentPropertiesIndex][1];
if (
Math.abs(currentCoordinates[0] * 1000000 - Number.parseFloat(currentSegmentLongitude)) < Number.EPSILON
&& Math.abs(currentCoordinates[1] * 1000000 - Number.parseFloat(currentSegmentLatitude)) < Number.EPSILON
) {
currentSegmentPropertiesIndex += 1;
}
}
let totalDuration = 0;
segments.forEach((segment) => {
totalDuration += segment.duration;
});
console.log(`${Math.round(totalDuration / 60, 2)} minutes`);
A demo CodePen is available at https://codepen.io/anon/pen/QJvjdN?editors=1010. cc @nrenner, not sure whether this might be worth pushing into brouter-web.
Note that I have an offset of about 150m on the total track length (on a total of almost 9000 meters) between my computed track length and the one sent by BRouter. I'm not sure where it comes from :/
Changing C_x
based on surface type or putting extra penalty for traffic signals should be doable quite easily from this base code.
EDIT I've just came across https://github.com/abrensch/brouter/issues/3 and apparently the difference in total length is not really an issue, it is already a known issue in BRouter and is likely to come from here then.
not sure whether this might be worth pushing into brouter-web
I guess it would make sense to also have this in the routing engine – someone to convert it to Java and integrate? But if that doesn't happen, I'm fine with a pull request until then.
I gave a try at a first PR here. The code does not work at the moment, I'm not exactly sure why. I'll have a deeper look at it.
If anyone is willing to help on this, feel free to comment on the PR and branch off it :)
EDIT: It's working now :) (except the bike=dismount
tag which I don't know how to handle in Java code… :/).
BRouter does not (yet) calculate travel times.
When used with OsmAnd, OsmAnds implicit "20km/h" for bikes is fine for me, but for car-routes, the implicit 60km/h is just a course guess and hust wrong for motorway trips.
Since BRouter's cost funtion is not based on speed, travel time calculations would require to create twins for the relevant terms in the routing profiles (costfactor, initialcost, turncost, ...) that deliver the estimated contribution to the travel time.
Simplest way to achieve that is to use separate files that are referenced from the profile-files that are processed by the same engine but deliver times in seconds instaed of costs and speeds instaed of costfactors.