Multiple Locations on Map (Using Mkmapitem and Clgeocoder)

Geocoding Multiple Locations - Knowing When All Completion Blocks Have Been Called

You can use a GCD dispatch group to do this. Also, I think you can make multiple requests with a single CLGeocoder.

Since we might not need to create the group and the geocoder at all, we'll create them lazily:

-(void)geocodeAllItems {
dispatch_group_t group = NULL;
CLGeocoder *geocoder = nil;

We loop over the items, skipping the ones that have already been geocoded:

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
if (item.eventLocationCLLocation)
continue;

Now that we've found one that needs geocoding, we create the geocoder and the dispatch group if we need to:

        if (!geocoder) {
geocoder = [[CLGeocoder alloc] init];
group = dispatch_group_create();
}

We'll use a helper method to geocode just this item:

        [self geocodeItem:item withGeocoder:geocoder dispatchGroup:group];
}

Now that we've gone through all the items, we'll check whether we geocoded any:

    if (group) {

If we geocoded any, then there are blocks in the dispatch group. We'll ask the group to execute a notification block when it becomes empty:

        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"note: all geocoding requests have completed");
});

Finally, we need to release the group to balance the +1 retain count returned by dispatch_group_create:

        dispatch_release(group);
}
}

Here's the helper method that geocodes just one item:

- (void)geocodeItem:(EventItem *)item withGeocoder:(CLGeocoder *)geocoder dispatchGroup:(dispatch_group_t)group {

We “enter” the group. This increments the group's membership counter atomically:

    dispatch_group_enter(group);

Then we can start the geocoding:

    [geocoder geocodeAddressString:item.eventLocationGeoQuery completionHandler:^(NSArray *placemarks, NSError *error) {
if (error) {
NSLog(@"error: geocoding failed for item %@: %@", item, error);
} else {

if (placemarks.count == 0) {
NSLog(@"error: geocoding found no placemarks for item %@", item);
} else {
if (placemarks.count > 1) {
NSLog(@"warning: geocoding found %u placemarks for item %@: using the first", item, placemarks.count);
}
CLPlacemark* placemark = placemarks[0];
thisEvent.eventLocationCLLocation = placemarks[0].location;
[[EventItemStore sharedStore] saveItems];
}
}

In the geocoding completion block, after all the work is done, we “leave” the group, which decrements its membership count:

        dispatch_group_leave(group);
}];
}

When the membership count goes to zero, the group will execute the notification block we added at the end of geocodeAllItems.

How to get multiple placemarks from CLGeocoder

I've done some sniffing on the packets and it seems that CLGeocoder doesn't connect to Google's geocoding service, but to Apple's. I've also noticed that I get only one placemark from there every time.

If you want something more sophisticated you should use Google's or other geocoding. I use SVGeocoder (https://github.com/samvermette/SVGeocoder), which has a very similar API to CLGeocoder.

iOS mapview search locations using word

i don't see any problem for using MKAnnotation if you want to display multiple locations.create array of objects formed by making lattitude-longitude pair.Then run the loop for array and useaddAnnotations

Refer mkmapview and mkAnnotation.

Batch Geocode MKLocalSearchResponse

The CLGeocoder method, geocodeAddressString, which is referenced in that other question, is designed for geocoding an address.

But you're performing a MKLocalSearch within a particular region, which returns MKMapItem objects which are already geocoded. As a result, you can entirely remove the geocodeAddressString code from your code sample.

In fact, this explains the source of your exception, that you're taking the MKMapItem returned by MKLocalSearch, and trying to use it as the search string parameter of CLGeocoder method geocodeAddressString (which is only used for looking up string representations of addresses).

Bottom line, to do a local search and show the results in the Maps app is far easier than what you've contemplated above. All you need to do is:

MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = searchString;
request.region = region;
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];

[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
[MKMapItem openMapsWithItems:response.mapItems launchOptions:nil];
}];

Or, if you wanted to show it on your own MKMapView, you could do something like

MKLocalSearchRequest *request = [[MKLocalSearchRequest alloc] init];
request.naturalLanguageQuery = searchString;
request.region = region;
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];

