1 /*
2  *  This library is free software; you can redistribute it and/or
3  *  modify it under the terms of the GNU Lesser General Public
4  *  License as published by the Free Software Foundation; either
5  *  version 2 of the License, or (at your option) any later version.
6  *
7  *  This library is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
10  *  Lesser General Public License for more details.
11  *
12  *  You should have received a copy of the GNU General Public License
13  *  along with this program; if not, write to the Free Software
14  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15  *
16  *  Copyright (C) 2002 - 2005 Liam Girdwood <lgirdwood@gmail.com>
17  *  Copyright (C) 2004 Petr Kubanek
18  */
19 
20 module nova.hyperbolic_motion;
21 
22 import std.math;
23 
24 import nova.parabolic_motion;
25 import nova.solar;
26 import nova.earth;
27 import nova.transform;
28 import nova.rise_set;
29 import nova.dynamical_time;
30 import nova.sidereal_time;
31 import nova.utility;
32 
33 enum GAUS_GRAV = 0.01720209895; /* Gaussian gravitational constant k */
34 enum PREC = 1e-10;
35 
36 extern (C) {
37 
38 /*! \fn double ln_solve_hyp_barker (double Q1, double G, double t);
39 * \param Q1 See 35.0
40 * \param G See 35.0
41 * \param t Time since perihelion in days
42 * \return Solution of Barkers equation
43 *
44 * Solve Barkers equation. LIAM add more
45 */
46 /* Equ 34.3, Barkers Equation */
47 @nogc double ln_solve_hyp_barker (double Q1, double G, double t) nothrow
48 {
49 	double S, S0, S1, Y, G1, Q2, Q3, Z1, F;
50 	int Z, L;
51 
52 	Q2 = Q1 * t;
53 	S = 2 / (3 * fabs(Q2));
54 	S = 2 / tan(2 * atan(cbrt (tan(atan(S) / 2))));
55 
56 	if (t< 0)
57 		S = -S;
58 
59 	L = 0;
60 
61 	/* we have initial s, so now do the iteration */
62 	do {
63 		S0 = S;
64 		Z = 1;
65 		Y = S * S;
66 		G1 = -Y * S;
67 		Q3 = Q2 + 2.0 * G * S * Y / 3.0;
68 
69 next_z:
70 		Z++;
71 		G1 = -G1 * G * Y;
72 		Z1 = (Z - (Z + 1) * G) / (2.0 * Z + 1.0);
73 		F = Z1 * G1;
74 		Q3 = Q3 + F;
75 
76 		if (Z > 100 || fabs(F) > 10000)
77 			return double.nan;
78 
79 		if (fabs(F) > PREC)
80 			goto next_z;
81 
82 		L++;
83 		if (L > 100)
84 			return double.nan;
85 
86 		do {
87 			S1 = S;
88 			S = (2 * S * S * S / 3 + Q3) / (S * S + 1);
89 		} while (fabs(S - S1) > PREC);
90 	} while (fabs(S - S0) > PREC);
91 
92 	return S;
93 }
94 
95 /*! \fn double ln_get_hyp_true_anomaly (double q, double e, double t);
96 * \param q Perihelion distance in AU
97 * \param e Orbit eccentricity
98 * \param t Time since perihelion
99 * \return True anomaly (degrees)
100 *
101 * Calculate the true anomaly.
102 */
103 /* equ 30.1 */
104 @nogc double ln_get_hyp_true_anomaly (double q, double e, double t) nothrow
105 {
106 	double v, s, Q, gama;
107 
108 	Q = (GAUS_GRAV / (2.0 * q)) * sqrt((1.0 + e) / q);
109 	gama = (1.0 - e) / (1.0 + e);
110 
111 	s = ln_solve_hyp_barker(Q, gama, t);
112 	v = 2.0 * atan(s);
113 
114 	return ln_range_degrees(ln_rad_to_deg(v));
115 }
116 
117 /*! \fn double ln_get_hyp_radius_vector (double q, double e, double t);
118 * \param q Perihelion distance in AU
119 * \param e Orbit eccentricity
120 * \param t Time since perihelion in days
121 * \return Radius vector AU
122 *
123 * Calculate the radius vector.
124 */
125 /* equ 30.2 */
126 @nogc double ln_get_hyp_radius_vector (double q, double e, double t) nothrow
127 {
128 	return q * (1.0 + e) /
129 		(1.0 + e * cos(ln_deg_to_rad(ln_get_hyp_true_anomaly(q, e, t))));
130 }
131 
132 /*! \fn void ln_get_hyp_helio_rect_posn(struct ln_hyp_orbit *orbit, double JD, struct ln_rect_posn *posn);
133 * \param orbit Orbital parameters of object.
134 * \param JD Julian day
135 * \param posn Position pointer to store objects position
136 *
137 * Calculate the objects rectangular heliocentric position given it's orbital
138 * elements for the given julian day.
139 */
140 @nogc void ln_get_hyp_helio_rect_posn(ref ln_hyp_orbit orbit, double JD,
141 	ref ln_rect_posn posn) nothrow
142 {
143 	double A, B, C, F, G, H, P, Q, R;
144 	double sin_e, cos_e;
145 	double a, b, c;
146 	double sin_omega, sin_i, cos_omega, cos_i;
147 	double r, v, t;
148 
149 	/* time since perihelion */
150 	t = JD - orbit.JD;
151 
152 	/* J2000 obliquity of the ecliptic */
153 	sin_e = 0.397777156;
154 	cos_e = 0.917482062;
155 
156 	/* equ 33.7 */
157 	sin_omega = sin(ln_deg_to_rad(orbit.omega));
158 	cos_omega = cos(ln_deg_to_rad(orbit.omega));
159 	sin_i = sin(ln_deg_to_rad(orbit.i));
160 	cos_i = cos(ln_deg_to_rad(orbit.i));
161 	F = cos_omega;
162 	G = sin_omega * cos_e;
163 	H = sin_omega * sin_e;
164 	P = -sin_omega * cos_i;
165 	Q = cos_omega * cos_i * cos_e - sin_i * sin_e;
166 	R = cos_omega * cos_i * sin_e + sin_i * cos_e;
167 
168 	/* equ 33.8 */
169 	A = atan2(F, P);
170 	B = atan2(G, Q);
171 	C = atan2(H, R);
172 	a = sqrt(F * F + P * P);
173 	b = sqrt(G * G + Q * Q);
174 	c = sqrt(H * H + R * R);
175 
176 	/* get true anomaly */
177 	v = ln_get_hyp_true_anomaly(orbit.q, orbit.e, t);
178 
179 	/* get radius vector */
180 	r = ln_get_hyp_radius_vector(orbit.q, orbit.e, t);
181 
182 	/* equ 33.9 */
183 	posn.X = r * a * sin(A + ln_deg_to_rad(orbit.w + v));
184 	posn.Y = r * b * sin(B + ln_deg_to_rad(orbit.w + v));
185 	posn.Z = r * c * sin(C + ln_deg_to_rad(orbit.w + v));
186 }
187 
188 /*! \fn void ln_get_hyp_geo_rect_posn(struct ln_hyp_orbit *orbit, double JD, struct ln_rect_posn *posn);
189 * \param orbit Orbital parameters of object.
190 * \param JD Julian day
191 * \param posn Position pointer to store objects position
192 *
193 * Calculate the objects rectangular geocentric position given it's orbital
194 * elements for the given julian day.
195 */
196 @nogc void ln_get_hyp_geo_rect_posn(ref ln_hyp_orbit orbit, double JD,
197 	ref ln_rect_posn posn) nothrow
198 {
199 	ln_rect_posn p_posn, e_posn;
200 	ln_helio_posn earth;
201 
202 	/* parabolic helio rect coords */
203 	ln_get_hyp_helio_rect_posn(orbit, JD, p_posn);
204 
205 	/* earth rect coords */
206 	ln_get_earth_helio_coords(JD, earth);
207 
208 	ln_get_rect_from_helio(earth, e_posn);
209 	posn.X = p_posn.X - e_posn.X;
210 	posn.Y = p_posn.Y - e_posn.Y;
211 	posn.Z = p_posn.Z - e_posn.Z;
212 }
213 
214 /*!
215 * \fn void ln_get_hyp_body_equ_coords(double JD, struct ln_hyp_orbit *orbit, struct ln_equ_posn *posn)
216 * \param JD Julian Day.
217 * \param orbit Orbital parameters.
218 * \param posn Pointer to hold asteroid position.
219 *
220 * Calculate a bodies equatorial coordinates for the given julian day.
221 */
222 @nogc void ln_get_hyp_body_equ_coords(double JD, ref ln_hyp_orbit orbit,
223 	ref ln_equ_posn posn) nothrow
224 {
225 	ln_rect_posn body_rect_posn, sol_rect_posn;
226 	double dist, t;
227 	double x, y, z;
228 
229 	/* get solar and body rect coords */
230 	ln_get_hyp_helio_rect_posn(orbit, JD, body_rect_posn);
231 	ln_get_solar_geo_coords(JD, sol_rect_posn);
232 
233 	/* calc distance and light time */
234 	dist = ln_get_rect_distance(body_rect_posn, sol_rect_posn);
235 	t = ln_get_light_time(dist);
236 
237 	/* repeat calculation with new time (i.e. JD - t) */
238 	ln_get_hyp_helio_rect_posn(orbit, JD - t, body_rect_posn);
239 
240 	/* calc equ coords equ 33.10 */
241 	x = sol_rect_posn.X + body_rect_posn.X;
242 	y = sol_rect_posn.Y + body_rect_posn.Y;
243 	z = sol_rect_posn.Z + body_rect_posn.Z;
244 
245 	posn.ra = ln_range_degrees(ln_rad_to_deg(atan2(y, x)));
246 	posn.dec = ln_rad_to_deg(atan2(z,sqrt(x * x + y * y)));
247 }
248 
249 /*!
250 * \fn double ln_get_hyp_body_earth_dist(double JD, struct ln_hyp_orbit *orbit)
251 * \param JD Julian day.
252 * \param orbit Orbital parameters
253 * \returns Distance in AU
254 *
255 * Calculate the distance between a body and the Earth
256 * for the given julian day.
257 */
258 @nogc double ln_get_hyp_body_earth_dist(double JD, ref ln_hyp_orbit orbit) nothrow
259 {
260 	ln_rect_posn body_rect_posn, earth_rect_posn;
261 
262 	/* get solar and body rect coords */
263 	ln_get_hyp_geo_rect_posn(orbit, JD, body_rect_posn);
264 	earth_rect_posn.X = 0.0;
265 	earth_rect_posn.Y = 0.0;
266 	earth_rect_posn.Z = 0.0;
267 
268 	/* calc distance */
269 	return ln_get_rect_distance(body_rect_posn, earth_rect_posn);
270 }
271 
272 /*!
273 * \fn double ln_get_hyp_body_solar_dist(double JD, struct ln_hyp_orbit *orbit)
274 * \param JD Julian Day.
275 * \param orbit Orbital parameters
276 * \return The distance in AU between the Sun and the body.
277 *
278 * Calculate the distance between a body and the Sun.
279 */
280 @nogc double ln_get_hyp_body_solar_dist(double JD, ref ln_hyp_orbit orbit) nothrow
281 {
282 	ln_rect_posn body_rect_posn, sol_rect_posn;
283 
284 	/* get solar and body rect coords */
285 	ln_get_hyp_helio_rect_posn (orbit, JD, body_rect_posn);
286 	sol_rect_posn.X = 0.0;
287 	sol_rect_posn.Y = 0.0;
288 	sol_rect_posn.Z = 0.0;
289 
290 	/* calc distance */
291 	return ln_get_rect_distance(body_rect_posn, sol_rect_posn);
292 }
293 
294 /*! \fn double ln_get_hyp_body_phase_angle(double JD, struct ln_hyp_orbit *orbit);
295 * \param JD Julian day
296 * \param orbit Orbital parameters
297 * \return Phase angle.
298 *
299 * Calculate the phase angle of the body. The angle Sun - body - Earth.
300 */
301 @nogc double ln_get_hyp_body_phase_angle(double JD, ref ln_hyp_orbit orbit) nothrow
302 {
303 	double r, R, d;
304 	double t;
305 	double phase;
306 
307 	/* time since perihelion */
308 	t = JD - orbit.JD;
309 
310 	/* get radius vector */
311 	r = ln_get_hyp_radius_vector(orbit.q, orbit.e, t);
312 
313 	/* get solar and Earth-Sun distances */
314 	R = ln_get_earth_solar_dist(JD);
315 	d = ln_get_hyp_body_solar_dist(JD, orbit);
316 
317 	phase = (r * r + d * d - R * R) / (2.0 * r * d);
318 	return ln_range_degrees(ln_rad_to_deg(acos(phase)));
319 }
320 
321 /*! \fn double ln_get_hyp_body_elong(double JD, struct ln_hyp_orbit *orbit);
322 * \param JD Julian day
323 * \param orbit Orbital parameters
324 * \return Elongation to the Sun.
325 *
326 * Calculate the bodies elongation to the Sun..
327 */
328 @nogc double ln_get_hyp_body_elong(double JD, ref ln_hyp_orbit orbit) nothrow
329 {
330 	double r, R, d;
331 	double t;
332 	double elong;
333 
334 	/* time since perihelion */
335 	t = JD - orbit.JD;
336 
337 	/* get radius vector */
338 	r = ln_get_hyp_radius_vector(orbit.q, orbit.e, t);
339 
340 	/* get solar and Earth-Sun distances */
341 	R = ln_get_earth_solar_dist(JD);
342 	d = ln_get_hyp_body_solar_dist(JD, orbit);
343 
344 	elong = (R * R + d * d - r * r) / ( 2.0 * R * d );
345 	return ln_range_degrees(ln_rad_to_deg(acos(elong)));
346 }
347 
348 /*! \fn double ln_get_hyp_body_rst(double JD, struct ln_lnlat_posn *observer, struct ln_hyp_orbit *orbit, struct ln_rst_time *rst);
349 * \param JD Julian day
350 * \param observer Observers position
351 * \param orbit Orbital parameters
352 * \param rst Pointer to store Rise, Set and Transit time in JD
353 * \return 0 for success, else 1 for circumpolar.
354 *
355 * Calculate the time the rise, set and transit (crosses the local meridian at upper culmination)
356 * time of a body with a parabolic orbit for the given Julian day.
357 *
358 * Note: this functions returns 1 if the body is circumpolar, that is it remains the whole
359 * day above or below the horizon. Returns -1 when it remains whole day below the horizon.
360 */
361 @nogc int ln_get_hyp_body_rst(double JD, const ref ln_lnlat_posn observer,
362 	ref ln_hyp_orbit orbit, ref ln_rst_time rst) nothrow
363 {
364 	return ln_get_hyp_body_rst_horizon(JD, observer, orbit,
365 		LN_STAR_STANDART_HORIZON, rst);
366 }
367 
368 /*! \fn double ln_get_hyp_body_rst_horizon(double JD, struct ln_lnlat_posn *observer, struct ln_hyp_orbit *orbit, double horizon, struct ln_rst_time *rst);
369 * \param JD Julian day
370 * \param observer Observers position
371 * \param orbit Orbital parameters
372 * \param horizon Horizon height
373 * \param rst Pointer to store Rise, Set and Transit time in JD
374 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
375 *
376 * Calculate the time the rise, set and transit (crosses the local meridian at upper culmination)
377 * time of a body with a parabolic orbit for the given Julian day.
378 *
379 * Note: this functions returns 1 if the body is circumpolar, that is it remains the whole
380 * day above or below the horizon. Returns -1 when it remains whole day below the horizon.
381 */
382 @nogc int ln_get_hyp_body_rst_horizon(double JD, const ref ln_lnlat_posn observer,
383 	ref ln_hyp_orbit orbit, double horizon, ref ln_rst_time rst) nothrow
384 {
385 	return ln_get_motion_body_rst_horizon(JD, observer,
386 		cast(get_motion_body_coords_t)&ln_get_hyp_body_equ_coords, &orbit,
387 		horizon, rst);
388 }
389 
390 /*! \fn double ln_get_hyp_body_next_rst(double JD, struct ln_lnlat_posn *observer, struct ln_hyp_orbit *orbit, struct ln_rst_time *rst);
391 * \param JD Julian day
392 * \param observer Observers position
393 * \param orbit Orbital parameters
394 * \param rst Pointer to store Rise, Set and Transit time in JD
395 * \return 0 for success, else 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
396 *
397 * Calculate the time of next rise, set and transit (crosses the local meridian at upper culmination)
398 * time of a body with an hyperbolic orbit for the given Julian day.
399 *
400 * This function guarantee, that rise, set and transit will be in <JD, JD+1> range.
401 *
402 * Note: this functions returns 1 if the body is circumpolar, that is it remains the whole
403 * day above the horizon. Returns -1 when it remains the whole day below the horizon.
404 */
405 @nogc int ln_get_hyp_body_next_rst(double JD, const ref ln_lnlat_posn observer,
406 	ref ln_hyp_orbit orbit, ref ln_rst_time rst) nothrow
407 {
408 	return ln_get_hyp_body_next_rst_horizon(JD, observer, orbit,
409 		LN_STAR_STANDART_HORIZON, rst);
410 }
411 
412 /*! \fn double ln_get_hyp_body_next_rst_horizon(double JD, struct ln_lnlat_posn *observer, struct ln_hyp_orbit *orbit, double horizon, struct ln_rst_time *rst);
413 * \param JD Julian day
414 * \param observer Observers position
415 * \param orbit Orbital parameters
416 * \param horizon Horizon height
417 * \param rst Pointer to store Rise, Set and Transit time in JD
418 * \return 0 for success, else 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
419 *
420 * Calculate the time of next rise, set and transit (crosses the local meridian at upper culmination)
421 * time of a body with an hyperbolic orbit for the given Julian day.
422 *
423 * This function guarantee, that rise, set and transit will be in <JD, JD+1> range.
424 *
425 * Note: this functions returns 1 if the body is circumpolar, that is it remains the whole
426 * day above the horizon. Returns -1 when it remains the whole day below the horizon.
427 */
428 @nogc int ln_get_hyp_body_next_rst_horizon(double JD, const ref ln_lnlat_posn observer,
429 	ref ln_hyp_orbit orbit, double horizon, ref ln_rst_time rst) nothrow
430 {
431 	return ln_get_motion_body_next_rst_horizon(JD, observer,
432 		cast(get_motion_body_coords_t)&ln_get_hyp_body_equ_coords, &orbit,
433 		horizon, rst);
434 }
435 
436 /*! \fn double ln_get_hyp_body_next_rst_horizon_future(double JD, struct ln_lnlat_posn *observer, struct ln_hyp_orbit *orbit, double horizon, int day_limit, struct ln_rst_time *rst);
437 * \param JD Julian day
438 * \param observer Observers position
439 * \param orbit Orbital parameters
440 * \param horizon Horizon height
441 * \param day_limit Maximal number of days that will be searched for next rise and set
442 * \param rst Pointer to store Rise, Set and Transit time in JD
443 * \return 0 for success, else 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
444 *
445 * Calculate the time of next rise, set and transit (crosses the local meridian at upper culmination)
446 * time of a body with an hyperbolic orbit for the given Julian day.
447 *
448 * This function guarantee, that rise, set and transit will be in <JD, JD + day_limit> range.
449 *
450 * Note: this functions returns 1 if the body is circumpolar, that is it remains the whole
451 * day above the horizon. Returns -1 when it remains the whole day below the horizon.
452 */
453 @nogc int ln_get_hyp_body_next_rst_horizon_future(double JD,
454 	const ref ln_lnlat_posn observer, ref ln_hyp_orbit orbit,
455 	double horizon, int day_limit, ref ln_rst_time rst) nothrow
456 {
457 	return ln_get_motion_body_next_rst_horizon_future(JD, observer,
458 		cast(get_motion_body_coords_t)&ln_get_hyp_body_equ_coords, &orbit,
459 		horizon, day_limit, rst);
460 }
461 
462 }