El tipo pose implementa el modelo de datos que subyace al estándar OGC GeoPose v1.0: una position más una orientation en un marco de referencia conocido, donde la orientación es un cuaternión unitario en la convención de Hamilton o, de forma equivalente, una terna yaw / pitch / roll bajo la convención Tait-Bryan intrínseca ZYX. Esta sección agrupa la superficie SQL que expone esa interoperabilidad: E/S JSON para las clases de conformidad Basic y Advanced del estándar, una función auxiliar de renormalización explícita para quienes ejecutan composiciones largas, y los accesores de ángulos de Euler que esperan los consumidores Basic-YPR.
Convertir hacia o desde la codificación JSON de OGC GeoPose v1.0 (clase de conformidad Basic-Quaternion, Basic-YPR o Advanced)
asGeoPose(pose,conformance integer=0,maxdecimaldigits integer=-1) → text poseFromGeoPose(text) → pose
El argumento conformance selecciona la clase de salida: 0 = Basic-Quaternion (predeterminado, sin pérdida), 1 = Basic-YPR (yaw, pitch, roll en grados, Tait-Bryan intrínseco ZYX), 2 = Advanced. El argumento maxdecimaldigits es el número de dígitos significativos que se conservan en los números JSON; -1 utiliza el valor predeterminado sin pérdida de json-c. La función de entrada detecta automáticamente la clase de conformidad a partir de las claves JSON, donde el miembro frameSpecification implica Advanced, el miembro quaternion implica Basic-Quaternion y el miembro angles implica Basic-YPR.
Las clases Basic llevan la posición en un miembro position y dejan el marco implícito. La clase Advanced no tiene miembro de posición: nombra su marco exterior de forma explícita y la pose se sitúa en el origen de ese marco, de modo que las dos codificaciones de una misma pose se releen iguales. Todas las clases exigen un marco exterior geográfico, por lo que la pose debe ser geodésica; una pose planar se rechaza en el límite de la conversión, al igual que un SRID proyectado.
SELECT asGeoPose(pose 'Geodpose(Point(8 47 1500), 0.707107, 0, 0, 0.707107)', 0, 6);
/* {"position":{"lat":47,"lon":8,"h":1500},"quaternion":{"x":0,"y":0,"z":0.707107,
"w":0.707107}} */
SELECT asGeoPose(pose 'Geodpose(Point(8 47 1500), 0.707107, 0, 0, 0.707107)', 1, 6);
-- {"position":{"lat":47,"lon":8,"h":1500},"angles":{"yaw":90,"pitch":0,"roll":0}}
SELECT asGeoPose(pose 'Geodpose(Point(8 47 1500), 0.707107, 0, 0, 0.707107)', 2, 6);
/* {"frameSpecification":{"authority":"/geopose/1.0","id":"LTP-ENU","parameters":
"longitude=8&latitude=47&height=1500&crs=EPSG:4979"},
"quaternion":{"x":0,"y":0,"z":0.707107,"w":0.707107}} */
SELECT asEWKT(poseFromGeoPose(
'{"position":{"lat":47,"lon":8,"h":1500},"angles":{"yaw":90,"pitch":0,"roll":0}}'), 6);
-- SRID=4326;GeodPose(POINT Z (8 47 1500),0.707107,0,0,0.707107)
Devuelve una pose cuyo cuaternión de orientación se ha llevado a una norma unitaria
poseNormalize(pose) → pose
Una pose 2D se devuelve sin cambios, ya que su orientación es un único ángulo. A una pose 3D se le divide el cuaternión por su norma euclidiana, de modo que composiciones largas de SLERP (o cualquier otra vía que acumule deriva de punto flotante en |q|) pueden restaurarse al invariante |q| = 1 que requiere el código de SLERP y de descomposición de Euler.
SELECT asText(poseNormalize(pose 'Pose(Point(1 1 1), 0.5, 0.5, 0.5, 0.5)')); -- Pose(POINT Z (1 1 1),0.5,0.5,0.5,0.5)
Devuelve la inversa de una pose
poseInverse(pose) → pose
La inversa invierte la relación entre marcos: donde una pose lleva un valor del marco que nombra al marco en el que ella misma está expresada, la inversa lo lleva de vuelta. Es R_BA = R_AB^T y t_BA = −R_AB^T t_AB, de modo que componer una pose con su inversa da la identidad. Esto es lo que expresa una posición del mundo en el marco de un vehículo.
Una pose sobre un marco geográfico no tiene inversa: el marco que nombraría no es un marco del elipsoide.
SELECT asEWKT(round(poseInverse(pose(ST_Point(10,5), pi()/2)), 6)); -- Pose(POINT(-5 10),-1.570796) SELECT asEWKT(round(applyPose(poseInverse(pose(ST_Point(10,5), pi()/2)), pose(ST_Point(10,5), pi()/2)), 6)); -- Pose(POINT(0 0),0) SELECT asEWKT(round(poseInverse(pose 'Pose(Point(1 0 0), 0, 0, 0, 1)'), 6)); -- Pose(POINT Z (1 0 0),0,0,0,-1)
Devuelve el ángulo yaw, pitch o roll de una pose, en radianes, bajo la convención Tait-Bryan intrínseca ZYX que exige la clase de conformidad Basic-YPR de OGC GeoPose
yaw(pose) → float pitch(pose) → float roll(pose) → float
Para una pose 2D, yaw devuelve la rotación almacenada theta,por convención, el yaw del marco del cuerpo— y pitch y roll devuelven 0. Para una pose 3D, los tres valores provienen de la descomposición Tait-Bryan intrínseca ZYX del cuaternión de orientación. El término de pitch asin se acota a [-1, 1] para absorber la pequeña deriva numérica que pueden introducir las composiciones largas de cuaterniones.
Sobre una pose temporal, la interpolación del resultado sigue a la dimensión. Una pose 2D interpola linealmente su ángulo almacenado y ese ángulo es su yaw, de modo que un tpose 2D lineal se proyecta en un tfloat lineal. Una pose 3D interpola mediante SLERP, cuya descomposición Tait-Bryan no es lineal en el tiempo, de modo que un tpose 3D se proyecta en un tfloat escalonado: leerlo entre dos instantes devuelve el ángulo del instante de la izquierda en lugar de un ángulo que ninguna pose contiene.
SELECT yaw(pose 'Pose(Point(1 1), 0.5)'); -- 0.5 SELECT pitch(pose 'Pose(Point(1 1), 0.5)'); -- 0 SELECT roll(pose 'Pose(Point(0 0 0), 0.7071067811865476, 0, 0, 0.7071067811865475)'); -- 0 SELECT yaw(pose 'Pose(Point(0 0 0), 0.7071067811865476, 0, 0, 0.7071067811865475)'); -- 1.5707963267948966
Devuelve la orientación de una pose como una terna yaw / pitch / roll, en radianes
ypr(pose) → ypr
Esta es la codificación Basic-YPR de la orientación en OGC GeoPose, la contraparte de quaternion, y devuelve los tres ángulos que yaw, pitch y roll responden de uno en uno. Al igual que quaternion, está definida para ambas dimensiones: una pose 2D gira por su ángulo almacenado y no cabecea ni alabea. Los ángulos están en radianes, mientras que la codificación JSON de GeoPose los escribe en grados.
SELECT ypr(pose 'Pose(Point(1 1), 0.5)'); -- (0.5,0,0) SELECT ypr(pose 'Pose(Point Z(1 1 1), 1, 0, 0, 0)'); -- (0,0,0) SELECT ypr(pose 'Pose(Point Z(1 1 1), 0.5, 0.5, 0.5, 0.5)'); -- (1.5707963267948966,0,1.5707963267948966)
Eleva los accesores de ángulos de Euler a través de una pose temporal, devolviendo un flotante temporal (radianes) por cada instante bajo la convención Tait-Bryan intrínseca ZYX
yaw(tpose) → tfloat pitch(tpose) → tfloat roll(tpose) → tfloat
SELECT asText(yaw(tpose '[Pose(Point(0 0), 0.0)@2000-01-01, Pose(Point(1 1), 0.5)@2000-01-02]')); -- [0@2000-01-01, 0.5@2000-01-02]
El estándar OGC GeoPose v1.0 distingue el marco exterior (la referencia global, por ejemplo, WGS-84 geográfico o ECEF) del marco interior (el marco del cuerpo cuya orientación es el cuaternión de la pose). Las clases de conformidad Basic exigen WGS-84 geográfico como marco exterior y un marco interior implícito de ejes de cuerpo dextrógiros; la clase Advanced nombra su marco exterior de forma explícita y sitúa la pose en el origen de ese marco.
En MobilityDB, el tipo pose codifica el marco exterior de forma implícita mediante su SRID y utiliza el marco interior convencional de ejes de cuerpo dextrógiros. La tabla geopose_frames registra esta correspondencia y declara todos los identificadores de marco que un documento puede nombrar: una serie de secuencia compuesta nombra LTP-ENU y RotateTranslate, y una cadena nombra esos mismos dos marcos como /Extrinsic/LTP-ENU e /Intrinsic/Translate-Rotate, todos bajo la autoridad /geopose/1.0. Las pilas de marcos de la clase Advanced no están admitidas.
SELECT frame_id, authority, code, name, is_geographic FROM geopose_frames ORDER BY frame_id; -- 1 | EPSG | 4326 | WGS-84 geographic (lat/lon/h) | t -- 2 | EPSG | 4978 | WGS-84 ECEF (Earth-Centred Earth-Fixed) | f -- 3 | OGC | LTP | Local Tangent Plane (East-North-Up) | f -- 4 | OGC | BODY | Right-handed body axes (default inner frame) | f -- 5 | /geopose/1.0 | LTP-ENU | GeoPose outer frame of a Composite Sequence Series | f -- 6 | /geopose/1.0 | RotateTranslate | GeoPose inner frame of a Composite Sequence Series | f -- 7 | /geopose/1.0 | /Extrinsic/LTP-ENU | GeoPose outer frame of a Chain | f -- 8 | /geopose/1.0 | /Intrinsic/Translate-Rotate | GeoPose inner frame of a Chain | f
Las filas anteriores provienen de MEOS, que las construye a partir de los identificadores que escriben los codificadores, de modo que un marco que esta compilación puede emitir nunca falta en el catálogo. Un usuario registra marcos adicionales insertándolos en la tabla.
Tres funciones auxiliares SQL proporcionan una interfaz de consulta estable que es independiente de la disposición de la tabla, y leen la tabla para que también se encuentre un marco que registre un usuario:
Devuelve el SRID de un marco, o NULL si el marco es paramétrico (LTP, BODY)
geoPoseFrameSRID(int) → int
SELECT geoPoseFrameSRID(1), geoPoseFrameSRID(2); -- 4326 | 4978 SELECT geoPoseFrameSRID(3); -- NULL
Devuelve el nombre legible del marco
geoPoseFrameName(int) → text
SELECT geoPoseFrameName(1); -- WGS-84 geographic (lat/lon/h) SELECT geoPoseFrameName(2), geoPoseFrameName(3); -- WGS-84 ECEF (Earth-Centred Earth-Fixed) | Local Tangent Plane (East-North-Up)
Devuelve true para los marcos lat/lon/h, false para los cartesianos o proyectados
geoPoseFrameIsGeographic(int) → boolean
SELECT geoPoseFrameIsGeographic(1), geoPoseFrameIsGeographic(2); -- true | false
Los usuarios pueden registrar marcos personalizados insertándolos en geopose_frames; el catálogo está marcado como una tabla de configuración, por lo que pg_dump preserva las filas del usuario.
Aplica la transformación de cuerpo rígido codificada por una pose (la correspondencia cuerpo al mundo de OGC GeoPose) a una geometría en el marco del cuerpo, produciendo la geometría correspondiente en el marco del mundo
La transformación es
world = R(q) · body + p
donde (p, q) son la posición y la orientación de la pose. La forma estática toma una única pose y una geometría estática; la forma temporal eleva la transformación rígida por instante de una tpose a lo largo de sus instantes, produciendo una trayectoria tgeompoint en el marco del mundo de la geometría del cuerpo. La forma estática admite una geometría de cuerpo de cualquier tipo y lleva su forma al marco de la pose; la forma temporal devuelve una tgeompoint, por lo que su geometría de cuerpo debe ser un punto. La pose y la geometría del cuerpo deben tener la misma dimensionalidad y deben coincidir en su SRID, adoptando el desconocido el del otro. La interpolación lineal de la trayectoria resultante es la cuerda de cada segmento, que aproxima la verdadera trayectoria de cuerpo rígido (un arco circular bajo SLERP), la misma concesión que MobilityDB ya adopta para las trayectorias espaciales.
applyPose(geometry, pose) → geometry applyPose(geometry, tpose) → tgeompoint applyPose(pose, pose) → pose
-- A body sensor offset 1 unit along the body X axis, traced through a -- tpose that ends 90 degrees yawed and translated to (10, 20). SELECT asText(applyPose(ST_Point(1,0), tpose '[Pose(Point(0 0), 0)@2026-01-01, Pose(Point(10 20), 1.5707963267948966)@2026-01-02]')); -- [POINT(1 0)@2026-01-01, POINT(10 21)@2026-01-02]
-- La forma de la geometría del cuerpo se lleva al marco de la pose: un casco -- de 4 por 2 alrededor del origen del cuerpo, situado en (10, 20) SELECT ST_AsText(applyPose(ST_MakeEnvelope(-2,-1,2,1), pose 'Pose(Point(10 20), 0)')); -- POLYGON((8 19,8 21,12 21,12 19,8 19))
Cualquiera de los dos marcos puede moverse. Aplicar una pose a una tpose lleva un cuerpo fijo a través de todo el movimiento de su padre, que es lo que pide un sensor montado sobre un vehículo en movimiento; aplicar una tpose a una pose lleva un cuerpo en movimiento a un marco que permanece quieto; y aplicar una tpose a una tpose compone dos marcos que ambos se mueven, sobre el tiempo que comparten. poseInverse se eleva de la misma manera, cambiando el punto de vista sobre un objeto en movimiento durante todo su movimiento.
applyPose(pose, tpose) → tpose applyPose(tpose, pose) → tpose applyPose(tpose, tpose) → tpose poseInverse(tpose) → tpose
-- Un sensor una unidad delante de un cuerpo que termina girado un cuarto de -- vuelta en (10, 20) termina una unidad al norte de él SELECT asEWKT(round(applyPose(pose 'Pose(Point(1 0), 0)', tpose '[Pose(Point(0 0), 0)@2026-01-01, Pose(Point(10 20), 1.5707963267948966)@2026-01-02]'), 6)); /* [Pose(POINT(1 0),0)@2026-01-01, Pose(POINT(10 21),1.570796)@2026-01-02] */
Una pose nombra un marco, por lo que ella misma es un valor en el marco del cuerpo: aplicar una pose a otra compone las dos relaciones entre marcos. Escribiendo P_WV para la pose de un vehículo en el mundo y P_VS para la pose de un sensor en el vehículo, la pose del sensor en el mundo es P_WS = P_WV ∘ P_VS. Esta es la operación que una cadena de poses pliega sobre sus eslabones, de modo que una cadena de dos eslabones se compone en lo que esto devuelve.
-- Un sensor una unidad delante de un vehículo girado un cuarto de vuelta -- queda una unidad al norte de él, y lleva la orientación del vehículo SELECT asEWKT(round(applyPose(pose 'Pose(Point(1 0), 0)', pose(ST_Point(0,0), pi()/2)), 6)); -- Pose(POINT(0 1),1.570796) SELECT asEWKT(round(applyPose(pose 'Pose(Point(1 0), 0)', pose(ST_Point(10,5), pi()/2)), 6)); -- Pose(POINT(10 6),1.570796) SELECT asEWKT(round(applyPose(pose 'Pose(Point(1 0 0), 1, 0, 0, 0)', pose 'Pose(Point(0 0 5), 1, 0, 0, 0)'), 6)); -- Pose(POINT Z (1 0 5),1,0,0,0)
Convierte una pose temporal hacia o desde una envoltura JSON TemporalGeoPose. Cada carga útil por instante es un documento GeoPose de clase Basic estrictamente válido según OGC más un miembro validTime; la envoltura registra la clase de conformidad, la interpolación y (para secuencias y conjuntos de secuencias) la inclusión de los límites del período. Un consumidor GeoPose ajeno a MEOS puede iterar sobre instants[] (o cada sequences[].instants[]) y consumir cada elemento como un documento GeoPose estático.
asGeoPose(tpose,conformance integer=0,maxdecimaldigits integer=-1) → text tposeFromGeoPose(text) → tpose
La forma de la envoltura es
{
"type": "TemporalGeoPose",
"version": "1.0",
"conformance": "Basic-Quaternion" | "Basic-YPR",
"interpolation": "None" | "Discrete" | "Step" | "Linear",
"instants": [...] // for TInstant + TSequence
"lower_inc": true|false, // for TSequence only
"upper_inc": true|false, // for TSequence only
"sequences": [{...}, ...] // for TSequenceSet only
}
SELECT asGeoPose(tpose '[Pose(Point(8 47), 0)@2026-01-01,
Pose(Point(9 48), 0.5)@2026-01-02]',
1, 4);
-- {"type":"TemporalGeoPose","version":"1.0","conformance":"Basic-YPR",
-- "interpolation":"Linear","lower_inc":true,"upper_inc":true,
-- "instants":[
-- {"position":{"lat":47,"lon":8,"h":0},
-- "angles":{"yaw":0,"pitch":0,"roll":0},
-- "validTime":"2026-01-01"},
-- {"position":{"lat":48,"lon":9,"h":0},
-- "angles":{"yaw":28.65,"pitch":0,"roll":0},
-- "validTime":"2026-01-02"}]}
Escribe una pose temporal como un Stream de OGC GeoPose
asGeoPoseStream(tpose,maxdecimaldigits integer=-1) → text
Un stream lleva los marcos de una Serie Irregular sin declarar cuántas poses hay ni cuándo terminan, ya que pueden llegar más. El estándar lo escribe como un header, que contiene el modelo de transición y el marco exterior, y un arreglo streamElements con un elemento por pose. El marco exterior es el plano tangente local Este-Norte-Arriba en la posición de la primera pose, de modo que la cabecera y cada elemento hablan de un mismo punto de tangencia.
La biblioteca C también escribe los dos documentos por partes, mediante tpose_as_geopose_stream_header y tpose_as_geopose_stream_element, para un productor que emite poses a medida que llegan. Una consulta devuelve un valor que ya posee entero, que es lo que esta función escribe.
SELECT asGeoPoseStream(tpose 'Geodpose(Point(0 0 0), 1, 0, 0, 0)@2026-01-01', 6);
/* {"header":{"transitionModel":{"authority":"/geopose/1.0","id":"none",
"parameters":"interpolation=None"},"outerFrame":{"authority":"/geopose/1.0",
"id":"LTP-ENU","parameters":"longitude=0&latitude=0&height=0&crs=EPSG:4979"}},
"streamElements":[{"streamElement":{"frame":{"authority":"/geopose/1.0",
"id":"RotateTranslate","parameters":"translation=[0, 0, 0]&rotation=[1, 0, 0, 0]"},
"validTime":1767254400000}}]} */