Merge lp:~vthompson/ubuntu-weather-app/reboot-sunrise-sunset into lp:ubuntu-weather-app

Proposed by Victor Thompson on 2015-06-26
Status: Merged
Approved by: Andrew Hayzen on 2015-07-25
Approved revision: 64
Merged at revision: 65
Proposed branch: lp:~vthompson/ubuntu-weather-app/reboot-sunrise-sunset
Merge into: lp:ubuntu-weather-app
Diff against target: 469 lines (+348/-8)
7 files modified
app/components/DayDelegate.qml (+2/-2)
app/components/ForecastDetailsDelegate.qml (+1/-0)
app/data/WeatherApi.js (+6/-0)
app/data/suncalc.js (+300/-0)
app/ui/HomePage.qml (+9/-4)
app/ui/LocationPane.qml (+5/-2)
debian/copyright (+25/-0)
To merge this branch: bzr merge lp:~vthompson/ubuntu-weather-app/reboot-sunrise-sunset
Reviewer Review Type Date Requested Status
Andrew Hayzen 2015-06-26 Approve on 2015-07-25
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve on 2015-07-25
Review via email: mp+263171@code.launchpad.net

Commit message

Use sunrise/sunset from API if available, otherwise use calculated times

Description of the change

Use sunrise/sunset from API if available, otherwise use calculated times

To post a comment you must log in.
58. By Victor Thompson on 2015-06-26

get each date in forecast

59. By Victor Thompson on 2015-06-27

Fix spelling mistake: mooon

Andrew Hayzen (ahayzen) wrote :

Some inline code comments/questions to start off with :-)

review: Needs Information
60. By Victor Thompson on 2015-06-27

Update date getter and formatting functions

Victor Thompson (vthompson) wrote :

Added responses and updated mp.

Nekhelesh Ramananthan (nik90) wrote :

> return Qt.formatDate(getDate(dateData), i18n.tr(format))

We should *never* be exposing the format as a translatable string and should instead be using Qt.locale to display the time/date as per the locale format. Considering that this is something that is also present in trunk, it doesn't block this MP.

61. By Victor Thompson on 2015-07-25

Update copyright file

62. By Victor Thompson on 2015-07-25

Add dots

63. By Victor Thompson on 2015-07-25

One more try

64. By Victor Thompson on 2015-07-25

Change to BSD 2-clause

Andrew Hayzen (ahayzen) wrote :

