package com.base.core.utiles;


import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

/**
 * <p>
 * Description:太阳高度角计算，[15, 70]之间的为光照区，其余的为阴影区
 * </p>
 * <p>
 * Copyright: Copyright (c) 2012
 * </p>
 * <p>
 * Company:航天恒星科技有限公司系统遥感事业部
 * </p>
 * 
 * @author 齐昕浒
 */
public class SunElevationAngleCalculator {

	/**
	 * 太阳高度角计算，存在缺陷：此处只考虑正规矩形，不规则形状不适用
	 * 
	 * @param startTime
	 *            卫星访问该区域开始时间(UTC时间)
	 * @param endTime
	 *            卫星访问该区域结束时间(UTC时间)
	 * @param LTLongitude
	 *            区域左上角经度(度,三位小数)(-180,180)
	 * @param LTLatitude
	 *            区域左上角纬度(度,三位小数)(-90,90)
	 * @param RTLongitude
	 *            区域右上角经度(度,三位小数)(-180,180)
	 * @param RTLatitude
	 *            区域右上角纬度(度,三位小数)(-90,90)
	 * @param RBLongitude
	 *            区域右下角经度(度,三位小数)(-180,180)
	 * @param RBLatitude
	 *            区域右下角纬度(度,三位小数)(-90,90)
	 * @param LBLongitude
	 *            区域左下角经度(度,三位小数)(-180,180)
	 * @param LBLatitude
	 *            区域左下角纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 * 
	 */
	//public static int				MAX_NUMBER_DECIMAL						= 4;
	public static double calculateSunElevationAngle(String startTime, String endTime, double LTLongitude, double LTLatitude,
			double RTLongitude, double RTLatitude, double RBLongitude, double RBLatitude, double LBLongitude, double LBLatitude) {
		//转换成Calendar类型
		Calendar startTimeCalendar = stringToCalendar(startTime);
		//转换成Calendar类型
		Calendar endTimeCalendar = stringToCalendar(endTime);
		//返回太阳高度角
		return calculateSunElevationAngle(startTimeCalendar, endTimeCalendar, LTLongitude, LTLatitude, RTLongitude, RTLatitude,
				RBLongitude, RBLatitude, LBLongitude, LBLatitude);
	}

	/**
	 * 太阳高度角计算，存在缺陷：此处只考虑正规矩形，不规则形状不适用
	 * 
	 * @param startTime
	 *            卫星访问该区域开始时间(UTC时间)
	 * @param endTime
	 *            卫星访问该区域结束时间(UTC时间)
	 * @param LTLongitude
	 *            区域左上角经度(度,三位小数)(-180,180)
	 * @param LTLatitude
	 *            区域左上角纬度(度,三位小数)(-90,90)
	 * @param RTLongitude
	 *            区域右上角经度(度,三位小数)(-180,180)
	 * @param RTLatitude
	 *            区域右上角纬度(度,三位小数)(-90,90)
	 * @param RBLongitude
	 *            区域右下角经度(度,三位小数)(-180,180)
	 * @param RBLatitude
	 *            区域右下角纬度(度,三位小数)(-90,90)
	 * @param LBLongitude
	 *            区域左下角经度(度,三位小数)(-180,180)
	 * @param LBLatitude
	 *            区域左下角纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 */
	public static double calculateSunElevationAngle(Calendar startTime, Calendar endTime, double LTLongitude, double LTLatitude,
			double RTLongitude, double RTLatitude, double RBLongitude, double RBLatitude, double LBLongitude, double LBLatitude) {
		//TODO 存在缺陷：此处只考虑正规矩形，不规则形状不适用
		//返回太阳高度角
		return calculateSunElevationAngle(startTime, endTime, LTLongitude, LTLatitude, RBLongitude, RBLatitude);
	}

