1 /***************************************************************************
2 * Copyright (C) 2007-2008 by Shawn Starr <shawn.starr@rogers.com> *
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. *
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. *
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 */
24 class NOAAIon::Private
: public QObject
39 QString sourceOptions
;
44 QHash
<QString
, NOAAIon::Private::XMLMapInfo
> m_place
;
45 QHash
<QString
, QString
> m_locations
;
47 QString m_station_name
;
50 // Weather information
51 QHash
<QString
, WeatherData
> m_weatherData
;
54 QMap
<KJob
*, QXmlStreamReader
*> m_jobXml
;
55 QMap
<KJob
*, QString
> m_jobList
;
56 QXmlStreamReader m_xmlSetup
;
58 KIO::TransferJob
*m_job
;
60 QDateTime m_dateFormat
;
63 QMap
<QString
, IonInterface::WindDirections
> NOAAIon::setupWindIconMappings(void)
65 QMap
<QString
, WindDirections
> windDir
;
67 windDir
["northeast"] = NE
;
69 windDir
["southwest"] = SW
;
71 windDir
["southeast"] = SE
;
73 windDir
["northwest"] = NW
;
78 QMap
<QString
, IonInterface::ConditionIcons
> NOAAIon::setupConditionIconMappings(void)
81 QMap
<QString
, ConditionIcons
> conditionList
;
85 QMap
<QString
, IonInterface::ConditionIcons
> const& NOAAIon::conditionIcons(void)
87 static QMap
<QString
, ConditionIcons
> const condval
= setupConditionIconMappings();
91 QMap
<QString
, IonInterface::WindDirections
> const& NOAAIon::windIcons(void)
93 static QMap
<QString
, WindDirections
> const wval
= setupWindIconMappings();
98 NOAAIon::NOAAIon(QObject
*parent
, const QVariantList
&args
)
99 : IonInterface(parent
, args
), d(new Private())
110 // Get the master list of locations to be parsed
113 // Get the real city XML URL so we can parse this
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]));
128 // Check if placeList is empty if so, return nothing.
129 if (placeList
.isEmpty()) {
130 return QStringList();
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"));
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("|")));
158 } else if (result
.size() > 1) {
159 setData(source
, "validate", QString("noaa|valid|multiple|%1").arg(result
.join("|")));
161 } else if (result
.size() == 0) {
162 setData(source
, "validate", QString("noaa|invalid|single|%1").arg(sourceAction
[2]));
166 } else if (sourceAction
[1] == QString("weather")) {
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
);
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
)
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"));
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
);
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
)
220 if (data
.isEmpty()) {
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
)) {
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
)
252 setInitialized(true);
255 void NOAAIon::parseStationID()
258 while (!d
->m_xmlSetup
.atEnd()) {
259 d
->m_xmlSetup
.readNext();
261 if (d
->m_xmlSetup
.isEndElement() && d
->m_xmlSetup
.name() == "station") {
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
;
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()) {
295 if (d
->m_xmlSetup
.isStartElement()) {
296 if (d
->m_xmlSetup
.name() == "station") {
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") {
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()) {
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();
386 parseUnknownElement(xml
);
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
)
398 while (!xml
.atEnd()) {
401 if (xml
.isEndElement()) {
405 if (xml
.isStartElement()) {
406 if (xml
.name() == "current_observation") {
407 data
= parseWeatherSite(data
, xml
);
409 parseUnknownElement(xml
);
414 d
->m_weatherData
[source
] = data
;
415 updateWeather(source
);
419 // handle when no XML tag is found
420 void NOAAIon::parseUnknownElement(QXmlStreamReader
& xml
)
423 while (!xml
.atEnd()) {
426 if (xml
.isEndElement()) {
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)) {
456 // - Fill in condition fuzzy logic
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");
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
)
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") {
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");
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");
617 pressureInfo
.insert("pressure", d
->m_weatherData
[source
].pressure
);
618 pressureInfo
.insert("pressureUnit", QString::number(WeatherUtils::InchesHG
));
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
));
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
));
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");
647 windInfo
.insert("windDirection", d
->m_weatherData
[source
].windDirection
);
652 #include "ion_noaa.moc"