Usando o mapa QML para construir vias aéreas - Parte 1

Há já algum tempo que uso o QML para criar interfaces gráficas, mas até agora não houve oportunidade de trabalhar em um projeto real com a API de localização Qt e o mapa QML.
Portanto, tornou-se interessante experimentar esse componente para a construção de vias aéreas.
Sob o cortador, está a descrição da implementação do editor, para criar caminhos semelhantes no mapa:

imagem

Para simplificar a implementação, nossos aviões voam no plano 2D na mesma altura. A velocidade e a sobrecarga permitida são fixas - 920 km / he 3g, o que fornece um raio de viragem

R= fracv2G=21770m


A trajetória consiste nos seguintes segmentos:
imagem
onde S é o início da manobra (é o ponto de saída da anterior), M é o início da curva, E é a saída e F é o ponto final (M para a próxima).

Para calcular o ponto de entrada e saída da trajetória, usei a equação de uma tangente a um círculo, os cálculos acabaram sendo bastante pesados, tenho certeza de que isso pode ser simplificado.

void Manoeuvre::calculate() { // General equation of line between first and middle points auto A = mStart.y() - mMiddle.y(); auto B = mMiddle.x() - mStart.x(); // Check cross product sign whether final point lies on left side auto crossProduct = (B*(mFinal.y() - mStart.y()) + A*(mFinal.x() - mStart.x())); // All three points lie on the same line if (isEqualToZero(crossProduct)) { mIsValid = true; mCircle = mExit = mMiddle; return; } mIsLeftTurn = crossProduct > 0; auto lineNorm = A*A + B*B; auto exitSign = mIsLeftTurn ? 1 : -1; auto projection = exitSign*mRadius * qSqrt(lineNorm); // Center lies on perpendicular to middle point if (!isEqualToZero(A) && !isEqualToZero(B)) { auto C = -B*mStart.y() - A*mStart.x(); auto right = (projection - C)/A - (mMiddle.x()*lineNorm + A*C) / (B*B); mCircle.ry() = right / (A/B + B/A); mCircle.rx() = (projection - B*mCircle.y() - C) / A; } else { // Entering line is perpendicular to either x- or y-axis auto deltaY = isEqualToZero(A) ? 0 : exitSign*mRadius; auto deltaX = isEqualToZero(B) ? 0 : exitSign*mRadius; mCircle.ry() = mMiddle.y() + deltaY; mCircle.rx() = mMiddle.x() + deltaX; } // Check if final point is outside manouevre circle auto circleDiffX = mFinal.x() - mCircle.x(); auto circleDiffY = mFinal.y() - mCircle.y(); auto distance = qSqrt(circleDiffX*circleDiffX + circleDiffY*circleDiffY); mIsValid = distance > mRadius; // Does not make sence to calculate futher if (!mIsValid) return; // Length of hypotenuse from final point to exit point auto beta = qAtan2(mCircle.y() - mFinal.y(), mCircle.x() - mFinal.x()); auto alpha = qAsin(mRadius / distance); auto length = qSqrt(distance*distance - mRadius*mRadius); // Depends on position of final point find exit point mExit.rx() = mFinal.x() + length*qCos(beta + exitSign*alpha); mExit.ry() = mFinal.y() + length*qSin(beta + exitSign*alpha); // Finally calculate start/span angles auto startAngle = qAtan2(mCircle.y() - mMiddle.y(), mMiddle.x() - mCircle.x()); auto endAngle = qAtan2(mCircle.y() - mExit.y(), mExit.x() - mCircle.x()); mStartAngle = startAngle < 0 ? startAngle + 2*M_PI : startAngle; endAngle = endAngle < 0 ? endAngle + 2*M_PI : endAngle; auto smallSpan = qFabs(endAngle - mStartAngle); auto bigSpan = 2*M_PI - qFabs(mStartAngle - endAngle); bool isZeroCrossed = mStartAngle > endAngle; if (!mIsLeftTurn) { mSpanAngle = isZeroCrossed ? bigSpan : smallSpan; } else { mSpanAngle = isZeroCrossed ? smallSpan : bigSpan; } } 

Depois de concluir o erro de cálculo do modelo matemático de nossa trajetória, passamos a trabalhar diretamente com o mapa. Uma opção natural para a construção de polilinhas em um mapa QML é adicionar o MapPolyline diretamente ao mapa.

 Map { id: map plugin: Plugin { name: "osm" } MapPolyline { path: [ { latitude: -27, longitude: 153.0 }, ... ] } } 

Inicialmente, eu queria oferecer ao usuário a oportunidade de simular cada seção subseqüente da rota "on the fly" - para criar um efeito do movimento da trajetória atrás do cursor.

imagem

Alterar o caminho ao mover o cursor é uma operação bastante cara, então tentei usar caminhos preliminares de “pixel” que são exibidos até o usuário finalmente salvar a rota.

 Repeater { id: trajectoryView model: flightRegistry.hasActiveFlight ? flightRegistry.flightModel : [] FlightItem { anchors.fill: parent startPoint: start endPoint: end manoeuvreRect: rect manoeuvreStartAngle: startAngle manoeuvreSpanAngle: spanAngle isVirtualLink: isVirtual } } 

FlightItem é um QQuickItem e QAbstractListModel flightModel permite atualizar as seções necessárias da trajetória ao alterar dados para manobra.

 QVariant FlightModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case FlightRoles::StartPoint: return mFlight->flightSegment(index.row()).line().p1(); case FlightRoles::EndPoint: return mFlight->flightSegment(index.row()).line().p2(); ... } 