	/**
	 * 太阳高度角计算
	 * 
	 * @param startTime
	 *            卫星访问该区域开始时间(UTC时间)
	 * @param endTime
	 *            卫星访问该区域结束时间(UTC时间)
	 * @param LTLongitude
	 *            区域左上角经度(度,三位小数)(-180,180)
	 * @param LTLatitude
	 *            区域左上角纬度(度,三位小数)(-90,90)
	 * @param RBLongitude
	 *            区域右下角经度(度,三位小数)(-180,180)
	 * @param RBLatitude
	 *            区域右下角纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 */
	public static double calculateSunElevationAngle(String startTime, String endTime, double LTLongitude, double LTLatitude,
			double RBLongitude, double RBLatitude) {

		//转换成Calendar类型
		Calendar startTimeCalendar = stringToCalendar(startTime);
		//转换成Calendar类型
		Calendar endTimeCalendar = stringToCalendar(endTime);
		//返回太阳高度角
		return calculateSunElevationAngle(startTimeCalendar, endTimeCalendar, LTLongitude, LTLatitude, RBLongitude, RBLatitude);
	}

	/**
	 * 太阳高度角计算
	 * 
	 * @param startTime
	 *            卫星访问该区域开始时间(UTC时间)
	 * @param endTime
	 *            卫星访问该区域结束时间(UTC时间)
	 * @param LTLongitude
	 *            区域左上角经度(度,三位小数)(-180,180)
	 * @param LTLatitude
	 *            区域左上角纬度(度,三位小数)(-90,90)
	 * @param RBLongitude
	 *            区域右下角经度(度,三位小数)(-180,180)
	 * @param RBLatitude
	 *            区域右下角纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 */
	public static double calculateSunElevationAngle(Calendar startTime, Calendar endTime, double LTLongitude, double LTLatitude,
			double RBLongitude, double RBLatitude) {

		//转换成Calendar类型
		Calendar centerTimeCalendar = calculateCenterTime(startTime, endTime);
		//区域中心经度
		double centerLongitude = (LTLongitude + RBLongitude) / 2;
		//区域中心纬度
		double centerLatitude = (LTLatitude + RBLatitude) / 2;
		//返回太阳高度角
		return calculateSunElevationAngle(centerTimeCalendar, centerLongitude, centerLatitude);
	}

	/**
	 * 太阳高度角计算
	 * 
	 * @param centerTime
	 *            卫星访问该区域中心点时间(UTC时间)
	 * @param centerLongitude
	 *            区域中心经度(度,三位小数)(-180,180)
	 * @param centerLatitude
	 *            区域中心纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 */
	public static double calculateSunElevationAngle(String centerTime, double centerLongitude, double centerLatitude) {
		//转换成Calendar类型
		Calendar centerTimeCalendar = stringToCalendar(centerTime);
		//返回太阳高度角
		return calculateSunElevationAngle(centerTimeCalendar, centerLongitude, centerLatitude);
	}
	
	/**
	 * 太阳高度角计算
	 * 
	 * @param startTime
	 * 			卫星访问该区域开始时间(UTC时间)
	 * @param endTime
	 * 			卫星访问该区域结束时间(UTC时间)
	 * @param centerLongitude
	 * 			区域中心经度(度,三位小数)(-180,180)
	 * @param centerLatitude
	 * 			区域中心纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 */
	public static double calculateSunElevationAngle(Calendar startTime, Calendar endTime, double centerLongitude, double centerLatitude){
		//将开始结束时间转换成Calendar类型的中间时间
		Calendar centerTimeCalendar = calculateCenterTime(startTime, endTime);
		//返回太阳高度角
		return calculateSunElevationAngle(centerTimeCalendar, centerLongitude, centerLatitude);
		
	}
	

	
	
