summaryrefslogtreecommitdiffstats
path: root/tcllib/modules/map/map_slippy.tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcllib/modules/map/map_slippy.tcl')
-rw-r--r--tcllib/modules/map/map_slippy.tcl221
1 files changed, 221 insertions, 0 deletions
diff --git a/tcllib/modules/map/map_slippy.tcl b/tcllib/modules/map/map_slippy.tcl
new file mode 100644
index 0000000..aebc095
--- /dev/null
+++ b/tcllib/modules/map/map_slippy.tcl
@@ -0,0 +1,221 @@
+## -*- tcl -*-
+# ### ### ### ######### ######### #########
+
+## Common information for slippy based maps. I.e. tile size,
+## relationship between zoom level and map size, etc.
+
+## See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Pseudo-Code
+## for the coordinate conversions and other information.
+
+# ### ### ### ######### ######### #########
+## Requisites
+
+package require Tcl 8.4
+package require snit
+package require math::constants
+
+# ### ### ### ######### ######### #########
+## Implementation
+
+namespace eval ::map::slippy {
+ math::constants::constants pi radtodeg degtorad
+}
+
+snit::type map::slippy {
+ # ### ### ### ######### ######### #########
+ ## API
+
+ typemethod length {level} {
+ return [expr {$ourtilesize * [tiles $level]}]
+ }
+
+ typemethod tiles {level} {
+ return [tiles $level]
+ }
+
+ typemethod {tile size} {} {
+ return $ourtilesize
+ }
+
+ typemethod {tile valid} {tile levels {msgv {}}} {
+ if {$msgv ne ""} { upvar 1 $msgv msg }
+
+ # Bad syntax.
+
+ if {[llength $tile] != 3} {
+ set msg "Bad tile <[join $tile ,]>, expected 3 elements (zoom, row, col)"
+ return 0
+ }
+
+ foreach {z r c} $tile break
+
+ # Requests outside of the valid ranges are rejected
+ # immediately, without even going to the filesystem or
+ # provider.
+
+ if {($z < 0) || ($z >= $levels)} {
+ set msg "Bad zoom level '$z' (max: $levels)"
+ return 0
+ }
+
+ set tiles [tiles $z]
+ if {($r < 0) || ($r >= $tiles) ||
+ ($c < 0) || ($c >= $tiles)
+ } {
+ set msg "Bad cell '$r $c' (max: $tiles)"
+ return 0
+ }
+
+ return 1
+ }
+
+ # Coordinate conversions.
+ # geo = zoom, latitude, longitude
+ # tile = zoom, row, column
+ # point = zoom, y, x
+
+ typemethod {geo 2tile} {geo} {
+ ::variable degtorad
+ ::variable pi
+ foreach {zoom lat lon} $geo break
+ # lat, lon are in degrees.
+ # The missing sec() function is computed using the 1/cos equivalency.
+ set tiles [tiles $zoom]
+ set latrad [expr {$degtorad * $lat}]
+ set row [expr {int((1 - (log(tan($latrad) + 1.0/cos($latrad)) / $pi)) / 2 * $tiles)}]
+ set col [expr {int((($lon + 180.0) / 360.0) * $tiles)}]
+ return [list $zoom $row $col]
+ }
+
+ typemethod {geo 2tile.float} {geo} {
+ ::variable degtorad
+ ::variable pi
+ foreach {zoom lat lon} $geo break
+ # lat, lon are in degrees.
+ # The missing sec() function is computed using the 1/cos equivalency.
+ set tiles [tiles $zoom]
+ set latrad [expr {$degtorad * $lat}]
+ set row [expr {(1 - (log(tan($latrad) + 1.0/cos($latrad)) / $pi)) / 2 * $tiles}]
+ set col [expr {(($lon + 180.0) / 360.0) * $tiles}]
+ return [list $zoom $row $col]
+ }
+
+ typemethod {geo 2point} {geo} {
+ ::variable degtorad
+ ::variable pi
+ foreach {zoom lat lon} $geo break
+ # Essence: [geo 2tile $geo] * $ourtilesize, with 'geo 2tile' inlined.
+ set tiles [tiles $zoom]
+ set latrad [expr {$degtorad * $lat}]
+ set y [expr {$ourtilesize * ((1 - (log(tan($latrad) + 1.0/cos($latrad)) / $pi)) / 2 * $tiles)}]
+ set x [expr {$ourtilesize * ((($lon + 180.0) / 360.0) * $tiles)}]
+ return [list $zoom $y $x]
+ }
+
+ typemethod {tile 2geo} {tile} {
+ ::variable radtodeg
+ ::variable pi
+ foreach {zoom row col} $tile break
+ # Note: For integer row/col the geo location is for the upper
+ # left corner of the tile. To get the geo location of
+ # the center simply add 0.5 to the row/col values.
+ set tiles [tiles $zoom]
+ set lat [expr {$radtodeg * (atan(sinh($pi * (1 - 2 * $row / double($tiles)))))}]
+ set lon [expr {$col / double($tiles) * 360.0 - 180.0}]
+ return [list $zoom $lat $lon]
+ }
+
+ typemethod {tile 2point} {tile} {
+ foreach {zoom row col} $tile break
+ # Note: For integer row/col the pixel location is for the
+ # upper left corner of the tile. To get the pixel
+ # location of the center simply add 0.5 to the row/col
+ # values.
+ #set tiles [tiles $zoom]
+ set y [expr {$ourtilesize * $row}]
+ set x [expr {$ourtilesize * $col}]
+ return [list $zoom $y $x]
+ }
+
+ typemethod {point 2geo} {point} {
+ ::variable radtodeg
+ ::variable pi
+ foreach {zoom y x} $point break
+ set length [expr {$ourtilesize * [tiles $zoom]}]
+ set lat [expr {$radtodeg * (atan(sinh($pi * (1 - 2 * double($y) / $length))))}]
+ set lon [expr {double($x) / $length * 360.0 - 180.0}]
+ return [list $zoom $lat $lon]
+ }
+
+ typemethod {point 2tile} {point} {
+ foreach {zoom y x} $point break
+ #set tiles [tiles $zoom]
+ set row [expr {double($y) / $ourtilesize}]
+ set col [expr {double($x) / $ourtilesize}]
+ return [list $zoom $row $col]
+ }
+
+ typemethod {fit geobox} {canvdim geobox zmin zmax} {
+ foreach {canvw canvh} $canvdim break
+ foreach {lat0 lat1 lon0 lon1} $geobox break
+
+ # NOTE we assume ourtilesize == [map::slippy length 0].
+ # Further, we assume that each zoom step "grows" the
+ # linear resolution by 2 (that's the log(2) down there)
+ set canvw [expr {abs($canvw)}]
+ set canvh [expr {abs($canvh)}]
+ set z [expr {int(log(min( \
+ ($canvh/$ourtilesize) / (abs($lat1 - $lat0)/180), \
+ ($canvw/$ourtilesize) / (abs($lon1 - $lon0)/360))) \
+ / log(2))}]
+ # clamp $z
+ set z [expr {($z<$zmin) ? $zmin : (($z>$zmax) ? $zmax : $z)}]
+ # Now $zoom is an approximation, since the scale factor isn't uniform
+ # across the map (the vertical dimension depends on latitude). So we have
+ # to refine iteratively (I expect it to take just one step):
+ while {1} {
+ # Now we can run "uphill", then there's z0 = z - 1 and "downhill",
+ # then there's z1 = z + 1 (from the last iteration)
+ #puts "try zoom $z"
+ foreach {_ y0 x0} [map::slippy geo 2point [list $z $lat0 $lon0]] break
+ foreach {_ y1 x1} [map::slippy geo 2point [list $z $lat1 $lon1]] break
+ set w [expr {abs($x1 - $x0)}]
+ set h [expr {abs($y1 - $y0)}]
+ if { $w > $canvw || $h > $canvh } {
+ # too big: shrink
+ #puts "too big: shrink..."
+ if { [info exists z0] } break; # but not if we come "from below"
+ if {$z <= $zmin} break; # can't be < $zmin
+ set z1 $z
+ incr z -1
+ } else {
+ # fits: grow
+ #puts "fits: grow..."
+ if { [info exists z1] } break; # but not if we come "from above"
+ set z0 $z
+ incr z
+ }
+ }
+ if { [info exists z0] } { return $z0 }
+ return $z
+ }
+
+ proc tiles {level} {
+ return [expr {1 << $level}]
+ }
+
+ # ### ### ### ######### ######### #########
+ ## Internal commands
+
+ # ### ### ### ######### ######### #########
+ ## State
+
+ typevariable ourtilesize 256 ; # Size of slippy tiles <pixels>
+
+ # ### ### ### ######### ######### #########
+}
+
+# ### ### ### ######### ######### #########
+## Ready
+
+package provide map::slippy 0.5