Thanks for the extra changes, LGTM :-)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'app/components/DayDelegate.qml'
2--- app/components/DayDelegate.qml 2015-06-21 14:02:58 +0000
3+++ app/components/DayDelegate.qml 2015-07-25 21:36:51 +0000
4@@ -224,13 +224,13 @@
5 ForecastDetailsDelegate {
6 id: sunriseForecast
7 forecast: i18n.tr("Sunrise")
8- // FIXME: need icon
9+ imageName: "info"
10 }
11
12 ForecastDetailsDelegate {
13 id: sunsetForecast
14 forecast: i18n.tr("Sunset")
15- // FIXME: need icon
16+ imageName: "info"
17 }
18 }
19 }
20
21=== modified file 'app/components/ForecastDetailsDelegate.qml'
22--- app/components/ForecastDetailsDelegate.qml 2015-06-21 15:02:05 +0000
23+++ app/components/ForecastDetailsDelegate.qml 2015-07-25 21:36:51 +0000
24@@ -24,6 +24,7 @@
25 spacing: units.gu(2)
26 visible: value !== ""
27
28+ property alias imageName: icon.name
29 property alias imageSource: icon.source
30 property alias forecast: forecastLabel.text
31 property alias value: forecastValue.text
32
33=== modified file 'app/data/WeatherApi.js'
34--- app/data/WeatherApi.js 2015-04-27 00:57:35 +0000
35+++ app/data/WeatherApi.js 2015-07-25 21:36:51 +0000
36@@ -512,6 +512,7 @@
37 "daily": combinedData[0]["DailyForecasts"],
38 "forecast": combinedData[0]["HourlyForecasts"],
39 "current": combinedData[0]["StandardObservation"],
40+ "sunRiseSet": combinedData[0]["SunRiseSet"],
41 };
42 print("["+location.name+"] "+JSON.stringify(localNow));
43 // add openweathermap id for faster responses
44@@ -522,6 +523,7 @@
45 for(var x=0;x<5;x++) {
46 var dayData = data["daily"][x],
47 date = getLocationTime(((dayData.validDate*1000)-1000)+offset); // minus 1 sec to handle +/-12 TZ
48+ var sunRiseSet = data["sunRiseSet"][x];
49 day = date.year+"-"+date.month+"-"+date.date;
50 if(!todayDate) {
51 if(localNow.year+"-"+localNow.month+"-"+localNow.date > day) {
52@@ -531,6 +533,10 @@
53 todayDate = date;
54 }
55 tmpResult[day] = _buildDayFormat(date, dayData, nowMs);
56+ var sunrise = new Date(sunRiseSet.rise*1000);
57+ var sunset = new Date(sunRiseSet.set*1000);
58+ tmpResult[day].sunrise = sunrise.toLocaleTimeString();
59+ tmpResult[day].sunset = sunset.toLocaleTimeString();
60 }
61 //
62 if(data["forecast"] !== undefined) {
63
64=== added file 'app/data/suncalc.js'
65--- app/data/suncalc.js 1970-01-01 00:00:00 +0000
66+++ app/data/suncalc.js 2015-07-25 21:36:51 +0000
67@@ -0,0 +1,300 @@
68+/*
69+ (c) 2011-2015, Vladimir Agafonkin
70+ SunCalc is a JavaScript library for calculating sun/moon position and light phases.
71+ https://github.com/mourner/suncalc
72+*/
73+
74+// shortcuts for easier to read formulas
75+
76+var PI = Math.PI,
77+ sin = Math.sin,
78+ cos = Math.cos,
79+ tan = Math.tan,
80+ asin = Math.asin,
81+ atan = Math.atan2,
82+ acos = Math.acos,
83+ rad = PI / 180;
84+
85+// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
86+
87+
88+// date/time constants and conversions
89+
90+var dayMs = 1000 * 60 * 60 * 24,
91+ J1970 = 2440588,
92+ J2000 = 2451545;
93+
94+function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
95+function fromJulian(j) { return new Date((j + 0.5 - J1970) * dayMs); }
96+function toDays(date) { return toJulian(date) - J2000; }
97+
98+
99+// general calculations for position
100+
101+var e = rad * 23.4397; // obliquity of the Earth
102+
103+function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
104+function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
105+
106+function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
107+function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
108+
109+function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
110+
111+
112+// general sun calculations
113+
114+function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
115+
116+function eclipticLongitude(M) {
117+
118+ var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
119+ P = rad * 102.9372; // perihelion of the Earth
120+
121+ return M + C + P + PI;
122+}
123+
124+function sunCoords(d) {
125+
126+ var M = solarMeanAnomaly(d),
127+ L = eclipticLongitude(M);
128+
129+ return {
130+ dec: declination(L, 0),
131+ ra: rightAscension(L, 0)
132+ };
133+}
134+
135+
136+var SunCalc = {};
137+
138+
139+// calculates sun position for a given date and latitude/longitude
140+
141+SunCalc.getPosition = function (date, lat, lng) {
142+
143+ var lw = rad * -lng,
144+ phi = rad * lat,
145+ d = toDays(date),
146+
147+ c = sunCoords(d),
148+ H = siderealTime(d, lw) - c.ra;
149+
150+ return {
151+ azimuth: azimuth(H, phi, c.dec),
152+ altitude: altitude(H, phi, c.dec)
153+ };
154+};
155+
156+
157+// sun times configuration (angle, morning name, evening name)
158+
159+var times = SunCalc.times = [
160+ [-0.833, 'sunrise', 'sunset' ],
161+ [ -0.3, 'sunriseEnd', 'sunsetStart' ],
162+ [ -6, 'dawn', 'dusk' ],
163+ [ -12, 'nauticalDawn', 'nauticalDusk'],
164+ [ -18, 'nightEnd', 'night' ],
165+ [ 6, 'goldenHourEnd', 'goldenHour' ]
166+];
167+
168+// adds a custom time to the times config
169+
170+SunCalc.addTime = function (angle, riseName, setName) {
171+ times.push([angle, riseName, setName]);
172+};
173+
174+
175+// calculations for sun times
176+
177+var J0 = 0.0009;
178+
179+function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
180+
181+function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
182+function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
183+
184+function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
185+
186+// returns set time for the given sun altitude
187+function getSetJ(h, lw, phi, dec, n, M, L) {
188+
189+ var w = hourAngle(h, phi, dec),
190+ a = approxTransit(w, lw, n);
191+ return solarTransitJ(a, M, L);
192+}
193+
194+
195+// calculates sun times for a given date and latitude/longitude
196+
197+SunCalc.getTimes = function (date, lat, lng) {
198+
199+ var lw = rad * -lng,
200+ phi = rad * lat,
201+
202+ d = toDays(date),
203+ n = julianCycle(d, lw),
204+ ds = approxTransit(0, lw, n),
205+
206+ M = solarMeanAnomaly(ds),
207+ L = eclipticLongitude(M),
208+ dec = declination(L, 0),
209+
210+ Jnoon = solarTransitJ(ds, M, L),
211+
212+ i, len, time, Jset, Jrise;
213+
214+
215+ var result = {
216+ solarNoon: fromJulian(Jnoon),
217+ nadir: fromJulian(Jnoon - 0.5)
218+ };
219+
220+ for (i = 0, len = times.length; i < len; i += 1) {
221+ time = times[i];
222+
223+ Jset = getSetJ(time[0] * rad, lw, phi, dec, n, M, L);
224+ Jrise = Jnoon - (Jset - Jnoon);
225+
226+ result[time[1]] = fromJulian(Jrise);
227+ result[time[2]] = fromJulian(Jset);
228+ }
229+
230+ return result;
231+};
232+
233+
234+// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
235+
236+function moonCoords(d) { // geocentric ecliptic coordinates of the moon
237+
238+ var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
239+ M = rad * (134.963 + 13.064993 * d), // mean anomaly
240+ F = rad * (93.272 + 13.229350 * d), // mean distance
241+
242+ l = L + rad * 6.289 * sin(M), // longitude
243+ b = rad * 5.128 * sin(F), // latitude
244+ dt = 385001 - 20905 * cos(M); // distance to the moon in km
245+
246+ return {
247+ ra: rightAscension(l, b),
248+ dec: declination(l, b),
249+ dist: dt
250+ };
251+}
252+
253+SunCalc.getMoonPosition = function (date, lat, lng) {
254+
255+ var lw = rad * -lng,
256+ phi = rad * lat,
257+ d = toDays(date),
258+
259+ c = moonCoords(d),
260+ H = siderealTime(d, lw) - c.ra,
261+ h = altitude(H, phi, c.dec);
262+
263+ // altitude correction for refraction
264+ h = h + rad * 0.017 / tan(h + rad * 10.26 / (h + rad * 5.10));
265+
266+ return {
267+ azimuth: azimuth(H, phi, c.dec),
268+ altitude: h,
269+ distance: c.dist
270+ };
271+};
272+
273+
274+// calculations for illumination parameters of the moon,
275+// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
276+// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
277+
278+SunCalc.getMoonIllumination = function (date) {
279+
280+ var d = toDays(date),
281+ s = sunCoords(d),
282+ m = moonCoords(d),
283+
284+ sdist = 149598000, // distance from Earth to Sun in km
285+
286+ phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra)),
287+ inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi)),
288+ angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) -
289+ cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra));
290+
291+ return {
292+ fraction: (1 + cos(inc)) / 2,
293+ phase: 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI,
294+ angle: angle
295+ };
296+};
297+
298+
299+function hoursLater(date, h) {
300+ return new Date(date.valueOf() + h * dayMs / 24);
301+}
302+
303+// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
304+
305+SunCalc.getMoonTimes = function (date, lat, lng) {
306+ var t = new Date(date);
307+ t.setHours(0);
308+ t.setMinutes(0);
309+ t.setSeconds(0);
310+ t.setMilliseconds(0);
311+
312+ var hc = 0.133 * rad,
313+ h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
314+ h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
315+
316+ // go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
317+ for (var i = 1; i <= 24; i += 2) {
318+ h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
319+ h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
320+
321+ a = (h0 + h2) / 2 - h1;
322+ b = (h2 - h0) / 2;
323+ xe = -b / (2 * a);
324+ ye = (a * xe + b) * xe + h1;
325+ d = b * b - 4 * a * h1;
326+ roots = 0;
327+
328+ if (d >= 0) {
329+ dx = Math.sqrt(d) / (Math.abs(a) * 2);
330+ x1 = xe - dx;
331+ x2 = xe + dx;
332+ if (Math.abs(x1) <= 1) roots++;
333+ if (Math.abs(x2) <= 1) roots++;
334+ if (x1 < -1) x1 = x2;
335+ }
336+
337+ if (roots === 1) {
338+ if (h0 < 0) rise = i + x1;
339+ else set = i + x1;
340+
341+ } else if (roots === 2) {
342+ rise = i + (ye < 0 ? x2 : x1);
343+ set = i + (ye < 0 ? x1 : x2);
344+ }
345+
346+ if (rise && set) break;
347+
348+ h0 = h2;
349+ }
350+
351+ var result = {};
352+
353+ if (rise) result.rise = hoursLater(t, rise);
354+ if (set) result.set = hoursLater(t, set);
355+
356+ if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
357+
358+ return result;
359+};
360+
361+
362+// export as AMD module / Node module / browser variable
363+//if (typeof define === 'function' && define.amd) define(SunCalc);
364+//else if (typeof module !== 'undefined') module.exports = SunCalc;
365+//else window.SunCalc = SunCalc;
366+
367+//}());
368
369=== modified file 'app/ui/HomePage.qml'
370--- app/ui/HomePage.qml 2015-07-18 19:08:21 +0000
371+++ app/ui/HomePage.qml 2015-07-25 21:36:51 +0000
372@@ -66,16 +66,21 @@
373 Format date object by given format.
374 */
375 function formatTimestamp(dateData, format) {
376- var date = new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
377- return Qt.formatDate(date, i18n.tr(format))
378+ return Qt.formatDate(getDate(dateData), i18n.tr(format))
379 }
380
381 /*
382 Format time object by given format.
383 */
384 function formatTime(dateData, format) {
385- var date = new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
386- return Qt.formatTime(date, i18n.tr(format))
387+ return Qt.formatTime(getDate(dateData), i18n.tr(format))
388+ }
389+
390+ /*
391+ Get Date object from dateData.
392+ */
393+ function getDate(dateData) {
394+ return new Date(dateData.year, dateData.month, dateData.date, dateData.hours, dateData.minutes)
395 }
396
397 /*
398
399=== modified file 'app/ui/LocationPane.qml'
400--- app/ui/LocationPane.qml 2015-06-21 14:02:58 +0000
401+++ app/ui/LocationPane.qml 2015-07-25 21:36:51 +0000
402@@ -20,6 +20,7 @@
403 import Ubuntu.Components 1.2
404 import Ubuntu.Components.ListItems 0.1 as ListItem
405 import "../components"
406+import "../data/suncalc.js" as SunCalc
407
408 Item {
409 id: locationItem
410@@ -99,6 +100,8 @@
411 image: (forecasts[x].icon !== undefined && iconMap[forecasts[x].icon] !== undefined) ? iconMap[forecasts[x].icon] : "",
412 chanceOfRain: forecasts[x].propPrecip === undefined ? -1 : forecasts[x].propPrecip,
413 humidity: emptyIfUndefined(forecasts[x].humidity, "%"),
414+ sunrise: forecasts[x].sunrise || SunCalc.SunCalc.getTimes(getDate(forecasts[x].date), data.location.coord.lat, data.location.coord.lon).sunrise.toLocaleTimeString(),
415+ sunset: forecasts[x].sunset || SunCalc.SunCalc.getTimes(getDate(forecasts[x].date), data.location.coord.lat, data.location.coord.lon).sunset.toLocaleTimeString(),
416 uvIndex: emptyIfUndefined(forecasts[x].uv),
417 wind: forecasts[x][tempUnits].windSpeed === undefined || forecasts[x].windDir === undefined
418 ? "" : Math.round(forecasts[x][tempUnits].windSpeed) + settings.windUnits + " " + forecasts[x].windDir
419@@ -185,8 +188,8 @@
420 humidity: model.humidity
421 // TODO: extra from API
422 //pollen: model.pollen
423- //sunrise: model.sunrise
424- //sunset: model.sunset
425+ sunrise: model.sunrise
426+ sunset: model.sunset
427 wind: model.wind
428 uvIndex: model.uvIndex
429 }
430
431=== modified file 'debian/copyright'
432--- debian/copyright 2015-02-02 15:25:53 +0000
433+++ debian/copyright 2015-07-25 21:36:51 +0000
434@@ -15,10 +15,35 @@
435 2015 Victor Thompson <victor.thompson@gmail.com>
436 License: GPL-3
437
438+Files: app/data/suncalc.js
439+Copyright: 2014 Vladimir Agafonkin
440+License: BSD-2-clause
441+
442 Files: debian/*
443 Copyright: 2013 Canonical Ltd.
444 License: LGPL-3
445
446+License: BSD-2-clause
447+ Redistribution and use in source and binary forms, with or without modification, are
448+ permitted provided that the following conditions are met:
449+ .
450+ 1. Redistributions of source code must retain the above copyright notice, this list of
451+ conditions and the following disclaimer.
452+ .
453+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
454+ of conditions and the following disclaimer in the documentation and/or other materials
455+ provided with the distribution.
456+ .
457+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
458+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
459+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
460+ COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
461+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
462+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
463+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
464+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
465+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
466+
467 License: GPL-3
468 This package is free software; you can redistribute it and/or
469 modify it under the terms of the GNU General Public

Subscribers

People subscribed via source and target branches

to all changes: