Мне нужно нарисовать геопроецированный круг на карте. например Я хочу, чтобы центр определялся с точки зрения широты и долготы, а его радиус определялся в метрах. я использую KDE Marble
. В API есть функция drawEllipse
это занимает центр как (широта, долгота) и ширину и высоту эллипса.
GeoPainter::drawEllipse(const GeoDataCoordinates& center, qreal width, qreal height, bool isGeoProjected = false)
Для эллипса с гео-проекцией ширина и высота считаются в градусах. Однако мне нужно, чтобы они были в метрах. Нет простого преобразования между градусами и метрами, потому что это зависит от положения центра на земном шаре.
Мне нужно преобразовать радиус круга (в метрах) в пару степеней смещения вектора, указывающего на центр от центра земли.
Я также использую геометрию наддува для других геометрических расчетов. Есть ли какая-либо функция в boost-geometry
что выполняет это преобразование?
Сначала я попытался получить две координаты GeoDataCoordinates, одна из которых находится в центре, а другая — по периметру. Я ожидал, что разница между их широтой и долготой будет работать с drawEllipse
функция.
painter->drawEllipse(_center, std::abs(_border.longitude(GeoDataCoordinates::Degree)-_center.longitude(GeoDataCoordinates::Degree)), std::abs(_border.latitude(GeoDataCoordinates::Degree)-_center.latitude(GeoDataCoordinates::Degree)), true);
Однако он производит эллипс намного меньше, чем я ожидал. Граничная точка, которая должна была быть на окружности, находится за пределами эллипса.
Затем я попытался использовать формулу центрального угла на википедия
double angular_distance = acos(
(sin(_center.latitude(GeoDataCoordinates::Radian))*sin(_border.latitude(GeoDataCoordinates::Radian)))
+(cos(_center.latitude(GeoDataCoordinates::Radian))*cos(_border.latitude(GeoDataCoordinates::Radian)))
*(double)cos(std::abs(_border.longitude(GeoDataCoordinates::Radian)-_center.longitude(GeoDataCoordinates::Radian)))
);
painter->drawEllipse(_center, stmr::internal::rad2deg(angular_distance), stmr::internal::rad2deg(angular_distance), true);
Результаты мало чем отличаются от предыдущего. Однако на этот раз эллипс немного больше предыдущего.
Отношение расстояний между дугой и окружностью большой окружности сферы используется для вычисления углового смещения в ответе @ ttemple
painter->drawEllipse(_center, 2*(distance/earthRadiusKm)* 180.0/3.14159, 2*(distance/earthRadiusKm)* 180.0/3.14159, true);
производит правильный эллипс.
Таким образом, я умножил углы с 2.0
с ОБНОВЛЕНИЕ II код, и это также произвело подобное (как ОБНОВЛЕНИЕ III) результат.
Но проблема с drawEllipse
в том, что он фактически рисует многоугольник с гораздо меньшим количеством точек в уменьшенном состоянии. Иногда это выглядит как квадрат.
Поэтому лучше рисовать больше точек на окружности эллипса, чтобы ограждение выглядело как круг на увеличенном изображении. KDE Forum Link размещенный в комментариях делает то же самое.
GeoDataLineString ellipse;
qreal lon = 0.0;
qreal lat = 0.0;
int precision = 180;
for ( int i = 0; i < precision; ++i){
qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
lat = center.latitude(GeoDataCoordinates::Degree) + 0.5 * radius * sqrt(1.0 - t * t);
lon = center.longitude(GeoDataCoordinates::Degree) + 0.5 * radius * t;
ellipse << GeoDataCoordinates(lon, lat, 0.0, GeoDataCoordinates::Degree);
}
for ( int i = 0; i < precision; ++i){
qreal t = 2.0 * (qreal)(i) / (qreal)(precision) - 1.0;
lat = center.latitude(GeoDataCoordinates::Degree) - 0.5 * radius * sqrt(1.0 - t * t);
lon = center.longitude(GeoDataCoordinates::Degree) + 0.5 * radius * t;
ellipse << GeoDataCoordinates(lon, lat, 0.0, GeoDataCoordinates::Degree);
}
painter->drawPolyline(ellipse);
Однако он рисует круг с гораздо большим радиусом, чем на входе. Я думаю, что фрагмент, размещенный там, является неполным. Есть неиспользованные условные блоки, которые я игнорировал в своем коде. Также в коде конкретно упоминается в комментариях, что радиус забора нужно указывать в км. Я так и сделал. Кроме того, я не понял, что математика за этим стоит. Он не использует Радиус Земли нигде во фрагменте. Может быть небольшая поправка к этому коду даст лучший эллипс. Математика выглядит как некоторая параметрическая кривая, которая производит половину эллипса. Но нет ссылки на уравнение.
Кроме того, это работает только в первом квадранте, где и широта и долгота положительны
Радиус ограждения, измеренный по кривой сферы, деленный на радиус сферы, представляет собой угол в радианах.
double circular_radius_of_fence = 1000; // circular radius of fence
double sphere_radius = 6378000; // radius of sphere, meters
// included angle in radians
double ang_radians = circular_radius_of_fence / sphere_radius;
// convert to degrees
double ang_degrees = ang_radians * 180.0/PI;
// width and height of circle is twice the angle
double width = ang_degrees * 2;
double height = ang_degrees * 2;
Ответ в Форум KDE на самом деле реализация в GeoPainter::drawEllipse
.
const int precision = qMin<qreal>( width / degreeResolution / 8 + 1, 81 )
Эта линия в этой реализации уменьшает точность, так что при уменьшении она выглядит как квадрат. Поэтому я скопировал код drawEllipse с однострочным изменением для большей точности.
void FenceLayer::drawFence(Marble::GeoPainter* painter, Marble::GeoDataCoordinates center, double radius){
double height = radius;
double width = radius;
// Initialize variables
const qreal centerLon = center.longitude( GeoDataCoordinates::Degree );
const qreal centerLat = center.latitude( GeoDataCoordinates::Degree );
const qreal altitude = center.altitude();
// Ensure a valid latitude range:
if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) {
return;
}
// Don't show the ellipse if it's too small:
GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height,
centerLon + 0.5 * width, centerLon - 0.5 * width,
GeoDataCoordinates::Degree );
if ( !_map->viewport()->viewLatLonAltBox().intersects( ellipseBox ) ||
!_map->viewport()->resolves( ellipseBox ) ) return;
GeoDataLinearRing ellipse;
// Optimizing the precision by determining the size which the
// ellipse covers on the screen:
const qreal degreeResolution = _map->viewport()->angularResolution() * RAD2DEG;
// To create a circle shape even for very small precision we require uneven numbers:
const int scaled_resolution = qMin<qreal>( width / degreeResolution / 8 + 1, 81 );
const int precision = qMin<qreal>( width / degreeResolution / 8 + 1, 81 ) < 81 ? 81 : scaled_resolution ;
// Calculate the shape of the upper half of the ellipse:
for ( int i = 0; i <= precision; ++i ) {
const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t );
const qreal lon = centerLon + 0.5 * width * t;
ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
}
// Calculate the shape of the lower half of the ellipse:
for ( int i = 0; i <= precision; ++i ) {
const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) - 1.0;
const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t );
const qreal lon = centerLon + 0.5 * width * t;
ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
}
painter->drawPolygon(ellipse);
}
Во время звонка drawFence
Я конвертирую расстояние в степень смещения, как предложено в ответе @ ttemple.
drawFence(painter, _center, 2*(distance/earthRadiusKm)* 180.0/3.14159);
куда distance
это радиус забора.