Merge lp:~martin-borho/ubuntu-weather-app/weather-data-api into lp:ubuntu-weather-app/obsolete.trunk

Proposed by Martin Borho
Status: Merged
Approved by: Michael Hall
Approved revision: 10
Merged at revision: 6
Proposed branch: lp:~martin-borho/ubuntu-weather-app/weather-data-api
Merge into: lp:ubuntu-weather-app/obsolete.trunk
Diff against target: 318 lines (+291/-0)
3 files modified
components/Storage.qml (+68/-0)
components/WeatherApi.js (+218/-0)
weather.qml (+5/-0)
To merge this branch: bzr merge lp:~martin-borho/ubuntu-weather-app/weather-data-api
Reviewer Review Type Date Requested Status
Michael Hall Approve
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve
Review via email: mp+155997@code.launchpad.net

Commit message

Added first version of Weather-Data-Api and Storage

Description of the change

Added first version of Weather-Data-Api and Storage (for now based on QtQuick.LocalStorage 2.0)

The WeatherApi can be expanded with other services (see OpenWeatherMapApi object in WeatherApi.js). The response object has 4 properties: obj.location (Location data), obj.current (CurrentCondition), obj.forecast (3-hours-based forecast) and obj.daily (DailyForecast).

The whole API data-response is meant to be saved "as is" in the storage, together with a timestamp.

Missing:
- better error-handling
- proper date handling
- better response format
- ...

For a complete cycle of location searching, requesting data from openweathermap.org and storage related actions copy the following to your weather.qml:

    function onApiError(err) {
        console.log("onApiError")
        console.log(err.msg+" / "+err.request.url)
    }

    function onSearchSuccess(locations) {
        console.log("search result");
        locations.results.forEach(function(v) {
            console.log(v.name+", "+v.country);
        })
    }

    function search() {
        // search
        Api.WeatherApi.searchLocationByName({name:"hamburg", units:"metric"},onSearchSuccess,onApiError);
        Api.WeatherApi.searchLocationByPoint({coords: {"lon":10,"lat":53.549999}, units:"metric"}, onSearchSuccess,onApiError);
    }

    function onLocationDataSuccess(resp) {
        storage.clearDB();
        storage.insertLocation(resp);
        storage.getLocations(function(locations){
            locations.forEach(function(loc) {
                console.log('DB-Entry: '+loc.db.id+" "+loc.db.updated);
                storage.updateLocation(loc.db.id, loc);
            });
            storage.getLocations(listLocations);
        });
    }

    function listLocations(locations) {
        locations.forEach(function(loc) {
            console.log('DB-Entry: '+loc.db.id+" "+loc.db.updated);
            console.log("Data for "+loc.location.name+" ["+loc.location.service+"/"+loc.location.service_id+"]");
            console.log("Current Condition:");
            if(loc.current.error === undefined) {
                console.log(loc.current.results.condition.main+" "+loc.current.results.condition.description)
            } else {
                console.log("ERROR: "+loc.current.msg+" / "+loc.current.request.url);
            }

            console.log("Forecast")
            if(loc.forecast.error === undefined) {
                loc.forecast.results.forEach(function(f) {
                    console.log(f.date+", "+f.temp);
                })
            } else {
                console.log("ERROR: "+loc.forecast.msg+" / "+loc.forecast.request.url);
            }

            console.log("Daily forecast")
            if(loc.daily.error === undefined) {
                loc.daily.results.forEach(function(f) {
                    console.log(f.timestamp+", "+f.temp);
                })
            } else {
                console.log("ERROR: "+loc.daily.msg+" / "+loc.daily.request.url);
            }
            console.log('delete location');
            storage.clearLocation(loc.db.id);

            storage.getLocations(function(res) {
                console.log(res.length+" entrys left in DB");
            });
        });
    }

    function requestLocationWeather() {
        // weather data requests
        var location = {service: "openweathermap", service_id: 2911298, name: "Hamburg"};
        Api.WeatherApi.getLocationData({location:location, units: "metric"}, onLocationDataSuccess, onApiError);
    }

    Component.onCompleted: {
        search();
        requestLocationWeather();
    }

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Hall (mhall119) wrote :

