-3

I am trying to load my Map which I created with a kml File in Google-Maps. The Google Maps Link. I have to say it is only an example, but it is the same principle.

The easiest way is to load in a WebView, but that is ugly in my eyes.

Thank you for reading my Question!

Best regards CTS

Rob
  • 415,655
  • 72
  • 787
  • 1,044
CTSchmidt
  • 1,155
  • 3
  • 17
  • 30

1 Answers1

4

To load a KML into a MKMapView:

  1. Add the necessary frameworks (MapKit.framework and CoreLocation.framework) to your target;

  2. Load and parse the KML;

  3. Create your annotations on the basis of the KML; and

  4. Set your map's region to encompass the annotations.

Thus, that might look like:

#import <MapKit/MapKit.h>

- (void)loadKml:(NSURL *)url
{
    // parse the kml

    Parser *parser = [[Parser alloc] initWithContentsOfURL:url];
    parser.rowElementName = @"Placemark";
    parser.elementNames = @[@"name", @"Snippet", @"coordinates", @"description"];
    parser.attributeNames = nil;
    [parser parse];

    // add annotations for each of the entries

    for (NSDictionary *locationDetails in parser.items)
    {
        MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
        annotation.title = locationDetails[@"name"];
        annotation.subtitle = locationDetails[@"Snippet"];
        NSArray *coordinates = [locationDetails[@"coordinates"] componentsSeparatedByString:@","];
        annotation.coordinate = CLLocationCoordinate2DMake([coordinates[1] floatValue], [coordinates[0] floatValue]);
        [self.mapView addAnnotation:annotation];
    }

    // update the map to focus on the region that encompasses all of your annotations

    MKCoordinateRegion region;
    if ([self.mapView.annotations count] > 1)
    {
        region = [self regionForAnnotations:self.mapView.annotations];
        region = MKCoordinateRegionMake(region.center, MKCoordinateSpanMake(region.span.latitudeDelta * 1.05, region.span.longitudeDelta * 1.05));  // expand the region by 5%
    }
    else
    {
        id<MKAnnotation> annotation = self.mapView.annotations[0];
        region = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 100.0, 100.0);
    }
    [self.mapView setRegion:region animated:YES];
}

My Parser class is just a NSXMLParser subclass that I've written that will create an array of items one per occurrence of rowElementName, and for each row, it will grab the elements listed in the elementNames array.

Parser.h:

#import <Foundation/Foundation.h>

@interface Parser : NSXMLParser

@property (nonatomic, strong) NSString *rowElementName; // this is the element name that identifies a new row of data in the XML
@property (nonatomic, strong) NSArray *attributeNames;  // this is the array of attributes we might want to retrieve for that element name
@property (nonatomic, strong) NSArray *elementNames;    // this is the list of sub element names for which we're retrieving values

@property (nonatomic, strong) NSMutableArray *items;    // after parsing, this is the array of parsed items

@end

Parser.m:

#import "Parser.h"

@interface Parser () <NSXMLParserDelegate>

@property (nonatomic, strong) NSMutableDictionary *item;     // while parsing, this is the item currently being parsed
@property (nonatomic, strong) NSMutableString *elementValue; // this is the element within that item being parsed

@end

@implementation Parser

- (id)initWithContentsOfURL:(NSURL *)url
{
    self = [super initWithContentsOfURL:url];

    if (self)
    {
        self.delegate = self;
    }

    return self;
}

- (id)initWithData:(NSData *)data
{
    self = [super initWithData:data];

    if (self)
    {
        self.delegate = self;
    }

    return self;
}

- (id)initWithStream:(NSInputStream *)stream
{
    self = [super initWithStream:stream];

    if (self)
    {
        self.delegate = self;
    }

    return self;
}

#pragma mark - NSXMLParserDelegate methods

- (void)parserDidStartDocument:(NSXMLParser *)parser
{
    self.items = [[NSMutableArray alloc] init];

    if (!self.rowElementName)
        NSLog(@"%s Warning: Failed to specify row identifier element name", __FUNCTION__);
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
    if ([elementName isEqualToString:self.rowElementName])
    {
        self.item  = [[NSMutableDictionary alloc] init];

        for (NSString *attributeName in self.attributeNames)
        {
            id attributeValue = [attributeDict valueForKey:attributeName];
            if (attributeValue)
                [self.item setObject:attributeValue forKey:attributeName];
        }
    }
    else if ([self.elementNames containsObject:elementName])
    {
        self.elementValue = [[NSMutableString alloc] init];
    }
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
    if (self.elementValue)
    {
        [self.elementValue appendString:string];
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
    if ([elementName isEqualToString:self.rowElementName])
    {
        [self.items addObject:self.item];
        self.item = nil;
    }
    else if ([self.elementNames containsObject:elementName])
    {
        [self.item setValue:self.elementValue forKey:elementName];
        self.elementValue = nil;
    }
}

@end

Finally, the only other utility method that my loadKml uses is regionForAnnotations, which defines a region based upon a series of annotations. Rob Mooney wrote a simple routine to do that:

- (MKCoordinateRegion)regionForAnnotations:(NSArray *)annotations {

    CLLocationDegrees minLat = 90.0;
    CLLocationDegrees maxLat = -90.0;
    CLLocationDegrees minLon = 180.0;
    CLLocationDegrees maxLon = -180.0;

    for (id <MKAnnotation> annotation in annotations) {
        if (annotation.coordinate.latitude < minLat) {
            minLat = annotation.coordinate.latitude;
        }
        if (annotation.coordinate.longitude < minLon) {
            minLon = annotation.coordinate.longitude;
        }
        if (annotation.coordinate.latitude > maxLat) {
            maxLat = annotation.coordinate.latitude;
        }
        if (annotation.coordinate.longitude > maxLon) {
            maxLon = annotation.coordinate.longitude;
        }
    }

    MKCoordinateSpan span = MKCoordinateSpanMake(maxLat - minLat, maxLon - minLon);

    CLLocationCoordinate2D center = CLLocationCoordinate2DMake((maxLat - span.latitudeDelta / 2), maxLon - span.longitudeDelta / 2);

    return MKCoordinateRegionMake(center, span);
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • wow thank you! this is the most comprehensive answer I've ever received. I try it soon as I can. CTS – CTSchmidt Jan 28 '13 at 16:32
  • little question: `KmlAnnotation *annotation = self.mapView.annotations[0]; region = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 100.0, 100.0);` result in errors: `undeclared identifier`. Do you know how to fix that? – CTSchmidt Feb 05 '13 at 12:57
  • Sorry, replace that with "`id annotation = ...`. In my app I actually used a custom annotation, and one of those references to that slipped through my attempts to edit the code for simplicity/brevity. – Rob Feb 05 '13 at 13:11