简体   繁体   English

在 iOS 的 MapKit 中绘制路线

[英]Drawing a route in MapKit in iOS

I want to draw a route between two locations on the map.我想在地图上的两个位置之间绘制一条路线。 Something like a tour guide.有点像导游。 When the tourist clicks another location, I want to be able to draw a route;当游客点击另一个位置时,我希望能够绘制路线; as well as, inform about the distance from the current location.以及,告知与当前位置的距离。

I am aware of sites on the Internet which tell how to draw polylines on map.我知道互联网上的网站告诉如何在地图上绘制折线。 But, most of the examples had a preloaded .csv file with various coordinates.但是,大多数示例都有一个带有各种坐标的预加载 .csv 文件。

Is there an alternative way to get the coordinates from Google or any other provider, as the location is selected dynamically.是否有其他方法可以从 Google 或任何其他提供商处获取坐标,因为位置是动态选择的。

If NO, how do I get the information for intermediate coordinates?如果否,我如何获得中间坐标的信息?

Does iOS 6 provide any direct way for this problem? iOS 6 是否为这个问题提供了任何直接的方法?

The following viewDidLoad will (1) set two locations, (2) remove all the previous annotations, and (3) call user defined helper functions (to get route points and draw the route).下面的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];
}

The following is the MKMapViewDelegate method, which draws overlay (iOS 4.0 and later).下面是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;
}

The following function will get both the locations and prepare URL to get all the route points.以下函数将获取位置并准备 URL 以获取所有路线点。 And of course, will call stringWithURL.当然,也会调用 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]];
}

The following code is the real magic (decoder for the response we got from the API).下面的代码是真正的魔法(我们从 API 得到的响应的解码器)。 I would not modify that code unless I know what I am doing :)除非我知道我在做什么,否则我不会修改该代码:)

- (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;
}

This function will draw a route and will add an overlay.此函数将绘制路线并添加叠加层。

- (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];
    }
}

The following code will center align the map.以下代码将使地图居中对齐。

- (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];
}

I hope this would help someone.我希望这会帮助某人。

This is a tricky one.这是一个棘手的问题。 There is no way to do that with MapKit: it's easy enough to draw lines when you know the coordinates, but MapKit won't give you access to the roads or other routing information. MapKit 没有办法做到这一点:当您知道坐标时,绘制线很容易,但是 MapKit 不会让您访问道路或其他路线信息。 I'd say you need to call an external API to get your data.我会说你需要调用一个外部 API 来获取你的数据。

I've been playing with cloudmade.com API.我一直在玩 cloudmade.com API。 The vector stream server should return what you need, and then you can draw that over your map.矢量流服务器应该返回您需要的内容,然后您可以在地图上绘制它。 However, discrepancies between the Google maps and the OSM maps used by cloudmade may make you want to use cloudmade maps all the way: they have an equivalent to MapKit.但是,Google 地图和 cloudmade 使用的OSM地图之间的差异可能会让您一直想使用 cloudmade 地图:它们与 MapKit 等效。

PS: Other mapping providers - Google, Bing, etc. may also provide equivalent data feeds. PS:其他地图提供商 - Google、Bing 等也可能提供等效的数据馈送。 I've just been looking at OSM/Cloudmade recently.我最近一直在看 OSM/Cloudmade。

PPS: None of this is trivial newbie stuff! PPS:这些都不是小菜一碟! Best of luck!祝你好运!

Andiih's got it right. Andiih 说得对。 MapKit won't let you do that. MapKit 不会让你这样做。 Unfortunately, Google won't let you do what you want to do either.不幸的是,谷歌也不会让你做你想做的事。

When Apple announced MapKit and all, they also explicitly stated that any navigational applications would be BYOM: Bring Your Own Maps, so any navigation application uses their own set of mapping tools.当 Apple 发布 MapKit 时,他们还明确表示任何导航应用程序都将是 BYOM:Bring Your Own Maps,因此任何导航应用程序都使用自己的一套地图工具。

Google's Terms of Service restrict you from even displaying routes on top of their maps: Google 的服务条款甚至限制您在其地图上显示路线:

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

License Restrictions:许可证限制:

10.9 use the Service or Content with any products, systems, or applications for or in connection with: 10.9将服务或内容与任何产品、系统或应用程序一起使用,用于或与之相关:

(a) real time navigation or route guidance, including but not limited to turn-by-turn route guidance that is synchronized to the position of a user's sensor-enabled device; (a) 实时导航或路线指引,包括但不限于与用户启用传感器的设备的位置同步的逐向路线指引;

(b) any systems or functions for automatic or autonomous control of vehicle behavior; (b) 用于自动或自主控制车辆行为的任何系统或功能; or或者

