// 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" "path/filepath" "strconv" "bytes" "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 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 } //fmt.Printf("x %d\n", x) //fmt.Printf("y %d\n", y) //fmt.Printf("z %d\n", z) tx := x >> (z - 8) ty := y >> (z - 8) rx := x & ^(^uint(0) << (z - 8)) ry := y & ^(^uint(0) << (z - 8)) parts := uint(1) << (z - 8) //fmt.Printf("z %d\n", z) //fmt.Printf("parts %d\n", parts) w := uint(256) / parts xo := w * rx yo := w * (parts - 1 - ry) //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) baseTile := filepath.Join(mapDir, fmt.Sprintf("8/%d/%d.png", tx, ty)) 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.ServeFile(rw, r, filepath.Join(mapDir, baseTile)) return } img = si.SubImage(image.Rect(int(xo), int(yo), int(xo+w), int(yo+w))) img = blowUp(img) var buf bytes.Buffer if err := png.Encode(&buf, img); err != nil { http.ServeFile(rw, r, filepath.Join(mapDir, baseTile)) return } b := buf.Bytes() rw.Header().Set("Content-Type", "image/png") // Don't set the content length to use chunked mode. // rw.Header().Set("Content-Length", strconv.Itoa(len(b))) if _, err := rw.Write(b); err != nil { log.Printf("WARM: sending image failed: %s", err) } }) 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) } }