Как определить правильную высоту для MKMapCamera с фокусировкой на MKPolygon

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

Заполнение свойств 'region', 'scale', 'size' и 'mapType' тривиально, так как у меня есть MKPolygon для работы. Сложность заключается в настройке "камеры" - в моем конкретном случае я использую MKMapSnapshotter независимо от MKMapView (на самом деле, даже не в главном потоке).

Однако я бы предпочел ориентировать снимок так, чтобы он соответствовал границам многоугольника на основе ненулевого заголовка, то есть область, на которую я делаю снимок, имеет "начало" и "конец", что Я бы хотел сориентироваться снизу вверх по полученному изображению. Так как многоугольник в принципе никогда не будет ориентирован естественным образом на уровне 0 градусов, мне нужно будет определить 'centerCoordinate', 'heading' и 'altitude'.

Поскольку у меня есть координаты многоугольника, я могу довольно легко получить координату центра и желаемый курс. Первая координата многоугольника соотносится с "началом" формы, а конец (или другая координата, в моем случае) коррелирует к концу'.

Выяснить высоту оказывается сложнее; Я хочу убедиться, что область многоугольника заполняет соотношение сторон снимка, который я хочу отобразить. Как рассчитать правильную высоту для использования с MKMapCamera, не полагаясь на селектор MKMapView'setRegion'?

2 ответа

Решение

Чтобы решить эту проблему, я сделал следующее:

1) вращение MKPolygon вокруг его центральной координаты для устранения проблем направления / поворота при определении ограничивающего прямоугольника: запрос MKPolygon для его 'boundingMapRect' без этого вернет любой минимальный прямоугольник, помещаемый вокруг всей формы. Если бы длинный, тощий многоугольник был ориентирован по диагонали с северо-востока на юго-запад, ограничивающий прямоугольник был бы почти квадратным. Выполнение поворота позволяет учитывать направление многоугольника при определении его соотношения сторон.

2) встраивание ограничивающего прямоугольника с поправкой на заголовок многоугольника в форматное соотношение окна просмотра снимка: это гарантирует, что очень "высокий" многоугольник все равно будет правильно помещаться в широкоформатном окне просмотра и наоборот.

3) [Удалено из моего примера кода] Создание многоугольника получающегося ограничивающего прямоугольника с коррекцией аспекта и поворот его обратно к исходному заголовку с использованием центральной координаты многоугольника: это, вероятно, понадобится при работе с большими областями, поскольку следующий шаг включает измерение между горизонтальными / вертикальными ограничивающими расстояниями. В моем случае, я работаю с очень маленькими областями, на которые не должно оказывать достаточного влияния искривление Земли, чтобы иметь реальное значение.

4) определение общей горизонтальной и вертикальной ограничивающей области в метрах

5) использование большего размера (Измерения) двух расстояний для формирования базового измерения треугольника, где A = минимальное положение координат на оси, B = максимальное положение координат на оси и C = местоположение камеры (центральная координата многоугольника).)

В этот момент я был немного озадачен тем, как решить высоту получившегося треугольника, не имея хотя бы 1 из углов. При выполнении некоторых тестов с использованием экземпляра MKMapView оказывается, что апертура MKMapCamera составляет около 30 градусов - это не зависит от увеличения соотношения сторон окна просмотра, соотношения сторон многоугольника или любого другого фактора, кроме кривизны земной шар. Я могу ошибаться в этом утверждении.

5) Используя угол раскрыва в моих тестах, рассчитайте необходимую высоту, используя (размерность / 2) / загар (aperture_angle_in_radians / 2)

Видя, сколько времени я потратил на это, я решил опубликовать комбинированный вопрос / ответ на Stackru в надежде, что он либо: 1) поможет кому-то еще в той же ситуации 2) исправлен кем-то более умным, чем я и приводит к еще лучшему решению

Спасибо!

ОН, и, конечно же, код:

