package pl.edu.agh.logic; import java.util.Calendar; import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import pl.edu.agh.adhoc.AdHocModule; import pl.edu.agh.jsonrpc.JSONRPCException; import pl.edu.agh.jsonrpc.JSONRPCSocketServer; import pl.edu.agh.jsonrpc.RawDataSocket; import pl.edu.agh.model.RectD; import pl.edu.agh.model.SimpleLocationInfo; import pl.edu.agh.model.TrafficData; import pl.edu.agh.service.TrafficAdHocDispatchService; import pl.edu.agh.service.TrafficService; import pl.edu.agh.service.TrafficServiceStub; import pl.edu.agh.utils.DateUtils; import pl.edu.agh.utils.GeometryUtils; import android.graphics.RectF; import android.util.Log; public class TrafficDataProvider extends AbstractProvider<TrafficDataListener> { private static final double OSMAND_BASE_RADIUS = 0.007186455; private static final double ZOOM_MULTIPLIER = 2.0; private static final int REFRESH_INTERVAL = 300; private static final int REFRESH_AFTER_ERROR_INTERVAL = 15; public static final int MIN_ZOOM = 12; public static final int MAX_ZOOM = 15; public enum Status { FETCHING, COMPLETED, ERROR; } public static class TrafficDataRequest { private SimpleLocationInfo location; private int zoom; private Date requestTime; public TrafficDataRequest(SimpleLocationInfo location, int zoom, Date lastUpdate) { this.location = location; this.zoom = zoom; this.requestTime = lastUpdate; } public SimpleLocationInfo getLocation() { return location; } public int getZoom() { return zoom; } public Date getRequestTime() { return requestTime; } } public static class TrafficDataSet { private TrafficData trafficData; private RectD bound; public TrafficDataSet(TrafficData trafficData, RectD bound) { this.trafficData = trafficData; this.bound = bound; } public TrafficDataSet() { } public TrafficData getTrafficData() { return trafficData; } public RectD getBound() { return bound; } } private TrafficService trafficService = TrafficServiceStub.getInstance(); private ExecutorService executor = Executors.newSingleThreadExecutor(); private final AdHocModule adHocModule; private final JSONRPCSocketServer adHocServer; private final TrafficAdHocDispatchService adHocDispatchService; private TrafficDataRequest lastRequest; private TrafficDataSet lastFetchedDataSet = new TrafficDataSet(); private Status currentStatus; public TrafficDataProvider(AdHocModule adHocModule) { this.adHocModule = adHocModule; adHocDispatchService = new TrafficAdHocDispatchService(this, adHocModule.socket); trafficService.configAdHoc(adHocModule.socket, adHocDispatchService); adHocServer = new JSONRPCSocketServer(adHocModule.socket, adHocDispatchService); } /* * for testing */ public TrafficDataProvider(RawDataSocket socket) { adHocModule = null; adHocDispatchService = new TrafficAdHocDispatchService(this, socket); trafficService = TrafficServiceStub.getCopy(); trafficService.configAdHoc(socket, adHocDispatchService); adHocServer = new JSONRPCSocketServer(socket, adHocDispatchService); } /* * for testing */ public void injectLastTrafficOperations(TrafficDataRequest request, TrafficDataSet data, int messageSize) { lastRequest = request; lastFetchedDataSet = data; adHocDispatchService.setMessageSize(messageSize); } public void startAdHocServer() { new Thread(new Runnable() { public void run() { try { adHocServer.start(); } catch (JSONRPCException e) { Log.e("Ad Hoc Dispatch", e.getMessage(), e); } } }, "Ad Hoc Server").start(); } public void stopAdHocServer() { adHocServer.stop(); } public TrafficService getTrafficService() { return trafficService; } private void trafficDataProvided(final TrafficData trafficData) { forAllListeners(new ListenerTask<TrafficDataListener>() { @Override public void execute(TrafficDataListener listener) { listener.onDataProvided(trafficData); } }); } private void statusChanged(final Status status) { forAllListeners(new ListenerTask<TrafficDataListener>() { @Override public void execute(TrafficDataListener listener) { listener.onStatusChanged(status); } }); } public synchronized TrafficData getTrafficData(SimpleLocationInfo location, int zoom, RectF bound) { if (currentStatus != Status.FETCHING) { TrafficDataRequest newRequest = new TrafficDataRequest(location, zoom, new Date()); if (newRequestRequired(lastRequest, lastFetchedDataSet.getBound(), newRequest, new RectD(bound))) { requestData(newRequest); } } return lastFetchedDataSet.getTrafficData(); } public synchronized TrafficData getCachedTrafficDataIfAvailable(SimpleLocationInfo location, double radius) { if(lastRequest == null || lastFetchedDataSet == null) { return null; } SimpleLocationInfo cachedLocation = lastRequest.getLocation(); double cachedRadius = calculateRadiusForZoom(lastRequest.getZoom()); if((GeometryUtils.euklideanDist(location, cachedLocation) < cachedRadius/4.0) && radius <= cachedRadius*1.25) { return lastFetchedDataSet.getTrafficData(); } else { return null; } } private synchronized void updateLastRequest(Status status, TrafficDataRequest request, TrafficDataSet fetchedDataSet) { lastRequest = new TrafficDataRequest(request.getLocation(), request.getZoom(), new Date()); currentStatus = status; lastFetchedDataSet = fetchedDataSet; } private boolean newRequestRequired(TrafficDataRequest previousRequest, RectD previousBound, TrafficDataRequest currentRequest, RectD currentBound) { if (previousRequest == null) { return true; } if (currentStatus == Status.ERROR) { return currentRequest.getRequestTime().after( DateUtils.add(previousRequest.getRequestTime(), Calendar.SECOND, REFRESH_AFTER_ERROR_INTERVAL)); } if (currentRequest.getRequestTime().after( DateUtils.add(previousRequest.getRequestTime(), Calendar.SECOND, REFRESH_INTERVAL))) { return true; } if ((currentRequest.getZoom() != previousRequest.getZoom()) && !(currentRequest.getZoom() < MIN_ZOOM && previousRequest.getZoom() < MIN_ZOOM) && !(currentRequest.getZoom() >= MAX_ZOOM && previousRequest.getZoom() >= MAX_ZOOM)) { return true; } return !previousBound.contains(currentBound); } private void requestData(final TrafficDataRequest request) { currentStatus = Status.FETCHING; executor.execute(new Runnable() { @Override public void run() { statusChanged(Status.FETCHING); double radius = calculateRadiusForZoom(request.getZoom()); TrafficData fetchedData; Status status; try { fetchedData = request.getZoom() < MIN_ZOOM ? null : trafficService.getTrafficData( request.getLocation(), radius); status = Status.COMPLETED; } catch (JSONRPCException e) { try { fetchedData = alternativeRequestData(request); status = Status.COMPLETED; } catch (JSONRPCException e2) { fetchedData = null; status = Status.ERROR; } } updateLastRequest(status, request, new TrafficDataSet(fetchedData, GeometryUtils.createBoundingBox(request.getLocation(), radius))); statusChanged(status); trafficDataProvided(fetchedData); } }); } private TrafficData alternativeRequestData(final TrafficDataRequest request) throws JSONRPCException { if(adHocModule.isProcessRunning()) { return trafficService.getTrafficDataViaAdHoc(request.getLocation(), calculateRadiusForZoom(request.getZoom())); } throw new JSONRPCException("Ad Hoc not available"); } public static double calculateRadiusForZoom(int zoom) { zoom = Math.min(zoom, MAX_ZOOM); return OSMAND_BASE_RADIUS * 2 * Math.pow(ZOOM_MULTIPLIER, MAX_ZOOM - zoom); } }