Uso del mapa QML para construir vías aéreas - Parte 1

Durante bastante tiempo, he estado usando QML para construir interfaces gráficas, pero hasta ahora no ha habido oportunidad de trabajar en un proyecto real con la API de ubicación Qt y el mapa QML.
Por lo tanto, se volvió interesante probar este componente para construir vías aéreas.
Debajo del cortador se encuentra la descripción de la implementación del editor, para crear rutas similares en el mapa:

imagen

Para simplificar la implementación, nuestros aviones vuelan en el plano 2D a la misma altura. La velocidad y la sobrecarga permisible son fijas: 920 km / hy 3g, lo que da un radio de giro

R= fracv2G=21770m


La trayectoria consta de los siguientes segmentos:
imagen
donde S es el comienzo de la maniobra (es el punto de salida del anterior), M es el comienzo del giro, E es la salida y F es el punto final (M para el siguiente).

Para calcular el punto de entrada y salida de la trayectoria, utilicé la ecuación de una tangente a un círculo, los cálculos resultaron ser bastante engorrosos, estoy seguro de que puede simplificarse.

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

Una vez completado el error de cálculo del modelo matemático de nuestra trayectoria, procedemos a trabajar directamente con el mapa. Una opción natural para construir polilíneas en un mapa QML es agregar MapPolyline directamente al mapa.

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

Inicialmente, quería ofrecer al usuario la oportunidad de simular cada sección posterior de la ruta "sobre la marcha", para crear un efecto de la trayectoria detrás del cursor.

imagen

Cambiar la ruta al mover el cursor es una operación bastante costosa, así que intenté usar rutas preliminares de "píxeles" que se muestran hasta que el usuario finalmente guarda la ruta.

 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 es un QQuickItem , y QAbstractListModel flightModel le permite actualizar las secciones necesarias de la trayectoria al cambiar los datos de maniobra.

 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(); ... } 

Tal actualización en vivo le permite advertir al usuario sobre maniobras irrealizables.

imagen

Solo después de la finalización de la creación de la vía aérea (por ejemplo, con el clic derecho del mouse), la ruta finalmente se agregará al Mapa QML como GeoPath con la posibilidad de georreferencia (hasta este momento el mapa no se puede mover y hacer zoom, los píxeles no saben nada sobre longitud y latitud).
Para recalcular un segmento de píxeles en una geocoordenada, para empezar necesitamos usar un sistema de coordenadas local para el punto de entrada de la maniobra (nuestro punto S) para cada maniobra.

 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); } 

Después de volver a calcular la maniobra ya metros, es necesario hacer la operación inversa y conocer la georreferenciación del punto S para transferir los metros a latitud-longitud.

 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); } 


Desde un punto de vista formal, es imposible, por supuesto, considerar idéntica nuestra trayectoria de "píxeles" y "metros", pero me pareció muy sabroso mirar hacia el futuro y mostrarle al usuario lo que sucederá (o no sucederá si el avión no vuela así) cuando hará clic la próxima vez. Después de finalizar la trayectoria (difiere ligeramente del píxel uno en color y transparencia, ya que incluso las líneas discontinuas estáticas no se ven muy suaves en el mapa).

imagen

Las fuentes están disponibles aquí ; para la compilación usé Qt 5.11.2.

En la siguiente parte, le enseñaremos a nuestro editor a mover los puntos de referencia de la trayectoria, así como a guardar / abrir rutas existentes para la simulación posterior del movimiento de la aeronave.

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


All Articles