簡體   English   中英

在 iOS 的 MapKit 中繪制路線

[英]Drawing a route in MapKit in iOS

我想在地圖上的兩個位置之間繪制一條路線。 有點像導游。 當游客點擊另一個位置時,我希望能夠繪制路線; 以及,告知與當前位置的距離。

我知道互聯網上的網站告訴如何在地圖上繪制折線。 但是,大多數示例都有一個帶有各種坐標的預加載 .csv 文件。

是否有其他方法可以從 Google 或任何其他提供商處獲取坐標,因為位置是動態選擇的。

如果否,我如何獲得中間坐標的信息?

iOS 6 是否為這個問題提供了任何直接的方法?

下面的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];
}

我希望這會幫助某人。

這是一個棘手的問題。 MapKit 沒有辦法做到這一點:當您知道坐標時,繪制線很容易,但是 MapKit 不會讓您訪問道路或其他路線信息。 我會說你需要調用一個外部 API 來獲取你的數據。

我一直在玩 cloudmade.com API。 矢量流服務器應該返回您需要的內容,然后您可以在地圖上繪制它。 但是,Google 地圖和 cloudmade 使用的OSM地圖之間的差異可能會讓您一直想使用 cloudmade 地圖:它們與 MapKit 等效。

PS:其他地圖提供商 - Google、Bing 等也可能提供等效的數據饋送。 我最近一直在看 OSM/Cloudmade。

PPS:這些都不是小菜一碟! 祝你好運!

Andiih 說得對。 MapKit 不會讓你這樣做。 不幸的是,谷歌也不會讓你做你想做的事。

當 Apple 發布 MapKit 時,他們還明確表示任何導航應用程序都將是 BYOM:Bring Your Own Maps,因此任何導航應用程序都使用自己的一套地圖工具。

Google 的服務條款甚至限制您在其地圖上顯示路線:

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

許可證限制:

10.9將服務或內容與任何產品、系統或應用程序一起使用,用於或與之相關:

(a) 實時導航或路線指引,包括但不限於與用戶啟用傳感器的設備的位置同步的逐向路線指引;

(b) 用於自動或自主控制車輛行為的任何系統或功能; 或者

(c) 調度、車隊管理、企業資產跟蹤或類似的企業應用程序(Google Maps API 可用於跟蹤資產(例如汽車、公共汽車或其他車輛),只要該跟蹤應用程序向公眾開放,無需例如,您可以提供一個免費的公共 Maps API 實現來顯示實時公共交通或其他交通狀態信息。

可悲的是,這包括您想做的事情。 希望有一天 MapKit 將被擴展以允許此類功能......盡管不太可能。

祝你好運。

您可能想查看https://github.com/leviathan/nvpolyline此解決方案特別針對 v.4.0 之前的 iPhone OS 版本

雖然它也可以用於 v.4.0 希望這有幫助。

使用 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 有一個 SDK,可以直接替代 MapKit。 它目前處於測試階段,但正在積極開發中。

它允許疊加、路由和地理編碼。

MapQuest iOS 地圖 API

只是為了澄清,看起來有幾件事正在討論中。 一種是獲取路線頂點的方法,另一種是使用這些頂點在地圖上繪制疊加層。 我知道 MapQuest API,所以我有一些下面的鏈接 - 我認為 Google 和Bing有等價物。

1)獲取路線的頂點
如果您正在尋找路線的新坐標來繪制路線疊加層,您可以使用 Web 服務調用路由 Web 服務 - 我假設您在此處使用 JavaScript 來顯示地圖。 如果您使用本機代碼,您仍然可以訪問 Web 服務,也可以使用本機調用(即 MapQuest iPhone SDK 中包含本機路由調用)。

大多數路線服務應該返回路線的“形狀點”,以便您可以繪制。

以下是一些使用 MapQuest-Directions Web Service 獲取 shapepoints 的示例(請參閱 Shape 返回對象)- http://www.mapquestapi.com/directions/

2) 繪制疊加層
一旦有了頂點,就需要繪制它們。 我認為大多數 JavaScript 地圖 API 都會有某種類型的覆蓋類。 這是 MapQuest 之一: http : //developer.mapquest.com/web/documentation/sdk/javascript/v7.0/overlays#line

3) 一通電話
MapQuest 也有一些方便的功能,可以為你進行路線調用和划線——我不能發兩個以上的鏈接! 因此,請轉到上面的鏈接並在左側的導航欄中查找“路由”。

要更新此問題,自 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

到 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)
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM