Skip to content

Commit 1a782ae

Browse files
authored
Merge pull request #8 from msdemlei/add-proper-motions
Add code for epoch propagation
2 parents ef881cd + 7c3e5a4 commit 1a782ae

9 files changed

+605
-7
lines changed

Diff for: Makefile

+7-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ MODULE_big = pg_sphere
77
OBJS = sscan.o sparse.o sbuffer.o vector3d.o point.o \
88
euler.o circle.o line.o ellipse.o polygon.o \
99
path.o box.o output.o gq_cache.o gist.o key.o \
10-
gnomo.o healpix.o moc.o process_moc.o healpix_bare/healpix_bare.o
10+
gnomo.o healpix.o moc.o process_moc.o healpix_bare/healpix_bare.o \
11+
epochprop.o
1112

1213
EXTENSION = pg_sphere
1314
RELEASE_SQL = $(EXTENSION)--$(PGSPHERE_VERSION).sql
@@ -23,13 +24,13 @@ DATA_built = $(RELEASE_SQL) \
2324
DOCS = README.pg_sphere COPYRIGHT.pg_sphere
2425
REGRESS = init tables points euler circle line ellipse poly path box index \
2526
contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
26-
moc mocautocast
27+
moc mocautocast epochprop
2728

2829
REGRESS_9_5 = index_9.5 # experimental for spoint3
2930

3031
TESTS = init_test tables points euler circle line ellipse poly path box index \
3132
contains_ops contains_ops_compat bounding_box_gist gnomo healpix \
32-
moc mocautocast
33+
moc mocautocast epochprop
3334

3435
ifndef CXXFLAGS
3536
# no support for CXXFLAGS in PGXS before v11
@@ -39,15 +40,15 @@ endif
3940

4041
EXTRA_CLEAN = $(PGS_SQL) pg_sphere.test.sql
4142

42-
CRUSH_TESTS = init_extended circle_extended
43+
CRUSH_TESTS = init_extended circle_extended
4344

4445
# order of sql files is important
4546
PGS_SQL = pgs_types.sql pgs_point.sql pgs_euler.sql pgs_circle.sql \
4647
pgs_line.sql pgs_ellipse.sql pgs_polygon.sql pgs_path.sql \
4748
pgs_box.sql pgs_contains_ops.sql pgs_contains_ops_compat.sql \
4849
pgs_gist.sql gnomo.sql \
4950
healpix.sql pgs_gist_spoint3.sql pgs_moc_type.sql pgs_moc_compat.sql pgs_moc_ops.sql \
50-
pgs_moc_geo_casts.sql
51+
pgs_moc_geo_casts.sql pgs_epochprop.sql
5152
PGS_SQL_9_5 = pgs_9.5.sql # experimental for spoint3
5253

5354
USE_PGXS = 1
@@ -199,7 +200,7 @@ ifeq ($(has_parallel), n)
199200
sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL
200201
endif
201202

202-
pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in
203+
pg_sphere--1.2.0--1.2.1.sql: pgs_moc_geo_casts.sql.in pgs_epochprop.sql.in
203204
cat $^ > $@
204205
ifeq ($(has_parallel), n)
205206
sed -i -e '/PARALLEL/d' $@ # version $(pg_version) does not have support for PARALLEL

Diff for: doc/functions.sgm

+129-1
Original file line numberDiff line numberDiff line change
@@ -667,5 +667,133 @@
667667
</example>
668668

669669
</sect2>
670-
670+
671+
<sect2 id="funcs.epochprop">
672+
<title>
673+
Epoch propagation
674+
</title>
675+
676+
<sect3 id="funcs.epochprop.full">
677+
<title>6-Parameter Epoch Propagation</title>
678+
<funcsynopsis>
679+
<funcprototype>
680+
<funcdef><type>double precision[6]</type>
681+
<function>epoch_prop</function></funcdef>
682+
<paramdef>spoint <parameter>pos</parameter></paramdef>
683+
<paramdef>double precision <parameter>parallax</parameter></paramdef>
684+
<paramdef>double precision <parameter>pm_long</parameter></paramdef>
685+
<paramdef>double precision <parameter>pm_lat</parameter></paramdef>
686+
<paramdef>double precision <parameter>radial_velocity</parameter></paramdef>
687+
<paramdef>double precision <parameter>delta_t</parameter></paramdef>
688+
</funcprototype>
689+
</funcsynopsis>
690+
<para>
691+
Propagates a spherical phase vector in time (in particular,
692+
applies proper motion to positions)
693+
</para>
694+
<para>
695+
Following both pg_sphere and, where missing, astronomical
696+
conventions makes units somewhat eclectic here; pm_long and pm_lat
697+
need to be in rad/yr, whereas parallax is in mas, and
698+
radial_velocity in km/s. The time difference must be in
699+
(Julian) years.
700+
</para>
701+
702+
<para>
703+
This function returns a 6-array of [long, lat, parallax,
704+
pm_long, pm_lat, radial_velocity] of the corresponding values
705+
delta_t years after the reference epoch for the original position.
706+
As in the function arguments, long and lat are in rad, pm_lon and
707+
pm_lat are in rad/yr, parallax is in mas, and radial_velocity is
708+
in km/s. If you are only interested in the position, consider
709+
the epoch_prop_pos functions below that have a somewhat less
710+
contorted signature.
711+
</para>
712+
713+
<para>
714+
It is an error to have either pos or delta_t NULL. For all
715+
other arguments, NULLs are turned into 0s, except for parallax,
716+
where some very small default is put in. In that case,
717+
both parallax and radial_velocity will be NULL in the output
718+
array.
719+
</para>
720+
721+
<para>
722+
This uses the rigorous method derived in "The Hipparcos and Tycho
723+
Catalogues", ESA Special Publication 1200 (1997), p 94f. It does
724+
not take into account relativistic effects, and it also does not
725+
account for secular aberration.
726+
</para>
727+
<example>
728+
<title>Propagating Barnard's star into the past</title>
729+
<programlisting><![CDATA[
730+
SELECT
731+
to_char(DEGREES(tp[1]), '999D9999999999'),
732+
to_char(DEGREES(tp[2]), '999D9999999999'),
733+
to_char(tp[3], '999D999'),
734+
to_char(DEGREES(tp[4])*3.6e6, '999D999'),
735+
to_char(DEGREES(tp[5])*3.6e6, '99999D999'),
736+
to_char(tp[6], '999D999')
737+
FROM (
738+
SELECT epoch_prop(
739+
spoint(radians(269.45207695), radians(4.693364966)), 546.9759,
740+
RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6), -110,
741+
-100) AS tp) AS q;
742+
to_char | to_char | to_char | to_char | to_char | to_char
743+
-----------------+-----------------+----------+----------+------------+----------
744+
269.4742714391 | 4.4072939987 | 543.624 | -791.442 | 10235.412 | -110.450
745+
]]></programlisting>
746+
</example>
747+
748+
</sect3>
749+
<sect3>
750+
<title>Epoch Propagation of Positions Only</title>
751+
<funcsynopsis>
752+
<funcprototype>
753+
<funcdef><type>spoint</type>
754+
<function>epoch_prop_pos</function></funcdef>
755+
<paramdef>spoint <parameter>pos</parameter></paramdef>
756+
<paramdef>double precision <parameter>parallax</parameter></paramdef>
757+
<paramdef>double precision <parameter>pm_long</parameter></paramdef>
758+
<paramdef>double precision <parameter>pm_lat</parameter></paramdef>
759+
<paramdef>double precision <parameter>radial_velocity</parameter></paramdef>
760+
<paramdef>double precision <parameter>delta_t</parameter></paramdef>
761+
</funcprototype>
762+
</funcsynopsis>
763+
<funcsynopsis>
764+
<funcprototype>
765+
<funcdef><type>spoint</type>
766+
<function>epoch_prop_pos</function></funcdef>
767+
<paramdef>spoint <parameter>pos</parameter></paramdef>
768+
<paramdef>double precision <parameter>pm_long</parameter></paramdef>
769+
<paramdef>double precision <parameter>pm_lat</parameter></paramdef>
770+
<paramdef>double precision <parameter>delta_t</parameter></paramdef>
771+
</funcprototype>
772+
</funcsynopsis>
773+
<para>
774+
These are simplified versions of epoch_prop returning only spoints;
775+
the propagated values for the other coordinates are discarded
776+
(but still internallay computed; these functions do not
777+
run any faster than epoch_prop itself).
778+
</para>
779+
780+
<para>
781+
As with epoch_prop itself, missing values (except for pos and
782+
delta_t) are substituted by 0 (or a very small value in the
783+
case of parallax).
784+
</para>
785+
<example>
786+
<title>Barnard's star, position and proper motion</title>
787+
<programlisting><![CDATA[
788+
SELECT epoch_prop_pos(
789+
spoint(radians(269.45207695), radians(4.693364966)),
790+
RADIANS(-801.551/3.6e6), RADIANS(10362/3.6e6),
791+
20) AS tp;
792+
tp
793+
-----------------------------------------
794+
(4.70274793061952 , 0.0829193989380876)
795+
]]></programlisting>
796+
</example>
797+
</sect3>
798+
</sect2>
671799
</sect1>

Diff for: epochprop.c

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/* Handling of (conventional) proper motions.
2+
3+
This code is largely based on a FORTRAN function written by Lennart Lindegren
4+
(Lund Obs) in 1995 that implements the procedure described in The Hipparcos
5+
and Tycho Catalogues (ESA SP-1200), Volume 1, Section 1.5.5, 'Epoch
6+
Transformation: Rigorous Treatment'; cf.
7+
<https://www.cosmos.esa.int/documents/532822/552851/vol1_all.pdf/99adf6e3-6893-4824-8fc2-8d3c9cbba2b5>.
8+
*/
9+
10+
#include <math.h>
11+
#include <pgs_util.h>
12+
13+
#include "point.h"
14+
#include "epochprop.h"
15+
#include "vector3d.h"
16+
17+
PG_FUNCTION_INFO_V1(epoch_prop);
18+
19+
/* Astronomical unit in kilometers */
20+
#define AU 1.495978701e8
21+
22+
/* Julian year in seconds */
23+
#define J_YEAR (365.25*86400)
24+
25+
/* A_nu as per ESA/SP-1200 */
26+
#define A_NU (AU/(J_YEAR))
27+
28+
/* Following SOFA, we use 1e-7 arcsec as minimal parallax
29+
("celestial sphere"); parallax=0 exactly means "infinite distance", which
30+
leads to all sorts for problems; our parallaxes come in in mas, so: */
31+
#define PX_MIN 1e-7*1000
32+
33+
/* propagate an object at a phase vector over a time difference of delta_t,
34+
stuffing an updated phase vector in result.
35+
36+
This does not propagate errors.
37+
*/
38+
static void propagate_phasevec(
39+
const phasevec *pv,
40+
const double delta_t,
41+
phasevec *result) {
42+
43+
double distance_factor, mu0abs, zeta0, parallax;
44+
45+
Vector3D p0, r0, q0;
46+
Vector3D mu0, pprime, qprime, mu, muprime, u, uprime;
47+
48+
/* for very small or null parallaxes, our algorithm breaks; avoid that
49+
and, if we did emergency measures, do not talk about parallax and
50+
radial velocity in the output */
51+
if (pv->parallax_valid) {
52+
parallax = pv->parallax;
53+
} else {
54+
parallax = PX_MIN;
55+
}
56+
result->parallax_valid = pv->parallax_valid;
57+
58+
/* compute the normal triad as Vector3D-s, eq. (1.2.15)*/
59+
spoint_vector3d(&r0, &(pv->pos));
60+
61+
p0.x = -sin(pv->pos.lng);
62+
p0.y = cos(pv->pos.lng);
63+
p0.z = 0;
64+
65+
q0.x = -sin(pv->pos.lat) * cos(pv->pos.lng);
66+
q0.y = -sin(pv->pos.lat) * sin(pv->pos.lng);
67+
q0.z = cos(pv->pos.lat);
68+
69+
/* the original proper motion vector */
70+
mu0.x = mu0.y = mu0.z = 0;
71+
vector3d_addwithscalar(&mu0, pv->pm[0], &p0);
72+
vector3d_addwithscalar(&mu0, pv->pm[1], &q0);
73+
mu0abs = vector3d_length(&mu0);
74+
75+
/* radial velocity in mas/yr ("change of parallax per year"). eq. (1.5.24)
76+
We're transforming this to rad/yr so the units work out below */
77+
zeta0 = (pv->rv * parallax / A_NU) / 3.6e6 / RADIANS;
78+
/* distance factor eq. (1.5.25) */
79+
distance_factor = 1/sqrt(1
80+
+ 2 * zeta0 * delta_t
81+
+ (mu0abs * mu0abs + zeta0 * zeta0) * delta_t * delta_t);
82+
83+
/* the propagated proper motion vector, eq. (1.5.28) */
84+
muprime.x = muprime.y = muprime.z = 0;
85+
vector3d_addwithscalar(&muprime, (1 + zeta0 * delta_t), &mu0);
86+
vector3d_addwithscalar(&muprime, -mu0abs * mu0abs * delta_t, &r0);
87+
mu.x = mu.y = mu.z = 0;
88+
vector3d_addwithscalar(&mu, pow(distance_factor, 3), &muprime);
89+
90+
/* parallax, eq. (1.5.27) */
91+
result->parallax = distance_factor*parallax;
92+
/* zeta/rv, eq. (1.5.29); go back from rad to mas, too */
93+
result->rv = (zeta0 + (mu0abs * mu0abs + zeta0 * zeta0) * delta_t)
94+
* distance_factor * distance_factor
95+
* 3.6e6 * RADIANS
96+
* A_NU / result->parallax;
97+
98+
/* propagated position, eq. (1.5.26) */
99+
uprime.x = uprime.y = uprime.z = 0;
100+
vector3d_addwithscalar(&uprime, (1 + zeta0 * delta_t), &r0);
101+
vector3d_addwithscalar(&uprime, delta_t, &mu0);
102+
u.x = u.y = u.z = 0;
103+
vector3d_addwithscalar(&u, distance_factor, &uprime);
104+
vector3d_spoint(&(result->pos), &u);
105+
106+
/* compute a new triad for the propagated position, eq (1.5.15) */
107+
pprime.x = -sin(result->pos.lng);
108+
pprime.y = cos(result->pos.lng);
109+
pprime.z = 0;
110+
qprime.x = -sin(result->pos.lat) * cos(result->pos.lng);
111+
qprime.y = -sin(result->pos.lat) * sin(result->pos.lng);
112+
qprime.z = cos(result->pos.lat);
113+
114+
/* use it to compute the proper motions, eq. (1.5.32) */
115+
result->pm[0] = vector3d_scalar(&pprime, &mu);
116+
result->pm[1] = vector3d_scalar(&qprime, &mu);
117+
}
118+
119+
120+
/*
121+
Propagate a position with proper motions and optionally parallax
122+
and radial velocity.
123+
124+
Arguments: pos0 (spoint), pm_long, pm_lat (in rad/yr)
125+
par (parallax, mas), rv (in km/s), delta_t (in years)
126+
127+
This returns a 6-array of lat, long (in rad), parallax (in mas)
128+
pmlat, pmlong (in rad/yr), rv (in km/s).
129+
*/
130+
Datum
131+
epoch_prop(PG_FUNCTION_ARGS) {
132+
double delta_t;
133+
phasevec input, output;
134+
ArrayType *result;
135+
Datum retvals[6];
136+
137+
if (PG_ARGISNULL(0)) {
138+
ereport(ERROR,
139+
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
140+
errmsg("NULL position not supported in epoch propagation"))); }
141+
memcpy(&(input.pos), (void*)PG_GETARG_POINTER(0), sizeof(SPoint));
142+
if (PG_ARGISNULL(1)) {
143+
input.parallax = 0;
144+
} else {
145+
input.parallax = PG_GETARG_FLOAT8(1);
146+
}
147+
input.parallax_valid = fabs(input.parallax) > PX_MIN;
148+
149+
if (PG_ARGISNULL(2)) {
150+
input.pm[0] = 0;
151+
} else {
152+
input.pm[0] = PG_GETARG_FLOAT8(2);
153+
}
154+
155+
if (PG_ARGISNULL(3)) {
156+
input.pm[1] = 0;
157+
} else {
158+
input.pm[1] = PG_GETARG_FLOAT8(3);
159+
}
160+
161+
if (PG_ARGISNULL(4)) {
162+
input.rv = 0;
163+
} else {
164+
input.rv = PG_GETARG_FLOAT8(4);
165+
}
166+
167+
if (PG_ARGISNULL(5)) {
168+
ereport(ERROR,
169+
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
170+
errmsg("NULL delta t not supported in epoch propagation"))); }
171+
delta_t = PG_GETARG_FLOAT8(5);
172+
173+
propagate_phasevec(&input, delta_t, &output);
174+
175+
/* change to internal units: rad, rad/yr, mas, and km/s */
176+
retvals[0] = Float8GetDatum(output.pos.lng);
177+
retvals[1] = Float8GetDatum(output.pos.lat);
178+
retvals[2] = Float8GetDatum(output.parallax);
179+
retvals[3] = Float8GetDatum(output.pm[0]);
180+
retvals[4] = Float8GetDatum(output.pm[1]);
181+
retvals[5] = Float8GetDatum(output.rv);
182+
183+
{
184+
bool isnull[6] = {0, 0, 0, 0, 0, 0};
185+
int lower_bounds[1] = {1};
186+
int dims[1] = {6};
187+
#ifdef USE_FLOAT8_BYVAL
188+
bool embyval = true;
189+
#else
190+
bool embyval = false;
191+
#endif
192+
193+
if (! output.parallax_valid) {
194+
/* invalidate parallax and rv */
195+
isnull[2] = 1;
196+
isnull[5] = 1;
197+
}
198+
199+
result = construct_md_array(retvals, isnull, 1, dims, lower_bounds,
200+
FLOAT8OID, sizeof(float8), embyval, 'd');
201+
}
202+
PG_RETURN_ARRAYTYPE_P(result);
203+
}

0 commit comments

Comments
 (0)