From c517ccc2dffc1ae345e4b93b1f3b00bfabbfe4de Mon Sep 17 00:00:00 2001 From: Arndt Brenschede Date: Sun, 24 Sep 2017 17:11:04 +0200 Subject: [PATCH] version 1.4.9 --- brouter-codec/pom.xml | 2 +- brouter-core/pom.xml | 2 +- .../java/btools/router/KinematicModel.java | 98 +++ .../btools/router/KinematicModelDummy.java | 62 ++ .../java/btools/router/KinematicPath.java | 271 ++++++++ .../java/btools/router/KinematicPrePath.java | 58 ++ .../main/java/btools/router/MessageData.java | 3 + .../src/main/java/btools/router/OsmPath.java | 337 ++++------ .../java/btools/router/OsmPathElement.java | 17 +- .../main/java/btools/router/OsmPathModel.java | 20 + .../main/java/btools/router/OsmPrePath.java | 29 + .../src/main/java/btools/router/OsmTrack.java | 59 +- .../main/java/btools/router/ProfileCache.java | 10 +- .../java/btools/router/RoutingContext.java | 99 ++- .../java/btools/router/RoutingEngine.java | 118 +++- .../src/main/java/btools/router/StdModel.java | 38 ++ .../src/main/java/btools/router/StdPath.java | 596 ++++++++++++++++++ .../btools/router/WaypointMatcherImpl.java | 2 +- brouter-expressions/pom.xml | 2 +- .../expressions/BExpressionContext.java | 20 +- .../expressions/BExpressionContextGlobal.java | 35 - brouter-map-creator/pom.xml | 2 +- brouter-mapaccess/pom.xml | 2 +- .../btools/mapaccess/GeometryDecoder.java | 18 +- .../main/java/btools/mapaccess/OsmNode.java | 2 +- brouter-mem-router/pom.xml | 2 +- .../main/java/btools/memrouter/OsmNodeP.java | 2 +- .../btools/memrouter/ScheduledRouter.java | 4 +- brouter-routing-app/AndroidManifest.xml | 4 +- brouter-routing-app/assets/modes.zip | Bin 214 -> 215 bytes brouter-routing-app/assets/profiles2.zip | Bin 20390 -> 22471 bytes brouter-routing-app/pom.xml | 2 +- .../java/btools/routingapp/BRouterView.java | 23 +- brouter-server/pom.xml | 2 +- .../src/main/java/btools/server/BRouter.java | 2 +- .../main/java/btools/server/RouteServer.java | 2 +- brouter-util/pom.xml | 2 +- misc/pbfparser/compile_parser.bat | 2 +- misc/profiles2/car-eco.brf | 181 ++++++ misc/profiles2/car-fast.brf | 181 ++++++ misc/profiles2/car-test.brf | 158 ----- misc/profiles2/lookups.dat | 39 +- pom.xml | 2 +- 43 files changed, 2002 insertions(+), 508 deletions(-) create mode 100644 brouter-core/src/main/java/btools/router/KinematicModel.java create mode 100644 brouter-core/src/main/java/btools/router/KinematicModelDummy.java create mode 100644 brouter-core/src/main/java/btools/router/KinematicPath.java create mode 100644 brouter-core/src/main/java/btools/router/KinematicPrePath.java create mode 100644 brouter-core/src/main/java/btools/router/OsmPathModel.java create mode 100644 brouter-core/src/main/java/btools/router/OsmPrePath.java create mode 100644 brouter-core/src/main/java/btools/router/StdModel.java create mode 100644 brouter-core/src/main/java/btools/router/StdPath.java delete mode 100644 brouter-expressions/src/main/java/btools/expressions/BExpressionContextGlobal.java create mode 100644 misc/profiles2/car-eco.brf create mode 100644 misc/profiles2/car-fast.brf delete mode 100644 misc/profiles2/car-test.brf diff --git a/brouter-codec/pom.xml b/brouter-codec/pom.xml index b14d8e2..8275910 100644 --- a/brouter-codec/pom.xml +++ b/brouter-codec/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-codec diff --git a/brouter-core/pom.xml b/brouter-core/pom.xml index dd97bac..86be86c 100644 --- a/brouter-core/pom.xml +++ b/brouter-core/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-core diff --git a/brouter-core/src/main/java/btools/router/KinematicModel.java b/brouter-core/src/main/java/btools/router/KinematicModel.java new file mode 100644 index 0000000..db35a30 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/KinematicModel.java @@ -0,0 +1,98 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import btools.expressions.BExpressionContext; +import btools.expressions.BExpressionContextNode; +import btools.expressions.BExpressionContextWay; + + +class KinematicModel extends OsmPathModel +{ + public OsmPrePath createPrePath() + { + return new KinematicPrePath(); + } + + public OsmPath createPath() + { + return new KinematicPath(); + } + + public double turnAngleDecayLength; + public double f_roll; + public double f_air; + public double f_recup; + public double p_standby; + public double recup_efficiency; + public double totalweight; + public double vmax; + public double leftWaySpeed; + public double rightWaySpeed; + + // derived values + public double xweight; // the weight-factor between time and energy for cost calculation + public double timecost0; // minimum possible "energy-adjusted-time" per meter + + private int wayIdxMaxspeed; + private int wayIdxMinspeed; + + private int nodeIdxMaxspeed; + + protected BExpressionContextWay ctxWay; + protected BExpressionContextNode ctxNode; + + + @Override + public void init( BExpressionContextWay expctxWay, BExpressionContextNode expctxNode ) + { + ctxWay = expctxWay; + ctxNode = expctxNode; + + BExpressionContext expctxGlobal = expctxWay; // just one of them... + + turnAngleDecayLength = expctxGlobal.getVariableValue( "turnAngleDecayLength", 50.f ); + f_roll = expctxGlobal.getVariableValue( "f_roll", 232.f ); + f_air = expctxGlobal.getVariableValue( "f_air", 0.4f ); + f_recup = expctxGlobal.getVariableValue( "f_recup", 400.f ); + p_standby = expctxGlobal.getVariableValue( "p_standby", 250.f ); + recup_efficiency = expctxGlobal.getVariableValue( "recup_efficiency", 0.7f ); + totalweight = expctxGlobal.getVariableValue( "totalweight", 1640.f ); + vmax = expctxGlobal.getVariableValue( "vmax", 80.f ) / 3.6; + leftWaySpeed = expctxGlobal.getVariableValue( "leftWaySpeed", 12.f ) / 3.6; + rightWaySpeed = expctxGlobal.getVariableValue( "rightWaySpeed", 12.f ) / 3.6; + + xweight = 1./( 2. * f_air * vmax * vmax * vmax - p_standby ); + timecost0 = 1./vmax + xweight*(f_roll + f_air*vmax*vmax + p_standby/vmax ); + + wayIdxMaxspeed = ctxWay.getOutputVariableIndex( "maxspeed", false ); + wayIdxMinspeed = ctxWay.getOutputVariableIndex( "minspeed", false ); + + nodeIdxMaxspeed = ctxNode.getOutputVariableIndex( "maxspeed", false ); + } + + public float getWayMaxspeed() + { + return ctxWay.getBuildInVariable( wayIdxMaxspeed ) / 3.6f; + } + + public float getWayMinspeed() + { + return ctxWay.getBuildInVariable( wayIdxMinspeed ) / 3.6f; + } + + public float getNodeMaxspeed() + { + return ctxNode.getBuildInVariable( nodeIdxMaxspeed ) / 3.6f; + } + + public double getMaxKineticEnergy() + { + // determine maximum possible speed and kinetic energy + double mspeed = Math.min( getWayMaxspeed(), Math.max( getWayMinspeed(), vmax ) ); + return 0.5*totalweight*mspeed*mspeed; + } +} diff --git a/brouter-core/src/main/java/btools/router/KinematicModelDummy.java b/brouter-core/src/main/java/btools/router/KinematicModelDummy.java new file mode 100644 index 0000000..b215f6c --- /dev/null +++ b/brouter-core/src/main/java/btools/router/KinematicModelDummy.java @@ -0,0 +1,62 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import btools.expressions.BExpressionContextNode; +import btools.expressions.BExpressionContextWay; + + +final class KinematicModelDummy extends KinematicModel +{ + public OsmPrePath createPrePath() + { + return null; + } + + public OsmPath createPath() + { + return null; + } + + public KinematicModelDummy() + { + turnAngleDecayLength = 50.; + f_roll = 232.; + f_air = 0.4; + f_recup = 600.; + p_standby = 250.; + recup_efficiency = 0.7; + totalweight = 1640.; + vmax = 60./ 3.6; + leftWaySpeed = 12.f / 3.6; + rightWaySpeed = 12.f / 3.6; + } + public boolean useNewtonApprox; + + // derived values + public double xweight = 1./( 2. * f_air * vmax * vmax * vmax - p_standby ); + public double timecost0 = 1./vmax + xweight*(f_roll + f_air*vmax*vmax + p_standby/vmax ); + + @Override + public void init( BExpressionContextWay expctxWay, BExpressionContextNode expctxNode ) + { + } + + public float getWayMaxspeed() + { + return 100.f; + } + + public float getWayMinspeed() + { + return 0.f; + } + + public float getNodeMaxspeed() + { + return 999.f; + } +} diff --git a/brouter-core/src/main/java/btools/router/KinematicPath.java b/brouter-core/src/main/java/btools/router/KinematicPath.java new file mode 100644 index 0000000..24884cd --- /dev/null +++ b/brouter-core/src/main/java/btools/router/KinematicPath.java @@ -0,0 +1,271 @@ +/** + * The path-instance of the kinematic model + * + * @author ab + */ +package btools.router; + + +final class KinematicPath extends OsmPath +{ + private double ekin; // kinetic energy (Joule) + private double totalTime; // travel time (seconds) + private double totalEnergy; // total route energy (Joule) + private float floatingAngleLeft; // sliding average left bend (degree) + private float floatingAngleRight; // sliding average right bend (degree) + + @Override + protected void init( OsmPath orig ) + { + KinematicPath origin = (KinematicPath)orig; + ekin = origin.ekin; + totalTime = origin.totalTime; + totalEnergy = origin.totalEnergy; + floatingAngleLeft = origin.floatingAngleLeft; + floatingAngleRight = origin.floatingAngleRight; + priorityclassifier = origin.priorityclassifier; + } + + @Override + protected void resetState() + { + ekin = 0.; + totalTime = 0.; + totalEnergy = 0.; + floatingAngleLeft = 0.f; + floatingAngleRight = 0.f; + } + + @Override + protected double processWaySection( RoutingContext rc, double dist, double delta_h, double angle, double cosangle, boolean isStartpoint, int nsection, int lastpriorityclassifier ) + { + KinematicModel km = (KinematicModel)rc.pm; + + double cost = 0.; + + if ( isStartpoint ) + { + // for forward direction, we start with target speed + if ( !rc.inverseDirection ) + { + cost = 0.5 * (1. - cosangle ) * 40. / km.timecost0; // 40 seconds turn penalty + } + } + else + { + double turnspeed = 999.; // just high + + if ( km.turnAngleDecayLength != 0. ) // process turn-angle slowdown + { + double decayFactor = exp( - dist / km.turnAngleDecayLength ); + floatingAngleLeft = (float)( floatingAngleLeft * decayFactor ); + floatingAngleRight = (float)( floatingAngleRight * decayFactor ); + if ( angle < 0 ) floatingAngleLeft -= (float)angle; + else floatingAngleRight += (float)angle; + float aa = Math.max( floatingAngleLeft, floatingAngleRight ); + + if ( aa > 130. ) turnspeed = 0.; + else if ( aa > 100. ) turnspeed = 1.; + else if ( aa > 70. ) turnspeed = 2.; + else if ( aa > 50. ) turnspeed = 4.; + else if ( aa > 30. ) turnspeed = 8.; + else if ( aa > 20. ) turnspeed = 14.; + else if ( aa > 10. ) turnspeed = 20.; + } + + if ( nsection == 0 ) // process slowdown by crossing geometry + { + int classifiermask = (int)rc.expctxWay.getClassifierMask(); + + // penalty for equal priority crossing + boolean hasLeftWay = false; + boolean hasRightWay = false; + boolean hasResidential = false; + for( OsmPrePath prePath = rc.firstPrePath; prePath != null; prePath = prePath.next ) + { + KinematicPrePath pp = (KinematicPrePath)prePath; + + if ( ( (pp.classifiermask ^ classifiermask) & 8 ) != 0 ) // exactly one is linktype + { + continue; + } + + if ( ( pp.classifiermask & 32 ) != 0 ) // touching a residential? + { + hasResidential = true; + } + + if ( pp.priorityclassifier > priorityclassifier || pp.priorityclassifier == priorityclassifier && priorityclassifier < 20 ) + { + double diff = pp.angle - angle; + if ( diff < -40. && diff > -140.) hasLeftWay = true; + if ( diff > 40. && diff < 140. ) hasRightWay = true; + } + } + double residentialSpeed = 13.; + + if ( hasLeftWay && turnspeed > km.leftWaySpeed ) turnspeed = km.leftWaySpeed; + if ( hasRightWay && turnspeed > km.rightWaySpeed ) turnspeed = km.rightWaySpeed; + if ( hasResidential && turnspeed > residentialSpeed ) turnspeed = residentialSpeed; + if ( (lastpriorityclassifier < 20) ^ (priorityclassifier < 20) ) turnspeed = 0; // full stop for entering or leaving road network + } + + cutEkin( km.totalweight, turnspeed ); // apply turnspeed + } + + double distanceCost = evolveDistance( km, dist, delta_h ); + + if ( message != null ) + { + message.costfactor = (float)(distanceCost/dist); + } + + return cost + distanceCost; + } + + + protected double evolveDistance( KinematicModel km, double dist, double delta_h ) + { + // elevation force + double fh = delta_h * km.totalweight * 9.81 / dist; + + double emax = km.getMaxKineticEnergy(); + if ( emax <= 0. ) + { + return -1.; + } + double elow = 0.5*emax; // recup phase below half energy (=70% vmax) + + double elapsedTime = 0.; + double dissipatedEnergy = 0.; + + double v = Math.sqrt( 2. * ekin / km.totalweight ); + double d = dist; + while( d > 0. ) + { + boolean slow = ekin < elow; + boolean fast = ekin >= emax; + double etarget = slow ? elow : emax; + double f = km.f_roll + km.f_air*v*v + fh; + double f_recup = Math.max( 0., fast ? -f : (slow ? km.f_recup :0 ) -fh ); // additional recup for slow part + f += f_recup; + + double delta_ekin; + double x; + if ( fast ) + { + x = d; + delta_ekin = x*f; + elapsedTime += x/v; + ekin = etarget; + } + else + { + delta_ekin = etarget-ekin; + double b = 2.*km.f_air / km.totalweight; + double x0 = delta_ekin/f; + double x0b = x0*b; + x = x0*(1. - x0b*(0.5 + x0b*(0.333333333-x0b*0.25 ) ) ); // = ln( delta_ekin*b/f + 1.) / b; + double maxstep = Math.min( 50., d ); + if ( x >= maxstep ) + { + x = maxstep; + double xb = x*b; + delta_ekin = x*f*(1.+xb*(0.5+xb*(0.166666667+xb*0.0416666667 ) ) ); // = f/b* exp(xb-1) + ekin += delta_ekin; + } + else + { + ekin = etarget; + } + double v2 = Math.sqrt( 2. * ekin / km.totalweight ); + double a = f / km.totalweight; // TODO: average force? + elapsedTime += (v2-v)/a; + v = v2; + } + d -= x; + + // dissipated energy does not contain elevation and efficient recup + dissipatedEnergy += delta_ekin - x*(fh + f_recup*km.recup_efficiency); + } + + dissipatedEnergy += elapsedTime * km.p_standby; + + totalTime += elapsedTime; + totalEnergy += dissipatedEnergy + dist*fh; + + return (elapsedTime + km.xweight * dissipatedEnergy)/km.timecost0; // =cost + } + + @Override + protected double processTargetNode( RoutingContext rc ) + { + KinematicModel km = (KinematicModel)rc.pm; + + // finally add node-costs for target node + if ( targetNode.nodeDescription != null ) + { + rc.expctxNode.evaluate( false , targetNode.nodeDescription ); + float initialcost = rc.expctxNode.getInitialcost(); + if ( initialcost >= 1000000. ) + { + return -1.; + } + cutEkin( km.totalweight, km.getNodeMaxspeed() ); // apply node maxspeed + + if ( message != null ) + { + message.linknodecost += (int)initialcost; + message.nodeKeyValues = rc.expctxNode.getKeyValueDescription( false, targetNode.nodeDescription ); + } + return initialcost; + } + return 0.; + } + + private void cutEkin( double weight, double speed ) + { + double e = 0.5*weight*speed*speed; + if ( ekin > e ) ekin = e; + } + + private static double exp( double e ) + { + double x = e; + double f = 1.; + while( e < -1. ) + { + e += 1.; + f *= 0.367879; + } + return f*( 1. + x*( 1. + x * ( 0.5 + x * ( 0.166667 + 0.0416667 * x) ) ) ); + } + + + @Override + public int elevationCorrection( RoutingContext rc ) + { + return 0; + } + + @Override + public boolean definitlyWorseThan( OsmPath path, RoutingContext rc ) + { + KinematicPath p = (KinematicPath)path; + + int c = p.cost; + return cost > c + 100; + } + + + + public double getTotalTime() + { + return totalTime; + } + + public double getTotalEnergy() + { + return totalEnergy; + } +} diff --git a/brouter-core/src/main/java/btools/router/KinematicPrePath.java b/brouter-core/src/main/java/btools/router/KinematicPrePath.java new file mode 100644 index 0000000..13013ef --- /dev/null +++ b/brouter-core/src/main/java/btools/router/KinematicPrePath.java @@ -0,0 +1,58 @@ +/** + * Simple version of OsmPath just to get angle and priority of first segment + * + * @author ab + */ +package btools.router; + +import btools.mapaccess.OsmNode; +import btools.mapaccess.OsmTransferNode; + +final class KinematicPrePath extends OsmPrePath +{ + public double angle; + public int priorityclassifier; + public int classifiermask; + + protected void initPrePath(OsmPath origin, RoutingContext rc ) + { + byte[] description = link.descriptionBitmap; + if ( description == null ) throw new IllegalArgumentException( "null description for: " + link ); + + // extract the 3 positions of the first section + int lon0 = origin.originLon; + int lat0 = origin.originLat; + + OsmNode p1 = sourceNode; + int lon1 = p1.getILon(); + int lat1 = p1.getILat(); + + boolean isReverse = link.isReverse( sourceNode ); + + // evaluate the way tags + rc.expctxWay.evaluate( rc.inverseDirection ^ isReverse, description ); + + OsmTransferNode transferNode = link.geometry == null ? null + : rc.geometryDecoder.decodeGeometry( link.geometry, p1, targetNode, isReverse ); + + int lon2; + int lat2; + + if ( transferNode == null ) + { + lon2 = targetNode.ilon; + lat2 = targetNode.ilat; + } + else + { + lon2 = transferNode.ilon; + lat2 = transferNode.ilat; + } + + int dist = rc.calcDistance( lon1, lat1, lon2, lat2 ); + + angle = rc.calcAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); + priorityclassifier = (int)rc.expctxWay.getPriorityClassifier(); + classifiermask = (int)rc.expctxWay.getClassifierMask(); + } +} diff --git a/brouter-core/src/main/java/btools/router/MessageData.java b/brouter-core/src/main/java/btools/router/MessageData.java index abe93fb..873b96d 100644 --- a/brouter-core/src/main/java/btools/router/MessageData.java +++ b/brouter-core/src/main/java/btools/router/MessageData.java @@ -26,6 +26,9 @@ final class MessageData implements Cloneable int lat; short ele; + float time; + float energy; + String toMessage() { if ( wayKeyValues == null ) diff --git a/brouter-core/src/main/java/btools/router/OsmPath.java b/brouter-core/src/main/java/btools/router/OsmPath.java index d413936..1454662 100644 --- a/brouter-core/src/main/java/btools/router/OsmPath.java +++ b/brouter-core/src/main/java/btools/router/OsmPath.java @@ -13,33 +13,27 @@ import btools.mapaccess.OsmNode; import btools.mapaccess.OsmTransferNode; import btools.mapaccess.TurnRestriction; -final class OsmPath implements OsmLinkHolder +abstract class OsmPath implements OsmLinkHolder { /** * The cost of that path (a modified distance) */ public int cost = 0; - /** - * The elevation-hysteresis-buffer (0-10 m) - */ - private int ehbd; // in micrometer - private int ehbu; // in micrometer - // the elevation assumed for that path can have a value // if the corresponding node has not public short selev; public int airdistance = 0; // distance to endpos - private OsmNode sourceNode; - private OsmNode targetNode; + protected OsmNode sourceNode; + protected OsmNode targetNode; - private OsmLink link; + protected OsmLink link; public OsmPathElement originElement; public OsmPathElement myElement; - private float traffic; + protected float traffic; private OsmLinkHolder nextForLink = null; @@ -51,7 +45,9 @@ final class OsmPath implements OsmLinkHolder public int originLat; // the classifier of the segment just before this paths position - public float lastClassifier; + protected float lastClassifier; + + protected int priorityclassifier; public MessageData message; @@ -81,13 +77,8 @@ final class OsmPath implements OsmLinkHolder } } - OsmPath() + public void init( OsmLink link ) { - } - - OsmPath( OsmLink link ) - { - this(); this.link = link; targetNode = link.getTarget( null ); selev = targetNode.getSElev(); @@ -96,9 +87,8 @@ final class OsmPath implements OsmLinkHolder originLat = -1; } - OsmPath( OsmPath origin, OsmLink link, OsmTrack refTrack, boolean detailMode, RoutingContext rc ) + public void init( OsmPath origin, OsmLink link, OsmTrack refTrack, boolean detailMode, RoutingContext rc ) { - this(); if ( origin.myElement == null ) { origin.myElement = OsmPathElement.create( origin, rc.countTraffic ); @@ -108,19 +98,22 @@ final class OsmPath implements OsmLinkHolder this.sourceNode = origin.targetNode; this.targetNode = link.getTarget( sourceNode ); this.cost = origin.cost; - this.ehbd = origin.ehbd; - this.ehbu = origin.ehbu; this.lastClassifier = origin.lastClassifier; + init( origin ); addAddionalPenalty(refTrack, detailMode, origin, link, rc ); } + + protected abstract void init( OsmPath orig ); - private void addAddionalPenalty(OsmTrack refTrack, boolean detailMode, OsmPath origin, OsmLink link, RoutingContext rc ) + protected abstract void resetState(); + + + protected void addAddionalPenalty(OsmTrack refTrack, boolean detailMode, OsmPath origin, OsmLink link, RoutingContext rc ) { byte[] description = link.descriptionBitmap; - if ( description == null ) throw new IllegalArgumentException( "null description for: " + link ); + if ( description == null ) throw new IllegalArgumentException( "null description for: " + link ); boolean recordTransferNodes = detailMode || rc.countTraffic; - boolean recordMessageData = detailMode; rc.nogomatch = false; @@ -128,55 +121,48 @@ final class OsmPath implements OsmLinkHolder int lon0 = origin.originLon; int lat0 = origin.originLat; - OsmNode p1 = sourceNode; - int lon1 = p1.getILon(); - int lat1 = p1.getILat(); + int lon1 = sourceNode.getILon(); + int lat1 = sourceNode.getILat(); short ele1 = origin.selev; int linkdisttotal = 0; - MessageData msgData = recordMessageData ? new MessageData() : null; + message = detailMode ? new MessageData() : null; boolean isReverse = link.isReverse( sourceNode ); // evaluate the way tags rc.expctxWay.evaluate( rc.inverseDirection ^ isReverse, description ); + // calculate the costfactor inputs + float costfactor = rc.expctxWay.getCostfactor(); boolean isTrafficBackbone = cost == 0 && rc.expctxWay.getIsTrafficBackbone() > 0.f; - float turncostbase = rc.expctxWay.getTurncost(); - float cfup = rc.expctxWay.getUphillCostfactor(); - float cfdown = rc.expctxWay.getDownhillCostfactor(); - float cf = rc.expctxWay.getCostfactor(); - cfup = cfup == 0.f ? cf : cfup; - cfdown = cfdown == 0.f ? cf : cfdown; + int lastpriorityclassifier = priorityclassifier; + priorityclassifier = (int)rc.expctxWay.getPriorityClassifier(); + int classifiermask = (int)rc.expctxWay.getClassifierMask(); // *** add initial cost if the classifier changed float newClassifier = rc.expctxWay.getInitialClassifier(); - if ( newClassifier == 0. ) - { - newClassifier = (cfup + cfdown + cf)/3; - } float classifierDiff = newClassifier - lastClassifier; if ( classifierDiff > 0.0005 || classifierDiff < -0.0005 ) { lastClassifier = newClassifier; float initialcost = rc.expctxWay.getInitialcost(); int iicost = (int)initialcost; - if ( recordMessageData ) + if ( message != null ) { - msgData.linkinitcost += iicost; + message.linkinitcost += iicost; } cost += iicost; } OsmTransferNode transferNode = link.geometry == null ? null - : rc.geometryDecoder.decodeGeometry( link.geometry, p1, targetNode, isReverse ); + : rc.geometryDecoder.decodeGeometry( link.geometry, sourceNode, targetNode, isReverse ); - boolean isFirstSection = true; - - for(;;) + for(int nsection=0; ;nsection++) { + originLon = lon1; originLat = lat1; @@ -197,19 +183,8 @@ final class OsmPath implements OsmLinkHolder ele2 = transferNode.selev; } - // check turn restrictions: do we have one with that origin? - boolean checkTRs = false; - if ( isFirstSection ) - { - isFirstSection = false; - - // TODO: TRs for inverse routing would need inverse TR logic, - // inverse routing for now just for target island check, so don't care (?) - // in detail mode (=final pass) no TR to not mess up voice hints - checkTRs = rc.considerTurnRestrictions && !rc.inverseDirection && !detailMode; - } - - if ( checkTRs ) + // check turn restrictions (n detail mode (=final pass) no TR to not mess up voice hints) + if ( nsection == 0 && rc.considerTurnRestrictions && !detailMode ) { boolean hasAnyPositive = false; boolean hasPositive = false; @@ -217,14 +192,22 @@ final class OsmPath implements OsmLinkHolder TurnRestriction tr = sourceNode.firstRestriction; while( tr != null ) { - boolean trValid = ! (tr.exceptBikes() && rc.bikeMode); - if ( trValid && tr.fromLon == lon0 && tr.fromLat == lat0 ) + if ( tr.exceptBikes() && rc.bikeMode ) + { + tr = tr.next; + continue; + } + int fromLon = rc.inverseDirection ? lon2 : lon0; + int fromLat = rc.inverseDirection ? lat2 : lat0; + int toLon = rc.inverseDirection ? lon0 : lon2; + int toLat = rc.inverseDirection ? lat0 : lat2; + if ( tr.fromLon == fromLon && tr.fromLat == fromLat ) { if ( tr.isPositive ) { hasAnyPositive = true; } - if ( tr.toLon == lon2 && tr.toLat == lat2 ) + if ( tr.toLon == toLon && tr.toLat == toLat ) { if ( tr.isPositive ) { @@ -246,13 +229,14 @@ final class OsmPath implements OsmLinkHolder } // if recording, new MessageData for each section (needed for turn-instructions) - if ( recordMessageData && msgData.wayKeyValues != null ) + if ( message != null && message.wayKeyValues != null ) { - originElement.message = msgData; - msgData = new MessageData(); + originElement.message = message; + message = new MessageData(); } int dist = rc.calcDistance( lon1, lat1, lon2, lat2 ); + boolean stopAtEndpoint = false; if ( rc.shortestmatch ) { @@ -263,10 +247,9 @@ final class OsmPath implements OsmLinkHolder } else { - // we just start here, reset cost + // we just start here, reset everything cost = 0; - ehbd = 0; - ehbu = 0; + resetState(); lon0 = -1; // reset turncost-pipe lat0 = -1; @@ -285,131 +268,57 @@ final class OsmPath implements OsmLinkHolder } } - if ( recordMessageData ) + if ( message != null ) { - msgData.linkdist += dist; + message.linkdist += dist; } linkdisttotal += dist; // apply a start-direction if appropriate (by faking the origin position) - if ( lon0 == -1 && lat0 == -1 ) + boolean isStartpoint = lon0 == -1 && lat0 == -1; + if ( isStartpoint ) { - double coslat = Math.cos( ( lat1 - 90000000 ) * 0.00000001234134 ); - if ( rc.startDirectionValid && coslat > 0. ) + if ( rc.startDirectionValid ) { double dir = rc.startDirection.intValue() / 57.29578; - lon0 = lon1 - (int) ( 1000. * Math.sin( dir ) / coslat ); + lon0 = lon1 - (int) ( 1000. * Math.sin( dir ) / rc.getCosLat() ); lat0 = lat1 - (int) ( 1000. * Math.cos( dir ) ); } - } - - // *** penalty for turning angles - if ( !isTrafficBackbone && lon0 != -1 && lat0 != -1 ) - { - // penalty proportional to direction change - double cos = rc.calcCosAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); - int actualturncost = (int)(cos * turncostbase + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty - cost += actualturncost; - if ( recordMessageData ) + else { - msgData.linkturncost += actualturncost; - msgData.turnangle = (float)rc.calcAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); + lon0 = lon1 - (lon2-lon1); + lat0 = lat1 - (lat2-lat1); } } + double angle = rc.calcAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); + double cosangle = rc.getCosAngle(); - // *** penalty for elevation (penalty is for descend! in a way that slow descends give no penalty) - // only the part of the descend that does not fit into the elevation-hysteresis-buffer - // leads to an immediate penalty - - int elefactor = 250000; + // *** elevation stuff + double delta_h = 0.; if ( ele2 == Short.MIN_VALUE ) ele2 = ele1; if ( ele1 != Short.MIN_VALUE ) { - ehbd += (ele1 - ele2)*elefactor - dist * rc.downhillcutoff; - ehbu += (ele2 - ele1)*elefactor - dist * rc.uphillcutoff; - } - - float downweight = 0.f; - if ( ehbd > rc.elevationpenaltybuffer ) - { - downweight = 1.f; - - int excess = ehbd - rc.elevationpenaltybuffer; - int reduce = dist * rc.elevationbufferreduce; - if ( reduce > excess ) - { - downweight = ((float)excess)/reduce; - reduce = excess; - } - excess = ehbd - rc.elevationmaxbuffer; - if ( reduce < excess ) - { - reduce = excess; - } - ehbd -= reduce; - if ( rc.downhillcostdiv > 0 ) - { - int elevationCost = reduce/rc.downhillcostdiv; - cost += elevationCost; - if ( recordMessageData ) - { - msgData.linkelevationcost += elevationCost; - } - } - } - else if ( ehbd < 0 ) - { - ehbd = 0; - } - - float upweight = 0.f; - if ( ehbu > rc.elevationpenaltybuffer ) - { - upweight = 1.f; - - int excess = ehbu - rc.elevationpenaltybuffer; - int reduce = dist * rc.elevationbufferreduce; - if ( reduce > excess ) + delta_h = (ele2 - ele1)/4.; + if ( rc.inverseDirection ) { - upweight = ((float)excess)/reduce; - reduce = excess; - } - excess = ehbu - rc.elevationmaxbuffer; - if ( reduce < excess ) - { - reduce = excess; - } - ehbu -= reduce; - if ( rc.uphillcostdiv > 0 ) - { - int elevationCost = reduce/rc.uphillcostdiv; - cost += elevationCost; - if ( recordMessageData ) - { - msgData.linkelevationcost += elevationCost; - } + delta_h = -delta_h; } } - else if ( ehbu < 0 ) - { - ehbu = 0; - } - // get the effective costfactor (slope dependent) - float costfactor = cfup*upweight + cf*(1.f - upweight - downweight) + cfdown*downweight; - if ( isTrafficBackbone ) - { - costfactor = 0.f; - } - float fcost = dist * costfactor + 0.5f; - if ( ( costfactor > 9998. && !detailMode ) || fcost + cost >= 2000000000. ) + double sectionCost = processWaySection( rc, dist, delta_h, angle, cosangle, isStartpoint, nsection, lastpriorityclassifier ); + if ( ( sectionCost < 0. || costfactor > 9998. && !detailMode ) || sectionCost + cost >= 2000000000. ) { cost = -1; return; } - int waycost = (int)(fcost); - cost += waycost; + + if ( isTrafficBackbone ) + { + sectionCost = 0.; + } + + cost += (int)sectionCost; // calculate traffic if ( rc.countTraffic ) @@ -419,15 +328,17 @@ final class OsmPath implements OsmLinkHolder traffic += dist*rc.expctxWay.getTrafficSourceDensity()*Math.pow(cost2/10000.f,rc.trafficSourceExponent); } - if ( recordMessageData ) + if ( message != null ) { - msgData.costfactor = costfactor; - msgData.priorityclassifier = (int)rc.expctxWay.getPriorityClassifier(); - msgData.classifiermask = (int)rc.expctxWay.getClassifierMask(); - msgData.lon = lon2; - msgData.lat = lat2; - msgData.ele = ele2; - msgData.wayKeyValues = rc.expctxWay.getKeyValueDescription( isReverse, description ); + message.turnangle = (float)angle; + message.time = (float)getTotalTime(); + message.energy = (float)getTotalEnergy(); + message.priorityclassifier = priorityclassifier; + message.classifiermask = classifiermask; + message.lon = lon2; + message.lat = lat2; + message.ele = ele2; + message.wayKeyValues = rc.expctxWay.getKeyValueDescription( isReverse, description ); } if ( stopAtEndpoint ) @@ -436,9 +347,9 @@ final class OsmPath implements OsmLinkHolder { originElement = OsmPathElement.create( rc.ilonshortest, rc.ilatshortest, ele2, originElement, rc.countTraffic ); originElement.cost = cost; - if ( recordMessageData ) + if ( message != null ) { - originElement.message = msgData; + originElement.message = message; } } if ( rc.nogomatch ) @@ -473,44 +384,26 @@ final class OsmPath implements OsmLinkHolder lon1 = lon2; lat1 = lat2; ele1 = ele2; - } // check for nogo-matches (after the *actual* start of segment) if ( rc.nogomatch ) { - cost = -1; - return; + cost = -1; + return; } - // finally add node-costs for target node - if ( targetNode.nodeDescription != null ) + // add target-node costs + double targetCost = processTargetNode( rc ); + if ( targetCost < 0. || targetCost + cost >= 2000000000. ) { - boolean nodeAccessGranted = rc.expctxWay.getNodeAccessGranted() != 0.; - rc.expctxNode.evaluate( nodeAccessGranted , targetNode.nodeDescription ); - float initialcost = rc.expctxNode.getInitialcost(); - if ( initialcost >= 1000000. ) - { - cost = -1; - return; - } - int iicost = (int)initialcost; - - cost += iicost; - - if ( recordMessageData ) - { - msgData.linknodecost += iicost; - msgData.nodeKeyValues = rc.expctxNode.getKeyValueDescription( nodeAccessGranted, targetNode.nodeDescription ); - } + cost = -1; + return; } - if ( recordMessageData ) - { - message = msgData; - } - + cost += (int)targetCost; } + public short interpolateEle( short e1, short e2, double fraction ) { if ( e1 == Short.MIN_VALUE || e2 == Short.MIN_VALUE ) @@ -520,29 +413,13 @@ final class OsmPath implements OsmLinkHolder return (short)( e1*(1.-fraction) + e2*fraction ); } - public int elevationCorrection( RoutingContext rc ) - { - return ( rc.downhillcostdiv > 0 ? ehbd/rc.downhillcostdiv : 0 ) - + ( rc.uphillcostdiv > 0 ? ehbu/rc.uphillcostdiv : 0 ); - } + protected abstract double processWaySection( RoutingContext rc, double dist, double delta_h, double angle, double cosangle, boolean isStartpoint, int nsection, int lastpriorityclassifier ); - public boolean definitlyWorseThan( OsmPath p, RoutingContext rc ) - { - int c = p.cost; - if ( rc.downhillcostdiv > 0 ) - { - int delta = p.ehbd - ehbd; - if ( delta > 0 ) c += delta/rc.downhillcostdiv; - } - if ( rc.uphillcostdiv > 0 ) - { - int delta = p.ehbu - ehbu; - if ( delta > 0 ) c += delta/rc.uphillcostdiv; - } - - return cost > c; - } - + protected abstract double processTargetNode( RoutingContext rc ); + + public abstract int elevationCorrection( RoutingContext rc ); + + public abstract boolean definitlyWorseThan( OsmPath p, RoutingContext rc ); public OsmNode getSourceNode() { @@ -569,4 +446,14 @@ final class OsmPath implements OsmLinkHolder { return nextForLink; } + + public double getTotalTime() + { + return 0.; + } + + public double getTotalEnergy() + { + return 0.; + } } diff --git a/brouter-core/src/main/java/btools/router/OsmPathElement.java b/brouter-core/src/main/java/btools/router/OsmPathElement.java index d061ca7..34807d8 100644 --- a/brouter-core/src/main/java/btools/router/OsmPathElement.java +++ b/brouter-core/src/main/java/btools/router/OsmPathElement.java @@ -18,7 +18,7 @@ public class OsmPathElement implements OsmPos private int ilat; // latitude private int ilon; // longitude private short selev; // longitude - + public MessageData message = null; // description public int cost; @@ -44,6 +44,19 @@ public class OsmPathElement implements OsmPos return selev / 4.; } + public final float getTime() + { + return message == null ? 0.f : message.time; + } + + public final void setTime( float t ) + { + if ( message != null ) + { + message.time = t; + } + } + public final long getIdFromPos() { return ((long)ilon)<<32 | ilat; @@ -58,7 +71,7 @@ public class OsmPathElement implements OsmPos double dlat = (ilat - p.getILat() )/1000000.; double dlon = (ilon - p.getILon() )/1000000. * coslat; - double d = Math.sqrt( dlat*dlat + dlon*dlon ) * (6378000. / 57.); + double d = Math.sqrt( dlat*dlat + dlon*dlon ) * 110984.; // 6378000. / 57.3; return (int)(d + 1.0 ); } diff --git a/brouter-core/src/main/java/btools/router/OsmPathModel.java b/brouter-core/src/main/java/btools/router/OsmPathModel.java new file mode 100644 index 0000000..17a7bda --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OsmPathModel.java @@ -0,0 +1,20 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import btools.expressions.BExpressionContext; +import btools.expressions.BExpressionContextNode; +import btools.expressions.BExpressionContextWay; + + +abstract class OsmPathModel +{ + public abstract OsmPrePath createPrePath(); + + public abstract OsmPath createPath(); + + public abstract void init( BExpressionContextWay expctxWay, BExpressionContextNode expctxNode ); +} diff --git a/brouter-core/src/main/java/btools/router/OsmPrePath.java b/brouter-core/src/main/java/btools/router/OsmPrePath.java new file mode 100644 index 0000000..266cf58 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OsmPrePath.java @@ -0,0 +1,29 @@ +/** + * Simple version of OsmPath just to get angle and priority of first segment + * + * @author ab + */ +package btools.router; + +import btools.mapaccess.OsmLink; +import btools.mapaccess.OsmNode; +import btools.mapaccess.OsmTransferNode; + +public abstract class OsmPrePath +{ + protected OsmNode sourceNode; + protected OsmNode targetNode; + protected OsmLink link; + + public OsmPrePath next; + + public void init( OsmPath origin, OsmLink link, RoutingContext rc ) + { + this.link = link; + this.sourceNode = origin.getTargetNode(); + this.targetNode = link.getTarget( sourceNode ); + initPrePath(origin, rc ); + } + + protected abstract void initPrePath(OsmPath origin, RoutingContext rc ); +} diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java index 1ca24af..217b03e 100644 --- a/brouter-core/src/main/java/btools/router/OsmTrack.java +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -37,6 +37,8 @@ public final class OsmTrack public long profileTimestamp; public boolean isDirty; + public boolean showspeed; + private static class OsmPathElementHolder { public OsmPathElement node; @@ -120,7 +122,7 @@ public final class OsmTrack MessageData current = null; for ( OsmPathElement n : nodes ) { - if ( n.message != null ) + if ( n.message != null && n.message.wayKeyValues != null ) { MessageData md = n.message.copy(); if ( current != null ) @@ -289,11 +291,15 @@ public final class OsmTrack public void appendTrack( OsmTrack t ) { + int ourSize = nodes.size(); + float t0 = ourSize > 0 ? nodes.get(ourSize - 1 ).getTime() : 0; for ( int i = 0; i < t.nodes.size(); i++ ) { - if ( i > 0 || nodes.size() == 0 ) + if ( i > 0 || ourSize == 0 ) { - nodes.add( t.nodes.get( i ) ); + OsmPathElement e = t.nodes.get( i ); + e.setTime( e.getTime() + t0 ); + nodes.add( e ); } } @@ -313,12 +319,16 @@ public final class OsmTrack ascend += t.ascend; plainAscend += t.plainAscend; cost += t.cost; + energy += t.energy; + + showspeed |= t.showspeed; } public int distance; public int ascend; public int plainAscend; public int cost; + public int energy; /** * writes the track in gpx-format to a file @@ -390,7 +400,7 @@ public final class OsmTrack } else { - sb.append( " creator=\"BRouter-1.4.8\" version=\"1.1\">\n" ); + sb.append( " creator=\"BRouter-1.4.9\" version=\"1.1\">\n" ); } if ( turnInstructionMode == 3) // osmand style @@ -537,6 +547,8 @@ public final class OsmTrack sb.append( " \"track-length\": \"" ).append( distance ).append( "\",\n" ); sb.append( " \"filtered ascend\": \"" ).append( ascend ).append( "\",\n" ); sb.append( " \"plain-ascend\": \"" ).append( plainAscend ).append( "\",\n" ); + sb.append( " \"total-time\": \"" ).append( getTotalSeconds() ).append( "\",\n" ); + sb.append( " \"total-energy\": \"" ).append( energy ).append( "\",\n" ); sb.append( " \"cost\": \"" ).append( cost ).append( "\",\n" ); sb.append( " \"messages\": [\n" ); sb.append( " [\"" ).append( MESSAGES_HEADER.replaceAll( "\t", "\", \"" ) ).append( "\"],\n" ); @@ -563,11 +575,27 @@ public final class OsmTrack sb.append( " \"type\": \"LineString\",\n" ); sb.append( " \"coordinates\": [\n" ); + OsmPathElement nn = null; for ( OsmPathElement n : nodes ) { String sele = n.getSElev() == Short.MIN_VALUE ? "" : ", " + n.getElev(); + if ( showspeed ) // hack: show speed instead of elevation + { + int speed = 0; + if ( nn != null ) + { + int dist = n.calcDistance( nn ); + float dt = n.getTime()-nn.getTime(); + if ( dt != 0.f ) + { + speed = (int)((3.6f*dist)/dt + 0.5); + } + } + sele = ", " + speed; + } sb.append( " [" ).append( formatILon( n.getILon() ) ).append( ", " ).append( formatILat( n.getILat() ) ) .append( sele ).append( "],\n" ); + nn = n; } sb.deleteCharAt( sb.lastIndexOf( "," ) ); @@ -580,6 +608,22 @@ public final class OsmTrack return sb.toString(); } + private int getTotalSeconds() + { + float s = nodes.size() < 2 ? 0 : nodes.get( nodes.size()-1 ).getTime() - nodes.get( 0 ).getTime(); + return (int)(s + 0.5); + } + + public String getFormattedTime() + { + return format1( getTotalSeconds()/60. ) + "m"; + } + + public String getFormattedEnergy() + { + return format1( energy/3600000. ) + "kwh"; + } + private static String formatILon( int ilon ) { return formatPos( ilon - 180000000 ); @@ -608,6 +652,13 @@ public final class OsmTrack ac[i--] = '-'; return new String( ac, i + 1, 11 - i ); } + + private String format1( double n ) + { + String s = "" + (long)(n*10 + 0.5); + int len = s.length(); + return s.substring( 0, len-1 ) + "." + s.charAt( len-1 ); + } public void dumpMessages( String filename, RoutingContext rc ) throws Exception { diff --git a/brouter-core/src/main/java/btools/router/ProfileCache.java b/brouter-core/src/main/java/btools/router/ProfileCache.java index f03e6ae..b6b4c8f 100644 --- a/brouter-core/src/main/java/btools/router/ProfileCache.java +++ b/brouter-core/src/main/java/btools/router/ProfileCache.java @@ -7,7 +7,6 @@ package btools.router; import java.io.File; -import btools.expressions.BExpressionContextGlobal; import btools.expressions.BExpressionContextNode; import btools.expressions.BExpressionContextWay; import btools.expressions.BExpressionMetaData; @@ -54,7 +53,7 @@ public final class ProfileCache rc.expctxWay = expctxWay; rc.expctxNode = expctxNode; profilesBusy = true; - rc.readGlobalConfig(expctxWay); + rc.readGlobalConfig(); return true; } } @@ -62,19 +61,16 @@ public final class ProfileCache BExpressionMetaData meta = new BExpressionMetaData(); - BExpressionContextGlobal expctxGlobal = new BExpressionContextGlobal( meta ); rc.expctxWay = new BExpressionContextWay( rc.memoryclass * 512, meta ); rc.expctxNode = new BExpressionContextNode( 0, meta ); rc.expctxNode.setForeignContext( rc.expctxWay ); meta.readMetaData( new File( profileDir, "lookups.dat" ) ); - expctxGlobal.parseFile( profileFile, null ); - expctxGlobal.evaluate( new int[0] ); - rc.readGlobalConfig(expctxGlobal); - rc.expctxWay.parseFile( profileFile, "global" ); rc.expctxNode.parseFile( profileFile, "global" ); + + rc.readGlobalConfig(); if ( rc.processUnusedTags ) { diff --git a/brouter-core/src/main/java/btools/router/RoutingContext.java b/brouter-core/src/main/java/btools/router/RoutingContext.java index 815afe0..0ae2641 100644 --- a/brouter-core/src/main/java/btools/router/RoutingContext.java +++ b/brouter-core/src/main/java/btools/router/RoutingContext.java @@ -14,6 +14,7 @@ import btools.expressions.BExpressionContext; import btools.expressions.BExpressionContextNode; import btools.expressions.BExpressionContextWay; import btools.mapaccess.GeometryDecoder; +import btools.mapaccess.OsmLink; public final class RoutingContext { @@ -71,8 +72,34 @@ public final class RoutingContext public double starttimeoffset; public boolean transitonly; - public void readGlobalConfig( BExpressionContext expctxGlobal ) + + private void setModel( String className ) { + if ( className == null ) + { + pm = new StdModel(); + } + else + { + try + { + Class clazz = Class.forName( className ); + pm = (OsmPathModel) clazz.newInstance(); + } + catch( Exception e ) + { + throw new RuntimeException( "Cannot create path-model: " + e ); + } + } + pm.init( expctxWay, expctxNode ); + } + + public void readGlobalConfig() + { + BExpressionContext expctxGlobal = expctxWay; // just one of them... + + setModel( expctxGlobal._modelClass ); + downhillcostdiv = (int)expctxGlobal.getVariableValue( "downhillcost", 0.f ); downhillcutoff = (int)(expctxGlobal.getVariableValue( "downhillcutoff", 0.f )*10000); uphillcostdiv = (int)expctxGlobal.getVariableValue( "uphillcost", 0.f ); @@ -112,6 +139,9 @@ public final class RoutingContext trafficSourceExponent = expctxGlobal.getVariableValue( "trafficSourceExponent", -0.7f ); trafficSourceMinDist = expctxGlobal.getVariableValue( "trafficSourceMinDist", 3000.f ); + showspeed = 0.f != expctxGlobal.getVariableValue( "showspeed", 0.f ); + inverseRouting = 0.f != expctxGlobal.getVariableValue( "inverseRouting", 0.f ); + int tiMode = (int)expctxGlobal.getVariableValue( "turnInstructionMode", 0.f ); if ( tiMode != 1 ) // automatic selection from coordinate source { @@ -128,6 +158,7 @@ public final class RoutingContext public boolean startDirectionValid; private double coslat; + private double cosangle; public boolean nogomatch = false; public boolean isEndpoint = false; @@ -148,6 +179,11 @@ public final class RoutingContext public double trafficSourceExponent; public double trafficSourceMinDist; + public boolean showspeed; + public boolean inverseRouting; + + public OsmPrePath firstPrePath; + public int turnInstructionMode; // 0=none, 1=auto, 2=locus, 3=osmand, 4=comment-style, 5=gpsies-style public double turnInstructionCatchingRange; public boolean turnInstructionRoundabouts; @@ -165,7 +201,7 @@ public final class RoutingContext try { ir = Integer.parseInt( s.substring( 4 ) ); } catch( Exception e ) { /* ignore */ } } - nogo.radius = ir / 111894.; // 6378000. / 57.; + nogo.radius = ir / 110984.; // 6378000. / 57.3; } } @@ -308,22 +344,19 @@ public final class RoutingContext } } } - double dd = d * 111894.7368; // 6378000. / 57.; + double dd = d * 110984.; // 6378000. / 57.3; return (int)(dd + 1.0 ); } // assumes that calcDistance/calcCosAngle called in sequence, so coslat valid - public double calcCosAngle( int lon0, int lat0, int lon1, int lat1, int lon2, int lat2 ) + public double getCosAngle() { - double dlat1 = (lat1 - lat0); - double dlon1 = (lon1 - lon0) * coslat; - double dlat2 = (lat2 - lat1); - double dlon2 = (lon2 - lon1) * coslat; + return cosangle; + } - double dd = Math.sqrt( (dlat1*dlat1 + dlon1*dlon1)*(dlat2*dlat2 + dlon2*dlon2) ); - if ( dd == 0. ) return 0.; - double cosp = (dlat1*dlat2 + dlon1*dlon2)/dd; - return 1.-cosp; // don't care to really do acos.. + public double getCosLat() + { + return coslat; } public double calcAngle( int lon0, int lat0, int lon1, int lat1, int lon2, int lat2 ) @@ -334,14 +367,15 @@ public final class RoutingContext double dlon2 = (lon2 - lon1) * coslat; double dd = Math.sqrt( (dlat1*dlat1 + dlon1*dlon1)*(dlat2*dlat2 + dlon2*dlon2) ); - if ( dd == 0. ) return 0.; + if ( dd == 0. ) { cosangle = 1.; return 0.; } double sinp = (dlat1*dlon2 - dlon1*dlat2)/dd; double cosp = (dlat1*dlat2 + dlon1*dlon2)/dd; + cosangle = cosp; double p; - if ( sinp > -0.7 && sinp < 0.7 ) + if ( sinp > -0.7071 && sinp < 0.7071 ) { - p = Math.asin( sinp )*57.3; + p = asin( sinp ); if ( cosp < 0. ) { p = 180. - p; @@ -349,7 +383,7 @@ public final class RoutingContext } else { - p = Math.acos( cosp )*57.3; + p = 90. - asin( cosp ); if ( sinp < 0. ) { p = - p; @@ -362,4 +396,37 @@ public final class RoutingContext return p; } + private static double asin( double x ) + { + double x2 = x*x; + double x4 = x2*x2; + return x * ( 57.4539 + 9.57565 * x2 + 4.30904 * x4 + 2.56491 * x2*x4 ); + } + + public OsmPathModel pm; + + public OsmPrePath createPrePath( OsmPath origin, OsmLink link ) + { + OsmPrePath p = pm.createPrePath(); + if ( p != null ) + { + p.init( origin, link, this ); + } + return p; + } + + public OsmPath createPath( OsmLink link ) + { + OsmPath p = pm.createPath(); + p.init( link ); + return p; + } + + public OsmPath createPath( OsmPath origin, OsmLink link, OsmTrack refTrack, boolean detailMode ) + { + OsmPath p = pm.createPath(); + p.init( origin, link, refTrack, detailMode, this ); + return p; + } + } diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java index 8491eab..56632fe 100644 --- a/brouter-core/src/main/java/btools/router/RoutingEngine.java +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -163,6 +163,10 @@ public class RoutingEngine extends Thread track = findTrack( refTracks, lastTracks ); track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend + " plain-ascend = " + track.plainAscend + " cost=" + track.cost; + if ( track.energy != 0 ) + { + track.message += " energy=" + track.getFormattedEnergy() + " time=" + track.getFormattedTime(); + } track.name = "brouter_" + routingContext.getProfileName() + "_" + i; messageList.add( track.message ); @@ -369,15 +373,26 @@ public class RoutingEngine extends Thread matchWaypointsToNodes( matchedWaypoints ); // detect target islands: restricted search in inverse direction - routingContext.inverseDirection = true; + routingContext.inverseDirection = !routingContext.inverseRouting; airDistanceCostFactor = 0.; for( int i=0; i 0 ) + if ( routingContext.inverseRouting ) { - throw new IllegalArgumentException( "target island detected for section " + i ); + OsmTrack seg = findTrack( "start-island-check", matchedWaypoints.get(i), matchedWaypoints.get(i+1), null, null, false ); + if ( seg == null && nodeLimit > 0 ) + { + throw new IllegalArgumentException( "start island detected for section " + i ); + } + } + else + { + OsmTrack seg = findTrack( "target-island-check", matchedWaypoints.get(i+1), matchedWaypoints.get(i), null, null, false ); + if ( seg == null && nodeLimit > 0 ) + { + throw new IllegalArgumentException( "target island detected for section " + i ); + } } } routingContext.inverseDirection = false; @@ -397,7 +412,18 @@ public class RoutingEngine extends Thread refTracks[i].addNodes( lastTracks[i] ); } - OsmTrack seg = searchTrack( matchedWaypoints.get(i), matchedWaypoints.get(i+1), i == matchedWaypoints.size()-2 ? nearbyTrack : null, refTracks[i] ); + OsmTrack seg; + if ( routingContext.inverseRouting ) + { + routingContext.inverseDirection = true; + seg = searchTrack( matchedWaypoints.get(i+1), matchedWaypoints.get(i), null, refTracks[i] ); + routingContext.inverseDirection = false; + } + else + { + seg = searchTrack( matchedWaypoints.get(i), matchedWaypoints.get(i+1), i == matchedWaypoints.size()-2 ? nearbyTrack : null, refTracks[i] ); + } + if ( seg == null ) return null; totaltrack.appendTrack( seg ); lastTracks[i] = seg; @@ -651,7 +677,7 @@ public class RoutingEngine extends Thread OsmPath bestPath = null; OsmLink bestLink = null; OsmLink startLink = new OsmLink( null, n1 ); - OsmPath startPath = new OsmPath( startLink ); + OsmPath startPath = routingContext.createPath( startLink ); startLink.addLinkHolder( startPath, null ); double minradius = 1e10; for( OsmLink link = n1.firstlink; link != null; link = link.getNext( n1 ) ) @@ -663,7 +689,7 @@ public class RoutingEngine extends Thread if ( nextNode != n2 ) continue; // just that link wp.radius = 1e9; - OsmPath testPath = new OsmPath( startPath, link, null, guideTrack != null, routingContext ); + OsmPath testPath = routingContext.createPath( startPath, link, null, guideTrack != null ); testPath.airdistance = endPos == null ? 0 : nextNode.calcDistance( endPos ); if ( wp.radius < minradius ) { @@ -692,12 +718,12 @@ public class RoutingEngine extends Thread { if ( wp != null ) routingContext.setWaypoint( wp, true ); OsmLink startLink = new OsmLink( null, n1 ); - OsmPath startPath = new OsmPath( startLink ); + OsmPath startPath = routingContext.createPath( startLink ); startLink.addLinkHolder( startPath, null ); if ( wp != null ) wp.radius = 1e-5; - return new OsmPath( startPath, link, null, guideTrack != null, routingContext ); + return routingContext.createPath( startPath, link, null, guideTrack != null ); } finally { @@ -761,7 +787,7 @@ public class RoutingEngine extends Thread if ( start1 == null || start2 == null ) return null; - if ( routingContext.startDirectionValid = ( fastPartialRecalc && routingContext.startDirection != null ) ) + if ( routingContext.startDirectionValid = ( fastPartialRecalc && routingContext.startDirection != null && !routingContext.inverseDirection ) ) { logInfo( "using start direction " + routingContext.startDirection ); } @@ -865,7 +891,9 @@ public class RoutingEngine extends Thread { // track found, compile logInfo( "found track at cost " + path.cost + " nodesVisited = " + nodesVisited ); - return compileTrack( path, verbose ); + OsmTrack t = compileTrack( path, verbose ); + t.showspeed = routingContext.showspeed; + return t; } // check for a match with the cost-cutting-track @@ -916,11 +944,38 @@ public class RoutingEngine extends Thread } // recheck cutoff before doing expensive stuff - if ( path.cost + path.airdistance > maxTotalCost + 10 ) + if ( path.cost + path.airdistance > maxTotalCost + 100 ) { path.unregisterUpTree( routingContext ); continue; } + + routingContext.firstPrePath = null; + + for( OsmLink link = currentNode.firstlink; link != null; link = link.getNext( currentNode) ) + { + OsmNode nextNode = link.getTarget( currentNode ); + + if ( ! nodesCache.obtainNonHollowNode( nextNode ) ) + { + continue; // border node? + } + if ( nextNode.firstlink == null ) + { + continue; // don't care about dead ends + } + if ( nextNode == sourceNode ) + { + continue; // border node? + } + + OsmPrePath prePath = routingContext.createPrePath( path, link ); + if ( prePath != null ) + { + prePath.next = routingContext.firstPrePath; + routingContext.firstPrePath = prePath; + } + } for( OsmLink link = currentNode.firstlink; link != null; link = link.getNext( currentNode) ) { @@ -946,14 +1001,14 @@ public class RoutingEngine extends Thread { continue; } - OsmPathElement guideNode = guideTrack.nodes.get( gidx ); + OsmPathElement guideNode = guideTrack.nodes.get( routingContext.inverseRouting ? guideTrack.nodes.size() - 1 - gidx : gidx ); long nextId = nextNode.getIdFromPos(); if ( nextId != guideNode.getIdFromPos() ) { // not along the guide-track, discard, but register for voice-hint processing if ( routingContext.turnInstructionMode > 0 ) { - OsmPath detour = new OsmPath( path, link, refTrack, true, routingContext ); + OsmPath detour = routingContext.createPath( path, link, refTrack, true ); if ( detour.cost >= 0. && nextId != startNodeId1 && nextId != startNodeId2 ) { guideTrack.registerDetourForId( currentNode.getIdFromPos(), OsmPathElement.create( detour, false ) ); @@ -985,7 +1040,7 @@ public class RoutingEngine extends Thread endPos.radius = 1e-5; routingContext.setWaypoint( endPos, true ); } - OsmPath testPath = new OsmPath( otherPath, link, refTrack, guideTrack != null, routingContext ); + OsmPath testPath = routingContext.createPath( otherPath, link, refTrack, guideTrack != null ); if ( testPath.cost >= 0 && ( bestPath == null || testPath.cost < bestPath.cost ) ) { bestPath = testPath; @@ -1004,7 +1059,7 @@ public class RoutingEngine extends Thread boolean inRadius = boundary == null || boundary.isInBoundary( nextNode, bestPath.cost ); - if ( inRadius && ( isFinalLink || bestPath.cost + bestPath.airdistance <= maxTotalCost + 10 ) ) + if ( inRadius && ( isFinalLink || bestPath.cost + bestPath.airdistance <= maxTotalCost + 100 ) ) { // add only if this may beat an existing path for that link OsmLinkHolder dominator = link.getFirstLinkHolder( currentNode ); @@ -1052,12 +1107,18 @@ public class RoutingEngine extends Thread private OsmTrack compileTrack( OsmPath path, boolean verbose ) { OsmPathElement element = OsmPathElement.create( path, false ); - + // for final track, cut endnode - if ( guideTrack != null ) element = element.origin; + if ( guideTrack != null ) + { + element = element.origin; + } + + float totalTime = element.getTime(); OsmTrack track = new OsmTrack(); track.cost = path.cost; + track.energy = (int)path.getTotalEnergy(); int distance = 0; double ascend = 0; @@ -1066,9 +1127,24 @@ public class RoutingEngine extends Thread short ele_start = Short.MIN_VALUE; short ele_end = Short.MIN_VALUE; + double eleFactor = routingContext.inverseRouting ? -0.25 : 0.25; while ( element != null ) { - track.addNode( element ); + if ( guideTrack != null && element.message == null ) + { + element.message = new MessageData(); + } + + if ( routingContext.inverseRouting ) + { + element.setTime( totalTime - element.getTime() ); + track.nodes.add( element ); + } + else + { + track.nodes.add( 0, element ); + } + OsmPathElement nextElement = element.origin; short ele = element.getSElev(); @@ -1081,7 +1157,7 @@ public class RoutingEngine extends Thread short ele_next = nextElement.getSElev(); if ( ele_next != Short.MIN_VALUE ) { - ehb = ehb + (ele - ele_next)/4.; + ehb = ehb + (ele - ele_next)*eleFactor; } if ( ehb > 10. ) { @@ -1098,7 +1174,7 @@ public class RoutingEngine extends Thread ascend += ehb; track.distance = distance; track.ascend = (int)ascend; - track.plainAscend = ( ele_end - ele_start ) / 4; + track.plainAscend = (int)(( ele_end - ele_start )*eleFactor+0.5); logInfo( "track-length = " + track.distance ); logInfo( "filtered ascend = " + track.ascend ); track.buildMap(); diff --git a/brouter-core/src/main/java/btools/router/StdModel.java b/brouter-core/src/main/java/btools/router/StdModel.java new file mode 100644 index 0000000..11f4701 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/StdModel.java @@ -0,0 +1,38 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import btools.expressions.BExpressionContext; +import btools.expressions.BExpressionContextNode; +import btools.expressions.BExpressionContextWay; + + +final class StdModel extends OsmPathModel +{ + public OsmPrePath createPrePath() + { + return null; + } + + public OsmPath createPath() + { + return new StdPath(); + } + + protected BExpressionContextWay ctxWay; + protected BExpressionContextNode ctxNode; + + + @Override + public void init( BExpressionContextWay expctxWay, BExpressionContextNode expctxNode ) + { + ctxWay = expctxWay; + ctxNode = expctxNode; + + BExpressionContext expctxGlobal = expctxWay; // just one of them... + + } +} diff --git a/brouter-core/src/main/java/btools/router/StdPath.java b/brouter-core/src/main/java/btools/router/StdPath.java new file mode 100644 index 0000000..6f73f92 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/StdPath.java @@ -0,0 +1,596 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import btools.mapaccess.OsmLink; +import btools.mapaccess.OsmNode; +import btools.mapaccess.OsmTransferNode; +import btools.mapaccess.TurnRestriction; + +final class StdPath extends OsmPath +{ + /** + * The elevation-hysteresis-buffer (0-10 m) + */ + private int ehbd; // in micrometer + private int ehbu; // in micrometer + + @Override + public void init( OsmPath orig ) + { + StdPath origin = (StdPath)orig; + this.ehbd = origin.ehbd; + this.ehbu = origin.ehbu; + } + + @Override + protected void resetState() + { + ehbd = 0; + ehbu = 0; + } + + @Override + protected double processWaySection( RoutingContext rc, double distance, double delta_h, double angle, double cosangle, boolean isStartpoint, int nsection, int lastpriorityclassifier ) + { + // calculate the costfactor inputs + float turncostbase = rc.expctxWay.getTurncost(); + float cfup = rc.expctxWay.getUphillCostfactor(); + float cfdown = rc.expctxWay.getDownhillCostfactor(); + float cf = rc.expctxWay.getCostfactor(); + cfup = cfup == 0.f ? cf : cfup; + cfdown = cfdown == 0.f ? cf : cfdown; + + int dist = (int)distance; // legacy arithmetics needs int + + // penalty for turning angle + int turncost = (int)((1.-cosangle) * turncostbase + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty + if ( message != null ) + { + message.linkturncost += turncost; + message.turnangle = (float)angle; + } + + double sectionCost = turncost; + + // *** penalty for elevation + // only the part of the descend that does not fit into the elevation-hysteresis-buffers + // leads to an immediate penalty + + int delta_h_micros = (int)(1000000. * delta_h); + ehbd += -delta_h_micros - dist * rc.downhillcutoff; + ehbu += delta_h_micros - dist * rc.uphillcutoff; + + float downweight = 0.f; + if ( ehbd > rc.elevationpenaltybuffer ) + { + downweight = 1.f; + + int excess = ehbd - rc.elevationpenaltybuffer; + int reduce = dist * rc.elevationbufferreduce; + if ( reduce > excess ) + { + downweight = ((float)excess)/reduce; + reduce = excess; + } + excess = ehbd - rc.elevationmaxbuffer; + if ( reduce < excess ) + { + reduce = excess; + } + ehbd -= reduce; + if ( rc.downhillcostdiv > 0 ) + { + int elevationCost = reduce/rc.downhillcostdiv; + sectionCost += elevationCost; + if ( message != null ) + { + message.linkelevationcost += elevationCost; + } + } + } + else if ( ehbd < 0 ) + { + ehbd = 0; + } + + float upweight = 0.f; + if ( ehbu > rc.elevationpenaltybuffer ) + { + upweight = 1.f; + + int excess = ehbu - rc.elevationpenaltybuffer; + int reduce = dist * rc.elevationbufferreduce; + if ( reduce > excess ) + { + upweight = ((float)excess)/reduce; + reduce = excess; + } + excess = ehbu - rc.elevationmaxbuffer; + if ( reduce < excess ) + { + reduce = excess; + } + ehbu -= reduce; + if ( rc.uphillcostdiv > 0 ) + { + int elevationCost = reduce/rc.uphillcostdiv; + sectionCost += elevationCost; + if ( message != null ) + { + message.linkelevationcost += elevationCost; + } + } + } + else if ( ehbu < 0 ) + { + ehbu = 0; + } + + // get the effective costfactor (slope dependent) + float costfactor = cfup*upweight + cf*(1.f - upweight - downweight) + cfdown*downweight; + + if ( message != null ) + { + message.costfactor = costfactor; + } + + sectionCost += dist * costfactor + 0.5f; + + return sectionCost; + } + + @Override + protected double processTargetNode( RoutingContext rc ) + { + // finally add node-costs for target node + if ( targetNode.nodeDescription != null ) + { + boolean nodeAccessGranted = rc.expctxWay.getNodeAccessGranted() != 0.; + rc.expctxNode.evaluate( nodeAccessGranted , targetNode.nodeDescription ); + float initialcost = rc.expctxNode.getInitialcost(); + if ( initialcost >= 1000000. ) + { + return -1.; + } + if ( message != null ) + { + message.linknodecost += (int)initialcost; + message.nodeKeyValues = rc.expctxNode.getKeyValueDescription( nodeAccessGranted, targetNode.nodeDescription ); + } + return initialcost; + } + return 0.; + } + + +// @Override + protected void xxxaddAddionalPenalty(OsmTrack refTrack, boolean detailMode, OsmPath origin, OsmLink link, RoutingContext rc ) + { + byte[] description = link.descriptionBitmap; + if ( description == null ) throw new IllegalArgumentException( "null description for: " + link ); + + boolean recordTransferNodes = detailMode || rc.countTraffic; + boolean recordMessageData = detailMode; + + rc.nogomatch = false; + + // extract the 3 positions of the first section + int lon0 = origin.originLon; + int lat0 = origin.originLat; + + OsmNode p1 = sourceNode; + int lon1 = p1.getILon(); + int lat1 = p1.getILat(); + short ele1 = origin.selev; + + int linkdisttotal = 0; + + MessageData msgData = recordMessageData ? new MessageData() : null; + + boolean isReverse = link.isReverse( sourceNode ); + + // evaluate the way tags + rc.expctxWay.evaluate( rc.inverseDirection ^ isReverse, description ); + + // calculate the costfactor inputs + boolean isTrafficBackbone = cost == 0 && rc.expctxWay.getIsTrafficBackbone() > 0.f; + float turncostbase = rc.expctxWay.getTurncost(); + float cfup = rc.expctxWay.getUphillCostfactor(); + float cfdown = rc.expctxWay.getDownhillCostfactor(); + float cf = rc.expctxWay.getCostfactor(); + cfup = cfup == 0.f ? cf : cfup; + cfdown = cfdown == 0.f ? cf : cfdown; + + // *** add initial cost if the classifier changed + float newClassifier = rc.expctxWay.getInitialClassifier(); + if ( newClassifier == 0. ) + { + newClassifier = (cfup + cfdown + cf)/3; + } + float classifierDiff = newClassifier - lastClassifier; + if ( classifierDiff > 0.0005 || classifierDiff < -0.0005 ) + { + lastClassifier = newClassifier; + float initialcost = rc.expctxWay.getInitialcost(); + int iicost = (int)initialcost; + if ( recordMessageData ) + { + msgData.linkinitcost += iicost; + } + cost += iicost; + } + + OsmTransferNode transferNode = link.geometry == null ? null + : rc.geometryDecoder.decodeGeometry( link.geometry, p1, targetNode, isReverse ); + + boolean isFirstSection = true; + + for(;;) + { + originLon = lon1; + originLat = lat1; + + int lon2; + int lat2; + short ele2; + + if ( transferNode == null ) + { + lon2 = targetNode.ilon; + lat2 = targetNode.ilat; + ele2 = targetNode.selev; + } + else + { + lon2 = transferNode.ilon; + lat2 = transferNode.ilat; + ele2 = transferNode.selev; + } + + // check turn restrictions: do we have one with that origin? + boolean checkTRs = false; + if ( isFirstSection ) + { + isFirstSection = false; + + // TODO: TRs for inverse routing would need inverse TR logic, + // inverse routing for now just for target island check, so don't care (?) + // in detail mode (=final pass) no TR to not mess up voice hints + checkTRs = rc.considerTurnRestrictions && !rc.inverseDirection && !detailMode; + } + + if ( checkTRs ) + { + boolean hasAnyPositive = false; + boolean hasPositive = false; + boolean hasNegative = false; + TurnRestriction tr = sourceNode.firstRestriction; + while( tr != null ) + { + boolean trValid = ! (tr.exceptBikes() && rc.bikeMode); + if ( trValid && tr.fromLon == lon0 && tr.fromLat == lat0 ) + { + if ( tr.isPositive ) + { + hasAnyPositive = true; + } + if ( tr.toLon == lon2 && tr.toLat == lat2 ) + { + if ( tr.isPositive ) + { + hasPositive = true; + } + else + { + hasNegative = true; + } + } + } + tr = tr.next; + } + if ( !hasPositive && ( hasAnyPositive || hasNegative ) ) + { + cost = -1; + return; + } + } + + // if recording, new MessageData for each section (needed for turn-instructions) + if ( recordMessageData && msgData.wayKeyValues != null ) + { + originElement.message = msgData; + msgData = new MessageData(); + } + + int dist = rc.calcDistance( lon1, lat1, lon2, lat2 ); + boolean stopAtEndpoint = false; + if ( rc.shortestmatch ) + { + if ( rc.isEndpoint ) + { + stopAtEndpoint = true; + ele2 = interpolateEle( ele1, ele2, rc.wayfraction ); + } + else + { + // we just start here, reset cost + cost = 0; + ehbd = 0; + ehbu = 0; + lon0 = -1; // reset turncost-pipe + lat0 = -1; + + if ( recordTransferNodes ) + { + if ( rc.wayfraction > 0. ) + { + ele1 = interpolateEle( ele1, ele2, 1. - rc.wayfraction ); + originElement = OsmPathElement.create( rc.ilonshortest, rc.ilatshortest, ele1, null, rc.countTraffic ); + } + else + { + originElement = null; // prevent duplicate point + } + } + } + } + + if ( recordMessageData ) + { + msgData.linkdist += dist; + } + linkdisttotal += dist; + + // apply a start-direction if appropriate (by faking the origin position) + if ( lon0 == -1 && lat0 == -1 ) + { + double coslat = Math.cos( ( lat1 - 90000000 ) * 0.00000001234134 ); + if ( rc.startDirectionValid && coslat > 0. ) + { + double dir = rc.startDirection.intValue() / 57.29578; + lon0 = lon1 - (int) ( 1000. * Math.sin( dir ) / coslat ); + lat0 = lat1 - (int) ( 1000. * Math.cos( dir ) ); + } + } + + // *** penalty for turning angles + if ( !isTrafficBackbone && lon0 != -1 && lat0 != -1 ) + { + // penalty proportional to direction change + double angle = rc.calcAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); + double cos = 1. - rc.getCosAngle(); + int actualturncost = (int)(cos * turncostbase + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty + cost += actualturncost; + if ( recordMessageData ) + { + msgData.linkturncost += actualturncost; + msgData.turnangle = (float)rc.calcAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); + } + } + + // *** penalty for elevation (penalty is for descend! in a way that slow descends give no penalty) + // only the part of the descend that does not fit into the elevation-hysteresis-buffer + // leads to an immediate penalty + + int elefactor = 250000; + if ( ele2 == Short.MIN_VALUE ) ele2 = ele1; + if ( ele1 != Short.MIN_VALUE ) + { + ehbd += (ele1 - ele2)*elefactor - dist * rc.downhillcutoff; + ehbu += (ele2 - ele1)*elefactor - dist * rc.uphillcutoff; + } + + float downweight = 0.f; + if ( ehbd > rc.elevationpenaltybuffer ) + { + downweight = 1.f; + + int excess = ehbd - rc.elevationpenaltybuffer; + int reduce = dist * rc.elevationbufferreduce; + if ( reduce > excess ) + { + downweight = ((float)excess)/reduce; + reduce = excess; + } + excess = ehbd - rc.elevationmaxbuffer; + if ( reduce < excess ) + { + reduce = excess; + } + ehbd -= reduce; + if ( rc.downhillcostdiv > 0 ) + { + int elevationCost = reduce/rc.downhillcostdiv; + cost += elevationCost; + if ( recordMessageData ) + { + msgData.linkelevationcost += elevationCost; + } + } + } + else if ( ehbd < 0 ) + { + ehbd = 0; + } + + float upweight = 0.f; + if ( ehbu > rc.elevationpenaltybuffer ) + { + upweight = 1.f; + + int excess = ehbu - rc.elevationpenaltybuffer; + int reduce = dist * rc.elevationbufferreduce; + if ( reduce > excess ) + { + upweight = ((float)excess)/reduce; + reduce = excess; + } + excess = ehbu - rc.elevationmaxbuffer; + if ( reduce < excess ) + { + reduce = excess; + } + ehbu -= reduce; + if ( rc.uphillcostdiv > 0 ) + { + int elevationCost = reduce/rc.uphillcostdiv; + cost += elevationCost; + if ( recordMessageData ) + { + msgData.linkelevationcost += elevationCost; + } + } + } + else if ( ehbu < 0 ) + { + ehbu = 0; + } + + // get the effective costfactor (slope dependent) + float costfactor = cfup*upweight + cf*(1.f - upweight - downweight) + cfdown*downweight; + if ( isTrafficBackbone ) + { + costfactor = 0.f; + } + + float fcost = dist * costfactor + 0.5f; + if ( ( costfactor > 9998. && !detailMode ) || fcost + cost >= 2000000000. ) + { + cost = -1; + return; + } + int waycost = (int)(fcost); + cost += waycost; + + // calculate traffic + if ( rc.countTraffic ) + { + int minDist = (int)rc.trafficSourceMinDist; + int cost2 = cost < minDist ? minDist : cost; + traffic += dist*rc.expctxWay.getTrafficSourceDensity()*Math.pow(cost2/10000.f,rc.trafficSourceExponent); + } + + if ( recordMessageData ) + { + msgData.costfactor = costfactor; + msgData.priorityclassifier = (int)rc.expctxWay.getPriorityClassifier(); + msgData.classifiermask = (int)rc.expctxWay.getClassifierMask(); + msgData.lon = lon2; + msgData.lat = lat2; + msgData.ele = ele2; + msgData.wayKeyValues = rc.expctxWay.getKeyValueDescription( isReverse, description ); + } + + if ( stopAtEndpoint ) + { + if ( recordTransferNodes ) + { + originElement = OsmPathElement.create( rc.ilonshortest, rc.ilatshortest, ele2, originElement, rc.countTraffic ); + originElement.cost = cost; + if ( recordMessageData ) + { + originElement.message = msgData; + } + } + if ( rc.nogomatch ) + { + cost = -1; + } + return; + } + + if ( transferNode == null ) + { + // *** penalty for being part of the reference track + if ( refTrack != null && refTrack.containsNode( targetNode ) && refTrack.containsNode( sourceNode ) ) + { + int reftrackcost = linkdisttotal; + cost += reftrackcost; + } + selev = ele2; + break; + } + transferNode = transferNode.next; + + if ( recordTransferNodes ) + { + originElement = OsmPathElement.create( lon2, lat2, ele2, originElement, rc.countTraffic ); + originElement.cost = cost; + originElement.addTraffic( traffic ); + traffic = 0; + } + lon0 = lon1; + lat0 = lat1; + lon1 = lon2; + lat1 = lat2; + ele1 = ele2; + + } + + // check for nogo-matches (after the *actual* start of segment) + if ( rc.nogomatch ) + { + cost = -1; + return; + } + + // finally add node-costs for target node + if ( targetNode.nodeDescription != null ) + { + boolean nodeAccessGranted = rc.expctxWay.getNodeAccessGranted() != 0.; + rc.expctxNode.evaluate( nodeAccessGranted , targetNode.nodeDescription ); + float initialcost = rc.expctxNode.getInitialcost(); + if ( initialcost >= 1000000. ) + { + cost = -1; + return; + } + int iicost = (int)initialcost; + + cost += iicost; + + if ( recordMessageData ) + { + msgData.linknodecost += iicost; + msgData.nodeKeyValues = rc.expctxNode.getKeyValueDescription( nodeAccessGranted, targetNode.nodeDescription ); + } + } + if ( recordMessageData ) + { + message = msgData; + } + + } + + @Override + public int elevationCorrection( RoutingContext rc ) + { + return ( rc.downhillcostdiv > 0 ? ehbd/rc.downhillcostdiv : 0 ) + + ( rc.uphillcostdiv > 0 ? ehbu/rc.uphillcostdiv : 0 ); + } + + @Override + public boolean definitlyWorseThan( OsmPath path, RoutingContext rc ) + { + StdPath p = (StdPath)path; + + int c = p.cost; + if ( rc.downhillcostdiv > 0 ) + { + int delta = p.ehbd - ehbd; + if ( delta > 0 ) c += delta/rc.downhillcostdiv; + } + if ( rc.uphillcostdiv > 0 ) + { + int delta = p.ehbu - ehbu; + if ( delta > 0 ) c += delta/rc.uphillcostdiv; + } + + return cost > c; + } + +} diff --git a/brouter-core/src/main/java/btools/router/WaypointMatcherImpl.java b/brouter-core/src/main/java/btools/router/WaypointMatcherImpl.java index 83456d0..4266dd3 100644 --- a/brouter-core/src/main/java/btools/router/WaypointMatcherImpl.java +++ b/brouter-core/src/main/java/btools/router/WaypointMatcherImpl.java @@ -27,7 +27,7 @@ public final class WaypointMatcherImpl implements WaypointMatcher this.waypoints = waypoints; for ( MatchedWaypoint mwp : waypoints ) { - mwp.radius = maxDistance / 111894.; // 6378000. / 57.; + mwp.radius = maxDistance * 110984.; // 6378000. / 57.3; } } diff --git a/brouter-expressions/pom.xml b/brouter-expressions/pom.xml index c4bbf38..daa4fd3 100644 --- a/brouter-expressions/pom.xml +++ b/brouter-expressions/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-expressions diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java index 3d265d5..74d874d 100644 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -27,11 +27,14 @@ import btools.util.LruMap; public abstract class BExpressionContext implements IByteArrayUnifier { private static final String CONTEXT_TAG = "---context:"; + private static final String MODEL_TAG = "---model:"; private String context; private boolean _inOurContext = false; private BufferedReader _br = null; private boolean _readerDone = false; + + public String _modelClass; private Map lookupNumbers = new HashMap(); private ArrayList lookupValues = new ArrayList(); @@ -79,7 +82,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier abstract String[] getBuildInVariableNames(); - protected final float getBuildInVariable( int idx ) + public final float getBuildInVariable( int idx ) { return currentVars[idx+currentVarOffset]; } @@ -678,14 +681,17 @@ public abstract class BExpressionContext implements IByteArrayUnifier return num != null && lookupData[num.intValue()] == 2; } - public int getOutputVariableIndex( String name ) + public int getOutputVariableIndex( String name, boolean mustExist ) { int idx = getVariableIdx( name, false ); if ( idx < 0 ) { - throw new IllegalArgumentException( "unknown variable: " + name ); + if ( mustExist ) + { + throw new IllegalArgumentException( "unknown variable: " + name ); + } } - if ( idx < minWriteIdx ) + else if ( idx < minWriteIdx ) { throw new IllegalArgumentException( "bad access to global variable: " + name ); } @@ -719,7 +725,7 @@ public abstract class BExpressionContext implements IByteArrayUnifier { throw new IllegalArgumentException( "unknown foreign context: " + context ); } - return foreignContext.getOutputVariableIndex( name ); + return foreignContext.getOutputVariableIndex( name, true ); } public void parseFile( File file, String readOnlyContext ) @@ -884,6 +890,10 @@ public abstract class BExpressionContext implements IByteArrayUnifier { _inOurContext = token.substring( CONTEXT_TAG.length() ).equals( context ); } + else if ( token.startsWith( MODEL_TAG ) ) + { + _modelClass = token.substring( MODEL_TAG.length() ).trim(); + } else if ( _inOurContext ) { return token; diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextGlobal.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContextGlobal.java deleted file mode 100644 index b0764f6..0000000 --- a/brouter-expressions/src/main/java/btools/expressions/BExpressionContextGlobal.java +++ /dev/null @@ -1,35 +0,0 @@ -// context for simple expression -// context means: -// - the local variables -// - the local variable names -// - the lookup-input variables - -package btools.expressions; - - - -public final class BExpressionContextGlobal extends BExpressionContext -{ - private static String[] buildInVariables = - {}; - - protected String[] getBuildInVariableNames() - { - return buildInVariables; - } - - public BExpressionContextGlobal( BExpressionMetaData meta ) - { - super( "global", meta ); - } - - /** - * Create an Expression-Context for way context - * - * @param hashSize size of hashmap for result caching - */ - public BExpressionContextGlobal( int hashSize, BExpressionMetaData meta ) - { - super( "global", hashSize, meta ); - } -} diff --git a/brouter-map-creator/pom.xml b/brouter-map-creator/pom.xml index cb1e6c3..0ebf90a 100644 --- a/brouter-map-creator/pom.xml +++ b/brouter-map-creator/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-map-creator diff --git a/brouter-mapaccess/pom.xml b/brouter-mapaccess/pom.xml index db24139..268b6a7 100644 --- a/brouter-mapaccess/pom.xml +++ b/brouter-mapaccess/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-mapaccess diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/GeometryDecoder.java b/brouter-mapaccess/src/main/java/btools/mapaccess/GeometryDecoder.java index 6533d5a..f297547 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/GeometryDecoder.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/GeometryDecoder.java @@ -14,6 +14,11 @@ public final class GeometryDecoder private OsmTransferNode[] cachedNodes; private int nCachedNodes = 128; + // result-cache + private OsmTransferNode firstTransferNode; + private boolean lastReverse; + private byte[] lastGeometry; + public GeometryDecoder() { // create some caches @@ -24,11 +29,14 @@ public final class GeometryDecoder } } - - public OsmTransferNode decodeGeometry( byte[] geometry, OsmNode sourceNode, OsmNode targetNode, boolean reverseLink ) { - OsmTransferNode firstTransferNode = null; + if ( ( lastGeometry == geometry ) && ( lastReverse == reverseLink ) ) + { + return firstTransferNode; + } + + firstTransferNode = null; OsmTransferNode lastTransferNode = null; OsmNode startnode = reverseLink ? targetNode : sourceNode; r.reset( geometry ); @@ -64,6 +72,10 @@ public final class GeometryDecoder lastTransferNode = trans; } } + + lastReverse = reverseLink; + lastGeometry = geometry; + return firstTransferNode; } } diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java index 3dc6e57..2e2ccd9 100644 --- a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java @@ -109,7 +109,7 @@ public class OsmNode extends OsmLink implements OsmPos double dlat = ( ilat - p.getILat() ) / 1000000.; double dlon = ( ilon - p.getILon() ) / 1000000. * coslat; - double d = Math.sqrt( dlat * dlat + dlon * dlon ) * ( 6378000. / 57.3 ); + double d = Math.sqrt( dlat * dlat + dlon * dlon ) * 110984.; // 6378000. / 57.3; return (int) ( d + 1.0 ); } diff --git a/brouter-mem-router/pom.xml b/brouter-mem-router/pom.xml index 3e07766..34840f0 100644 --- a/brouter-mem-router/pom.xml +++ b/brouter-mem-router/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-mem-router diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java index 18ed30e..5c7c2f8 100644 --- a/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java +++ b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java @@ -109,7 +109,7 @@ public class OsmNodeP extends OsmLinkP implements Comparable, OsmPos double dlat = (ilat - p.getILat() )/1000000.; double dlon = (ilon - p.getILon() )/1000000. * coslat; - double d = Math.sqrt( dlat*dlat + dlon*dlon ) * (6378000. / 57.3); + double d = Math.sqrt( dlat*dlat + dlon*dlon ) * 110984.; // 6378000. / 57.3; return (int)(d + 1.0 ); } diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java index 3059f60..b7e0cb3 100644 --- a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java +++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java @@ -379,7 +379,9 @@ System.out.println( "*** finishedOffsets = " + finishedOffsets ); if ( trip.originNode != null ) { // penalty proportional to direction change - double cos = rc.calcCosAngle( trip.originNode.ilon, trip.originNode.ilat, currentNode.ilon, currentNode.ilat, node.ilon, node.ilat ); + double angle = rc.calcAngle( trip.originNode.ilon, trip.originNode.ilat, currentNode.ilon, currentNode.ilat, node.ilon, node.ilat ); + double cos = 1. - rc.getCosAngle(); + int turncost = (int) ( cos * rc.expctxWay.getTurncost() + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty waycost += turncost; } diff --git a/brouter-routing-app/AndroidManifest.xml b/brouter-routing-app/AndroidManifest.xml index 242705c..0de810b 100644 --- a/brouter-routing-app/AndroidManifest.xml +++ b/brouter-routing-app/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="21" + android:versionName="1.4.9" package="btools.routingapp"> zs14G+H@mQlpoz5mto{2A6_MwQ8A;6oF WNrVB5N}y^%AO?ep%eB}V!7KnTMHC&Q7VZ+4+AzU&z;>0ivFaUrMJ^+9S002yk zoEglGTwEEAoz2%ZW#UdaFgFa2-#*ARX0+2%Lmar3&{wLbR95P_&QY*N!d-B2G!q_> zUGMt5MmR-4A?oQ?XibDywf$4J7RFyJiPuY*W4qJ=&&W^%Wpc5oF$h~XQGq0$pGQ6v)&R~YF_ z%Ipzn)wiN^VI;?QHi42fly7BoPfv5EtY0~N^{hzKGx1py<~Df{z#cH#hBR%Io3~4u zkytInmt-121!Ic!%1sC0MQBVx=&?N3Uy#*5D7Qc~tLKT;OHIWgX+C{sa~9#9X(R@G zz-ny#Mh}tG7=bZeQ+abj!_yMX=sVS^12(D~l$%=Uxcv07o|F~W?fjM7Yni9F{b43& za^sbut#qRVGpUu;lG${PDnHYqqVTuSW_P3c;x$KYV-9rj3Clb1m-u*v6KaX^RD!#{ z_byC5*k+UoSN~`ox~Do_2o1RFJ##ap0Uj;K{!tWaCdaDp#cQwyTZh$#g|-W#$Na&0 zzeLJme4Tm~5_bqMYZPo?J3KkzH1MGltNXE%d;#f}y2JKL@}Is}V@i|X$-A|GU6F?0 zO7LMH-xAJHdz>NF^&I`<1wLA=W1EP~yfu|WTjFahx!y+=|71C;b9Q;LZT*h44R;~b?@Q{lV`9+2A_nK)AC`w)N zQBm=OxQAlkv=@lsE_Nss6maltGPd33qn+UO#U~ zgzUW?mPw~8SZ%<%vtO22NP@-)mbVki1n9mGol6S_)Tu`=6IMk_zO+{XhTgZAn`HMmlYn&^T8n2OHbL!JKk z4!^)xB+#|UI@O#-qr6kPVdjyDZjmd~MheG0c`c(O@$!-oJ}3GiP!Ot9Ng;fv!}O@; zc((j1KrU1IL?G8J9|hbLp0_-@euF3=vo!dF3p@bx7Kc-MM3CVKPAak8D{+;r-;f}v zgF0=9bEb!wU+~!^lskpQl1OAg3J7CvGkIjgY(yoC*B7W=l<7#OK-GG<67HNP-mHXN zx_bB)MftLoeFJCR4S7#`SWoVr-MGYl=(&rO&1Auq#K7e!P{!?tAyD?~nBGFfW(({q z>k`HxQ0w9p=#u}*aZ-Y8&!Qhs(BhDLs@fv=fPk1j&;=AeBq21+-9WwDxCL2Ma{<&b z1l}5oXbucWeT>w{^>^O_nV{nOdC|=Xt)eB85chOt@F zk#M>1rt-1U4q(&3QQg;_lZtVFum(%jx2wsrH&9t5cIxA|z8eP-b|7|0ukjCb@r)J- zr}F8N+Qe+v>G*4kiMF*zRH9bb2`yLq1*`e0b9vjt!fGg4iydZs-DiI+HOsVL>B*xO zAg(iv!bUIno+7)AXqKCXlD@Pr{N{QgNtn*anb^*Mbz2M7-u}w#L1+oF?g!rbe%c+o z3wE6v4i%@Pr`Xd|3SJ7w3V|PoH||`Nc_|601j(R0$3D=sMm^_h&|R-LY-W6?`!qrL zj(AaX7R&aLw++~2b%dUx3?0ah6F#XG7>i$?vzY=F7 z<3BA10RaBnW6Vq({?9Dt|LZW||9gx1hr@XPI76uC$O=d*wr!PZ(4R|0*S~;Blgk4W zUg!t;UZd7)TTX1i)o^wsWe75`QZ*vi-zmcnc~@He%D{N~;I$Map(ty%IcD48IBom< zSmOJBvy0P?_SM$JaiV^tq4?}v*=nL{L4b94=JWf2b|u(g zvW~T7TMFo_5&lfGtNaP^5g^=vN(-V*PWTz5`79CK$M=MhtU;P1#V%IJZ&^5AS&gD1{((&w`@P?5_bJ$DT1ux!e5S$&ztiOU z+c(Igi_ua0QkO#&@s^4Lb&Qj2;>AY-w(A%^N_ zXy9s?1Fn8Sc6zfH3Jh*CJ!jFw`Rz+zOjl&TxaYI|R7ConUK{s&{0x%I+s{udgl31w zXo+V5USqmZZnar9@hpEPF>1h+RXds?_I)Fsu*uQjEljorS5l0+0SimyB3^{NaLPdCS75N50JH3_IhR_Q zVuNU9>r|0K^#z8!rW9k5k3!4r z42MHX*DiT3DeT%25f%3>7=5`jXZJeH7@ww3C}3py$a6-s{xHV@#>1P1qcK!i|Kdmz zaN?C1Z}B@FdVoH-#v%$39xax5m0VB;+UEW|B{d+ksE{udusd@a(Y_+3Oe{5&(!|8g zl~mRp7{y=lQ^#z+LY z`~tOF1od+P-IN%?F%E@(g5LhO!|*P&!qa>qAxW@^6N}k1ONImT>8t@z&ElW7MRVot zry5cA8Dp%A2xMEQeq+hq_jB)K%zHzheV#OuIODRYvzojf`p9K4=}4*LxaBMF@=WO~ ze>kT#8M4^-lzxC{=c{|T19>Q9vy~kuIx-c+=07>>i5wFb5bI*9 zG-S~aEv~Z;*U|@j)E`nG5}L$;-p%}a)+aSY!u))rUKrRy4iXmd_3^=yxGPc4lP~K= zvSAoZSv|L`%8nLxJR4&0GF1&>P)3(OGg$o`<%~R^Xy{UvZfUA83G(P>w}1Z*zTV&`sN^f4-;c=v68S=xF@QJ5$3)CrHy-?Yyq3VjtY;5$@!2}o`{3;I z4cYP1_hre4aQOWwHx402T~m6bvk;~Zg$@=g6>HEPT$6S$1E(gHN_0zjsbY_H$6u$u z(`nYr_C>=wo%aoIO=pGBg2UYr&T?*=g0=*JSSR<+%6TngL!shiaO&rg1`-~-ZrC&U zm&a88@fgvIa27H=0AN8A06_Tj8Ds0J|<_N(4Zl1G%_f;;BBjEVQ_rj>hj_W7)1X<6FnVL( zYSVGCx_hsF_t0Tu0DOP7YF*VkZ8<$`IQvDV;d%D6;Mv5}i>X&-v_!z*>Jj0vVQUOi zy`iULsjQOlq(N&ph)&5=y5462+`BMr9dkW#;5CWBal3!56M#!sY#w*Noc!SPTU&!F zP6*+(?vu}XN57kOD2*Pq3Gibj(fYBU;(@p}<8loFqX0>OWo)c*eOW!eeyUlaVQWuM z7;dYWQA0FgFL4&_8DcBnQtHs7beA`W&qKHo)1eMZK!BB!2E}l}6zlCscqb zzG}jyM;zD%SoY8aopS?8o3jatJYY3Ez6fPl-ShhswrT~2dVp{1a4qN6m`N?dFX3QN ze=S$kZ7Q!77%?BEdT)B5O#CkJL4?Df43pTL^$u|ENT_uhGkWv~R}m=Xe&$2ueb}!^ zM6-0U3`J8VwS-SPbOwq6f9jfHlz&!KSvLkExmg*<%;9&8*qUp3_3Dk8Uoi)&7vSbw zZ$!ihkd-KHIY(b3{vu=ZUe{T8jPGw?0FoFNpwK{F9GKy!vP(T2$U^Fnz)GKc(m^wO2A`T$ z>%2@vdLXrT) zm9ajRSG!OApre+P9WRVTTadmjd;R;)z5m18v#2H;-*{xuXo4hVL>v1S8Adigq#6-B&e zKJ=2zP`!|4u;H4y?P#%f5Lw7~CL#6~Q)MWnpOQO>5>$jKZYHfJSXt#F6Cn(!Tjm_< zqssIeUglWA{iZ9+>Z!Kl*wf@ei`vt5Yxs`}{Pf5#r{I5KM zudBTZD80XV1d4Soi2YU+$LGE|%t2`r5l`PdUY))MfK!J*G@TJ;GZMyZ9GTFl#mA}L zZ%W1`-*s2`!b4|-xoJmFzypU(QxB_cGiinLYa47EromNZJk!Q;rW^(2x``i_rd4Tv|#ufsZ;-m)_@_h?5{Q`rElta z91cLIV>Dq@$Hm+CMm{9U<>C%@V5YK(Fb;yGoJ4Y0la+T@s&7G%HmZiA35l6;G4r@anN$+%G-*-)FBZAZts8CW?8l zK#BF{^LT&g@)8-9J?nB?{1s!XFx*#DjU} z!Hn}xyNN&h=(OU-zZorq)~J?t-M$9xvdio6{rp6@$3vdF_(12IOA6%XZaYozVFq!v zjz9X!;!?}Uu?S2`J^9e*Gs#5bC|0f2pE^d2ZRgcrgc%l=G76CPL+;CL07mE=0fW9kSaM zZbyvoZ#5nBY-L+tvhQ6yr0^fXxfsH$ik8tHX{k+8_|B}O~M2CWxz+x;tg-NM)?JXawdgbqnT4S2;+ zT^8}&MrBtk>4@*808}HXR$xn=K&L5E%-`bswl~FgzSDO7e5U$1by*yTkjOubnD;Fs zK>Q6HA*Z=HV4xMYYa(JyS~rX?&sfPQ>(Xhj2hLOfGC@t0AIKQmL|j^GL|;P9vFOcE zBcuu7B0SZv7S72~dsu>Ha&FF)024QGL#{g5J#4^45QH~P3>+$4rd3+UrZwa?g*K$p zan_?Ia4$Y_b)W{Zf1~8QYj)rLVil(i?= z=!L>N&;&!=A>mS(69b#_Rte`c^27Qwhsd^2dqYjy7g_nv-)KQx?&nML;c!W^)*;Ar zo=8E0q52M;-Xx~z)x=MS5CqRgx%R}&&`lfcS^|Z7T}NCOP**%>Y+zK)DLodrY6E0K z;g^dD>G2sfy#p{SO-F#NQZU3o3<;H6l&nSAD=R{q3-j7WMmTI6+S4EqC6MTIF%QaO zc=6&BpEfaR0%7=RnhCT_Bfdt!tiarwi{~Me2lc2Ir?Lzz49a-Hppg}I3-VZjUuZrT zA9eUpD)jetVeSl88SqyRO-4(Fbg&!%pODpw8-&*HUkHiF9-s#)boIj%K3|161Bfaz zHqSdkrp5Vd->G*eZyRsvOj#j|fJhP*v>s|}wtm4`PErM((PXodYx(VP>S$PRwxN9{ z^$a_vKC$lSD~2`w=x9X*>ay)%&K^Qvz?&5>I_lq3dq;hIDTR${?hI)vRI0J33nP1RN`12NJ$yQPYgHrdeo@Z_ok#ZRL|dGB zy^#`D4g$}Xi}cQe#OdS`K`96^*zEMzM-(bzq`>9h_vI#{)Ia>e*-Yu!ClUbiO10`P z;qkQLFRR8!r{FoSHKUPc_5qfl?uGqhCj#Av<=;-dYUh4817Q-Y>OacIT*ducch@R$ z6BBS;3Dh~|S?%|K>*vzth1ob$M|A2}WNo-Y%XgZhqFs5yk_syw>cGADcB%SA+n2{e zEJ# zfM@%XYH)<%ygb1`%#w*uK&` z!~Vw4nhSPdwUP@bVfn;lgFb6=Y;|4UBv2Eh3g7@YXCz!?7ZW|2w}UHe~@Kf=8MMY3S7WB zN)c)WKGUK*Fe%C@9~y~H19TIv5m5=z@ZJh63L~nlZY6i@(2LS!X~Red!jlG}y5x53 zSg$#b_)D)i5Vhvo3E%wtC1fSn)(a8{1wgr10Zf)&t^J#XJdH&tuX4XU!wb&nSIs;J zpujzY+1N1jt7ugo(0)6?&-MKmgx5YF!Y=!+bMob_X^UAQ%R2{9P{8?33p2wo&w11+9p|^zvgdH|@ ztnUUhH%G~15IvNOwN)p&C*&tRL|!;bF?9(d%bUT(!=S99>#x7O=?aWUT)_dqtNT5* zK9-Pix&l>ZXtph-dihs$R&74MqH8H{)2F&=l5lYmb& zUI!j=3BCTjH6n!gpJ}$(kB^5p@MT#W5%q}GC+jvmRK~7iy2`s1uD}6m4M(yra5=@1 zDLmpHeZDJD)($KHOzIR|bpwq$G7~V$D*dEC?^aZ!K<8+%pr}qjfGDi;)?I zs&~Ggj&V<=B|+4wJ+0m52&`v@_V$)*R^KmGmeq*Xt(EWKp*;g$uSbUwQRRG?H&5y@ z=l132x|EeH(dQU#p(;H(#*T2l6*-sv(xh9e{m6L!D}V7IUcsH{5(u51+5}#AqExS6b2XG$X>1l(!ox;ac;&o2v2!`0X z;x!QOwniu>tT-Y4n1>^iU!py!LNMf@F5V?w{&cop`@k<10xhj^f1f_wAfcLMD=n0& z7dQJ#@#GZ#!6&nkxGUzH9SDpqSbNqxjM9O(PgW27I=NC)v`iZ)Okbu$^v8G;MX34f z2K^&zkGO0I^_mexT~0>QSo_z#7DSM<*fuA&heo`p>=}!3xazLlb?nFFS_w|g-_^-w z>$1GlTk+U%8>M9DFttarKg_0=s)|kWXUHZ=l^7jlE0)5cV)S?Fhk)&ZrDp>_9OFE0 zx`z57vl!G@TcGn~W1dwlQ=+-9C%gj=T`!~kQdU8HySr{_a*&94`jaTIKmvmAJ(a}q zLPdQzQQ|Mjv>jJiakmvj2pAqtN|m&q2B~BPwy>##Ti`5@6 zMtgF<3r?}I*>N~1wt-945eBKUme7(5M!YHu+}Ed3v@>y-*P|Oo8SeyFX1itz?Sxxn z(HTBvF@z@1Ma?dbnWl|l=$5TqSaVsri1N(Fq`DgPDqOOqy3|Qv2=k&*8y(pz%T7-L z@rl$ENG#1ERj4U2fJIf4YNg4W5&=6{{a4<}W3* zMX85RW@i$X-)`IVdoSBHnea94F;ey-AQ4|tGVnj+{|tM7ZkB?A?lEgy`$E1PBM#>D*NTi>@44}q%FVlQ;zp<0x5c=_Er)Yoe; z*9sl{`Efyz>pn8N0K$SgtJXfUJR#i*9u+aN76NgBZf2DI zt94+N*qYRMyi`dI`^TR*+M-JUyP3wDroBT&&lJKgRW`fFyKTuQsrMq|879i(3){nR zO276D548EFSW}Iy@-a|_JT!XO*7poA&E|mnj%@q1Ml;_)IWsYEiTd2(yRke!c7KLN zeBG5#F`zXS_K30v8*wv5aqr&!qWbOs)5Gpp7x;6Eq(ocQ1BB#*>e5R!3q;{LcX5vL zF1D#Gwc8wlu`B1-?44INMz_@*TEUD=b9Ef?G8GPJcAoVkc}gGU+)w*1lPgKX9UeY* z{*=z7HPK=}$KUr`wtPEAnjP}ZgMs>HDP%C=PsKr<(X|#F;2YmQVzYyri3T>ajX|lx z4%D5gL{Y4qm4JhchC`|&^C9yI3cB;S{pOQze7a-8O-ja7Z;K0d$+6m|SHC%DL%d3D zx9x##RY23S&+voF(Ch8dtd^hFxmRQ{)sqeNJ}u~QgBtvhU@k32!hp#GL{z?=vTAp` zn7g5L;ap%1u%iE&c;b7+=duZ7wvBUm*dxX}*XX9aNwLrH>D0Ji745BPQ9mlUk<&{^ zW<9$FqUoq|(GP!0AeWmld9?xTdMk}?;@~d%$T{xwRc@e;-6dqprMZK2#8IGLK z>o8S^91QvxSFspSpE)awa+JaQcEe11Jm#a)i~Q0?3B$hO>}bOw1TuGCL(T{YfOLY{ ziwNT(0(s_792p-Q@loBPeIK3V=5W98uK19o0ex6R5EeGua;g$CSF`DGSKfy4xiGH} z*Ui>i)3rNnvS){=Xk5|Y!;ufrCP@%CVOr{u5R5$Mp-F&VfEt!rWLX}AnsY7X2L#v! zY;Lv=k-<+qZaa0C%=q@`V<*pT&qKGo`&YcsYNgolclx6-zwH4_@34dG?aI8l!WzZP z>|Jdz5XeuEfMSY6wz|mXL`-)xdNlAXo=MYu;jn%5UwK(VVmrt(2plHeKa|&*@I7QF zg3Z>P>=o(P0%7;zOGU%{KZSM+WSVGdOVS#^B6HmA4FJjm>Q@l(^;#?HNy zmM<&kPL?Mo5!NQ()+{>GkeGQ~qF6b~x2IbXGhvQiMnCag+rg~ghSi0tn}M-Gi(^dE9ZdCd zK*gF!v#?=r28v6&Yh_D3jxeJmf4YpxUUe>FMhr8cSn}D2;_!TCCgfK+a z=(P<(L6>4mq*O-4HKD%-vzoo!K{V6X7$km0$F@ym`t!GElU1^kq=%j5`)8r_rAP@& zCc~s~L({WJktI4uy5a-;>v4;B0Wh%)7$vy~7aVrnu%@+^ zv^ZWomjM~P)M$7z$w~C=(e8x`jfcm?GdXF`LK*Kk$nf@VSq8y2gH@{1VIPZP(pGvw z{ayDiweR{lS&VTYuDj&><>r^4SIuPxM`^qE+`Z*(q&JWxD zB+Q*&1knM^sa`1geNE@Nr{Eg4F-FZ@l%)08D(v;~j868s+rpCdoW|+OQLF28YD`p@ zr?yY#_GK>{f1ajUT89M2lHGGQrWh^{eek1aLxFhaiejPQ!(!0yK{xf3P*md(NQxhzI0Vz68#F+-rymRe){`s3h6Mp z8G=?refV-zm9XJOJ1^g92M!uplX_C&yX#3MZ<7K#5RWOd)c{vrI;0L;jU{UM%#Ck- z2;A0M>fl*4_G;CMFZk9xia?$_ZBYx+IyfaddAb&Y=BdG4%*+9bu`8~q-Q(i?vemMfy#CoTw8_6`0lq4{2mmOd|BcZ7OZY?q0JvB>IJ=tt4b=UoGBl?Q zGW<`u%7^BENponEP!zEB|N8{>?yoh_VE-(L5D*khhi_F>X z&wrFd!~npb0@**>-`k25NFew~|DX8+mlDNa|KvN80RTAvBOgj4DPEpJ_^&M&y+!{F rfd&Au{!oK(|0C&G0RXUba5OXhdo9p^4*jnbH~=9e0FbQq*XX|hvoAeR delta 8257 zcmZ{pWmJ{XwubkHO-gr{bhi>xlCtUU+=6sBd`dS+cZzf)5|W#ilZOpOY}6WlQ~(-IZv_{{12z`N~_sOwKhWU<1LPcCdoh z@$`v(Js!qxrpnBlX)MGX64u6XQUPZO%C{Rhs_Q?gV-*9W~^Yb8cndC~b=q4?a@Qj6Y(Etb*ApPUq{%K5|Cc znDCG_%kQBpDNO;$=y4H#@`=2LTuVtmPV!_z$_iU$I=1f)^(QOfbn!=(rb z=K^q2bx6Jf!Vkk@!sw|C`Qe(4+@=i#oz1tjHIGd}n2IjD+&AA44aMr`M?uxt>w$K2 zK0#Np<@S4-@9?Qywf&~k=H&->WtlrO1$akD*u2X(^$vhVSGil=LA-H24IPiywLjzx z5Fx^!o;NW@*Ym~d!ygOtsY41O(;el{)vu7x^` za@=MrCH>6#zZBJ1HM%HzddZb&q|gK;3mqw!C3|M z^;f1McC7XCs_KiAfFj)PL&DKT5rIyv&BmFW3p!V0+_FTeT^pUsQ7Uxjb$rEM3K zPRJ}AWD*$OurxZiOk8@~T~cZo{0azd&tsv9six!2m8CqR>ppTZ6CQR>_VA$;n|ET! z=8TmG&Ww^>S}x*ubh>-pZp2F57<4m&_WXVGx-76#2fS5RoxoYwFENzbeaP1?b{h=J z1cjgJ8unKBzcPN7(On@7=fQ)X=rpipi;|(tW;Y5V`4AG%qzW6FBu;NzunQtWnP(iNeI$wT&!dXYij^*;GLGq4{lb>uW z41b_Xiugt>x}xwhc=V@&rG#yeVerTPfx~Ih;Ua2PH+?$j(XdQ+C!;nYbYMnk<6A0cxU z02&RPkh~&Y2Fsz|YjQ1hl85H1vna1HY5+0y}b`9J;BLbfs%Sk;@WH zGg3^}i+fcbs!Y!1kWRC0yP@?MIA!+NrwSYABB4%wkViXR((jbFiuy*o7?+PIlxM2G zOL`kCUjEUi&9ORduU>1*o>n^(e{)9&0Rm9ppxq)eY2FRtCmVatWEpa48tL#+$nCX` zmSwWJ_BP(EcoO{TuzH^rMZa|mRh`7$KV8?nURf|cM5!+-%@}3VS8E%}_L3L0R7Myps~-9)b~f9z30|0;&9d<=H3oPZq3NEz zcv1{q-4Rxq75^QZ?|bC^~t?Lejq zV_thv__<;zyXWy%=~?(qWCHug0w1Qfs6*{@HZ{vyUBL3t;cNNgRsZIdw#Rjuy)oqJ z7WQ?~sQfc<_*wAoGRjf3QOV?SrQ;j1cY^P*$T7XCUEfOL4Rl+BfOth;lEPPLXdI%-2_hkpHR z(&xG+9wf@dfxIt%xO6$l0BB&av?~Td;;Y{3bqhU!k)qXoTA;7VebkavzSsxgw{lG4Z#vKAC&P_a{W`Ixw(3z#M$r`UmQNYsH-+? ztXC0GcPe!W<>c<^uMNwSg)pWvT7*>zGY|!{g+0wxa^B81w)W9z>zN}TR<_CU?i8g5 z+4k~Gu<2Ue3+`Ua=n(O+W~WrVBp4~R*M4w$x5MFL@(dpGA8#piqrsDbn-@Ody!Rp^PCySeu5$itzDC2@?s^G zj$6R|{GaX$eUV;25KVE`r^9+5JXrR{Iw3y2obY!4=T{-&NwShU6+AhE(C^V=*V;3* zmNh6@peeD8T@Dxxijatw(M!|;BgWcS>clVw~{4n66 zs#D5ZMuVIHOut3-cv;|e5veM)vwP`OqNq~u7vSA1cUKqD4A#Td4m$H;i+vy?qzfXE zl6{zZ`jA71S4zKi*k*4b@sbJSqR$nBhtt(|-gY|aZpz%$R+7A~ZdaZI1$8WGmBODI zQME;=Rcs?)g3snf#YB#Dfx-Haq(3m#5b|2WJ4FA=65bxrOw_UJ2=hLZLe4G-HV@_N zy`Eh}>Nw$}@6K7*Qzn37N%Knf6oHA;*M@7N(eU9yvO6b?%zo5?ce!tW!z)AxMx9c5 z?A-0sD0VFam0j$=z|soH!U=HK+a6BXChEI;TvOD>5Y@^INJzsmx9vl7pgQ;$DapY z>GM}Dk-dCQIrZZ1JJ1%5-4TQRbd`F#zWO9AcJJ-;I~Aa$4!Ao%i9c%gb(2wg757)M z>+K~m28ZVsqg;rJ=2<-fYwmldlHWRQiYSEGWv+uRX7iy;ewmoomPkE?401~d2_V7&QkzKQO~27UNLqb4Gn7d z9*&p$W%Q!sb2GU6iuoS=N^RK>^B>4gihQ7#iPHYjG{xzf41eSXnyrI&w=ZC|c8N7= zPFYiRm{`Rj8l|cBrXiyQixD+Eu5bA88$O`ubCWl+Jgek-6f5Y7{jMvENKNun4oij9 zfx}zdk()^|oWc$Te}}2nnlWx#Xr@7pjh%3B??}H3=Jl+#yV!L&)32k91-ah_?20n7 zrR#iDwpn=8_PZ+tpi?=uk}sjC1wCk5U1;R!-red``rFmeN* zX?BZqicovR*B>*?(rr0qe1;+{v~MClDNW|0Hl>iUB~o=J%6DgUk>Q2q*gH9PjlUP? zV+;=QBzLkxUboqbLedA3IQCIpyQtve^-@Mttwi0TDDA5PH%(n*iZ+PVmO|B_FtbFb z-23p<5%k!}8oOWfoO?90q_qt#ChO-HJOhIUhNGhIX}g-UcZkiqC7h9?(VUfs#7)5u zJSb4yFGHBES7BNr_xLj_Yw0@tkX`ji|5%w9sMWj#ym+pG9xf=qo5ZCg&@ovSaCYJe zJ|haVrBMI!5DS%`)e@lz}0LDht+)d9=BVAh_uFEpM!YU# zvy+hVqe2ZT#G$$7^R4mPtZ)baV_#{s729wY7cPVd>?n*(m~uid^NnHLb|i^~?N&Dp zFmOnTVk)vWFx>za$LM^8rVt`HlYo-NDVklOQ(jZKX=pZRK&S=_DP3hNDR>Hil1r zjCaEyG5A;jVLAujbhInm-8Bpsm1FvKVHpaDO=Q8_pWX zf(AED7x!bvIxLKHI-9@=Uv`YMi9s!zU!~f4mJ-s3+ZAp@$mTG9)(f0&XkZw*3;S;W z*vrAq!9A#CRgSYb4(mUPyQ{Q2DkPo}&-h&7?^#2<<>@=hkwHt^Tv%UJc9r1Fc67aA z7|L&$_*!xaQ~%-Yv&mW#Ls0npet!(KNa0oVxt-0TB?}LK?}I%Fb~=2w&JS3~;vHhK zb{`Vskj~D%8Zmbq>V<$T?PG=k5t=IAZP;YaZdr9=2D=~8H;n07c zfhkD&u(z>;lN-^9I9Y->7Qffh2VX&hF=!*aSeMI$X2uT@1DT&~KRHKbBu}97sEEDC z0M)&)pkuJx(UZk;*6TBxNY6CgC*QT+-XYsr1f=s|v{xL=` zr8aBck=@`U^PDcgc!G30NYUQ&BdhQigdH}F5!o0c>A9Y>zNowrmDprJU zV@&u%F+GmMS4Ft}P-2fEnCcqOzu-#12?OxRqgQcvJWU^zRUIwwCXXUtReF(OMn8puR8ux2c*?vEWWPDU0t8>tR65uddY z`uY$WsR7d}W4)HLlSs*BSm57-s<7{OOtdyE3>XYfF<{KGcg|Xj5HlOXY(~+ea;o*Z zH$!xiOvm`}^#n05fa2RwGz}(|Akw#%>xX8q_&6Lq)X2(~4Pb_vusQ#ET7ohfif;hd zv&dg2pYNE$F9rOXEPUHiM$m-aru=v)nSAf{H39gS@@kV6Tpagj0cHWwv7)Uc8(8_& zB1ADN-XW?fR+|R8VbpB8Z#d_D^(yZO+?3r5rXxUArUSpOnvS9=pfftf1uK)V_=h$s z<+m2R;NDx~tTKpHda&gzg2BO;7L#wYUqkcxamtAv#tK9jRGXMp`l=*=9N+s|@wg8H zmjIuFQx3M?LUW8ua$%b;{||=bUdd^MHNjy#l(bll9iKL(#wcbiyP9TG*0lioOy4ZwW zvT|x{I)@QAlsjrsZMfLj!9NzHpZQvB9`J6$Cot1jZ`oAMniD5xNtI!#LtKRJB& z!0^d0k^B3IX_)AH-l!gzzPXp}Oc6Dbdo3$ezjWQgZ4!*o2I{EQGCHyX9p2O(zXL|Z zev^eQ%CiR;&I4E-yKj@?I`Mjht-hw1mqU=}zsu6&*VULKzw4x4 zb8Ma|J?<{|2p-*AA-&`Ls^HZZ_KPzUlE(Wj3mX+eLDAb@jxC?3w)U*@?OhUU%gL9V zy^BWYx37AbN;GXERnq)AIaIr)4mO;{i<3_Y9dk)4;M}l2*)J{B3RlzgkAQbhI?wz! z$@Nx!yZCs!1v%AFx}DP44454C1;4#%3n?t|^?pEYG0Q|jWubvwM9%(5j`@(Y+&f#C zi+nFy)ug4T07U}}9px^vVoUW(|1s%zZ|;JRd8BAb+-@)4DUB;Z$f!~jai;J;IKLT$ ze2~jO;4SLr`9TYJ1sPf?BLj#hCsVDevM_Tbl|uY(=6BjBlm8fDL*fs$tRc<3O$ z@Q+2`YO6uTQ$@|Q)RHMKwLOyp-VSLP5HVyj5s-7zF6xkKGxQ@u^lNMv?L8G0MVNHo zcmyB*D%@fxUY-@gkuZ3?uHY)sLpk`-d|yOkQCgFrpMP>SeTDHy()$;~epYYg&JCsl z1P=#`EmO2UzOlBtDCn_61I#DKi{>qz$)-bwp!JUv0$cc$<)g)X-9rjYl`4w2NGGMF z=ng?XG7gkGn?~vWmwYA&6$v))W9qjptk zX?%?I&e;sT3{E0K8L4MwWoI6a9!aZ4nWv6oLpV#5__Ac(*B>~ZNY2{R9 zP+2XFQ>3%1qL`UZ3w(p~A~FHRaKCs?^YIocEitRr4}Ay_^@(K3L&{?^Z~jSt<>TWS z@-v)FeR>=*<<(EhOyE4DWRD)j->B^X+_PQkV|^Q@PW_Ab7nn2}I#sbN1+*Hgu@-!2 z^JV+>pgqkZHO`~bVv`z?V1dx^Sdx`Ga~Gt4@&GP7ERh!)KPQgyV69gyH@ z%?MA4vBzMV&frf5UGA3)$Fq*+5^%|yRaacPTD;diFA;E&40#5?nC=5#kn@IjkCx3| zzCpw79aQ!fYycjSjX}>ce2dPiKOUz8?qZ6w?;nsF?*d9&cyCx|7iv6*>A@HbN4hhMWI(XJu+{^Qv4C(|UX(q~!L&6E zb6_O;?4jxQsXFCDWfTd1ili%rHb^^SIfB-WtR_Jrx60xWyuZ_A?5lhUdF)}cAriBK z{?ugonE#}R=<7dqd8D48J?Ds?suQ3)JZT$QuS?X#I30Hx=)LR35+^PzXNhFs!O}LQ zOACLh2c9?gNln61g1?kBnV#}2S^Q&)8PIp3wPK-gB zY*u3Qi#=Xg<(>vWjtE?~m|Hf~KrV=!TVL;BZQ*X!EZH)JoMcSXsH6jBm(S+l>gCil zkL(vxc0koFeUY6V0Yg>MrJcESu3|ZuuPmSlP)!>B{(_Y_H96zNt}=5J_}u*l24l?m z+SvaH;n?qY&%ID%p0+G~Th)!8lNYV=*136zcOh)n;2^t*p_kuqz}AEKn@YgD5w&?vNt{DtpdxhzXaPmqWy-o%Is zVDDOF9c@0Tw=u?87J_bUTOQ*aHb?o4@(n9c8W}e_3|j|S`0wEhGRhwo!}yGod8%w4 z&Czc=a4{9~wJ0qdj8vIhaZ;iUYIRRe4DGajlXbE(>q0-O8Xfn;;@o=aXi32#OYBN< zzrYOhZCbGB*&pM0alU3EX*`GaF=UnlNb?mGiUceHs&Lc~^LV>5W;9DXdo~Ak4!9qG z*7!+c=C2trL})fNXbtW<1~baxsXO<_qN35l;D?&65noWd91S{Wy(AS2RY?Eg zhOQCXUu0rZX^AiX;aN2y)?DOavd#iwp5-@Z&WvC#+Ln|GeI|EBrvfe;X%N05aL>>` zXoj(Db}21uAC}5Ts5PfX_;R;Ws?6d=1XkY3&#I!4V>1(bFENvcm(^$yb8IquvAomO z%Y>kCb*(cRZ%=iq@6(~#`SCN%0r5Mk=9$-0jr2w$O8#!Xl+N$lwX%^9iCtXRg1-zP zuqD~ofy^wWCj*Q2dUvj2B@VPZKr=8h zC55LKF%*wWC?{{t-$Fg;P@ApINCXi?Y`((w ztV*@)cjE-rquFpn_iH^7Mh}NdEyzO%a7hW>b zdnm{vLKwoOZs>{CSR2aY0qB)L)Gm8(yjt_ES|}_0RZV5)T7_*To9`PZTzKx)xqRuP_XJ|mLj|HBE zcS(*D$xWTyf0|gq2zT!6d1ZfuIbiVk;^@o0?ltnVnmti}aX>6brXHKSB!1_ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-routing-app diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java index 43f9de3..d611000 100644 --- a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -28,10 +28,9 @@ import android.os.Environment; import android.util.DisplayMetrics; import android.view.View; import android.widget.Toast; -import btools.expressions.BExpressionContextGlobal; +import btools.expressions.BExpressionContextWay; import btools.expressions.BExpressionMetaData; import btools.mapaccess.OsmNode; -import btools.mapaccess.StorageConfigHelper; import btools.router.OsmNodeNamed; import btools.router.OsmTrack; import btools.router.RoutingContext; @@ -171,7 +170,7 @@ public class BRouterView extends View String basedir = fbd.getAbsolutePath(); AppLogger.log( "using basedir: " + basedir ); - String version = "v1.4.7"; + String version = "v1.4.9"; // create missing directories assertDirectoryExists( "project directory", basedir + "/brouter", null, null ); @@ -185,7 +184,7 @@ public class BRouterView extends View profileDir = basedir + "/brouter/profiles2"; assertDirectoryExists( "profile directory", profileDir, "profiles2.zip", version ); modesDir = basedir + "/brouter/modes"; - assertDirectoryExists( "modes directory", modesDir, "modes.zip", null ); + assertDirectoryExists( "modes directory", modesDir, "modes.zip", version ); assertDirectoryExists( "readmes directory", basedir + "/brouter/readmes", "readmes.zip", version ); cor = CoordinateReader.obtainValidReader( basedir, segmentDir ); @@ -564,7 +563,7 @@ public class BRouterView extends View { exists = !vtag.createNewFile(); } - catch( IOException io ) { throw new RuntimeException( "error checking version tag " + vtag ); } + catch( IOException io ) { } // well.. } if ( !exists ) @@ -767,7 +766,7 @@ public class BRouterView extends View else { String memstat = memoryClass + "mb pathPeak " + ((cr.getPathPeak()+500)/1000) + "k"; - String result = "version = BRouter-1.4.8\n" + "mem = " + memstat + "\ndistance = " + cr.getDistance() / 1000. + " km\n" + "filtered ascend = " + cr.getAscend() + String result = "version = BRouter-1.4.9\n" + "mem = " + memstat + "\ndistance = " + cr.getDistance() / 1000. + " km\n" + "filtered ascend = " + cr.getAscend() + " m\n" + "plain ascend = " + cr.getPlainAscend(); rawTrack = cr.getFoundRawTrack(); @@ -920,13 +919,13 @@ public class BRouterView extends View // parse global section of profile for mode preselection BExpressionMetaData meta = new BExpressionMetaData(); - BExpressionContextGlobal expctxGlobal = new BExpressionContextGlobal( meta ); + BExpressionContextWay expctx = new BExpressionContextWay( meta ); meta.readMetaData( new File( profileDir, "lookups.dat" ) ); - expctxGlobal.parseFile( new File( profilePath ), null ); - expctxGlobal.evaluate( new int[0] ); - boolean isFoot = 0.f != expctxGlobal.getVariableValue( "validForFoot", 0.f ); - boolean isBike = 0.f != expctxGlobal.getVariableValue( "validForBikes", 0.f ); - boolean isCar = 0.f != expctxGlobal.getVariableValue( "validForCars", 0.f ); + expctx.parseFile( new File( profilePath ), "global" ); + + boolean isFoot = 0.f != expctx.getVariableValue( "validForFoot", 0.f ); + boolean isBike = 0.f != expctx.getVariableValue( "validForBikes", 0.f ); + boolean isCar = 0.f != expctx.getVariableValue( "validForCars", 0.f ); if ( isFoot || isBike || isCar ) { diff --git a/brouter-server/pom.xml b/brouter-server/pom.xml index d96f3a7..03b798c 100644 --- a/brouter-server/pom.xml +++ b/brouter-server/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-server diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java index 199c10f..6a72c3d 100644 --- a/brouter-server/src/main/java/btools/server/BRouter.java +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -88,7 +88,7 @@ public class BRouter } System.exit(0); } - System.out.println("BRouter 1.4.8 / 10122016 / abrensch"); + System.out.println("BRouter 1.4.9 / 24092017 / abrensch"); if ( args.length < 6 ) { System.out.println("Find routes in an OSM map"); diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java index 53344b2..4a9cd56 100644 --- a/brouter-server/src/main/java/btools/server/RouteServer.java +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -155,7 +155,7 @@ public class RouteServer extends Thread public static void main(String[] args) throws Exception { - System.out.println("BRouter 1.4.8 / 10122016"); + System.out.println("BRouter 1.4.9 / 24092017"); if ( args.length != 5 ) { System.out.println("serve BRouter protocol"); diff --git a/brouter-util/pom.xml b/brouter-util/pom.xml index a3f4a78..c71dd03 100644 --- a/brouter-util/pom.xml +++ b/brouter-util/pom.xml @@ -5,7 +5,7 @@ org.btools brouter - 1.4.8 + 1.4.9 ../pom.xml brouter-util diff --git a/misc/pbfparser/compile_parser.bat b/misc/pbfparser/compile_parser.bat index 7a5452b..a95926a 100644 --- a/misc/pbfparser/compile_parser.bat +++ b/misc/pbfparser/compile_parser.bat @@ -1 +1 @@ -javac -d . -cp pbfparser.jar;brouter.jar BPbfFieldDecoder.java BPbfBlobDecoder.java OsmParser.java +javac -d . -cp pbfparser.jar;brouter.jar BPbfFieldDecoder.java BPbfBlobDecoder.java OsmParser.java OsmParser2.java diff --git a/misc/profiles2/car-eco.brf b/misc/profiles2/car-eco.brf new file mode 100644 index 0000000..0cbec3e --- /dev/null +++ b/misc/profiles2/car-eco.brf @@ -0,0 +1,181 @@ +# +# Car-Routing based on a kinematic model +# +# Depending on the vmax-parameter (target-speed) +# this can be anything from least-time routing to eco-routing +# +# +---model:btools.router.KinematicModel + +---context:global + +# kinematic parameters + +assign vmax = 90 # kmh +assign recup_efficiency = 0.7 # (ratio) +assign totalweight = 1640 # kg +assign f_roll = 232 # Newton +assign f_air = 0.4 # 0.5*cw*A*rho +assign f_recup = 400 # Newton +assign p_standby = 250 # Watt + +# technical parameters + +assign validForCars = true +assign pass1coefficient = 1.3 +assign turnInstructionMode = 1 # 0=none, 1=auto-choose, 2=locus-style, 3=osmand-style + +---context:way # following code refers to way-tags + +assign initialcost switch route=ferry 20000 0 + +# +# calculate logical car access +# +assign caraccess + switch motorcar= + switch motor_vehicle= + switch vehicle= + switch access= + switch highway=motorway|motorway_link 1 + switch highway=trunk|trunk_link 1 + switch highway=primary|primary_link 1 + switch highway=secondary|secondary_link 1 + switch highway=tertiary|tertiary_link 1 + switch highway=unclassified 1 + switch route=ferry 1 + switch highway=residential|living_street 1 + switch highway=service 1 + 0 + access=yes|permissive|designated|destination + vehicle=yes|designated|destination + motor_vehicle=yes|permissive|designated|destination + motorcar=yes|permissive|designated|destination + +assign accessspeedlimit = if caraccess then 999 else 0 + +assign isbadoneway = if reversedirection=yes then ( if oneway= then junction=roundabout else oneway=yes|true|1 ) else oneway=-1 +assign onewayspeedlimit = if isbadoneway then 0 else 999 + +assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link + +assign maxspeed_surface = + switch or surface= surface=paved|asphalt|concrete 999 + switch surface=paving_stones|cobblestone 30 + 2 + +assign maxspeed_tracktype = + switch tracktype= 999 + switch tracktype=grade1 40 + switch tracktype=grade2 5 + 1 + +assign maxspeed_implicit = + switch highway=motorway 999 + switch highway=motorway_link 130 + switch highway=trunk 250 + switch highway=trunk_link 100 + switch highway=primary|primary_link 100 + switch highway=secondary|secondary_link 90 + switch highway=tertiary|tertiary_link 80 + switch highway=unclassified 50 + switch route=ferry 10 + switch highway=bridleway 10 + switch highway=residential|living_street 30 + switch highway=service 30 + switch highway=track|road|path switch tracktype=grade1 30 5 + 0 + +assign maxspeed = + min onewayspeedlimit + min accessspeedlimit + switch maxspeed=50 50 + switch maxspeed=30 30 + switch maxspeed=10 10 + switch maxspeed=20 20 + switch maxspeed=40 40 + switch maxspeed=60 60 + switch maxspeed=70 70 + switch maxspeed=80 80 + switch maxspeed=90 90 + switch maxspeed=100 100 + switch maxspeed=110 110 + switch maxspeed=120 120 + switch maxspeed=130 130 + switch maxspeed=urban 50 + switch maxspeed=rural 100 + min maxspeed_implicit + min maxspeed_surface maxspeed_tracktype + +assign costfactor = if equal maxspeed 0 then 10000 else 0 + +assign minspeed = + switch highway=motorway|trunk 75 0 + +# way priorities used for voice hint generation + +assign priorityclassifier = + + if ( highway=motorway ) then 30 + else if ( highway=motorway_link ) then 29 + else if ( highway=trunk ) then 28 + else if ( highway=trunk_link ) then 27 + else if ( highway=primary ) then 26 + else if ( highway=primary_link ) then 25 + else if ( highway=secondary ) then 24 + else if ( highway=secondary_link ) then 23 + else if ( highway=tertiary ) then 22 + else if ( highway=tertiary_link ) then 21 + else if ( highway=unclassified ) then 20 + else if ( highway=residential|living_street ) then 6 + else if ( highway=service ) then 6 + else if ( highway=track ) then if tracktype=grade1 then 4 else 2 + else if ( highway=bridleway|road ) then 2 + else 0 + +# some more classifying bits used for voice hint generation... + +assign isgoodoneway = if reversedirection=yes then oneway=-1 + else if oneway= then junction=roundabout else oneway=yes|true|1 +assign isroundabout = junction=roundabout +assign isgoodforcars = if greater priorityclassifier 6 then true + else if highway=residential|living_street|service then true + else if ( and highway=track tracktype=grade1 ) then true + else false + +# ... encoded into a bitmask + +assign classifiermask add isbadoneway + add multiply isgoodoneway 2 + add multiply isroundabout 4 + add multiply islinktype 8 + add multiply isgoodforcars 16 + multiply highway=residential|living_street 32 + + +---context:node # following code refers to node tags + +# +# calculate logical car access to nodes +# +assign caraccess + switch motorcar= + switch motor_vehicle= + switch vehicle= + switch access= + not barrier=gate|bollard|lift_gate|cycle_barrier + access=yes|permissive|designated|destination + vehicle=yes|permissive|designated|destination + motor_vehicle=yes|permissive|designated|destination + motorcar=yes|permissive|designated|destination + +assign initialcost = + + switch caraccess + 0 + 1000000 + +assign maxspeed = + + if or crossing=traffic_signals highway=traffic_signals then 0 + else 999 diff --git a/misc/profiles2/car-fast.brf b/misc/profiles2/car-fast.brf new file mode 100644 index 0000000..3c9959a --- /dev/null +++ b/misc/profiles2/car-fast.brf @@ -0,0 +1,181 @@ +# +# Car-Routing based on a kinematic model +# +# Depending on the vmax-parameter (target-speed) +# this can be anything from least-time routing to eco-routing +# +# +---model:btools.router.KinematicModel + +---context:global + +# kinematic parameters + +assign vmax = 160 # kmh +assign recup_efficiency = 0.7 # (ratio) +assign totalweight = 1640 # kg +assign f_roll = 232 # Newton +assign f_air = 0.4 # 0.5*cw*A*rho +assign f_recup = 400 # Newton +assign p_standby = 250 # Watt + +# technical parameters + +assign validForCars = true +assign pass1coefficient = 1.3 +assign turnInstructionMode = 1 # 0=none, 1=auto-choose, 2=locus-style, 3=osmand-style + +---context:way # following code refers to way-tags + +assign initialcost switch route=ferry 20000 0 + +# +# calculate logical car access +# +assign caraccess + switch motorcar= + switch motor_vehicle= + switch vehicle= + switch access= + switch highway=motorway|motorway_link 1 + switch highway=trunk|trunk_link 1 + switch highway=primary|primary_link 1 + switch highway=secondary|secondary_link 1 + switch highway=tertiary|tertiary_link 1 + switch highway=unclassified 1 + switch route=ferry 1 + switch highway=residential|living_street 1 + switch highway=service 1 + 0 + access=yes|permissive|designated|destination + vehicle=yes|designated|destination + motor_vehicle=yes|permissive|designated|destination + motorcar=yes|permissive|designated|destination + +assign accessspeedlimit = if caraccess then 999 else 0 + +assign isbadoneway = if reversedirection=yes then ( if oneway= then junction=roundabout else oneway=yes|true|1 ) else oneway=-1 +assign onewayspeedlimit = if isbadoneway then 0 else 999 + +assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link + +assign maxspeed_surface = + switch or surface= surface=paved|asphalt|concrete 999 + switch surface=paving_stones|cobblestone 30 + 2 + +assign maxspeed_tracktype = + switch tracktype= 999 + switch tracktype=grade1 40 + switch tracktype=grade2 5 + 1 + +assign maxspeed_implicit = + switch highway=motorway 999 + switch highway=motorway_link 130 + switch highway=trunk 250 + switch highway=trunk_link 100 + switch highway=primary|primary_link 100 + switch highway=secondary|secondary_link 90 + switch highway=tertiary|tertiary_link 80 + switch highway=unclassified 50 + switch route=ferry 10 + switch highway=bridleway 10 + switch highway=residential|living_street 30 + switch highway=service 30 + switch highway=track|road|path switch tracktype=grade1 30 5 + 0 + +assign maxspeed = + min onewayspeedlimit + min accessspeedlimit + switch maxspeed=50 50 + switch maxspeed=30 30 + switch maxspeed=10 10 + switch maxspeed=20 20 + switch maxspeed=40 40 + switch maxspeed=60 60 + switch maxspeed=70 70 + switch maxspeed=80 80 + switch maxspeed=90 90 + switch maxspeed=100 100 + switch maxspeed=110 110 + switch maxspeed=120 120 + switch maxspeed=130 130 + switch maxspeed=urban 50 + switch maxspeed=rural 100 + min maxspeed_implicit + min maxspeed_surface maxspeed_tracktype + +assign costfactor = if equal maxspeed 0 then 10000 else 0 + +assign minspeed = + switch highway=motorway|trunk 75 0 + +# way priorities used for voice hint generation + +assign priorityclassifier = + + if ( highway=motorway ) then 30 + else if ( highway=motorway_link ) then 29 + else if ( highway=trunk ) then 28 + else if ( highway=trunk_link ) then 27 + else if ( highway=primary ) then 26 + else if ( highway=primary_link ) then 25 + else if ( highway=secondary ) then 24 + else if ( highway=secondary_link ) then 23 + else if ( highway=tertiary ) then 22 + else if ( highway=tertiary_link ) then 21 + else if ( highway=unclassified ) then 20 + else if ( highway=residential|living_street ) then 6 + else if ( highway=service ) then 6 + else if ( highway=track ) then if tracktype=grade1 then 4 else 2 + else if ( highway=bridleway|road ) then 2 + else 0 + +# some more classifying bits used for voice hint generation... + +assign isgoodoneway = if reversedirection=yes then oneway=-1 + else if oneway= then junction=roundabout else oneway=yes|true|1 +assign isroundabout = junction=roundabout +assign isgoodforcars = if greater priorityclassifier 6 then true + else if highway=residential|living_street|service then true + else if ( and highway=track tracktype=grade1 ) then true + else false + +# ... encoded into a bitmask + +assign classifiermask add isbadoneway + add multiply isgoodoneway 2 + add multiply isroundabout 4 + add multiply islinktype 8 + add multiply isgoodforcars 16 + multiply highway=residential|living_street 32 + + +---context:node # following code refers to node tags + +# +# calculate logical car access to nodes +# +assign caraccess + switch motorcar= + switch motor_vehicle= + switch vehicle= + switch access= + not barrier=gate|bollard|lift_gate|cycle_barrier + access=yes|permissive|designated|destination + vehicle=yes|permissive|designated|destination + motor_vehicle=yes|permissive|designated|destination + motorcar=yes|permissive|designated|destination + +assign initialcost = + + switch caraccess + 0 + 1000000 + +assign maxspeed = + + if or crossing=traffic_signals highway=traffic_signals then 0 + else 999 diff --git a/misc/profiles2/car-test.brf b/misc/profiles2/car-test.brf deleted file mode 100644 index 7d446f2..0000000 --- a/misc/profiles2/car-test.brf +++ /dev/null @@ -1,158 +0,0 @@ -# -# Car-Routing is experimantal !!! -# -# DO NOT USE FOR ACTUAL NAVIGATION -# -# Turn restrictions are not complete, leading to wrong routes -# - ----context:global - -assign downhillcost 0 -assign downhillcutoff 0 -assign uphillcost 0 -assign uphillcutoff 0 - -assign validForCars 1 -assign pass1coefficient 1.3 - -assign turnInstructionMode = 1 # 0=none, 1=auto-choose, 2=locus-style, 3=osmand-style - ----context:way # following code refers to way-tags - -assign turncost if junction=roundabout then 0 else 200 -assign initialcost switch route=ferry 20000 0 - - -# -# calculate logical car access -# -assign caraccess - switch motorcar= - switch motor_vehicle= - switch vehicle= - switch access= - switch or highway=motorway highway=motorway_link 1 - switch or highway=trunk highway=trunk_link 1 - switch or highway=primary highway=primary_link 1 - switch or highway=secondary highway=secondary_link 1 - switch or highway=tertiary highway=tertiary_link 1 - switch highway=unclassified 1 - switch route=ferry 1 - switch or highway=residential highway=living_street 1 - switch highway=service 1 - 0 - or access=yes or access=permissive or access=designated access=destination - or vehicle=yes or vehicle=designated vehicle=destination - or motor_vehicle=yes or motor_vehicle=permissive or motor_vehicle=designated motor_vehicle=destination - or motorcar=yes or motorcar=permissive or motorcar=designated motorcar=destination - -assign accesspenalty - switch caraccess - 0 - 10000 - -assign onewaypenalty - switch switch reversedirection=yes - switch oneway= - junction=roundabout - or oneway=yes or oneway=true oneway=1 - oneway=-1 - 10000 - 0.0 - - -assign ispaved or surface=paved or surface=asphalt or surface=concrete surface=paving_stones - -assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link - -assign costfactor - - add max onewaypenalty accesspenalty - - add switch islinktype 0.05 0 - - switch and highway= not route=ferry 10000 - - switch or highway=motorway highway=motorway_link 1 - switch or highway=trunk highway=trunk_link 1 - switch or highway=primary highway=primary_link switch maxspeed=30 2.0 switch maxspeed=50 1.5 1.3 - switch or highway=secondary highway=secondary_link 1.4 - switch or highway=tertiary highway=tertiary_link 1.5 - switch highway=unclassified 1.6 - switch route=ferry 5.67 - switch highway=bridleway 5 - switch or highway=residential highway=living_street 2 - switch highway=service 2 - switch or highway=track or highway=road highway=path - switch tracktype=grade1 5 - switch ispaved 5 - 30 - 10000 - - -# way priorities used for voice hint generation - -assign priorityclassifier = - - if ( highway=motorway ) then 30 - else if ( highway=motorway_link ) then 29 - else if ( highway=trunk ) then 28 - else if ( highway=trunk_link ) then 27 - else if ( highway=primary ) then 26 - else if ( highway=primary_link ) then 25 - else if ( highway=secondary ) then 24 - else if ( highway=secondary_link ) then 23 - else if ( highway=tertiary ) then 22 - else if ( highway=tertiary_link ) then 21 - else if ( highway=unclassified ) then 20 - else if ( highway=residential|living_street ) then 6 - else if ( highway=service ) then 6 - else if ( highway=track ) then if tracktype=grade1 then 4 else 2 - else if ( highway=bridleway|road ) then 2 - else 0 - -# some more classifying bits used for voice hint generation... - -assign isbadoneway = not equal onewaypenalty 0 -assign isgoodoneway = if reversedirection=yes then oneway=-1 - else if oneway= then junction=roundabout else oneway=yes|true|1 -assign isroundabout = junction=roundabout -assign isgoodforcars = if greater priorityclassifier 6 then true - else if highway=residential|living_street|service then true - else if ( and highway=track tracktype=grade1 ) then true - else false - -# ... encoded into a bitmask - -assign classifiermask add isbadoneway - add multiply isgoodoneway 2 - add multiply isroundabout 4 - add multiply islinktype 8 - multiply isgoodforcars 16 - - ----context:node # following code refers to node tags - -# -# calculate logical car access to nodes -# -assign caraccess - switch motorcar= - switch motor_vehicle= - switch vehicle= - switch access= - switch barrier=gate 0 - switch barrier=bollard 0 - switch barrier=lift_gate 0 - switch barrier=cycle_barrier 0 - 1 - or access=yes or access=permissive or access=designated access=destination - or vehicle=yes or vehicle=permissive or vehicle=designated vehicle=destination - or motor_vehicle=yes or motor_vehicle=permissive or motor_vehicle=designated motor_vehicle=destination - or motorcar=yes or motorcar=permissive or motorcar=designated motorcar=destination - -assign initialcost - switch caraccess - 0 - 1000000 diff --git a/misc/profiles2/lookups.dat b/misc/profiles2/lookups.dat index a1cc7fe..3826d05 100644 --- a/misc/profiles2/lookups.dat +++ b/misc/profiles2/lookups.dat @@ -1,5 +1,5 @@ ---lookupversion:10 ----minorversion:6 +---minorversion:7 ---context:way @@ -35,6 +35,12 @@ highway;0000002631 services highway;0000002133 corridor highway;0000002093 crossing highway;0000001440 bus_stop +highway;0000001274 yes +highway;0000000679 unsurfaced +highway;0000000108 byway +highway;0000000037 driveway +highway;0000000021 mini_roundabout +highway;0000000020 turning_loop tracktype;0000887965 grade2 tracktype;0000868414 grade3 @@ -512,6 +518,35 @@ mtb:scale:uphill;0000009099 3 3+ 3- mtb:scale:uphill;0000005825 4 4+ 4- mtb:scale:uphill;0000004628 5 5+ 5- +crossing;0000101049 zebra +crossing;0000017509 unmarked +crossing;0000013817 traffic_signals +crossing;0000011062 uncontrolled +crossing;0000001722 yes +crossing;0000001678 island +crossing;0000000457 marked +crossing;0000000131 pedestrian_signals +crossing;0000000122 no + +informal;0000002424 yes + +indoor;0000058418 yes +indoor;0000025038 room +indoor;0000005295 wall +indoor;0000004322 corridor +indoor;0000002410 area +indoor;0000000816 column +indoor;0000000568 no +indoor;0000000129 shop +indoor;0000000099 steps + +4wd_only;0000008129 yes Yes +4wd_only;0000000487 recommended +4wd_only;0000000041 no + +concrete;0000000043 plates +concrete;0000000013 lanes + ---context:node highway;0001314954 bus_stop @@ -565,6 +600,8 @@ highway;0000000113 culvert highway;0000000100 highway;0000000046 toll_bridge highway;0000000037 city_entry +highway;0000002967 traffic_mirror +highway;0000001724 priority barrier;0000606512 gate barrier;0000164120 bollard diff --git a/pom.xml b/pom.xml index 168f359..a8999e9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.btools brouter - 1.4.8 + 1.4.9 pom http://brouter.de/brouter/ brouter