在MKMapView中将KML文件映射到折线

时间:2014-08-07 04:02:48

标签: ios objective-c mkmapview

概述:

我有一个代表公交路线的KML文件数据库。我计划使用Objective-C在iOS中的MKMapView上绘制一条线(如果路线很复杂,有时是多条线)。但是,我在这方面遇到了非常困难的时期,需要你的帮助。您可以在此处查看我正在使用的一些示例KML文件:

  • 示例A:将在生产中使用的完整KML文件
  • 示例B:上述完整文件中的单个路径
  • 示例C:重新格式化的KML文件为JSON

我有两种方法可以解决这个问题。我有JSON方式和XML方式。在使用不同的示例KML文件时,两者都无法正常工作,并且会出现不同的结果问题。

在JSON和KML方法中,我使用此代码返回mapview的叠加层

- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay{
    // Checking if the called overlay is a polyline
    if([overlay class] == MKPolyline.class){

        // Make the polyline
        MKPolyline* polyline = (MKPolyline *)overlay;
        @try {

            // Extract the title and make sure it isnt nil
            NSString *title = polyline.title;
            if(title){

                // Grab the PolyLine from the container object using the title
                MKPolylineRenderer * rtn = [self.polylineContainer objectForKey:title];
                if(rtn){
                    return rtn;
                } else {
                    return nil;
                }
            }
        }
        @catch (NSException *exception) {
            NSLog(@"%@",exception);
        }
    } else {
        return nil;
    }
}

JSON Way

就我个人而言,我更喜欢使用JSON方式,因为Objective C在使用JSON序列化(至少在我的指点)比XML更好。我有一个PHP脚本设置为只提取<MultiGeometry>节点以及<LineString>节点。这个脚本似乎没有问题,所以我会在这个问题中省略它,但是如果您愿意,请询问并且我会添加它。

KML方式将使用上面的示例C,并始终在[self.mapView addOverlay:polyline];行失败,并将无法识别的选择器发送到实例。它还会触发EXEC_BAD_ACCESS异常,但我无法追踪它发生的位置(即使有异常断点)

// A Synchrnous URLRequest is performed, and the JSON is serialised into response_root

// Metadata.  Used for iteration later.
NSDictionary * meta = [response_root valueForKey:@"meta"];
NSInteger mg_count = [[meta valueForKey:@"MultiGeometryCount"] integerValue];
NSInteger ls_count = [[meta valueForKey:@"LineStringCount"] integerValue];

// The Data dictionary holds the data.  Obviously
NSDictionary * data = [response_root valueForKey:@"data"];

// mgi is just short for MultiGeometry.  It contains LineStrings (lsi)
int mgi = 0;

// Loop through the MultiGeometry nodes
while (mgi < mg_count) {

    // Grab the Root Node
    NSDictionary * root_node = [data valueForKey:[NSString stringWithFormat:@"root_%d",mgi]];

    // lsi is just short for LineString.  It contains the coordinates in a JSON object
    int lsi = 0;
    while (lsi < ls_count) {

        // Grab the sub node containing all of the coordinate pairs
        NSDictionary * sub_node = [root_node valueForKey:[NSString stringWithFormat:@"node_%d",lsi]];
        NSInteger pair_count = [[sub_node valueForKey:@"CoordPairCount"] integerValue];
        int pc = 0;

        // Set up the C Array for the Coordinates
        CLLocationCoordinate2D coordinates[pair_count];

        // Loop through the pairs
        while (pc < pair_count) {

            // Grab the Pair Node
            NSDictionary * pair_node = [sub_node valueForKey:[NSString stringWithFormat:@"set_%d",pc]];

            // Set X and Y and add them to the coordinate array
            double longtitude = [[pair_node valueForKey:@"x"] doubleValue];
            double latitude = [[pair_node valueForKey:@"y"] doubleValue];
            coordinates[pc].latitude = latitude;
            coordinates[pc].longitude = longtitude;
            pc ++;
        }

        // When we've finished with all of the pairs, we create the polyline
        MKPolyline * polyline = [[MKPolyline alloc] init];
        polyline = [MKPolyline polylineWithCoordinates:coordinates count:pair_count];
        if(polyline){
            @try {

                // This always triggers a "Unrecognised selector sent to instance" exception.  Although polyline is correctly set
                [self.mapView addOverlay:polyline];
            }
            @catch (NSException *exception) {
                NSLog(@"%@",exception);
            }

            // Create the rendered line and set its properties
            MKPolylineRenderer * line = [[MKPolylineRenderer alloc] initWithPolyline:polyline];
            line.strokeColor = [UIColor blueColor];
            line.lineWidth = 2;
            line.polyline.title = [NSString stringWithFormat:@"ls_%d", lsi];

            // Add it to the polyline container, which is just a NSMutableDictionary
            [self.polylineContainer setObject:line forKey:[NSString stringWithFormat:@"ls_%d", lsi]];
        }
        lsi ++;
    }
    mgi ++;
}

KML Way

KML方式将使用上面的示例A和B,并且始终在[self.mapView addOverlay:polyline];

