Подтвердить что ты не робот

Центрирование MKMapView на месте N-пикселей ниже булавки

Хотите сосредоточить MKMapView на точке N-пикселей ниже заданного контакта (который может быть или не быть видимым в текущем MapRect).

Я пытался решить это, используя различные игры с -(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view, чтобы добиться успеха.

Кто-нибудь был по этой дороге (каламбур не предназначен)?

enter image description here

4b9b3361

Ответ 1

Самый простой способ - просто сдвинуть карту вниз, скажем, на 40% от того, где будет coordinate, используя span region для MKMapView. Если вам не нужны фактические пиксели, но просто нужно, чтобы он опустился вниз, так что CLLocationCoordinate2D находится рядом с верхней частью карты (скажем, на 10% от вершины):

CLLocationCoordinate2D center = coordinate;
center.latitude -= self.mapView.region.span.latitudeDelta * 0.40;
[self.mapView setCenterCoordinate:center animated:YES];

Если вы хотите учитывать поворот и шаг камеры, описанная выше техника может оказаться недостаточной. В этом случае вы можете:

  • Определите позицию в представлении, в которое вы хотите переместить местоположение пользователя;

  • Преобразуйте это в CLLocation;

  • Рассчитайте расстояние текущего местоположения пользователя от этого нового желаемого местоположения;

  • Переместите камеру на это расстояние в направлении 180 & deg; из текущего заголовка карты.

например. в Swift 3, что-то вроде:

var point = mapView.convert(mapView.centerCoordinate, toPointTo: view)
point.y -= offset
let coordinate = mapView.convert(point, toCoordinateFrom: view)
let offsetLocation = coordinate.location

let distance = mapView.centerCoordinate.location.distance(from: offsetLocation) / 1000.0

let camera = mapView.camera
let adjustedCenter = mapView.centerCoordinate.adjust(by: distance, at: camera.heading - 180.0)
camera.centerCoordinate = adjustedCenter

Где CLLocationCoordinate2D имеет следующий extension:

extension CLLocationCoordinate2D {
    var location: CLLocation {
        return CLLocation(latitude: latitude, longitude: longitude)
    }

    private func radians(from degrees: CLLocationDegrees) -> Double {
        return degrees * .pi / 180.0
    }

    private func degrees(from radians: Double) -> CLLocationDegrees {
        return radians * 180.0 / .pi
    }

    func adjust(by distance: CLLocationDistance, at bearing: CLLocationDegrees) -> CLLocationCoordinate2D {
        let distanceRadians = distance / 6_371.0   // 6,371 = Earth radius in km
        let bearingRadians = radians(from: bearing)
        let fromLatRadians = radians(from: latitude)
        let fromLonRadians = radians(from: longitude)

        let toLatRadians = asin( sin(fromLatRadians) * cos(distanceRadians)
            + cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians) )

        var toLonRadians = fromLonRadians + atan2(sin(bearingRadians)
            * sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians)
                - sin(fromLatRadians) * sin(toLatRadians))

        // adjust toLonRadians to be in the range -180 to +180...
        toLonRadians = fmod((toLonRadians + 3.0 * .pi), (2.0 * .pi)) - .pi

        let result = CLLocationCoordinate2D(latitude: degrees(from: toLatRadians), longitude: degrees(from: toLonRadians))

        return result
    }
}

Таким образом, даже когда камера разбита и в другом месте, кроме северного, это перемещает местоположение пользователя (в центре которого находится нижнее перекрестье) до 150 пикселей (где находится верхнее перекрестье), что дает что-то вроде:

введите описание изображения здесь

Очевидно, что вы должны быть в курсе дегенеративных ситуаций (например, вы в 1 км от южного полюса, и вы пытаетесь переместить карту на 2 км в метрах, вы используете угол камеры, расположенный до сих пор, что желаемое расположение экрана за горизонтом и т.д.), но для практических сценариев реального мира может быть достаточно чего-то подобного выше. Очевидно, что если вы не позволите пользователю изменить высоту камеры, ответ будет еще проще.


Оригинальный ответ: для перемещения аннотации n пикселей

Если у вас есть CLLocationCoordinate2D, вы можете преобразовать его в CGPoint, переместить его x пикселей и затем преобразовать обратно в CLLocationCoordinate2D:

- (void)moveCenterByOffset:(CGPoint)offset from:(CLLocationCoordinate2D)coordinate
{
    CGPoint point = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView];
    point.x += offset.x;
    point.y += offset.y;
    CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
    [self.mapView setCenterCoordinate:center animated:YES];
}

Вы можете позвонить по этому адресу:

[self moveCenterByOffset:CGPointMake(0, 100) from:coordinate];

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

Ответ 2

Единственный способ надежно сделать это - использовать следующее:

- (void)setVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate

Чтобы сделать это с учетом области карты, на которой вы хотите сосредоточиться, вам необходимо преобразовать область карты в MKMapRect. Очевидно, используйте крайнее дополнение для смещения пикселей.

См. здесь: Преобразовать MKCoordinateRegion в MKMapRect

Комментарий: Мне показалось довольно странным, что это единственный способ сделать это, учитывая, что MKMapRect - это не то, что обычно используется с MKMapView - все методы преобразования для MKMapRegion. Но, ладно, по крайней мере, это работает. Протестировано в моем собственном проекте.

Ответ 3

Для Свифта:

import MapKit

