// Copyright 2014, 2015 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" "time" "bitbucket.org/s_l_teichmann/mtsatellite/common" "github.com/gorilla/mux" ) type subBaseLine struct { mapDir string bg color.RGBA } func newSubBaseLine(mapDir string, bg color.RGBA) *subBaseLine { return &subBaseLine{mapDir: mapDir, bg: bg} } func (sb *subBaseLine) ServeHTTP(rw http.ResponseWriter, r *http.Request) { rw.Header().Set("Cache-Control", "max-age=0, no-cache, no-store") 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 { http.ServeFile(rw, r, filepath.Join(sb.mapDir, strconv.Itoa(int(z)), strconv.Itoa(int(x)), strconv.Itoa(int(y))+".png")) return } if z > 16 { z = 16 } tx := x >> (z - 8) ty := y >> (z - 8) baseTile := filepath.Join( sb.mapDir, "8", strconv.Itoa(int(tx)), strconv.Itoa(int(ty))+".png") var err error var fi os.FileInfo if fi, err = os.Stat(baseTile); err != nil { http.NotFound(rw, r) return } if checkLastModified(rw, r, fi.ModTime()) || checkETag(rw, r, fi) { 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, sb.bg) 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) return } img = blowUp(img) rw.Header().Set("Content-Type", "image/png") if err = png.Encode(rw, img); err != nil { log.Printf("WARN: encoding image failed: %s\n", err) } } func blowUp(src image.Image) *image.RGBA { // Fast path for RGBA -> RGBA if rgba, ok := src.(*image.RGBA); ok { return blowUpRGBA(rgba) } // Fallback 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 //start := time.Now() pix := dst.Pix lineOfs := dst.PixOffset(0, 0) // Should be 0. py := by var r, g, b, a uint8 for y := 0; y < 256; y++ { sy := (py >> 8) & 0xff ox := -1 px := bx ofs := lineOfs // Should not really b needed lineOfs += dst.Stride for x := 0; x < 256; x++ { sx := (px >> 8) & 0xff if sx != ox { // Minimize interface indirection access. ox = sx xr, xg, xb, xa := src.At(sx, sy).RGBA() r, g, b, a = uint8(xr), uint8(xg), uint8(xb), uint8(xa) } pix[ofs] = r pix[ofs+1] = g pix[ofs+2] = b pix[ofs+3] = a ofs += 4 px += dx } py += dy } //log.Printf("Rendering took: %s\n", time.Since(start)) return dst } func blowUpRGBA(src *image.RGBA) *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 //start := time.Now() sPix := src.Pix dPix := dst.Pix py := by // Assuming memory layout is packed 256*256*4 with stride of 4*256. // for dLineOfs, dEnd := dst.PixOffset(0, 0), dst.PixOffset(0, 256); dLineOfs < dEnd; dLineOfs += dst.Stride { for ofs := 0; ofs < 256*256*4; { sy := (py >> 8) & 0xff sLineOfs := src.PixOffset(0, sy) px := bx // ofs := dLineOfs for end := ofs + 4*256; ofs < end; ofs += 4 { sOfs := sLineOfs + ((px >> 6) & 0x3fc) px += dx dPix[ofs] = sPix[sOfs] dPix[ofs+1] = sPix[sOfs+1] dPix[ofs+2] = sPix[sOfs+2] dPix[ofs+3] = sPix[sOfs+3] } py += dy } //log.Printf("Rendering took: %s\n", time.Since(start)) return dst } func checkETag(w http.ResponseWriter, r *http.Request, fi os.FileInfo) bool { etag := fmt.Sprintf("%x-%x", fi.ModTime().Unix(), fi.Size()) if ifNoneMatch := r.Header.Get("If-None-Match"); ifNoneMatch == etag { w.WriteHeader(http.StatusNotModified) return true } w.Header().Set("ETag", etag) return false } func checkLastModified(w http.ResponseWriter, r *http.Request, modtime time.Time) bool { if modtime.IsZero() { return false } // The Date-Modified header truncates sub-second precision, so // use mtime < t+1s instead of mtime <= t to check for unmodified. if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) { w.WriteHeader(http.StatusNotModified) return true } w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) return false } func toUint(s string) uint { x, err := strconv.Atoi(s) if err != nil { log.Printf("WARN: Cannot convert to int: %s\n", err) return 0 } return uint(x) }