0

I am making requests for distance calculation from my current location to campsite locations in an array countySites using the calculate() method of the MKDirections class in a for loop. When I have an array of length 47 campsites only some of the campsite distances are calculated, the remainder produce an error message suggesting too many requests are being made. I don't know how to check that the calculate() isn't too busy before I make a request in the for loop.

I have tried placing a while loop in the code to repeatedly check for a calculation still being processed using the isCalculating Bool property but it fails to stop the error messages.

            countySites = england.counties[selectedCounty!]!

            let sourcePlacemark = MKPlacemark(coordinate: currentCoordinate!)
            let request = MKDirections.Request()

            request.source = MKMapItem(placemark: sourcePlacemark)
            request.transportType = .automobile
            request.requestsAlternateRoutes = false
            let group = DispatchGroup()

            activityIndicator.startAnimating()

            // Step through sites one by one
            for siteIndex in 0..<countySites.count {

                var selectedSite = countySites[siteIndex]
                let destinationPlacemark = MKPlacemark(coordinate: selectedSite.locationCoordinate)

                request.destination = MKMapItem(placemark: destinationPlacemark)

                let distanceAndDirections = MKDirections(request: request)

                if currentCoordinate != nil {

                    group.enter()
                    distanceAndDirections.calculate { (response, error) in
                        if error == nil {

                            let distanceInMetres = response?.routes.first?.distance
                            let distanceInMiles = distanceInMetres! / 1610
                            let roundedDistanceInMiles = Int(distanceInMiles.rounded())
                            let distanceToSite = roundedDistanceInMiles

                            selectedSite.distance = distanceToSite
                            self.countySites[siteIndex] = selectedSite

                        } else {
                            print(error.debugDescription)                            
                        }
                        group.leave()
                    }

                }

            }

            group.notify(queue: .main) {

                self.countySites = self.sortByDistance(sites: self.countySites)

                self.tableView.reloadData()

                self.activityIndicator.stopAnimating()

            }
        }

    }
  • I get the error message from the ```print(error.debugDescription)``` statement ```Optional(Error Domain=MKErrorDomain Code=3 "Directions Not Available" UserInfo={NSLocalizedFailureReason=Route information is not available at this moment., MKErrorGEOError=-3, MKErrorGEOErrorUserInfo={ }, MKDirectionsErrorCode=3, NSLocalizedDescription=Directions Not Available})``` –  Apr 22 '19 at 14:28
  • When I try the ```while``` loop solution, the value of ```isCalculating``` is always false so it never gets to execute the code inside the ```while``` loop block. ```isCalculating``` is only ever ```true``` in the ```else``` statement in the closure when there is a non-nil value to ```error``` –  Apr 22 '19 at 16:09
  • What you might be searching for is a rate limiter. Use this keyword to search an you’ll find solutions. – Gerd Castan Apr 22 '19 at 16:22
  • @Gerd Castan Thanks I'll look it up. –  Apr 22 '19 at 17:25
  • @Gerd Castan I tried the rate limiter but setting the rate to 1 token every second still doesn't help too many requests –  Apr 23 '19 at 17:48
  • Weird. Some random remarks: Should you just need the bee-line, there are much cheaper ways to calculate the distance without calling apples server (CLLocation.distanc()). You can nest rate limiters: 1 per second and 20 per minute and 200 per hour. Since you are throwing away the route anyways, you can use calculateETA() - still calls apples server but is much faster and cheaper for apple and your users. You did not set the transportType, so a distance calculated via a route makes no sense anyways. – Gerd Castan Apr 24 '19 at 05:30
  • @GerdCastan What do you mean by "bee-line"? Also It is the ```MKDirections.calculate()``` method that I am calling. I set the transport type in another function when configuring Location Services. –  Apr 25 '19 at 08:14
  • Bee-line: the shortest distance ignoring streets. For fun and debugging: calculate it to make sure your points are in the same continent. Also check if currentCoordinate is not (0,0) or something stupid. And since you are not using the route, use `calculateETA()` – Gerd Castan Apr 25 '19 at 08:55
  • @GerdCastan My code needs a distance measure along roads and streets for navigation purposes later. My source and destination co-ordinates are always in the UK and ```currentCoordinate``` is never (0,0). The method ```calculateETA()``` will not provide me with distance measurements between source and destination locations so I must continue to use the ```calculate()``` method of the ```MKDirections``` class. –  Apr 26 '19 at 09:15
  • `CalculateETA()`returns an instance of `MKDirections.ETAResponse`which has a property `distance` – Gerd Castan Apr 26 '19 at 10:55
  • @GerdCastan Yes you are right, I will try that and see if it improves the performance of my code, thanks. –  Apr 26 '19 at 11:16
  • @GerdCastan I have tried using the ```calculateETA()``` method and it does speed up my code execution but I am still getting the error messages suggesting I am making too many requests for distance calculation to Apple's servers. –  Apr 26 '19 at 17:12
  • I now see that you are reusing the same request object. Try to create a new request object for each calculation. – Gerd Castan Apr 27 '19 at 07:16
  • @GerdCastan I have tried creating a new ```MKDirections.Request()``` object within the ```for``` loop but I am still getting error messages saying ```Route information is not available at this moment``` and ```Directions Not Available```. I think I am still making too many requests. The obvious solution would seem to be to use the ```isCalculating``` property of ```MKDirections``` but this doesn't seem to tell me when the ```calculateETA()``` method is busy. Any help appreciated –  May 01 '19 at 11:15
  • @GerdCastan There must be a way of using the ```isCalculating``` property to synchronise the calling of ```calculateETA()``` when it is not busy calculating. –  May 01 '19 at 11:37

1 Answers1

0

Only one solution to this problem is Just keep an array of MKRoute and whenever you come across with new path just save into that array and use the same array to draw a path, Using this method you can save mapKit distance calls as well

Check the below snippet which worked for me

var distanceArray: [MKRoute]? // like this create an array inside the class

func drawPath(destination: CLLocationCoordinate2D, source: CLLocationCoordinate2D) {

   let sourcePlacemark = MKPlacemark(coordinate: source, addressDictionary: nil)
   let destinationPlacemark = MKPlacemark(coordinate: destination, addressDictionary: nil)
    
   let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
   let destinationMapItem = MKMapItem(placemark: destinationPlacemark)
    
    
   let directionRequest = MKDirectionsRequest()
   directionRequest.source = sourceMapItem
   directionRequest.destination = destinationMapItem
   directionRequest.transportType = .walking

   let directions = MKDirections(request: directionRequest)
    if distanceArray.count > 0 {
       guard let route = distanceArray.first?.routes else {return}
         self.showPath(route: route)
    } else {
       directions.calculate { (response, error) -> Void in
           guard let response = response else {
              if let error = error {
                 print("Error YuluMapView: \(error)")
              }
              return
            }
           guard let route = response.routes.first else {return}
           distanceArray.append(route)
          self.showPath(route: route)
        }
    }

}

func showPath(route: MKRoute) {
    self.add((route.polyline), level: MKOverlayLevel.aboveRoads)
    
    let rect = route.polyline.boundingMapRect
    self.setVisibleMapRect(rect, edgePadding: defaultEdgeInsets, animated: true)
}

It worked for me 100% and hope you can use this logic to avoid this issue.

Purnendu roy
  • 818
  • 8
  • 15