/** * Simple Train Router * * @author ab */ package btools.memrouter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; 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; 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; } private static List trips = new ArrayList(); private static long oldChecksum = 0; private String startEndText() { return (start == null ? "unmatched" : start.getName() ) + "->" + (end == null ? "unmatched" : end.getName() ); } public OsmTrack findRoute( OsmPos startPos, OsmPos endPos, int alternativeIdx ) throws Exception { if ( alternativeIdx == -1 ) // lowest cost result { List singleTrip = _findRoute( startPos, endPos, true ); if ( singleTrip.isEmpty() ) { if ( linksProcessed + linksReProcessed > 5000000 ) throw new RuntimeException( "5 million links limit reached" ); else throw new RuntimeException( "no track found! (" + startEndText() + ")" ); } Iternity iternity = singleTrip.get( 0 ); OsmTrack t = iternity.track; t.iternity = iternity.details; return t; } // check for identical params long[] nogocheck = rc.getNogoChecksums(); long checksum = nogocheck[0] + nogocheck[1] + nogocheck[2]; checksum += startPos.getILat() + startPos.getILon() + endPos.getILat() + endPos.getILon(); checksum += rc.localFunction.hashCode(); if ( checksum != oldChecksum ) { trips = _findRoute( startPos, endPos, false ); Collections.sort( trips ); // sort by arrival time oldChecksum = checksum; } if ( trips.isEmpty() ) { if ( linksProcessed + linksReProcessed > 5000000 ) throw new RuntimeException( "5 million links limit reached" ); else throw new RuntimeException( "no track found! (" + startEndText() + ")" ); } if ( alternativeIdx == 0 ) // = result overview { List details = new ArrayList(); for ( int idx = 0; idx < trips.size(); idx++ ) { if ( idx > 0 ) details.add( "" ); Iternity iternity = trips.get( idx ); iternity.appendSummary( details ); } Iternity iternity = trips.get( 0 ); OsmTrack t = iternity.track; t.iternity = details; return t; } int idx = alternativeIdx > trips.size() ? trips.size()-1 : alternativeIdx-1; Iternity iternity = trips.get( idx ); OsmTrack t = iternity.track; t.iternity = iternity.details; return t; } private List _findRoute( OsmPos startPos, OsmPos endPos, boolean fastStop ) throws Exception { List iternities = new ArrayList(); start = graph.matchNodeForPosition( startPos, rc.expctxWay, rc.transitonly ); if ( start == null ) throw new IllegalArgumentException( "unmatched start: " + startPos ); end = graph.matchNodeForPosition( endPos, rc.expctxWay, rc.transitonly ); if ( end == null ) throw new IllegalArgumentException( "unmatched end: " + endPos ); time0 = System.currentTimeMillis() + (long) ( rc.starttimeoffset * 60000L ); long minutes0 = ( time0 + 59999L ) / 60000L; time0 = minutes0 * 60000L; OffsetSet fullSet = OffsetSet.fullSet(); OffsetSet finishedOffsets = fullSet.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 ) { break; } // 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++; } /* int maxOffset = (int)((maxarrival - trip.arrival)/60000L); OffsetSet offsets = trip.offsets.ensureMaxOffset( maxOffset ); if ( offsets == null ) continue; */ OffsetSet offsets = trip.offsets; // check global closure offsets = finishedOffsets.filter( offsets ); if ( offsets == null ) continue; /* offsets = efficientSubset( offsets, trip.adjustedCost ); if ( offsets == null ) continue; */ boolean continueOnLineOnly = false; // check local closure for the target node: if ( currentNode.filterAndCloseNode( offsets, true ) == null ) { continueOnLineOnly = true; } // check local closure for links: offsets = currentLink.filterAndClose( offsets, trip.arrival ); if ( offsets == null ) continue; // check for arrival if ( currentNode == end ) { Iternity iternity = null; OffsetSet toffsets = trip.offsets; int lastIdx = iternities.size()-1; // hack: for equal cost tracks, merge offsets (assuming identical iternity) if ( lastIdx >= 0 && iternities.get( lastIdx ).track.cost == trip.cost ) { toffsets = toffsets.add( iternities.get( lastIdx ).offsets ); iternities.remove( lastIdx ); } for ( int offset = 0; offset < trip.offsets.size(); offset++ ) { if ( trip.offsets.contains( offset ) ) { iternity = compileTrip( trip, offset ); iternity.offsets = toffsets; System.out.println( "---- begin route ------ (cost " + iternity.track.cost + ")" ); for ( String s : iternity.details ) System.out.println( s ); System.out.println( "---- end route ------ (arrival " + new Date( iternity.arrivaltime ) + ")" ); break; // + plus more offsets.. } } finishedOffsets = finishedOffsets.add( offsets ); if ( iternity != null ) { // tracks come in cost-ascending order, so the arrival time // must decrease for the new track to be efficient // if ( iternities.isEmpty() || iternities.get( iternities.size() - 1 ).arrivaltime > iternity.arrivaltime ) if ( efficientSubset( iternities, iternity.offsets, iternity.track.cost ) != null ) { iternities.add( iternity ); if ( fastStop ) { break; } System.out.println( "*** added track to result list !**** "); } } System.out.println( "*** finishedOffsets = " + finishedOffsets ); } for ( OsmLinkP link = currentNode.getFirstLink(); link != null; link = link.getNext( currentNode ) ) { if ( continueOnLineOnly ) { if ( ! ( currentLink instanceof ScheduledLink && link instanceof ScheduledLink ) ) continue; if ( ((ScheduledLink) currentLink).line != ((ScheduledLink) link ).line ) continue; } /* // pre-check target closure if ( link.getTarget( currentNode ).filterAndCloseNode( offsets, false ) == null ) { continue; } */ if ( !rc.transitonly || link instanceof ScheduledLink ) { addNextTripsForLink( trip, currentNode, currentLink, link, offsets, 0 ); } } } return iternities; } private OffsetSet efficientSubset( List iternities, OffsetSet offsets, int cost ) { for( Iternity it : iternities ) { // determine a time-diff from a cost-diff int dcost = cost - it.track.cost; int dtime = (int) ( dcost * .06 / rc.cost1speed + 1. ); // 22km/h offsets = offsets.sweepWith( it.offsets, dtime ); if ( offsets == null ) { return null; } } return offsets; } 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 ) { OsmNodeP node = link.getTarget( currentNode ); if ( node == null ) { System.out.println( "ups2: " + link ); return; } if ( node == trip.originNode ) { link.filterAndClose( offsets, -1 ); // invalidate reverse link return; } // calc distance and check nogos rc.nogoCost = 0.; int distance = rc.calcDistance( currentNode.ilon, currentNode.ilat, node.ilon, node.ilat ); if ( Math.abs(rc.nogoCost) > 0.) { 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. ); // 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 ); // *** 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() ); 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 angle = rc.calcAngle( trip.originNode.ilon, trip.originNode.ilat, currentNode.ilon, currentNode.ilat, node.ilon, node.ilat ); double cos = 1. - rc.getCosAngle(); int turncost = (int) ( cos * rc.expctxWay.getTurncost() + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty waycost += turncost; } 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 ); // 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 Iternity compileTrip( ScheduledTrip trip, int offset ) { Iternity iternity = new Iternity(); OsmTrack track = new OsmTrack(); ScheduledTrip current = trip; ScheduledLine lastLine = new ScheduledLine(); ScheduledLine dummyLine = new ScheduledLine(); List list = new ArrayList(); int distance = 0; long departure = 0; ScheduledTrip itrip = null; String profile = extractProfile( rc.localFunction ); SimpleDateFormat df = new SimpleDateFormat( "dd.MM HH:mm" ); // time0 = df.parse(startTime).getTime(); OsmNodeP nextNode = null; while (current != null) { departure = current.departure; // System.out.println( "trip=" + current ); OsmNodeP node = current.getTargetNode(); OsmPathElement pe = OsmPathElement.create( node.ilon, node.ilat, node.selev, null, false ); 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; } if ( i == 0 && current.link.targetNode instanceof StationNode ) { nextStationName = ( (StationNode)current.link.targetNode ).name; } { Date d0 = new Date( time0 + 60000L * offset + current.departure ); Date d1 = new Date( time0 + 60000L * offset + current.arrival ); if ( iternity.details.size() > 0 ) iternity.details.add( "" ); iternity.details.add( "depart: " + df.format( d0 ) + " " + stationName ); iternity.details.add( " --- " + lineName + " ---" ); iternity.details.add( "arrive: " + df.format( d1 ) + " " + nextStationName ); if ( !iternity.lines.contains( lineName ) ) { iternity.lines.add( lineName ); } } } iternity.track = track; iternity.arrivaltime = time0 + 60000L * offset + trip.arrival; iternity.departtime = time0 + 60000L * offset + departure; return iternity; } 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; } }