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) 2000 - 2005 Liam Girdwood <lgirdwood@gmail.com>
17  */
18 
19 module nova.rise_set;
20 
21 import std.math;
22 import nova.rise_set;
23 import nova.utility;
24 import nova.dynamical_time;
25 import nova.sidereal_time;
26 import nova.transform;
27 
28 public import nova.ln_types;
29 
30 enum LN_STAR_STANDART_HORIZON = -0.5667;
31 
32 extern (C) {
33 
34 @nogc alias get_equ_body_coords_t = void function(double, ref ln_equ_posn) nothrow;
35 @nogc alias get_motion_body_coords_t = void function(double, void *, ref ln_equ_posn) nothrow;
36 // alias get_motion_body_coords_t = void function(double, void * orbit, ref ln_equ_posn); // pure nothrow;
37 
38 // helper function to check if object can be visible
39 static @nogc int check_coords(const ref ln_lnlat_posn observer, double H1,
40 	double horizon, const ref ln_equ_posn object) nothrow
41 {
42 	double h;
43 
44 	/* check if body is circumpolar */
45 	if (fabs(H1) > 1.0) {
46 		/* check if maximal height < horizon */
47 		// h = asin(cos(ln_deg_to_rad(observer->lat - object->dec)))
48 		h = 90.0 + object.dec - observer.lat;
49 		// normalize to <-90;+90>
50 		if (h > 90.0)
51 			h = 180.0 - h;
52 		if (h < -90.0)
53 		  	h = -180.0 - h;
54 		if (h < horizon)
55 			return -1;
56 		// else it must be above horizon
57 		return 1;
58 	}
59 	return 0;
60 }
61 
62 /*! \fn int ln_get_object_rst(double JD, struct ln_lnlat_posn *observer, struct ln_equ_posn *object, struct ln_rst_time *rst);
63 * \param JD Julian day
64 * \param observer Observers position
65 * \param object Object position
66 * \param rst Pointer to store Rise, Set and Transit time in JD
67 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
68 *
69 * Calculate the time the rise, set and transit (crosses the local meridian at upper culmination)
70 * time of the object for the given Julian day.
71 *
72 * Note: this functions returns 1 if the object is circumpolar, that is it remains the whole
73 * day above the horizon. Returns -1 when it remains the whole day bellow the horizon.
74 */
75 @nogc int ln_get_object_rst(double JD, const ref ln_lnlat_posn observer,
76 	const ref ln_equ_posn object, ref ln_rst_time rst) nothrow
77 {
78 	return ln_get_object_rst_horizon(JD, observer, object,
79 		LN_STAR_STANDART_HORIZON, rst);	/* standard altitude of stars */
80 }
81 
82 /*! \fn int ln_get_object_rst_horizon(double JD, struct ln_lnlat_posn *observer, struct ln_equ_posn *object, long double horizon, struct ln_rst_time *rst);
83 * \param JD Julian day
84 * \param observer Observers position
85 * \param object Object position
86 * \param horizon Horizon height
87 * \param rst Pointer to store Rise, Set and Transit time in JD
88 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
89 *
90 * Calculate the time the rise, set and transit (crosses the local meridian at upper culmination)
91 * time of the object for the given Julian day and horizon.
92 *
93 * Note: this functions returns 1 if the object is circumpolar, that is it remains the whole
94 * day above the horizon. Returns -1 when it remains whole day bellow the horizon.
95 */
96 @nogc int ln_get_object_rst_horizon(double JD, const ref ln_lnlat_posn observer,
97 	const ref ln_equ_posn object, real horizon, ref ln_rst_time rst) nothrow
98 {
99 	return ln_get_object_rst_horizon_offset(JD, observer, object, horizon,
100 			rst, 0.5);
101 }
102 
103 @nogc int ln_get_object_rst_horizon_offset(double JD, const ref ln_lnlat_posn observer,
104 	const ref ln_equ_posn object, real horizon, ref ln_rst_time rst,
105 	double ut_offset) nothrow
106 {
107 	int jd;
108 	real O, JD_UT, H0, H1;
109 	double Hat, Har, Has, altr, alts;
110 	double mt, mr, ms, mst, msr, mss;
111 	double dmt, dmr, dms;
112 	int ret, i;
113 
114 	if (isNaN(ut_offset)) {
115 		JD_UT = JD;
116 	} else {
117 		/* convert local sidereal time into degrees
118 			 for 0h of UT on day JD */
119 		jd = cast(int)JD;
120 		JD_UT = jd + ut_offset;
121 	}
122 
123 	O = ln_get_apparent_sidereal_time(JD_UT);
124 	O *= 15.0;
125 
126 	/* equ 15.1 */
127 	H0 = (sin(ln_deg_to_rad(horizon)) -
128 		 sin(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(object.dec)));
129 	H1 = (cos(ln_deg_to_rad(observer.lat)) * cos(ln_deg_to_rad(object.dec)));
130 
131 	H1 = H0 / H1;
132 
133 	ret = check_coords(observer, H1, horizon, object);
134 	if (ret)
135 		return ret;
136 
137 	H0 = acos(H1);
138 	H0 = ln_rad_to_deg(H0);
139 
140 	/* equ 15.2 */
141 	mt = (object.ra - observer.lng - O) / 360.0;
142 	mr = mt - H0 / 360.0;
143 	ms = mt + H0 / 360.0;
144 
145 	for (i = 0; i < 3; i++) {
146 		/* put in correct range */
147 		if (mt > 1.0)
148 			mt--;
149 		else if (mt < 0)
150 			mt++;
151 		if (mr > 1.0)
152 			mr--;
153 		else if (mr < 0)
154 			mr++;
155 		if (ms > 1.0)
156 			ms--;
157 		else if (ms < 0)
158 			ms++;
159 
160 		/* find sidereal time at Greenwich, in degrees, for each m */
161 		mst = O + 360.985647 * mt;
162 		msr = O + 360.985647 * mr;
163 		mss = O + 360.985647 * ms;
164 
165 		/* find local hour angle */
166 		Hat = mst + observer.lng - object.ra;
167 		Har = msr + observer.lng - object.ra;
168 		Has = mss + observer.lng - object.ra;
169 
170 		/* find altitude for rise and set */
171 		altr = sin(ln_deg_to_rad(observer.lat)) *
172 			sin(ln_deg_to_rad(object.dec)) +
173 			cos(ln_deg_to_rad(observer.lat)) *
174 			cos(ln_deg_to_rad(object.dec)) *
175 			cos(ln_deg_to_rad(Har));
176 		alts = sin(ln_deg_to_rad(observer.lat)) *
177 			sin(ln_deg_to_rad(object.dec)) +
178 			cos(ln_deg_to_rad(observer.lat)) *
179 			cos(ln_deg_to_rad(object.dec)) *
180 			cos(ln_deg_to_rad(Has));
181 
182 		/* must be in degrees */
183 		altr = ln_rad_to_deg(altr);
184 		alts = ln_rad_to_deg(alts);
185 
186 		/* corrections for m */
187         // TODO: this call has no side-effects
188 		// ln_range_degrees(Hat);
189 		if (Hat > 180.0)
190 			Hat -= 360;
191 
192 		dmt = -(Hat / 360.0);
193 		dmr = (altr - horizon) / (360 * cos(ln_deg_to_rad(object.dec)) *
194 			cos(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(Har)));
195 		dms = (alts - horizon) / (360 * cos(ln_deg_to_rad(object.dec)) *
196 			cos(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(Has)));
197 
198 		/* add corrections and change to JD */
199 		mt += dmt;
200 		mr += dmr;
201 		ms += dms;
202 
203 		if (mt <= 1.0 && mt >= 0.0 &&
204 			mr <= 1.0 && mr >= 0.0 &&
205 			ms <= 1.0 && ms >= 0.0)
206 			break;
207 	}
208 
209 	rst.rise = JD_UT + mr;
210 	rst.transit = JD_UT + mt;
211 	rst.set = JD_UT + ms;
212 
213 	/* not circumpolar */
214 	return 0;
215 }
216 
217 /*! \fn int ln_get_object_next_rst(double JD, struct ln_lnlat_posn *observer, struct ln_equ_posn *object, struct ln_rst_time *rst);
218 * \param JD Julian day
219 * \param observer Observers position
220 * \param object Object position
221 * \param rst Pointer to store Rise, Set and Transit time in JD
222 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
223 *
224 * Calculate the time of next rise, set and transit (crosses the local meridian at upper culmination)
225 * time of the object for the given Julian day and horizon.
226 *
227 * This function guarantee, that rise, set and transit will be in <JD, JD+1> range.
228 *
229 * Note: this functions returns 1 if the object is circumpolar, that is it remains the whole
230 * day above the horizon. Returns -1 when it remains whole day bellow the horizon.
231 */
232 @nogc int ln_get_object_next_rst(double JD, const ref ln_lnlat_posn observer,
233 	const ref ln_equ_posn object, ref ln_rst_time rst) nothrow
234 {
235 	return ln_get_object_next_rst_horizon(JD, observer, object,
236 		LN_STAR_STANDART_HORIZON, rst);
237 }
238 
239 //helper functions for ln_get_object_next_rst_horizon
240 static @nogc void set_next_rst(ref ln_rst_time rst, double diff, ref ln_rst_time rst2) nothrow
241 {
242 	rst2.rise = rst.rise + diff;
243 	rst2.transit = rst.transit + diff;
244 	rst2.set = rst.set + diff;
245 }
246 
247 static @nogc double find_next(double JD, double jd1, double jd2, double jd3) nothrow
248 {
249 	if (isNaN(jd1) && isNaN(jd2))
250 		return jd3;
251 
252 	if(JD < jd1)
253 		return jd1;
254 
255 	if(JD < jd2)
256 		return jd2;
257 
258 	return jd3;
259 }
260 
261 /*! \fn int ln_get_object_next_rst_horizon(double JD, struct ln_lnlat_posn *observer, struct ln_equ_posn *object, double horizon, struct ln_rst_time *rst);
262 * \param JD Julian day
263 * \param observer Observers position
264 * \param object Object position
265 * \param horizon Horizon height
266 * \param rst Pointer to store Rise, Set and Transit time in JD
267 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
268 *
269 * Calculate the time of next rise, set and transit (crosses the local meridian at upper culmination)
270 * time of the object for the given Julian day and horizon.
271 *
272 * This function guarantee, that rise, set and transit will be in <JD, JD+1> range.
273 *
274 * Note: this functions returns 1 if the object is circumpolar, that is it remains the whole
275 * day above the horizon. Returns -1 when it remains whole day bellow the horizon.
276 */
277 @nogc int ln_get_object_next_rst_horizon(double JD, const ref ln_lnlat_posn observer,
278 	const ref ln_equ_posn object, double horizon, ref ln_rst_time rst) nothrow
279 {
280 	int ret;
281 	ln_rst_time rst_1, rst_2;
282 
283 	ret = ln_get_object_rst_horizon_offset(JD, observer, object, horizon, rst, double.nan);
284 	if (ret)
285 		// circumpolar
286 		return ret;
287 
288 	if (rst.rise > (JD + 0.5) || rst.transit > (JD + 0.5)
289 			|| rst.set > (JD + 0.5))
290 		ln_get_object_rst_horizon_offset(JD - 1.0, observer, object, horizon,
291 				rst_1, double.nan);
292 	else
293 		set_next_rst(rst, -1.0, rst_1);
294 
295 	if (rst.rise < JD || rst.transit < JD || rst.set < JD)
296 		ln_get_object_rst_horizon_offset(JD + 1.0, observer, object, horizon,
297 				rst_2, double.nan);
298 	else
299 		set_next_rst (rst, 1.0, rst_2);
300 
301 	rst.rise = find_next(JD, rst_1.rise, rst.rise, rst_2.rise);
302 	rst.transit = find_next(JD, rst_1.transit, rst.transit, rst_2.transit);
303 	rst.set = find_next(JD, rst_1.set, rst.set, rst_2.set);
304 
305 	if (isNaN (rst.rise))
306 		return ret;
307 
308 	return 0;
309 }
310 
311 /*! \fn int ln_get_body_rst_horizon(double JD, struct ln_lnlat_posn *observer, void (*get_equ_body_coords) (double, struct ln_equ_posn *), double horizon, struct ln_rst_time *rst);
312 * \param JD Julian day
313 * \param observer Observers position
314 * \param get_equ_body_coords Pointer to get_equ_body_coords() function
315 * \param horizon Horizon, see LN_XXX_HORIZON constants
316 * \param rst Pointer to store Rise, Set and Transit time in JD
317 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
318 *
319 * Calculate the time the rise, set and transit (crosses the local meridian at
320 * upper culmination) time of the body for the given Julian day and given
321 * horizon.
322 *
323 *
324 * Note 1: this functions returns 1 if the object is circumpolar, that is it remains the whole
325 * day above the horizon. Returns -1 when it remains whole day bellow the horizon.
326 *
327 * Note 2: this function will not work for body, which ra changes more
328 * then 180 deg in one day (get_equ_body_coords changes so much). But
329 * you should't use that function for any body which moves to fast..use
330 * some special function for such things.
331 */
332 @nogc int ln_get_body_rst_horizon(double JD, const ref ln_lnlat_posn observer,
333 	get_equ_body_coords_t get_equ_body_coords, double horizon,
334 	ref ln_rst_time rst) nothrow
335 {
336 	return ln_get_body_rst_horizon_offset(JD, observer, get_equ_body_coords, horizon, rst, 0.5);
337 }
338 
339 @nogc int ln_get_body_rst_horizon_offset(double JD, const ref ln_lnlat_posn observer,
340 	get_equ_body_coords_t get_equ_body_coords, double horizon,
341 	ref ln_rst_time rst, double ut_offset) nothrow
342 {
343 	int jd;
344 	double T, O, JD_UT, H0, H1;
345 	double Hat, Har, Has, altr, alts;
346 	double mt, mr, ms, mst, msr, mss, nt, nr, ns;
347 	ln_equ_posn sol1, sol2, sol3, post, posr, poss;
348 	double dmt, dmr, dms;
349 	int ret, i;
350 
351 	/* dynamical time diff */
352 	T = ln_get_dynamical_time_diff(JD);
353 
354 	if (isNaN(ut_offset)) {
355 		JD_UT = JD;
356 	} else {
357 		jd = cast(int)JD;
358 		JD_UT = jd + ut_offset;
359 	}
360 	/* convert local sidereal time into degrees
361 		 for 0h of UT on day JD */
362 	JD_UT = JD;
363 	O = ln_get_apparent_sidereal_time(JD_UT);
364 	O *= 15.0;
365 
366 	/* get body coords for JD_UT -1, JD_UT and JD_UT + 1 */
367 	get_equ_body_coords(JD_UT - 1.0, sol1);
368 	get_equ_body_coords(JD_UT, sol2);
369 	get_equ_body_coords(JD_UT + 1.0, sol3);
370 
371 	/* equ 15.1 */
372 	H0 =
373 		(sin(ln_deg_to_rad(horizon)) -
374 		 sin(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(sol2.dec)));
375 	H1 = (cos(ln_deg_to_rad(observer.lat)) * cos(ln_deg_to_rad(sol2.dec)));
376 
377 	H1 = H0 / H1;
378 
379 	ret = check_coords (observer, H1, horizon, sol2);
380 	if (ret)
381 		return ret;
382 
383 	H0 = acos(H1);
384 	H0 = ln_rad_to_deg(H0);
385 
386 	/* correct ra values for interpolation	- put them to the same side of circle */
387 	if ((sol1.ra - sol2.ra) > 180.0)
388 		sol2.ra += 360.0;
389 
390 	if ((sol2.ra - sol3.ra) > 180.0)
391 		sol3.ra += 360.0;
392 
393 	if ((sol3.ra - sol2.ra) > 180.0)
394 		sol3.ra -= 360.0;
395 
396 	if ((sol2.ra - sol1.ra) > 180.0)
397 		sol3.ra -= 360.0;
398 
399 	/* equ 15.2 */
400 	mt = (sol2.ra - observer.lng - O) / 360.0;
401 	mr = mt - H0 / 360.0;
402 	ms = mt + H0 / 360.0;
403 
404 	for (i = 0; i < 3; i++) {
405 		/* put in correct range */
406 		if (mt > 1.0)
407 			mt--;
408 		else if (mt < 0)
409 			mt++;
410 		if (mr > 1.0)
411 			mr--;
412 		else if (mr < 0)
413 			mr++;
414 		if (ms > 1.0)
415 			ms--;
416 		else if (ms < 0)
417 			ms++;
418 
419 		/* find sidereal time at Greenwich, in degrees, for each m */
420 		mst = O + 360.985647 * mt;
421 		msr = O + 360.985647 * mr;
422 		mss = O + 360.985647 * ms;
423 
424 		nt = mt + T / 86400.0;
425 		nr = mr + T / 86400.0;
426 		ns = ms + T / 86400.0;
427 
428 		/* interpolate ra and dec for each m, except for transit dec (dec2) */
429 		posr.ra = ln_interpolate3(nr, sol1.ra, sol2.ra, sol3.ra);
430 		posr.dec = ln_interpolate3(nr, sol1.dec, sol2.dec, sol3.dec);
431 		post.ra = ln_interpolate3(nt, sol1.ra, sol2.ra, sol3.ra);
432 		poss.ra = ln_interpolate3(ns, sol1.ra, sol2.ra, sol3.ra);
433 		poss.dec = ln_interpolate3(ns, sol1.dec, sol2.dec, sol3.dec);
434 
435 		/* find local hour angle */
436 		Hat = mst + observer.lng - post.ra;
437 		Har = msr + observer.lng - posr.ra;
438 		Has = mss + observer.lng - poss.ra;
439 
440 		/* find altitude for rise and set */
441 		altr = sin(ln_deg_to_rad(observer.lat)) *
442 				sin(ln_deg_to_rad(posr.dec)) +
443 				cos(ln_deg_to_rad(observer.lat)) *
444 				cos(ln_deg_to_rad(posr.dec)) *
445 				cos(ln_deg_to_rad(Har));
446 		alts = sin(ln_deg_to_rad(observer.lat)) *
447 				sin(ln_deg_to_rad(poss.dec)) +
448 				cos(ln_deg_to_rad(observer.lat)) *
449 				cos(ln_deg_to_rad(poss.dec)) *
450 				cos(ln_deg_to_rad(Has));
451 
452 		/* must be in degrees */
453 		altr = ln_rad_to_deg(altr);
454 		alts = ln_rad_to_deg(alts);
455 
456 		/* corrections for m */
457         // TODO: this call has no side-effects
458 		// ln_range_degrees(Hat);
459 		if (Hat > 180.0)
460 			Hat -= 360;
461 
462 		dmt = -(Hat / 360.0);
463 		dmr = (altr - horizon) / (360.0 * cos(ln_deg_to_rad(posr.dec)) *
464 			cos(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(Har)));
465 		dms = (alts - horizon) / (360.0 * cos(ln_deg_to_rad(poss.dec)) *
466 			cos(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(Has)));
467 
468 		/* add corrections and change to JD */
469 		mt += dmt;
470 		mr += dmr;
471 		ms += dms;
472 
473 		if (mt <= 1.0 && mt >= 0.0 &&
474 			mr <= 1.0 && mr >= 0.0 &&
475 			ms <= 1.0 && ms >= 0.0)
476 			break;
477 	}
478 
479 	rst.rise = JD_UT + mr;
480 	rst.transit = JD_UT + mt;
481 	rst.set = JD_UT + ms;
482 
483 	/* not circumpolar */
484 	return 0;
485 }
486 
487 /*! \fn int ln_get_body_next_rst_horizon(double JD, struct ln_lnlat_posn *observer, struct ln_equ_posn *object, double horizon, struct ln_rst_time *rst);
488 * \param JD Julian day
489 * \param observer Observers position
490 * \param get_equ_body_coords Pointer to get_equ_body_coords() function
491 * \param horizon Horizon, see LN_XXX_HORIZON constants
492 * \param rst Pointer to store Rise, Set and Transit time in JD
493 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
494 *
495 * Calculate the time of next rise, set and transit (crosses the local meridian at
496 * upper culmination) time of the body for the given Julian day and given
497 * horizon.
498 *
499 * This function guarantee, that rise, set and transit will be in <JD, JD+1> range.
500 *
501 * Note 1: this functions returns 1 if the body is circumpolar, that is it remains
502 * the whole day either above or below the horizon.
503 *
504 * Note 2: This function will not work for body, which ra changes more
505 * then 180 deg in one day (get_equ_body_coords changes so much). But
506 * you should't use that function for any body which moves to fast..use
507 * some special function for such things.
508 */
509 @nogc int ln_get_body_next_rst_horizon(double JD, const ref ln_lnlat_posn observer,
510 	get_equ_body_coords_t get_equ_body_coords, double horizon,
511 	ref ln_rst_time rst) nothrow
512 {
513 	return ln_get_body_next_rst_horizon_future(JD, observer, get_equ_body_coords, horizon, 1, rst);
514 }
515 
516 /*! \fn int ln_get_body_next_rst_horizon_future(double JD, struct ln_lnlat_posn *observer, void (*get_equ_body_coords) (double,struct ln_equ_posn *), double horizon, int day_limit, struct ln_rst_time *rst);
517 * \param JD Julian day
518 * \param observer Observers position
519 * \param get_equ_body_coords Pointer to get_equ_body_coords() function
520 * \param horizon Horizon, see LN_XXX_HORIZON constants
521 * \param day_limit Maximal number of days that will be searched for next rise and set
522 * \param rst Pointer to store Rise, Set and Transit time in JD
523 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
524 *
525 * Calculate the time of next rise, set and transit (crosses the local meridian at
526 * upper culmination) time of the body for the given Julian day and given
527 * horizon.
528 *
529 * This function guarantee, that rise, set and transit will be in <JD, JD + day_limit> range.
530 *
531 * Note 1: this functions returns 1 if the body is circumpolar, that is it remains
532 * the whole day either above or below the horizon.
533 *
534 * Note 2: This function will not work for body, which ra changes more
535 * than 180 deg in one day (get_equ_body_coords changes so much). But
536 * you should't use that function for any body which moves to fast..use
537 * some special function for such things.
538 */
539 @nogc int ln_get_body_next_rst_horizon_future(double JD,
540 	const ref ln_lnlat_posn observer,
541 	get_equ_body_coords_t get_equ_body_coords,
542 	double horizon, int day_limit, ref ln_rst_time rst) nothrow
543 {
544 	int ret;
545 	ln_rst_time rst_1, rst_2;
546 
547 	ret = ln_get_body_rst_horizon_offset(JD, observer, get_equ_body_coords,
548 		horizon, rst, double.nan);
549 	if (ret && day_limit == 1)
550 		// circumpolar
551 		return ret;
552 
553 	if (!ret &&
554 		(rst.rise >(JD + 0.5) || rst.transit >(JD + 0.5) ||
555 		rst.set >(JD + 0.5))) {
556 
557 		ret = ln_get_body_rst_horizon_offset(JD - 1, observer,
558                             get_equ_body_coords, horizon, rst_1, double.nan);
559 		if (ret)
560 			set_next_rst (rst, -1, rst_1);
561 	} else {
562 		rst.rise = double.nan;
563 		rst.transit = double.nan;
564 		rst.set = double.nan;
565 
566 		set_next_rst(rst, -1, rst_1);
567 	}
568 
569 	if (ret || (rst.rise < JD || rst.transit < JD || rst.set < JD)) {
570 	  	// find next day when it will rise, up to day_limit days
571 		int day = 1;
572 
573 		while (day <= day_limit) {
574 			ret = ln_get_body_rst_horizon_offset(JD + day, observer,
575                             get_equ_body_coords, horizon, rst_2, double.nan);
576 
577 			if (!ret) {
578 				day = day_limit + 2;
579 				break;
580 			}
581 			day++;
582 		}
583 		if (day == day_limit + 1)
584 			// it's then really circumpolar in searched period
585 			return ret;
586 	} else {
587 		set_next_rst(rst, +1, rst_2);
588 	}
589 
590 	rst.rise = find_next(JD, rst_1.rise, rst.rise, rst_2.rise);
591 	rst.transit = find_next(JD, rst_1.transit, rst.transit, rst_2.transit);
592 	rst.set = find_next(JD, rst_1.set, rst.set, rst_2.set);
593 	if (isNaN (rst.rise))
594 		return ret;
595 
596 	return 0;
597 }
598 
599 /*! \fn int ln_get_body_rst_horizon(double JD, struct ln_lnlat_posn *observer, void (*get_equ_body_coords) (double, struct ln_equ_posn *), double horizon, struct ln_rst_time *rst);
600 * \param JD Julian day
601 * \param observer Observers position
602 * \param get_motion_body_coords Pointer to ln_get_ell_body_equ_coords. ln_get_para_body_equ_coords or ln_get_hyp_body_equ_coords function
603 * \param horizon Horizon, see LN_XXX_HORIZON constants
604 * \param rst Pointer to store Rise, Set and Transit time in JD
605 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
606 *
607 * Calculate the time the rise, set and transit (crosses the local meridian at
608 * upper culmination) time of the body for the given Julian day and given
609 * horizon.
610 *
611 * Note 1: this functions returns 1 if the body is circumpolar, that is it remains
612 * the whole day either above or below the horizon.
613 */
614 @nogc int ln_get_motion_body_rst_horizon(double JD, const ref ln_lnlat_posn observer,
615 	get_motion_body_coords_t get_motion_body_coords,
616 	void * orbit, double horizon, ref ln_rst_time rst) nothrow
617 {
618 	return ln_get_motion_body_rst_horizon_offset(JD, observer,
619                         get_motion_body_coords, orbit, horizon, rst, 0.5);
620 }
621 
622 @nogc int ln_get_motion_body_rst_horizon_offset(double JD,
623 	const ref ln_lnlat_posn observer,
624 	get_motion_body_coords_t get_motion_body_coords, void *orbit,
625 	double horizon, ref ln_rst_time rst, double ut_offset) nothrow
626 {
627 	int jd;
628 	double T, O, JD_UT, H0, H1;
629 	double Hat, Har, Has, altr, alts;
630 	double mt, mr, ms, mst, msr, mss, nt, nr, ns;
631 	ln_equ_posn sol1, sol2, sol3, post, posr, poss;
632 	double dmt, dmr, dms;
633 	int ret, i;
634 
635 	/* dynamical time diff */
636 	T = ln_get_dynamical_time_diff(JD);
637 
638 	if (isNaN(ut_offset)) {
639 		JD_UT = JD;
640 	} else {
641 		jd = cast(int)JD;
642 		JD_UT = jd + ut_offset;
643 	}
644 	O = ln_get_apparent_sidereal_time(JD_UT);
645 	O *= 15.0;
646 
647 	/* get body coords for JD_UT -1, JD_UT and JD_UT + 1 */
648 	get_motion_body_coords(JD_UT - 1.0, orbit, sol1);
649 	get_motion_body_coords(JD_UT, orbit, sol2);
650 	get_motion_body_coords(JD_UT + 1.0, orbit, sol3);
651 
652 	/* equ 15.1 */
653 	H0 = (sin(ln_deg_to_rad(horizon)) - sin(ln_deg_to_rad(observer.lat)) *
654 			sin(ln_deg_to_rad(sol2.dec)));
655 	H1 = (cos(ln_deg_to_rad(observer.lat)) * cos(ln_deg_to_rad(sol2.dec)));
656 
657 	H1 = H0 / H1;
658 
659 	ret = check_coords(observer, H1, horizon, sol2);
660 	if (ret)
661 		return ret;
662 
663 	H0 = acos(H1);
664 	H0 = ln_rad_to_deg(H0);
665 
666 	/* correct ra values for interpolation	- put them to the same side of circle */
667 	if ((sol1.ra - sol2.ra) > 180.0)
668 		sol2.ra += 360.0;
669 
670 	if ((sol2.ra - sol3.ra) > 180.0)
671 		sol3.ra += 360.0;
672 
673 	if ((sol3.ra - sol2.ra) > 180.0)
674 		sol3.ra -= 360.0;
675 
676 	if ((sol2.ra - sol1.ra) > 180.0)
677 		sol3.ra -= 360.0;
678 
679 	for (i = 0; i < 3; i++) {
680 		/* equ 15.2 */
681 		mt = (sol2.ra - observer.lng - O) / 360.0;
682 		mr = mt - H0 / 360.0;
683 		ms = mt + H0 / 360.0;
684 
685 		/* put in correct range */
686 		if (mt > 1.0 )
687 			mt--;
688 		else if (mt < 0.0)
689 			mt++;
690 		if (mr > 1.0 )
691 			mr--;
692 		else if (mr < 0.0)
693 			mr++;
694 		if (ms > 1.0 )
695 			ms--;
696 		else if (ms < 0.0)
697 			ms++;
698 
699 		/* find sidereal time at Greenwich, in degrees, for each m*/
700 		mst = O + 360.985647 * mt;
701 		msr = O + 360.985647 * mr;
702 		mss = O + 360.985647 * ms;
703 
704 		nt = mt + T / 86400.0;
705 		nr = mr + T / 86400.0;
706 		ns = ms + T / 86400.0;
707 
708 		/* interpolate ra and dec for each m, except for transit dec (dec2) */
709 		posr.ra = ln_interpolate3(nr, sol1.ra, sol2.ra, sol3.ra);
710 		posr.dec = ln_interpolate3(nr, sol1.dec, sol2.dec, sol3.dec);
711 		post.ra = ln_interpolate3(nt, sol1.ra, sol2.ra, sol3.ra);
712 		poss.ra = ln_interpolate3(ns, sol1.ra, sol2.ra, sol3.ra);
713 		poss.dec = ln_interpolate3(ns, sol1.dec, sol2.dec, sol3.dec);
714 
715 		/* find local hour angle */
716 		Hat = mst + observer.lng - post.ra;
717 		Har = msr + observer.lng - posr.ra;
718 		Has = mss + observer.lng - poss.ra;
719 
720 		/* find altitude for rise and set */
721 		altr = sin(ln_deg_to_rad(observer.lat)) *
722 				sin(ln_deg_to_rad(posr.dec)) +
723 				cos(ln_deg_to_rad(observer.lat)) *
724 				cos(ln_deg_to_rad(posr.dec)) *
725 				cos(ln_deg_to_rad(Har));
726 		alts = sin(ln_deg_to_rad(observer.lat)) *
727 				sin(ln_deg_to_rad(poss.dec)) +
728 				cos(ln_deg_to_rad(observer.lat)) *
729 				cos(ln_deg_to_rad(poss.dec)) *
730 				cos(ln_deg_to_rad(Has));
731 
732 		/* corrections for m */
733 		dmt = - (Hat / 360.0);
734 		dmr = (altr - horizon) / (360.0 * cos(ln_deg_to_rad(posr.dec)) *
735 			cos(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(Har)));
736 		dms = (alts - horizon) / (360.0 * cos(ln_deg_to_rad(poss.dec)) *
737 			cos(ln_deg_to_rad(observer.lat)) * sin(ln_deg_to_rad(Has)));
738 
739 		/* add corrections and change to JD */
740 		mt += dmt;
741 		mr += dmr;
742 		ms += dms;
743 
744 		if (mt <= 1.0 && mt >= 0.0 &&
745 			mr <= 1.0 && mr >= 0.0 &&
746 			ms <= 1.0 && ms >= 0.0)
747 			break;
748 	}
749 
750 	rst.rise = JD_UT + mr;
751 	rst.transit = JD_UT + mt;
752 	rst.set = JD_UT + ms;
753 
754 	/* not circumpolar */
755 	return 0;
756 }
757 
758 /*! \fn int ln_get_body_next_rst_horizon(double JD, struct ln_lnlat_posn *observer, void (*get_equ_body_coords) (double, struct ln_equ_posn *), double horizon, struct ln_rst_time *rst);
759 * \param JD Julian day
760 * \param observer Observers position
761 * \param get_motion_body_coords Pointer to ln_get_ell_body_equ_coords. ln_get_para_body_equ_coords or ln_get_hyp_body_equ_coords function
762 * \param horizon Horizon, see LN_XXX_HORIZON constants
763 * \param rst Pointer to store Rise, Set and Transit time in JD
764 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
765 *
766 * Calculate the time of next rise, set and transit (crosses the local meridian at
767 * upper culmination) time of the body for the given Julian day and given
768 * horizon.
769 *
770 * This function guarantee, that rise, set and transit will be in <JD, JD+1> range.
771 *
772 * Note 1: this functions returns 1 if the body is circumpolar, that is it remains
773 * the whole day either above or below the horizon.
774 */
775 @nogc int ln_get_motion_body_next_rst_horizon(double JD,
776 	const ref ln_lnlat_posn observer,
777 	get_motion_body_coords_t get_motion_body_coords, void *orbit,
778 	double horizon, ref ln_rst_time rst) nothrow
779 {
780 	return ln_get_motion_body_next_rst_horizon_future(JD, observer,
781 		get_motion_body_coords, orbit, horizon, 1, rst);
782 }
783 
784 /*! \fn int ln_get_motion_body_next_rst_horizon_future(double JD, struct ln_lnlat_posn *observer, void (*get_equ_body_coords) (double, struct ln_equ_posn *), double horizon, int day_limit, struct ln_rst_time *rst);
785 * \param JD Julian day
786 * \param observer Observers position
787 * \param get_motion_body_coords Pointer to ln_get_ell_body_equ_coords. ln_get_para_body_equ_coords or ln_get_hyp_body_equ_coords function
788 * \param horizon Horizon, see LN_XXX_HORIZON constants
789 * \param day_limit Maximal number of days that will be searched for next rise and set
790 * \param rst Pointer to store Rise, Set and Transit time in JD
791 * \return 0 for success, 1 for circumpolar (above the horizon), -1 for circumpolar (bellow the horizon)
792 *
793 * Calculate the time of next rise, set and transit (crosses the local meridian at
794 * upper culmination) time of the body for the given Julian day and given
795 * horizon.
796 *
797 * This function guarantee, that rise, set and transit will be in <JD, JD + day_limit> range.
798 *
799 * Note 1: this functions returns 1 if the body is circumpolar, that is it remains
800 * the whole day either above or below the horizon.
801 */
802 @nogc int ln_get_motion_body_next_rst_horizon_future(double JD,
803 	const ref ln_lnlat_posn observer,
804 	get_motion_body_coords_t get_motion_body_coords, void *orbit,
805 	double horizon, int day_limit, ref ln_rst_time rst) nothrow
806 {
807 	int ret;
808 	ln_rst_time rst_1, rst_2;
809 
810 	ret = ln_get_motion_body_rst_horizon_offset(JD, observer,
811 		get_motion_body_coords, orbit, horizon, rst, double.nan);
812 	if (ret && day_limit == 1)
813 		// circumpolar
814 		return ret;
815 
816 	if (!ret &&
817 		(rst.rise >(JD + 0.5) || rst.transit >(JD + 0.5) ||
818 		rst.set >(JD + 0.5))) {
819 
820 		ret = ln_get_motion_body_rst_horizon_offset(JD - 1.0, observer,
821 			get_motion_body_coords, orbit, horizon, rst_1, double.nan);
822 		if (ret)
823 			set_next_rst(rst, -1.0, rst_1);
824 	} else {
825 		rst.rise = double.nan;
826 		rst.transit = double.nan;
827 		rst.set = double.nan;
828 
829 		set_next_rst(rst, -1.0, rst_1);
830 	}
831 
832 	if (ret || (rst.rise < JD || rst.transit < JD || rst.set < JD)) {
833 	  	// find next day when it will rise, up to day_limit days
834 		int day = 1;
835 
836 		while (day <= day_limit) {
837 
838 			ret = ln_get_motion_body_rst_horizon_offset(JD + day, observer,
839 				get_motion_body_coords, orbit, horizon, rst_2, double.nan);
840 
841 			if (!ret) {
842 				day = day_limit + 2;
843 				break;
844 			}
845 			day++;
846 		}
847 
848 		if (day == day_limit + 1)
849 
850 			// it's then really circumpolar in searched period
851 			return ret;
852 	} else {
853 		set_next_rst(rst, +1.0, rst_2);
854 	}
855 
856 	rst.rise = find_next(JD, rst_1.rise, rst.rise, rst_2.rise);
857 	rst.transit = find_next(JD, rst_1.transit, rst.transit, rst_2.transit);
858 	rst.set = find_next(JD, rst_1.set, rst.set, rst_2.set);
859 
860 	if (isNaN (rst.rise))
861 		return ret;
862 
863 	return 0;
864 }
865 
866 }