extension MKMapView {

func moveCenterByOffSet(offSet: CGPoint, coordinate: CLLocationCoordinate2D) {
    var point = self.convert(coordinate, toPointTo: self)

    point.x += offSet.x
    point.y += offSet.y

    let center = self.convert(point, toCoordinateFrom: self)
    self.setCenter(center, animated: true)
}

func centerCoordinateByOffSet(offSet: CGPoint) -> CLLocationCoordinate2D {
    var point = self.center

    point.x += offSet.x
    point.y += offSet.y

    return self.convert(point, toCoordinateFrom: self)
}
}

Ответ 4

Одним из простых решений является то, что вы создаете рамку вашего вида карты больше видимой области. Затем поместите свой штифт в центр обзора карты и спрячьте все нежелательные области за другим видом или за пределами границ экрана.

Позвольте мне уточнить. Если я посмотрю ваш снимок экрана, сделайте следующее:

Расстояние между вами и нижним - 353 пикселя. Поэтому сделайте так, чтобы ваша карта выглядела вдвое больше высоты: 706 пикселей. Вы снимок экрана имеет высоту 411 пикселей. Расположите свой кадр в начале 706px - 411px = -293 пикселя. Теперь сосредоточьте свой вид карты на координате штифта, и все готово.

Обновление 4 марта 2014 года:

Я создал небольшое примерное приложение с Xcode 5.0.2 для демонстрации этого: http://cl.ly/0e2v0u3G2q1d

Ответ 5

ОБНОВЛЕНО SWIFT 3

Обновлена ​​функция с увеличением

func zoomToPos() {

        let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta:  0.1)

        // Create a new MKMapRegion with the new span, using the center we want.
        let coordinate = moveCenterByOffset(offset: CGPoint(x: 0, y: 100), coordinate: (officeDetail?.coordinate)!)
        let region = MKCoordinateRegion(center: coordinate, span: span)

        mapView.setRegion(region, animated: true)


    }

    func moveCenterByOffset (offset: CGPoint, coordinate: CLLocationCoordinate2D) -> CLLocationCoordinate2D {
        var point = self.mapView.convert(coordinate, toPointTo: self.mapView)
        point.x += offset.x
        point.y += offset.y
        return self.mapView.convert(point, toCoordinateFrom: self.mapView)
    }

Ответ 6

В качестве альтернативы принятому ответу, я предполагаю, что ваши оригинальные инстинкты были правильными. Вы можете работать строго в пиксельном координатном пространстве видов карты, чтобы получить смещения и окончательное позиционирование. Затем, используя конверсионный вызов от местоположения до вида на экран, вы можете получить окончательное местоположение и установить центр карты.

Это будет работать с повернутой камерой и относительно пространства на экране. В моем случае мне нужно было центрировать карту на булавке со смещением для учета ящика карты.

Вот конверсионные звонки

func convert(_ coordinate: CLLocationCoordinate2D, toPointTo view: UIView?) -> CGPoint
func convert(_ point: CGPoint, toCoordinateFrom view: UIView?) -> CLLocationCoordinate2D

И вот быстрый пример 4

//First get the position you want the pin to be (say 1/4 of the way up the screen)
let targetPoint = CGPoint(x: self.frame.width / 2.0, y: self.frame.height * CGFloat(0.25))

//Then get the center of the screen (this is used for calculating the offset as we are using setCenter to move the region
let centerPoint = CGPoint(x: self.frame.width / 2.0, y: self.frame.height / 2.0)

//Get convert the CLLocationCoordinate2D of the pin (or map location) to a screen space CGPoint
let annotationPoint = mapview.convert(myPinCoordinate, toPointTo: mapview)

//And finally do the math to set the offsets in screen space                
let mapViewPointFromAnnotation = CGPoint(x: annotationPoint.x + (centerPoint.x - targetPoint.x), y: annotationPoint.y + (centerPoint.y - targetPoint.y))

//Now convert that result to a Coordinate
let finalLocation = self.convert(mapViewPointFromAnnotation, toCoordinateFrom: mapview)

//And set the map center
mapview.setCenter(finalLocation, animated: true)

Ответ 7

после прочтения этого потока рекламы, особенно с увеличением аннотации, я получил следующие процедуры:

** Центрирование аннотации: **

- (void) centerOnSelection:(id<MKAnnotation>)annotation
{
    MKCoordinateRegion region = self.mapView.region;
    region.center = annotation.coordinate;

    CGFloat per = ([self sizeOfBottom] - [self sizeOfTop]) / (2 * self.mapView.frame.size.height);
    region.center.latitude -= self.mapView.region.span.latitudeDelta * per;

    [self.mapView setRegion:region animated:YES];
}

** Масштабирование аннотации: **

- (void) zoomAndCenterOnSelection:(id<MKAnnotation>)annotation
{
    DLog(@"zoomAndCenterOnSelection");

    MKCoordinateRegion region = self.mapView.region;
    MKCoordinateSpan span = MKCoordinateSpanMake(0.005, 0.005);

    region.center = annotation.coordinate;

    CGFloat per = ([self sizeOfBottom] - [self sizeOfTop]) / (2 * self.mapView.frame.size.height);
    region.center.latitude -= self.mapView.region.span.latitudeDelta * span.latitudeDelta / region.span.latitudeDelta * per;

    region.span = span;

    [self.mapView setRegion:region animated:YES];
}

-(CGFloat) sizeOfBottom и -(CGFloat) sizeOfTop и возвратная высота панелей, покрывающих вид карты из направляющих макета

Ответ 8

Посмотрите на этот метод на MKMapView:

- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated