1
0
forked from MTSR/mapserver
mapserver/tilerenderer/renderer.go
2023-12-29 16:00:11 +01:00

249 lines
5.5 KiB
Go

package tilerenderer
import (
"bytes"
"errors"
"image"
"image/draw"
"image/png"
"mapserver/coords"
"mapserver/db"
"mapserver/eventbus"
"mapserver/mapblockrenderer"
"mapserver/tiledb"
"mapserver/types"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
)
type TileRenderer struct {
mapblockrenderer *mapblockrenderer.MapBlockRenderer
layers []*types.Layer
tdb *tiledb.TileDB
dba db.DBAccessor
Eventbus *eventbus.Eventbus
}
func resizeImage(src *image.NRGBA, tgt *image.NRGBA, xoffset int, yoffset int) {
if src == nil {
return
}
w := src.Bounds().Dy() >> 1
h := src.Bounds().Dx() >> 1
sinc := src.Bounds().Dy() * 4
tinc := tgt.Bounds().Dx() * 4
for y := 0; y < h; y++ {
six := y * sinc * 2
tix := 4*xoffset + (yoffset+y)*tinc
for x := 0; x < w; x++ {
r := (uint16(src.Pix[six]) + uint16(src.Pix[six+4]) + uint16(src.Pix[six+sinc]) + uint16(src.Pix[six+sinc+4])) >> 2
g := (uint16(src.Pix[six+1]) + uint16(src.Pix[six+5]) + uint16(src.Pix[six+sinc+1]) + uint16(src.Pix[six+sinc+5])) >> 2
b := (uint16(src.Pix[six+2]) + uint16(src.Pix[six+6]) + uint16(src.Pix[six+sinc+2]) + uint16(src.Pix[six+sinc+6])) >> 2
a := (uint16(src.Pix[six+3]) + uint16(src.Pix[six+7]) + uint16(src.Pix[six+sinc+3]) + uint16(src.Pix[six+sinc+7])) >> 2
tgt.Pix[tix] = uint8(r)
tgt.Pix[tix+1] = uint8(g)
tgt.Pix[tix+2] = uint8(b)
tgt.Pix[tix+3] = uint8(a)
tix += 4
six += 8
}
}
}
func NewTileRenderer(mapblockrenderer *mapblockrenderer.MapBlockRenderer,
tdb *tiledb.TileDB,
dba db.DBAccessor,
layers []*types.Layer) *TileRenderer {
return &TileRenderer{
mapblockrenderer: mapblockrenderer,
layers: layers,
tdb: tdb,
dba: dba,
Eventbus: eventbus.New(),
}
}
const (
IMG_SIZE = 256
SUB_IMG_SIZE = IMG_SIZE >> 1
)
func (tr *TileRenderer) Render(tc *coords.TileCoords) error {
//No tile in db
_, err := tr.renderImage(tc, 2)
if err != nil {
return err
}
return nil
}
func (tr *TileRenderer) renderImage(tc *coords.TileCoords, recursionDepth int) (*image.NRGBA, error) {
if recursionDepth < 2 {
cachedtile, err := tr.tdb.GetTile(tc)
if err != nil {
return nil, err
}
if cachedtile != nil {
reader := bytes.NewReader(cachedtile)
cachedimg, err := png.Decode(reader)
if err != nil {
return 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, 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
}
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 := types.FindLayerById(tr.layers, tc.LayerId)
if currentLayer == nil {
return nil, errors.New("no layer found")
}
if tc.Zoom > 13 || tc.Zoom < 1 {
return 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, err
}
if img == nil {
return nil, nil
}
buf := new(bytes.Buffer)
png.Encode(buf, img)
return img, 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, err
}
upperRight, err := tr.renderImage(quads.UpperRight, recursionDepth-1)
if err != nil {
return nil, err
}
lowerLeft, err := tr.renderImage(quads.LowerLeft, recursionDepth-1)
if err != nil {
return nil, err
}
lowerRight, err := tr.renderImage(quads.LowerRight, recursionDepth-1)
if err != nil {
return 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},
},
)
resizeImage(upperLeft, img, 0, 0)
resizeImage(upperRight, img, SUB_IMG_SIZE, 0)
resizeImage(lowerLeft, img, 0, SUB_IMG_SIZE)
resizeImage(lowerRight, img, SUB_IMG_SIZE, SUB_IMG_SIZE)
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, nil
}