// Copyright 2014 by Sascha L. Teichmann // Use of this source code is governed by the MIT license // that can be found in the LICENSE file. package main import ( "flag" "fmt" "image" "image/color" "image/png" "log" "net/http" "os" "path/filepath" "strconv" "bitbucket.org/s_l_teichmann/mtredisalize/common" "github.com/gorilla/mux" ) func blowUp(src image.Image) *image.RGBA { // TODO: Fast path for image.RGBA dst := image.NewRGBA(image.Rect(0, 0, 256, 256)) // fix point numbers x:8 dx, dy := src.Bounds().Dx(), src.Bounds().Dy() bx, by := src.Bounds().Min.X<<8, src.Bounds().Min.Y<<8 py := by for y := 0; y < 256; y++ { sy := (py >> 8) & 0xff ox := -1 px := bx var col color.Color for x := 0; x < 256; x++ { sx := (px >> 8) & 0xff if sx != ox { // Minimize interface indirection access. ox = sx col = src.At(sx, sy) } dst.Set(x, y, col) px += dx } py += dy } return dst } func toUint(s string) uint { var err error var x int if x, err = strconv.Atoi(s); err != nil { log.Printf("WARN: Cannot convert to int: %s", err) return 0 } return uint(x) } func createETag(path string) (etag string, err error) { var fi os.FileInfo if fi, err = os.Stat(path); err != nil { return } etag = fmt.Sprintf("%x-%x", fi.ModTime().Unix(), fi.Size()) return } func main() { var ( port int host string webDir string mapDir string ) flag.IntVar(&port, "port", 8808, "port of the web server") flag.IntVar(&port, "p", 8808, "port of the web server (shorthand)") flag.StringVar(&host, "host", "localhost", "address to bind") flag.StringVar(&host, "h", "localhost", "address to bind (shorthand)") flag.StringVar(&webDir, "web", "web", "static served web files.") flag.StringVar(&webDir, "w", "web", "static served web files (shorthand)") flag.StringVar(&mapDir, "map", "map", "directory of prerendered tiles") flag.StringVar(&mapDir, "m", "map", "directory of prerendered tiles (shorthand)") flag.Parse() r := mux.NewRouter() r.HandleFunc("/map/{z:[0-9]+}/{x:[0-9]+}/{y:[0-9]+}.png", func(rw http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) xs := vars["x"] ys := vars["y"] zs := vars["z"] x, y, z := toUint(xs), toUint(ys), toUint(zs) if z < 9 { filename := fmt.Sprintf("%d/%d/%d.png", z, x, y) http.ServeFile(rw, r, filepath.Join(mapDir, filename)) return } if z > 16 { z = 16 } tx := x >> (z - 8) ty := y >> (z - 8) baseTile := filepath.Join(mapDir, fmt.Sprintf("8/%d/%d.png", tx, ty)) var etag string var err error if ifNoneMatch := r.Header.Get("If-None-Match"); ifNoneMatch != "" { if etag, err = createETag(baseTile); err == nil { if ifNoneMatch == etag { http.Error(rw, http.StatusText(http.StatusNotModified), http.StatusNotModified) return } } } img := common.LoadPNG(baseTile) type subImage interface { image.Image SubImage(image.Rectangle) image.Image } var si subImage var ok bool if si, ok = img.(subImage); !ok { // Should not happen. http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } rx := x & ^(^uint(0) << (z - 8)) ry := y & ^(^uint(0) << (z - 8)) parts := uint(1) << (z - 8) w := uint(256) / parts xo := w * rx yo := w * (parts - 1 - ry) //fmt.Printf("x %d\n", x) //fmt.Printf("y %d\n", y) //fmt.Printf("z %d\n", z) //fmt.Printf("z %d\n", z) //fmt.Printf("parts %d\n", parts) //fmt.Printf("w %d\n", w) //fmt.Printf("xo %d\n", xo) //fmt.Printf("yo %d\n", yo) //fmt.Printf("rx %d\n", rx) //fmt.Printf("ry %d\n", ry) img = si.SubImage(image.Rect(int(xo), int(yo), int(xo+w), int(yo+w))) img = blowUp(img) rw.Header().Set("Content-Type", "image/png") if etag == "" { if etag, err = createETag(baseTile); err != nil { log.Printf("Cannot create ETag: %s", baseTile) } else { rw.Header().Set("ETag", etag) } } else { rw.Header().Set("ETag", etag) } if err = png.Encode(rw, img); err != nil { log.Printf("WARN: encoding image failed: %s", err) return } }) r.PathPrefix("/").Handler(http.FileServer(http.Dir(webDir))) http.Handle("/", r) addr := fmt.Sprintf("%s:%d", host, port) if err := http.ListenAndServe(addr, nil); err != nil { log.Fatalf("Starting server failed: %s\n", err) } }