Drawing a route in MapKit in iOS

Я хочу нарисовать маршрут между двумя точками на карте. Что-то вроде гида. Когда турист нажимает на другое место, я хочу иметь возможность нарисовать маршрут; а также, сообщите о расстоянии от текущего местоположения.

Мне известны сайты в Интернете, которые рассказывают, как рисовать полилинии на карте. Но большинство примеров содержало предварительно загруженный файл.csv с различными координатами.

Есть ли альтернативный способ получения координат от Google или любого другого провайдера, так как местоположение выбирается динамически.

Если НЕТ, как я могу получить информацию для промежуточных координат?

Предлагает ли iOS 6 прямой способ решения этой проблемы?

9 ответов

Решение

Это хитрый. Это невозможно сделать с помощью MapKit: достаточно просто нарисовать линии, когда вы знаете координаты, но MapKit не предоставит вам доступ к дорогам или другой информации о маршруте. Я бы сказал, что вам нужно вызвать внешний API для получения ваших данных.

Я играл с API Cloudmade.com. Сервер векторного потока должен вернуть то, что вам нужно, а затем вы можете нарисовать это на своей карте. Однако расхождения между картами Google и картами OSM, используемыми cloudmade, могут привести к тому, что вы захотите использовать карты cloudmade полностью: они имеют эквивалент MapKit.

PS: другие поставщики карт - Google, Bing и т. Д. Также могут предоставлять эквивалентные потоки данных. Я только недавно смотрел на OSM/Cloudmade.

PPS: Ничто из этого не является тривиальным новичком! Удачи!

Следующие 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];
}

Я надеюсь, что это поможет кому-то.

Andiih понял это правильно. MapKit не позволит вам сделать это. К сожалению, Google не позволит вам делать то, что вы хотите.

Когда Apple анонсировала MapKit и все, они также прямо заявили, что любые навигационные приложения будут BYOM: принесите свои собственные карты, поэтому любое навигационное приложение использует свой собственный набор картографических инструментов.

Условия использования Google запрещают вам даже показывать маршруты на своих картах:

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

Лицензионные ограничения:

10.9 использовать Сервис или Контент с любыми продуктами, системами или приложениями для или в связи с:

(a) навигация в режиме реального времени или навигация по маршруту, включая, помимо прочего, пошаговую навигацию по маршруту, синхронизированную с положением пользовательского устройства с поддержкой датчиков;

(b) любые системы или функции для автоматического или автономного контроля поведения транспортного средства; или же

(c) диспетчеризация, управление парком машин, отслеживание активов предприятия или аналогичные корпоративные приложения (API Карт Google можно использовать для отслеживания активов (таких как автомобили, автобусы или другие транспортные средства), если приложение отслеживания доступно для общественности без Например, вы можете предложить бесплатную общедоступную реализацию API Карт, которая отображает общественный транспорт в режиме реального времени или другую информацию о состоянии транспорта.

К сожалению, это включает в себя то, что вы хотели бы сделать. Надеюсь, однажды MapKit будет расширен, чтобы позволить такие функции... хотя вряд ли.

Удачи.

Возможно, вы захотите взглянуть на https://github.com/leviathan/nvpolyline Это решение особенно предназначено для версий iPhone OS до v.4.0.

Хотя это также может быть использовано в 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];
    }
}];

Попробуйте MapKit-Route-Directions ( GitHub).

MapQuest имеет SDK, который заменяет MapKit. В настоящее время он находится в бета-версии, но находится в стадии активной разработки.

Это позволяет наложения, маршрутизации и геокодирования.

MapQuest iOS Maps API

Просто чтобы уточнить, похоже, что есть пара обсуждаемых вещей. Один из них - это способ получить вершины маршрута, а другой - нарисовать наложение на карте, используя эти вершины. Я знаю API MapQuest, поэтому у меня есть несколько ссылок на них ниже - Google и Bing имеют эквиваленты, я думаю.

1) Получение вершин маршрута
Если вы ищете новые координаты маршрута, чтобы нарисовать наложение маршрута, вы можете использовать вызов веб-службы для веб-службы маршрутизации - я предполагаю, что вы используете здесь JavaScript для отображения карты. Если вы используете собственный код, вы все равно можете подключиться к веб-службе или использовать собственный вызов (т. Е. В MapQuest iPhone SDK есть собственный маршрутный вызов).

Большинство служб маршрутов должны возвращать "точки формы" маршрута, чтобы вы могли рисовать.

Вот несколько примеров использования веб-службы MapQuest- Directions для получения точек формы (см. Возвращаемый объект Shape) - http://www.mapquestapi.com/directions/

2) Рисование наложения
Когда у вас есть вершины, вам нужно их нарисовать. Я думаю, что большинство API карт JavaScript будут иметь некоторый класс оверлеев. Вот MapQuest: http://developer.mapquest.com/web/documentation/sdk/javascript/v7.0/overlays

3) Делать это одним звонком
В MapQuest также есть несколько удобных функций для вызова маршрута и рисования линии - я не могу опубликовать более двух ссылок! Поэтому перейдите по ссылке выше и найдите "роутинг" в навигационной панели слева.

Чтобы обновить этот вопрос, нет необходимости внешнего apk начиная с iOS7.

Вот очень простое и эффективное решение:

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)
}
Другие вопросы по тегам