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

Proposed by Martin Borho on 2013-03-28
Status: Merged
Approved by: Michael Hall on 2013-04-03
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 2013-03-28 Approve on 2013-04-03
Ubuntu Phone Apps Jenkins Bot continuous-integration Approve on 2013-03-28
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.
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
1=== added file 'components/Storage.qml'
2--- components/Storage.qml 1970-01-01 00:00:00 +0000
3+++ components/Storage.qml 2013-03-28 15:37:25 +0000
4@@ -0,0 +1,68 @@
5+import QtQuick.LocalStorage 2.0
6+import QtQuick 2.0
7+
8+Item {
9+ property var db: null
10+
11+ function openDB() {
12+ if(db !== null) return;
13+
14+ db = LocalStorage.openDatabaseSync("ubuntu-weather-app", "", "Default Ubuntu weather app", 100000);
15+
16+ if (db.version === "") {
17+ db.changeVersion("", "0.1",
18+ function(tx) {
19+ tx.executeSql('CREATE TABLE IF NOT EXISTS Locations(id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT, date TEXT)');
20+ console.log('Database created');
21+ });
22+ }
23+ }
24+
25+ function insertLocation(data) {
26+ openDB();
27+ var res;
28+ db.transaction( function(tx){
29+ var r = tx.executeSql('INSERT INTO Locations(data, date) VALUES(?, ?)', [JSON.stringify(data), new Date().getTime()]);
30+ res = r.insertId;
31+ });
32+ return res;
33+ }
34+
35+ function updateLocation(dbId, data) {
36+ openDB();
37+ db.transaction( function(tx){
38+ var r = tx.executeSql('UPDATE Locations SET data = ?, date=? WHERE id = ?', [JSON.stringify(data), new Date().getTime(), dbId])
39+ });
40+ }
41+
42+ function getLocations(callback) {
43+ openDB();
44+ db.transaction(
45+ function(tx){
46+ var locations = [];
47+ var rs = tx.executeSql('SELECT * FROM Locations');
48+ for(var i = 0; i < rs.rows.length; i++) {
49+ var row = rs.rows.item(i),
50+ locData = JSON.parse(row.data);
51+ locData["db"] = {id: row.id, updated: new Date(parseInt(row.date, 10))};
52+ locations.push(locData);
53+ }
54+ callback(locations);
55+ }
56+ );
57+ }
58+
59+ function clearLocation(location_id) {
60+ openDB();
61+ db.transaction(function(tx){
62+ tx.executeSql('DELETE FROM Locations WHERE id = ?', [location_id]);
63+ });
64+ }
65+
66+ function clearDB() { // for dev purposes
67+ openDB();
68+ db.transaction(function(tx){
69+ tx.executeSql('DELETE FROM Locations WHERE 1');
70+ });
71+ }
72+}
73
74=== added file 'components/WeatherApi.js'
75--- components/WeatherApi.js 1970-01-01 00:00:00 +0000
76+++ components/WeatherApi.js 2013-03-28 15:37:25 +0000
77@@ -0,0 +1,218 @@
78+.pragma library
79+
80+var OpenWeatherMapApi = (function() {
81+ /**
82+ provides neccessary methods for requesting and preparing data from OpenWeatherMap.org
83+ */
84+ var _baseUrl = "http://api.openweathermap.org/data/2.1/";
85+ //
86+ function _buildSearchResult(request, data) {
87+ var searchResult = {
88+ results: [],
89+ request: request
90+ }
91+ data.list.forEach(function(r) {
92+ searchResult.results.push({
93+ service: "openweathermap",
94+ service_id: r.id,
95+ name: r.name,
96+ coord: r.coords,
97+ country: (r.sys && r.sys.country) ? r.sys.country : null
98+ });
99+ })
100+ return searchResult;
101+ }
102+ //
103+ function _buildDataPoint(data) {
104+ // TODO add snow or rain data
105+ var result = {
106+ timestamp: data.dt,
107+ date: (data.date !== undefined) ? data.date : ((data.dt_txt !== undefined) ? data.dt_txt: null),
108+ temp: data.main.temp,
109+ temp_min: data.main.temp_min,
110+ temp_max: data.main.temp_max,
111+ humidity: data.main.humidity,
112+ pressure: data.main.pressure,
113+ wind_speed: data.wind.speed,
114+ wind_deg: data.wind.deg,
115+ condition: data.weather[0]
116+ };
117+ if(data.id !== undefined) {
118+ result["service"] = "openweathermap";
119+ result["service_id"] = data.id;
120+
121+ }
122+ return result;
123+ }
124+ //
125+ function _buildCurrentCondition(request, data) {
126+ var currentResult = {
127+ results: _buildDataPoint(data),
128+ request: request
129+ }
130+ return currentResult;
131+ }
132+ //
133+ function _buildForecast(request, data) {
134+ var forecastResult = {
135+ results: [],
136+ request: request
137+ }
138+ data.list.forEach(function(f) {
139+ forecastResult.results.push(_buildDataPoint(f));
140+ });
141+ return forecastResult;
142+ }
143+ //
144+ function _buildDailyForecast(request, data) {
145+ var forecastResult = {
146+ results: [],
147+ request: request
148+ }
149+ data.list.forEach(function(f) {
150+ // TODO date_txt
151+ forecastResult.results.push({
152+ timestamp: f.dt,
153+ temp: f.temp,
154+ night: f.night,
155+ eve: f.eve,
156+ morn: f.morn,
157+ pressure: f.pressure,
158+ humidity: f.humidity,
159+ condition: f.weather,
160+ wind_speed: f.speed,
161+ wind_deg: f.deg
162+ });
163+ });
164+ return forecastResult;
165+ }
166+ //
167+ return {
168+ //
169+ getSearchByNameRequest: function(params) {
170+ var request = {
171+ type: "search",
172+ url: _baseUrl+"find/name?q="+encodeURIComponent(params.name)+"&units="+params.units,
173+ formatter: _buildSearchResult
174+ }
175+ return request;
176+ },
177+ //
178+ getSearchByPointRequest: function(params) {
179+ var url = _baseUrl+"find/city?lat="+encodeURIComponent(params.coords.lat)
180+ +"&lon="+encodeURIComponent(params.coords.lon)+"&units="+params.units,
181+ request = {
182+ type: "search",
183+ url: url,
184+ formatter: _buildSearchResult
185+ };
186+ return request;
187+ },
188+ //
189+ getCurrentConditionRequest: function(params) {
190+ var request = {
191+ type: "current",
192+ url: _baseUrl + "weather/city/"+params.location.service_id+"?units="+params.units,
193+ formatter: _buildCurrentCondition
194+ }
195+ return request;
196+ },
197+ //
198+ getForecastRequest: function(params) {
199+ var request = {
200+ type: "forecast",
201+ url: _baseUrl + "forecast/city/"+params.location.service_id+"?units="+params.units,
202+ formatter: _buildForecast
203+ }
204+ return request;
205+ },
206+ //
207+ getDailyForecastRequest: function(params) {
208+ var request = {
209+ type: "daily",
210+ url: _baseUrl + "forecast/city/"+params.location.service_id+"?mode=daily_compact&units="+params.units,
211+ formatter: _buildDailyForecast
212+ }
213+ return request;
214+ }
215+ }
216+
217+})();
218+
219+var WeatherApi = (function(_services) {
220+ /**
221+ proxy for requesting weather apis, the passed _services are providing the respective api endpoints
222+ and formatters to build a uniform response object
223+ */
224+ function _getService(name) {
225+ if(_services[name] !== undefined) {
226+ return _services[name];
227+ }
228+ return _services["openweathermap"];
229+ }
230+ //
231+ function _sendRequest(request, onSuccess, onError) {
232+ var xmlHttp = new XMLHttpRequest();
233+ if (xmlHttp) {
234+ console.log(request.url);
235+ xmlHttp.open('GET', request.url, true);
236+ xmlHttp.onreadystatechange = function () {
237+ if (xmlHttp.readyState == 4) {
238+ var json = JSON.parse(xmlHttp.responseText);
239+ if(xmlHttp.status === 200) {
240+ var result = request.formatter(request,json);
241+ onSuccess(result);
242+ } else {
243+ onError({error: true, msg: "something went wrong", request: request});
244+ }
245+ }
246+ };
247+ xmlHttp.send(null);
248+ }
249+ }
250+ //
251+ return {
252+ getForecast: function(params, onSuccess, onError) {
253+ var request = _getService().getForecastRequest(params);
254+ _sendRequest(request, onSuccess, onError);
255+ },
256+ //
257+ getDailyForecast: function(params, onSuccess, onError) {
258+ var request = _getService().getDailyForecastRequest(params);
259+ _sendRequest(request, onSuccess, onError);
260+ },
261+ //
262+ getCurrentCondition: function(params, onSuccess, onError) {
263+ var request = _getService().getCurrentConditionRequest(params);
264+ _sendRequest(request, onSuccess, onError);
265+ },
266+ //
267+ searchLocationByName: function(params, onSuccess, onError) {
268+ var request = _getService().getSearchByNameRequest(params);
269+ _sendRequest(request, onSuccess, onError);
270+ },
271+ //
272+ searchLocationByPoint: function(params, onSuccess, onError) {
273+ var request = _getService().getSearchByPointRequest(params);
274+ _sendRequest(request, onSuccess, onError);
275+ },
276+ //
277+ getLocationData: function(params, onSuccess, onError) {
278+ var response = {
279+ location: params.location
280+ },
281+ addDataToResponse = (function(data) {
282+ response[data.request.type] = data;
283+ if(response["current"] !== undefined && response["forecast"] !== undefined && response["daily"] !== undefined) {
284+ onSuccess(response);
285+ }
286+ })
287+
288+ this.getCurrentCondition(params, addDataToResponse, addDataToResponse)
289+ this.getForecast(params, addDataToResponse, addDataToResponse);
290+ this.getDailyForecast(params, addDataToResponse, addDataToResponse);
291+ }
292+ }
293+})({
294+ "openweathermap": OpenWeatherMapApi
295+});
296
297=== modified file 'weather.qml'
298--- weather.qml 2013-03-14 21:46:34 +0000
299+++ weather.qml 2013-03-28 15:37:25 +0000
300@@ -1,6 +1,7 @@
301 import QtQuick 2.0
302 import Ubuntu.Components 0.1
303 import "components" as Components
304+import "components/WeatherApi.js" as Api
305
306 /*!
307 \brief MainView with Tabs element.
308@@ -15,6 +16,10 @@
309 width: units.gu(50)
310 height: units.gu(75)
311
312+ Components.Storage{
313+ id: storage
314+ }
315+
316 Tabs {
317 id: tabs
318 anchors.fill: parent

Subscribers

People subscribed via source and target branches