(c) dispatch, fleet management, business asset tracking, or similar enterprise applications (the Google Maps API can be used to track assets (such as cars, buses or other vehicles) as long as the tracking application is made available to the public without charge. For example, you may offer a free, public Maps API Implementation that displays real-time public transit or other transportation status information. (c) 调度、车队管理、企业资产跟踪或类似的企业应用程序(Google Maps API 可用于跟踪资产(例如汽车、公共汽车或其他车辆),只要该跟踪应用程序向公众开放,无需例如,您可以提供一个免费的公共 Maps API 实现来显示实时公共交通或其他交通状态信息。

Sadly, this includes what you would like to do.可悲的是,这包括您想做的事情。 Hopefully one day MapKit will be expanded to allow such features... although unlikely.希望有一天 MapKit 将被扩展以允许此类功能......尽管不太可能。

Good luck.祝你好运。

You might want to have a look at https://github.com/leviathan/nvpolyline This solution is especially targeted at iPhone OS versions prior to v.4.0您可能想查看https://github.com/leviathan/nvpolyline此解决方案特别针对 v.4.0 之前的 iPhone OS 版本

Although it can also be used in v.4.0 Hope this helps.虽然它也可以用于 v.4.0 希望这有帮助。

Obtaining and drawing a route on the map is super easy with iOS 7 API:使用 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];
    }
}];

MapQuest has an SDK that's a drop-in replacement for MapKit. MapQuest 有一个 SDK,可以直接替代 MapKit。 It's currently in beta, but under active development.它目前处于测试阶段,但正在积极开发中。

It allows overlays, routing, and geocoding.它允许叠加、路由和地理编码。

MapQuest iOS Maps API MapQuest iOS 地图 API

Just to clarify, it looks like there are a couple of things being discussed.只是为了澄清,看起来有几件事正在讨论中。 One is a way to get the vertices of a route and the other is to draw an overlay on the map using those vertices.一种是获取路线顶点的方法,另一种是使用这些顶点在地图上绘制叠加层。 I know the MapQuest APIs, so I have some links for those below - Google and Bing have equivalents I think.我知道 MapQuest API,所以我有一些下面的链接 - 我认为 Google 和Bing有等价物。

1) Getting vertices of a route 1)获取路线的顶点
If you're looking for the new coordinates of a route to draw a route overlay, you can either use a web service call to a routing web service - I'm assuming you're using JavaScript here to display the map.如果您正在寻找路线的新坐标来绘制路线叠加层,您可以使用 Web 服务调用路由 Web 服务 - 我假设您在此处使用 JavaScript 来显示地图。 If you're using native code, you can still hit a web service or you can use a native call (that is, the MapQuest iPhone SDK has a native route call in it).如果您使用本机代码,您仍然可以访问 Web 服务,也可以使用本机调用(即 MapQuest iPhone SDK 中包含本机路由调用)。

Most of the directions services should return the "shapepoints" of the route so that you can draw.大多数路线服务应该返回路线的“形状点”,以便您可以绘制。

Here are some examples using MapQuest- Directions Web Service to get shapepoints (see the Shape return object) - http://www.mapquestapi.com/directions/以下是一些使用 MapQuest-Directions Web Service 获取 shapepoints 的示例(请参阅 Shape 返回对象)- http://www.mapquestapi.com/directions/

2) Drawing an overlay 2) 绘制叠加层
Once you have your vertices, you need to draw them.一旦有了顶点,就需要绘制它们。 I think most JavaScript map APIs will have an overlay class of some sort.我认为大多数 JavaScript 地图 API 都会有某种类型的覆盖类。 Here's the MapQuest one: http://developer.mapquest.com/web/documentation/sdk/javascript/v7.0/overlays#line这是 MapQuest 之一: http : //developer.mapquest.com/web/documentation/sdk/javascript/v7.0/overlays#line

3) Doing it with one call 3) 一通电话
MapQuest also have some convenience functions to make the route call and draw the line for you - I can't post more than two links! MapQuest 也有一些方便的功能,可以为你进行路线调用和划线——我不能发两个以上的链接! So go to the link above and look for "routing" in the navigation bar on the left.因此,请转到上面的链接并在左侧的导航栏中查找“路由”。

To update this question, there is no need to external apk since iOS7.要更新此问题,自 iOS7 起无需外部 apk。

Here a very simple and effective solution :这是一个非常简单有效的解决方案:

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

I know the question was about iOS 6 but I believe that this solution will be useful for many people.我知道这个问题是关于 iOS 6 的,但我相信这个解决方案对很多人都有用。

The only thing missing in this solution is implementing the following delegate method to display the beginning and ending pin此解决方案中唯一缺少的是实现以下委托方法来显示开始和结束引脚

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

As per 2019, with iOS12 or iOS13, it's quite easy to draw a route between two points.到 2019 年,使用 iOS12 或 iOS13,在两点之间绘制路线非常容易。 Heres is a typical code of mine, that draws a route between two POIs, a CLLocation and a CLPlacemark, that you can easily adapt to your context.这是我的一个典型代码,它在两个 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)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM