Merge lp:~ubuntu-weather-dev/ubuntu-weather-app/new-weather-api into lp:ubuntu-weather-app

Proposed by Victor Thompson
Status: Merged
Approved by: Victor Thompson
Approved revision: 237
Merged at revision: 242
Proposed branch: lp:~ubuntu-weather-dev/ubuntu-weather-app/new-weather-api
Merge into: lp:ubuntu-weather-app
Diff against target: 395 lines (+167/-91)
4 files modified
app/data/WeatherApi.js (+151/-87)
app/ui/LocationPane.qml (+1/-1)
debian/changelog (+14/-2)
manifest.json.in (+1/-1)
To merge this branch: bzr merge lp:~ubuntu-weather-dev/ubuntu-weather-app/new-weather-api
Reviewer Review Type Date Requested Status
Jenkins Bot continuous-integration Approve
Victor Thompson Approve
Andrew Hayzen Needs Information
Review via email: mp+289584@code.launchpad.net

Commit message

* Update to use new weather API
* Change the trimAPIKey function so it only trims the API Key
* Release 3.2 and bump version to 3.3

Description of the change

Move to the new weather api.

For reference, here is the commit to use the new API in the scope: http://bazaar.launchpad.net/~ubuntuone-hackers/ubuntu-rest-scopes/trunk/revision/505

To post a comment you must log in.
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

OK, this is the first iteration of a 'working' solution, still needs lots of testing :-) Basically as TWC now, from what I understand, requires us to do multiple url calls to get the current, daily and hourly data. I've copied the OWM code and modified it to match the TWC api (as OWM is similar in the fact that it requires multiple url calls).

Main inline comment so far is:
Not sure what todo here? This is for "weather codes" such as, UKXX0085, which we were using, but I cannot see that in the new API? Currently the 1==2 is forcing it to use coordinate based lookups.

Also was the search using TWC? I need to check if that needs to be updated?

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Thanks for finishing this off, Andrew!!

I ran AP and all tests pass. Code changes look fine so far, but one thing we might want to take the opportunity to do now (otherwise we may never do so) is to audit our code coverage in WeatherApi.js and remove any dead code so we don't do needless maintenance on unused code in the future. QtCreator doesn't seem to have a coverage tool, but there seem to be numerous third party tools and qml plugins that might be helpful. Maybe just a JS only coverage tool would be sufficient.

I've been testing this change out and have not hit any regressions thus far. Unless we want to look at removing any dead code, I think this is ready for QA.

TODO:
1. Update changelog
2. Bump version number?
3. Potentially look at removing any dead code

review: Needs Fixing
233. By Andrew Hayzen

* Release 3.2 and update changelog

234. By Andrew Hayzen

* Comment out 'dead code' for now

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

1) Done
2) Done bumped the version and put the relevant stuff in the sections
3) I've commented out the 'dead code' where it uses the location code for TWC, let me know if you want me to remove

Also, I've spotted one todo on line 44 of the diff
+ var partData = dataObj["metric"] || dataObj["imperial"] || dataObj; // TODO: be more intelligent?

Do you think this is OK? or should it be more like?
if (there is metric) { use metric } else if (there is imperial) { use imperial } else { fallback }

Futhermore the lines below have things like calcFahrenheit() which convert from metric->imperial, so it assumes the data is metric, maybe we should remove the imperial option... so just

+ var partData = dataObj["metric"] || dataObj;

I think i've talked myself into changing it to that :')

review: Needs Information
235. By Andrew Hayzen

* Remove dead code
* Fix up imperial todo

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

This isn't a regression, as I've noticed it before--TWC always displays the high temp as the current temp as soon as the high has been reached. Maybe we're using the API incorrectly to determine today's high? Since it's not a regression, I'm fine leaving it as-is... but we should probably file a bug or add a comment to the code with a TODO to investigate. Bug would probably be preferred.

review: Needs Information
Revision history for this message
Victor Thompson (vthompson) wrote :

Actually, maybe it's not a bug. Weather.com seems to do the same. Maybe that's their way of indicating that the temp won't rise?

