def get_coordinate(self, ds=None): """Return the coordinate as in the output of group.apply. Currently, only implemented for groupings with prop == month or dayofyear. For prop == dayfofyear, a ds (Dataset or DataArray) can be passed to infer the max doy from the available years and calendar. """ if self.prop == "month": return xr.DataArray(np.arange(1, 13), dims=("month", ), name="month") if self.prop == "season": return xr.DataArray(["DJF", "MAM", "JJA", "SON"], dims=("season", ), name="season") if self.prop == "dayofyear": if ds is not None: cal = get_calendar(ds, dim=self.dim) mdoy = max( days_in_year(yr, cal) for yr in np.unique(ds[self.dim].dt.year)) else: mdoy = 365 return xr.DataArray(np.arange(1, mdoy + 1), dims=("dayofyear"), name="dayofyear") if self.prop == "group": return xr.DataArray([1], dims=("group", ), name="group") # TODO woups what happens when there is no group? (prop is None) raise NotImplementedError()
def day_lengths( dates: xr.DataArray, lat: xr.DataArray, obliquity: float = -0.4091, summer_solstice: DayOfYearStr = "06-21", start_date: Optional[Union[xarray.DataArray, DayOfYearStr]] = None, end_date: Optional[Union[xarray.DataArray, DayOfYearStr]] = None, freq: str = "YS", ) -> xr.DataArray: r"""Day-lengths according to latitude, obliquity, and day of year. Parameters ---------- dates: xr.DataArray lat: xarray.DataArray Latitude coordinate. obliquity: float Obliquity of the elliptic (radians). Default: -0.4091. summer_solstice: DayOfYearStr Date of summer solstice in northern hemisphere. Used for approximating solar julian dates. start_date: xarray.DataArray or DayOfYearStr, optional Start date to consider for calculating mean day lengths. Default: None. end_date: xarray.DataArray or DayOfYearStr, optional End date to consider for calculating mean day lengths. Default: None. freq : str Resampling frequency. Returns ------- xarray.DataArray If start and end date provided, returns total sum of daylight-hour between dates at provided frequency. If no start and end date provided, returns day-length in hours per individual day. Notes ----- Daylight-hours are dependent on latitude, :math:`lat`, the Julian day (solar day) from the summer solstice in the Northern hemisphere, :math:`Jday`, and the axial tilt :math:`Axis`, therefore day-length at any latitude for a given date on Earth, :math:`dayLength_{lat_{Jday}}`, for a given year in days, :math:`Year`, can be approximated as follows: .. math:: dayLength_{lat_{Jday}} = f({lat}, {Jday}) = \frac{\arccos(1-m_{lat_{Jday}})}{\pi} * 24 Where: .. math:: m_{lat_{Jday}} = f({lat}, {Jday}) = 1 - \tan({Lat}) * \tan \left({Axis}*\cos\left[\frac{2*\pi*{Jday}}{||{Year}||} \right] \right) The total sum of daylight hours for a given period between two days (:math:`{Jday} = 0` -> :math:`N`) within a solar year then is: .. math:: \sum({SeasonDayLength_{lat}}) = \sum_{Jday=1}^{N} dayLength_{lat_{Jday}} References ---------- Modified day-length equations for Huglin heliothermal index published in Hall, A., & Jones, G. V. (2010). Spatial analysis of climate in winegrape-growing regions in Australia. Australian Journal of Grape and Wine Research, 16(3), 389‑404. https://doi.org/10.1111/j.1755-0238.2010.00100.x Examples available from Glarner, 2006 (http://www.gandraxa.com/length_of_day.xml). """ cal = get_calendar(dates) year_length = dates.time.copy( data=[days_in_year(x, calendar=cal) for x in dates.time.dt.year]) julian_date_from_solstice = dates.time.copy(data=doy_to_days_since( dates.time.dt.dayofyear, start=summer_solstice, calendar=cal)) m_lat_dayofyear = 1 - np.tan(np.radians(lat)) * np.tan(obliquity * (np.cos( (2 * np.pi * julian_date_from_solstice) / year_length))) day_length_hours = (np.arccos(1 - m_lat_dayofyear) / np.pi) * 24 if start_date and end_date: return aggregate_between_dates(day_length_hours, start=start_date, end=end_date, op="sum", freq=freq) else: return day_length_hours
def test_days_in_year(year, calendar, exp): assert days_in_year(year, calendar) == exp