schedule-router proptotype

This commit is contained in:
Arndt 2015-04-03 13:49:53 +02:00
parent 0316c41924
commit 55f8e7fb4a
18 changed files with 2359 additions and 0 deletions

View file

@ -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<OsmNodeP> 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<LazyArrayOfLists<OsmNodeP>> seglistsArray = new ArrayList<LazyArrayOfLists<OsmNodeP>>(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 <node-tiles-in> <way-tiles-in> <lookup-file> <profile-file> <fahtplan-file>");
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<LazyArrayOfLists<OsmNodeP>>(MAXTILES);
for( int i=0; i < MAXTILES; i++ )
{
seglistsArray.add( null );
}
abUnifier = new ByteArrayUnifier( 16384, false );
nodesMap = new CompactLongMap<OsmNodeP>();
// read all nodes
new NodeIterator( this, false ).processDir( nodeTilesIn, ".u5d" );
// freeze the nodes-map
nodesMap = new FrozenLongMap<OsmNodeP>( nodesMap );
// trim the list array
for( int i=0; i<MAXTILES; i++ )
{
if ( seglistsArray.get(i) != null )
{
seglistsArray.get(i).trimAll();
}
}
// then read the ways
new WayIterator( this, false ).processDir( wayTilesIn, ".wt5" );
nodesMap = null; // don't need that anymore
System.out.println( "nodesLoaded=" + nodesLoaded + " linksLoaded=" + linksLoaded );
// now load the train-schedules
ScheduleParser.parseTrainTable( fahrplanFiles, this, expctxWay );
System.gc();
long mem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
System.out.println( "memory after graph loading: " + mem / 1024 / 1024 + " MB" );
}
public OsmNodeP matchNodeForPosition( OsmPos pos, BExpressionContextWay wayCtx )
{
// todo: this creates empty lists lazy
int ilon = pos.getILon();
int ilat = pos.getILat();
List<OsmNodeP> nodes = new ArrayList<OsmNodeP>();
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<OsmNodeP>(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<way.nodes.size(); i++)
{
long nid = way.nodes.get(i);
n1 = n2;
n2 = nodesMap.get( nid );
if ( n1 != null && n2 != null && n1 != n2 )
{
if ( tileForPos( n1.ilon, n1.ilat ) == currentTile )
{
OsmLinkP link = n2.createLink(n1);
link.descriptionBitmap = description;
linksLoaded++;
}
}
if ( n2 != null )
{
n2.wayBits |= wayBits;
}
}
}
// from BInstallerView.java
private int tileForFilename( String filename )
{
String basename = filename.substring( 0, filename.length() - 4 );
String uname = basename.toUpperCase();
int idx = uname.indexOf( "_" );
if ( idx < 0 ) return -1;
String slon = uname.substring( 0, idx );
String slat = uname.substring( idx+1 );
int ilon = slon.charAt(0) == 'W' ? -Integer.valueOf( slon.substring(1) ) :
( slon.charAt(0) == 'E' ? Integer.valueOf( slon.substring(1) ) : -1 );
int ilat = slat.charAt(0) == 'S' ? -Integer.valueOf( slat.substring(1) ) :
( slat.charAt(0) == 'N' ? Integer.valueOf( slat.substring(1) ) : -1 );
if ( ilon < -180 || ilon >= 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<OsmNodeP> 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<OsmNodeP>();
return seglistsArray.get(tileNr).getList( subIdxForPos( ilon, ilat ) );
}
}

View file

@ -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<Long,OffsetSet> existingSets = new HashMap<Long,OffsetSet>();
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<Integer> 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();
}
}

View file

@ -0,0 +1,13 @@
/**
* Set off departure offsets (immutable)
*
* @author ab
*/
package btools.memrouter;
public interface OffsetSetHolder
{
OffsetSet getOffsetSet();
void setOffsetSet( OffsetSet offsetSet );
}

View file

@ -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" );
}
}
}

View file

@ -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<OsmNodeP>, 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;
}
}

View file

@ -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;
}
}

View file

@ -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<String,StationNode> stationMap = new HashMap<String,StationNode>();
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 );
}
}
}

View file

@ -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;
}
}

View file

@ -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<Integer> offsetMinutes = new ArrayList<Integer>();
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<ScheduledDeparture> getScheduledDepartures( int idx, long timeFrom, OffsetSet offsets )
{
List<ScheduledDeparture> result = new ArrayList<ScheduledDeparture>();
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<Integer,List<Integer>> waitOffsets = getDepartures( offsetStart, timeFrom + timeFromCorrection, offsets );
for( Map.Entry<Integer,List<Integer>> 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<Integer,List<Integer>> getDepartures( int offsetStart, long timeFrom, OffsetSet offsets )
{
Map<Integer,List<Integer>> waitOffsets = new HashMap<Integer,List<Integer>>();
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<Integer> offsetList = waitOffsets.get( Integer.valueOf( waitTime ) );
if ( offsetList == null )
{
offsetList = new ArrayList<Integer>();
waitOffsets.put( Integer.valueOf( waitTime ), offsetList );
}
offsetList.add( Integer.valueOf( lastOffset ) );
}
}
}

View file

@ -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;
}
}

View file

@ -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<ScheduledTrip> openSet = new SortedHeap<ScheduledTrip>();
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<trip.offsets.size(); offset++ )
{
if ( trip.offsets.contains( offset ) )
{
track = compileTrip( trip, offset );
System.out.println( "---- begin route ------ (cost " + track.cost + ")" );
for( String s : track.iternity ) System.out.println( s );
System.out.println( "---- end route ------" );
break; // + plus more offsets..
}
}
finishedOffsets = finishedOffsets.add( offsets );
if ( solutionCount++ >= 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<ScheduledDeparture> 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<String>();
ScheduledTrip current = trip;
ScheduledLine lastLine = new ScheduledLine();
ScheduledLine dummyLine = new ScheduledLine();
List<ScheduledTrip> list = new ArrayList<ScheduledTrip>();
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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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<<i);
}
}
return res;
}
boolean matches( int minute, int hour, int dow )
{
return ( (1L << minute) & minutes ) != 0
&& ( (1L << hour) & hours ) != 0
&& ( (1L << dow) & dows ) != 0;
}
}
private List<TrainScheduleCron> cronsPositive;
private List<TrainScheduleCron> cronsNegative;
public TrainSchedule( String cronstring )
{
StringTokenizer tk = new StringTokenizer( cronstring, " " );
cronsPositive = new ArrayList<TrainScheduleCron>();
cronsNegative = new ArrayList<TrainScheduleCron>();
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;
}
}