这是我的解决方案:
- 我想要自动的日夜模式功能,但不想启用安卓中繁琐的车载模式。
-> 从NOAA网页中可以找到算法,根据给定的位置和日期计算太阳在地平线上的高度。
--> 使用这些算法,我创建了一个方法,根据两个双精度经纬度和一个日历计算太阳在地平线上的高度。
public class SolarCalculations {
/**
* Calculate height of the sun above horizon for a given position and date
* @param lat Positive to N
* @param lon Positive to E
* @param cal Calendar containing current time, date, timezone, daylight time savings
* @return height of the sun in degrees, positive if above the horizon
*/
public static double CalculateSunHeight(double lat, double lon, Calendar cal){
double adjustedTimeZone = cal.getTimeZone().getRawOffset()/3600000 + cal.getTimeZone().getDSTSavings()/3600000;
double timeOfDay = (cal.get(Calendar.HOUR_OF_DAY) * 3600 + cal.get(Calendar.MINUTE) * 60 + cal.get(Calendar.SECOND))/(double)86400;
double julianDay = dateToJulian(cal.getTime()) - adjustedTimeZone/24;
double julianCentury = (julianDay-2451545)/36525;
double geomMeanLongSun = (280.46646 + julianCentury * (36000.76983 + julianCentury * 0.0003032)) % 360;
double geomMeanAnomSun = 357.52911+julianCentury*(35999.05029 - 0.0001537*julianCentury);
double eccentEarthOrbit = 0.016708634-julianCentury*(0.000042037+0.0000001267*julianCentury);
double sunEqOfCtr = Math.sin(Math.toRadians(geomMeanAnomSun))*(1.914602-julianCentury*(0.004817+0.000014*julianCentury))+Math.sin(Math.toRadians(2*geomMeanAnomSun))*(0.019993-0.000101*julianCentury)+Math.sin(Math.toRadians(3*geomMeanAnomSun))*0.000289;
double sunTrueLong = geomMeanLongSun + sunEqOfCtr;
double sunAppLong = sunTrueLong-0.00569-0.00478*Math.sin(Math.toRadians(125.04-1934.136*julianCentury));
double meanObliqEcliptic = 23+(26+((21.448-julianCentury*(46.815+julianCentury*(0.00059-julianCentury*0.001813))))/60)/60;
double obliqueCorr = meanObliqEcliptic+0.00256*Math.cos(Math.toRadians(125.04-1934.136*julianCentury));
double sunDeclin = Math.toDegrees(Math.asin(Math.sin(Math.toRadians(obliqueCorr))*Math.sin(Math.toRadians(sunAppLong))));
double varY = Math.tan(Math.toRadians(obliqueCorr/2))*Math.tan(Math.toRadians(obliqueCorr/2));
double eqOfTime = 4*Math.toDegrees(varY*Math.sin(2*Math.toRadians(geomMeanLongSun))-2*eccentEarthOrbit*Math.sin(Math.toRadians(geomMeanAnomSun))+4*eccentEarthOrbit*varY*Math.sin(Math.toRadians(geomMeanAnomSun))*Math.cos(2*Math.toRadians(geomMeanLongSun))-0.5*varY*varY*Math.sin(4*Math.toRadians(geomMeanLongSun))-1.25*eccentEarthOrbit*eccentEarthOrbit*Math.sin(2*Math.toRadians(geomMeanAnomSun)));
double trueSolarTime = (timeOfDay*1440+eqOfTime+4*lon-60*adjustedTimeZone) % 1440;
double hourAngle;
if(trueSolarTime/4<0)
hourAngle = trueSolarTime/4+180;
else
hourAngle = trueSolarTime/4-180;
double solarZenithAngle = Math.toDegrees(Math.acos(Math.sin(Math.toRadians(lat))*Math.sin(Math.toRadians(sunDeclin))+Math.cos(Math.toRadians(lat))*Math.cos(Math.toRadians(sunDeclin))*Math.cos(Math.toRadians(hourAngle))));
double solarElevation = 90 - solarZenithAngle;
double athmosphericRefraction;
if(solarElevation>85)
athmosphericRefraction = 0;
else if(solarElevation>5)
athmosphericRefraction = 58.1/Math.tan(Math.toRadians(solarElevation))-0.07/Math.pow(Math.tan(Math.toRadians(solarElevation)),3)+0.000086/Math.pow(Math.tan(Math.toRadians(solarElevation)),5);
else if(solarElevation>-0.575)
athmosphericRefraction = 1735+solarElevation*(-518.2+solarElevation*(103.4+solarElevation*(-12.79+solarElevation*0.711)));
else
athmosphericRefraction = -20.772/Math.tan(Math.toRadians(solarElevation));
athmosphericRefraction /= 3600;
double solarElevationCorrected = solarElevation + athmosphericRefraction;
return solarElevationCorrected;
}
/**
* Return Julian day from date
* @param date
* @return
*/
public static double dateToJulian(Date date) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(date);
int a = (14-(calendar.get(Calendar.MONTH)+1))/12;
int y = calendar.get(Calendar.YEAR) + 4800 - a;
int m = (calendar.get(Calendar.MONTH)+1) + 12*a;
m -= 3;
double jdn = calendar.get(Calendar.DAY_OF_MONTH) + (153.0*m + 2.0)/5.0 + 365.0*y + y/4.0 - y/100.0 + y/400.0 - 32045.0 + calendar.get(Calendar.HOUR_OF_DAY) / 24 + calendar.get(Calendar.MINUTE)/1440 + calendar.get(Calendar.SECOND)/86400;
return jdn;
}
}
然后在MainActivity中,我有一个方法,每5分钟检查给定位置的太阳高度:
if(displayMode.equals("auto")){
double sunHeight = SolarCalculations.CalculateSunHeight(lat, lon, cal);
if(sunHeight > 0 && mThemeId != R.style.AppTheme_Daylight)
{
mThemeId = R.style.AppTheme_Daylight;
this.recreate();
}
else if (sunHeight < 0 && sunHeight >= -6 && mThemeId != R.style.AppTheme_Dusk)
{
mThemeId = R.style.AppTheme_Dusk;
this.recreate();
}
else if(sunHeight < -6 && mThemeId != R.style.AppTheme_Night)
{
mThemeId = R.style.AppTheme_Night;
this.recreate();
}
}
这个方法用于设置当前的样式,我有三种不同的样式可供使用。其中两种用于白天和夜晚,另一种用于黄昏时太阳光线开始被大气层折射的情况。
<!-- Application theme. -->
<style name="AppTheme.Daylight" parent="AppBaseTheme">
<item name="android:background">@color/white</item>
<item name="android:panelBackground">@color/gray</item>
<item name="android:textColor">@color/black</item>
</style>
<style name="AppTheme.Dusk" parent="AppBaseTheme">
<item name="android:background">@color/black</item>
<item name="android:panelBackground">@color/gray</item>
<item name="android:textColor">@color/salmon</item>
</style>
<style name="AppTheme.Night" parent="AppBaseTheme">
<item name="android:background">@color/black</item>
<item name="android:panelBackground">@color/gray</item>
<item name="android:textColor">@color/red</item>
</style>
这一功能一直表现不错,并考虑到了夏令时的修正。
来源:
美国国家海洋和大气管理局日出日落时间
儒略日
-night
作为夜间模式的资源集限定符,将您的夜间特定资源放在其中。 - CommonsWare