// 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 ( "fmt" "image" "image/color" "image/png" "log" "net/http" "os" "path/filepath" "strconv" "bitbucket.org/s_l_teichmann/mtredisalize/common" "github.com/gorilla/mux" ) type subBaseLine struct { mapDir string } func newSubBaseLine(mapDir string) *subBaseLine { return &subBaseLine{mapDir: mapDir} } func (sb *subBaseLine) ServeHTTP(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(sb.mapDir, filename)) return } if z > 16 { z = 16 } tx := x >> (z - 8) ty := y >> (z - 8) baseTile := filepath.Join(sb.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 { http.NotFound(rw, r) return } if ifNoneMatch == etag { http.Error(rw, http.StatusText(http.StatusNotModified), http.StatusNotModified) 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) img := common.LoadPNG(baseTile) type subImage interface { SubImage(image.Rectangle) image.Image } if si, ok := img.(subImage); ok { img = si.SubImage(image.Rect(int(xo), int(yo), int(xo+w), int(yo+w))) } else { // Should not happen. http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } img = blowUp(img) rw.Header().Set("Content-Type", "image/png") if etag == "" { if etag, err = createETag(baseTile); err != nil { // Unlikely 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 } } 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 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 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) }