	public static void main(String[] args) {
		Calendar centerTime = Calendar.getInstance();
		String time = "2012-08-09 14:45:00.000";
		centerTime = TimeFormatTool.stringToCalendar(time);
		double centerLongitude = -66.9;
		double centerLatitude = 9.3;
		double angle = calculateSunElevationAngle(centerTime, centerLongitude, centerLatitude);
		System.out.println(angle);
	}
	
	
	/**
	 * 太阳高度角计算
	 * 
	 * @param centerTime
	 *            卫星访问该区域中心点时间(UTC时间)
	 * @param centerLongitude
	 *            区域中心经度(度,三位小数)(-180,180)
	 * @param centerLatitude
	 *            区域中心纬度(度,三位小数)(-90,90)
	 * @return double 太阳高度角(度,一位小数)
	 */
	public static double calculateSunElevationAngle(Calendar centerTime, double centerLongitude, double centerLatitude) {

//		final double PI=3.1415926535897932;
//		// 获得太阳时角
//		double sunTimeAngle = 0;
//		sunTimeAngle = 15 * (centerTime.get(11) + centerTime.get(12) / 60.0 + centerTime.get(13) / 3600.0) - 180.0;
//		//sunTimeAngle = sunTimeAngle * PI / 180;
//
//		// 获得太阳赤纬
//		double sunDeclination = 0;
//		int year = centerTime.get(Calendar.YEAR);
//		int month = centerTime.get(Calendar.MONTH) + 1;
//		int day = centerTime.get(Calendar.DAY_OF_MONTH);
//		int hour = centerTime.get(Calendar.HOUR_OF_DAY);
//		int minute = centerTime.get(Calendar.MINUTE);
//		int second = centerTime.get(Calendar.SECOND);
//
//		double N0 = 79.6764 + 0.2422 * (year - 1985) - Math.floor((year - 1985) / 4.0);
//		double N = 0;
//		double dN = 0;
//		double c = 32.8;
//		if (month <= 2) {
//			c = 30.6;
//		} else if (month > 2 && year % 4 == 0) {
//			c = 31.8;
//		}
//
//		N = (int) (30.6 * month - c + 0.5) + day;
//		double l = centerLongitude / 15;
//		double h = hour + minute / 60.0 + second / 3600.0;
//		dN = (h - l) / 24;
//		// 获得太阳日角
//		double sunAngle = 0;
//		sunAngle = 2 * PI * (N + dN - N0) / 365.2422;
//		sunDeclination = 0.3723 + 23.2567 * Math.sin(sunAngle* PI / 180) + 0.1149 * Math.sin(2 * sunAngle* PI / 180) - 0.1712 * Math.sin(3 * sunAngle* PI / 180) - 0.758
//				* Math.cos(sunAngle* PI / 180) + 0.3656 * Math.cos(2 * sunAngle* PI / 180) + 0.0201 * Math.cos(3 * sunAngle* PI / 180);
//
//		//sunDeclination = sunDeclination * PI / 180;
//
//		// 获得太阳高度角
//		double sunElevationAngle = 0;
//		sunElevationAngle = Math.asin(Math.sin(centerLatitude* PI / 180) * Math.sin(sunDeclination* PI / 180) + Math.cos(centerLatitude* PI / 180)* Math.cos(sunDeclination* PI / 180) * Math.cos(sunTimeAngle* PI / 180));
//		sunElevationAngle = sunElevationAngle*180/PI;
//		
		
		
		/**
		 * 新
		 */
		int year = centerTime.get(Calendar.YEAR);
		int month = centerTime.get(Calendar.MONTH) + 1;
		int day = centerTime.get(Calendar.DAY_OF_MONTH);
		int hour = centerTime.get(Calendar.HOUR_OF_DAY);
		int minute = centerTime.get(Calendar.MINUTE);
		int second = centerTime.get(Calendar.SECOND);
		
		double A, N0, N, B, C, K, L, t, G, H;
		double sunElevationAngle = 0.0;
		final double PI=3.1415926535897932;
		A=year/4;
		K=2*PI/365.2422;
		N0=79.6764+0.2422*(year-1985)-(int)(((year-1985)/4.0));
		
		B = A - (int)A;

		C=32.8;
		if(month<=2)
			C =30.6;
		if(B==0 && month>2)
			C = 31.8;
		G = (int)(30.6*month-C+0.5)+day;

		//TODO centerLongitude本身为负值，是否应该改成正的值
		L=centerLongitude/15;              //每15度代表一个时区
		
		H=hour+minute/60.0 + second/3600.0;  //-8是因为输入的时间是北京时，如果用到的时间是UTC，就不用-8
		
		N=G+(H-L)/24;
		t=(N-N0);
		
		double Q0, delta;
		Q0 = t*K;
		
		//太阳纬度角
//		delta = 0.3723+23.2567*Math.sin(Q0*PI/180)+0.1149*Math.sin(2*Q0*PI/180)-0.1712*Math.sin(3*Q0*PI/180)
//			           -0.7580*Math.cos(Q0*PI/180)+0.3656*Math.cos(2*Q0*PI/180)+0.0201*Math.cos(3*Q0*PI/180);
		
		delta = 0.3723+23.2567*Math.sin(Q0)+0.1149*Math.sin(2*Q0)-0.1712*Math.sin(3*Q0) -0.7580*Math.cos(Q0)+0.3656*Math.cos(2*Q0)+0.0201*Math.cos(3*Q0);

		delta = delta*PI/180;

		centerLatitude = centerLatitude * PI/180;
		
		sunElevationAngle = Math.asin(Math.sin(centerLatitude)*Math.sin(delta) + Math.cos(centerLatitude)*Math.cos(delta)*Math.cos((15*(hour+minute/60.0+second/3600.0)+centerLongitude-180)*PI/180.0));
		//sunElevationAngle = Math.asin(Math.sin(centerLatitude* PI/180)*Math.sin(delta) + Math.cos(centerLatitude* PI/180)*Math.cos(delta)*Math.cos((15*(hour+minute/60.0+second/3600.0)+centerLongitude-300)*PI/180.0));

		double temp;
		//cos(sunAzimuth)
		temp = (Math.sin(sunElevationAngle)*Math.sin(centerLatitude) - Math.sin(delta))/(Math.cos(sunElevationAngle)*Math.cos(centerLatitude)); 
		//temp = (Math.sin(sunElevationAngle* PI/180)*Math.sin(centerLatitude* PI/180) - Math.sin(delta))/(Math.cos(sunElevationAngle* PI/180)*Math.cos(centerLatitude* PI/180)); 
		
		if(temp > 1.0) temp = 0.0;
		else if(temp < -1.0) temp = PI;
		else temp = Math.acos(temp);	

		temp = temp+PI;   //太阳在地面点的正北时太阳方位角为0，太阳绕地面点顺时针方向，为正，逆时针方向为负
		
		//时角是以太阳正午时刻为0点的，顺时针方向（下午）为正，反之为负
		//double Et=0.0028-1.9857*Math.sin(Q0* PI/180)+9.9059*Math.sin(2*Q0* PI/180)-7.0924*Math.cos(Q0* PI/180)-0.6882*Math.cos(2*Q0* PI/180); 
		
		////////////////////////////////////////////////////////////////////////////////////////
		//temp2 = (centerLongitude* PI/180-120.0)*4.0/60.0 + hour + minute/60.0 + second/3600.0 -12.0 + Et/60;
		//上面公式中-120.0是因为输入的时间是北京时，如果是UTC时间，此处不用-120 
		////////////////////////////////////////////////////////////////////////////////////////

		sunElevationAngle = sunElevationAngle * 180 / PI;   //太阳高度角，弧度到度
		
		
		return NumberUtils.getRoundHalfUp(sunElevationAngle,4);//4舍5入，保留4位小数
	}