Essa atualização ao vivo permite que você avise o usuário sobre manobras irrealizáveis.

imagem

Somente após a conclusão da criação da via aérea (por exemplo, com o clique direito do mouse) a rota será finalmente adicionada ao Mapa QML como um GeoPath com possibilidade de georreferenciamento (até o momento em que o mapa não possa ser movido e ampliado, os pixels não sabem nada sobre longitude e latitude).
Para recalcular um segmento de pixel em uma coordenada geográfica, para iniciantes, precisamos usar para cada manobra um sistema de coordenadas local ao ponto de entrada da manobra (nosso ponto S).

 QPointF FlightGeoRoute::toPlaneCoordinate(const QGeoCoordinate &origin, const QGeoCoordinate &point) { auto distance = origin.distanceTo(point); auto azimuth = origin.azimuthTo(point); auto x = qSin(qDegreesToRadians(azimuth)) * distance; auto y = qCos(qDegreesToRadians(azimuth)) * distance; return QPointF(x, y); } 

Após recalcularmos a manobra já em metros, é necessário fazer a operação reversa e conhecer o georreferenciamento do ponto S para transferir os medidores para latitude-longitude.

 QGeoCoordinate FlightGeoRoute::toGeoCoordinate(const QGeoCoordinate &origin, const QPointF &point) { auto distance = qSqrt(point.x()*point.x() + point.y()*point.y()); auto radianAngle = qAtan2(point.x(), point.y()); auto azimuth = qRadiansToDegrees(radianAngle < 0 ? radianAngle + 2*M_PI : radianAngle); return origin.atDistanceAndAzimuth(distance, azimuth); } 


Do ponto de vista formal, é impossível, é claro, considerar nossa trajetória “pixel” e “em metros” idêntica, mas me pareceu muito saboroso olhar para o futuro e mostrar ao usuário o que acontecerá (ou não acontecerá se o avião não voar assim) quando ele clicará na próxima vez. Depois de finalizar a trajetória (difere um pouco do pixel em cores e transparência, pois mesmo linhas quebradas estáticas não parecem muito suaves no mapa).

imagem

As fontes estão disponíveis aqui ; para compilação, usei o Qt 5.11.2.

Na próxima parte, ensinaremos nosso editor a mover os pontos de referência da trajetória, além de salvar / abrir as trilhas existentes para simulação subsequente do movimento da aeronave.

Source: https://habr.com/ru/post/pt433828/


All Articles