Рисование маршрута в MapKit в iOS

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

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

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

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

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


person PRN    schedule 14.05.2010    source источник
comment
Попробуйте это, надеюсь, это поможет вам github.com/Surya121/SBMapWithRoute   -  person iCoder4777    schedule 26.12.2011
comment
iphonegeeksworld .wordpress.com / 2010/09/08 /   -  person viral    schedule 12.08.2012
comment
Если вы используете код, это обновление необходимо stackoverflow.com / questions / 8166179 /   -  person John Jamieson    schedule 26.06.2013
comment
Здравствуйте, думаю, вам стоит показать эту ссылку stackoverflow.com/a/32067989/3683148   -  person Kaushik Movaliya    schedule 15.08.2016


Ответы (10)


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

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

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

P.P.S .: Все это мелочи для новичков! Удачи!

person Andiih    schedule 14.05.2010

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

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

person viral    schedule 12.08.2012
comment
Надеюсь, ваш метод рисования маршрута работает нормально. Я использовал почти ту же функцию, что и у вас с именем decodePolyLine. Я получил массив CLLocations, но после этого я застрял, потому что метод polylineWithCordinates принимает массив CLLocationCordinate2D, и я не знал, как преобразовать мой массив в массив CLLocationCordinate2D. Завтра попробую ваш метод рисования маршрута на своем рабочем месте. Кстати, не могли бы вы объяснить эту строку: «CLLocationCoordinate2D * coords = malloc (numPoints * sizeof (CLLocationCoordinate2D))»; - person nr5; 16.12.2012
comment
@ rd4code CLLocationCoordinate2D* coords = malloc(numPoints * sizeof(CLLocationCoordinate2D)); - он выделяет в памяти кусок, равный размеру нашего arrRoutePoints, с каждым индексным объектом как CLLocationCoordinate2D. Надеюсь, вам это ясно. - person viral; 17.12.2012
comment
твой способ сегодня сработал :). спасибо и спасибо за объяснение. . Также я хочу спросить, есть ли другой способ создания массива CLLocationCoordinate2D? Я имею в виду, нужен ли malloc? - person nr5; 17.12.2012
comment
@ rd4code malloc используется только для того, чтобы избежать CFArray, вот и все. Без этого вам пришлось бы использовать CFArrays - это просто упрощает. Ничего больше. - person viral; 17.12.2012

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

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

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

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

Ограничения лицензии:

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

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

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

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

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

Удачи.

person Daniel Amitay    schedule 14.05.2010
comment
PRN не говорит, что он пытается что-то предпринять или делать пошагово: просто он хочет перекрыть направления. Это то, что вы можете сделать с Google Maps Javascript API (я думаю), но не с MapKit. Вы, очевидно, более знакомы с Google API, чем я - если не пошагово, есть ли какой-нибудь способ получить векторы дорог? [см. этот вопрос меня заинтересовал сейчас!] - person Andiih; 15.05.2010
comment
это не поворот за поворотом, но это руководство по маршруту, не так ли? - person Daniel; 13.04.2012

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

Хотя его также можно использовать в версии 4.0. Надеюсь, это поможет.

person leviathan    schedule 01.07.2010
comment
Привет, Колинс, Прежде всего, извините за отложенный ответ ... но в примере, упомянутом выше, координаты жестко запрограммированы в приложении, я хочу получить координаты, когда и когда выбрано местоположение .. . не то, где координаты предварительно загружены. Например, турист может выбрать любое случайное место в городе, и ему следует указать маршрут, основанный на его текущих координатах. Координаты этого выбранного местоположения не могут быть предварительно загружены. - person PRN; 29.07.2010
comment
PRN перейдите в проект GitHub с полилиниями и откройте отчет о проблеме (github.com/mobilemelting/nvpolyline/issues). Я постараюсь включить в проект решение вашей проблемы. - person leviathan; 29.07.2010

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

person iOS_User    schedule 05.01.2011

Получить и нарисовать маршрут на карте очень просто с 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];
    }
}];
person nalexn    schedule 14.02.2015
comment
Как вы собираетесь сделать то же самое для стран, в которых нет поддержки направлений в MapKit? - person madLokesh; 09.03.2015

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

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

MapQuest iOS Maps API

person Fabian    schedule 04.03.2012

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

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

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

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

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

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

person Roman    schedule 12.03.2012

Чтобы обновить этот вопрос, нет необходимости во внешнем 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
person Omaty    schedule 25.02.2015

Согласно данным на 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)
}
person Stéphane de Luca    schedule 25.10.2019