	// /////////////////////////////////////////
	// TODO 公用方法 待整理
	// ///////////////////////////////////////////
	/**
	 * 将yyyy-MM-ddTHH:mm:ss.UUU格式的字符串转换为Calendar类型
	 * 
	 * @param calendarString
	 *            String格式的时间字符串
	 * @return Calendar类型
	 */
	private static Calendar stringToCalendar(String calendarString) {
		if (calendarString == null || calendarString.trim().length() == 0) {
			return null;
		}
		String dateStr = calendarString;
		if (calendarString.trim().indexOf("T") != -1) {
			dateStr = calendarString.trim().replace("T", " ");
		}
		String dateFormat = "yyyy-MM-dd HH:mm:ss";
		if (dateStr.indexOf(".") != -1) {
			dateFormat += ".SSS";
		}
		SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);
		Calendar calendar = Calendar.getInstance();
		try {
			calendar.setTime(formatter.parse(dateStr));
		} catch (ParseException e) {
			return null;
		}
		return calendar;
	}

	/**
	 * 计算时间区间中心点时间
	 * 
	 * @param startTime
	 *            区间开始时间
	 * @param endTime
	 *            区间结束时间
	 * @return 区间中间时间
	 */
	private static Calendar calculateCenterTime(Calendar startTime, Calendar endTime) {
		//计算时间区间， 取中点
		long centerTime = startTime.getTimeInMillis() + (endTime.getTimeInMillis() - startTime.getTimeInMillis()) / 2;

		//转换成Calendar类型
		Calendar centerTimeCalendar = Calendar.getInstance();
		centerTimeCalendar.setTimeInMillis(centerTime);

		return centerTimeCalendar;
	}
}