失败
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
    if ([elementName isEqualToString:@"coordinates"]) {

        // The coordinate pairs are provided as one big string, I remove the garbage charters from it
        coords = [coords stringByReplacingOccurrencesOfString:@"0 -" withString:@"-"];
        coords = [coords stringByReplacingOccurrencesOfString:@"\n" withString:@""];
        coords = [coords stringByReplacingOccurrencesOfString:@" " withString:@""];
        coords = [coords stringByReplacingOccurrencesOfString:@"(null)" withString:@""];

        // Split the string into a big array
        NSArray * t = [[NSArray alloc] initWithArray:[coords componentsSeparatedByString:@","]];

        // Create the C Array for the coordinates
        CLLocationCoordinate2D coordinates[t.count];

        // Because the X coordinate comes first, I use this toggle to go back between setting the X then the Y and loop
        int isXorY = 0;
        int i = 0;
        double x = 0;
        double y = 0;
        for (NSString * c in t) {

            // X always comes first
            if(isXorY == 0){
                isXorY = 1;
                x = [c doubleValue];

            // Then comes the Y
            } else if(isXorY == 1){
                isXorY = 0;
                y = [c doubleValue];
            }

            // If both the X and the Y coordinate are set, add the pair to the Coordinates array and start over again
            if(x != 0 && y != 0){
                coordinates[i].latitude = y;
                coordinates[i].longitude = x;
                x = 0;
                y = 0;
                i ++;
            }
        }

        // Create the Polyline using the coordinates
        MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coordinates count:i];

        // This always triggers a "Unrecognised selector sent to instance" exception.  Although polyline is correctly set
        [self.mapView addOverlay:polyline];

        // Create the polyline and set its properties
        MKPolylineRenderer * line = [[MKPolylineRenderer alloc] initWithPolyline:polyline];
        line.strokeColor = [UIColor blueColor];
        line.lineWidth = 2;
        line.polyline.title = [NSString stringWithFormat:@"ls_%d", totalCordPairs];

        // Add it to the container object with its name.  Polylinecontainer is just a NSMutableDictionary
        [self.polylineContainer setObject:line forKey:[NSString stringWithFormat:@"ls_%d", totalCordPairs]];

        // This is global integer that is used with the above name
        totalCordPairs ++;
    }
}

结果

如果这确实有效(非常罕见),我会完全搞砸了。最简单的向您展示图片:

Basically, it doesnt work.

正如你所看到的,折线循环回来,有时它们只是穿过地图一直到法国的这个村庄!那是第一张照片中那条偏红线的地方。

1 个答案:

答案 0 :(得分:1)

至少有两个单独的问题会导致您看到的行为:

  1. {<1}}委托方法在之前被称为 rendererForOverlay已使用所需的polylineContainer进行更新,委托方法结束没有返回任何东西(甚至没有)。

    您不能真正假设地图视图会调用其委托方法,但当覆盖图位于可见区域时会调用MKPolylineRenderer。这也可以解释为什么它有效&#34;有时&#34;当您在不在可见区域时添加叠加层并且在您创建并添加渲染器后调用委托方法时,将会出现这种情况。

    在这种情况下,折线的rendererForOverlay仍为title(因为您在调用{{1}之后设置折线nil }})。

    由于title中的当前代码无法处理addOverlayrendererForOverlay的情况,并且该方法在此方案中返回 nothing

    返回任何内容的方法(甚至不是title)是调用nil时导致异常的原因。基本上,地图视图最终会访问渲染器的垃圾值,在您的情况下会导致无法识别的选择器&#34;异常。

    在最后总是至少返回nil以处理不可预见的此类情况,这是一种很好的做法。

    然而,真正的修复方法是将渲染器的创建移至addOverlay委托方法。您仍然可以保留nil方法,但如果对象是在那里找不到,然后在委托方法中创建并添加渲染器。

    这是修复的一个例子......

    在您创建rendererForOverlay的部分:

    polylineContainer

    然后在MKPolyline

    //MKPolyline * polyline = [[MKPolyline alloc] init];
    //The above alloc+init is unnecessary since the polylineWithCoordinates
    //method effectively does that for you.
    
    MKPolyline * polyline = [MKPolyline polylineWithCoordinates:coordinates 
                                                          count:pair_count];
    
    //set the polyline's title BEFORE adding it to the map view...
    polyline.title = [NSString stringWithFormat:@"ls_%d", lsi];
    
    //Call addOverlay but then do NOT create the renderer
    //and add to polylineContainer HERE.  Comment that code out. 
    

    顺便说一下,不清楚为什么要将渲染器存储在rendererForOverlay中。如果你认为创建渲染器很昂贵并且想要优化性能,那么现在可能还为时过早(除非你已经发现在你的情况下需要这样做)。

  2. 线条的原因&#34;循环回来&#34;或者出现在法国一个村庄等意想不到的地方可能是因为数据不好。查看或记录要添加到折线的坐标并确认它们是否正确。例如,在您在问题中链接到的JSON文件中,有很多这些:

    - (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay{
        // Checking if the called overlay is a polyline
        if([overlay class] == MKPolyline.class){
    
            // Make the polyline
            MKPolyline* polyline = (MKPolyline *)overlay;
            @try {
    
                // Extract the title and make sure it isnt nil
                NSString *title = polyline.title;
                if(title){
    
                    // Grab the PolyLine from the container object using the title
                    MKPolylineRenderer * rtn = [self.polylineContainer objectForKey:title];
    
                    //HERE, if we did not get already-created renderer,
                    //create it now and add to polylineContainer...
                    if (rtn == nil)
                    {
                        // Create the rendered line and set its properties
                        rtn = [[MKPolylineRenderer alloc] initWithPolyline:polyline];
                        rtn.strokeColor = [UIColor blueColor];
                        rtn.lineWidth = 2;
    
                        // Add it to the polyline container, which is just a NSMutableDictionary
                        [self.polylineContainer setObject:rtn forKey:title];
                    }                   
    
                    if(rtn){
                        return rtn;
                    } else {
                        return nil;
                    }
                }
            }
            @catch (NSException *exception) {
                NSLog(@"%@",exception);
            }
        } else {
            return nil;
        }
    
        //Always return a default from a method 
        //that is supposed to return something...
        return nil;
    }
    

    这最终将在0,0位于非洲西海岸的大西洋上增加一个坐标。似乎数据或代码如何解释数据是错误的。