[search startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
for (MKMapItem *item in response.mapItems) {
[self.mapView addAnnotation:item.placemark];
}
}];

In practice, in this latter example where you're showing results on your own map view, you might create your own custom annotation class so that you have greater control over your rendering of the annotation view in viewForAnnotation method. But hopefully this illustrates the main observation, that when using MKLocalSearch, you should not be using CLGeocoder method geocodeAddressString at all.

CLGeocoder returning locations in other countries

Though it seems unlikely, it certainly appears that either a) Apple didn't intend on forward geocoding business names near a region, or b) this is a bug.

The CLGeocoder reference, in its overview, states:

Forward-geocoding requests take a user-readable address and find the corresponding latitude and longitude value...

which certainly implies a real physical address as the search string (especially the "user-readable address" part). However, the docs for geocodeAddressString: inRegion: completionHandler: states that the search string is:

... a string describing the location you want to look up...

which is even more vague. I tried running your code, and code very similar to it, through a couple of my projects and I get the same result. Even Apple's GeocoderDemo sample exhibits the problem (although I live in California just ~150 miles from your lat/long example), so it's certainly nothing either of us have written.

I set out looking to help solve this problem for/with you! But, this bit of is research is all I've got. Best of luck.

MKMapItem.openInMaps() displays place mark exactly 50% of the time

Disclaimer

Yes, there seem to be a bug in mapItem.openInMaps, which opens driving directions as an overlay.

As accurately described by @return0:

...I tried your code and the problem lies in the fact that once you have the location centered and showing, if you don't dismiss it and launch the app again it will not show...

More Oddities

  1. The second time around, mapItem.openInMaps does dismiss the driving directions overlay (instead showing the new location)
  2. If the user closes said overlay, Map is no longer confused (*)

(*) A further oddity to that situation is that once that overlay is closed, the location disappears.

Bug? → Workaround!

Force the directions overlay to go away by requesting more than one location. In practice, it means invoking Maps with twice the same location:

MKMapItem.openMaps(with: [mapItem, mapItem], launchOptions: options)

no directions, but not stuck directions but not stuck

Workaround bonus

Even if the user taps on the anchor and requests driving direction, subsequent invocation to Maps from your application to that or a different location will not leave it hanging. In other words, it works as expected.



Complete Swift 3 Source Code

func displayMap(_ address:String, name:String, tph:String)
{
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemark: [CLPlacemark]?, error: Error?) -> Void in
assert(nil == error, "Unable to geocode \(error)")
if error == nil
{
if let placemark = placemark, placemark.count > 0
{
let location = placemark.first
let latitude = (location?.location?.coordinate.latitude)!
let longitude = (location?.location?.coordinate.longitude)!
let coordinates = CLLocationCoordinate2DMake(latitude, longitude)

let regionDistance:CLLocationDistance = 10000
let regionSpan = MKCoordinateRegionMakeWithDistance(coordinates, regionDistance, regionDistance)

let options = [
MKLaunchOptionsMapCenterKey: NSValue(mkCoordinate: regionSpan.center),
MKLaunchOptionsMapSpanKey: NSValue(mkCoordinateSpan: regionSpan.span)
]
let placemark = MKPlacemark(coordinate: coordinates, addressDictionary: nil)
let mapItem = MKMapItem(placemark: placemark)

mapItem.name = name
mapItem.phoneNumber = tph
MKMapItem.openMaps(with: [mapItem, mapItem], launchOptions: options)
} else {
print("Something wrong with \(placemark)")
}
}
}
}

...and invocation

@IBAction func doApple() {
displayMap("1 Infinite Loop, Cupertino, California", name: "Apple", tph: "(408) 996–1010")
}

@IBAction func doMicrosoft() {
displayMap("One Microsoft Way, Redmond, WA", name: "Microsoft", tph: "1-800-MICROSOFT")
}

@IBAction func doIBM() {
displayMap("1 New Orchard Road. Armonk, New York", name: "IBM", tph: "(914) 499-1900")
}


Related Topics



Leave a reply



Submit