-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsunrise.m
319 lines (291 loc) · 9.49 KB
/
sunrise.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
function varargout=sunrise(varargin)
%SUNRISE Computes sunset and sunrise time.
% SUNRISE(LAT,LON,ALT,TZ) displays time of sunrise and sunset at
% location latitude LAT (decimal degrees, positive towards North),
% longitude LON (decimal degrees, positive towards East), altitude ALT
% (in meters above sea level) for today. Time zone TZ (in hour) is
% recommanded but optional (default is the computer system time zone).
%
% SUNRISE(LAT,LON,ALT,TZ,DATE) computes sunrise and sunset time for
% date DATE (in any datenum compatible format). DATE can be scalar,
% vector or matrix of dates (strings or datenum).
%
% SUNRISE by itself displays time of sunrise and sunset at your
% approximate location (needs Java activated and internet connection).
%
% [SRISE,SSET,NOON]=SUNRISE(...) returns time of sunrise, sunset and noon
% in datenum format. To get only hours, try datestr(srise,'HH:MM').
%
% DAYLENGTH=SUNRISE(...) returns the day length of light expressed in
% fraction of day. Multiply by 24 to get hours.
%
% LAT=SUNRISE(DAYLENGTH,ALT,DATE,'day2lat') estimates the latitude from
% day length (in fraction of day) and altitude ALT (in meters) for the
% day DATE. If ALT is ommitted it uses sea level (0 m). If DATE is
% ommitted it computes for today.
%
% [LAT,LON]=SUNRISE(SRISE,SSET,ALT,'sun2ll') estimates the latitude and
% longitude from sunrise and sunset dates/time, both in GMT time zone
% (TZ = 0), and altitude ALT (in meters). If ALT is ommitted it uses sea
% level (0 m).
%
% Note: reverse function does not try to fit the sunrise and sunset times,
% but it uses both noon time (average of sunrise/sunset) and day length
% (difference between sunrise/sunset) values to look for the best
% latitude and longitude.
%
%
% Examples:
% >> sunrise(37.71,15.03,2031,2,'2017-07-17')
% Sunrise: 17-Jul-2017 05:43:10 +02
% Sunset: 17-Jul-2017 20:30:39 +02
% Day length: 14h 47mn 30s
%
% >> sunrise
% Location: -8.65°N, 115.2167°E, 0m
% Sunrise: 16-Oct-2017 05:57:07 +08
% Sunset: 16-Oct-2017 18:14:31 +08
% Day length: 12h 17mn 24s
%
% >> sunrise(14/24,0,'2019-04-21','day2lat')
% Estimated latitude: 49.07°N
%
% >> sunrise('2019-04-22 04:52:12','2019-04-22 18:51:04',0,'sun2ll')
% Estimated location: 47.9995°N, 2.00142°E
%
% Plot sunrise and sunset time variation for the year:
% days = datenum(2017,1,1:365);
% [srise,sset,noon]=sunrise(48.8,2.3,0,2,days);
% plot(days,rem([srise;sset;noon]',1)*24,'linewidth',4)
% datetick('x')
%
%
% Reference:
% https://en.wikipedia.org/wiki/Sunrise_equation
%
% Author: Francois Beauducel, IPGP
% Created: 2017-10-10 in Paris, France
% Updated: 2022-09-14
% Release history:
% [2022-09-14] v1.5
% - GNU Octave full compatibility
% [2019-08-18] v1.4
% - fix an issue in reverse function for western longitudes
% [2019-04-21] v1.3
% - adds reverse functions to compute lat/lon from sunrise/sunset
% (thanks to a suggestion by Jaechan Lim)
% [2018-10-17] v1.2
% - changes to ip-api.com for automatic geolocalisation
% [2017-10-16] v1.1
% - removes dependency from jsondecode function
% - automatic detection of local timezone (needs Java)
% [2017-10-10] v1.0
% - initial function
%
% Copyright (c) 2022, François Beauducel, covered by BSD License.
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are
% met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in
% the documentation and/or other materials provided with the
% distribution
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
% IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
% TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
% OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
% SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
% LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
% OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
day2lat = any(strcmpi(varargin,'day2lat'));
sun2ll = any(strcmpi(varargin,'sun2ll'));
% --- Forward problem: sunrise(lat,lon,alt,tz,dte)
if ~day2lat && ~sun2ll
if nargin < 5
dte = floor(now);
else
dte = floor(datenum(varargin{5}));
end
if nargin < 4 || isempty(varargin{4})
if exist('java.util.Date','class')
tz = -java.util.Date().getTimezoneOffset/60;
else
tz = 0;
end
elseif nargin > 3
tz = varargin{4};
end
if nargin < 3
alt = 0;
else
alt = varargin{3};
end
% try to guess the location...
if nargin < 2 || isempty(varargin{1}) || isempty(varargin{2})
api = 'http://ip-api.com/json';
if exist('webread','file') == 2
D = webread(api);
if ~isstruct(D) % for GNU Octave compatibility
D = json2struct(D);
end
else % backward compatibility for Matlab < 2014b
D = json2struct(urlread(api,'TimeOut',5));
end
if ~isfield(D,'lat') || ~isfield(D,'lon')
error('Cannot determine automatic location... sorry!')
end
lat = D.lat;
lon = D.lon;
autoloc = 1;
else
lat = varargin{1};
lon = varargin{2};
autoloc = 0;
end
% main computation
[omega,noon] = omeganoon(lat,lon,alt,tz,dte);
sset = noon + omega/360;
srise = noon - omega/360;
dayl = omega/180;
switch nargout
case 0
if autoloc
fprintf('Location: %g°N, %g°E, %gm\n',lat,lon,alt);
end
for n = 1:numel(dte)
fprintf('Sunrise: %s %+03d\nSunset: %s %+03d\nDay length: %gh %gmn %gs\n\n', ...
datestr(srise(n)),tz,datestr(sset(n)),tz, ...
floor(24*dayl),floor(mod(24*dayl,1)*60),round(mod(1440*dayl,1)*60));
end
case 1 % daylength
varargout{1} = dayl;
case 2 % sunrise, sunset
varargout{1} = srise;
varargout{2} = sset;
case 3 % sunrise, sunset, noon
varargout{1} = srise;
varargout{2} = sset;
varargout{3} = noon;
end
end
% --- Inverse problem: sunrise(daylength,alt,dte,'day2lat') to latitude
if day2lat
if nargin > 1
dayl = varargin{1};
if dayl < 0 || dayl > 1
error('DAYLENGTH must be a value between 0 and 1.');
end
if nargin < 3
alt = 0;
else
alt = varargin{2};
end
if nargin < 4
dte = floor(now);
else
dte = varargin{3};
% for GNU Octave compatibility: must check argument of datenum()
if isnumeric(dte) && numel(dte) > 2 || ischar(dte)
dte = datenum(dte);
end
dte = floor(dte);
end
vlat = -90:90;
vomega = omeganoon(vlat,0,alt,0,dte); % supposes longitude = 0
[vdl,k] = unique(vomega/180);
lat = interp1(vdl,vlat(k),dayl);
if nargout > 0
varargout{1} = lat;
else
fprintf('Estimated latitude: %g°N\n',lat);
end
else
error('DAYLENGTH must be defined for reverse computation.')
end
end
% --- Inverse problem: sunrise(srise,sset,alt,'sun2ll') to lat/lon
if sun2ll
if nargin > 2
srise = datenum(varargin{1});
sset = datenum(varargin{2});
dayl = sset - srise;
if dayl < 0 || dayl > 1
error('SUNRISE and SUNSET times must be within 24 hours.')
end
if nargin < 4
alt = 0;
else
alt = varargin{3};
end
% longitude from noon date and time
vlon = -180:180;
[~,vnoon] = omeganoon(0,vlon,alt,0,floor(srise)); % supposes latitude = 0
[vno,k] = unique(vnoon);
lon = interp1(vno,vlon(k),(srise+sset)/2);
% latitude from daylength
vlat = -90:90;
vomega = omeganoon(vlat,lon,alt,0,floor(srise));
[vdl,k] = unique(vomega/180);
lat = interp1(vdl,vlat(k),dayl);
if nargout > 1
varargout(1:2) = {lat,lon};
else
fprintf('Estimated location: %g°N, %g°E\n',lat,lon);
end
else
error('SUNRISE and SUNSET must be defined for reverse computation.')
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [omega,noon] = omeganoon(lat,lon,alt,tz,dte)
% main function that computes daylength and noon time
% https://en.wikipedia.org/wiki/Sunrise_equation
% number of days since Jan 1st, 2000 12:00 UT
n2000 = dte - datenum(2000,1,1,12,0,0) + 68.184/86400;
% mean solar moon
Js = n2000 - lon/360;
% solar mean anomaly
M = mod(357.5291 + 0.98560028*Js,360);
% center
C = 1.9148*sind(M) + 0.0200*sind(2*M) + 0.0003*sind(3*M);
% ecliptic longitude
lambda = mod(M + C + 180 + 102.9372,360);
% solar transit
Jt = 2451545.5 + Js + 0.0053*sind(M) - 0.0069*sind(2*lambda);
% Sun declination
delta = asind(sind(lambda)*sind(23.44));
% hour angle (day expressed in geometric degrees)
h = (sind(-0.83 - 2.076*sqrt(alt)/60) - sind(lat).*sind(delta))./(cosd(lat).*cosd(delta));
omega = acosd(h);
% to avoid meaningless complex angles: forces omega to 0 or 12h
omega(h<-1) = 180;
omega(h>1) = 0;
omega = real(omega);
% noon
noon = Jt + datenum(2000,1,1,12,0,0) - 2451545 + tz/24;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function J=json2struct(json)
% very simple JSON interpreter
C = textscan(json,'%s','delimiter',',');
for n = 1:length(C{1})
s = textscan(regexprep(C{1}{n},'[{}]',''),'%s','Delimiter',':');
key = regexprep(s{1}{1},'[".]',''); % removes any double quote or dot
if length(s{1}) > 1
if ~isempty(strfind(s{1}{2},'"'))
val = regexprep(s{1}{2},'"','');
else
val = str2double(s{1}{2});
end
J.(key) = val;
end
end