Centrando MKMapView en el punto N-pixels debajo del pin


Desea centrar MKMapView en un punto N píxeles debajo de un pin dado (que puede o no ser visible en el MapRect actual ).

He estado tratando de resolver esto usando varias jugadas con -(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view sin éxito.

Alguien ha estado por este camino ( sin juego de palabras)?

introduzca la descripción de la imagen aquí

Author: eric, 2013-03-15

7 answers

La técnica más fácil es simplemente desplazar el mapa hacia abajo, digamos un 40% desde donde estaría el coordinate, aprovechando el span del region del MKMapView. Si no necesita píxeles reales, pero solo necesita que se mueva hacia abajo para que el CLLocationCoordinate2D en cuestión esté cerca de la parte superior del mapa (digamos un 10% lejos de la parte superior):

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

Si desea tener en cuenta la rotación y el tono de la cámara, la técnica anterior puede no ser adecuada. En ese caso, usted podría:

  • Identificar el posición en la vista a la que desea cambiar la ubicación del usuario;

  • Convertir eso en un CLLocation;

  • Calcule la distancia de la ubicación del usuario actual desde esa nueva ubicación deseada;

  • Mueva la cámara a esa distancia en la dirección 180° desde el rumbo actual de la cámara del mapa.

Por ejemplo, en Swift 3, algo como: {[19]]}

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

Donde CLLocationCoordinate2D tiene lo siguiente 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's 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
    }
}

Así que, incluso con la cámara inclinada y en una dirección que no sea hacia el norte, esto mueve la ubicación del usuario (que está centrada, donde está el punto de mira inferior) hasta 150 píxeles (donde está el punto de mira superior), produciendo algo como:

introduzca la descripción de la imagen aquí

Obviamente, debes ser consciente de las situaciones degeneradas (por ejemplo, estás a 1 km del polo sur y tratas de cambiar el mapa a 2 km de metros; estás usando un ángulo de cámara tan inclinado que la ubicación de la pantalla deseada está más allá del horizonte; etc.), pero para escenarios prácticos del mundo real, algo como lo anterior podría ser suficiente. Obviamente, si no dejas que el usuario cambie el tono de la cámara, la respuesta es aún más fácil.


Respuesta original: para mover la anotación n píxeles

Si tienes un CLLocationCoordinate2D, puedes convertirlo a un CGPoint, moverlo x píxeles, y luego convertirlo de nuevo a un 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];
}

Puede llamar a esto por:

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

Desafortunadamente, esto solo funciona si el coordinate es visible antes de comenzar, por lo que es posible que tenga que ir a la coordenada original primero, y luego ajustar el centro.

 76
Author: Rob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-05 14:38:40

La única manera de hacer esto de manera confiable es usar lo siguiente:

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

Para hacer eso dada una región del mapa en la que desea centrar, tiene que convertir la región del mapa a un MKMapRect. Utilice el relleno de borde para el desplazamiento de píxeles, obviamente.

Ver aquí para eso: Convertir MKCoordinateRegion a MKMapRect

Comentario: Me parece bastante extraño que esa sea la única manera de hacerlo, dado que MKMapRect no es algo que normalmente se usa con un MKMapView-todos los los métodos de conversión son para MKMapRegion. Pero, ok, al menos funciona. Probado en mi propio proyecto.

 7
Author: n13,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:27

Para Swift:

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)
}
}
 3
Author: Steve Stomp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-14 05:19:08

Una solución sencilla es hacer que el marco de la vista de mapa sea más grande que el área visible. Luego coloque su pin en el centro de la vista de mapa y oculte todas las áreas no deseadas detrás de otra vista o fuera de los límites de la pantalla.

Déjame explicarte. Si miro su captura de pantalla, haga lo siguiente:

La distancia entre el pin y la parte inferior es de 353 píxeles. Así que haz que tus vistas de mapa tengan el doble de altura: 706 píxeles. La captura de pantalla tiene una altura de 411 píxeles. Coloque su marco en un origen de 706px-411px = -293 pixel. Ahora centre su vista de mapa en la coordenada del pin y listo.

Actualización 4-Marzo-2014:

He creado una pequeña aplicación de ejemplo con Xcode 5.0.2 para demostrar esto: http://cl.ly/0e2v0u3G2q1d

 2
Author: Klaas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-04 14:29:01

SWIFT 3 ACTUALIZADO

Se ha actualizado la función con Zoom

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)
    }
 1
Author: Mikel Sanchez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-28 08:27:28

Después de leer este anuncio de hilo jugando, especialmente con el zoom en la anotación, terminé con los siguientes procedimientos:

** Centrado en la anotación:**

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

* * Zoom sobre la anotación: * *

- (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 y -(CGFloat) sizeOfTop ambos devuelven la altura de los paneles que cubren la vista del mapa desde las guías de diseño

 -1
Author: altagir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-20 16:59:01

Mira este método en MKMapView:

- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
 -6
Author: Jack Freeman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-14 22:27:23