delay a few things on startup, such as setting the visibility mode, which ensures...
[personal-kdebase.git] / workspace / plasma / dataengines / weather / ions / ion_noaa.cpp
bloba3515f334ba5e7e1dbe4d1e89c412de4185a8fb7
1 /***************************************************************************
2 * Copyright (C) 2007-2008 by Shawn Starr <shawn.starr@rogers.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA *
18 ***************************************************************************/
20 /* Ion for NOAA's National Weather Service XML data */
22 #include "ion_noaa.h"
24 class NOAAIon::Private : public QObject
26 public:
27 Private() {
28 m_url = 0;
30 ~Private() {
31 delete m_url;
34 private:
35 struct XMLMapInfo {
36 QString stateName;
37 QString stationName;
38 QString XMLurl;
39 QString sourceOptions;
42 public:
43 // Key dicts
44 QHash<QString, NOAAIon::Private::XMLMapInfo> m_place;
45 QHash<QString, QString> m_locations;
46 QString m_state;
47 QString m_station_name;
48 QString m_xmlurl;
50 // Weather information
51 QHash<QString, WeatherData> m_weatherData;
53 // Store KIO jobs
54 QMap<KJob *, QXmlStreamReader*> m_jobXml;
55 QMap<KJob *, QString> m_jobList;
56 QXmlStreamReader m_xmlSetup;
57 KUrl *m_url;
58 KIO::TransferJob *m_job;
60 QDateTime m_dateFormat;
63 QMap<QString, IonInterface::WindDirections> NOAAIon::setupWindIconMappings(void)
65 QMap<QString, WindDirections> windDir;
66 windDir["north"] = N;
67 windDir["northeast"] = NE;
68 windDir["south"] = S;
69 windDir["southwest"] = SW;
70 windDir["east"] = E;
71 windDir["southeast"] = SE;
72 windDir["west"] = W;
73 windDir["northwest"] = NW;
74 windDir["calm"] = VR;
75 return windDir;
78 QMap<QString, IonInterface::ConditionIcons> NOAAIon::setupConditionIconMappings(void)
81 QMap<QString, ConditionIcons> conditionList;
82 return conditionList;
85 QMap<QString, IonInterface::ConditionIcons> const& NOAAIon::conditionIcons(void)
87 static QMap<QString, ConditionIcons> const condval = setupConditionIconMappings();
88 return condval;
91 QMap<QString, IonInterface::WindDirections> const& NOAAIon::windIcons(void)
93 static QMap<QString, WindDirections> const wval = setupWindIconMappings();
94 return wval;
97 // ctor, dtor
98 NOAAIon::NOAAIon(QObject *parent, const QVariantList &args)
99 : IonInterface(parent, args), d(new Private())
101 Q_UNUSED(args)
104 NOAAIon::~NOAAIon()
106 // Destroy dptr
107 delete d;
110 // Get the master list of locations to be parsed
111 void NOAAIon::init()
113 // Get the real city XML URL so we can parse this
114 getXMLSetup();
117 QStringList NOAAIon::validate(const QString& source) const
119 QStringList placeList;
120 QHash<QString, QString>::const_iterator it = d->m_locations.constBegin();
121 while (it != d->m_locations.constEnd()) {
122 if (it.value().toLower().contains(source.toLower())) {
123 placeList.append(QString("place|%1").arg(it.value().split('|')[1]));
125 ++it;
128 // Check if placeList is empty if so, return nothing.
129 if (placeList.isEmpty()) {
130 return QStringList();
133 placeList.sort();
134 return placeList;
137 bool NOAAIon::updateIonSource(const QString& source)
139 // We expect the applet to send the source in the following tokenization:
140 // ionname:validate:place_name - Triggers validation of place
141 // ionname:weather:place_name - Triggers receiving weather of place
143 QStringList sourceAction = source.split('|');
145 // Guard: if the size of array is not 2 then we have bad data, return an error
146 if (sourceAction.size() < 2) {
147 setData(source, "validate", QString("noaa|timeout"));
148 return true;
151 if (sourceAction[1] == QString("validate")) {
152 kDebug() << "Initiate Validating of place: " << sourceAction[2];
153 QStringList result = validate(QString("%1|%2").arg(sourceAction[0]).arg(sourceAction[2]));
155 if (result.size() == 1) {
156 setData(source, "validate", QString("noaa|valid|single|%1").arg(result.join("|")));
157 return true;
158 } else if (result.size() > 1) {
159 setData(source, "validate", QString("noaa|valid|multiple|%1").arg(result.join("|")));
160 return true;
161 } else if (result.size() == 0) {
162 setData(source, "validate", QString("noaa|invalid|single|%1").arg(sourceAction[2]));
163 return true;
166 } else if (sourceAction[1] == QString("weather")) {
167 getXMLData(source);
168 return true;
170 return false;
173 // Parses city list and gets the correct city based on ID number
174 void NOAAIon::getXMLSetup()
176 d->m_url = new KUrl("http://www.weather.gov/data/current_obs/index.xml");
178 KIO::TransferJob *job = KIO::get(d->m_url->url(), KIO::NoReload, KIO::HideProgressInfo);
180 if (job) {
181 connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)), this,
182 SLOT(setup_slotDataArrived(KIO::Job *, const QByteArray &)));
183 connect(job, SIGNAL(result(KJob *)), this, SLOT(setup_slotJobFinished(KJob *)));
187 // Gets specific city XML data
188 void NOAAIon::getXMLData(const QString& source)
190 KUrl url;
192 QString dataKey = source;
193 dataKey.remove("|weather");
194 url = d->m_place[dataKey].XMLurl;
196 kDebug() << "URL Location: " << url.url();
198 // If this is empty we have no valid data, send out an error and abort.
200 if (url.url().isEmpty()) {
201 setData(source, "validate", QString("noaa|timeout"));
202 return;
205 d->m_job = KIO::get(url.url(), KIO::Reload, KIO::HideProgressInfo);
206 d->m_jobXml.insert(d->m_job, new QXmlStreamReader);
207 d->m_jobList.insert(d->m_job, source);
209 if (d->m_job) {
210 connect(d->m_job, SIGNAL(data(KIO::Job *, const QByteArray &)), this,
211 SLOT(slotDataArrived(KIO::Job *, const QByteArray &)));
212 connect(d->m_job, SIGNAL(result(KJob *)), this, SLOT(slotJobFinished(KJob *)));
216 void NOAAIon::setup_slotDataArrived(KIO::Job *job, const QByteArray &data)
218 Q_UNUSED(job)
220 if (data.isEmpty()) {
221 return;
224 // Send to xml.
225 d->m_xmlSetup.addData(data);
228 void NOAAIon::slotDataArrived(KIO::Job *job, const QByteArray &data)
230 if (data.isEmpty() || !d->m_jobXml.contains(job)) {
231 return;
234 // Send to xml.
235 d->m_jobXml[job]->addData(data);
238 void NOAAIon::slotJobFinished(KJob *job)
240 // Dual use method, if we're fetching location data to parse we need to do this first
241 setData(d->m_jobList[job], Data());
242 readXMLData(d->m_jobList[job], *d->m_jobXml[job]);
243 d->m_jobList.remove(job);
244 delete d->m_jobXml[job];
245 d->m_jobXml.remove(job);
248 void NOAAIon::setup_slotJobFinished(KJob *job)
250 Q_UNUSED(job)
251 readXMLSetup();
252 setInitialized(true);
255 void NOAAIon::parseStationID()
257 QString tmp;
258 while (!d->m_xmlSetup.atEnd()) {
259 d->m_xmlSetup.readNext();
261 if (d->m_xmlSetup.isEndElement() && d->m_xmlSetup.name() == "station") {
262 break;
265 if (d->m_xmlSetup.isStartElement()) {
266 if (d->m_xmlSetup.name() == "state") {
267 d->m_state = d->m_xmlSetup.readElementText();
268 } else if (d->m_xmlSetup.name() == "station_name") {
269 d->m_station_name = d->m_xmlSetup.readElementText();
270 } else if (d->m_xmlSetup.name() == "xml_url") {
271 d->m_xmlurl = d->m_xmlSetup.readElementText();
273 tmp = "noaa|" + d->m_station_name + ", " + d->m_state; // Build the key name.
274 d->m_place[tmp].stateName = d->m_state;
275 d->m_place[tmp].stationName = d->m_station_name;
276 d->m_place[tmp].XMLurl = d->m_xmlurl.replace("http://", "http://www.");
278 d->m_locations[tmp] = tmp;
279 } else {
280 parseUnknownElement(d->m_xmlSetup);
286 void NOAAIon::parseStationList()
288 while (!d->m_xmlSetup.atEnd()) {
289 d->m_xmlSetup.readNext();
291 if (d->m_xmlSetup.isEndElement()) {
292 break;
295 if (d->m_xmlSetup.isStartElement()) {
296 if (d->m_xmlSetup.name() == "station") {
297 parseStationID();
298 } else {
299 parseUnknownElement(d->m_xmlSetup);
305 // Parse the city list and store into a QMap
306 bool NOAAIon::readXMLSetup()
308 while (!d->m_xmlSetup.atEnd()) {
309 d->m_xmlSetup.readNext();
311 if (d->m_xmlSetup.isStartElement()) {
312 if (d->m_xmlSetup.name() == "wx_station_index") {
313 parseStationList();
317 return !d->m_xmlSetup.error();
320 WeatherData NOAAIon::parseWeatherSite(WeatherData& data, QXmlStreamReader& xml)
322 data.temperature_C = "N/A";
323 data.temperature_F = "N/A";
324 data.dewpoint_C = "N/A";
325 data.dewpoint_F = "N/A";
326 data.weather = "N/A";
327 data.stationID = "N/A";
328 data.pressure = "N/A";
329 data.visibility = "N/A";
330 data.humidity = "N/A";
331 data.windSpeed = "N/A";
332 data.windGust = "N/A";
333 data.windchill_F = "N/A";
334 data.windchill_C = "N/A";
335 data.heatindex_F = "N/A";
336 data.heatindex_C = "N/A";
338 while (!xml.atEnd()) {
339 xml.readNext();
341 if (xml.isStartElement()) {
342 if (xml.name() == "location") {
343 data.locationName = xml.readElementText();
344 } else if (xml.name() == "station_id") {
345 data.stationID = xml.readElementText();
346 } else if (xml.name() == "observation_time") {
347 data.observationTime = xml.readElementText();
348 QStringList tmpDateStr = data.observationTime.split(' ');
349 data.observationTime = QString("%1 %2").arg(tmpDateStr[5]).arg(tmpDateStr[6]);
350 d->m_dateFormat = QDateTime::fromString(data.observationTime, "h:mm ap");
351 data.iconPeriodHour = d->m_dateFormat.toString("HH");
352 data.iconPeriodAP = d->m_dateFormat.toString("ap");
354 } else if (xml.name() == "weather") {
355 data.weather = xml.readElementText();
356 // Pick which icon set depending on period of day
357 } else if (xml.name() == "temp_f") {
358 data.temperature_F = xml.readElementText();
359 } else if (xml.name() == "temp_c") {
360 data.temperature_C = xml.readElementText();
361 } else if (xml.name() == "relative_humidity") {
362 data.humidity = xml.readElementText();
363 } else if (xml.name() == "wind_dir") {
364 data.windDirection = xml.readElementText();
365 } else if (xml.name() == "wind_mph") {
366 data.windSpeed = xml.readElementText();
367 } else if (xml.name() == "wind_gust_mph") {
368 data.windGust = xml.readElementText();
369 } else if (xml.name() == "pressure_in") {
370 data.pressure = xml.readElementText();
371 } else if (xml.name() == "dewpoint_f") {
372 data.dewpoint_F = xml.readElementText();
373 } else if (xml.name() == "dewpoint_c") {
374 data.dewpoint_C = xml.readElementText();
375 } else if (xml.name() == "heat_index_f") {
376 data.heatindex_F = xml.readElementText();
377 } else if (xml.name() == "heat_index_c") {
378 data.heatindex_C = xml.readElementText();
379 } else if (xml.name() == "windchill_f") {
380 data.windchill_F = xml.readElementText();
381 } else if (xml.name() == "windchill_c") {
382 data.windchill_C = xml.readElementText();
383 } else if (xml.name() == "visibility_mi") {
384 data.visibility = xml.readElementText();
385 } else {
386 parseUnknownElement(xml);
390 return data;
393 // Parse Weather data main loop, from here we have to decend into each tag pair
394 bool NOAAIon::readXMLData(const QString& source, QXmlStreamReader& xml)
396 WeatherData data;
398 while (!xml.atEnd()) {
399 xml.readNext();
401 if (xml.isEndElement()) {
402 break;
405 if (xml.isStartElement()) {
406 if (xml.name() == "current_observation") {
407 data = parseWeatherSite(data, xml);
408 } else {
409 parseUnknownElement(xml);
414 d->m_weatherData[source] = data;
415 updateWeather(source);
416 return !xml.error();
419 // handle when no XML tag is found
420 void NOAAIon::parseUnknownElement(QXmlStreamReader& xml)
423 while (!xml.atEnd()) {
424 xml.readNext();
426 if (xml.isEndElement()) {
427 break;
430 if (xml.isStartElement()) {
431 parseUnknownElement(xml);
436 void NOAAIon::updateWeather(const QString& source)
438 QMap<QString, QString> dataFields;
439 QStringList fieldList;
441 setData(source, "Country", country(source));
442 setData(source, "Place", place(source));
443 setData(source, "Station", station(source));
445 // Real weather - Current conditions
446 setData(source, "Observation Period", observationTime(source));
447 setData(source, "Current Conditions", condition(source));
449 // FIXME: We'll need major fuzzy logic, this isn't pretty: http://www.weather.gov/xml/current_obs/weather.php
450 //QMap<QString, ConditionIcons> conditionList;
451 //conditionList = conditionIcons();
454 if ((periodHour(source) >= 0 && periodHour(source) < 6) || (periodHour(source) >= 18)) {
455 // Night
456 // - Fill in condition fuzzy logic
457 } else {
458 // Day
459 // - Fill in condition fuzzy logic
462 setData(source, "Condition Icon", "weather-none-available");
464 dataFields = temperature(source);
465 setData(source, "Temperature", dataFields["temperature"]);
467 if (dataFields["temperature"] != "N/A") {
468 setData(source, "Temperature Unit", dataFields["temperatureUnit"]);
471 // Do we have a comfort temperature? if so display it
472 if (dataFields["comfortTemperature"] != "N/A") {
473 if (d->m_weatherData[source].windchill_F != "NA") {
474 setData(source, "Windchill", QString("%1").arg(dataFields["comfortTemperature"]));
475 setData(source, "Humidex", "N/A");
477 if (d->m_weatherData[source].heatindex_F != "NA" && d->m_weatherData[source].temperature_F.toInt() != d->m_weatherData[source].heatindex_F.toInt()) {
478 setData(source, "Humidex", QString("%1").arg(dataFields["comfortTemperature"]));
479 setData(source, "Windchill", "N/A");
481 } else {
482 setData(source, "Windchill", "N/A");
483 setData(source, "Humidex", "N/A");
486 setData(source, "Dewpoint", dewpoint(source));
487 if (dewpoint(source) != "N/A") {
488 setData(source, "Dewpoint Unit", dataFields["temperatureUnit"]);
491 dataFields = pressure(source);
492 setData(source, "Pressure", dataFields["pressure"]);
494 if (dataFields["pressure"] != "N/A") {
495 setData(source, "Pressure Unit", dataFields["pressureUnit"]);
498 dataFields = visibility(source);
499 setData(source, "Visibility", dataFields["visibility"]);
501 if (dataFields["visibility"] != "N/A") {
502 setData(source, "Visibility Unit", dataFields["visibilityUnit"]);
505 setData(source, "Humidity", humidity(source));
507 // Set number of forecasts per day/night supported, none for this ion right now
508 setData(source, QString("Total Weather Days"), 0);
510 dataFields = wind(source);
511 setData(source, "Wind Speed", dataFields["windSpeed"]);
513 if (dataFields["windSpeed"] != "Calm") {
514 setData(source, "Wind Speed Unit", dataFields["windUnit"]);
517 setData(source, "Wind Gust", dataFields["windGust"]);
518 setData(source, "Wind Gust Unit", dataFields["windGustUnit"]);
519 setData(source, "Wind Direction", getWindDirectionIcon(windIcons(), dataFields["windDirection"].toLower()));
520 setData(source, "Credit", "Data provided by NOAA National Weather Service");
523 QString NOAAIon::country(const QString& source)
525 Q_UNUSED(source);
526 return QString("USA");
528 QString NOAAIon::place(const QString& source)
530 return d->m_weatherData[source].locationName;
532 QString NOAAIon::station(const QString& source)
534 return d->m_weatherData[source].stationID;
537 QString NOAAIon::observationTime(const QString& source)
539 return d->m_weatherData[source].observationTime;
543 bool NOAAIon::night(const QString& source)
545 if (d->m_weatherData[source].iconPeriodAP == "pm") {
546 return true;
548 return false;
552 int NOAAIon::periodHour(const QString& source)
554 return d->m_weatherData[source].iconPeriodHour.toInt();
557 QString NOAAIon::condition(const QString& source)
559 if (d->m_weatherData[source].weather.isEmpty() || d->m_weatherData[source].weather == "NA") {
560 d->m_weatherData[source].weather = "N/A";
562 return d->m_weatherData[source].weather;
565 QString NOAAIon::dewpoint(const QString& source)
567 return d->m_weatherData[source].dewpoint_F;
570 QString NOAAIon::humidity(const QString& source)
572 if (d->m_weatherData[source].humidity == "NA") {
573 return QString("N/A");
574 } else {
575 return QString("%1%").arg(d->m_weatherData[source].humidity);
579 QMap<QString, QString> NOAAIon::visibility(const QString& source)
581 QMap<QString, QString> visibilityInfo;
582 if (d->m_weatherData[source].visibility.isEmpty()) {
583 visibilityInfo.insert("visibility", QString("N/A"));
584 return visibilityInfo;
586 visibilityInfo.insert("visibility", d->m_weatherData[source].visibility);
587 visibilityInfo.insert("visibilityUnit", QString::number(WeatherUtils::Miles));
588 return visibilityInfo;
591 QMap<QString, QString> NOAAIon::temperature(const QString& source)
593 QMap<QString, QString> temperatureInfo;
594 temperatureInfo.insert("temperature", d->m_weatherData[source].temperature_F);
595 temperatureInfo.insert("temperatureUnit", QString::number(WeatherUtils::Fahrenheit));
596 temperatureInfo.insert("comfortTemperature", "N/A");
598 if (d->m_weatherData[source].heatindex_F != "NA" && d->m_weatherData[source].windchill_F == "NA") {
599 temperatureInfo.insert("comfortTemperature", d->m_weatherData[source].heatindex_F);
602 if (d->m_weatherData[source].windchill_F != "NA" && d->m_weatherData[source].heatindex_F == "NA") {
603 temperatureInfo.insert("comfortTemperature", d->m_weatherData[source].windchill_F);
606 return temperatureInfo;
609 QMap<QString, QString> NOAAIon::pressure(const QString& source)
611 QMap<QString, QString> pressureInfo;
612 if (d->m_weatherData[source].pressure.isEmpty()) {
613 pressureInfo.insert("pressure", "N/A");
614 return pressureInfo;
617 pressureInfo.insert("pressure", d->m_weatherData[source].pressure);
618 pressureInfo.insert("pressureUnit", QString::number(WeatherUtils::InchesHG));
619 return pressureInfo;
622 QMap<QString, QString> NOAAIon::wind(const QString& source)
624 QMap<QString, QString> windInfo;
626 // May not have any winds
627 if (d->m_weatherData[source].windSpeed == "NA") {
628 windInfo.insert("windSpeed", "Calm");
629 windInfo.insert("windUnit", QString::number(WeatherUtils::NoUnit));
630 } else {
631 windInfo.insert("windSpeed", QString::number(d->m_weatherData[source].windSpeed.toFloat(), 'f', 1));
632 windInfo.insert("windUnit", QString::number(WeatherUtils::MilesAnHour));
635 // May not always have gusty winds
636 if (d->m_weatherData[source].windGust == "NA") {
637 windInfo.insert("windGust", "N/A");
638 windInfo.insert("windGustUnit", QString::number(WeatherUtils::NoUnit));
639 } else {
640 windInfo.insert("windGust", QString::number(d->m_weatherData[source].windGust.toFloat(), 'f', 1));
641 windInfo.insert("windGustUnit", QString::number(WeatherUtils::MilesAnHour));
644 if (d->m_weatherData[source].windDirection.isEmpty()) {
645 windInfo.insert("windDirection", "N/A");
646 } else {
647 windInfo.insert("windDirection", d->m_weatherData[source].windDirection);
649 return windInfo;
652 #include "ion_noaa.moc"