在iOS中的MapKit中绘制路线

时间:2010-05-14 13:37:24

标签: ios iphone mapkit

我想在地图上的两个位置之间绘制一条路线。像导游一样的东西。当游客点击另一个位置时,我希望能够绘制一条路线;以及通知当前位置的距离。

我知道互联网上的网站告诉我们如何在地图上绘制折线。但是,大多数示例都有一个带有各种坐标的预加载的.csv文件。

是否有另一种方法可以从Google或任何其他提供商处获取坐标,因为该位置是动态选择的。

如果否,我如何获取中间坐标的信息?

iOS 6是否提供了解决此问题的直接方法?

10 个答案:

答案 0 :(得分:61)

以下viewDidLoad将(1)设置两个位置,(2)删除所有先前的注释,以及(3)调用用户定义的辅助函数(获取路径点并绘制路径)。

-(void)viewDidLoad
{
    [super viewDidLoad];

    // Origin Location.
    CLLocationCoordinate2D loc1;
    loc1.latitude = 29.0167;
    loc1.longitude = 77.3833;
    Annotation *origin = [[Annotation alloc] initWithTitle:@"loc1" subTitle:@"Home1" andCoordinate:loc1];
    [objMapView addAnnotation:origin];

    // Destination Location.
    CLLocationCoordinate2D loc2;
    loc2.latitude = 19.076000;
    loc2.longitude = 72.877670;
    Annotation *destination = [[Annotation alloc] initWithTitle:@"loc2" subTitle:@"Home2" andCoordinate:loc2];
    [objMapView addAnnotation:destination];

    if(arrRoutePoints) // Remove all annotations
        [objMapView removeAnnotations:[objMapView annotations]];

    arrRoutePoints = [self getRoutePointFrom:origin to:destination];
    [self drawRoute];
    [self centerMap];
}

以下是MKMapViewDelegate方法,它绘制叠加层(iOS 4.0及更高版本)。

/* MKMapViewDelegate Meth0d -- for viewForOverlay*/
- (MKOverlayView*)mapView:(MKMapView*)theMapView viewForOverlay:(id <MKOverlay>)overlay
{
    MKPolylineView *view = [[MKPolylineView alloc] initWithPolyline:objPolyline];
    view.fillColor = [UIColor blackColor];
    view.strokeColor = [UIColor blackColor];
    view.lineWidth = 4;
    return view;
}

以下函数将获取位置并准备URL以获取所有路线点。当然,会调用stringWithURL。

/* This will get the route coordinates from the Google API. */
- (NSArray*)getRoutePointFrom:(Annotation *)origin to:(Annotation *)destination
{
    NSString* saddr = [NSString stringWithFormat:@"%f,%f", origin.coordinate.latitude, origin.coordinate.longitude];
    NSString* daddr = [NSString stringWithFormat:@"%f,%f", destination.coordinate.latitude, destination.coordinate.longitude];

    NSString* apiUrlStr = [NSString stringWithFormat:@"http://maps.google.com/maps?output=dragdir&saddr=%@&daddr=%@", saddr, daddr];
    NSURL* apiUrl = [NSURL URLWithString:apiUrlStr];

    NSError *error;
    NSString *apiResponse = [NSString stringWithContentsOfURL:apiUrl encoding:NSUTF8StringEncoding error:&error];
    NSString* encodedPoints = [apiResponse stringByMatching:@"points:\\\"([^\\\"]*)\\\"" capture:1L];

    return [self decodePolyLine:[encodedPoints mutableCopy]];
}

以下代码是真正的魔力(我们从API获得的响应的解码器)。我不会修改那段代码,除非我知道我在做什么:)

