carto

dzach : This is a small package for plotting lines and symbols on cartographic (geographic) space. Any canvas can be converted to carto and use both canvas and carto spaces. The package is by no means complete, nor the best of species of tcl programming.

 package provide carto 0.3
 # Creates the carto command handler for canvas widgets.
        # The canvas coordinate space is changed to a cartographic coordinate space
        # where y coordinates are positive north. A number of functions
        # facilitating cartography are added for convenience.
        # The shape primitives of the canvas widget are not affected by the carto command.
        # A new virtual primitive crosshair is added which is a special
        # composite canvas primitive.
        #
        # Usage :
        # carto pathName ?options?
        #
        # (C) 2004-2005, Dimitrios Zachariadis
        # Licensed under the BSD License
        #
 proc carto {w args} {
        namespace eval ::carto::${w} {
                variable var
                array set var [list \
                        -autoscale "meet" \
                        -detail 1 \
                        -meets "x" \
                        -pixelaspectratio 1 \
                        -space cartesian \
                        w 200 \
                        h 200 \
                        scale 1 \
                        cxy {}
                ]
        }
        upvar ::carto::${w}::var v
        if {![winfo exists $w]} {
                eval [list canvas $w -width 200 -height 200]
        }
        rename ::$w ::_$w
        ::carto::dispatchConfig $w $args
        # the window dimensions returned by winfo include border and highlight
        # which we must subtract to get the actual width and height
        set brdrs [expr {2*([::_$w cget -highlightthickness]+[::_$w cget -borderwidth])}]
        scan [winfo geometry $w] "%dx%d" v(ww) v(wh)
        set v(ww) [expr {$v(ww)-$brdrs}]
        set v(wh) [expr {$v(wh)-$brdrs}]
        ::_$w configure -xscrollincrement 1
        ::_$w configure -yscrollincrement 1
        bind $w <Configure> {::carto::setScale %W}
        proc ::$w {cmd args} "return \[eval ::carto::Handler $w \$cmd \$args]"
        set w
 }

 namespace eval carto {
        proc Handler {w cmd args} {
                 set debug 0
                upvar ::carto::${w}::var v
                switch -- $cmd {
                        center {
                                set vcx [expr {([::_$w canvasx 0]+$v(ww)/2.0)/($v(scale)*$v(-pixelaspectratio))}]
                                set vcy [expr {-([::_$w canvasy 0]+$v(wh)/2.0)/$v(scale)}]
                                if {$args=={}} {
                                        return [list $vcx $vcy $v(scale)]
                                }
                                if {[llength $args]!=3} {
                                        error "wrong # args: should be \"$w center x y scale\""
                                }
                                set redraw 0
                                # Set center of cartoview and scale
                                foreach {x y scale} $args {}
                                # if scale has changed find the new viewbox width
                                if {$scale!="-"} {
                                        set v(scale) $scale
                                        if {$debug} {puts "...set scale to $v(scale)"}
                                        set v(w) [expr {double($v(ww))/($v(scale)*$v(-pixelaspectratio))}]
                                        set v(h) [expr {double($v(wh))/$v(scale)}]
                                        # scale changed so force a redraw
                                        set redraw 1
                                }
                                # if x or y did not change, get them from current view
                                if {$x=="-" || $x==""} {
                                        set x $vcx
                                }
                                if {$y=="-" || $y==""} {
                                        set y $vcy
                                }
                                # set viewbox according to the new center
                                set x0 [expr {$x-$v(ww)/(2.0*$v(scale)*$v(-pixelaspectratio))}]
                                set y0 [expr {$y+$v(wh)/(2.0*$v(scale))}]
                                # Redraw or just scroll canvas to view
                                if {$redraw} {
                                        if {$debug} {puts "...generating event $w <<CartoRedraw>>"}
                                        event generate $w <<CartoRedraw>>
                                }
                                setScroll $w $x0 $y0
                        }
                        cget {
                                switch -- [lindex $args 0] {
                                        -autoscale {return $v(-autoscale)}
                                        -detail {return $v(-detail)}
                                        -meets {return $v(-meets)}
                                        -space {return $v(-space)}
                                        -pixelaspectratio {return $v(-pixelaspectratio)}
                                        default {eval {::_$w $cmd } $args}
                                }
                        }
                        config -
                        configure {eval [list dispatchConfig $w] $args}
                        create {
                                switch -- [lindex $args 0] {
                                        crosshair {
                                                if {[llength $args]<2} {
                                                        error "wrong # args: should be \"$w create crosshair tag ?option value?\""
                                                }
                                                set tag [lindex $args 1]
                                                eval [list ::_$w create line -3000 0 3000 0  -tag [list $tag ${tag}x]] [lrange $args 2 end]
                                                eval [list ::_$w create line 0 -3000 0 3000  -tag [list $tag ${tag}y]] [lrange $args 2 end]
                                                foreach {x1 y1 x2 y2} [::_$w bbox $tag] {}
                                                set v($tag,offset) [list [expr {(abs($x1)+abs($x2))/2.0}] [expr {(abs($y1)+abs($y2))/2.0}]]
                                                # save tag in a list so that we can find it for redraw
                                        }
                                        default {eval [list ::_$w $cmd] $args}
                                }
                        }
                        delete {
                                set tag [lindex $args 0]
                                if {[info exists v($tag,offset)]} {
                                        unset v($tag,offset)
                                }
                                eval [list ::_$w $cmd] $args
                        }
                        destroy {
                                # destroy canvas widget and carto command
                                namespace delete ::carto::${w}
                                destroy ::$w
                                rename ::$w {}
                                rename ::_$w {}
                        }
                        forget {
                                # destroy carto command but retain carto widget
                                namespace delete ::carto::${w}
                                rename ::$w {}
                                rename ::_$w ::$w
                        }
                        Move {
                                if {[llength $args]<3} {error "wrong # args: should be \"$w Move tagOrId x y\""}
                                foreach {tag x y} $args {}
                                # calculate tag's center coords
                                foreach {bbx1 bby1 bbx2 bby2} [::_$w bbox $tag] {}
                                if {![info exists bbx1]} {
                                        # couldn't find tag
                                        return
                                }
                                if {![info exists v($tag,offset)]} {
                                        # tag is not a symbol
                                        set cx0 $bbx1
                                        set cy0 $bby1
                                } else {
                                        set cx0 [expr {($bbx1+[lindex $v($tag,offset) 0])}]
                                        set cy0 [expr {($bby1+[lindex $v($tag,offset) 1])}]
                                }
                                set cx [expr {$x*$v(scale)*$v(-pixelaspectratio)}]
                                set cy [expr {-$y*$v(scale)}]
                                ::_$w move $tag [expr {$cx-$cx0}] [expr {$cy-$cy0}]
                        }
                        Plot {
                                # accepts one of:
                                # pathName Plot x y ?-tag tag? ?-mode [abs|rel]? ?-space [cartesian|polar]? ?-option option?
                                # pathName Plot {x y ...} ?-option option?
                                if {$debug} {puts "Plot...args=$args"}
                                set arglen [llength $args]
                                if {$arglen==0 || $arglen==2 || $arglen==4} {
                                        return;error "wrong # args: should be \"$w Plot tagOrId ?x y ... ?\""
                                }
                                foreach  {tag x1 y1 x2 y2} [lrange $args 0 4] {}
                                if {[::_$w find withtag $tag]=={}} {
                                        # this is a new tag
                                        if {$x2=={}} {
                                                set x2 $x1
                                                set y2 $y1
                                        }
                                        ::_$w create line \
                                                [expr {$x1*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y1*$v(scale)}] \
                                                [expr {$x2*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y2*$v(scale)}] \
                                                -tag $tag -fill grey
                                        if {$arglen>5} {
                                                set d [::_$w coords $tag]
                                                foreach {x y} [lrange $args 5 end] {
                                                        lappend d  [expr {$x*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y*$v(scale)}]
                                                }
                                                ::_$w coords $tag $d
                                        }
                                } else {
                                        set d [::_$w coords $tag]
                                        foreach {x y} [lrange $args 1 end] {
                                                lappend d  [expr {$x*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y*$v(scale)}]
                                        }
                                        ::_$w coords $tag $d
                                }
                        }
                        lPlot {
                                if {$debug} {puts "lPlot...args=$args"}
                                if {[llength $args]<=5} {return;error "wrong # args: should be \"$w lPlot tagOrId ?x1 y1 x2 y2 ... ?\"" }
                                set tag [lindex $args 0]
                                if {[::_$w find withtag $tag]!={}} {
                                        foreach {x y} [lrange $args 1 end] {
                                                lappend d  [expr {$x*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y*$v(scale)}]
                                        }
                                } else {
                                        foreach  {tag x1 y1 x2 y2} [lrange $args 0 4] {}
                                        lappend d  [expr {$x1*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y1*$v(scale)}]
                                        lappend d  [expr {$x2*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y2*$v(scale)}]
                                        ::_$w create line $d -tag $tag -fill grey
                                        foreach {x y} [lrange $args 5 end] {
                                                lappend d  [expr {$x*$v(scale)*$v(-pixelaspectratio)}]  [expr {-$y*$v(scale)}]
                                        }
                                }
                                ::_$w coords $tag $d
                        }
                        setview {
                                set args [join $args]
                                set vx0 [expr {[::_$w canvasx 0]/($v(scale)*$v(-pixelaspectratio))}]
                                set vy0 [expr {-[::_$w canvasy 0]/$v(scale)}]
                                if {$args=={}} {
                                        return [list $vx0 $vy0 [expr {$v(ww)/($v(scale)*$v(-pixelaspectratio))}] [expr {$v(wh)/$v(scale)}]]
                                }
                                if {[llength $args]!=4} {
                                        error "wrong # args: should be \"$w setview x y w h\""
                                }
                                set redraw 0
                                foreach {x0 y0 wi hi} $args {}
                                # do we need this ?
                                if {$x0=="-"} {
                                        set x0 $vx0
                                }
                                if {$y0=="-"} {
                                        set y0 $vy0
                                }
                                # if viewbox width changed force redraw
                                if {$wi!="-"} {
                                        set v(w) $wi
                                        set redraw 1
                                }
                                # if viewbox height changed force redraw
                                if {$hi!="-"} {
                                        set v(h) $hi
                                        set redraw 1
                                }
                                # Redraw or just scroll canvas to view
                                if {$redraw} {
                                        setScale $w
                                }
                                setScroll $w $x0 $y0
                        }
                        symbolize {
                                if {[llength $args]!=1 && [llength $args]!=3} {
                                        error "wrong # args: should be \"$w symbolize tag ?offsetx offsety?\""
                                }
                                set symbol [lindex $args 0]
                                if {[$w find withtag $symbol]=={}} {
                                        error "tag $symbol does not exist"
                                }
                                if {[llength $args]==1} {
                                        foreach {x1 y1 x2 y2} [::_$w bbox $symbol] {}
                                        set v($symbol,offset) [list [expr {($x2-$x1)/2.0}] [expr {($y2-$y1)/2.0}]]
                                } else {
                                        set v($symbol,offset) [lrange $args 1 end]
                                }
                        }
                        default {eval [list ::_$w $cmd] $args}
                }
        }

        #dispatch configure pairs
        proc dispatchConfig {w args} {
                set debug 0
                if {$debug} {puts "dispatchConfig called by [expr {[info level]>1?[lindex [info level -1] 0]:[list user]}]"}
                # strip possible ::_ so that namespace is referenced properly
                regexp {^(::_)*(.*)} $w m pre wg
                upvar ::carto::${wg}::var v
                if {[llength $args]==0} {
                        return [concat [eval {::_$w configure}] \
                                [list [list -space $v(-space)]] \
                                [list [list -pixelaspectratio $v(-pixelaspectratio)]] \
                                [list [list -autoscale $v(-autoscale)]] \
                                ]
                }
                foreach {opt val} $args {
                        switch -- $opt {
                                -autoscale {
                                        if {$val=={}} {return $v(-autoscale)}
                                        if {![regexp -- {meet|slice|none} $val]} {
                                                error "unknown option $val: should be meet, slice or none"
                                        }
                                        set v(-autoscale) $val
                                }
                                -detail {
                                        if {$val=={}} {return $v(-detail)}
                                        if {![string is int $val]} {
                                                error "expected integer but got $val"
                                        }
                                        set v(-detail) $val
                                }
                                -space {
                                        if {$val=={}} {return $v(-space)}
                                        if {![regexp -- {meet|slice|none} $val]} {
                                                error "unknown option $val: should be cartesian or polar"
                                        }
                                        set v(-space) $val
                                }
                                -pixelaspectratio {
                                        if {$val=={}} {return $v(-pixelaspectratio)}
                                        if {![string is double $val]} {
                                                error "expected real but got $val"
                                        }
                                        set v(-pixelaspectratio) $val
                                }
                                default {eval {::_$w configure} $opt $val}
                        }
                }
        }

        proc setScale wg {
                set debug 0
                if {$debug} {puts "setScale called by [expr {[info level]>1?[lindex [info level -1] 0]:[list user]}]"}
                upvar ::carto::${wg}::var v
                scan [winfo geometry $wg] "%dx%d" v(ww) v(wh)
                set brdrs [expr {2*([::_$wg cget -highlightthickness]+[::_$wg cget -borderwidth])}]
                set v(ww) [expr {$v(ww)-$brdrs}]
                set v(wh) [expr {$v(wh)-$brdrs}]
                set aspectratio [expr {double($v(ww))/$v(wh)}]
                switch -regexp -- $v(-autoscale) {
                        meet {
                                # max(viewbox) -> min(window)
                                if {$v(wh)!=0 && $v(w)!=0 && $v(h)!=0} {
                                        if {double($v(w))/$v(h)>$aspectratio} {
                                                set v(-meets) "x"
                                                set v(scale) [expr {double($v(ww))/$v(w)}]
                                        } else {
                                                set v(-meets) "y"
                                                set v(scale) [expr {double($v(wh))/$v(h)}]
                                        }
                                }
                        }
                        slice {
                                # min(viewbox) -> max(window)
                                if {$v(wh)!=0 && $v(w)!=0 && $v(h)!=0} {
                                        if {double($v(w))/$v(h)>$aspectratio} {
                                                set v(-meets) "y"
                                                set v(scale) [expr {double($v(wh))/$v(h)}]
                                        } else {
                                                set v(-meets) "x"
                                                set v(scale) [expr {double($v(ww))/$v(w)}]
                                        }
                                }
                        }
                        none {
                                # no event is generated
                                return
                        }
                        default {
                                return
                        }
                }
 #                set v(w) [expr {double($v(ww))/$v(scale)}]
 #                set v(h) [expr {double($v(wh))/$v(scale)}]
                if {$debug} {puts "...event generate $wg <<CartoRedraw>>"}
                event generate $wg <<CartoRedraw>>
                if {$debug} {puts "...set scale to $v(scale)"}
        }

        proc setScroll {w x0 y0} {
                upvar ::carto::${w}::var v
                ::_$w xview scroll [expr {round($x0*$v(scale)*$v(-pixelaspectratio)-[::_$w canvasx 0])}] u
                ::_$w yview scroll [expr {-round($y0*$v(scale)+[::_$w canvasy 0])}] u
        }

        proc screentocarto {w x y} {
                upvar ::carto::${w}::var v
                return [list \
                                [expr {($x+[::_$w canvasx 0])/($v(scale)*$v(-pixelaspectratio))}] \
                                [expr {-($y+[::_$w canvasy 0])/$v(scale)}]]
        }
        proc tocarto {w x y} {
                upvar ::carto::${w}::var v
                return [list [expr {$x/($v(scale)*$v(-pixelaspectratio))}] [expr {-$y/$v(scale)}]]
        }
        proc tocanvas {w x y} {
                upvar ::carto::${w}::var v
                return [list [expr {$x*$v(scale)*$v(-pixelaspectratio)}] [expr {-$y*$v(scale)}]]
        }
        proc centertoview {w args} {
        }
        proc viewtocenter {w args} {
        }
 }

 # A sample of its usage:

 package req Tk
 package req carto
 pack [carto .c --bg white]
 # set cartographic origin in canvas units x0=0 y0=200
 .c setview 0 200 - -
 # create and plot line l1
 .c Plot l1 10 10 20 25 23 56 100 23
 # create symbol pt in canvas
 .c create rect -5 -5 5 5 -tag pt
 # create symbol offset so that it is centered on the coordinate. Here default centering is used
 .c symbolize pt
 .c Move pt 100 23