Added location class for location search results and added location search dialog
[situare] / src / routing / routingservice.cpp
1 /*
2    Situare - A location system for Facebook
3    Copyright (C) 2010  Ixonos Plc. Authors:
4
5       Henri Lampela - henri.lampela@ixonos.com
6       Sami Rämö - sami.ramo@ixonos.com
7
8    Situare is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License
10    version 2 as published by the Free Software Foundation.
11
12    Situare is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Situare; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
20    USA.
21 */
22
23 #include <QDebug>
24 #include <QtGlobal>
25 #include <QStringList>
26 #include <QNetworkReply>
27 #include <QPointF>
28 #include <QCryptographicHash>
29
30 #if defined(Q_WS_MAEMO_5) & defined(ARMEL)
31 #include <QDBusInterface>
32 #include <QDBusMessage>
33 #endif
34
35 #include "common.h"
36 #include "network/networkaccessmanager.h"
37 #include "parser.h"
38 #include "location.h"
39 #include "route.h"
40 #include "routesegment.h"
41
42 #include "routingservice.h"
43
44 // defines for IMEI retrieval via dbus
45 #define SIM_DBUS_SERVICE  "com.nokia.phone.SIM"
46 #define SIM_DBUS_PATH     "/com/nokia/phone/SIM/security"
47 #define SIM_DBUS_IFACE    "Phone.Sim.Security"
48 #define SIM_IMEI_REQ      "get_imei"
49
50 const int NO_ERROR = 0;
51
52 // cloudmade
53 const QString CLOUDMADE_AUTH_PART = "http://auth.cloudmade.com/token/";
54 const QString CLOUDMADE_API_KEY = "0cea0072adbf42ce823f4c78b974f12d";
55
56 // geocode
57 const QString GEOCODE = "http://maps.google.com/maps/api/geocode/json?address=";
58
59
60 RoutingService::RoutingService(QObject *parent)
61         : QObject(parent)
62 {
63     qDebug() << __PRETTY_FUNCTION__;
64
65     m_pendingRequest.clear();
66     m_token.clear();
67
68     m_networkManager = new NetworkAccessManager(this);
69     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)),
70             this, SLOT(requestFinished(QNetworkReply*)), Qt::QueuedConnection);
71
72 }
73
74 void RoutingService::parseRouteData(const QByteArray &jsonReply)
75 {
76     qDebug() << __PRETTY_FUNCTION__;
77
78     QJson::Parser parser;
79     bool ok;
80     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
81     if (!ok) {
82         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
83         return;
84     } else {
85         if(result.value("status").toInt() == NO_ERROR) {
86             QVariant routeSummary = result.value("route_summary");
87             QMap<QString, QVariant> routeSummaryMap = routeSummary.toMap();
88
89             Route route;
90             route.setEndPointName(routeSummaryMap["end_point"].toString());
91             route.setStartPointName(routeSummaryMap["start_point"].toString());
92             route.setTotalDistance(routeSummaryMap["total_distance"].toInt());
93             route.setTotalTime(routeSummaryMap["total_time"].toInt());
94
95             foreach(QVariant routeGeometry, result["route_geometry"].toList()) {
96                 QStringList list = routeGeometry.toStringList();
97                 route.appendGeometryPoint(QPointF(list.at(1).toDouble(), list.at(0).toDouble()));
98             }
99
100             foreach(QVariant routeInstructions, result["route_instructions"].toList()) {
101                 QStringList list = routeInstructions.toStringList();
102                 RouteSegment segment;
103                 segment.setInstruction(list.at(0));
104                 segment.setLength(list.at(1).toDouble());
105                 segment.setPositionIndex(list.at(2).toInt());
106                 segment.setTime(list.at(3).toInt());
107                 segment.setLengthCaption(list.at(4));
108                 segment.setEarthDirection(list.at(5));
109                 segment.setAzimuth(list.at(6).toDouble());
110                 if (list.count() == 8) {
111                     segment.setTurnType(list.at(7));
112                     segment.setTurnAngle(list.at(8).toDouble());
113                 }
114                 route.appendSegment(segment);
115             }
116
117             emit routeParsed(route);
118         }
119     }
120 }
121
122 void RoutingService::parseSearchResults(const QByteArray &jsonReply)
123 {
124     qDebug() << __PRETTY_FUNCTION__;
125
126     const QString NO_DATA = "ZERO_RESULTS";
127
128     m_searchResults.clear();
129
130     QJson::Parser parser;
131     bool ok;
132     QVariantMap result = parser.parse (jsonReply, &ok).toMap();
133     if (!ok) {
134         emit error(ErrorContext::SITUARE, SituareError::INVALID_JSON);
135         return;
136     } else {
137         if(result.value("status") != NO_DATA) {
138             foreach(QVariant hitVariant, result["results"].toList()) {
139                 Location location;
140                 QMap<QString, QVariant> hitMap = hitVariant.toMap();
141                 foreach(QVariant hitTypeVariant, hitMap["types"].toList()) {
142                     QStringList hitTypeList = hitTypeVariant.toStringList();
143                     foreach(QString hitType, hitTypeList) {
144                         if(hitType != "political")
145                             location.setLocationType(hitType);
146                     }
147                 }
148                 location.setFormattedAddress(hitMap["formatted_address"].toString());
149                 foreach(QVariant addrComponent, hitMap["address_components"].toList()) {
150                     QMap<QString, QVariant> addrComponentMap = addrComponent.toMap();
151                     foreach(QVariant addrType, addrComponentMap["types"].toList()) {
152                         QStringList addrTypeList = addrType.toStringList();
153                         foreach(QString type, addrTypeList) {
154                             if(type == "street_number")
155                                 location.setStreetNumber(addrComponentMap["long_name"].toInt());
156                             else if(type == "route")
157                                 location.setStreet(addrComponentMap["long_name"].toString());
158                             else if(type == "locality")
159                                 location.setLocality(addrComponentMap["long_name"].toString());
160                             else if(type == "country")
161                                 location.setCountry(addrComponentMap["long_name"].toString());
162                             else if(type == "postal_code")
163                                 location.setPostalCode(addrComponentMap["long_name"].toInt());
164                         }
165                     }
166                 }
167                 QVariant geometryVariant = hitMap["geometry"];
168                 QMap<QString, QVariant> geometryMap = geometryVariant.toMap();
169                 QVariant locationVariant = geometryMap["location"];
170                 QMap<QString, QVariant> locationMap = locationVariant.toMap();
171                 location.setCoordinates(locationMap["lat"].toDouble(),
172                                         locationMap["lng"].toDouble());
173
174                 m_searchResults.append(location);
175             }
176             emit locationDataParsed(m_searchResults);
177         }
178     }
179 }
180
181 void RoutingService::requestAuthorizationToken()
182 {
183     qDebug() << __PRETTY_FUNCTION__;
184
185     // create token request of format: http://auth.cloudmade.com/token/APIKEY?userid=UserID
186     // where APIKEY is our application specific key and userID is md5 hashed value of IMEI code
187     QString url = CLOUDMADE_AUTH_PART;
188     url.append(CLOUDMADE_API_KEY);
189     QString uidPart = "?userid=";
190     QByteArray rawData;
191
192 #ifdef Q_WS_MAEMO_5
193     // get the device IMEI code
194     QDBusInterface interface(SIM_DBUS_SERVICE, SIM_DBUS_PATH, SIM_DBUS_IFACE,
195                              QDBusConnection::systemBus());
196
197     QDBusMessage reply = interface.call(SIM_IMEI_REQ);
198     if (reply.type() == QDBusMessage::ErrorMessage)
199         qDebug() << reply.errorMessage();
200     else {
201         QList<QVariant> args = reply.arguments();
202         qDebug() << QString("Phone's IMEI: %1").arg(args.at(0).toString());
203         rawData = args.at(0).toByteArray();
204     }
205 #else
206     // for desktop we need to use login name
207     rawData.append(system("whoami"));
208 #endif
209
210     QCryptographicHash crypto(QCryptographicHash::Md5);
211     crypto.addData(rawData);
212     uidPart.append(crypto.result().toHex());
213
214     url.append(uidPart);
215     sendRequest(url);
216 }
217
218 void RoutingService::requestFinished(QNetworkReply *reply)
219 {
220     qDebug() << __PRETTY_FUNCTION__;
221
222     if (m_currentRequests.contains(reply)) {
223
224         QByteArray replyArray = reply->readAll();
225         qDebug() << "Reply from: " << reply->url() << "reply " << replyArray;
226
227         if (reply->error()) {
228             emit error(ErrorContext::NETWORK, reply->error());
229         } else if(reply->url().toString().contains(CLOUDMADE_AUTH_PART)) {
230             m_token = replyArray;
231             m_pendingRequest.append(m_token);
232             sendRequest(m_pendingRequest);
233             m_pendingRequest.clear();
234         } else if(reply->url().toString().contains(GEOCODE)) {
235             parseSearchResults(replyArray);
236         } else {
237             parseRouteData(replyArray);
238         }
239
240         m_currentRequests.removeAll(reply);
241         reply->deleteLater();
242     }
243 }
244
245 void RoutingService::requestLocation(QString location)
246 {
247     qDebug() << __PRETTY_FUNCTION__;
248
249     const QString urlEnd = "&sensor=false";
250
251     QString url = GEOCODE;
252     url.append(location);
253     url.append(urlEnd);
254
255     sendRequest(url);
256 }
257
258 void RoutingService::requestRoute(QPointF from, QPointF to)
259 {
260     qDebug() << __PRETTY_FUNCTION__;
261
262     QString url = "http://routes.cloudmade.com/";
263     url.append(CLOUDMADE_API_KEY);
264     url.append("/api/0.3/");
265     url.append(QString::number(from.x()) + "," + QString::number(from.y()) + ",");
266     url.append(QString::number(to.x()) + "," + QString::number(to.y()));
267     url.append("/car/fastest.js?lang=en&units=km&token=");
268
269     if(m_token.isEmpty()) {
270         m_pendingRequest.append(url);
271         requestAuthorizationToken();
272     } else {
273         url.append(m_token);
274         sendRequest(QUrl(url));
275     }
276 }
277
278 void RoutingService::sendRequest(const QUrl &url)
279 {
280     qDebug() << __PRETTY_FUNCTION__;
281
282     QNetworkRequest request;
283
284     request.setUrl(url);
285     request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false);
286
287     QByteArray ba;
288     QNetworkReply *reply = m_networkManager->post(request, ba, false);
289
290     m_currentRequests.append(reply);
291 }