Looks good to me. We should consider using U1DB for storing locations, so we can sync across devices, but this if fine for now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'components/Storage.qml'
--- components/Storage.qml 1970-01-01 00:00:00 +0000
+++ components/Storage.qml 2013-03-28 15:37:25 +0000
@@ -0,0 +1,68 @@
1import QtQuick.LocalStorage 2.0
2import QtQuick 2.0
3
4Item {
5 property var db: null
6
7 function openDB() {
8 if(db !== null) return;
9
10 db = LocalStorage.openDatabaseSync("ubuntu-weather-app", "", "Default Ubuntu weather app", 100000);
11
12 if (db.version === "") {
13 db.changeVersion("", "0.1",
14 function(tx) {
15 tx.executeSql('CREATE TABLE IF NOT EXISTS Locations(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT, date TEXT)');
16 console.log('Database created');
17 });
18 }
19 }
20
21 function insertLocation(data) {
22 openDB();
23 var res;
24 db.transaction( function(tx){
25 var r = tx.executeSql('INSERT INTO Locations(data, date) VALUES(?, ?)', [JSON.stringify(data), new Date().getTime()]);
26 res = r.insertId;
27 });
28 return res;
29 }
30
31 function updateLocation(dbId, data) {
32 openDB();
33 db.transaction( function(tx){
34 var r = tx.executeSql('UPDATE Locations SET data = ?, date=? WHERE id = ?', [JSON.stringify(data), new Date().getTime(), dbId])
35 });
36 }
37
38 function getLocations(callback) {
39 openDB();
40 db.transaction(
41 function(tx){
42 var locations = [];
43 var rs = tx.executeSql('SELECT * FROM Locations');
44 for(var i = 0; i < rs.rows.length; i++) {
45 var row = rs.rows.item(i),
46 locData = JSON.parse(row.data);
47 locData["db"] = {id: row.id, updated: new Date(parseInt(row.date, 10))};
48 locations.push(locData);
49 }
50 callback(locations);
51 }
52 );
53 }
54
55 function clearLocation(location_id) {
56 openDB();
57 db.transaction(function(tx){
58 tx.executeSql('DELETE FROM Locations WHERE id = ?', [location_id]);
59 });
60 }
61
62 function clearDB() { // for dev purposes
63 openDB();
64 db.transaction(function(tx){
65 tx.executeSql('DELETE FROM Locations WHERE 1');
66 });
67 }
68}
069
=== added file 'components/WeatherApi.js'
--- components/WeatherApi.js 1970-01-01 00:00:00 +0000
+++ components/WeatherApi.js 2013-03-28 15:37:25 +0000
@@ -0,0 +1,218 @@
1.pragma library
2
3var OpenWeatherMapApi = (function() {
4 /**
5 provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
6 */
7 var _baseUrl = "http://api.openweathermap.org/data/2.1/";
8 //
9 function _buildSearchResult(request, data) {
10 var searchResult = {
11 results: [],
12 request: request
13 }
14 data.list.forEach(function(r) {
15 searchResult.results.push({
16 service: "openweathermap",
17 service_id: r.id,
18 name: r.name,
19 coord: r.coords,
20 country: (r.sys && r.sys.country) ? r.sys.country : null
21 });
22 })
23 return searchResult;
24 }
25 //
26 function _buildDataPoint(data) {
27 // TODO add snow or rain data
28 var result = {
29 timestamp: data.dt,
30 date: (data.date !== undefined) ? data.date : ((data.dt_txt !== undefined) ? data.dt_txt: null),
31 temp: data.main.temp,
32 temp_min: data.main.temp_min,
33 temp_max: data.main.temp_max,
34 humidity: data.main.humidity,
35 pressure: data.main.pressure,
36 wind_speed: data.wind.speed,
37 wind_deg: data.wind.deg,
38 condition: data.weather[0]
39 };
40 if(data.id !== undefined) {
41 result["service"] = "openweathermap";
42 result["service_id"] = data.id;
43
44 }
45 return result;
46 }
47 //
48 function _buildCurrentCondition(request, data) {
49 var currentResult = {
50 results: _buildDataPoint(data),
51 request: request
52 }
53 return currentResult;
54 }
55 //
56 function _buildForecast(request, data) {
57 var forecastResult = {
58 results: [],
59 request: request
60 }
61 data.list.forEach(function(f) {
62 forecastResult.results.push(_buildDataPoint(f));
63 });
64 return forecastResult;
65 }
66 //
67 function _buildDailyForecast(request, data) {
68 var forecastResult = {
69 results: [],
70 request: request
71 }
72 data.list.forEach(function(f) {
73 // TODO date_txt
74 forecastResult.results.push({
75 timestamp: f.dt,
76 temp: f.temp,
77 night: f.night,
78 eve: f.eve,
79 morn: f.morn,
80 pressure: f.pressure,
81 humidity: f.humidity,
82 condition: f.weather,
83 wind_speed: f.speed,
84 wind_deg: f.deg
85 });
86 });
87 return forecastResult;
88 }
89 //
90 return {
91 //
92 getSearchByNameRequest: function(params) {
93 var request = {
94 type: "search",
95 url: _baseUrl+"find/name?q="+encodeURIComponent(params.name)+"&units="+params.units,
96 formatter: _buildSearchResult
97 }
98 return request;
99 },
100 //
101 getSearchByPointRequest: function(params) {
102 var url = _baseUrl+"find/city?lat="+encodeURIComponent(params.coords.lat)
103 +"&lon="+encodeURIComponent(params.coords.lon)+"&units="+params.units,
104 request = {
105 type: "search",
106 url: url,
107 formatter: _buildSearchResult
108 };
109 return request;
110 },
111 //
112 getCurrentConditionRequest: function(params) {
113 var request = {
114 type: "current",
115 url: _baseUrl + "weather/city/"+params.location.service_id+"?units="+params.units,
116 formatter: _buildCurrentCondition
117 }
118 return request;
119 },
120 //
121 getForecastRequest: function(params) {
122 var request = {
123 type: "forecast",
124 url: _baseUrl + "forecast/city/"+params.location.service_id+"?units="+params.units,
125 formatter: _buildForecast
126 }
127 return request;
128 },
129 //
130 getDailyForecastRequest: function(params) {
131 var request = {
132 type: "daily",
133 url: _baseUrl + "forecast/city/"+params.location.service_id+"?mode=daily_compact&units="+params.units,
134 formatter: _buildDailyForecast
135 }
136 return request;
137 }
138 }
139
140})();
141
142var WeatherApi = (function(_services) {
143 /**
144 proxy for requesting weather apis, the passed _services are providing the respective api endpoints
145 and formatters to build a uniform response object
146 */
147 function _getService(name) {
148 if(_services[name] !== undefined) {
149 return _services[name];
150 }
151 return _services["openweathermap"];
152 }
153 //
154 function _sendRequest(request, onSuccess, onError) {
155 var xmlHttp = new XMLHttpRequest();
156 if (xmlHttp) {
157 console.log(request.url);
158 xmlHttp.open('GET', request.url, true);
159 xmlHttp.onreadystatechange = function () {
160 if (xmlHttp.readyState == 4) {
161 var json = JSON.parse(xmlHttp.responseText);
162 if(xmlHttp.status === 200) {
163 var result = request.formatter(request,json);
164 onSuccess(result);
165 } else {
166 onError({error: true, msg: "something went wrong", request: request});
167 }
168 }
169 };
170 xmlHttp.send(null);
171 }
172 }
173 //
174 return {
175 getForecast: function(params, onSuccess, onError) {
176 var request = _getService().getForecastRequest(params);
177 _sendRequest(request, onSuccess, onError);
178 },
179 //
180 getDailyForecast: function(params, onSuccess, onError) {
181 var request = _getService().getDailyForecastRequest(params);
182 _sendRequest(request, onSuccess, onError);
183 },
184 //
185 getCurrentCondition: function(params, onSuccess, onError) {
186 var request = _getService().getCurrentConditionRequest(params);
187 _sendRequest(request, onSuccess, onError);
188 },
189 //
190 searchLocationByName: function(params, onSuccess, onError) {
191 var request = _getService().getSearchByNameRequest(params);
192 _sendRequest(request, onSuccess, onError);
193 },
194 //
195 searchLocationByPoint: function(params, onSuccess, onError) {
196 var request = _getService().getSearchByPointRequest(params);
197 _sendRequest(request, onSuccess, onError);
198 },
199 //
200 getLocationData: function(params, onSuccess, onError) {
201 var response = {
202 location: params.location
203 },
204 addDataToResponse = (function(data) {
205 response[data.request.type] = data;
206 if(response["current"] !== undefined && response["forecast"] !== undefined && response["daily"] !== undefined) {
207 onSuccess(response);
208 }
209 })
210
211 this.getCurrentCondition(params, addDataToResponse, addDataToResponse)
212 this.getForecast(params, addDataToResponse, addDataToResponse);
213 this.getDailyForecast(params, addDataToResponse, addDataToResponse);
214 }
215 }
216})({
217 "openweathermap": OpenWeatherMapApi
218});
0219
=== modified file 'weather.qml'
--- weather.qml 2013-03-14 21:46:34 +0000
+++ weather.qml 2013-03-28 15:37:25 +0000
@@ -1,6 +1,7 @@
1import QtQuick 2.01import QtQuick 2.0
2import Ubuntu.Components 0.12import Ubuntu.Components 0.1
3import "components" as Components3import "components" as Components
4import "components/WeatherApi.js" as Api
45
5/*!6/*!
6 \brief MainView with Tabs element.7 \brief MainView with Tabs element.
@@ -15,6 +16,10 @@
15 width: units.gu(50)16 width: units.gu(50)
16 height: units.gu(75)17 height: units.gu(75)
17 18
19 Components.Storage{
20 id: storage
21 }
22
18 Tabs {23 Tabs {
19 id: tabs24 id: tabs
20 anchors.fill: parent25 anchors.fill: parent

Subscribers

People subscribed via source and target branches