+ (double)determineAltitudeForPolygon:(MKPolygon *)polygon withHeading:(double)heading andWithViewport:(CGSize)viewport {
    // Get a bounding rectangle that encompasses the polygon and represents its
    // true aspect ratio based on the understanding of its heading.
    MKMapRect boundingRect = [[self rotatePolygon:polygon withCenter:MKMapPointForCoordinate(polygon.coordinate) byHeading:heading] boundingMapRect];

    MKCoordinateRegion boundingRectRegion = MKCoordinateRegionForMapRect(boundingRect);

    // Calculate a new bounding rectangle that is corrected for the aspect ratio
    // of the viewport/camera -- this will be needed to ensure the resulting
    // altitude actually fits the polygon in view for the observer.
    CLLocationCoordinate2D upperLeftCoord = CLLocationCoordinate2DMake(boundingRectRegion.center.latitude + boundingRectRegion.span.latitudeDelta / 2, boundingRectRegion.center.longitude - boundingRectRegion.span.longitudeDelta / 2);
    CLLocationCoordinate2D upperRightCoord = CLLocationCoordinate2DMake(boundingRectRegion.center.latitude + boundingRectRegion.span.latitudeDelta / 2, boundingRectRegion.center.longitude + boundingRectRegion.span.longitudeDelta / 2);
    CLLocationCoordinate2D lowerLeftCoord = CLLocationCoordinate2DMake(boundingRectRegion.center.latitude - boundingRectRegion.span.latitudeDelta / 2, boundingRectRegion.center.longitude - boundingRectRegion.span.longitudeDelta / 2);

    CLLocationDistance hDist = MKMetersBetweenMapPoints(MKMapPointForCoordinate(upperLeftCoord), MKMapPointForCoordinate(upperRightCoord));
    CLLocationDistance vDist = MKMetersBetweenMapPoints(MKMapPointForCoordinate(upperLeftCoord), MKMapPointForCoordinate(lowerLeftCoord));

    double adjacent;
    double newHDist, newVDist;

    if (boundingRect.size.height > boundingRect.size.width) {
        newVDist = vDist;
        newHDist = (viewport.width / viewport.height) * vDist;

        adjacent = vDist / 2;
    } else {
        newVDist = (viewport.height / viewport.width) * hDist;
        newHDist = hDist;

        adjacent = hDist / 2;
    }

    double result = adjacent / tan(Deg_to_Rad(15));
    return result;
}

+ (MKPolygon *)rotatePolygon:(MKPolygon *)polygon withCenter:(MKMapPoint)centerPoint byHeading:(double)heading {
    MKMapPoint points[polygon.pointCount];
    double rotation_angle = -Deg_to_Rad(heading);

    for(int i = 0; i < polygon.pointCount; i++) {
        MKMapPoint point = polygon.points[i];

        // Translate each point by the coordinate to rotate around, use matrix
        // algebra to perform the rotation, then translate back into the
        // original coordinate space.
        double newX = ((point.x - centerPoint.x) * cos(rotation_angle)) + ((centerPoint.y - point.y) * sin(rotation_angle)) + centerPoint.x;
        double newY = ((point.x - centerPoint.x) * sin(rotation_angle)) - ((centerPoint.y - point.y) * cos(rotation_angle)) + centerPoint.y;

        point.x = newX;
        point.y = newY;

        points[i] = point;
    }

    return [MKPolygon polygonWithPoints:points count:polygon.pointCount];
}

Обновленный ответ для Swift 5, для iOS 13 и новее

      func calculateCenterCoordinateDistance(for zoomLevel: CGFloat) -> CLLocationDistance {
    let width = self.frame.size.width
    let span = MKCoordinateSpan(latitudeDelta: 0.0, longitudeDelta:
        CLLocationDegrees(360 * width / (pow(2, (zoomLevel - 1)) * 256)))
    let region = MKCoordinateRegion(center: self.region.center, span: span)
    
    let aspectRatio = Double(self.frame.size.height / self.frame.size.width)
    let radianCameraAperture: Double = 30 * .pi / 180
    let areaRadius = aspectRatio * region.longitudinalMeters / 2

    return areaRadius / tan(radianCameraAperture / 2)
}

Его можно использовать для расчета минимального расстояния до центра для определенного уровня масштабирования.

      let minDistance = mapView.calculateCenterCoordinateDistance(for: 12)
mapView.setCameraZoomRange(MKMapView.CameraZoomRange(minCenterCoordinateDistance: minDistance), animated: false)

Продольные метры рассчитываются следующим образом:

      extension MKCoordinateRegion {
    var east: CLLocation {
        return CLLocation(latitude: center.latitude, longitude: center.longitude + span.longitudeDelta / 2)
    }
    var west: CLLocation {
        return CLLocation(latitude: center.latitude, longitude: center.longitude - span.longitudeDelta / 2)
    }
    var longitudinalMeters: CLLocationDistance {
        return east.distance(from: west)
    }
}

Кредиты: https://gist.github.com/marmelroy/0fee54bfe69bfbfcbbf7057298fca046

Другие вопросы по тегам