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