diff --git a/brouter-mem-router/pom.xml b/brouter-mem-router/pom.xml
new file mode 100644
index 0000000..d1c9cb9
--- /dev/null
+++ b/brouter-mem-router/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+ org.btools
+ brouter
+ 1.1
+ ../pom.xml
+
+ brouter-mem-router
+ jar
+
+
+
+ org.btools
+ brouter-util
+ ${project.version}
+
+
+ org.btools
+ brouter-expressions
+ ${project.version}
+
+
+ org.btools
+ brouter-mapaccess
+ ${project.version}
+
+
+ org.btools
+ brouter-core
+ ${project.version}
+
+
+ org.btools
+ brouter-map-creator
+ ${project.version}
+
+
+ junit
+ junit
+
+
+
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/GraphLoader.java b/brouter-mem-router/src/main/java/btools/memrouter/GraphLoader.java
new file mode 100644
index 0000000..8c015b6
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/GraphLoader.java
@@ -0,0 +1,265 @@
+package btools.memrouter;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import btools.expressions.BExpressionContext;
+import btools.expressions.BExpressionContextWay;
+import btools.expressions.BExpressionMetaData;
+import btools.mapaccess.OsmPos;
+import btools.mapcreator.MapCreatorBase;
+import btools.mapcreator.NodeData;
+import btools.mapcreator.NodeIterator;
+import btools.mapcreator.WayData;
+import btools.mapcreator.WayIterator;
+import btools.util.ByteArrayUnifier;
+import btools.util.CompactLongMap;
+import btools.util.FrozenLongMap;
+import btools.util.LazyArrayOfLists;
+
+/**
+ * GraphLoader loads the routing graph from
+ * the nodes+way files (much like mapcreator.WayLinker)
+ *
+ * @author ab
+ */
+public class GraphLoader extends MapCreatorBase
+{
+ private CompactLongMap nodesMap;
+
+ private BExpressionContextWay expctxWay;
+
+ private ByteArrayUnifier abUnifier;
+
+ private int currentTile;
+
+ private long linksLoaded = 0L;
+ private long nodesLoaded = 0L;
+
+ private static final int MAXTILES = 2592;
+ private List> seglistsArray = new ArrayList>(2592);
+
+ public static void main(String[] args) throws Exception
+ {
+ System.out.println("*** GraphLoader: load a routing graph in memory");
+ if (args.length != 5)
+ {
+ System.out.println("usage: java GraphLoader ");
+ return;
+ }
+
+ BExpressionMetaData meta = new BExpressionMetaData();
+
+ // read lookup + profile for lookup-version + access-filter
+ BExpressionContextWay expctxWay = new BExpressionContextWay(meta);
+ File lookupFile = new File( args[2] );
+ File profileFile = new File( args[3] );
+ meta.readMetaData( lookupFile );
+ expctxWay.parseFile( profileFile, "global" );
+
+ GraphLoader graph = new GraphLoader();
+ File[] fahrplanFiles = new File[2];
+ fahrplanFiles[0] = new File( args[4] );
+ fahrplanFiles[1] = new File( args[5] );
+ graph.process( new File( args[0] ), new File( args[1] ), fahrplanFiles, expctxWay );
+ }
+
+ public void process( File nodeTilesIn, File wayTilesIn, File[] fahrplanFiles, BExpressionContextWay expctxWay ) throws Exception
+ {
+ this.expctxWay = expctxWay;
+
+ seglistsArray = new ArrayList>(MAXTILES);
+ for( int i=0; i < MAXTILES; i++ )
+ {
+ seglistsArray.add( null );
+ }
+
+ abUnifier = new ByteArrayUnifier( 16384, false );
+
+ nodesMap = new CompactLongMap();
+
+ // read all nodes
+ new NodeIterator( this, false ).processDir( nodeTilesIn, ".u5d" );
+
+ // freeze the nodes-map
+ nodesMap = new FrozenLongMap( nodesMap );
+
+ // trim the list array
+ for( int i=0; i nodes = new ArrayList();
+ nodes.addAll( subListForPos( ilon-6125, ilat-6125 ) );
+ nodes.addAll( subListForPos( ilon-6125, ilat+6125 ) );
+ nodes.addAll( subListForPos( ilon+6125, ilat-6125 ) );
+ nodes.addAll( subListForPos( ilon+6125, ilat+6125 ) );
+
+ int mindist = Integer.MAX_VALUE;
+ OsmNodeP bestmatch = null;
+
+ for( OsmNodeP node : nodes )
+ {
+ int dist = pos.calcDistance( node );
+ if ( dist < mindist )
+ {
+ if ( wayCtx == null || hasRoutableLinks(node, wayCtx) )
+ {
+ mindist = dist;
+ bestmatch = node;
+ }
+ }
+ }
+ return bestmatch;
+ }
+
+ private boolean hasRoutableLinks( OsmNodeP node, BExpressionContextWay wayCtx )
+ {
+ for( OsmLinkP link = node.getFirstLink(); link != null; link = link.getNext( node ) )
+ {
+ if ( link.isWayLink() )
+ {
+ wayCtx.evaluate( false, link.descriptionBitmap, null );
+ if ( wayCtx.getCostfactor() < 10000.f )
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void nodeFileStart( File nodefile ) throws Exception
+ {
+ currentTile = tileForFilename( nodefile.getName() );
+ seglistsArray.set(currentTile, new LazyArrayOfLists(160000) );
+ System.out.println( "nodes currentTile=" + currentTile );
+ }
+
+ @Override
+ public void nextNode( NodeData data ) throws Exception
+ {
+ OsmNodeP n = data.description == null ? new OsmNodeP() : new OsmNodePT(data.description);
+ n.ilon = data.ilon;
+ n.ilat = data.ilat;
+ n.selev = data.selev;
+
+ // add to the map
+ nodesMap.fastPut( data.nid, n );
+
+ // add also to the list array
+ subListForPos( n.ilon, n.ilat ).add( n );
+
+ nodesLoaded++;
+ }
+
+ @Override
+ public void wayFileStart( File wayfile ) throws Exception
+ {
+ currentTile = tileForFilename( wayfile.getName() );
+ System.out.println( "ways currentTile=" + currentTile );
+ }
+
+ @Override
+ public void nextWay( WayData way ) throws Exception
+ {
+ byte[] description = abUnifier.unify( way.description );
+
+ byte wayBits = 0;
+ expctxWay.decode( description );
+ if ( !expctxWay.getBooleanLookupValue( "bridge" ) ) wayBits |= OsmNodeP.NO_BRIDGE_BIT;
+ if ( !expctxWay.getBooleanLookupValue( "tunnel" ) ) wayBits |= OsmNodeP.NO_TUNNEL_BIT;
+
+ OsmNodeP n1 = null;
+ OsmNodeP n2 = null;
+ for (int i=0; i= 180 || ilon % 5 != 0 ) return -1;
+ if ( ilat < - 90 || ilat >= 90 || ilat % 5 != 0 ) return -1;
+ return (ilon+180) / 5 + 72*((ilat+90)/5);
+ }
+
+ private int tileForPos( int ilon, int ilat )
+ {
+ return ilon / 5000000 + 72 * ( ilat / 5000000 );
+ }
+
+ private int subIdxForPos( int ilon, int ilat )
+ {
+ int lonModulo = ilon % 5000000;
+ int latModulo = ilat % 5000000;
+ return ( lonModulo / 12500 ) + 400 * (latModulo / 12500);
+ }
+
+ private List subListForPos( int ilon, int ilat )
+ {
+ if ( ilon < 0 || ilon >= 360000000 || ilat < 0 || ilat >= 180000000 )
+ {
+ throw new IllegalArgumentException( "illegal position: " + ilon + " " + ilat );
+ }
+ int tileNr = tileForPos( ilon, ilat );
+ if ( seglistsArray.get(tileNr) == null ) return new ArrayList();
+ return seglistsArray.get(tileNr).getList( subIdxForPos( ilon, ilat ) );
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OffsetSet.java b/brouter-mem-router/src/main/java/btools/memrouter/OffsetSet.java
new file mode 100644
index 0000000..02a9d1e
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/OffsetSet.java
@@ -0,0 +1,145 @@
+/**
+ * Set off departure offsets (immutable)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OffsetSet
+{
+ private static Map existingSets = new HashMap();
+ private static OffsetSet empty = new OffsetSet( 0L );
+ private static OffsetSet full = new OffsetSet( -1L );
+
+ protected long mask;
+
+ private static int instancecount = 0;
+
+ public static OffsetSet emptySet()
+ {
+ return empty;
+ }
+
+ public static OffsetSet fullSet()
+ {
+ return full;
+ }
+
+ private OffsetSet( long m )
+ {
+ mask = m;
+ }
+
+ private static OffsetSet create( long m, OffsetSet template )
+ {
+ if ( m == 0L )
+ {
+ return null;
+ }
+ if ( m == template.mask )
+ {
+ return template;
+ }
+
+ Long mm = Long.valueOf( m );
+ OffsetSet set = existingSets.get( mm );
+ if ( set == null )
+ {
+ set = new OffsetSet( m );
+ existingSets.put( mm, set );
+ instancecount++;
+ System.out.println( "created set: " + set + " instancecount=" + instancecount );
+ }
+ return set;
+ }
+ public static OffsetSet create( List offsets, OffsetSet template )
+ {
+ long m = 0L;
+ for( Integer offset : offsets )
+ {
+ int i = offset.intValue();
+ if ( i >= 0 && i < 64 )
+ {
+ m |= ( 1L << i );
+ }
+ }
+ return create( m, template );
+ }
+
+ public int size()
+ {
+ return 64;
+ }
+
+ public boolean contains( int offset )
+ {
+ return ( ( 1L << offset ) & mask ) != 0L;
+ }
+
+ public OffsetSet add( int offset )
+ {
+ return create( mask | ( 1L << offset ), this );
+ }
+
+ public OffsetSet add( OffsetSet offsets )
+ {
+ return create(mask | offsets.mask, this );
+ }
+
+ public OffsetSet filter( OffsetSet in )
+ {
+ long fmask = in.mask;
+ fmask = fmask ^ ( fmask & mask );
+ return create( fmask, in );
+ }
+
+ public static OffsetSet filterAndClose( OffsetSet in, OffsetSetHolder gateHolder, int timeDiff )
+ {
+ OffsetSet gate = gateHolder.getOffsetSet();
+ long gmask = gate.mask;
+
+ long fmask = in.mask;
+
+ // delete the high offsets with offset + timeDiff >= maxoffset
+ fmask = timeDiff > 31 ? 0L : ( fmask << timeDiff ) >> timeDiff;
+
+ fmask = fmask ^ ( fmask & gmask );
+
+ gmask |= fmask;
+
+ gateHolder.setOffsetSet( create( gmask, gate ) ); // modify the gate
+
+ if ( timeDiff > 0 )
+ {
+ fmask = fmask ^ ( fmask & (gmask >> timeDiff) );
+ }
+ return create( fmask, in );
+ }
+
+
+ @Override
+ public String toString()
+ {
+ if ( mask == -1L ) return "*";
+
+ StringBuilder sb = new StringBuilder();
+ int nbits = 0;
+ for( int i=0; i<65; i++ )
+ {
+ boolean bit = i < 64 ? ((1L << i) & mask) != 0L : false;
+ if ( bit ) nbits++;
+ else if ( nbits > 0)
+ {
+ if ( sb.length() > 0 ) sb.append( ',' );
+ if ( nbits == 1) sb.append( i-1 );
+ else sb.append( (i-nbits) + "-" + (i-1) );
+ nbits = 0;
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OffsetSetHolder.java b/brouter-mem-router/src/main/java/btools/memrouter/OffsetSetHolder.java
new file mode 100644
index 0000000..2ec1669
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/OffsetSetHolder.java
@@ -0,0 +1,13 @@
+/**
+ * Set off departure offsets (immutable)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+
+public interface OffsetSetHolder
+{
+ OffsetSet getOffsetSet();
+ void setOffsetSet( OffsetSet offsetSet );
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OsmLinkP.java b/brouter-mem-router/src/main/java/btools/memrouter/OsmLinkP.java
new file mode 100644
index 0000000..38933c0
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/OsmLinkP.java
@@ -0,0 +1,157 @@
+/**
+ * Container for link between two Osm nodes (pre-pocessor version)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+
+public class OsmLinkP implements OffsetSetHolder
+{
+ /**
+ * The description bitmap is mainly the way description
+ * used to calculate the costfactor
+ */
+ public byte[] descriptionBitmap;
+
+ /**
+ * The target is either the next link or the target node
+ */
+ protected OsmNodeP sourceNode;
+ protected OsmNodeP targetNode;
+
+ protected OsmLinkP previous;
+ protected OsmLinkP next;
+
+ public static int currentserial = 0; // serial version to invalidate link occupation
+ private int instanceserial = 0;
+
+ private OffsetSet offsets;
+ private int time;
+
+ public boolean isConnection()
+ {
+ return descriptionBitmap == null;
+ }
+
+ public boolean isWayLink()
+ {
+ return descriptionBitmap != null;
+ }
+
+ public OsmLinkP( OsmNodeP source, OsmNodeP target )
+ {
+ sourceNode = source;
+ targetNode = target;
+ }
+
+ protected OsmLinkP()
+ {
+ }
+
+ public OffsetSet getOffsetSet()
+ {
+ return offsets;
+ }
+
+ public void setOffsetSet( OffsetSet offsets )
+ {
+ this.offsets = offsets;
+ }
+
+ public boolean isVirgin()
+ {
+ return instanceserial != currentserial;
+ }
+
+ public OffsetSet filterAndClose( OffsetSet in, long arrival, boolean scheduled )
+ {
+ int minutesArrival = (int)(arrival/60000L);
+ if ( offsets == null || isVirgin() )
+ {
+ time = minutesArrival;
+ instanceserial = currentserial;
+ offsets = in;
+ return in;
+ }
+ return OffsetSet.filterAndClose( in, this, scheduled ? minutesArrival - time : 0 );
+ }
+
+
+ /**
+ * Set the relevant next-pointer for the given source
+ */
+ public void setNext( OsmLinkP link, OsmNodeP source )
+ {
+ if ( sourceNode == source )
+ {
+ next = link;
+ }
+ else if ( targetNode == source )
+ {
+ previous = link;
+ }
+ else
+ {
+ throw new IllegalArgumentException( "internal error: setNext: unknown source" );
+ }
+ }
+
+ /**
+ * Get the relevant next-pointer for the given source
+ */
+ public OsmLinkP getNext( OsmNodeP source )
+ {
+ if ( sourceNode == source )
+ {
+ return next;
+ }
+ else if ( targetNode == source )
+ {
+ return previous;
+ }
+ else
+ {
+ throw new IllegalArgumentException( "internal error: gextNext: unknown source" );
+ }
+ }
+
+ /**
+ * Get the relevant target-node for the given source
+ */
+ public OsmNodeP getTarget( OsmNodeP source )
+ {
+ if ( sourceNode == source )
+ {
+ return targetNode;
+ }
+ else if ( targetNode == source )
+ {
+ return sourceNode;
+ }
+ else
+ {
+ throw new IllegalArgumentException( "internal error: getTarget: unknown source" );
+ }
+ }
+
+ /**
+ * Check if reverse link for the given source
+ */
+ public boolean isReverse( OsmNodeP source )
+ {
+ if ( sourceNode == source )
+ {
+ return false;
+ }
+ else if ( targetNode == source )
+ {
+ return true;
+ }
+ else
+ {
+ throw new IllegalArgumentException( "internal error: isReverse: unknown source" );
+ }
+ }
+
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java
new file mode 100644
index 0000000..a5e8dbb
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodeP.java
@@ -0,0 +1,150 @@
+/**
+ * Container for an osm node (pre-pocessor version)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import btools.mapaccess.OsmPos;
+
+public class OsmNodeP extends OsmLinkP implements Comparable, OsmPos
+{
+ public OsmNodeP( double dlon, double dlat )
+ {
+ ilon = (int)(dlon * 1000000 + 180000000);
+ ilat = (int)(dlat * 1000000 + 90000000);
+ }
+ public OsmNodeP()
+ {
+ }
+
+ /**
+ * The latitude
+ */
+ public int ilat;
+
+ /**
+ * The longitude
+ */
+ public int ilon;
+
+
+ /**
+ * The elevation
+ */
+ public short selev;
+
+ public final static int NO_BRIDGE_BIT = 1;
+ public final static int NO_TUNNEL_BIT = 2;
+
+ public byte wayBits = 0;
+
+ // interface OsmPos
+ @Override
+ public int getILat()
+ {
+ return ilat;
+ }
+
+ @Override
+ public int getILon()
+ {
+ return ilon;
+ }
+
+ @Override
+ public short getSElev()
+ {
+ // if all bridge or all tunnel, elevation=no-data
+ return ( wayBits & NO_BRIDGE_BIT ) == 0 || ( wayBits & NO_TUNNEL_BIT ) == 0 ? Short.MIN_VALUE : selev;
+ }
+
+ @Override
+ public double getElev()
+ {
+ return selev / 4.;
+ }
+
+ // populate and return the inherited link, if available,
+ // else create a new one
+ public OsmLinkP createLink( OsmNodeP source )
+ {
+ if ( sourceNode == null && targetNode == null )
+ {
+ // inherited instance is available, use this
+ sourceNode = source;
+ targetNode = this;
+ source.addLink( this );
+ return this;
+ }
+ OsmLinkP link = new OsmLinkP( source, this );
+ addLink( link );
+ source.addLink( link );
+ return link;
+ }
+
+
+ // memory-squeezing-hack: OsmLinkP's "previous" also used as firstlink..
+
+ public void addLink( OsmLinkP link )
+ {
+ link.setNext( previous, this );
+ previous = link;
+ }
+
+ public OsmLinkP getFirstLink()
+ {
+ return sourceNode == null && targetNode == null ? previous : this;
+ }
+
+ // interface OsmPos
+
+ @Override
+ public int calcDistance( OsmPos p )
+ {
+ double l = (ilat-90000000) * 0.00000001234134;
+ double l2 = l*l;
+ double l4 = l2*l2;
+ double coslat = 1.- l2 + l4 / 6.;
+
+ double dlat = (ilat - p.getILat() )/1000000.;
+ double dlon = (ilon - p.getILon() )/1000000. * coslat;
+ double d = Math.sqrt( dlat*dlat + dlon*dlon ) * (6378000. / 57.3);
+ return (int)(d + 1.0 );
+ }
+
+ @Override
+ public long getIdFromPos()
+ {
+ return ((long)ilon)<<32 | ilat;
+ }
+
+ public byte[] getNodeDecsription()
+ {
+ return null;
+ }
+
+
+ public String toString2()
+ {
+ return (ilon-180000000) + "_" + (ilat-90000000) + "_" + (selev/4);
+ }
+
+
+
+ /**
+ * Compares two OsmNodes for position ordering.
+ *
+ * @return -1,0,1 depending an comparson result
+ */
+ public int compareTo( OsmNodeP n )
+ {
+ long id1 = getIdFromPos();
+ long id2 = n.getIdFromPos();
+ if ( id1 < id2 ) return -1;
+ if ( id1 > id2 ) return 1;
+ return 0;
+ }
+
+
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/OsmNodePT.java b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodePT.java
new file mode 100644
index 0000000..2a6d27f
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/OsmNodePT.java
@@ -0,0 +1,27 @@
+/**
+ * Container for an osm node with tags (pre-pocessor version)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+
+public class OsmNodePT extends OsmNodeP
+{
+ public byte[] descriptionBits;
+
+ public OsmNodePT()
+ {
+ }
+
+ public OsmNodePT( byte[] descriptionBits )
+ {
+ this.descriptionBits = descriptionBits;
+ }
+
+ @Override
+ public final byte[] getNodeDecsription()
+ {
+ return descriptionBits;
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduleParser.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduleParser.java
new file mode 100644
index 0000000..681420b
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduleParser.java
@@ -0,0 +1,156 @@
+/**
+ * Parser for a train schedule
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import btools.expressions.BExpressionContextWay;
+
+
+final class ScheduleParser
+{
+ public static void parseTrainTable( File[] files, GraphLoader graph, BExpressionContextWay expctxWay )
+ {
+ try
+ {
+ ScheduledLine currentLine = null;
+ StationNode lastStationInLine = null;
+
+ boolean readingLocations = false;
+
+ Map stationMap = new HashMap();
+
+
+ for( File file : files )
+ {
+ BufferedReader br = new BufferedReader( new FileReader( file ) );
+ for(;;)
+ {
+ String line = br.readLine();
+ if ( line == null ) break;
+ line = line.trim();
+ if ( line.length() == 0 ) continue;
+
+ if ( line.startsWith( "#" ) ) continue;
+
+ if ( line.startsWith( "-- locations" ) )
+ {
+ readingLocations = true;
+ continue;
+ }
+ if ( line.startsWith( "-- trainline" ) )
+ {
+ readingLocations = false;
+ currentLine = new ScheduledLine();
+ currentLine.name = line.substring("-- trainline".length() ).trim();
+ lastStationInLine = null;
+ continue;
+ }
+ if ( readingLocations )
+ {
+ StationNode station = new StationNode();
+
+ // Eschborn 50.14323,8.56112
+ StringTokenizer tk = new StringTokenizer( line, " " );
+ station.name = tk.nextToken();
+
+ if ( stationMap.containsKey( station.name ) )
+ {
+ System.out.println( "skipping station name already known: " + station.name );
+ continue;
+ }
+
+ int locIdx = 0;
+ String loc = null;
+ int elev = 0;
+ int nconnections = 0;
+ while( tk.hasMoreTokens() || locIdx == 1 )
+ {
+ if ( tk.hasMoreTokens() )
+ {
+ loc = tk.nextToken();
+ }
+ StringTokenizer tloc = new StringTokenizer( loc, "," );
+ int ilat = (int)( ( Double.parseDouble( tloc.nextToken() ) + 90. ) *1000000. + 0.5);
+ int ilon = (int)( ( Double.parseDouble( tloc.nextToken() ) + 180. ) *1000000. + 0.5);
+ if ( locIdx == 0 )
+ {
+ station.ilat = ilat;
+ station.ilon = ilon;
+ }
+ else
+ {
+ OsmNodeP pos = new OsmNodeP();
+ pos.ilat = ilat;
+ pos.ilon = ilon;
+
+ OsmNodeP node = graph.matchNodeForPosition( pos,expctxWay );
+ if ( node != null )
+ {
+ elev += node.selev;
+ nconnections++;
+
+ // link station to connecting node
+ OsmLinkP link = new OsmLinkP( station, node );
+ link.descriptionBitmap = null;
+ station.addLink( link );
+ node.addLink( link );
+
+ int distance = station.calcDistance( node );
+ System.out.println( "matched connection for station " + station.name + " at " + distance + " meter" );
+ }
+ }
+ locIdx++;
+ }
+ if ( nconnections > 0 )
+ {
+ station.selev = (short)(elev / nconnections);
+ }
+ stationMap.put( station.name, station );
+ }
+ else if ( currentLine != null )
+ {
+ int idx = line.indexOf( ' ' );
+ String name = line.substring( 0, idx );
+ StationNode nextStationInLine = stationMap.get( name );
+ String value = line.substring( idx ).trim();
+ int offsetMinute = 0;
+ if ( lastStationInLine == null )
+ {
+ currentLine.schedule = new TrainSchedule( value );
+ }
+ else
+ {
+ if ( value.startsWith( "+") ) value = value.substring( 1 );
+ offsetMinute = Integer.parseInt( value );
+
+ ScheduledLink link = new ScheduledLink( lastStationInLine, nextStationInLine );
+ link.line = currentLine;
+ link.indexInLine = currentLine.offsetMinutes.size()-1;
+
+System.out.println( "adding: " + link );
+ lastStationInLine.addLink( link );
+ }
+ currentLine.offsetMinutes.add( Integer.valueOf( offsetMinute ) );
+
+ lastStationInLine = nextStationInLine;
+ }
+ }
+ br.close();
+ System.out.println( "read " + stationMap.size() + " stations" );
+ }
+ }
+ catch( Exception e )
+ {
+ throw new RuntimeException( e );
+ }
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledDeparture.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledDeparture.java
new file mode 100644
index 0000000..81b6821
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledDeparture.java
@@ -0,0 +1,22 @@
+/**
+ * A specific departure
+ * (relative to a certain station and line)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+
+
+final class ScheduledDeparture
+{
+ long waitTime;
+ long rideTime;
+ OffsetSet offsets;
+
+ @Override
+ public String toString()
+ {
+ return "wait=" + waitTime + " ride=" + rideTime + " offsets=" + offsets;
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledLine.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledLine.java
new file mode 100644
index 0000000..78054ca
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledLine.java
@@ -0,0 +1,103 @@
+/**
+ * A train line as a common set of stations with many departures
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+final class ScheduledLine
+{
+ String name;
+ List offsetMinutes = new ArrayList();
+ TrainSchedule schedule;
+
+
+ /**
+ * get a list of departures relative to the start-time plus
+ * the individual offsets according to the offset mask
+ *
+ * departures with the same wait-time are aggregated in one
+ * result element with multiple 1-bits in the offset mask
+ *
+ * departures with different wait-times are returned as separate items
+ *
+ * @param id the value to add to this set.
+ * @return true if "id" already contained in this set.
+ */
+ public List getScheduledDepartures( int idx, long timeFrom, OffsetSet offsets )
+ {
+ List result = new ArrayList();
+
+ long minutesFrom = (timeFrom + 59999L) / 60000L;
+ long timeFromCorrection = minutesFrom * 60000L - timeFrom;
+
+ if ( idx < 0 || idx >= offsetMinutes.size() -1 ) return result;
+
+ int offsetStart = offsetMinutes.get(idx).intValue();
+ int offsetEnd = offsetMinutes.get(idx+1).intValue();
+
+
+ Map> waitOffsets = getDepartures( offsetStart, timeFrom + timeFromCorrection, offsets );
+
+ for( Map.Entry> e : waitOffsets.entrySet() )
+ {
+ ScheduledDeparture depart = new ScheduledDeparture( );
+ depart.waitTime = e.getKey().intValue() * 60000L + timeFromCorrection;
+ depart.offsets = OffsetSet.create( e.getValue(), offsets );
+ depart.rideTime = (offsetEnd-offsetStart)*60000L;
+ result.add( depart );
+ }
+ return result;
+ }
+
+ private Map> getDepartures( int offsetStart, long timeFrom, OffsetSet offsets )
+ {
+ Map> waitOffsets = new HashMap>();
+ int size = offsets.size();
+
+ for(int offset = 0;;)
+ {
+ // skip to next offset bit
+ while( offset < size && !offsets.contains( offset ) )
+ {
+ offset++;
+ }
+ if ( offset >= size ) return waitOffsets;
+
+ int toNext = schedule.getMinutesToNext( timeFrom + 60000L*(offset - offsetStart ) );
+ if ( toNext < 0 ) return waitOffsets;
+ int departOffset = offset + toNext;
+
+ // whats the closest offset within the next toNext minutes
+ int lastOffset = offset;
+ while( toNext-- >= 0 && offset < size )
+ {
+ if ( offsets.contains( offset ) )
+ {
+ lastOffset = offset;
+ }
+ offset++;
+ }
+
+ if ( lastOffset == size-1 ) return waitOffsets; // todo?
+
+ int waitTime = departOffset - lastOffset;
+
+ // if we have that wait time in the list, just add the offset bit
+ List offsetList = waitOffsets.get( Integer.valueOf( waitTime ) );
+ if ( offsetList == null )
+ {
+ offsetList = new ArrayList();
+ waitOffsets.put( Integer.valueOf( waitTime ), offsetList );
+ }
+ offsetList.add( Integer.valueOf( lastOffset ) );
+ }
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledLink.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledLink.java
new file mode 100644
index 0000000..84bef12
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledLink.java
@@ -0,0 +1,33 @@
+/**
+ * Container for link between two Osm nodes (pre-pocessor version)
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+
+public class ScheduledLink extends OsmLinkP
+{
+ public ScheduledLink( StationNode source, StationNode target )
+ {
+ super( source, target );
+ }
+
+ public ScheduledLine line;
+ public int indexInLine;
+
+ public boolean isConnection()
+ {
+ return false;
+ }
+
+ public boolean isWayLink()
+ {
+ return false;
+ }
+
+ public String toString()
+ {
+ return "ScheduledLink: line=" + line.name + " indexInLine=" + indexInLine;
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java
new file mode 100644
index 0000000..30cff22
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledRouter.java
@@ -0,0 +1,421 @@
+/**
+ * Simple Train Router
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import btools.mapaccess.OsmPos;
+import btools.router.OsmPathElement;
+import btools.router.OsmTrack;
+import btools.router.RoutingContext;
+import btools.router.RoutingEngine;
+import btools.util.SortedHeap;
+
+
+final class ScheduledRouter
+{
+ private GraphLoader graph;
+
+ private int solutionCount = 0;
+
+ public long linksProcessed = 0L;
+ public long linksReProcessed = 0L;
+ public long closedSkippedChained = 0L;
+ public long skippedChained = 0L;
+
+ private RoutingContext rc;
+ private RoutingEngine re;
+
+ private long time0;
+ private OsmNodeP start;
+ private OsmNodeP end;
+
+ SortedHeap openSet = new SortedHeap();
+
+ ScheduledRouter( GraphLoader graph, RoutingContext rc, RoutingEngine re )
+ {
+ this.graph = graph;
+ this.rc = rc;
+ this.re = re;
+ }
+
+
+ public OsmTrack findRoute( OsmPos startPos, OsmPos endPos, String startTime, int alternativeIdx ) throws Exception
+ {
+ OsmTrack track = null;
+
+ start = graph.matchNodeForPosition( startPos, rc.expctxWay );
+ if ( start == null ) throw new IllegalArgumentException( "unmatched start: " + startPos );
+ end = graph.matchNodeForPosition( endPos, rc.expctxWay );
+ if ( end == null ) throw new IllegalArgumentException( "unmatched end: " + endPos );
+
+// SimpleDateFormat df = new SimpleDateFormat( "dd.MM.yyyy-HH:mm" );
+// time0 = df.parse(startTime).getTime();
+time0 = System.currentTimeMillis() + (long)(rc.starttimeoffset * 60000L );
+long minutes0 = (time0 + 59999L) / 60000L;
+time0 = minutes0 * 60000L;
+
+ OffsetSet finishedOffsets = OffsetSet.emptySet();
+
+ OsmLinkP startLink = new OsmLinkP( null, start );
+
+ ScheduledTrip startTrip = new ScheduledTrip( OffsetSet.fullSet(), startLink, null, null );
+ openSet.add( 0, startTrip );
+ for(;;)
+ {
+ if ( re.isTerminated() )
+ {
+ throw new RuntimeException( "operation terminated" );
+ }
+ if ( linksProcessed + linksReProcessed > 5000000 )
+ {
+ throw new RuntimeException( "5 Million links limit reached" );
+ }
+
+ // get cheapest trip from heap
+ ScheduledTrip trip = openSet.popLowestKeyValue();
+ if ( trip == null )
+ {
+ break;
+ }
+ OsmLinkP currentLink = trip.link;
+ OsmNodeP currentNode = trip.getTargetNode();
+ if ( currentNode == null )
+ {
+ System.out.println( "ups: " + trip );
+ continue;
+ }
+
+ if ( currentLink.isVirgin() )
+ {
+ linksProcessed++;
+ }
+ else
+ {
+ linksReProcessed++;
+ }
+
+ // check global closure
+ OffsetSet offsets = finishedOffsets.filter( trip.offsets );
+ if ( offsets == null ) continue;
+
+ // check local closure for links:
+ offsets = currentLink.filterAndClose( offsets, trip.arrival, currentLink instanceof ScheduledLink );
+ if ( offsets == null ) continue;
+
+
+ // check for arrival
+ if ( currentNode == end )
+ {
+ for( int offset = 0; offset= alternativeIdx ) return track;
+ }
+ for( OsmLinkP link = currentNode.getFirstLink(); link != null; link = link.getNext( currentNode ) )
+ {
+ addNextTripsForLink(trip, currentNode, currentLink, link, offsets, 0 );
+ }
+ }
+ return track;
+ }
+
+ private void addToOpenSet( ScheduledTrip nextTrip )
+ {
+ int distance = nextTrip.getTargetNode().calcDistance( end );
+ nextTrip.adjustedCost = nextTrip.cost + (int)(distance * rc.pass1coefficient + 0.5);
+ openSet.add( nextTrip.adjustedCost, nextTrip );
+ }
+
+
+ private void addNextTripsForLink( ScheduledTrip trip, OsmNodeP currentNode, OsmLinkP currentLink, OsmLinkP link, OffsetSet offsets, int level )
+ {
+ if ( link == currentLink )
+ {
+ return; // just reverse, ignore
+ }
+ OsmNodeP node = link.getTarget(currentNode);
+ if ( node == null )
+ {
+ System.out.println( "ups2: " + link );
+ return;
+ }
+
+ // calc distance and check nogos
+ rc.nogomatch = false;
+ int distance = rc.calcDistance( currentNode.ilon, currentNode.ilat, node.ilon, node.ilat );
+ if ( rc.nogomatch )
+ {
+ return;
+ }
+
+ if ( link instanceof ScheduledLink )
+ {
+// System.out.println( "next trip for link: " + link + " at offset " + offsets );
+
+ ScheduledLink slink = (ScheduledLink)link;
+ ScheduledLine line = slink.line;
+
+ // line change delay
+ long delay = 0L;
+ if ( currentLink instanceof ScheduledLink )
+ {
+ delay = ((ScheduledLink)currentLink).line == line ? 0L : (long)(rc.changetime * 1000.); // 3 minutes
+ }
+ long changePenalty = delay > 0 ? 60000L : 0L;
+
+ List nextDepartures = line.getScheduledDepartures( slink.indexInLine, time0 + trip.arrival + delay, offsets );
+ for( ScheduledDeparture nextDeparture : nextDepartures )
+ {
+ ScheduledTrip nextTrip = new ScheduledTrip( nextDeparture.offsets, link, currentNode, trip );
+ long waitTime = nextDeparture.waitTime + delay;
+ long rideTime = nextDeparture.rideTime;
+
+ nextTrip.cost = trip.cost + (int)( ( rideTime + changePenalty + waitTime*rc.waittimeadjustment ) * rc.cost1speed / 3600. ); // 160ms / meter = 22km/h
+ nextTrip.departure = trip.arrival + waitTime;
+ nextTrip.arrival = nextTrip.departure + rideTime;
+
+ addToOpenSet( nextTrip );
+
+// System.out.println( "found: " + nextTrip );
+ }
+ }
+ else if ( link.isWayLink() )
+ {
+ // get costfactor
+ rc.expctxWay.evaluate( link.isReverse(currentNode), link.descriptionBitmap, null );
+
+ // *** penalty for distance
+ float costfactor = rc.expctxWay.getCostfactor();
+ if ( costfactor > 9999. )
+ {
+ return;
+ }
+ int waycost = (int)(distance * costfactor + 0.5f);
+
+ // *** add initial cost if factor changed
+ float costdiff = costfactor - trip.lastcostfactor;
+ if ( costdiff > 0.0005 || costdiff < -0.0005 )
+ {
+ waycost += (int)rc.expctxWay.getInitialcost();
+ }
+
+
+ if ( node.getNodeDecsription() != null )
+ {
+ rc.expctxNode.evaluate( rc.expctxWay.getNodeAccessGranted() != 0. , node.getNodeDecsription(), null );
+ float initialcost = rc.expctxNode.getInitialcost();
+ if ( initialcost >= 1000000. )
+ {
+ return;
+ }
+ waycost += (int)initialcost;
+ }
+
+ // *** penalty for turning angles
+ 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 );
+ int turncost = (int)(cos * rc.expctxWay.getTurncost() + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty
+ waycost += turncost;
+ }
+
+ ScheduledTrip nextTrip = new ScheduledTrip( offsets, link, currentNode, trip );
+
+ // *** penalty for elevation
+ short ele2 = node.selev;
+ short ele1 = trip.selev;
+ int elefactor = 250000;
+ if ( ele2 == Short.MIN_VALUE ) ele2 = ele1;
+ nextTrip.selev = ele2;
+ if ( ele1 != Short.MIN_VALUE )
+ {
+ nextTrip.ehbd = trip.ehbd + (ele1 - ele2)*elefactor - distance * rc.downhillcutoff;
+ nextTrip.ehbu = trip.ehbu + (ele2 - ele1)*elefactor - distance * rc.uphillcutoff;
+ }
+
+ if ( nextTrip.ehbd > rc.elevationpenaltybuffer )
+ {
+ int excess = nextTrip.ehbd - rc.elevationpenaltybuffer;
+ int reduce = distance * rc.elevationbufferreduce;
+ if ( reduce > excess )
+ {
+ reduce = excess;
+ }
+ excess = nextTrip.ehbd - rc.elevationmaxbuffer;
+ if ( reduce < excess )
+ {
+ reduce = excess;
+ }
+ nextTrip.ehbd -= reduce;
+ if ( rc.downhillcostdiv > 0 )
+ {
+ int elevationCost = reduce/rc.downhillcostdiv;
+ waycost += elevationCost;
+ }
+ }
+ else if ( nextTrip.ehbd < 0 )
+ {
+ nextTrip.ehbd = 0;
+ }
+
+ if ( nextTrip.ehbu > rc.elevationpenaltybuffer )
+ {
+ int excess = nextTrip.ehbu - rc.elevationpenaltybuffer;
+ int reduce = distance * rc.elevationbufferreduce;
+ if ( reduce > excess )
+ {
+ reduce = excess;
+ }
+ excess = nextTrip.ehbu - rc.elevationmaxbuffer;
+ if ( reduce < excess )
+ {
+ reduce = excess;
+ }
+ nextTrip.ehbu -= reduce;
+ if ( rc.uphillcostdiv > 0 )
+ {
+ int elevationCost = reduce/rc.uphillcostdiv;
+ waycost += elevationCost;
+ }
+ }
+ else if ( nextTrip.ehbu < 0 )
+ {
+ nextTrip.ehbu = 0;
+ }
+
+ nextTrip.lastcostfactor = costfactor;
+ nextTrip.cost = trip.cost + (int)(waycost*rc.additionalcostfactor + 0.5);
+ nextTrip.departure = trip.arrival;
+ nextTrip.arrival = nextTrip.departure + (long) ( waycost * 3600. / rc.cost1speed ); // 160ms / meter = 22km/h
+
+ addToOpenSet( nextTrip );
+ }
+ else // connecting link
+ {
+ ScheduledTrip nextTrip = new ScheduledTrip( offsets, link, currentNode, trip );
+
+ long delay = (long)(rc.buffertime * 1000.); // 2 min
+ nextTrip.cost = trip.cost + (int)( delay*rc.waittimeadjustment * rc.cost1speed / 3600. );
+ nextTrip.departure = trip.arrival;
+ nextTrip.arrival = nextTrip.departure + delay;
+
+ addToOpenSet( nextTrip );
+ }
+ }
+
+ private OsmTrack compileTrip( ScheduledTrip trip, int offset )
+ {
+ OsmTrack track = new OsmTrack();
+ track.iternity = new ArrayList();
+ ScheduledTrip current = trip;
+ ScheduledLine lastLine = new ScheduledLine();
+ ScheduledLine dummyLine = new ScheduledLine();
+ List list = new ArrayList();
+
+ int distance = 0;
+
+ ScheduledTrip itrip = null;
+
+ String profile = extractProfile( rc.localFunction );
+
+ OsmNodeP nextNode = null;
+ while( current != null )
+ {
+System.out.println( "trip=" + current );
+ OsmNodeP node = current.getTargetNode();
+ OsmPathElement pe = new OsmPathElement(node.ilon, node.ilat, node.selev, null );
+ track.addNode(pe);
+
+ if ( nextNode != null )
+ {
+ distance += node.calcDistance( nextNode );
+ }
+
+ boolean isScheduled = current.link instanceof ScheduledLink;
+ boolean isConnection = current.link.descriptionBitmap == null && !isScheduled;
+ ScheduledLine line = isScheduled ? ((ScheduledLink)current.link).line : isConnection ? dummyLine : null;
+
+ if ( line != lastLine && !isConnection )
+ {
+ itrip = new ScheduledTrip();
+ itrip.departure = current.departure;
+ itrip.arrival = current.arrival;
+ itrip.originNode = current.originNode;
+ itrip.link = current.link;
+
+ if ( isScheduled && list.size() > 0 )
+ {
+ list.get( list.size()-1 ).originNode = current.getTargetNode();
+ }
+ list.add(itrip);
+ }
+ else if ( itrip != null && !isConnection )
+ {
+ itrip.departure = current.departure;
+ itrip.originNode = current.originNode;
+ }
+ lastLine = line;
+ current = current.origin;
+ nextNode = node;
+ }
+ track.distance = distance;
+ track.cost = trip.cost;
+
+ for( int i=list.size()-1; i>=0; i-- )
+ {
+ current = list.get(i);
+ String lineName = profile;
+
+ boolean isScheduled = current.link instanceof ScheduledLink;
+ if ( isScheduled )
+ {
+ lineName = ((ScheduledLink)current.link).line.name;
+ }
+ String stationName = "*position*";
+ if ( current.originNode instanceof StationNode )
+ {
+ stationName = ((StationNode)current.originNode).name;
+ }
+ String nextStationName = "*position*";
+ if ( i > 0 && list.get(i-1).originNode instanceof StationNode )
+ {
+ nextStationName = ((StationNode)list.get(i-1).originNode).name;
+ }
+ {
+ Date d0 = new Date( time0 + 60000L * offset + current.departure );
+ Date d1 = new Date( time0 + 60000L * offset + current.arrival );
+ if ( track.iternity.size() > 0 ) track.iternity.add( "" );
+ track.iternity.add( "depart: " + d0 + " " + stationName );
+ track.iternity.add( " --- " + lineName + " ---" );
+ track.iternity.add( "arrive: " + d1 + " " + nextStationName );
+ }
+ }
+
+ return track;
+ }
+
+ private String extractProfile( String s )
+ {
+ int idx = s.lastIndexOf( '/' );
+ if ( idx >= 0 ) s = s.substring( idx+1 );
+ idx = s.indexOf( '.' );
+ if ( idx >= 0 ) s = s.substring( 0,idx );
+ return s;
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/ScheduledTrip.java b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledTrip.java
new file mode 100644
index 0000000..9756b4f
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/ScheduledTrip.java
@@ -0,0 +1,64 @@
+/**
+ * Simple Train Router
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+
+
+
+public final class ScheduledTrip
+{
+ public OffsetSet offsets;
+
+ ScheduledTrip origin;
+ OsmLinkP link;
+ OsmNodeP originNode;
+ int cost; // in meter!
+ int adjustedCost; // in meter!
+ long arrival; // in millis!
+ long departure; // in millis!
+
+ public float lastcostfactor;
+
+ public int ehbd; // in micrometer
+ public int ehbu; // in micrometer
+ public short selev = Short.MIN_VALUE;
+
+ ScheduledTrip()
+ {
+ // dummy for OpenSetM
+ }
+
+ ScheduledTrip( OffsetSet offsets, OsmLinkP link, OsmNodeP originNode, ScheduledTrip origin )
+ {
+ this.offsets = offsets;
+ this.link = link;
+ this.origin = origin;
+ this.originNode = originNode;
+ }
+
+ public OsmNodeP getTargetNode()
+ {
+ return link.getTarget(originNode);
+ }
+
+ @Override
+ public String toString()
+ {
+ String prefix = "PlainLink";
+ if ( link instanceof ScheduledLink )
+ {
+ ScheduledLink l = (ScheduledLink)link;
+ ScheduledLine line = l.line;
+ prefix = "ScheduledLink: line=" + line.name;
+ }
+ else if ( link.isConnection() )
+ {
+ prefix = "ConnectingLink";
+ }
+
+ return prefix + " depart=" + departure + " arrival=" + arrival + " cost=" + cost + " offsets=" + offsets;
+ }
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/StationNode.java b/brouter-mem-router/src/main/java/btools/memrouter/StationNode.java
new file mode 100644
index 0000000..a2054b9
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/StationNode.java
@@ -0,0 +1,25 @@
+/**
+ * A train station and it's connections
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+
+final class StationNode extends OsmNodeP
+{
+ String name;
+}
diff --git a/brouter-mem-router/src/main/java/btools/memrouter/TrainSchedule.java b/brouter-mem-router/src/main/java/btools/memrouter/TrainSchedule.java
new file mode 100644
index 0000000..718e9a0
--- /dev/null
+++ b/brouter-mem-router/src/main/java/btools/memrouter/TrainSchedule.java
@@ -0,0 +1,146 @@
+/**
+ * Information on matched way point
+ *
+ * @author ab
+ */
+package btools.memrouter;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.StringTokenizer;
+
+
+
+final class TrainSchedule
+{
+ private static class TrainScheduleCron
+ {
+ long minutes;
+ long hours;
+ long dows;
+
+ boolean isNegative;
+
+ String cronSource;
+
+ TrainScheduleCron( String value )
+ {
+ StringTokenizer tk = new StringTokenizer( value, "_" );
+ minutes = parseElement( tk.nextToken() );
+ hours = parseElement( tk.nextToken() );
+ dows = parseElement( tk.nextToken() );
+
+ cronSource = value;
+ }
+
+ private long parseElement( String s )
+ {
+ if ( "*".equals( s ) ) return Long.MAX_VALUE;
+ StringTokenizer tk = new StringTokenizer( s, "," );
+ long res = 0;
+ while( tk.hasMoreTokens() )
+ {
+ String sub = tk.nextToken();
+ int start, end;
+ int idx = sub.indexOf( '-' );
+ if ( idx < 0 )
+ {
+ start = Integer.parseInt( sub );
+ end = start;
+ }
+ else
+ {
+ start = Integer.parseInt( sub.substring( 0, idx ) );
+ end = Integer.parseInt( sub.substring( idx + 1) );
+ }
+ for( int i=start; i <= end ; i++ )
+ {
+ res |= (1L< cronsPositive;
+ private List cronsNegative;
+
+ public TrainSchedule( String cronstring )
+ {
+ StringTokenizer tk = new StringTokenizer( cronstring, " " );
+
+ cronsPositive = new ArrayList();
+ cronsNegative = new ArrayList();
+
+ while ( tk.hasMoreTokens() )
+ {
+ String sign = tk.nextToken();
+ String value = tk.nextToken();
+ TrainScheduleCron cron = new TrainScheduleCron( value );
+ if ( "+".equals( sign ) )
+ {
+ cronsPositive.add( cron );
+ }
+ else if ( "-".equals( sign ) )
+ {
+ cronsNegative.add( cron );
+ }
+ else throw new IllegalArgumentException( "invalid cron sign: " + sign );
+ }
+ }
+
+
+
+ public int getMinutesToNext( long timeFrom )
+ {
+ Calendar cal = Calendar.getInstance();
+ cal.setTime( new Date( timeFrom ) );
+ int minute = cal.get( Calendar.MINUTE );
+ int hour = cal.get( Calendar.HOUR_OF_DAY );
+ int dow = cal.get( Calendar.DAY_OF_WEEK );
+ dow = dow > 1 ? dow -1 : dow+6;
+
+ for( int cnt=0; cnt < 10080; cnt++ )
+ {
+ boolean veto = false;
+ for( TrainScheduleCron cron : cronsNegative )
+ {
+ if ( cron.matches( minute, hour, dow ) )
+ {
+ veto = true;
+ break;
+ }
+ }
+ if ( !veto )
+ {
+ for( TrainScheduleCron cron : cronsPositive )
+ {
+ if ( cron.matches( minute, hour, dow ) ) return cnt;
+ }
+ }
+
+ if ( ++minute == 60 )
+ {
+ minute = 0;
+ if ( ++hour == 24 )
+ {
+ hour = 0;
+ if ( ++dow == 8 )
+ {
+ dow = 1;
+ }
+ }
+ }
+ }
+ return -1;
+ }
+}
diff --git a/brouter-mem-router/src/test/java/btools/memrouter/MemrouterTest.java b/brouter-mem-router/src/test/java/btools/memrouter/MemrouterTest.java
new file mode 100644
index 0000000..1fc8bea
--- /dev/null
+++ b/brouter-mem-router/src/test/java/btools/memrouter/MemrouterTest.java
@@ -0,0 +1,41 @@
+package btools.memrouter;
+
+import java.util.Random;
+import java.util.HashMap;
+
+import org.junit.Assert;
+import org.junit.Test;
+import java.net.URL;
+import java.io.File;
+
+import btools.expressions.BExpressionContextWay;
+import btools.expressions.BExpressionMetaData;
+
+public class MemrouterTest
+{
+ @Test
+ public void memrouterTest() throws Exception
+ {
+ URL dummyurl = this.getClass().getResource( "/dummy.txt" );
+ Assert.assertTrue( "dummy.txt not found", dummyurl != null );
+ File workingDir = new File(dummyurl.getFile()).getParentFile();
+ File profileDir = new File( workingDir, "/../../../misc/profiles2" );
+ File dataDir = new File( workingDir, "/../../../brouter-map-creator/target/test-classes/tmp" );
+ File lookupFile = new File( profileDir, "lookups.dat" );
+ File profileFile = new File( profileDir, "trekking.brf" );
+ File waytiles55 = new File( dataDir, "waytiles55" );
+ File unodes55 = new File( dataDir, "unodes55" );
+ File[] fahrplanFiles = new File[1];
+ fahrplanFiles[0] = new File( workingDir, "fahrplan.txt" );
+
+ // read lookup + profile for lookup-version + access-filter
+ BExpressionMetaData meta = new BExpressionMetaData();
+ BExpressionContextWay expctxWay = new BExpressionContextWay(meta);
+ meta.readMetaData( lookupFile );
+ expctxWay.parseFile( profileFile, "global" );
+
+
+ // run GraphLoader
+ new GraphLoader().process( unodes55, waytiles55, fahrplanFiles, expctxWay );
+ }
+}
diff --git a/brouter-mem-router/src/test/resources/dummy.txt b/brouter-mem-router/src/test/resources/dummy.txt
new file mode 100644
index 0000000..e69de29
diff --git a/brouter-mem-router/src/test/resources/fahrplan.txt b/brouter-mem-router/src/test/resources/fahrplan.txt
new file mode 100644
index 0000000..4ffaa26
--- /dev/null
+++ b/brouter-mem-router/src/test/resources/fahrplan.txt
@@ -0,0 +1,546 @@
+-- locations
+KoblenzHbf 50.35066,7.58997
+Frankfurt(M)Hbf 50.10676,8.66323 50.10754,8.66107 50.10596,8.66448 50.10765,8.66566
+Frankfurt/Hoechst 50.10271,8.54212 50.10363,8.54240 50.10186,8.54298
+Bensheim-Auerbach 49.70216,8.61371
+Bensheim 49.68108,8.61724
+F-Niederrad 50.08096,8.63768
+Walldorf 50.00159,8.58155
+Moerfelden 49.97940,8.56534
+Gross-G-Dornberg 49.91241,8.49420
+Riedstatt-Goddelau 49.83462,8.49087
+Stockstadt-Rhein 49.80906,8.47207
+Biebesheim 49.78091,8.47350
+Gernsheim 49.75359,8.49062
+Gross-Rohrheim 49.71415,8.47826
+Biblis 49.68821,8.45130
+Buerstadt 49.64535,8.45888
+Lampertheim 49.59978,8.47715
+Mannheim-Waldhof 49.52538,8.48217
+MannheimHbf 49.47983,8.47003
+WiesbadenHbf 50.07118,8.24377
+MainzHbf 50.00159,8.25939
+Mainz-Roemisches-Th 49.99350,8.27852
+Mainz-Bischofsheim 49.99228,8.35835
+Nauheim 49.94192,8.45068
+Gross-Gerau 49.92421,8.48589
+Klein-Gerau 49.92004,8.51734
+Weiterstadt 49.91027,8.57899
+DarmstadtHbf 49.87254,8.63142
+Darmstadt-Sued 49.85558,8.63703
+Wiesbaden-Ost 50.04053,8.25616
+Mainz-Nord 50.01062,8.24626
+Mainz-Gustavsburg 49.99454,8.31414
+Ruesselsheim-Opel 49.98780,8.40201
+Ruesselsheim 49.99205,8.41375
+Raunheim 50.01000,8.45704
+Kelsterbach 50.06329,8.53006
+F-Stadion 50.06778,8.63561
+Bad-Soden 50.14283,8.50455 50.14339,8.50457 50.14308,8.50359
+Sulzbach-Nord 50.13905,8.51788 50.13812,8.51823
+Schwalbach-Limes 50.15444,8.52819 50.15485,8.52801
+Schwalbach-Nord 50.15976,8.53459 50.15965,8.53487
+Niederhoechsstadt 50.15439,8.54707 50.15476,8.54627
+Eschborn 50.14371,8.56076 50.14323,8.56112
+Eschborn-Sued 50.13352,8.57863 50.13327,8.57807 50.13441,8.57850
+Frankfurt-Roedelheim 50.12457,8.60708 50.12431,8.60628 50.12473,8.60800
+Frankfurt-West 50.11885,8.63981 50.11876,8.63932
+Frankfurt-Messe 50.11190,8.64354 50.11271,8.64371
+Frankfurt-Galluswarte 50.10384,8.64487 50.10384,8.64487
+F-Taunusanlage 50.11325,8.66906
+F-Hauptwache 50.11393,8.67835
+F-Konstablerwache 50.11423,8.68621
+F-Ostendstrasse 50.11226,8.69710
+F-Lokalbahnhof 50.10198,8.69294
+Frankfurt-Süd 50.09907,8.68627
+F-Stresemannallee 50.09455,8.67173
+F-Louisa 50.08320,8.66947
+Neu-Isenburg 50.05303,8.66450
+Dreieich-Buchschlag 50.02230,8.66256
+Langen-Flugsicherung 50.00559,8.65843
+Langen 49.99373,8.65786
+Egelsbach 49.96785,8.65403
+Erzhausen 49.95128,8.65086
+D-Wixhausen 49.92889,8.64724
+D-Arheiligen 49.91424,8.64625
+F-ZusckschwerdStr 50.10290,8.55181
+F-Bolognaropalast 50.10140,8.55297
+F-TillyStr 50.10173,8.56031
+F-NiedKirche 50.09823,8.56726
+F-LuthmerStr 50.09907,8.57265
+F-BirminghamStr 50.10009,8.57790
+F-Jaegerallee 50.10024,8.59112
+F-Linnegraben 50.10017,8.59781
+F-WaldschulStr 50.10061,8.60305
+F-MoenchhofStr 50.10079,8.61903
+F-WickererStr 50.10057,8.62240
+F-RebstoeckerStr 50.10154,8.62866
+F-SchwalbacherStr 50.10235,8.63628
+F-SpeyererStr 50.10542,8.65043
+F-Gueterplatz 50.10751,8.65557
+F-PlatzDerRepublik 50.10929,8.66073
+Kronberg 50.17983,8.51726 50.17949,8.51744
+Kronberg-Sued 50.17289,8.52965 50.17368,8.52843
+Sulzbach(Taunus) 50.12957,8.52836 50.13012,8.52812
+Weisskirchen-Steinb 50.17346,8.58782
+Stierstadt 50.18486,8.58486
+Oberursel 50.19853,8.58769
+Bad-Homburg 50.21945,8.62057
+Seulberg 50.24286,8.64559
+Friedrichsdorf 50.25200,8.64432
+Diez-Ost 50.37614,8.03851 50.37512,8.03780
+Staffel 50.39929,8.04620 50.39838,8.04605
+Elz 50.41450,8.03848 50.41490,8.03826
+Niederhadamar 50.43309,8.03737 50.43340,8.03737
+Hadamar 50.44710,8.04351 50.44771,8.04416
+Niederzeuzheim 50.46802,8.03966 50.46880,8.03954
+Frickofen 50.50485,8.03060 50.50485,8.03002
+Wilsenroth 50.53191,8.03165 50.53076,8.03371
+Wilmenroth 50.53940,7.98573 50.53940,7.98477
+Westerburg 50.55747,7.96709 50.55674,7.96668
+Langenhahn 50.58733,7.91494 50.58761,7.91676
+Elz-Sued 50.40851,8.03211 50.40845,8.03290
+Niedererbach 50.42488,7.97713
+Dreikirchen 50.44986,7.95879
+Steinefrenz 50.44778,7.93970,50.44737,7.94294
+Girod 50.45439,7.90259
+Goldhausen 50.46131,7.87345 50.46185,7.87268
+Montabaur 50.44479,7.82520 50.44415,7.82538 50.44504,7.83017
+Dernbach 50.45672,7.78386
+Wirges 50.46728,7.78485
+Sirshahn 50.48593,7.77162 50.48597,7.77052
+Limburg(Lahn) 50.38426,8.06296 50.38495,8.06215
+Eschofen 50.39337,8.10427 50.39396,8.10594
+Lindenholzhausen 50.37840,8.13335 50.37768,8.13321
+Niederbrechen 50.35957,8.15976 50.36031,8.15854
+Oberbrechen 50.35454,8.18765 50.35524,8.18722
+Niederselters 50.33249,8.22995 50.33283,8.23066
+Bad-Camberg 50.29644,8.25516 50.29597,8.25585
+Woersdorf 50.24550,8.24926 50.24614,8.24910
+Idstein 50.21591,8.25755 50.21665,8.25795
+Niedernhausen 50.15972,8.31272 50.16053,8.31281
+Hofheim 50.08406,8.44493 50.08502,8.44549
+Unterliederbach 50.10796,8.52798 50.10842,8.52797
+Liederbach-Sued 50.11538,8.50159 50.11571,8.50118
+Liederbach 50.11861,8.48631 50.11823,8.48802
+Kelkheim-Muenster 50.12477,8.46183 50.12517,8.46202
+Kelkheim 50.13694,8.44781 50.13730,8.44812
+Kelkheim-Hornau 50.14692,8.44459 50.14770,8.44509
+Schneidhain 50.17261,8.45209 50.17245,8.45160
+Koenigstein 50.17758,8.46892 50.17738,8.46989
+FfmFlughfFbf 50.05297,8.57086 50.05202,8.57030
+Limburg-Sued 50.38241,8.09621 50.38283,8.09445 50.38349,8.09597
+Siegburg-Bonn 50.79377,7.20269 50.79446,7.20331
+Koeln 50.94299,6.95907 50.94220,6.95798
+Kerkerbach 50.40172,8.13610
+Runkel 50.40502,8.15961
+Villmar 50.39477,8.18734
+Arfurt(Lahn) 50.40644,8.21076
+Aumenau 50.39990,8.24952
+Fürfurt 50.42938,8.25411
+Gräveneck 50.45194,8.25117
+Weilburg 50.48731,8.26838
+Löhnberg 50.50920,8.27367
+Stockhausen(Lahn) 50.54039,8.32492
+Leun/Braunfels 50.53957,8.36628
+Solms 50.54520,8.40752
+Albshausen 50.54561,8.43360
+Wetzlar 50.56509,8.50360
+Dutenhofen(Wetzlar) 50.56379,8.59979
+Gießen 50.57804,8.66280
+DA-RheinNeckarStrasse 49.87183,8.64463
+DA-EscholbrueckerStr 49.86537,8.64717
+DA-PrinzEmilGarten 49.86154,8.64708
+DA-BessungerStr 49.85836,8.64698
+DA-LandskronenStr 49.85335,8.64668
+DA-Marienhoehe 49.84355,8.64609
+Eberstadt-FriedrichEbert 49.83855,8.64559
+Eberstadt-CarlUlrich 49.83321,8.64496
+Eberstadt-VonKetteler 49.82911,8.64520
+Eberstadt-KatharinenStr 49.82476,8.64482
+Eberstadt-Wartehalle 49.82027,8.64411
+Eberstadt-Modaubruecke 49.81739,8.64425
+Eberstadt-Kirche 49.81429,8.64600
+Eberstadt-Friedhof 49.81108,8.64603
+Eberstadt-Frankenstein 49.80744,8.64542
+Eberstadt-Mittelschneise 49.80234,8.64689
+Malchen-Seeheim 49.79020,8.64875
+Seeheim-Wingert 49.77274,8.64814
+Seeheim-NeuesRathaus 49.76666,8.64623
+Seeheim-Tannenberg 49.76287,8.64482
+Jugenheim-LudwigStr 49.75676,8.63487
+Jugenheim-Bickenbacher 49.75357,8.62937
+Alsbach-Beuneweg 49.74641,8.62061
+Alsbach-Hikelstein 49.74028,8.61432
+
+-- trainline STR11
+Frankfurt(M)Hbf + 4,14,24,34,44,54_6-22_*
+F-PlatzDerRepublik +2
+F-Gueterplatz +4
+F-SpeyererStr +5
+Frankfurt-Galluswarte +7
+F-SchwalbacherStr +9
+F-RebstoeckerStr +11
+F-WickererStr +12
+F-MoenchhofStr +13
+F-WaldschulStr +15
+F-Linnegraben +16
+F-Jaegerallee +17
+F-BirminghamStr +19
+F-LuthmerStr +20
+F-NiedKirche +22
+F-TillyStr +24
+F-ZusckschwerdStr +26
+
+-- trainline STR11
+F-ZusckschwerdStr + 0,10,20,30,40,50_6-22_*
+F-Bolognaropalast +1
+F-TillyStr +3
+F-NiedKirche +4
+F-LuthmerStr +5
+F-BirminghamStr +7
+F-Jaegerallee +9
+F-Linnegraben +10
+F-WaldschulStr +11
+F-MoenchhofStr +13
+F-WickererStr +14
+F-RebstoeckerStr +15
+F-SchwalbacherStr +17
+Frankfurt-Galluswarte +19
+F-SpeyererStr +20
+F-Gueterplatz +22
+F-PlatzDerRepublik +24
+Frankfurt(M)Hbf +26
+
+-- trainline S3
+Bad-Soden + 20,50_0,5-23_*
+Sulzbach-Nord +2
+Schwalbach-Limes +5
+Schwalbach-Nord +6
+Niederhoechsstadt +9
+Eschborn +11
+Eschborn-Sued +13
+Frankfurt-Roedelheim +17
+Frankfurt-West +20
+Frankfurt-Messe +22
+Frankfurt-Galluswarte +24
+Frankfurt(M)Hbf +26
+F-Taunusanlage +28
+F-Hauptwache +30
+F-Konstablerwache +31
+F-Ostendstrasse +33
+F-Lokalbahnhof +35
+Frankfurt-Süd +37
+F-Stresemannallee +39
+F-Louisa +41
+Neu-Isenburg +44
+Dreieich-Buchschlag +47
+Langen-Flugsicherung +49
+Langen +51
+Egelsbach +54
+Erzhausen +56
+D-Wixhausen +58
+D-Arheiligen +61
+DarmstadtHbf +64
+
+-- trainline S4
+Kronberg + 8,38_0,5-23_*
+Kronberg-Sued +2
+Niederhoechsstadt +6
+Eschborn +8
+Eschborn-Sued +10
+Frankfurt-Roedelheim +14
+Frankfurt-West +17
+Frankfurt-Messe +19
+Frankfurt-Galluswarte +21
+Frankfurt(M)Hbf +23
+F-Taunusanlage +25
+F-Hauptwache +27
+F-Konstablerwache +28
+F-Ostendstrasse +30
+F-Lokalbahnhof +32
+Frankfurt-Süd +34
+F-Stresemannallee +36
+F-Louisa +38
+Neu-Isenburg +41
+Dreieich-Buchschlag +44
+Langen-Flugsicherung +46
+Langen +48
+
+-- trainline S3
+Frankfurt(M)Hbf + 14,44_0,5-23_*
+Frankfurt-Galluswarte +3
+Frankfurt-Messe +4
+Frankfurt-West +6
+Frankfurt-Roedelheim +9
+Eschborn-Sued +12
+Eschborn +15
+Bad-Soden +26
+
+-- trainline S4
+Frankfurt(M)Hbf + 29,59_0,5-23_*
+Frankfurt-Galluswarte +3
+Frankfurt-Messe +4
+Frankfurt-West +6
+Frankfurt-Roedelheim +9
+Eschborn-Sued +12
+Eschborn +15
+Niederhoechsstadt +17
+Kronberg-Sued +20
+Kronberg +22
+
+-- trainline S5
+Frankfurt(M)Hbf + 24,54_0,5-23_*
+Frankfurt-Galluswarte +3
+Frankfurt-Messe +4
+Frankfurt-West +6
+Frankfurt-Roedelheim +9
+Weisskirchen-Steinb +13
+Stierstadt +15
+Oberursel +18
+Bad-Homburg +21
+Seulberg +25
+Friedrichsdorf +26
+
+-- trainline S5
+Friedrichsdorf + 8,38_0,5-23_*
+Seulberg +3
+Bad-Homburg +7
+Oberursel +11
+Stierstadt +13
+Weisskirchen-Steinb +15
+Frankfurt-Roedelheim +19
+Frankfurt-West +22
+Frankfurt-Messe +24
+Frankfurt-Galluswarte +26
+Frankfurt(M)Hbf +29
+
+-- trainline S8
+WiesbadenHbf + 19,49_0,5-23_*
+Wiesbaden-Ost +4
+Mainz-Nord +8
+MainzHbf +14
+Mainz-Roemisches-Th +17
+Mainz-Gustavsburg +20
+Mainz-Bischofsheim +23
+Ruesselsheim-Opel +26
+Ruesselsheim +29
+Raunheim +32
+Kelsterbach +37
+FfmFlughfFbf +43
+F-Stadion +47
+F-Niederrad +50
+Frankfurt(M)Hbf +54
+
+
+-- trainline HLB
+Frankfurt/Hoechst + 28,58_6-23_*
+Frankfurt(M)Hbf +11
+
+-- trainline HLB
+Frankfurt(M)Hbf + 17,47_6-23_*
+Frankfurt/Hoechst +12
+Unterliederbach +14
+Liederbach-Sued +16
+Liederbach +18
+Kelkheim-Muenster +21
+Kelkheim +25
+Kelkheim-Hornau +27
+Schneidhain +31
+Koenigstein +35
+
+-- trainline HLB
+Frankfurt/Hoechst + 0,30_5-20_* - 0_9-15_*
+Sulzbach(Taunus) +6
+Bad-Soden +9
+
+-- trainline HLB
+Bad-Soden + 14,44_5-20_* - 14_9-15_*
+Sulzbach(Taunus) +3
+Frankfurt/Hoechst +9
+
+-- trainline SE60
+Frankfurt(M)Hbf + 6_0,5-23_*
+DarmstadtHbf +24
+Darmstadt-Sued +27
+Bensheim-Auerbach +50
+Bensheim +53
+
+-- trainline SE60
+Bensheim + 0_0,5-23_*
+Bensheim-Auerbach +3
+DarmstadtHbf +30
+Frankfurt(M)Hbf +48
+
+-- trainline RE60
+Frankfurt(M)Hbf + 34_6,8,12,14,16,18,20_1-5
+Langen +10
+DarmstadtHbf +20
+#Bickenbach +29
+Bensheim +34
+
+-- trainline RB15707
+WiesbadenHbf + 38_6-23_*
+MainzHbf +11
+Mainz-Roemisches-Th +14
+Mainz-Bischofsheim +19
+Nauheim +26
+Gross-Gerau +29
+Klein-Gerau +32
+Weiterstadt +36
+DarmstadtHbf +43
+
+-- trainline RE50
+Frankfurt(M)Hbf + 10_6-23_*
+F-Niederrad +6
+Walldorf +15
+Moerfelden +18
+Gross-G-Dornberg +24
+Riedstatt-Goddelau +30
+Stockstadt-Rhein +33
+Biebesheim +37
+Gernsheim +41
+Gross-Rohrheim +45
+Biblis +48
+Buerstadt +53
+Lampertheim +57
+Mannheim-Waldhof +62
+MannheimHbf +69
+
+-- trainline Vectus
+Frickofen + 49_6,7,9,11,13,15,17,19_* + 19_6_* + 13_9_* + 6_15_*
+Niederzeuzheim +6
+Hadamar +4
+Niederhadamar +13
+Elz +16
+Staffel +19
+Diez-Ost +23
+Limburg(Lahn) +26
+
+-- trainline Vectus
+Limburg(Lahn) + 8_8,10,12,14,16,18,20_*
+Diez-Ost +3
+Staffel +8
+Elz +11
+Niederhadamar +14
+Hadamar +16
+Niederzeuzheim +20
+Frickofen +26
+Wilsenroth +31
+Wilmenroth +36
+Westerburg +42
+Langenhahn +49
+
+-- trainline Vectus
+Limburg(Lahn) + 54_7,9,10,11,12,13,14,15,16,17,18,19,20_*
+Diez-Ost +4
+Staffel +7
+Elz-Sued +10
+Niedererbach +17
+Dreikirchen +23
+Steinefrenz +26
+Girod +30
+Goldhausen +38
+Montabaur +46
+Dernbach +53
+Wirges +55
+Sirshahn +59
+
+
+-- trainline Vectus
+Sirshahn + 9_7,9,10,11,12,13,14,15,16,17,18,19,20_*
+Wirges +4
+Dernbach +7
+Montabaur +14
+Goldhausen +21
+Girod +25
+Steinefrenz +29
+Dreikirchen +32
+Niedererbach +38
+Elz-Sued +45
+Staffel +48
+Diez-Ost +52
+Limburg(Lahn) +55
+
+-- trainline SE10
+Limburg(Lahn) + 18_7-21_*
+Eschofen +4
+Lindenholzhausen +8
+Niederbrechen +12
+Oberbrechen +15
+Niederselters +19
+Bad-Camberg +24
+Woersdorf +29
+Idstein +33
+Niedernhausen +40
+Hofheim +52
+Frankfurt/Hoechst +59
+Frankfurt(M)Hbf +70
+
+-- trainline HLB
+Limburg(Lahn) + 23_7-21_*
+Eschofen +4
+Kerkerbach +7
+Runkel +10
+Villmar +14
+Arfurt(Lahn) +19
+Aumenau +23
+Fürfurt +27
+Gräveneck +31
+Weilburg +37
+Löhnberg +41
+Stockhausen(Lahn) +46
+Leun/Braunfels +50
+Solms +54
+Albshausen +57
+Wetzlar +63
+Dutenhofen(Wetzlar) +69
+Gießen +75
+
+-- trainline SE10
+Frankfurt(M)Hbf + 31_7-22_*
+Frankfurt/Hoechst +10
+Hofheim +18
+Niedernhausen +30
+Idstein +37
+Woersdorf +41
+Bad-Camberg +46
+Niederselters +50
+Oberbrechen +54
+Niederbrechen +57
+Lindenholzhausen +61
+Eschofen +65
+Limburg(Lahn) +69
+
+
+-- trainline RE1530x
+Limburg(Lahn) + 55_6,7_*
+Eschofen +4
+Frankfurt/Hoechst +52
+Frankfurt(M)Hbf +63
+
+-- trainline STR8
+DA-RheinNeckarStrasse + 16,46_6-23_*
+DA-EscholbrueckerStr +2
+DA-PrinzEmilGarten +3
+DA-BessungerStr +4
+DA-LandskronenStr +6
+DA-Marienhoehe +8
+Eberstadt-FriedrichEbert +9
+Eberstadt-CarlUlrich +10
+Eberstadt-VonKetteler +12
+Eberstadt-KatharinenStr +13
+Eberstadt-Wartehalle +14
+Eberstadt-Modaubruecke +16
+Eberstadt-Kirche +17
+Eberstadt-Friedhof +18
+Eberstadt-Frankenstein +20
+Eberstadt-Mittelschneise +21
+Malchen-Seeheim +23
+Seeheim-Wingert +26
+Seeheim-NeuesRathaus +28
+Seeheim-Tannenberg +29
+Jugenheim-LudwigStr +31
+Jugenheim-Bickenbacher +32
+Alsbach-Beuneweg +34
+Alsbach-Hikelstein +36