- (NSMutableArray *)decodePolyLine:(NSMutableString *)encodedString
{
    [encodedString replaceOccurrencesOfString:@"\\\\" withString:@"\\"
                                  options:NSLiteralSearch
                                    range:NSMakeRange(0, [encodedString length])];
    NSInteger len = [encodedString length];
    NSInteger index = 0;
    NSMutableArray *array = [[NSMutableArray alloc] init];
    NSInteger lat=0;
    NSInteger lng=0;
    while (index < len) {
        NSInteger b;
        NSInteger shift = 0;
        NSInteger result = 0;
        do {
            b = [encodedString characterAtIndex:index++] - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
        } while (b >= 0x20);
        NSInteger dlat = ((result & 1) ? ~(result >> 1) : (result >> 1));
        lat += dlat;
        shift = 0;
        result = 0;
        do {
            b = [encodedString characterAtIndex:index++] - 63;
            result |= (b & 0x1f) << shift;
            shift += 5;
       } while (b >= 0x20);
        NSInteger dlng = ((result & 1) ? ~(result >> 1) : (result >> 1));
        lng += dlng;
        NSNumber *latitude = [[NSNumber alloc] initWithFloat:lat * 1e-5];
        NSNumber *longitude = [[NSNumber alloc] initWithFloat:lng * 1e-5];
        printf("\n[%f,", [latitude doubleValue]);
        printf("%f]", [longitude doubleValue]);
        CLLocation *loc = [[CLLocation alloc] initWithLatitude:[latitude floatValue] longitude:[longitude floatValue]];
        [array addObject:loc];
    }
    return array;
}

此功能将绘制路线并添加叠加。

- (void)drawRoute
{
    int numPoints = [arrRoutePoints count];
    if (numPoints > 1)
    {
        CLLocationCoordinate2D* coords = malloc(numPoints * sizeof(CLLocationCoordinate2D));
        for (int i = 0; i < numPoints; i++)
        {
            CLLocation* current = [arrRoutePoints objectAtIndex:i];
            coords[i] = current.coordinate;
        }

        self.objPolyline = [MKPolyline polylineWithCoordinates:coords count:numPoints];
        free(coords);

        [objMapView addOverlay:objPolyline];
        [objMapView setNeedsDisplay];
    }
}

以下代码将居中对齐地图。

- (void)centerMap
{
    MKCoordinateRegion region;

    CLLocationDegrees maxLat = -90;
    CLLocationDegrees maxLon = -180;
    CLLocationDegrees minLat = 90;
    CLLocationDegrees minLon = 180;

    for(int idx = 0; idx < arrRoutePoints.count; idx++)
    {
        CLLocation* currentLocation = [arrRoutePoints objectAtIndex:idx];

        if(currentLocation.coordinate.latitude > maxLat)
            maxLat = currentLocation.coordinate.latitude;
        if(currentLocation.coordinate.latitude < minLat)
            minLat = currentLocation.coordinate.latitude;
        if(currentLocation.coordinate.longitude > maxLon)
            maxLon = currentLocation.coordinate.longitude;
        if(currentLocation.coordinate.longitude < minLon)
            minLon = currentLocation.coordinate.longitude;
    }

    region.center.latitude     = (maxLat + minLat) / 2;
    region.center.longitude    = (maxLon + minLon) / 2;
    region.span.latitudeDelta  = maxLat - minLat;
    region.span.longitudeDelta = maxLon - minLon;

    [objMapView setRegion:region animated:YES];
}

我希望这会对某人有所帮助。

答案 1 :(得分:24)

这是一个棘手的问题。使用MapKit无法做到这一点:当您知道坐标时,绘制线条很容易,但MapKit不会让您访问道路或其他路线信息。我要说你需要调用外部API来获取数据。

我一直在玩cloudmade.com API。矢量流服务器应该返回您需要的内容,然后您可以在地图上绘制它。但是,Google地图与cloudmade使用的OSM地图之间的差异可能会让您想要一直使用cloudmade地图:它们与MapKit等效。

P.S。:其他地图提供商 - Google,Bing等也可能提供相应的数据Feed。我最近一直在关注OSM / Cloudmade。

P.P.S。:这些都不是琐碎的新手!祝你好运!

答案 2 :(得分:12)

Andiih说得对。 MapKit不允许你这样做。不幸的是,谷歌不会让你做你想做的事。

