mtsatellite/cmd/mtwebmapper/subbaseline.go
Sascha L. Teichmann 0db9b519a6 Implement fetaure request issue #17
mtseeder and mtwebmapper got an option to set the background color where no nodes are generated, yet.
2016-04-23 16:45:33 +02:00

228 lines
4.9 KiB
Go

// 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)
}