package pl.edu.agh.bo; import static com.google.common.collect.Lists.newArrayList; import static pl.edu.agh.utils.DateUtils.timeDifference; import static pl.edu.agh.utils.Preconditions.checkNotNull; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import org.hibernate.exception.GenericJDBCException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import pl.edu.agh.dao.SpeedInfoDao; import pl.edu.agh.dao.WayDao; import pl.edu.agh.exception.BusinessException; import pl.edu.agh.logic.Path; import pl.edu.agh.logic.PathComparatorByCost; import pl.edu.agh.logic.PathCorrector; import pl.edu.agh.logic.PathSplitter; import pl.edu.agh.logic.PathUtils; import pl.edu.agh.logic.PathWithoutLowQualityMatchingsSplitter; import pl.edu.agh.logic.PathWithoutUTurnsSplitter; import pl.edu.agh.logic.Road; import pl.edu.agh.logic.RoadNetwork; import pl.edu.agh.logic.SpeedInfoProcessor; import pl.edu.agh.logic.UTurnPathCorrector; import pl.edu.agh.logic.WayWithBothEndPoints; import pl.edu.agh.logic.WayWithOneEndPoint; import pl.edu.agh.model.LocationData; import pl.edu.agh.model.LocationInfo; import pl.edu.agh.model.SpeedInfo; import pl.edu.agh.model.Way; import pl.edu.agh.model.WayWithSpeedInfo; import pl.edu.agh.spatial.GeometryHelper; import pl.edu.agh.spatial.PointComparator; import pl.edu.agh.spatial.WGS84GeometryFactory; import pl.edu.agh.utils.Collections; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.Point; @Component public class RoutingBOImpl implements RoutingBO { private static final double REACHING_END_OF_ROAD_FACTOR = 0.5; private static final int MAX_LOW_QUALITY_MATCHINGS_IN_ROW = 3; private static final int MAX_STORED_PATHS = 30; private static final double MAX_POINT_INTERVAL = 5.0; private static final double GPS_DEGREE_ACCURACY = 0.0004; private static final double GPS_METER_ACCURACY = 30.0; private final PathSplitter withoutUTurnsAndLowQualityMatchings = new PathWithoutLowQualityMatchingsSplitter( new PathWithoutUTurnsSplitter(), GPS_METER_ACCURACY); private final SpeedInfoProcessor speedInfoProcessor = new SpeedInfoProcessor(); @Autowired private WayDao wayDao; @Autowired private SpeedInfoDao speedInfoDao; @Autowired private WGS84GeometryFactory geometryFactory; // @Autowired // private TrafficGenerator trafficGenerator; @Autowired private WayWithEndpointsFactory wayWithEndpointsFactory; @Transactional @Override public List<Point> calculateRoute(Point start, Point end, boolean useTrafficDataToRoute) throws BusinessException { Way startWay = checkNotNull(wayDao.findNearestWay(start), Error.NO_START_ROUTE); Way endWay = checkNotNull(wayDao.findNearestWay(end), Error.NO_END_ROUTE); List<Point> route; if (startWay.equals(endWay) && wayWithEndpointsFactory.create(startWay, start, end).isValid()) { route = calculateRouteWithOneWay(wayWithEndpointsFactory.create(startWay, start, end)); } else { route = calculateRouteWithManyWays(wayWithEndpointsFactory.createWayWithStartPoint(startWay, start), wayWithEndpointsFactory.createWayWithEndPoint(endWay, end), useTrafficDataToRoute); } return Collections.removeAdjacentDuplicates(completeRoute(start, end, route), PointComparator.COMPARATOR); } private List<Point> calculateRouteWithOneWay(WayWithBothEndPoints wayWithBothEndPoints) { return wayWithBothEndPoints.createRoute(); } @SuppressWarnings("unchecked") private List<Point> calculateRouteWithManyWays(WayWithOneEndPoint startWayWithPoint, WayWithOneEndPoint endWayWithPoint, boolean useTrafficDataToRoute) { List<Way> route; try { route = wayDao.findRoute(startWayWithPoint.getWayEndPoint(), endWayWithPoint.getWayEndPoint(), useTrafficDataToRoute); // trafficGenerator.generate(route); } catch (GenericJDBCException ex) { throw new BusinessException(Error.CALCULATING_ERROR); } Integer startIndex = removeWayFromRouteIfExists(startWayWithPoint, route); Integer endIndex = removeWayFromRouteIfExists(endWayWithPoint, route); List<Point> routeInterior = normalizeRoute(startIndex, route); List<Point> startFragment = startWayWithPoint.createRouteFromPointToIndex(startIndex); List<Point> endFragment = endWayWithPoint.createRouteFromIndexToPoint(endIndex); return Collections.concatLists(startFragment, routeInterior, endFragment); } private Integer removeWayFromRouteIfExists(WayWithOneEndPoint wayWithPoint, List<Way> route) { if (route.contains(wayWithPoint.getWay())) { route.remove(wayWithPoint.getWay()); return wayWithPoint.getWayEndPointOppositeTo(wayWithPoint.getWayEndPoint()); } return wayWithPoint.getWayEndPoint(); } private List<Point> normalizeRoute(Integer startVertexNumber, List<Way> route) { Integer currentSource = startVertexNumber; List<Point> points = newArrayList(); for (Way way : route) { if (way.getSource().equals(currentSource)) { points.addAll(GeometryHelper.getPointsFromGeometry(way.getLineString(), geometryFactory)); currentSource = way.getTarget(); } else { points.addAll(GeometryHelper.getPointsFromGeometry(way.getLineString().reverse(), geometryFactory)); currentSource = way.getSource(); } } return points; } private List<Point> completeRoute(Point start, Point end, List<Point> route) { route.add(0, start); route.add(end); return route; } @Transactional @Override public List<WayWithSpeedInfo> getTrafficData(Point point, double radius) { return wayDao.getTrafficData(point, radius); } @Transactional @Override public void processLocationData(LocationData locationData) { List<Point> points = retrievePointsFromLocationData(locationData); RoadNetwork roadNetwork = createRoadNetworkForPoints(points); PathCorrector pathCorrector = new UTurnPathCorrector(roadNetwork); ListIterator<Point> currentPointIterator = points.listIterator(); while (currentPointIterator.hasNext()) { Path bestPath = calculateBestPath(currentPointIterator, points, roadNetwork); if (bestPath == null) { continue; } List<Path> splittedBestPath = withoutUTurnsAndLowQualityMatchings.split(pathCorrector.correct(bestPath)); retrieveAndSaveSpeedInfoFromPaths(splittedBestPath); } } private void retrieveAndSaveSpeedInfoFromPaths(List<Path> paths) { for (Path path : paths) { List<SpeedInfo> infos = speedInfoProcessor.retrieveSpeedInfoFromPath(path); for (SpeedInfo speedInfo : infos) { speedInfoDao.save(speedInfo); } } } private Path calculateBestPath(ListIterator<Point> currentPointIterator, List<Point> points, RoadNetwork roadNetwork) { List<Path> currentBestPaths = filterPathsWithLowCost(createInitialPaths(currentPointIterator, roadNetwork)); Path bestPath = null; while (currentPointIterator.hasNext() && !pathIsBroken(points.get(currentPointIterator.previousIndex()), points.get(currentPointIterator.previousIndex())) && !currentBestPaths.isEmpty()) { List<Path> nextStepBestPaths = createPathsForNextPoint(currentPointIterator.next(), currentBestPaths, roadNetwork); bestPath = java.util.Collections.min(nextStepBestPaths, PathComparatorByCost.COMPARATOR); currentBestPaths = filterPathsWithLowCost(nextStepBestPaths); } return bestPath; } private List<Path> createPathsForNextPoint(Point point, List<Path> currentPaths, RoadNetwork roadNetwork) { List<Path> nextStepBestPaths = newArrayList(); Date pointTime = (Date) point.getUserData(); for (Path currentPath : currentPaths) { nextStepBestPaths.add(currentPath.copy().matchPointToLastRoad(point.getCoordinate(), pointTime)); if (PathUtils.pathContainsOneDirectedRoad(currentPath) || currentPath.hasPointReachedEndOfLastRoad(point.getCoordinate(), REACHING_END_OF_ROAD_FACTOR)) { Collection<Road> forwardStar = roadNetwork.getRoadsBySource(currentPath.getTarget()); for (Road nextRoad : forwardStar) { nextStepBestPaths.add(currentPath.copy().matchPointToRoad(point.getCoordinate(), nextRoad, pointTime)); } } } return nextStepBestPaths; } private List<Path> filterPathsWithLowCost(List<Path> paths) { List<Path> filtered = newArrayList(Iterables.filter(paths, new Predicate<Path>() { @Override public boolean apply(Path path) { return path.getRelativeCostOfLastMatchings(MAX_LOW_QUALITY_MATCHINGS_IN_ROW) < GPS_METER_ACCURACY; } })); java.util.Collections.sort(filtered, PathComparatorByCost.COMPARATOR); return filtered.subList(0, Math.min(MAX_STORED_PATHS, filtered.size())); } private boolean pathIsBroken(Point previousPoint, Point currentPoint) { Date previousPointTime = (Date) previousPoint.getUserData(); Date currentPointTime = (Date) currentPoint.getUserData(); return timeDifference(previousPointTime, currentPointTime).toSeconds() > MAX_POINT_INTERVAL; } private List<Path> createInitialPaths(Iterator<Point> pointIterator, RoadNetwork roadNetwork) { while (pointIterator.hasNext()) { List<Path> paths = roadNetwork.getNearestPathsForPoint(pointIterator.next(), GPS_DEGREE_ACCURACY, GPS_METER_ACCURACY); if (!paths.isEmpty()) { return paths; } } return newArrayList(); } private RoadNetwork createRoadNetworkForPoints(List<Point> points) { Geometry boundingBox = getBoundingBoxForPoints(points); List<Way> ways = wayDao.getWaysInsideBox(boundingBox); RoadNetwork roadNetwork = new RoadNetwork(ways, geometryFactory); return roadNetwork; } private List<Point> retrievePointsFromLocationData(LocationData locationData) { return newArrayList(Lists.transform(locationData.getLocationInfos(), new Function<LocationInfo, Point>() { @Override public Point apply(LocationInfo info) { Point point = geometryFactory.createPoint(info.getLongitude(), info.getLatitude()); point.setUserData(info.getTime()); return point; } })); } private Geometry getBoundingBoxForPoints(List<Point> points) { MultiPoint multipoint = geometryFactory.createMultiPoint(points.toArray(new Point[0])); return GeometryHelper.getExpandedEnvelopeAsGeometry(multipoint, GPS_DEGREE_ACCURACY); } }