1package main 2 3import ( 4 "errors" 5 "fmt" 6 "math" 7 "strconv" 8) 9 10// Tile represents a single tile in a tile 11// pyramid, usually referenced in a URL path 12// of the form "Zoom/X/Y.Ext" 13type Tile struct { 14 Zoom int `json:"zoom"` 15 X int `json:"x"` 16 Y int `json:"y"` 17 Ext string `json:"ext"` 18 Bounds Bounds `json:"bounds"` 19} 20 21// makeTile uses the map populated by the mux.Router 22// containing x, y and z keys, and extracts integers 23// from them 24func makeTile(vars map[string]string) (Tile, error) { 25 // Router path restriction ensures 26 // these are always numbers 27 x, _ := strconv.Atoi(vars["x"]) 28 y, _ := strconv.Atoi(vars["y"]) 29 zoom, _ := strconv.Atoi(vars["z"]) 30 ext := vars["ext"] 31 tile := Tile{Zoom: zoom, X: x, Y: y, Ext: ext} 32 // No tile numbers outside the tile grid implied 33 // by the zoom? 34 if !tile.IsValid() { 35 return tile, errors.New(fmt.Sprintf("invalid tile address %s", tile.String())) 36 } 37 e := tile.CalculateBounds() 38 return tile, e 39} 40 41func (tile *Tile) width() float64 { 42 return math.Abs(tile.Bounds.Xmax - tile.Bounds.Xmin) 43} 44 45// IsValid tests that the tile contains 46// only tile addresses that fit within the 47// zoom level, and that the zoom level is 48// not crazy large 49func (tile *Tile) IsValid() bool { 50 if tile.Zoom > 32 || tile.Zoom < 0 { 51 return false 52 } 53 worldTileSize := int(1) << uint(tile.Zoom) 54 if tile.X < 0 || tile.X >= worldTileSize || 55 tile.Y < 0 || tile.Y >= worldTileSize { 56 return false 57 } 58 return true 59} 60 61// CalculateBounds calculates the cartesian bounds that 62// correspond to this tile 63func (tile *Tile) CalculateBounds() (e error) { 64 serverBounds, e := getServerBounds() 65 if e != nil { 66 return e 67 } 68 69 worldWidthInTiles := float64(int(1) << uint(tile.Zoom)) 70 tileWidth := math.Abs(serverBounds.Xmax-serverBounds.Xmin) / worldWidthInTiles 71 72 // Calculate geographic bounds from tile coordinates 73 // XYZ tile coordinates are in "image space" so origin is 74 // top-left, not bottom right 75 xmin := serverBounds.Xmin + (tileWidth * float64(tile.X)) 76 xmax := serverBounds.Xmin + (tileWidth * float64(tile.X+1)) 77 ymin := serverBounds.Ymax - (tileWidth * float64(tile.Y+1)) 78 ymax := serverBounds.Ymax - (tileWidth * float64(tile.Y)) 79 tile.Bounds = Bounds{serverBounds.SRID, xmin, ymin, xmax, ymax} 80 81 return nil 82} 83 84// String returns a path-like representation of the Tile 85func (tile *Tile) String() string { 86 return fmt.Sprintf("%d/%d/%d.%s", tile.Zoom, tile.X, tile.Y, tile.Ext) 87} 88