/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.astro.internal.calc;
import java.util.Calendar;
import org.openhab.binding.astro.internal.model.Season;
import org.openhab.binding.astro.internal.model.SeasonName;
import org.openhab.binding.astro.internal.util.DateTimeUtils;
/**
* Calculates the seasons of the year.
*
* @author Gerhard Riegler
* @since 1.6.0
* @see based on the calculations of http://stellafane.org/misc/equinox.html
*/
public class SeasonCalc {
private int currentYear;
private Season currentSeason;
/**
* Returns the seasons of the year of the specified calendar.
*/
public Season getSeason(Calendar calendar, double latitude) {
int year = calendar.get(Calendar.YEAR);
boolean isSouthernHemisphere = latitude < 0.0;
Season season = currentSeason;
if (currentYear != year) {
season = new Season();
if (!isSouthernHemisphere) {
season.setSpring(calcEquiSol(0, year));
season.setSummer(calcEquiSol(1, year));
season.setAutumn(calcEquiSol(2, year));
season.setWinter(calcEquiSol(3, year));
} else {
season.setSpring(calcEquiSol(2, year));
season.setSummer(calcEquiSol(3, year));
season.setAutumn(calcEquiSol(0, year));
season.setWinter(calcEquiSol(1, year));
}
currentSeason = season;
currentYear = year;
}
season.setName(!isSouthernHemisphere ? getCurrentSeasonNameNorthern(calendar)
: getCurrentSeasonNameSouthern(calendar));
return season;
}
/**
* Returns the current season name for the northern hemisphere.
*/
private SeasonName getCurrentSeasonNameNorthern(Calendar calendar) {
long currentMillis = calendar.getTimeInMillis();
if (currentMillis < currentSeason.getSpring().getTimeInMillis()
|| currentMillis >= currentSeason.getWinter().getTimeInMillis()) {
return SeasonName.WINTER;
} else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
&& currentMillis < currentSeason.getSummer().getTimeInMillis()) {
return SeasonName.SPRING;
} else if (currentMillis >= currentSeason.getSummer().getTimeInMillis()
&& currentMillis < currentSeason.getAutumn().getTimeInMillis()) {
return SeasonName.SUMMER;
} else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
&& currentMillis < currentSeason.getWinter().getTimeInMillis()) {
return SeasonName.AUTUMN;
}
return null;
}
/**
* Returns the current season name for the southern hemisphere.
*/
private SeasonName getCurrentSeasonNameSouthern(Calendar calendar) {
long currentMillis = calendar.getTimeInMillis();
if (currentMillis < currentSeason.getAutumn().getTimeInMillis()
|| currentMillis >= currentSeason.getSummer().getTimeInMillis()) {
return SeasonName.SUMMER;
} else if (currentMillis >= currentSeason.getAutumn().getTimeInMillis()
&& currentMillis < currentSeason.getWinter().getTimeInMillis()) {
return SeasonName.AUTUMN;
} else if (currentMillis >= currentSeason.getWinter().getTimeInMillis()
&& currentMillis < currentSeason.getSpring().getTimeInMillis()) {
return SeasonName.WINTER;
} else if (currentMillis >= currentSeason.getSpring().getTimeInMillis()
&& currentMillis < currentSeason.getSummer().getTimeInMillis()) {
return SeasonName.SPRING;
}
return null;
}
/**
* Calculates the date of the season.
*/
private Calendar calcEquiSol(int season, int year) {
double estimate = calcInitial(season, year);
double t = (estimate - 2451545.0) / 36525;
double w = 35999.373 * t - 2.47;
double dl = 1 + 0.0334 * cosDeg(w) + 0.0007 * cosDeg(2 * w);
double s = periodic24(t);
double julianDate = estimate + ((0.00001 * s) / dl);
return DateTimeUtils.toCalendar(julianDate);
}
/**
* Calculate an initial guess of the Equinox or Solstice of a given year.
*/
private double calcInitial(int season, int year) {
double Y = (year - 2000) / 1000d;
switch (season) {
case 0:
return 2451623.80984 + 365242.37404 * Y + 0.05169 * Math.pow(Y, 2) - 0.00411 * Math.pow(Y, 3)
- 0.00057 * Math.pow(Y, 4);
case 1:
return 2451716.56767 + 365241.62603 * Y + 0.00325 * Math.pow(Y, 2) + 0.00888 * Math.pow(Y, 3)
- 0.00030 * Math.pow(Y, 4);
case 2:
return 2451810.21715 + 365242.01767 * Y - 0.11575 * Math.pow(Y, 2) + 0.00337 * Math.pow(Y, 3)
+ 0.00078 * Math.pow(Y, 4);
case 3:
return 2451900.05952 + 365242.74049 * Y - 0.06223 * Math.pow(Y, 2) - 0.00823 * Math.pow(Y, 3)
+ 0.00032 * Math.pow(Y, 4);
}
return 0;
}
/**
* Calculate 24 periodic terms
*/
private double periodic24(double T) {
int[] a = new int[] { 485, 203, 199, 182, 156, 136, 77, 74, 70, 58, 52, 50, 45, 44, 29, 18, 17, 16, 14, 12, 12,
12, 9, 8 };
double[] b = new double[] { 324.96, 337.23, 342.08, 27.85, 73.14, 171.52, 222.54, 296.72, 243.58, 119.81,
297.17, 21.02, 247.54, 325.15, 60.93, 155.12, 288.79, 198.04, 199.76, 95.39, 287.11, 320.81, 227.73,
15.45 };
double[] c = new double[] { 1934.136, 32964.467, 20.186, 445267.112, 45036.886, 22518.443, 65928.934, 3034.906,
9037.513, 33718.147, 150.678, 2281.226, 29929.562, 31555.956, 4443.417, 67555.328, 4562.452, 62894.029,
31436.921, 14577.848, 31931.756, 34777.259, 1222.114, 16859.074 };
double result = 0;
for (int i = 0; i < 24; i++) {
result += a[i] * cosDeg(b[i] + (c[i] * T));
}
return result;
}
/**
* Cosinus of a degree value.
*/
private double cosDeg(double deg) {
return Math.cos(deg * Math.PI / 180);
}
}