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 }