Keith Vetter 2007-01-23 : The National Oceanic and Atmospheric Administration (NOAA) has a some nice web services providing current weather conditions and forecasts. For details on some of those services, check out [1] and [2].Here's a little program that gets the weather forecast for a given latitude and longitude. It parses the SOAP reply and displays the result.KPV 2007-02-03 : Added some more features including graphing predicted temperatures (using tklib's PlotChart), a few built-in cities and more robust XML handling.KPV 2011-09-29 : Updated NOAA's url

PDH 2007-02-15 Corrected tempeture to temperature on line 44, and (pedantically) Januayr to January on line 4. This is an impressive app that really needs a screenshot to show it off. That said, I don't understand why the forecast days scroll horizontally instead of vertically, but that's easily corrected. Replace lines 359-362 with these:
if {[incr row] > 1} {
incr col
set row 0
}This is the app I would have written had I the proper mojo.KPV 2009-08-27 -- vertical is better but you then have to figure out the correct row to start in because day 1 may have only one entry.S_M 2007-07-04 I also like and use this application, at first I did not understand the temperature ranges for the day and night. Replacing the line 350 (set txt "$WEATHER($key3,$id2,temp,minimum)\xB0 -...) with:
if {[regexp -nocase "night" $WEATHER($key,$id,name)]} {
set txt "Low $WEATHER($key3,$id2,temp,minimum)\xB0"
} else {
set txt "High $WEATHER($key2,$id2,temp,maximum)\xB0"
}will make it more similar to the forecast on the NOAA page.spacecowboy - 2009-08-21 00:20:47I have messed with this ndfdXML.htm file for HOURS... I finally had to drop a copy of nusoap.php in the same folder as the aforementioned file and the ndfdXMLclient.php file... the error "Parse error: syntax error, unexpected T_REQUIRE_ONCE in C:\Inetpub\vhosts\worldnewsvine.com\httpdocs\nws\ndfdSOAPclientByDay.php on line 55" finally disappeared by simply changing the runonce statement to 'nusoap.php'Now I am back to another error I was getting which is:Warning: Cannot modify header information - headers already sent by (output started at C:\Inetpub\vhosts\worldnewsvine.com\httpdocs\nws\ndfdXMLclient.php:1) in C:\Inetpub\vhosts\worldnewsvine.com\httpdocs\nws\ndfdXMLclient.php on line 111
(which is this line of code: header("Content-Type: text/xml");... what is it supposed to be? // Send the appropriate mime type for XML isn't text/xml correct?And to finish that all off, the error continues with:ERROR HTTP Error: Unsupported HTTP response status 404 Not Found (soapclient->response has contents of the response)Okay.. I am not professing to be a programmer but one would think that this would be easier to figure-out than this...Right now I am just using simplepie to fetch the rss feed however, I would love to get this mapping function/application running...Please any help, in plain ole english.... thanksKPV 2009-08-27 - huh? what are ndfdXML.htm, nusoap.php and ndfdXMLclient.php? Are you really running this app or some other php one?KPV 2009-08-27 - while trying to figure out the above error, I decided to replace everything with the more current code on my machine. Some of the changes include caching icon images (turned off for demoing); noon markers on temperature graph; the two suggestions from above; etc.
##+##########################################################################
#
# noaa.tcl -- Displays weather forecast from NOAA
# by Keith Vetter, January 2007
# 2017-02-22: https protocol
#
package require Tk
package require http
package require tdom
package require Img
package require Plotchart
package require tile
namespace import -force ::ttk::button
package require tooltip
package require tls
http::register https 443 [list ::tls::socket -tls1 1] ;# "-tls1 1" is required since [POODLE]
set S(noCache) 0
set S(mustFetch) 1
set S(iconDir) ~/bin/noaaIcons
set S(x,axisStep) 24
set S(box,size) 3
# see http://www.nws.noaa.gov/xml/
# http://www.weather.gov/forecasts/xml/SOAP_server/ndfdSOAPByDay.htm
set S(url,forecast) https://graphical.weather.gov/xml/SOAP_server/ndfdSOAPclientByDay.php
set S(url,temp) https://graphical.weather.gov/xml/SOAP_server/ndfdXMLclient.php
set S(url,temp,parameters) {?lat=${LAT}&lon=${LON}&product=time-series&begin=${BEGINDATE}T00%3A00%3A00&end=${ENDDATE}T00%3A00%3A00&temp=temp&Submit=Submit}
# Both forecast and current conditions
# http://forecast.weather.gov/MapClick.php?lat=37.4411&lon=-122.1203&unit=0&lg=english&FcstType=dwml
set S(format) 12+hourly
set S(days) 8
set COLORS {lightblue violet} ;# Temperature day's columns
set COLORS {\#82eeee \#ee82ee \#eeee82 \#8282ee \#82ee82 \#ee8282}
set COLORS {lightblue}
array set CITIES {
"Boston, MA" "42.35 -71.066666"
"Boulder, CO" "40.27 -105.252"
"Chicago, IL" "41.8675 -87.6243"
"Denver, CO" "39.75 -104.98"
"Granville, OH" "40.068088 -82.517967"
"Honolulu, HI" "21.31 -157.83"
"Leland, MI" "45.024361 -85.762431"
"Los Angeles, CA" "34.054 -118.245"
"Mt View, CA" "37.392778 -122.041944"
"New York, NY" "40.7563 -73.9865"
"Palmer, AK" "61.6019 -149.1172"
"Providence, RI" "41.82355 -71.422132"
"San Francisco, CA" "37.77 -122.43"
"Washington, DC" "38.9136 -77.0132"
"Woods Hole, MA" "41.52645 -70.6545"
}
proc Submit {who} {
set ll [PrettyLat $::S(lat) $::S(lon)]
if {$who eq "temperature"} {
set ::S(msg) "Fetching NOAA temperature forecast"
set n [GetNOAATemp $::S(lat) $::S(lon)]
if {$n} {
set ::S(msg) "NOAA temperature forecast for $ll"
GetPlotData
PlotTemp
} else { set ::S(msg) "error fetching NOAA temperature forecast" }
} else {
set ::S(msg) "Fetching NOAA weather forecast"
set n [GetNOAA $::S(lat) $::S(lon)]
if {$n} {
set ::S(msg) "NOAA weather forecast for $ll"
DisplayWeather
} else { set ::S(msg) "error fetching NOAA weather forecast" }
}
}
proc GetNOAA {lat lon {XML ""}} {
global doc root xml
if {$XML ne ""} {
set xml $XML
} else {
set xml [GetForecastXML $lat $lon]
}
set n [catch {dom parse $xml doc}]
if {$n} {
tk_messageBox -icon error -message "Bad reply from NOAA"
return 0
}
set root [$doc documentElement]
ReadTimeLayouts $root
GetIcons $root
GetTemperatures $root
GetPrecipitation $root
GetWeather $root
unset doc
return 1
}
proc GetForecastXML {lat lon} {
set startdate [clock format [clock scan now] -format "%Y-%m-%d"]
set url $::S(url,forecast)
append url "?lat=$lat&lon=$lon&format=$::S(format)&startDate=$startdate"
append url "&numDays=$::S(days)&Submit=Submit"
set ::URL $url
set token [::http::geturl $url]
set ncode [::http::ncode $token]
set xml [::http::data $token] ; list
::http::cleanup $token
return $xml
}
proc GetNOAATemp {lat lon {XML ""}} {
global doc root xml
if {$XML ne ""} {
set xml $XML
} else {
set xml [GetTempForecastXML $lat $lon]
}
set n [catch {dom parse $xml doc}]
if {$n} {
tk_messageBox -icon error -message "Bad reply from NOAA"
return 0
}
set root [$doc documentElement]
ReadTimeLayouts $root
GetTemperatures $root
unset doc
return 1
}
proc GetTempForecastXML {lat lon} {
global S url
if {! [string is double $lat] || ! [string is double $lon]} {
error "Bad latitude or longitude ($lat,$lon)"
return
}
set LAT $lat
set LON $lon
set BEGINDATE [clock format [clock scan now] -format "%Y-%m-%d"]
set ENDDATE [clock format [clock scan "now + $S(days) days"] \
-format "%Y-%m-%d"]
set params [subst -nobackslashes -nocommands $S(url,temp,parameters)]
set url "$S(url,temp)$params"
set token [::http::geturl $url]
::http::wait $token
set xml [::http::data $token] ; list
::http::cleanup $token
return $xml
}
proc ReadTimeLayouts {root} {
global WEATHER
unset -nocomplain WEATHER
# <time-layout summarization='12hourly'>
# <layout-key>KEY</layout-key>
# <start-valid-time period-name='NAME'>...</start-valid-time>
# <end-valid-time>...</end-valid-time>
# <start-valid-time>...</start-valid-time>
# <end-valid-time>...</end-valid-time>
set nodes [$root selectNodes /dwml/data/time-layout]
foreach node $nodes {
set key [[$node selectNodes layout-key/text()] data]
set WEATHER($key,summary) [$node getAttribute summarization "???"]
set starts [$node selectNodes start-valid-time]
set ends [$node selectNodes end-valid-time]
set cnt -1
foreach start $starts end $ends {
incr cnt
set name ""
set etime ""
if {[$start hasAttribute period-name]} {
set name [$start getAttribute period-name "???"]
}
set stime [[$start firstChild] data]
if {$end ne ""} {
set etime [[$end firstChild] data]
}
set WEATHER($key,$cnt,name) $name
set WEATHER($key,$cnt,start) $stime
set WEATHER($key,$cnt,end) $etime
}
}
}
proc GetIcons {root} {
set node [$root selectNodes /dwml/data/parameters/conditions-icon]
set key [$node getAttribute time-layout]
set ::WEATHER(icon,key) $key
set nodes [$node selectNodes icon-link]
for {set cnt 0} {$cnt < [llength $nodes]} {incr cnt} {
set url ""
set inode [lindex $nodes $cnt]
if {[$inode hasChildNodes]} {
set url [[$inode firstChild] data]
}
set ::WEATHER($key,$cnt,icon) $url
}
}
proc GetTemperatures {root} {
global WEATHER
array unset WEATHER *temp*
set nodes [$root selectNodes /dwml/data/parameters/temperature]
foreach node $nodes {
set type [$node getAttribute type]
set units [$node getAttribute units]
set key [$node getAttribute time-layout]
set WEATHER(temp,$type,key) $key
set WEATHER(temp,$type,units) $units
set vnodes [$node selectNodes value]
for {set cnt 0} {$cnt < [llength $vnodes]} {incr cnt} {
set vnode [lindex $vnodes $cnt]
set temp "?"
if {[$vnode hasChildNodes]} {
set temp [[$vnode firstChild] data]
}
set WEATHER($key,$cnt,temp,$type) $temp
}
}
}
proc GetPrecipitation {root} {
global WEATHER
array unset WEATHER *rain*
set node [$root selectNodes \
/dwml/data/parameters/probability-of-precipitation]
set units [$node getAttribute units]
set key [$node getAttribute time-layout]
set WEATHER(rain,key) $key
set WEATHER(rain,units) $units
set vnodes [$node selectNodes value]
for {set cnt 0} {$cnt < [llength $vnodes]} {incr cnt} {
set vnode [lindex $vnodes $cnt]
set rain "?"
if {[$vnode hasChildNodes]} {
set rain [[$vnode firstChild] data]
}
set WEATHER($key,$cnt,rain) $rain
}
}
proc GetWeather {root} {
global WEATHER
array unset WEATHER *weather*
set node [$root selectNodes /dwml/data/parameters/weather]
set key [$node getAttribute time-layout]
set WEATHER(weather,key) $key
set cnt -1
foreach value [$node selectNodes weather-conditions] {
incr cnt
set WEATHER($key,$cnt,weather,summary) \
[$value getAttribute weather-summary "?"]
}
}
proc DoDisplay {} {
wm title . "NOAA Weather Forecast"
bind all <F2> {console show}
frame .w -bd 2 -relief ridge
frame .ctrl -bd 2 -relief ridge -pady 5 -padx 30
label .msg -bd 2 -relief ridge -padx 30 -textvariable S(msg)
pack .msg -side bottom -fill x
pack .ctrl -side bottom -fill x
pack .w -side top -fill both -expand 1
set cities [lsort [array names ::CITIES]]
::ttk::combobox .ctrl.cb -values $cities -state readonly \
-textvariable ::S(city) -validatecommand {SetCity %P} -validate all
label .ctrl.llat -text "Latitude" -anchor w
entry .ctrl.elat -textvariable ::S(lat) -width 12 \
-validate key -vcmd {string is double %P}
label .ctrl.llon -text "Longitude" -anchor w
entry .ctrl.elon -textvariable ::S(lon) -width 12 \
-validate key -vcmd {string is double %P}
label .ctrl.ldays -text "Days" -anchor w
spinbox .ctrl.sbox -from 1 -to 10 -textvariable ::S(days) -width 7 \
-justify c -state readonly
.ctrl.sbox config -readonlybackground [.ctrl.sbox cget -bg]
frame .buttons
button .forecast -text "Forecast" -command {Submit forecast}
button .temp -text "Temperatures" -command {Submit temperature}
grid x .ctrl.cb - x .buttons -pady {0 5} -sticky news
grid x .ctrl.llat .ctrl.elat x ^ -sticky ew
grid x .ctrl.llon .ctrl.elon x ^ -sticky ew
grid x .ctrl.ldays .ctrl.sbox x ^ -sticky ew -pady {5 0}
grid columnconfigure .ctrl 3 -minsize 30
grid columnconfigure .ctrl 0 -weight 1
grid columnconfigure .ctrl 100 -weight 1
grid .forecast -in .buttons -sticky ew
grid .temp -in .buttons -sticky ew
grid rowconfigure .buttons {0 1} -weight 1
eval destroy [winfo child .w]
label .w.icon -image ::img::noaa
label .w.title1 -text "NOAA" -font {Times 32 bold}
label .w.title2 -text "Weather Forecast" -font {Times 28 bold}
#grid .w.icon .w.title1
#grid .w.title2 - -sticky ew -padx 10
#grid config .w.icon -padx {30 0}
#grid columnconfigure .w 1 -weight 1
pack .w.title2 -side bottom -padx 10
pack .w.icon -side left -padx {30 0}
pack .w.title1 -side left -expand 1
}
proc SetCity {where} {
global CITIES S
foreach {S(lat) S(lon)} $CITIES($where) break
return 1
}
image create photo ::img::noaa -data {
R0lGODlhNwA6ALMAACQybBSKtIzW9Eyy1DRGfHSCrJzy/ASe1FRilPwCBIyexPT+/Jy63CSaxMTa
9CxKlCH5BAEAAAkALAAAAAA3ADoAAwT/MMlJq7046827/2AojmRpnmiqrmzrvqXDMEpdKIwDazJC
/IBg8IfI7SgOxSP4IDyeUCAAodDtlAAndMtdAh6MlwPR7Jq3vwfCqmL4tOd4k1BsI75mePQ8DZ/G
eFAIZFmDRG9qZVBZbCRugXQODgtFC5QKlgUFCzKBAAVGJZYLBV9OCJQMBWMzqjg0VU5OOaOilkk/
kQQ0rFWTM5wOc5ijCyTFmUQLDw6rRTiv0A4EU8gyI8iZU6ygkqCaMwVrPwzIAw2NHdm3dz5kdO2n
dHSf1g0BAiHA2dPU/llPZD3JsquYgHMBBoAw0KBSNgWm5ATcVGxAgAMHGhj4wDBAQgPZ/+5IXEJg
kiUD5xqo1MixAcYGDEAWeyQnCDFLKV+y9GDg4gGPMZEx8Ndl3pqTCDFm3Nmhp8ufDQZkGwqQCzVi
KAM81bmRp1alGQXIHDWG6Jx6Cw76BKuya9OtSj9aU/BGCEUGHsEqbQtCpd6lAvipohJTwD2/f/l+
OPzXI8yx6wxIFvD3pcIQXysffuUAsoGDAypjDOCnL1y9HgNQMWmAQcrTbBuMsCga4wCxJw8ehg02
4QjDtQ+InZw0+N7SIWgHv5fZ+OjLJBCL5r0cpgngzrO/RB6duvbYKpR/r+x7hfjxe6GvWOlds1b1
LNSuNe7x9pHWFpvrZW79yITPr/G3kh19/l0g2WcCJChZgQw26OCDEEYo4YQUVmjhhSxEAAA7}
image create photo ::img::noaaLogo -data {
/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAkGBwgHBgkIBwgKCgkLDRYPDQwMDRsUFRAWIB0iIiAd
Hx8kKDQsJCYxJx8fLT0tMTU3Ojo6LCs/RD84Qyk5OjgBCgoKDg0OGxAQGjQmICY0Ly84MDc3NzY0
Ly8vLDcsLDcxLzA0LzUsLDQsNCw0NDc0LDQ0LCwsLC80NCw3NCwvNP/AABEIADIAMgMBEQACEQED
EQH/xAAbAAACAwEBAQAAAAAAAAAAAAAGBwAEBQMIAf/EADIQAAIBAwIEBAUCBwEAAAAAAAECAwAE
BQYREiExQQcTYXEiUYGhwWKRMkNTgpKx4RT/xAAaAQACAwEBAAAAAAAAAAAAAAAABQIDBAEG/8QA
KhEAAgICAQIFAwUBAAAAAAAAAQIAAwQRIRIxBRNBUfBxgdEyQmGRoSL/2gAMAwEAAhEDEQA/AHjR
CAmtPEmxwEr2OPjW9v15OOLaOI/Jj3PoP3FaqcYvyeBMt2UqcDkxZ5DxD1TfOWOTa3XsluioB9ev
3rYuNWPSYWyrD6zha681TauHTM3D7dpQrg/uK6ces+k4MmwesPdKeLEdxKlrqOFLdm5C7i34N/1L
29+ntWW3EI5Sa6swHh40EZXQOjBlYbgg7gisU3T7RCVMrbXF5jri2tLtrOaVCq3CrxGPfuBUlIB2
RIsCRoGeeM3pe+wGet7DKJxJPKoSZCeGVSwBIPz58x1FNktDrtYoelkfTQqfSmH/APWpgxs8imYw
GATudgLvyTJuOf8ADz+QNUea+uT81uaRShbt83qc4tJ4R47ZOElwYmlPnPuyvHM3xctgN4xtw89t
675r8/PaQFKaHz3mDdaZN9qq3xODUFbi3hmBLMUQNGrM25G/CN+436DrVot6ULNKjT1WdK/OI8tM
YdNOYa2xhvZbngJCvMQOfXhUdhyOw50ssfrYtqNK1FahdzYquWQB1BrK7wurntyBLYIiCSLYbjcb
llPz59KY04i2U79YmyfEGpyen9vE3tQ4mw1npwxRyowkXzLW4X+W/Y/gj3rIjNS/MZMEvr2DPPeR
tr/E5CazvfNhuYWKuvEfse4PXfvTVSrDYilgyHRlQSSDpI49mNS1I7MbXhDYJisTfakyr+VFKoih
eT+mvUj3OwA9Kw5JLsK1m/G1Whsc6Eq3+rLjK6qsLtOKO1t7hRDFv2J2JPqRWtMUJUV9SIqszmty
FYdgY36Sz0sT/iZbtDqh5CPhnhR1PsOE/wCqdYLbq17TzHiqav37gfiZ2nNS3+n5ibZhJbsd5Ldz
8Leo+R9atux0tHPf3lGLmWY547e0JczdaV11FFHeC5ssmBwxSJCXcfp+EHiX32+lYRRdRyORHAzM
fIAB2D9PxM1PDPEYVxdaiziPADukATyvM9DzLfQc6BkPZwiybUV1f9WNM3U+WfITxwRXccljAoWC
GCFoo4wOQAU+netuPUEG9cxNmZBtbXVsfxwJTwNs13m7C3Qbl7hP233P23qy1umsn+JTjIXtUD3E
flednsoKeIWAfM4tZ7VOK7td2VR1dT1X35bj/tbMO/y30exi7xHFN1e17iJ+nU8vNKwzuTx0DQWN
0YEbqUReL/Lbf71W9KOdsNzRXlW1jSHUo3E81zKZbmWSWRuryMWJ+pqYUKNCUs7MdsdznXZGMTww
0+4kOaukKrsUtgR136t+B9aW59415Y+8e+FYpB85vtGPSuPJKIQP1ToW1y8j3dg62t23Nht8Eh+Z
HY+orbRmNWOluRFmX4alx6k4P+GAd5o3P2jlWx7yjs0JDg/n7UxXLqb1iZ/D8hD+nf0nK30nn53C
pi7hd+8gCD7105NQ/dIrg5DdkML9O+HSxSLcZyRZNuYtoz8P9x7+wrFdn74r/uNMbwkKeq0/aMFF
VFCIoVVGwAGwApb3jkDXAn2idkohJRCSiElEJKISUQkohP/Z}
proc DisplayWeather {} {
global WEATHER
wm geom . {} ;# Reset main window geometry
wm iconphoto . -default ::img::noaaLogo
set W .w
label $W.tmp
set font "[font actual [$W.tmp cget -font]] -weight bold"
eval destroy [winfo child $W]
pack [frame $W.f] -side left -fill both -expand 1
set W $W.f
set keyWeather $WEATHER(weather,key)
set keyMaxTemp $WEATHER(temp,maximum,key)
set keyMinTemp $WEATHER(temp,minimum,key)
set keyRain $WEATHER(rain,key)
set keyIcon $WEATHER(icon,key)
set row 0
set col 0
foreach arr [lsort -dictionary \
[array names WEATHER $keyWeather,*,weather,summary]] {
set id [lindex [split $arr ","] 1]
set id2 [expr {$id/2}]
set WF $W.col$id
frame $WF -bd 2 -relief ridge
label $WF.name -text $WEATHER($keyWeather,$id,name) -font $font
label $WF.icon -image [DownloadIcon $WEATHER($keyIcon,$id,icon)] \
-relief ridge
if {[regexp -nocase "night" $WEATHER($keyWeather,$id,name)]} {
set txt "Low $WEATHER($keyMinTemp,$id2,temp,minimum)\xB0"
if {$row == 0 && $col == 0} { incr row}
} else {
set txt "High $WEATHER($keyMaxTemp,$id2,temp,maximum)\xB0"
}
append txt "\n$WEATHER($keyRain,$id,rain)%"
append txt "\n$WEATHER($keyWeather,$id,weather,summary)"
label $WF.txt -text $txt -wraplength 100
grid $WF -row $row -column $col -sticky news
grid columnconfigure $W $col -uniform a
eval pack [winfo child $WF] -side top
#update
if {[incr row] > 1} {
incr col
set row 0
}
}
}
proc DownloadIcon {url} {
if {$url eq ""} {return ::img::noaa}
set cacheName [file join $::S(iconDir) [file tail $url]]
set sname [file rootname [file tail $url]]
set iname ::img::$sname
if {[lsearch [image names] $iname] == -1} {
image create photo $iname -width 55 -height 58
if {! $::S(mustFetch) && [file exists $cacheName]} {
$iname config -file $cacheName
} else {
$iname copy ::img::noaa
set start [clock milliseconds]
lappend ::ALL [list $url $iname $cacheName]
after idle [list ::http::geturl $url \
-command [list DownloadIcon_Callback $iname $cacheName]]
lappend ::TIMES [expr {[clock milliseconds] - $start}]
}
}
return $iname
}
proc DownloadIcon_Callback {iname cacheName token} {
set ncode [::http::ncode $token]
if {[::http::ncode $token] != 200} {
error "bad http ncode for $iname"
} else {
set data [::http::data $token] ; list
$iname config -data [::http::data $token]
if {! $::S(noCache)} {
catch {
set fout [open $cacheName wb]
puts -nonewline $fout $data
close $fout
}
}
}
::http::cleanup $token
}
proc ScanTime {when} {
#2007-01-25T19:00:00-05:00
set ticks [clock scan "[string range $when 0 9] [string range $when 11 18]"]
return $ticks
}
proc PrettyLat {lat lon} {
set lat [int2lat $lat]
set lon [int2lat $lon]
foreach {lat1 lat2 lat3} $lat break
foreach {lon1 lon2 lon3} $lon break
set lat "$lat1\xB0 $lat2' $lat3\x22N"
set lon "$lon1\xB0 $lon2' $lon3\x22W"
return "$lat $lon"
}
proc GetTempTime {hourOffset} {
set seconds [expr {$::PLOT(basetime) + $hourOffset*60*60}]
return [clock format $seconds -format "%a %l:%M %P"]
}
proc int2lat {int} {
set int [expr {abs($int) * 3600}]
if {[string is integer -strict $int]} {
set sec [expr {$int % 60}]
} else {
#set fra [expr {$int - int($int)}]
#set fra [expr {round($fra * 10) / 10.0}]
#set int [expr {int($int)}]
#set sec [expr {$int % 60 + $fra}]
set v [expr {$int + .05}] ;# Round to 1 decimal place
foreach {int fra} [split $v "."] break ;# Use string representation
set fra [string range $fra 0 0] ;# 1 decimal place only
set sec [expr {$int % 60}]
if {$fra ne {0}} { append sec ".$fra"}
}
set int [expr {$int / 60}]
set min [expr {$int % 60}]
set deg [expr {$int / 60}]
return [list $deg $min $sec]
}
proc PlotTemp {} {
global PLOT s
wm geom . {} ;# Reset main window geometry
set W .w.c
set PLOT(W) $W
if {[winfo exists $W]} {
$W config -width [winfo width $W] -height [winfo height $W]
$W delete all
bind $W <Configure> {}
} else {
eval destroy [winfo child .w]
canvas $W -width 700
pack $W -fill both -expand 1
}
# Bug in plotchart
::Plotchart::clearcanvas $W
array unset ::Plotchart::scaling *$W*
array unset ::Plotchart::data_series *$W*
array unset ::Plotchart::config *$W*
set s [::Plotchart::createXYPlot $W $PLOT(XS) $PLOT(YS)]
foreach x $PLOT(X) y $PLOT(Y) {
$s plot series1 $x $y
set xy [::Plotchart::coordsToPixel $W $x $y]
set xy [Box $xy $::S(box,size)]
set id [$W create oval $xy -tag oval -fill red -outline red]
set when [GetTempTime $x]
::tooltip::tooltip $W -items $id "$y\xB0\n$when"
}
$s grid $PLOT(Xgrid) $PLOT(Ygrid)
$s title "Temperature Forecast"
$s ytext $PLOT(YText)
XAxis
Freezing
Noons
Colorize
$W raise oval
update
if {[bind $W <Configure>] eq ""} { bind $W <Configure> PlotTemp }
}
proc XAxis {} {
global PLOT
set W $PLOT(W)
$W delete xaxis
set Xticks [lindex $PLOT(Xgrid) 0]
set Ymin [lindex $PLOT(YS) 0]
for {set i 0} {$i < [llength $Xticks]} {incr i} {
set x [lindex $Xticks $i] ;# Hours from starting
set day [expr {$x / 24.}]
if {int($day) != $day} continue
set ticks [expr {$PLOT(basetime) + int($day)*60*60*24}]
set day [clock format $ticks -format "%a"]
set xy [::Plotchart::coordsToPixel $W $x $Ymin]
$W create text $xy -tag xaxis -anchor n -text $day
}
}
proc Freezing {} {
global PLOT
set W $PLOT(W)
foreach {xmin xmax} $PLOT(XS) break
foreach {ymin ymax} $PLOT(YS) break
foreach val {32 0} {
if {$ymin < $val && $ymax > $val} {
set xy0 [::Plotchart::coordsToPixel $W $xmin $val]
set xy1 [::Plotchart::coordsToPixel $W $xmax $val]
$W create line [concat $xy0 $xy1] -fill red -dash 1 -width 2
}
}
}
proc Noons {} {
global PLOT COLORS
set W $PLOT(W)
set xticks [lindex $PLOT(Xgrid) 0]
foreach {ymin ymax} $PLOT(YS) break
set x1 [lindex $xticks 0]
for {set i 1} {$i < [llength $xticks]} {incr i} {
set x0 $x1
set x1 [lindex $xticks $i]
set x [expr {($x0 + $x1)/2}]
set xy0 [::Plotchart::coordsToPixel $W $x $ymin]
set xy1 [::Plotchart::coordsToPixel $W $x $ymax]
$W create line [concat $xy0 $xy1] -fill black -dash 1 -width 1
}
}
proc Colorize {} {
global PLOT COLORS
set W $PLOT(W)
set xticks [lindex $PLOT(Xgrid) 0]
foreach {ymin ymax} $PLOT(YS) break
set x1 [lindex $xticks 0]
for {set i 1} {$i < [llength $xticks]} {incr i} {
set x0 $x1
set clr [lindex $COLORS [expr {$i % [llength $COLORS]}]]
set x1 [lindex $xticks $i]
set xy0 [::Plotchart::coordsToPixel $W $x0 $ymin]
set xy1 [::Plotchart::coordsToPixel $W $x1 $ymax]
$W create rect [concat $xy0 $xy1] -fill $clr -tag bg
}
$W lower bg
}
proc lat2int {lat1 lat2 lat3} {
scan "$lat1 $lat2 $lat3" "%g %g %g" lat1 lat2 lat3
set lat [expr {abs($lat1) + $lat2 / 60.0 + $lat3 / 3600.0}]
return $lat
}
proc GetPlotData {} {
global PLOT WEATHER
unset -nocomplain PLOT
set key $WEATHER(temp,hourly,key)
set basetime 0
set X {}
set Y {}
foreach arr [lsort -dictionary [array names WEATHER $key,*,temp,hourly]] {
set idx [lindex [split $arr ","] 1]
set ticks [ScanTime $WEATHER($key,$idx,start)]
if {$idx == 0} {
# Get start of the day for first time range
set basetime [ScanTime [string range $WEATHER($key,$idx,start) 0 9]]
}
lappend X [expr {($ticks - $basetime)/60/60}] ;# Hours from basetime
lappend Y $WEATHER($key,$idx,temp,hourly)
}
;# Compute Y axis
set y_sort [lsort -real $Y]
set ys [::Plotchart::determineScale [lindex $y_sort 0] [lindex $y_sort end]]
set ys [MakeInt $ys]
set min 0
set max [lindex $X end]
set delta [expr {$max - $min}]
if {$delta/24 != $delta/24.0} { set max [expr {$min + 24*(1+$delta/24)}]}
set xs [list $min $max 24]
set xs [list $min $max $::S(x,axisStep)]
set Xticks {}
foreach {a b c} $xs break
while {$a <= $b} {
lappend Xticks $a
incr a $c
}
set Yticks {}
foreach {a b c} $ys break
while {$a <= $b} {
lappend Yticks $a
incr a $c
}
set Xgrid {}
foreach . $Yticks { lappend Xgrid $Xticks }
set Ygrid {}
set cnt [llength $Xticks]
foreach tick $Yticks { lappend Ygrid [string repeat "$tick " $cnt] }
set PLOT(X) $X
set PLOT(Y) $Y
set PLOT(XS) $xs
set PLOT(YS) $ys
set PLOT(Xgrid) $Xgrid
set PLOT(Ygrid) $Ygrid
set PLOT(YText) $WEATHER(temp,hourly,units)
set PLOT(basetime) $basetime
}
proc Box {xy r} {
foreach {x y} $xy break
return [list [expr {$x-$r}] [expr {$y-$r}] [expr {$x+$r}] [expr {$y+$r}]]
}
proc MakeInt {nlist} {
set ilist {}
foreach num $nlist { lappend ilist [expr {int($num)}]}
return $ilist
}
if {! $S(noCache)} {catch {file mkdir $S(iconDir)}}
DoDisplay
set S(city) "Woods Hole, MA"
set S(city) "Granville, OH"
set S(city) "Mt View, CA"
SetCity $S(city)
if {! $tcl_interactive} {
Submit forecast
}
return