当Apple宣布MapKit时,他们还明确表示任何导航应用程序都是BYOM:自带地图,因此任何导航应用程序都使用他们自己的一套地图工具。

Google的服务条款限制您甚至在地图上显示路线:

http://code.google.com/intl/de/apis/maps/iphone/terms.html

许可限制:

  

10.9 将服务或内容与任何产品,系统或应用程序一起使用   用于或与之相关:

     

(a)实时导航或路线   指导,包括但不限于   转弯路线指导即是   同步到a的位置   用户的传感器设备;

     

(b)任何系统或功能   自动或自动控制   车辆行为;或

     

(c)派遣,车队管理,   商业资产跟踪或类似   企业应用程序(谷歌   Maps API可用于跟踪资产   (如汽车,公共汽车或其他   车辆)只要跟踪   申请已提供给   公众免费。例如,   您可以提供免费的公共Maps API   实时显示的实现   公共交通或其他交通工具   状态信息。

可悲的是,这包括你想做的事情。希望有一天MapKit会扩展到允许这样的功能......虽然不太可能。

祝你好运。

答案 3 :(得分:5)

您可能需要查看https://github.com/leviathan/nvpolyline此解决方案特别针对v.4.0之前的iPhone OS版本

虽然它也可以在v.4.0中使用希望这有帮助。

答案 4 :(得分:4)

答案 5 :(得分:4)

使用iOS 7 API非常容易在地图上获取和绘制路线:

MKDirectionsRequest *directionsRequest = [[MKDirectionsRequest alloc] init];

// Set the origin of the route to be current user location
[directionsRequest setSource:[MKMapItem mapItemForCurrentLocation]];

// Set the destination point of the route
CLLocationCoordinate2D destinationCoordinate = CLLocationCoordinate2DMake(34.0872, 76.235);
MKPlacemark *destinationPlacemark = [[MKPlacemark alloc] initWithCoordinate:destinationCoordinate addressDictionary:nil];
[directionsRequest setDestination:[[MKMapItem alloc] initWithPlacemark:destinationPlacemark]];

MKDirections *directions = [[MKDirections alloc] initWithRequest:directionsRequest];

// Requesting route information from Apple Map services
[directions calculateDirectionsWithCompletionHandler:^(MKDirectionsResponse *response, NSError *error) {
    if (error) {
        NSLog(@"Cannot calculate directions: %@",[error localizedDescription]);
    } else {
        // Displaying the route on the map
        MKRoute *route = [response.routes firstObject];
        [mapView addOverlay:route.polyline];
    }
}];

答案 6 :(得分:3)

MapQuest有一个SDK,它是MapKit的直接替代品。它目前处于测试阶段,但正在积极开发中。

它允许叠加,路由和地理编码。

MapQuest iOS Maps API

答案 7 :(得分:3)

只是为了澄清,看起来有几件事情正在讨论中。一种是获取路径顶点的方法,另一种是使用这些顶点在地图上绘制叠加层。我知道MapQuest API,所以我有一些下面的链接 - 谷歌和Bing有我认为的等价物。

1)获取路线的顶点
如果您正在寻找绘制路径覆盖的路线的新坐标,您可以使用对路由Web服务的Web服务调用 - 我假设您在此处使用JavaScript来显示地图。如果您使用的是本机代码,您仍然可以访问Web服务,或者您可以使用本机调用(即MapQuest iPhone SDK中包含本机路由调用)。

大多数路线服务应返回路线的“shapepoint”,以便您可以绘制。

以下是使用MapQuest的一些示例 - 用于获取shapepoint的Web Service(请参阅Shape返回对象) - http://www.mapquestapi.com/directions/

2)绘制叠加层 拥有顶点后,需要绘制顶点。我认为大多数JavaScript map API都有某种覆盖类。这是MapQuest之一: http://developer.mapquest.com/web/documentation/sdk/javascript/v7.0/overlays#line

