Несколько местоположений на карте (используя MKMapItem и CLGeocoder)

Я пытаюсь отобразить несколько мест в MKMapItem, Я получаю эти места из CLGeocoderК сожалению, он принимает только одно местоположение. Хотя я прохожу в NSArray он просто возвращает одно местоположение.

Следующее прекрасно работает с одним местоположением, но не с несколькими местоположениями. Как я могу геокодировать несколько мест?

Class mapItemClass = [MKMapItem class];
if (mapItemClass && [mapItemClass respondsToSelector:@selector(openMapsWithItems:launchOptions:)]) {
    NSArray *addresses = @[@"Mumbai",@"Delhi","Banglore"];

    CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    [geocoder geocodeAddressString:@[addresses] completionHandler:^(NSArray *placemarks, NSError *error) {
        CLPlacemark *geocodedPlacemark = [placemarks objectAtIndex:0];
        MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:geocodedPlacemark.location.coordinate addressDictionary:geocodedPlacemark.addressDictionary];
        MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
        [mapItem setName:geocodedPlacemark.name];

        [MKMapItem openMapsWithItems:@[mapItem] launchOptions:nil];
    }];
}

3 ответа

Решение

Отвечая на ваш вопрос, вы правы, что можете отправлять только один запрос геокода одновременно. На самом деле в справочнике по классам CLGeocoder говорится, что наши приложения должны "отправлять не более одного запроса геокодирования для любого действия пользователя".

Итак, для этого необходимо отправить отдельные запросы. Но эти запросы (которые выполняются асинхронно) не должны выполняться одновременно. Итак, вопрос заключается в том, как заставить серию асинхронных запросов геокодирования выполняться последовательно, один за другим.

Есть много разных способов решения этой проблемы, но один особенно элегантный подход заключается в использовании одновременного NSOperation подкласс, который не завершает операцию (т.е. не выполняет isFinished KVN) до тех пор, пока не будет вызван блок асинхронного завершения запроса геокода. (Для получения информации о параллельных операциях см. Раздел " Настройка операций для параллельного выполнения " главы "Очередь операций" в Руководстве по программированию параллелизма). Затем просто добавьте эти операции в очередь последовательных операций.

Другой подход состоит в том, чтобы заставить этот асинхронный запрос геокодирования вести себя синхронно, и тогда вы можете просто добавить запросы в последовательную очередь, и запросы будут выполняться последовательно, а не параллельно. Этого можно добиться, используя семафоры, эффективно инструктируя отправленную задачу не возвращать до завершения запроса геокодирования. Вы можете сделать это так:

CLGeocoder *geocoder = [[CLGeocoder alloc]init];
NSMutableArray *mapItems = [NSMutableArray array];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;   // make it a serial queue

NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
    [MKMapItem openMapsWithItems:mapItems launchOptions:nil];
}];

NSArray *addresses = @[@"Mumbai, India", @"Delhi, India", @"Bangalore, India"];

for (NSString *address in addresses) {
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

        [geocoder geocodeAddressString:address completionHandler:^(NSArray *placemarks, NSError *error) {
            if (error) {
                NSLog(@"%@", error);
            } else if ([placemarks count] > 0) {
                CLPlacemark *geocodedPlacemark = [placemarks objectAtIndex:0];
                MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:geocodedPlacemark.location.coordinate
                                                               addressDictionary:geocodedPlacemark.addressDictionary];
                MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:placemark];
                [mapItem setName:geocodedPlacemark.name];

                [mapItems addObject:mapItem];
            }
            dispatch_semaphore_signal(semaphore);
        }];

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }];

    [completionOperation addDependency:operation];
    [queue addOperation:operation];
}

[[NSOperationQueue mainQueue] addOperation:completionOperation];

В качестве альтернативы, вы также можете использовать более традиционные модели. Например, вы можете написать метод, который выполняет один запрос геокодирования, и в блоке завершения инициирует следующий запрос и повторять этот процесс, пока не будут выполнены все запросы.

Для парней, которые ищут Swift Solution:

func getCoordinate( addressString : String, completionHandler: @escaping(CLLocationCoordinate2D, NSError?) -> Void ){
            let geocoder = CLGeocoder()
            geocoder.geocodeAddressString(addressString) { (placemarks, error) in
                if error == nil {
                    if let placemark = placemarks?[0] {
                        let location = placemark.location!
                        completionHandler(location.coordinate, nil)
                        return
                    }
                }

                completionHandler(kCLLocationCoordinate2DInvalid, error as NSError?)
            }
        }

Спасибо Apple. Официальная ссылка на документ

Вы можете использовать фрагмент следующим образом для нескольких адресов:

let address = ["India","Nepal"]

for i in 0..<address.count {
 getCoordinate(addressString: address[i], completionHandler: { (location, error) in
                //Do your stuff here. For e.g. Adding annotation or storing location
      })

}

Для современных читателей Swift,async-awaitзначительно упрощает процесс:

      func mapItems(for strings: [String]) async throws -> [MKMapItem] {
    var mapItems: [MKMapItem] = []
    let geocoder = CLGeocoder()
    
    for string in strings {
        try await geocoder.geocodeAddressString(string)
            .map { MKPlacemark(placemark: $0) }
            .map { MKMapItem(placemark: $0) }
            .forEach { mapItems.append($0) }
    }
    
    return mapItems
}

Параллелизм Swift обрабатывает зависимости между асинхронными задачами гораздо элегантнее, чем старыйNSOperationподход, который нам пришлось реализовать в Objective-C.


Или, если искать на карте, возможноMKLocalSearch:

      extension MKMapView {
    func mapItems(for strings: [String]) async throws -> [MKMapItem] {
        var results: [MKMapItem] = []
        
        for string in strings {
            let request = MKLocalSearch.Request()
            request.naturalLanguageQuery = string
            request.region = region
            let mapItems = try await MKLocalSearch(request: request).start().mapItems
            results.append(contentsOf: mapItems)
        }
        
        return results
    }
}

Излишне говорить, что, как отмечалось выше, сама идея множественных запросов геокодирования является антишаблоном, явно не поощряемым вCLGeocoder документация .

Другие вопросы по тегам