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