1 7 package com.ibm.icu.impl; 8 9 import java.util.*; 10 11 45 public class CalendarAstronomer { 46 47 51 56 public static final double SIDEREAL_DAY = 23.93446960027; 57 58 63 public static final double SOLAR_DAY = 24.065709816; 64 65 75 public static final double SYNODIC_MONTH = 29.530588853; 76 77 88 public static final double SIDEREAL_MONTH = 27.32166; 89 90 99 public static final double TROPICAL_YEAR = 365.242191; 100 101 113 public static final double SIDEREAL_YEAR = 365.25636; 114 115 119 123 public static final int SECOND_MS = 1000; 124 125 129 public static final int MINUTE_MS = 60*SECOND_MS; 130 131 135 public static final int HOUR_MS = 60*MINUTE_MS; 136 137 141 public static final long DAY_MS = 24*HOUR_MS; 142 143 152 public static final long JULIAN_EPOCH_MS = -210866760000000L; 153 154 164 172 175 static final long EPOCH_2000_MS = 946598400000L; 176 177 181 static private final double PI = 3.14159265358979323846; 183 static private final double PI2 = PI * 2.0; 184 185 static private final double RAD_HOUR = 12 / PI; static private final double DEG_RAD = PI / 180; static private final double RAD_DEG = 180 / PI; 189 193 198 public CalendarAstronomer() { 199 this(System.currentTimeMillis()); 200 } 201 202 207 public CalendarAstronomer(Date d) { 208 this(d.getTime()); 209 } 210 211 219 public CalendarAstronomer(long aTime) { 220 time = aTime; 221 } 222 223 237 public CalendarAstronomer(double longitude, double latitude) { 238 this(); 239 fLongitude = normPI(longitude * DEG_RAD); 240 fLatitude = normPI(latitude * DEG_RAD); 241 fGmtOffset = (long)(fLongitude * 24 * HOUR_MS / PI2); 242 } 243 244 245 249 260 public void setTime(long aTime) { 261 time = aTime; 262 clearCache(); 263 } 264 265 275 public void setDate(Date date) { 276 setTime(date.getTime()); 277 } 278 279 293 public void setJulianDay(double jdn) { 294 time = (long)(jdn * DAY_MS) + JULIAN_EPOCH_MS; 295 clearCache(); 296 julianDay = jdn; 297 } 298 299 308 public long getTime() { 309 return time; 310 } 311 312 320 public Date getDate() { 321 return new Date(time); 322 } 323 324 333 public double getJulianDay() { 334 if (julianDay == INVALID) { 335 julianDay = (double)(time - JULIAN_EPOCH_MS) / (double)DAY_MS; 336 } 337 return julianDay; 338 } 339 340 347 public double getJulianCentury() { 348 if (julianCentury == INVALID) { 349 julianCentury = (getJulianDay() - 2415020.0) / 36525; 350 } 351 return julianCentury; 352 } 353 354 358 public double getGreenwichSidereal() { 359 if (siderealTime == INVALID) { 360 363 double UT = normalize((double)time/HOUR_MS, 24); 364 365 siderealTime = normalize(getSiderealOffset() + UT*1.002737909, 24); 366 } 367 return siderealTime; 368 } 369 370 private double getSiderealOffset() { 371 if (siderealT0 == INVALID) { 372 double JD = Math.floor(getJulianDay() - 0.5) + 0.5; 373 double S = JD - 2451545.0; 374 double T = S / 36525.0; 375 siderealT0 = normalize(6.697374558 + 2400.051336*T + 0.000025862*T*T, 24); 376 } 377 return siderealT0; 378 } 379 380 384 public double getLocalSidereal() { 385 return normalize(getGreenwichSidereal() + (double)fGmtOffset/HOUR_MS, 24); 386 } 387 388 397 private long lstToUT(double lst) { 398 double lt = normalize((lst - getSiderealOffset()) * 0.9972695663, 24); 400 401 long base = DAY_MS * ((time + fGmtOffset)/DAY_MS) - fGmtOffset; 403 404 407 return base + (long)(lt * HOUR_MS); 408 } 409 410 411 415 422 public final Equatorial eclipticToEquatorial(Ecliptic ecliptic) 423 { 424 return eclipticToEquatorial(ecliptic.longitude, ecliptic.latitude); 425 } 426 427 436 public final Equatorial eclipticToEquatorial(double eclipLong, double eclipLat) 437 { 438 441 double obliq = eclipticObliquity(); 442 double sinE = Math.sin(obliq); 443 double cosE = Math.cos(obliq); 444 445 double sinL = Math.sin(eclipLong); 446 double cosL = Math.cos(eclipLong); 447 448 double sinB = Math.sin(eclipLat); 449 double cosB = Math.cos(eclipLat); 450 double tanB = Math.tan(eclipLat); 451 452 return new Equatorial(Math.atan2(sinL*cosE - tanB*sinE, cosL), 453 Math.asin(sinB*cosE + cosB*sinE*sinL) ); 454 } 455 456 464 public final Equatorial eclipticToEquatorial(double eclipLong) 465 { 466 return eclipticToEquatorial(eclipLong, 0); } 468 469 472 public Horizon eclipticToHorizon(double eclipLong) 473 { 474 Equatorial equatorial = eclipticToEquatorial(eclipLong); 475 476 double H = getLocalSidereal()*PI/12 - equatorial.ascension; 478 double sinH = Math.sin(H); 479 double cosH = Math.cos(H); 480 double sinD = Math.sin(equatorial.declination); 481 double cosD = Math.cos(equatorial.declination); 482 double sinL = Math.sin(fLatitude); 483 double cosL = Math.cos(fLatitude); 484 485 double altitude = Math.asin(sinD*sinL + cosD*cosL*cosH); 486 double azimuth = Math.atan2(-cosD*cosL*sinH, sinD - sinL * Math.sin(altitude)); 487 488 return new Horizon(azimuth, altitude); 489 } 490 491 492 496 static final double JD_EPOCH = 2447891.5; 502 static final double SUN_ETA_G = 279.403303 * PI/180; static final double SUN_OMEGA_G = 282.768422 * PI/180; static final double SUN_E = 0.016713; 508 517 527 543 553 564 public double getSunLongitude() 565 { 566 569 if (sunLongitude == INVALID) { 570 double[] result = getSunLongitude(getJulianDay()); 571 sunLongitude = result[0]; 572 meanAnomalySun = result[1]; 573 } 574 return sunLongitude; 575 } 576 577 580 double[] getSunLongitude(double julianDay) 581 { 582 585 double day = julianDay - JD_EPOCH; 587 double epochAngle = norm2PI(PI2/TROPICAL_YEAR*day); 590 591 double meanAnomaly = norm2PI(epochAngle + SUN_ETA_G - SUN_OMEGA_G); 594 595 return new double[] { 600 norm2PI(trueAnomaly(meanAnomaly, SUN_E) + SUN_OMEGA_G), 601 meanAnomaly 602 }; 603 } 604 605 610 public Equatorial getSunPosition() { 611 return eclipticToEquatorial(getSunLongitude(), 0); 612 } 613 614 private static class SolarLongitude { 615 double value; 616 SolarLongitude(double val) { value = val; } 617 } 618 619 625 public static final SolarLongitude VERNAL_EQUINOX = new SolarLongitude(0); 626 627 633 public static final SolarLongitude SUMMER_SOLSTICE = new SolarLongitude(PI/2); 634 635 641 public static final SolarLongitude AUTUMN_EQUINOX = new SolarLongitude(PI); 642 643 649 public static final SolarLongitude WINTER_SOLSTICE = new SolarLongitude((PI*3)/2); 650 651 656 public long getSunTime(double desired, boolean next) 657 { 658 return timeOfAngle( new AngleFunc() { public double eval() { return getSunLongitude(); } }, 659 desired, 660 TROPICAL_YEAR, 661 MINUTE_MS, 662 next); 663 } 664 665 670 public long getSunTime(SolarLongitude desired, boolean next) { 671 return getSunTime(desired.value, next); 672 } 673 674 686 public long getSunRiseSet(boolean rise) 687 { 688 long t0 = time; 689 690 long noon = ((time + fGmtOffset)/DAY_MS)*DAY_MS - fGmtOffset + 12*HOUR_MS; 692 693 setTime(noon + (long)((rise ? -6 : 6) * HOUR_MS)); 694 695 long t = riseOrSet(new CoordFunc() { 696 public Equatorial eval() { return getSunPosition(); } 697 }, 698 rise, 699 .533 * DEG_RAD, 34 /60.0 * DEG_RAD, MINUTE_MS / 12); 703 setTime(t0); 704 return t; 705 } 706 707 789 801 812 823 836 980 984 static final double moonL0 = 318.351648 * PI/180; static final double moonP0 = 36.340410 * PI/180; static final double moonN0 = 318.510107 * PI/180; static final double moonI = 5.145366 * PI/180; static final double moonE = 0.054900; 990 static final double moonA = 3.84401e5; static final double moonT0 = 0.5181 * PI/180; static final double moonPi = 0.9507 * PI/180; 995 1000 public Equatorial getMoonPosition() 1001 { 1002 if (moonPosition == null) { 1007 double sunLongitude = getSunLongitude(); 1010 1011 double day = getJulianDay() - JD_EPOCH; 1017 double meanLongitude = norm2PI(13.1763966*PI/180*day + moonL0); 1020 double meanAnomalyMoon = norm2PI(meanLongitude - 0.1114041*PI/180 * day - moonP0); 1021 1022 double evection = 1.2739*PI/180 * Math.sin(2 * (meanLongitude - sunLongitude) 1029 - meanAnomalyMoon); 1030 double annual = 0.1858*PI/180 * Math.sin(meanAnomalySun); 1031 double a3 = 0.3700*PI/180 * Math.sin(meanAnomalySun); 1032 1033 meanAnomalyMoon += evection - annual - a3; 1034 1035 double center = 6.2886*PI/180 * Math.sin(meanAnomalyMoon); 1043 double a4 = 0.2140*PI/180 * Math.sin(2 * meanAnomalyMoon); 1044 1045 moonLongitude = meanLongitude + evection + center - annual + a4; 1047 1048 double variation = 0.6583*PI/180 * Math.sin(2*(moonLongitude - sunLongitude)); 1054 1055 moonLongitude += variation; 1056 1057 double nodeLongitude = norm2PI(moonN0 - 0.0529539*PI/180 * day); 1065 1066 nodeLongitude -= 0.16*PI/180 * Math.sin(meanAnomalySun); 1067 1068 double y = Math.sin(moonLongitude - nodeLongitude); 1069 double x = Math.cos(moonLongitude - nodeLongitude); 1070 1071 moonEclipLong = Math.atan2(y*Math.cos(moonI), x) + nodeLongitude; 1072 double moonEclipLat = Math.asin(y * Math.sin(moonI)); 1073 1074 moonPosition = eclipticToEquatorial(moonEclipLong, moonEclipLat); 1075 } 1076 return moonPosition; 1077 } 1078 1079 1088 public double getMoonAge() { 1089 getMoonPosition(); 1096 1097 return norm2PI(moonEclipLong - sunLongitude); 1098 } 1099 1100 1114 public double getMoonPhase() { 1115 return 0.5 * (1 - Math.cos(getMoonAge())); 1118 } 1119 1120 private static class MoonAge { 1121 double value; 1122 MoonAge(double val) { value = val; } 1123 } 1124 1125 1130 public static final MoonAge NEW_MOON = new MoonAge(0); 1131 1132 1137 public static final MoonAge FIRST_QUARTER = new MoonAge(PI/2); 1138 1139 1144 public static final MoonAge FULL_MOON = new MoonAge(PI); 1145 1146 1151 public static final MoonAge LAST_QUARTER = new MoonAge((PI*3)/2); 1152 1153 1162 public long getMoonTime(double desired, boolean next) 1163 { 1164 return timeOfAngle( new AngleFunc() { 1165 public double eval() { return getMoonAge(); } }, 1166 desired, 1167 SYNODIC_MONTH, 1168 MINUTE_MS, 1169 next); 1170 } 1171 1172 1181 public long getMoonTime(MoonAge desired, boolean next) { 1182 return getMoonTime(desired.value, next); 1183 } 1184 1185 1190 public long getMoonRiseSet(boolean rise) 1191 { 1192 return riseOrSet(new CoordFunc() { 1193 public Equatorial eval() { return getMoonPosition(); } 1194 }, 1195 rise, 1196 .533 * DEG_RAD, 34 /60.0 * DEG_RAD, MINUTE_MS); } 1200 1201 1205 private interface AngleFunc { 1206 public double eval(); 1207 } 1208 1209 private long timeOfAngle(AngleFunc func, double desired, 1210 double periodDays, long epsilon, boolean next) 1211 { 1212 double lastAngle = func.eval(); 1214 1215 double deltaAngle = norm2PI(desired - lastAngle) ; 1217 1218 double deltaT = (deltaAngle + (next ? 0 : -PI2)) * (periodDays*DAY_MS) / PI2; 1221 1222 double lastDeltaT = deltaT; long startTime = time; 1225 setTime(time + (long)deltaT); 1226 1227 do { 1231 double angle = func.eval(); 1233 1234 double factor = Math.abs(deltaT / normPI(angle-lastAngle)); 1236 1237 deltaT = normPI(desired - angle) * factor; 1239 1240 if (Math.abs(deltaT) > Math.abs(lastDeltaT)) { 1262 long delta = (long) (periodDays * DAY_MS / 8); 1263 setTime(startTime + (next ? delta : -delta)); 1264 return timeOfAngle(func, desired, periodDays, epsilon, next); 1265 } 1266 1267 lastDeltaT = deltaT; 1268 lastAngle = angle; 1269 1270 setTime(time + (long)deltaT); 1271 } 1272 while (Math.abs(deltaT) > epsilon); 1273 1274 return time; 1275 } 1276 1277 private interface CoordFunc { 1278 public Equatorial eval(); 1279 } 1280 1281 private long riseOrSet(CoordFunc func, boolean rise, 1282 double diameter, double refraction, 1283 long epsilon) 1284 { 1285 Equatorial pos = null; 1286 double tanL = Math.tan(fLatitude); 1287 long deltaT = Long.MAX_VALUE; 1288 int count = 0; 1289 1290 do { 1296 pos = func.eval(); 1298 double angle = Math.acos(-tanL * Math.tan(pos.declination)); 1299 double lst = ((rise ? PI2-angle : angle) + pos.ascension ) * 24 / PI2; 1300 1301 long newTime = lstToUT( lst ); 1303 1304 deltaT = newTime - time; 1305 setTime(newTime); 1306 } 1307 while (++ count < 5 && Math.abs(deltaT) > epsilon); 1308 1309 double cosD = Math.cos(pos.declination); 1311 double psi = Math.acos(Math.sin(fLatitude) / cosD); 1312 double x = diameter / 2 + refraction; 1313 double y = Math.asin(Math.sin(x) / Math.sin(psi)); 1314 long delta = (long)((240 * y * RAD_DEG / cosD)*SECOND_MS); 1315 1316 return time + (rise ? -delta : delta); 1317 } 1318 1319 1323 1327 private static final double normalize(double value, double range) { 1328 return value - range * Math.floor(value / range); 1329 } 1330 1331 1336 private static final double norm2PI(double angle) { 1337 return normalize(angle, PI2); 1338 } 1339 1340 1343 private static final double normPI(double angle) { 1344 return normalize(angle + PI, PI2) - PI; 1345 } 1346 1347 1360 private double trueAnomaly(double meanAnomaly, double eccentricity) 1361 { 1362 double delta; 1365 double E = meanAnomaly; 1366 do { 1367 delta = E - eccentricity * Math.sin(E) - meanAnomaly; 1368 E = E - delta / (1 - eccentricity * Math.cos(E)); 1369 } 1370 while (Math.abs(delta) > 1e-5); 1372 return 2.0 * Math.atan( Math.tan(E/2) * Math.sqrt( (1+eccentricity) 1373 /(1-eccentricity) ) ); 1374 } 1375 1376 1384 private double eclipticObliquity() { 1385 if (eclipObliquity == INVALID) { 1386 final double epoch = 2451545.0; 1388 double T = (getJulianDay() - epoch) / 36525; 1389 1390 eclipObliquity = 23.439292 1391 - 46.815/3600 * T 1392 - 0.0006/3600 * T*T 1393 + 0.00181/3600 * T*T*T; 1394 1395 eclipObliquity *= DEG_RAD; 1396 } 1397 return eclipObliquity; 1398 } 1399 1400 1401 1405 1409 private long time; 1410 1411 1414 private double fLongitude = 0.0; 1415 private double fLatitude = 0.0; 1416 private long fGmtOffset = 0; 1417 1418 static final private double INVALID = Double.MIN_VALUE; 1424 1425 private transient double julianDay = INVALID; 1426 private transient double julianCentury = INVALID; 1427 private transient double sunLongitude = INVALID; 1428 private transient double meanAnomalySun = INVALID; 1429 private transient double moonLongitude = INVALID; 1430 private transient double moonEclipLong = INVALID; 1431 private transient double meanAnomalyMoon = INVALID; 1432 private transient double eclipObliquity = INVALID; 1433 private transient double siderealT0 = INVALID; 1434 private transient double siderealTime = INVALID; 1435 1436 private transient Equatorial moonPosition = null; 1437 1438 private void clearCache() { 1439 julianDay = INVALID; 1440 julianCentury = INVALID; 1441 sunLongitude = INVALID; 1442 meanAnomalySun = INVALID; 1443 moonLongitude = INVALID; 1444 moonEclipLong = INVALID; 1445 meanAnomalyMoon = INVALID; 1446 eclipObliquity = INVALID; 1447 siderealTime = INVALID; 1448 siderealT0 = INVALID; 1449 moonPosition = null; 1450 } 1451 1452 1456 1460 1464 1467 public String local(long localMillis) { 1468 return new Date(localMillis - TimeZone.getDefault().getRawOffset()).toString(); 1469 } 1470 1471 1472 1489 public static final class Ecliptic { 1490 1497 public Ecliptic(double lat, double lon) { 1498 latitude = lat; 1499 longitude = lon; 1500 } 1501 1502 1506 public String toString() { 1507 return Double.toString(longitude*RAD_DEG) + "," + (latitude*RAD_DEG); 1508 } 1509 1510 1516 public final double latitude; 1517 1518 1529 public final double longitude; 1530 } 1531 1532 1548 public static final class Equatorial { 1549 1556 public Equatorial(double asc, double dec) { 1557 ascension = asc; 1558 declination = dec; 1559 } 1560 1561 1566 public String toString() { 1567 return Double.toString(ascension*RAD_DEG) + "," + (declination*RAD_DEG); 1568 } 1569 1570 1575 public String toHmsString() { 1576 return radToHms(ascension) + "," + radToDms(declination); 1577 } 1578 1579 1586 public final double ascension; 1587 1588 1594 public final double declination; 1595 } 1596 1597 1614 public static final class Horizon { 1615 1622 public Horizon(double alt, double azim) { 1623 altitude = alt; 1624 azimuth = azim; 1625 } 1626 1627 1632 public String toString() { 1633 return Double.toString(altitude*RAD_DEG) + "," + (azimuth*RAD_DEG); 1634 } 1635 1636 1640 public final double altitude; 1641 1642 1646 public final double azimuth; 1647 } 1648 1649 static private String radToHms(double angle) { 1650 int hrs = (int) (angle*RAD_HOUR); 1651 int min = (int)((angle*RAD_HOUR - hrs) * 60); 1652 int sec = (int)((angle*RAD_HOUR - hrs - min/60.0) * 3600); 1653 1654 return Integer.toString(hrs) + "h" + min + "m" + sec + "s"; 1655 } 1656 1657 static private String radToDms(double angle) { 1658 int deg = (int) (angle*RAD_DEG); 1659 int min = (int)((angle*RAD_DEG - deg) * 60); 1660 int sec = (int)((angle*RAD_DEG - deg - min/60.0) * 3600); 1661 1662 return Integer.toString(deg) + "\u00b0" + min + "'" + sec + "\""; 1663 } 1664} 1665 | Popular Tags |