// 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 ( "encoding/json" "fmt" "image" "image/draw" "log" "net/http" "path/filepath" "strconv" "sync" "github.com/nfnt/resize" "bitbucket.org/s_l_teichmann/mtredisalize/common" ) type tileUpdater struct { changes map[xz]bool mapDir string redisAddress string colors *common.Colors workers int cond *sync.Cond mu sync.Mutex } type xz struct { X int16 Z int16 } type xzm struct { P xz Mask uint16 } func (c xz) quantize() xz { return xz{X: (c.X - -1933) / 16, Z: (c.Z - -1933) / 16} } func (c xz) dequantize() xz { return xz{X: c.X*16 + -1933, Z: c.Z*16 + -1933} } func (c xz) parent() xzm { xp, xr := c.X>>1, uint16(c.X&1) zp, zr := c.Z>>1, uint16(c.Z&1) return xzm{ P: xz{X: xp, Z: zp}, Mask: 1 << (xr<<1 | zr)} } func (c xzm) numChanges() (n int) { for i := uint16(0); i < 4; i++ { if c.Mask&(1< 0 { tu.cond.L.Lock() for _, c := range newChanges { tu.changes[c.quantize()] = true } tu.cond.L.Unlock() tu.cond.Signal() } rw.WriteHeader(http.StatusOK) } func (tu *tileUpdater) doUpdates() { for { var changes map[xz]bool tu.cond.L.Lock() for len(tu.changes) == 0 { tu.cond.Wait() } changes = tu.changes tu.changes = map[xz]bool{} tu.cond.L.Unlock() baseDir := filepath.Join(tu.mapDir, "8") jobs := make(chan xz) var done sync.WaitGroup for i, n := 0, min(tu.workers, len(changes)); i < n; i++ { var client *common.RedisClient var err error if client, err = common.NewRedisClient("tcp", tu.redisAddress); err != nil { log.Printf("WARN: Cannot connect to redis server: %s", err) continue } btc := common.NewBaseTileCreator(client, tu.colors, baseDir, true) done.Add(1) go updateBaseTiles(jobs, btc, &done) } parentJobs := make(map[xz]uint16) for c, _ := range changes { //log.Printf("job: %+v", c) jobs <- c pxz := c.parent() parentJobs[pxz.P] |= pxz.Mask } close(jobs) done.Wait() for level := 7; level >= 0; level-- { pJobs := make(chan xzm) for i, n := 0, min(len(parentJobs), tu.workers); i < n; i++ { done.Add(1) go updatePyramidTiles(level, tu.mapDir, pJobs, &done) } ppJobs := make(map[xz]uint16) for c, mask := range parentJobs { pJobs <- xzm{P: c, Mask: mask} pxz := c.parent() ppJobs[pxz.P] |= pxz.Mask } close(pJobs) done.Wait() parentJobs = ppJobs } } } func min(a, b int) int { if a < b { return a } return b } func updatePyramidTiles(level int, baseDir string, jobs chan xzm, done *sync.WaitGroup) { defer done.Done() scratch := image.NewRGBA(image.Rect(0, 0, 256, 256)) for job := range jobs { if err := updatePyramidTile(scratch, level, baseDir, job); err != nil { log.Printf("Updating pyramid tile failed: %s", err) } } } var dps = [4]image.Point{ image.Pt(0, 128), image.Pt(0, 0), image.Pt(128, 128), image.Pt(128, 0)} var ofs = [4][2]int{ {0, 0}, {1, 0}, {0, 1}, {1, 1}} func clip8(x int) int { switch { case x < 0: return 0 case x > 256: return 256 } return x } func clipRect(r image.Rectangle) image.Rectangle { return image.Rectangle{ Min: image.Point{X: clip8(r.Min.X), Y: clip8(r.Min.Y)}, Max: image.Point{X: clip8(r.Max.X), Y: clip8(r.Max.Y)}} } func updatePyramidTile(scratch *image.RGBA, level int, baseDir string, j xzm) error { var orig image.Image origPath := filepath.Join( baseDir, strconv.Itoa(level), strconv.Itoa(int(j.P.X)), fmt.Sprintf("%d.png", j.P.Z)) if j.numChanges() < 4 { orig = common.LoadPNG(origPath) } for i := uint16(0); i < 4; i++ { if j.Mask&(1<