3)只需一次通话即可 MapQuest也有一些方便的功能来进行路线调用并为你画线 - 我不能发布超过两个链接!所以转到上面的链接,在左侧的导航栏中查找“路由”。

答案 8 :(得分:1)

要更新此问题,iOS7之后不需要外部apk。

这是一个非常简单有效的解决方案:

http://technet.weblineindia.com/mobile/draw-route-between-2-points-on-map-with-ios7-mapkit-api/2/

我知道问题是关于iOS 6的,但我相信这个解决方案对很多人都有用。

此解决方案中唯一缺少的是实现以下委托方法以显示开始和结束引脚

-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation

答案 9 :(得分:0)

根据2019年的数据,使用iOS12或iOS13,在两点之间绘制路线非常容易。 这是我的典型代码,它在两个POI(CLLocation和CLPlacemark)之间绘制了一条路线,您可以轻松地适应上下文。

/// The map view to use to draw the route
lazy var mapView: MKMapView = {
    let m = MKMapView()
    m.delegate = self

    // Attach to the controller view
    view.addSubview(m)
    // Don't forget to setup its details
    return m
}()

/// Having the source:
let from: CLLocation

/// And the destination
let to: CLPlacemark


/// Compute and draw the route between the two POI.
func getAndDrawRoute() {

    /// Get the route, driving

    let ok = route(from: from, to: to, transportType: .automobile) { routes, error in
        if let error = error {
            // *** Handle the error here
            print("\(type(of: self)).\(#function): *** Error: \(error)")

            // blah blah
            return
        }

        // Get the route among the multiple possibilities. Here we take the first one to keep this sniper short

        guard let route = routes.first else {
            // *** Handle the error: no route exits
            print("\(type(of: self)).\(#function): *** Warning: no route exits")
            // blah blah
            return 
        }

        // Route exists
        print("Found the route: \(route)")

        // Draw it
        self.mapView.draw(route: route)
    }
}

/**
Route from a source to the destination locations.

- Parameters:
    - from: The source location;
    - toPlacemark: The destination `MKPlaceMark`;
    - transportType: The transport type;
    - completion: The completion closure.

- Returns: `true` if the route can be traced, or false if the user's position is not yet available

*/
public func route(from source: CLLocation, toMKPlacemark destinationPlacemark: MKPlacemark, transportType: MKDirectionsTransportType, completion: @escaping RouteCompletion) {

    let sourcePlacemark = MKPlacemark(coordinate: source.coordinate)

    let sourceMapItem = MKMapItem(placemark: sourcePlacemark)
    let destinationMapItem = MKMapItem(placemark: destinationPlacemark)

    let directionRequest = MKDirections.Request()
    directionRequest.source = sourceMapItem
    directionRequest.destination = destinationMapItem
    directionRequest.transportType = transportType

    // Calculate the direction
    let directions = MKDirections(request: directionRequest)

    // And get the routes
    directions.calculate { response, error in

        guard let response = response else {
            if let error = error {
                print("\(type(of: self)).\(#function): *** Error: \(error.localizedDescription)")
            }
            completion(nil, error)
            return
        }

        completion(response.routes, nil)
    }
}

/// Adds the route overlay
public func draw(route: MKRoute) {

    mapView.addOverlay(route.polyline)
}

/// Renders the overlays, inclusion the route
public func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    // If you want to draw a circle to fit your specifics
    if overlay is MKCircle {
        let renderer = MKCircleRenderer(overlay: overlay)
        renderer.fillColor      = UIColor.blue.withAlphaComponent(0.1)
        renderer.strokeColor    = .blue
        renderer.lineWidth      = 1
        return renderer
    }

    // If you want to draw a route (polyline) to fit your specifics
    if overlay is MKPolyline {

        let renderer = MKPolylineRenderer(overlay: overlay)

        renderer.strokeColor    = UIColor.init(displayP3Red: 0.15, green: 0.5, blue: 1, alpha: 0.9)

        renderer.lineWidth      = 10.0

        return renderer
    }

    return MKOverlayRenderer(overlay: overlay)
}