我有一个代表公交路线的KML文件数据库。我计划使用Objective-C在iOS中的MKMapView上绘制一条线(如果路线很复杂,有时是多条线)。但是,我在这方面遇到了非常困难的时期,需要你的帮助。您可以在此处查看我正在使用的一些示例KML文件:
我有两种方法可以解决这个问题。我有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方式,因为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方式将使用上面的示例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 ++;
}
}
如果这确实有效(非常罕见),我会完全搞砸了。最简单的向您展示图片:
正如你所看到的,折线循环回来,有时它们只是穿过地图一直到法国的这个村庄!那是第一张照片中那条偏红线的地方。
答案 0 :(得分:1)
至少有两个单独的问题会导致您看到的行为:
{<1}}委托方法在之前被称为 rendererForOverlay
已使用所需的polylineContainer
进行更新,委托方法结束没有返回任何东西(甚至没有)。
您不能真正假设地图视图会调用其委托方法,但当覆盖图位于可见区域时会调用MKPolylineRenderer
。这也可以解释为什么它有效&#34;有时&#34;当您在不在可见区域时添加叠加层并且在您创建并添加渲染器后调用委托方法时,将会出现这种情况。
在这种情况下,折线的rendererForOverlay
仍为title
(因为您在调用{{1}之后设置折线nil
}})。
由于title
中的当前代码无法处理addOverlay
为rendererForOverlay
的情况,并且该方法在此方案中返回 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
中。如果你认为创建渲染器很昂贵并且想要优化性能,那么现在可能还为时过早(除非你已经发现在你的情况下需要这样做)。
线条的原因&#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位于非洲西海岸的大西洋上增加一个坐标。似乎数据或代码如何解释数据是错误的。