package tilerenderer import ( "bytes" "errors" "image" "image/draw" "image/png" "mapserver/coords" "mapserver/db" "mapserver/eventbus" "mapserver/layer" "mapserver/mapblockrenderer" "mapserver/tiledb" "strconv" "time" "github.com/disintegration/imaging" "github.com/sirupsen/logrus" "github.com/prometheus/client_golang/prometheus" ) type TileRenderer struct { mapblockrenderer *mapblockrenderer.MapBlockRenderer layers []*layer.Layer tdb *tiledb.TileDB dba db.DBAccessor Eventbus *eventbus.Eventbus } func NewTileRenderer(mapblockrenderer *mapblockrenderer.MapBlockRenderer, tdb *tiledb.TileDB, dba db.DBAccessor, layers []*layer.Layer) *TileRenderer { return &TileRenderer{ mapblockrenderer: mapblockrenderer, layers: layers, tdb: tdb, dba: dba, Eventbus: eventbus.New(), } } const ( IMG_SIZE = 256 ) func (tr *TileRenderer) Render(tc *coords.TileCoords) ([]byte, error) { //No tile in db img, data, err := tr.renderImage(tc, 2) if err != nil { return nil, err } if img == nil { //empty tile return nil, nil } return data, nil } func (tr *TileRenderer) renderImage(tc *coords.TileCoords, recursionDepth int) (*image.NRGBA, []byte, error) { if recursionDepth < 2 { cachedtile, err := tr.tdb.GetTile(tc) if err != nil { return nil, nil, err } if cachedtile != nil { reader := bytes.NewReader(cachedtile) cachedimg, err := png.Decode(reader) if err != nil { return nil, nil, err } rect := image.Rectangle{ image.Point{0, 0}, image.Point{IMG_SIZE, IMG_SIZE}, } img := image.NewNRGBA(rect) draw.Draw(img, rect, cachedimg, image.ZP, draw.Src) log.WithFields(logrus.Fields{"x": tc.X, "y": tc.Y, "zoom": tc.Zoom}).Debug("Cached image") return img, cachedtile, nil } } if recursionDepth <= 1 && tc.Zoom < 13 { //non-cached layer and not in "origin" zoom, skip tile log.WithFields(logrus.Fields{"x": tc.X, "y": tc.Y, "zoom": tc.Zoom}).Debug("Skip image") return nil, nil, nil } log.WithFields(logrus.Fields{"x": tc.X, "y": tc.Y, "zoom": tc.Zoom}).Debug("RenderImage") renderedTiles.With(prometheus.Labels{"zoom": strconv.Itoa(tc.Zoom)}).Inc() timer := prometheus.NewTimer(renderDuration) defer timer.ObserveDuration() currentLayer := layer.FindLayerById(tr.layers, tc.LayerId) if currentLayer == nil { return nil, nil, errors.New("No layer found") } if tc.Zoom > 13 || tc.Zoom < 1 { return nil, nil, errors.New("Invalid zoom") } if tc.Zoom == 13 { //max zoomed in on mapblock level mbr := coords.GetMapBlockRangeFromTile(tc, 0) mbr.Pos1.Y = currentLayer.From mbr.Pos2.Y = currentLayer.To img, err := tr.mapblockrenderer.Render(mbr.Pos1, mbr.Pos2) if err != nil { fields := logrus.Fields{ "pos1": mbr.Pos1, "pos2": mbr.Pos2, "err": err, } log.WithFields(fields).Debug("mapblock render from tilerender") return nil, nil, err } if img == nil { return nil, nil, nil } buf := new(bytes.Buffer) png.Encode(buf, img) return img, buf.Bytes(), nil } //zoom 1-12 quads := tc.GetZoomedQuadrantsFromTile() fields := logrus.Fields{ "UpperLeft": quads.UpperLeft, "UpperRight": quads.UpperRight, "LowerLeft": quads.LowerLeft, "LowerRight": quads.LowerRight, } log.WithFields(fields).Debug("Quad image stats") start := time.Now() upperLeft, _, err := tr.renderImage(quads.UpperLeft, recursionDepth-1) if err != nil { return nil, nil, err } upperRight, _, err := tr.renderImage(quads.UpperRight, recursionDepth-1) if err != nil { return nil, nil, err } lowerLeft, _, err := tr.renderImage(quads.LowerLeft, recursionDepth-1) if err != nil { return nil, nil, err } lowerRight, _, err := tr.renderImage(quads.LowerRight, recursionDepth-1) if err != nil { return nil, nil, err } t := time.Now() quadrender := t.Sub(start) start = t img := image.NewNRGBA( image.Rectangle{ image.Point{0, 0}, image.Point{IMG_SIZE, IMG_SIZE}, }, ) rect := image.Rect(0, 0, 128, 128) if upperLeft != nil { resizedImg := imaging.Resize(upperLeft, 128, 128, imaging.Lanczos) draw.Draw(img, rect, resizedImg, image.ZP, draw.Src) } rect = image.Rect(128, 0, 256, 128) if upperRight != nil { resizedImg := imaging.Resize(upperRight, 128, 128, imaging.Lanczos) draw.Draw(img, rect, resizedImg, image.ZP, draw.Src) } rect = image.Rect(0, 128, 128, 256) if lowerLeft != nil { resizedImg := imaging.Resize(lowerLeft, 128, 128, imaging.Lanczos) draw.Draw(img, rect, resizedImg, image.ZP, draw.Src) } rect = image.Rect(128, 128, 256, 256) if lowerRight != nil { resizedImg := imaging.Resize(lowerRight, 128, 128, imaging.Lanczos) draw.Draw(img, rect, resizedImg, image.ZP, draw.Src) } t = time.Now() quadresize := t.Sub(start) start = t buf := new(bytes.Buffer) png.Encode(buf, img) t = time.Now() encode := t.Sub(start) start = t tile := buf.Bytes() tr.tdb.SetTile(tc, tile) t = time.Now() cache := t.Sub(start) fields = logrus.Fields{ "X": tc.X, "Y": tc.Y, "Zoom": tc.Zoom, "LayerId": tc.LayerId, "size": len(tile), "quadrender": quadrender, "quadresize": quadresize, "encode": encode, "cache": cache, } log.WithFields(fields).Debug("Cross stitch") tr.Eventbus.Emit(eventbus.TILE_RENDERED, tc) return img, buf.Bytes(), nil }