236. By Andrew Hayzen

* Fix for max_temp being null in TWC causing 'high' to be 0 and therefore lower than the 'low', now show a "-"

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
237. By Andrew Hayzen

* Use "en dash"

Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Victor Thompson (vthompson) wrote :

Ok, this looks good now. Approve!

review: Approve
Revision history for this message
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'app/data/WeatherApi.js'
--- app/data/WeatherApi.js 2015-10-21 02:16:50 +0000
+++ app/data/WeatherApi.js 2016-06-04 21:12:20 +0000
@@ -100,15 +100,20 @@
100}100}
101101
102102
103// Remove anything including and after APPID in the given term103// Remove just the API key
104function trimAPIKey(data) {104function trimAPIKey(data) {
105 var owm = data.indexOf("&APPID=");105 var owm = data.indexOf("APPID=");
106 var twc = data.indexOf("&key=");106 var twc = data.indexOf("apiKey=");
107
108 // Key length is 32 char for both APIs
109 var keySize = 32;
107110
108 if (owm > -1) {111 if (owm > -1) {
109 data = data.substr(0, owm);112 var replace = data.substr(owm+6, keySize);
113 data = data.replace(replace,"")
110 } else if (twc > -1) {114 } else if (twc > -1) {
111 data = data.substr(0, twc);115 var replace = data.substr(twc+7, keySize);
116 data = data.replace(replace,"")
112 }117 }
113118
114 return data;119 return data;
@@ -400,7 +405,7 @@
400 /**405 /**
401 provides neccessary methods for requesting and preparing data from OpenWeatherMap.org406 provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
402 */407 */
403 var _baseUrl = "http://wxdata.weather.com/wxdata/";408 var _baseUrl = "https://api.weather.com/";
404 //409 //
405 var _serviceName = "weatherchannel";410 var _serviceName = "weatherchannel";
406 //411 //
@@ -457,65 +462,70 @@
457 };462 };
458 //463 //
459 function _buildDataPoint(date, dataObj) {464 function _buildDataPoint(date, dataObj) {
460 var data = dataObj["Observation"] || dataObj,465 var partData = dataObj["metric"] || dataObj;
466 var data = dataObj["observation"] || dataObj,
461 result = {467 result = {
462 timestamp: data.date || data.dateTime,468 timestamp: data.fcst_valid,
463 date: date,469 date: date,
464 metric: {470 metric: {
465 temp: data.temp,471 temp: partData.temp,
466 tempFeels: data.feelsLike,472 tempFeels: partData.feels_like,
467 windSpeed: data.wSpeed473 windSpeed: partData.wspd
468 },474 },
469 imperial: {475 imperial: {
470 temp: calcFahrenheit(data.temp),476 temp: calcFahrenheit(partData.temp),
471 tempFeels: calcFahrenheit(data.feelsLike),477 tempFeels: calcFahrenheit(partData.feels_like),
472 windSpeed: convertKphToMph(data.wSpeed)478 windSpeed: convertKphToMph(partData.wspd)
473 },479 },
474 precipType: (data.precip_type !== undefined) ? data.precip_type : null,480 precipType: (data.precip_type !== undefined) ? data.precip_type : null,
475 propPrecip: (data.pop !== undefined) ? data.pop : null,481 propPrecip: (data.pop !== undefined) ? data.pop : null,
476 humidity: data.humid,482 humidity: partData.rh,
477 pressure: data.pressure,483 pressure: partData.mslp,
478 windDeg: data.wDir,484 windDeg: data.wdir,
479 windDir: data.wDirText,485 windDir: data.wdir_cardinal,
480 icon: _iconMap[(data.wxIcon||data.icon)],486 icon: _iconMap[data.icon_code],
481 condition: data.text || data.wDesc,487 condition: data.phrase_32char,
482 uv: data.uv488 uv: data.uv_index
483 };489 };
484 if(_iconMap[data.wxIcon||data.icon] === undefined) {490
485 print("ICON MISSING POINT: "+(data.wxIcon||data.icon)+" "+result.condition)491 if (_iconMap[data.icon_code] === undefined) {
492 print("ICON MISSING POINT: " + data.icon_code + " " + result.condition)
486 }493 }
494
487 return result;495 return result;
488 }496 }
489 //497 //
490 function _buildDayFormat(date, data, now) {498 function _buildDayFormat(date, data, now) {
491 var partData = (now > data.validDate || data.day === undefined) ? data.night : data.day,499 var partData = (now > data.fcst_valid || data.day === undefined) ? data.night : data.day,
492 result = {500 result = {
493 date: date,501 date: date,
494 timestamp: data.validDate,502 timestamp: data.fcst_valid,
495 metric: {503 metric: {
496 tempMin: data.minTemp,504 tempMin: data.min_temp,
497 tempMax: data.maxTemp,505 tempMax: data.max_temp !== null ? data.max_temp : undefined,
498 windSpeed: partData.wSpeed506 windSpeed: partData.wspd
499 },507 },
500 imperial: {508 imperial: {
501 tempMin: calcFahrenheit(data.minTemp),509 tempMin: calcFahrenheit(data.min_temp),
502 tempMax: calcFahrenheit(data.maxTemp !== undefined ? data.maxTemp : data.minTemp),510 tempMax: data.max_temp !== null && data.max_temp !== undefined ? calcFahrenheit(data.max_temp) : undefined,
503 windSpeed: convertKphToMph(partData.wSpeed)511 windSpeed: convertKphToMph(partData.wspd)
504 },512 },
505 precipType: partData.precip_type,513 precipType: partData.precip_type,
506 propPrecip: partData.pop,514 propPrecip: partData.pop,
507 pressure: null,515 pressure: null,
508 humidity: partData.humid,516 humidity: partData.rh,
509 icon: _iconMap[partData.icon],517 icon: _iconMap[partData.icon_code],
510 condition: partData.phrase,518 condition: partData.phrase_32char,
511 windDeg: partData.wDir,519 windDeg: partData.wdir,
512 windDir: partData.wDirText,520 windDir: partData.wdir_cardinal,
513 uv: partData.uv,521 uv: partData.uv_index,
514 hourly: []522 hourly: []
515 }523 }
516 if(_iconMap[partData.icon] === undefined) {524
517 print("ICON MISSING DAY: "+partData.icon+" "+result.condition)525 if (_iconMap[partData.icon_code] === undefined) {
526 print("ICON MISSING DAY: " + partData.icon_code + " " + result.condition)
518 }527 }
528
519 return result;529 return result;
520 }530 }
521 //531 //
@@ -527,35 +537,43 @@
527 nowMs = parseInt(now/1000),537 nowMs = parseInt(now/1000),
528 localNow = getLocationTime(now+offset),538 localNow = getLocationTime(now+offset),
529 data = {539 data = {
530 "location": combinedData[0]["Location"],540 "location": combinedData["current"]["metadata"],
531 "daily": combinedData[0]["DailyForecasts"],541 "daily": combinedData["daily"]["forecasts"],
532 "forecast": combinedData[0]["HourlyForecasts"],542 "forecast": combinedData["forecast"]["forecasts"],
533 "current": combinedData[0]["StandardObservation"],543 "current": combinedData["current"]["observation"],
534 "sunRiseSet": combinedData[0]["SunRiseSet"],
535 };544 };
536 print("["+location.name+"] "+JSON.stringify(localNow));545 print("["+location.name+"] "+JSON.stringify(localNow));
537 // add openweathermap id for faster responses546 // add openweathermap id for faster responses
538 if(location.services && !location.services[_serviceName] && data["location"].key) {547 if(location.services && !location.services[_serviceName] && data["location"].key) {
539 location.services[_serviceName] = data["location"].key548 location.services[_serviceName] = data["location"].key
540 } 549 }
541 // only 5 days of forecast for TWC550 // only 5 days of forecast for TWC
542 for(var x=0;x<5;x++) {551 for(var x=0; x<5; x++) {
543 var dayData = data["daily"][x],552 var dayData = data["daily"][x],
544 date = getLocationTime(((dayData.validDate*1000)-1000)+offset); // minus 1 sec to handle +/-12 TZ553 date = getLocationTime(((dayData.fcst_valid * 1000) - 1000) + offset); // minus 1 sec to handle +/-12 TZ
545 var sunRiseSet = data["sunRiseSet"][x];554
555 // Sun{rise,set} is in ISOString format so use getTime() to convert
556 var sunrise = new Date(dayData.sunrise).getTime(),
557 sunset = new Date(dayData.sunset).getTime();
546 day = date.year+"-"+date.month+"-"+date.date;558 day = date.year+"-"+date.month+"-"+date.date;
547 if(!todayDate) {559
548 if(localNow.year+"-"+localNow.month+"-"+localNow.date > day) {560 if (!todayDate) {
561 if (localNow.year + "-" + localNow.month + "-" + localNow.date > day) {
549 // skip "yesterday"562 // skip "yesterday"
550 continue;563 continue;
551 }564 }
565
552 todayDate = date;566 todayDate = date;
553 }567 }
568
554 tmpResult[day] = _buildDayFormat(date, dayData, nowMs);569 tmpResult[day] = _buildDayFormat(date, dayData, nowMs);
570
555 var timezoneOffset = new Date().getTimezoneOffset();571 var timezoneOffset = new Date().getTimezoneOffset();
556 var timesOffset = (location.timezone && location.timezone.dstOffset !== undefined) ? (location.timezone.dstOffset*60 + timezoneOffset)*60*1000: 0572 var timesOffset = (location.timezone && location.timezone.dstOffset !== undefined) ? (location.timezone.dstOffset*60 + timezoneOffset)*60*1000: 0
557 var sunrise = new Date(sunRiseSet.rise*1000 + timesOffset);573
558 var sunset = new Date(sunRiseSet.set*1000 + timesOffset);574 sunrise = new Date(sunrise + timesOffset);
575 sunset = new Date(sunset + timesOffset);
576
559 var options = { timeZone: location.timezone.timeZoneId, timeZoneName: 'long' };577 var options = { timeZone: location.timezone.timeZoneId, timeZoneName: 'long' };
560 tmpResult[day].sunrise = sunrise.toLocaleTimeString(Qt.locale().name, options);578 tmpResult[day].sunrise = sunrise.toLocaleTimeString(Qt.locale().name, options);
561 tmpResult[day].sunset = sunset.toLocaleTimeString(Qt.locale().name, options);579 tmpResult[day].sunset = sunset.toLocaleTimeString(Qt.locale().name, options);
@@ -563,8 +581,9 @@
563 //581 //
564 if(data["forecast"] !== undefined) {582 if(data["forecast"] !== undefined) {
565 data["forecast"].forEach(function(hourData) {583 data["forecast"].forEach(function(hourData) {
566 var dateData = getLocationTime((hourData.dateTime*1000)+offset),584 var dateData = getLocationTime((hourData.fcst_valid * 1000) + offset),
567 day = dateData.year+"-"+dateData.month+"-"+dateData.date;585 day = dateData.year+"-"+dateData.month+"-"+dateData.date;
586
568 if(tmpResult[day]) {587 if(tmpResult[day]) {
569 tmpResult[day]["hourly"].push(_buildDataPoint(dateData, hourData));588 tmpResult[day]["hourly"].push(_buildDataPoint(dateData, hourData));
570 }589 }
@@ -591,48 +610,93 @@
591 return result;610 return result;
592 }611 }
593 //612 //
594 function _getUrl(params) {613 function _getUrls(params) {
595 var url, serviceId,614 var baseParams = {
596 baseParams = {615 units: (params.units === "metric") ? "m" : "e",
597 key: params.twc_api_key,616 language: Qt.locale().name.replace("_","-"),
598 units: (params.units === "metric") ? "m" : "e",617 apiKey: params.twc_api_key,
599 locale: Qt.locale().name,618 };
600 hours: "48",619 var commands = {
601 },620 "geocode": "v1/geocode/",
602 commands = {621 };
603 "mobileaggregation": "mobile/mobagg/",622 var urls = {
623 current: "",
624 daily: "",
625 hourly: "",
626 };
627
628 // FIXME: only use coords for now and not location codes (UKXX0085)
629 if (params.location.coord) {
630 var coord = {
631 lat: params.location.coord.lat,
632 lng: params.location.coord.lon
604 };633 };
605 if(params.location.services && params.location.services[_serviceName]) {634
606 serviceId = encodeURIComponent(params.location.services[_serviceName]);635 urls.current = _baseUrl + commands["geocode"] +
607 url = _baseUrl+commands["mobileaggregation"]+serviceId+".js?"+parameterize(baseParams);636 coord.lat + "/" + coord.lng +
608 } else if (params.location.coord) {637 "/observations/current.json?" +
609 var coord = {lat: params.location.coord.lat, lng: params.location.coord.lon};638 parameterize(baseParams);
610 url = _baseUrl+commands["mobileaggregation"]+"get.js?"+parameterize(baseParams)+"&"+639 urls.daily = _baseUrl + commands["geocode"] +
611 parameterize(coord);640 coord.lat + "/" + coord.lng +
641 "/forecast/daily/5day.json?" +
642 parameterize(baseParams);
643 urls.hourly = _baseUrl + commands["geocode"] +
644 coord.lat + "/" + coord.lng +
645 "/forecast/hourly/48hour.json?" +
646 parameterize(baseParams);
612 }647 }
613 return url;648
649 return urls;
614 }650 }
615 //651 //
616 return {652 return {
617 getData: function(params, apiCaller, onSuccess, onError) {653 getData: function(params, apiCaller, onSuccess, onError) {
618 var url = _getUrl(params),654 var urls = _getUrls(params),
619 handlerMap = {655 handlerMap = {
620 all: { type: "all", url: url}656 current: {
621 },657 type: "current",
622 response = {658 url: urls.current
623 location: params.location,659 },
624 db: (params.db) ? params.db : null,660 daily: {
625 format: RESPONSE_DATA_VERSION661 type: "daily",
626 },662 url: urls.daily
627 addDataToResponse = (function(request, data) {663 },
628 var formattedResult;664 forecast: {
629 response["data"] = formatResult(data, params.location);665 type: "forecast",
666 url: urls.hourly
667 }
668 },
669 response = {
670 location: params.location,
671 db: (params.db) ? params.db : null,
672 format: RESPONSE_DATA_VERSION
673 },
674 respData = {},
675 addDataToResponse = (function(request, data) {
676 var formattedResult;
677 respData[request.type] = data;
678
679 if (respData["current"] !== undefined &&
680 respData["forecast"] !== undefined &&
681 respData["daily"] !== undefined) {
682
683 response["data"] = formatResult(respData, params.location);
630 onSuccess(response);684 onSuccess(response);
631 }),685 }
632 onErrorHandler = (function(err) {686 }),
633 onError(err);687 onErrorHandler = (function(err) {
634 });688 onError(err);
635 apiCaller(handlerMap.all, addDataToResponse, onErrorHandler);689 }),
690 retryHandler = (function(err) {
691 console.log("retry of " + trimAPIKey(err.request.url));
692 var retryFunc = handlerMap[err.request.type];
693
694 apiCaller(retryFunc, addDataToResponse, onErrorHandler);
695 });
696
697 apiCaller(handlerMap.current, addDataToResponse, retryHandler);
698 apiCaller(handlerMap.forecast, addDataToResponse, retryHandler);
699 apiCaller(handlerMap.daily, addDataToResponse, retryHandler);
636 }700 }
637 }701 }
638})();702})();
639703
=== modified file 'app/ui/LocationPane.qml'
--- app/ui/LocationPane.qml 2016-02-06 01:17:07 +0000
+++ app/ui/LocationPane.qml 2016-06-04 21:12:20 +0000
@@ -150,7 +150,7 @@
150 return {150 return {
151 day: formatTimestamp(data.date, 'dddd'),151 day: formatTimestamp(data.date, 'dddd'),
152 low: Math.round(data[tempUnits].tempMin).toString() + settings.tempScale,152 low: Math.round(data[tempUnits].tempMin).toString() + settings.tempScale,
153 high: (data[tempUnits].tempMax !== undefined) ? Math.round(data[tempUnits].tempMax).toString() + settings.tempScale : "",153 high: (data[tempUnits].tempMax !== undefined) ? Math.round(data[tempUnits].tempMax).toString() + settings.tempScale : "–",
154 image: (data.icon !== undefined && iconMap[data.icon] !== undefined) ? iconMap[data.icon] : "",154 image: (data.icon !== undefined && iconMap[data.icon] !== undefined) ? iconMap[data.icon] : "",
155 condition: emptyIfUndefined(data.condition),155 condition: emptyIfUndefined(data.condition),
156 chanceOfPrecip: emptyIfUndefined(data.propPrecip, "%"),156 chanceOfPrecip: emptyIfUndefined(data.propPrecip, "%"),
157157
=== modified file 'debian/changelog'
--- debian/changelog 2016-03-26 16:38:54 +0000
+++ debian/changelog 2016-06-04 21:12:20 +0000
@@ -1,4 +1,16 @@
1ubuntu-weather-app (3.2) UNRELEASED; urgency=medium1ubuntu-weather-app (3.3ubuntu1) UNRELEASED; urgency=medium
2
3 [ Andrew Hayzen ]
4 * Release 3.2 and bump version to 3.3
5 * Update to use new weather API
6
7 [ Victor Thompson ]
8 * Update to use new weather API
9 * Change the trimAPIKey function so it only trims the API Key
10
11 -- Andrew Hayzen <ahayzen@gmail.com> Sat, 04 Jun 2016 18:31:29 +0100
12
13ubuntu-weather-app (3.2ubuntu1) vivid; urgency=medium
214
3 [ Andrew Hayzen ]15 [ Andrew Hayzen ]
4 * Release 3.1 and bump version to 3.216 * Release 3.1 and bump version to 3.2
@@ -20,7 +32,7 @@
20 [ Vamshi Balanaga ]32 [ Vamshi Balanaga ]
21 * Added test to swipe between location tabs on home page (LP: #1452492)33 * Added test to swipe between location tabs on home page (LP: #1452492)
2234
23 -- Andrew Hayzen <ahayzen@gmail.com> Thu, 03 Dec 2015 15:20:49 +000035 -- Andrew Hayzen <ahayzen@gmail.com> Sat, 04 Jun 2016 18:30:00 +0100
2436
25ubuntu-weather-app (3.1ubuntu1) vivid; urgency=medium37ubuntu-weather-app (3.1ubuntu1) vivid; urgency=medium
2638
2739
=== modified file 'manifest.json.in'
--- manifest.json.in 2016-02-28 22:15:28 +0000
+++ manifest.json.in 2016-06-04 21:12:20 +0000
@@ -12,7 +12,7 @@
12 "maintainer": "Ubuntu App Cats <ubuntu-touch-coreapps@lists.launchpad.net>",12 "maintainer": "Ubuntu App Cats <ubuntu-touch-coreapps@lists.launchpad.net>",
13 "name": "@PROJECT_NAME@",13 "name": "@PROJECT_NAME@",
14 "title": "Weather",14 "title": "Weather",
15 "version": "3.2.@BZR_REVNO@",15 "version": "3.3.@BZR_REVNO@",
16 "x-source": {16 "x-source": {
17 "vcs-bzr": "@BZR_SOURCE@",17 "vcs-bzr": "@BZR_SOURCE@",
18 "vcs-bzr-revno": "@BZR_REVNO@"18 "vcs-bzr-revno": "@BZR_REVNO@"

Subscribers

People subscribed via source and target branches