Как определить правильную высоту для 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