From b27b75636cb8215d59a06d805cacfcf3bfff979a Mon Sep 17 00:00:00 2001 From: "scotts@microsoft.com" Date: Fri, 2 Sep 2016 14:25:37 -0700 Subject: [PATCH] WIP: Cache computed route polylines - Cache computed polylines (against EntityId) for re-use --- LoopTripIOS/MapRouteLineCache.swift | 23 +++-- LoopTripIOS/MapViewController.swift | 152 ++++++++++++++++++++-------- LoopTripIOS/RepositoryManager.swift | 1 - 3 files changed, 124 insertions(+), 52 deletions(-) diff --git a/LoopTripIOS/MapRouteLineCache.swift b/LoopTripIOS/MapRouteLineCache.swift index 15f5af2..9b2fd17 100644 --- a/LoopTripIOS/MapRouteLineCache.swift +++ b/LoopTripIOS/MapRouteLineCache.swift @@ -8,9 +8,10 @@ import Foundation import CoreLocation +import MapKit import LoopSDK -typealias MapRouteLineCacheDataModel = [String: String] +typealias MapRouteLineCacheDataModel = [String: [MKPolyline]] let MapRouteLineCacheAddedContentNotification = "ms.loop.trip.MapRouteLineCacheAddedContentNotification" public class MapRouteLineCache { @@ -26,20 +27,22 @@ public class MapRouteLineCache { return polyLineEntityMapCopy } - func addPolyLine() { + func appendPolyLine(entityId: String, polyline: MKPolyline) { dispatch_barrier_sync(self.concurrentMapRouteLineCacheQueue) { - self._polyLineEntityMap["foo"] = "bar" + if self._polyLineEntityMap[entityId] == nil { + self._polyLineEntityMap[entityId] = [MKPolyline]() + } + + self._polyLineEntityMap[entityId]!.append(polyline) } } - func appendPolyLine() { - dispatch_barrier_sync(self.concurrentMapRouteLineCacheQueue) { - self._polyLineEntityMap["foo"] = "bar" - } - } - - func removeEntityData() { + func removeEntityData(entityId: String) { dispatch_barrier_sync(self.concurrentMapRouteLineCacheQueue) { + guard self._polyLineEntityMap[entityId] != nil else { + return + } + self._polyLineEntityMap.removeAll() } } diff --git a/LoopTripIOS/MapViewController.swift b/LoopTripIOS/MapViewController.swift index c121200..9745286 100644 --- a/LoopTripIOS/MapViewController.swift +++ b/LoopTripIOS/MapViewController.swift @@ -22,20 +22,29 @@ class LoopPointAnnotation: MKPointAnnotation { class MapViewController: UIViewController, MKMapViewDelegate { @IBOutlet weak var mapView: MKMapView! + + private var mapViewUpdateObserver: NSObjectProtocol! var showTrips = true - var tripData:LoopTrip? + var tripData: LoopTrip? + var transportMode = MKDirectionsTransportType.Walking let mapRouteLineCache = MapRouteLineCache.sharedInstance override func viewDidLoad() { super.viewDidLoad() - self.mapView.delegate = self; - self.title = self.showTrips ? "TRIP ROUTE" : "DRIVE ROUTE" + self.mapView.delegate = self + + mapViewUpdateObserver = NSNotificationCenter.defaultCenter() + .addObserverForName(MapRouteLineCacheAddedContentNotification, + object: nil, + queue: NSOperationQueue.mainQueue()) { + notification in + self.contentChangedNotification(notification) + } - if let loopTrip = tripData { - var transportMode = MKDirectionsTransportType.Walking + if let loopTrip = self.tripData { if let loopTripTransportMode = loopTrip.transportMode { switch loopTripTransportMode { case "driving": @@ -51,53 +60,39 @@ class MapViewController: UIViewController, MKMapViewDelegate { NSLog("Trip type: \(transportMode)") - let paths = loopTrip.path - let mapStartEndAnnotations = [ - self.createAnnotationFromLocation(RouteAnnotationPosition.startPosition, loopTripPoint: paths[0]), - self.createAnnotationFromLocation(RouteAnnotationPosition.endPosition, loopTripPoint: paths[paths.count - 1]) - ] - if (transportMode == MKDirectionsTransportType.Automobile) { -// throw this into a background queue async - // create route segments (and overlays) based on mode, speed, and other attributes - createRouteForMode(paths[0].coordinate, destinationLocation: paths[1].coordinate, routeType: transportMode) - var index = 0 - repeat { - let nextIndex = findNextRoutePointIndex(loopTrip, currentIndex: index) - createRouteForMode(paths[index].coordinate, destinationLocation: paths[nextIndex].coordinate, routeType: transportMode) - index = nextIndex - } while index < paths.count - 1 + self.createRoutePathsAsync() } - -// put this into a notification handler (on route done notification) - // set the map to show start/end annotations - mapView.showAnnotations(mapStartEndAnnotations, animated: false) - - // set the map to encompass all of our route points - var mapPoints = paths.enumerate().map { - index, element in - return element.coordinate - } - let routePolyline = MKPolyline(coordinates: &mapPoints, count: mapPoints.count) - self.mapView.setRegion(MKCoordinateRegionForMapRect(routePolyline.boundingMapRect), animated: false) - self.mapView.camera.altitude = self.mapView.camera.altitude * 2.0 - - // if walking or biking use the basic polyline instead of route-based line - if (transportMode != MKDirectionsTransportType.Automobile) { - self.mapView.addOverlay(routePolyline) + else { + self.setMapView() } } else { NSLog("No trip data set for MapView") } } +} + +// MARK - Privates + +extension MapViewController { func setData(tripData: LoopTrip, showTrips: Bool) { self.tripData = tripData self.showTrips = showTrips } - func createAnnotationFromLocation(routePosition: RouteAnnotationPosition, loopTripPoint: LoopTripPoint) -> MKPointAnnotation { + private func contentChangedNotification(notification: NSNotification!) { + switch notification.name { + case MapRouteLineCacheAddedContentNotification: + NSLog("Received update notification in MapView") + self.setMapView() + default: + NSLog("Unknown notification") + } + } + + private func createAnnotationFromLocation(routePosition: RouteAnnotationPosition, loopTripPoint: LoopTripPoint) -> MKPointAnnotation { let annotation = LoopPointAnnotation() annotation.coordinate = loopTripPoint.coordinate @@ -108,7 +103,39 @@ class MapViewController: UIViewController, MKMapViewDelegate { return annotation; } - func createRouteForMode(sourceLocation: CLLocationCoordinate2D, destinationLocation: CLLocationCoordinate2D, routeType: MKDirectionsTransportType) { + private func createRoutePathsAsync() { + if let loopTrip = self.tripData { + let paths = loopTrip.path + + // check in the cache first + if let entityId = loopTrip.entityId { + if let polylines = mapRouteLineCache.polyLineEntityMap[entityId] { + NSLog("Found cached polylines") + + for polyline in polylines { + self.mapView.addOverlay(polyline, level: MKOverlayLevel.AboveRoads) + } + } + else { + NSLog("Creating new route polylines") + + // create route segments (and overlays) based on mode, speed, and other attributes + createRouteForMode(paths[0].coordinate, destinationLocation: paths[1].coordinate, routeType: transportMode) + var index = 0 + repeat { + let nextIndex = findNextRoutePointIndex(loopTrip, currentIndex: index) + createRouteForMode(paths[index].coordinate, destinationLocation: paths[nextIndex].coordinate, routeType: transportMode) + index = nextIndex + } while index < paths.count - 1 + } + } + } + + NSLog("Sending update notification for automobile route") + NSNotificationCenter.defaultCenter().postNotificationName(MapRouteLineCacheAddedContentNotification, object: nil) + } + + private func createRouteForMode(sourceLocation: CLLocationCoordinate2D, destinationLocation: CLLocationCoordinate2D, routeType: MKDirectionsTransportType) { let sourcePlacemark = MKPlacemark(coordinate: sourceLocation, addressDictionary: nil) let destinationPlacemark = MKPlacemark(coordinate: destinationLocation, addressDictionary: nil) @@ -134,11 +161,17 @@ class MapViewController: UIViewController, MKMapViewDelegate { } self.mapView.addOverlay(response.routes[0].polyline, level: MKOverlayLevel.AboveRoads) + if let loopTrip = self.tripData { + if let entityId = loopTrip.entityId { + self.mapRouteLineCache.appendPolyLine(entityId, polyline: response.routes[0].polyline) + } + } + NSLog("Created map overlay for new segment") } } - func findNextRoutePointIndex(loopTrip: LoopTrip, currentIndex: Int) -> Int { + private func findNextRoutePointIndex(loopTrip: LoopTrip, currentIndex: Int) -> Int { let timeOffsetLimit = (60 * 2) // 2 minutes let distanceOffsetLimit = (300.0) // 300 m ~= 900ft (an estimated city block) let averageSpeedLimit = (9.0) // 9 m/s ~= 20 mph @@ -173,9 +206,46 @@ class MapViewController: UIViewController, MKMapViewDelegate { return paths.count - 1 } + + private func setMapView() { + if let loopTrip = self.tripData { + let paths = loopTrip.path + + // set the map to show start/end annotations + let mapStartEndAnnotations = [ + self.createAnnotationFromLocation(RouteAnnotationPosition.startPosition, loopTripPoint: paths[0]), + self.createAnnotationFromLocation(RouteAnnotationPosition.endPosition, loopTripPoint: paths[paths.count - 1]) + ] + + self.mapView.showAnnotations(mapStartEndAnnotations, animated: false) + + // set the map to encompass all of our route points + var mapPoints = paths.enumerate().map { + index, element in + return element.coordinate + } + let routePolyline = MKPolyline(coordinates: &mapPoints, count: mapPoints.count) + + self.mapView.setRegion(MKCoordinateRegionForMapRect(routePolyline.boundingMapRect), animated: false) + self.mapView.camera.altitude = self.mapView.camera.altitude * 2.0 + + // if walking or biking use the basic polyline instead of route-based line + if (self.transportMode != MKDirectionsTransportType.Automobile) { + self.mapView.addOverlay(routePolyline) + } + + if let entityId = loopTrip.entityId { + if let polylines = mapRouteLineCache.polyLineEntityMap[entityId] { + NSLog("Found \(polylines.count) cached polylines for entityId: \(entityId)") + } + } + } + } } - //MARK:- MapViewDelegate methods + +//MARK:- MapViewDelegate methods + extension MapViewController { func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer { let polylineRenderer = MKPolylineRenderer(overlay: overlay) diff --git a/LoopTripIOS/RepositoryManager.swift b/LoopTripIOS/RepositoryManager.swift index 8f88c64..098c28d 100644 --- a/LoopTripIOS/RepositoryManager.swift +++ b/LoopTripIOS/RepositoryManager.swift @@ -12,7 +12,6 @@ let RepositoryManagerAddedContentNotification = "ms.loop.trip.RepositoryManagerA public class RepositoryManager { static let sharedInstance = RepositoryManager() private init() {} - private let concurrentRepositoryManagerQueue = dispatch_queue_create("ms.loop.trip.RepositoryManagerQueue", DISPATCH_QUEUE_CONCURRENT) let driveRepository = DriveRepository.sharedInstance let tripRepository = TripRepository.sharedInstance