tkmap.tcl

Go to the documentation of this file.
00001 /*  tkmap.tcl --*/
00002 /* */
00003 /*  Example application demonstrating the use of Tcllib's 'mapproj'*/
00004 /*  package.*/
00005 
00006 package require Tcl 8.4
00007 package require Tk 8.4
00008 package require mapproj 1.0
00009 
00010 /* ----------------------------------------------------------------------*/
00011 /* */
00012 /*  Module for reading NCAR DS780.0 is included literally*/
00013 /* */
00014 
00015 namespace ncar780_0 {
00016     variable libdir [file dirname [info script]]
00017     variable mapFile [file join $libdir ncar780.txt]
00018     namespace export readMap cancelReadMap
00019 }
00020 
00021 /* ----------------------------------------------------------------------*/
00022 /* */
00023 /*  ncar780_0::readMap --*/
00024 /* */
00025 /*  Read in the continental outlines from NCAR data set 780.0.*/
00026 /* */
00027 /*  Parameters:*/
00028 /*  lineCallback*/
00029 /*      Callback to make after each polyline is read.*/
00030 /*  doneCallback*/
00031 /*      Callback to make when the entire map has been*/
00032 /*      read.*/
00033 /* */
00034 /*  Results:*/
00035 /*  An integer that identifies the map-reading task.*/
00036 /* */
00037 /*  Side effects:*/
00038 /*  A chain of `after' callbacks is initiated to read the map.*/
00039 /* */
00040 /*  When the ncar780_0::readMap procedure is invoked, it reads in*/
00041 /*  the list of line segments from the data set.  Rather than freeze*/
00042 /*  the user interface for the amount of time that it takes to*/
00043 /*  process this large file, it sets up `after' callbacks that*/
00044 /*  actually do the work.*/
00045 /* */
00046 /*  For each polyline that is read from the file, the `lineCallback'*/
00047 /*  is executed at global level.  To the callback are appended*/
00048 /*  six parameters: the `group ID' (see the documentation for*/
00049 /*  NCAR 780.0 for an explanation), the minimum latitude and longitude*/
00050 /*  of the line, the maximum latitude and longitude of the line,*/
00051 /*  and a list of co-ordinates that have longitude and latitude*/
00052 /*  values alternating: {lon1 lat1 lon2 lat2 ...}.*/
00053 /* */
00054 /*  At the end of the file, the `doneCallback' is evaluated at global*/
00055 /*  level.*/
00056 /* */
00057 /*  The ncar780_0::cancelReadMap procedure may be used to cancel*/
00058 /*  a ncar780_0::readMap call before the map has been completely read*/
00059 /*  in.*/
00060 /* */
00061 /* ----------------------------------------------------------------------*/
00062 
00063 ret  ncar780_0::readMap (type lineCallback , type doneCallback) {
00064     variable mapFile
00065     variable mapReaders
00066     if {![info exists mapReaders]} {
00067     set mapReaders 0
00068     } else {
00069     incr mapReaders
00070     }
00071     upvar #0 [namespace current]::mapReader$mapReaders state
00072     set state(lineCallback) $lineCallback
00073     set state(doneCallback) $doneCallback
00074     set state(channel) [open $mapFile RDONLY]
00075     readMapGroup $mapReaders
00076     return $mapReaders
00077 }
00078 
00079 /* ----------------------------------------------------------------------*/
00080 /* */
00081 /*  ncar780_0::cancelReadMap --*/
00082 /* */
00083 /*  Cancel the operation begun by ncar780_0::readMap*/
00084 /* */
00085 /*  Parameters:*/
00086 /*  reader*/
00087 /*      Token returned by ncar780_0::readMap*/
00088 /* */
00089 /*  Results:*/
00090 /*  None.*/
00091 /* */
00092 /*  Side effects:*/
00093 /*  Cancels the `after' calls set up by ncar780_0::readMap and*/
00094 /*  cleans up variables.*/
00095 /* */
00096 /* ----------------------------------------------------------------------*/
00097 
00098 ret  ncar780_0::cancelReadMap (type reader) {
00099     upvar #0 [namespace current]::mapReader$reader state
00100     catch {
00101     after cancel $state(idleHandler)
00102     }
00103     unset [namespace current]::mapReader$reader
00104     return
00105 }
00106 
00107 /* ----------------------------------------------------------------------*/
00108 /* */
00109 /*  ncar780_0::readMapGroup --*/
00110 /* */
00111 /*  Read a single group of points from the NCAR 780.0 data set.*/
00112 /* */
00113 /*  Parameters:*/
00114 /*  reader*/
00115 /*      Token identifying the map-reading process.*/
00116 /* */
00117 /*  Results:*/
00118 /*  None.*/
00119 /* */
00120 /*  Side effects:*/
00121 /*  Reads a group of points from the file, and invokes the*/
00122 /*  line callback (after each group) and the done callback*/
00123 /*  (at end of file).  If end of file has not been reached,*/
00124 /*  schedules an `after' callback to process the next group.*/
00125 /* */
00126 /* ----------------------------------------------------------------------*/
00127 
00128 ret  ncar780_0::readMapGroup (type reader) {
00129     upvar #0 [namespace current]::mapReader$reader state
00130 
00131     set f $state(channel)
00132     for {set i 0} {$i < 10} {incr i} {
00133     set pointList {}
00134     if {[gets $f line] >= 0} {
00135         regexp {^(........)(.*)} $line junk nPoints line
00136         set nPoints [string trim $nPoints]
00137         if {$nPoints < 2} {
00138         close $f
00139         uplevel #0 $state(doneCallback)
00140         unset [namespace current]::mapReader$reader
00141         return
00142         }
00143         regexp {^(........)(.*)} $line junk groupId line
00144         set groupId [string trim $groupId]
00145         regexp {^(........)(.*)} $line junk maxLat line
00146         set maxLat [string trim $maxLat]
00147         regexp {^(........)(.*)} $line junk minLat line
00148         set minLat [string trim $minLat]
00149         regexp {^(........)(.*)} $line junk maxLon line
00150         set maxLon [string trim $maxLon]
00151         regexp {^(........)(.*)} $line junk minLon line
00152         set minLon [string trim $minLon]
00153         set pointList {}
00154         set ptsLeft 0
00155         for {set i 0} {$i < $nPoints} {incr i 2} {
00156         if {$ptsLeft == 0} {
00157             gets $f line
00158             set ptsLeft 5
00159         }
00160         regexp {^(........)(........)(.*)} $line junk lat lon line
00161         lappend pointList [string trim $lon] [string trim $lat]
00162         incr ptsLeft -1
00163         }
00164         uplevel \#0 $state(lineCallback) [list $groupId \
00165                           $minLat $minLon $maxLat $maxLon \
00166                           $pointList]
00167 
00168     } else {
00169         unset [namespace current]::mapReader$reader
00170         close $f
00171         uplevel #0 $doneCallback
00172         return
00173     }
00174     }
00175     set state(idleHandler) [after 2 [namespace code \
00176                      [list readMapGroup $reader]]]
00177     return
00178 }
00179 
00180 /* */
00181 /* ----------------------------------------------------------------------*/
00182 
00183 /*  plot --*/
00184 /* */
00185 /*  Plots a line in the '.c' canvas.*/
00186 /* */
00187 /*  Parameters:*/
00188 /*  id - Line ID from the NCAR DS780.0 file.  'id$id' will be added as*/
00189 /*       a canvas tag for the plotted line.*/
00190 /*  la0, lo0 - Co-ordinates of the southwest corner of the bounding box*/
00191 /*  la1, lo1 - Co-ordinates of the northeast corenr of the bounding box*/
00192 /*  ptlist - List of points on the line, expressed as alternating*/
00193 /*           longitude and latitude in degrees.*/
00194 /* */
00195 /*  Results:*/
00196 /*  None.*/
00197 /* */
00198 /*  Side effects:*/
00199 /*  Line is added to the canvas '.c', scaled to 100 pixels per Earth*/
00200 /*  radius.*/
00201 
00202 ret  plot (type id , type la0 , type lo0 , type la1 , type lo1 , type ptlist) {
00203     variable toProjCmd
00204     set command [list .c create line]
00205     foreach {lo la} $ptlist {
00206     set ok 0
00207     set pcmd $toProjCmd
00208     lappend pcmd $lo $la
00209     foreach {x y} [eval $pcmd] {
00210         set ok 1
00211     }
00212     if {!$ok
00213         || ([info exists lastx] && hypot($x-$lastx, $y-$lasty) > 0.25)} {
00214         if {[llength $command] >= 7} {
00215         if {$id == 0} {
00216             lappend command -fill \#cccccc
00217         } else {
00218             lappend command -fill \#cc0000
00219         }
00220         eval $command
00221         } 
00222         set command [list .c create line]
00223     } 
00224     if {$ok} {
00225         lappend command [expr {316 + 100 * $x}] \
00226         [expr {316 - 100 * $y}]
00227         set lastx $x
00228         set lasty $y
00229     }
00230     }
00231     if {[llength $command] >= 7} {
00232     if {$id == 0} {
00233         lappend command -fill \#cccccc
00234     } else {
00235         lappend command -fill \#cc0000
00236     }
00237     lappend command -tags id$id
00238     eval $command
00239     }
00240     return
00241 }
00242 
00243 /*  done --*/
00244 /* */
00245 /*  Completes the plot of the map*/
00246 /* */
00247 /*  Results:*/
00248 /*  None.*/
00249 /* */
00250 /*  Side effects:*/
00251 /*  Updates the canvas's scrollregion to its bounding box.*/
00252 
00253 ret  done () {
00254     variable reader
00255     unset reader
00256     .c configure -scrollregion [.c bbox all]
00257     return
00258 }
00259 
00260 /*  locate --*/
00261 /* */
00262 /*  Computes longitude and latitude of a point on the map*/
00263 /* */
00264 /*  Parameters:*/
00265 /*  w -- Path name of the canvas showing the map*/
00266 /*  x,y -- Window co-ordinates of the point to convert*/
00267 /* */
00268 /*  Results:*/
00269 /*  None.*/
00270 /* */
00271 /*  Side effects:*/
00272 /*  Stores longitude and latitude (in degrees) in 'lon' and 'lat'.*/
00273 
00274 ret  locate (type w , type x , type y) {
00275     variable lon
00276     variable lat
00277     variable fromProjCmd
00278     set x [$w canvasx $x]
00279     set y [$w canvasy $y]
00280     set x [expr {($x - 316.) / 100.}]
00281     set y [expr {(316. - $y) / 100.}]
00282     set pcmd $fromProjCmd
00283     lappend pcmd $x $y
00284     foreach {lon lat} [eval $pcmd] break
00285     return
00286 }
00287 
00288 /*  showMap --*/
00289 /* */
00290 /*  Redisplays the world map*/
00291 /* */
00292 /*  Results:*/
00293 /*  None.*/
00294 /* */
00295 /*  Side effects:*/
00296 /*  Launches a reader to read the NCAR data set and plot the continent*/
00297 /*  outlines.  Cancels any existing reader.  Has a check so that new*/
00298 /*  readers are launched at most every half second.*/
00299 
00300 ret  showMap () {
00301     variable showMapScheduled
00302     if {[info exists showMapScheduled]} {
00303     after cancel $showMapScheduled
00304     unset showMapScheduled
00305     }
00306     set showMapScheduled [after 500 showMap2]
00307     return
00308 }
00309 ret  showMap2 () {
00310     variable showMapScheduled
00311     if {[info exists showMapScheduled]} {
00312     after cancel $showMapScheduled
00313     unset showMapScheduled
00314     }
00315     variable projection
00316     variable fromProjCmd
00317     variable toProjCmd
00318     variable reader
00319     if {[info exists reader]} {
00320     ncar780_0::cancelReadMap $reader
00321     unset reader
00322     }
00323     .c delete all
00324 
00325     foreach {toProjCmd fromProjCmd} [makeProjCmds $projection] break
00326     for {set m -180} {$m <= 180} {incr m 15} {
00327     set plist {}
00328     for {set p -89} {$p <= 89} {incr p} {
00329         lappend plist $m $p
00330     }
00331     plot 0 -90.0 $m 90.0 $m $plist
00332     }
00333     for {set p -75} {$p <= 75} {incr p 15} {
00334     set plist {}
00335     for {set m -180} {$m <= 180} {incr m} {
00336         lappend plist $m $p
00337     }
00338     plot 0 $p -180.0 $p 180.0 $plist
00339     }
00340     set reader [ncar780_0::readMap plot done]
00341     return
00342 }
00343 
00344 /*  makeProjCmds --*/
00345 /* */
00346 /*  Switches projections, making commands to convert to/from the new */
00347 /*  projection.*/
00348 /* */
00349 /*  Parameters:*/
00350 /*  pro -- Name of the new projection.*/
00351 /*  comps -- 1 if GUI components for the projection's parameters are*/
00352 /*       required, 0 otherwise.*/
00353 /* */
00354 /*  Results:*/
00355 /*  Returns a list of command prefixes, {toProj fromProj}.  'toProj'*/
00356 /*  should have longitude and latitude postpended, and converts to*/
00357 /*  the given projection.  'fromProj' should have canvas x and y appended*/
00358 /*  and converts back to longitude and latitude.*/
00359 /* */
00360 /*  Side effects:*/
00361 /*  If requested, changes the GUI to show components for the projection's*/
00362 /*  parameters.*/
00363 
00364 ret  makeProjCmds (type pro , optional comps =1) {
00365     variable phi_0
00366     variable phi_1
00367     variable phi_2
00368     variable lambda_0
00369     set toProjCmd ::mapproj::to$pro
00370     set alist [info args ::mapproj::to$pro]
00371     if {[llength $alist] < 2} {
00372     return -code error "$toProjCmd has too few args"
00373     }
00374     if {[lindex $alist end-1] ne {lambda}
00375     || [lindex $alist end] ne {phi}} {
00376     return -code error "$toProjCmd does not accept lambda and phi"
00377     }
00378     foreach a [lrange $alist 0 end-2] {
00379     switch -exact $a {
00380         phi_0 - phi_1 - phi_2 - lambda_0 {
00381         lappend toProjCmd [set $a]
00382         set have($a) {}
00383         }
00384         default {
00385         return -code error "$toProjCmd accepts an unknown arg $a"
00386         }
00387     }
00388     }
00389     set fromProjCmd ::mapproj::from$pro
00390     set alist [info args ::mapproj::from$pro]
00391     if {[llength $alist] < 2} {
00392     return -code error "$fromProjCmd has too few args"
00393     }
00394     if {[lindex $alist end-1] ne {x}
00395     || [lindex $alist end] ne {y}} {
00396     return -code error "$fromProjCmd does not accept x and y"
00397     }
00398     foreach a [lrange $alist 0 end-2] {
00399     switch -exact $a {
00400         phi_0 - phi_1 - phi_2 - lambda_0 {
00401         lappend fromProjCmd [set $a]
00402         set have($a) {}
00403         }
00404         default {
00405         return -code error "$fromProjCmd accepts an unknown arg $a"
00406         }
00407     }
00408     }
00409     if {$comps} {
00410     foreach item {lambda_0 phi_0 phi_1 phi_2} {
00411         if {[info exists have($item)] && ![winfo ismapped .extras.$item]} {
00412         grid .extras.$item -sticky ew -columnspan 2
00413         } elseif {![info exists have($item)]
00414               && [winfo ismapped .extras.$item]} {
00415         grid forget .extras.$item
00416         }
00417     }
00418     }
00419     return [list $toProjCmd $fromProjCmd]
00420 }   
00421 
00422 /*  isProjection --*/
00423 /* */
00424 /*  Tests whether a given name represents a known map projection.*/
00425 /* */
00426 /*  Parameters:*/
00427 /*  pro -- Name to test*/
00428 /* */
00429 /*  Results:*/
00430 /*  Returns 1 if the name is a known projection, 0 otherwise.*/
00431 
00432 ret  isProjection (type pro) {
00433     if {![catch {makeProjCmds $pro 0} r]} {
00434     return 1
00435     } else {
00436     puts $r
00437     return 0
00438     }
00439 }
00440 
00441 /*  Parameters of various projections*/
00442 
00443  phi = _0 15.0;             /*  Reference latitude*/
00444  phi = _1 -30.0;            /*  First standard parallel*/
00445  phi = _2 60.0;             /*  Second standard parallel*/
00446  lambda = _0 12.0;          /*  Reference longitude*/
00447 
00448 /*  Create a GUI to display the map*/
00449 
00450 canvas .c -width 632 -height 632 -bg white
00451 listbox .projs -height 10 -width 30 -yscrollcommand [list .projsy ]
00452 scrollbar =  .projsy -orient vertical -command [list .projs yview]
00453 frame .extras
00454 label .extras.llat -text "Latitude:" -anchor w
00455 entry .extras.elat -width 20 -textvariable lat -state disabled
00456 label .extras.llon -text "Longitude:" -anchor w
00457 entry .extras.elon -width 20 -textvariable lon -state disabled
00458 scale .extras.phi_0 -label "Reference latitude" \
00459     -variable phi_0 -from -90.0 -to 90.0 -length 180 -orient horizontal
00460 scale .extras.lambda_0 -label "Reference longitude" \
00461     -variable lambda_0 -from -180.0 -to 180.0 -length 180 -orient horizontal
00462 scale .extras.phi_1 -label "First standard parallel" \
00463     -variable phi_1 -from -90.0 -to 90.0 -length 180 -orient horizontal
00464 scale .extras.phi_2 -label "Second standard parallel" \
00465     -variable phi_2 -from -90.0 -to 90.0 -length 180 -orient horizontal
00466 
00467 grid .extras.llat     .extras.elat -sticky nsew
00468 grid .extras.llon     .extras.elon -sticky nsew
00469 grid .extras.lambda_0 -            -sticky nsew
00470 grid .extras.phi_0    -            -sticky nsew
00471 grid .extras.phi_1    -            -sticky nsew
00472 grid .extras.phi_2    -            -sticky nsew
00473 
00474 grid rowconfigure .extras 20 -weight 1
00475 
00476 grid .c .projs  .projsy  -sticky nsew
00477 grid ^  .extras -        -sticky nsew
00478 
00479 grid rowconfigure . 1 -weight 1
00480 grid columnconfigure . 0 -weight 1
00481 
00482 foreach cmd [info commands ::mapproj::to*] {
00483     if {[regexp ^::mapproj::to(.*) $cmd -> pro]
00484     && [namespace origin ::mapproj::from$pro] ne {}
00485     && [isProjection $pro]} {
00486     lappend prolist $pro
00487     }
00488 }
00489 
00490 bind .c <1> {locate %W %x %y}
00491 bind .projs <<ListboxSelect>> {
00492     foreach p [.projs curselection] {
00493      projection =  [.projs get $p]
00494     }
00495     showMap
00496 }
00497 foreach pro [lsort -dictionary $prolist] {
00498     .projs insert end $pro
00499 }
00500 
00501 .projs selection  0
00502 event =  generate .projs <<ListboxSelect>>
00503 
00504 trace add variable phi_0 write "showMap;\/* "*/
00505 trace add variable phi_1 write "showMap;\/* "*/
00506 trace add variable phi_2 write "showMap;\/* "*/
00507 trace add variable lambda_0 write "showMap;\/* "*/
00508 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1