Skip to content

Commit

Permalink
Extract the FS caching into its own file
Browse files Browse the repository at this point in the history
Rather than bundling the FS caching in with the VIPS implementation, it
makes more sense to have it as a middleware-like service. I'm not sure
if this is the best way to achieve it, but it'll do for now.
  • Loading branch information
andrewdodd committed Jul 28, 2019
1 parent fc1fb6f commit 686683a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 22 deletions.
110 changes: 110 additions & 0 deletions mapimage/fscached.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package mapimage

import (
"bytes"
"fmt"
"golang.org/x/image/draw"
"image"
"image/color"
_ "image/jpeg"
"image/png"
"io"
"io/ioutil"
"log"
"math"
"os"
)

type cached struct {
mi MapImage
}

func FilesystemCachedImage(mi MapImage) MapImage {
return &cached{mi: mi}
}

func (i cached) Id() string {
return i.mi.Id()
}
func (i cached) Text() string {
return i.mi.Text()
}
func (i cached) GeoBounds() [2]LatLng {
return i.mi.GeoBounds()
}

func (i cached) PixelBounds() [2]LatLng {
return i.mi.PixelBounds()
}

func (i cached) MinZoom() int {
return i.mi.MinZoom()
}
func (i cached) MaxZoom() int {
return i.mi.MaxZoom()
}

func (i cached) GeoFromPixel(p LatLng) LatLng {
return i.mi.GeoFromPixel(p)
}

func (i cached) PixelFromGeo(p LatLng) LatLng {
return i.mi.PixelFromGeo(p)
}

func (i cached) ImageContent() io.ReadSeeker {
return i.mi.ImageContent()
}

func (i cached) MapTile(zoom, x, y int64) io.ReadSeeker {
tileMin, tileMax := TileLatLonBounds(x, y, zoom)
pxlMin := i.mi.PixelFromGeo(LatLng(tileMin))
pxlMax := i.mi.PixelFromGeo(LatLng(tileMax))

tileSize := image.Rect(0, 0, 256, 256)
tileRect := image.Rect(
int(math.Round(pxlMax.Lng)),
int(math.Round(pxlMax.Lat)),
int(math.Round(pxlMin.Lng)),
int(math.Round(pxlMin.Lat)),
)

pixelBounds := i.mi.PixelBounds()
imgBounds := image.Rect(
int(pixelBounds[0].Lng), int(pixelBounds[0].Lat),
int(pixelBounds[1].Lng), int(pixelBounds[1].Lat),
)
// If the requested area is not inside the map image,
// then just return a black square from ram
if !imgBounds.Overlaps(tileRect) {
img := image.NewRGBA(tileSize)
draw.Draw(img, img.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src)
w := bytes.Buffer{}
png.Encode(&w, img)
return bytes.NewReader(w.Bytes())
}

// Check if file already exists
path := fmt.Sprintf("./media/%s/%d/%d", i.mi.Id(), zoom, x)
filename := fmt.Sprintf("%s/%d", path, y)
if buf, err := ioutil.ReadFile(filename); err == nil {
return bytes.NewReader(buf)
}

// Produce the image with the underlying MapImage implementation
img := i.mi.MapTile(zoom, x, y)

err := os.MkdirAll(path, 0777)
if err != nil {
log.Println("path", err)
return colorTile(zoom, x, y)
}

// Pay the cost of putting on the filesystem now
w := bytes.Buffer{}
w.ReadFrom(img)

ioutil.WriteFile(filename, w.Bytes(), 0777)

return bytes.NewReader(w.Bytes())
}
23 changes: 1 addition & 22 deletions mapimage/libvips.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package mapimage

import (
"bytes"
"fmt"
"github.com/h2non/bimg"
"golang.org/x/image/draw"
"image"
Expand Down Expand Up @@ -143,19 +142,6 @@ func (ii libvipsImage) MapTile(zoom, x, y int64) io.ReadSeeker {
return bytes.NewReader(w.Bytes())
}

// Check if file already exists
// FWIW...it is probably better to change this into its own middlewaresque
// functionality / MapImage implementation. That way any MapImage implementation could
// also cache its results to disk (and thus trade computation for storage).
// Maybe I'll do that in the future, but for now I'll just leave it here (VIPS has
// a concurrent limit that is quite obvious if you don't do this, so I think it
// always makes sense here)
path := fmt.Sprintf("./media/%s/%d/%d", ii.id, zoom, x)
filename := fmt.Sprintf("%s/%d", path, y)
if buf, err := ioutil.ReadFile(filename); err == nil {
return bytes.NewReader(buf)
}

srcRect := image.Rect(
max(tileRect.Min.X, imgBounds.Min.X),
max(tileRect.Min.Y, imgBounds.Min.Y),
Expand Down Expand Up @@ -196,6 +182,7 @@ func (ii libvipsImage) MapTile(zoom, x, y int64) io.ReadSeeker {
return colorTile(zoom, x, y)
}

// change into an in-memory golang image (after a libvips one)
srcImage, _, err := image.Decode(bytes.NewReader(newImage))
if err != nil {
log.Println("decode image", err)
Expand All @@ -208,16 +195,8 @@ func (ii libvipsImage) MapTile(zoom, x, y int64) io.ReadSeeker {
scaler := draw.ApproxBiLinear
scaler.Scale(img, dstRect, srcImage, srcImage.Bounds(), draw.Over, nil)

err = os.MkdirAll(path, 0777)
if err != nil {
log.Println("path", err)
return colorTile(zoom, x, y)
}

w := bytes.Buffer{}
png.Encode(&w, img)

ioutil.WriteFile(filename, w.Bytes(), 0777)

return bytes.NewReader(w.Bytes())
}
1 change: 1 addition & 0 deletions mapimage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type MapImage interface {
MaxZoom() int
//ReferencePoints() []MapImagePair
GeoFromPixel(p LatLng) LatLng
PixelFromGeo(p LatLng) LatLng
}

type MapImagesSource interface {
Expand Down
2 changes: 2 additions & 0 deletions serverd.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ func init() {
f)
log.Printf(" >> MinZoom: %v MaxZoom: %v\n", mi.MinZoom(), mi.MaxZoom())
}

mi = mapimage.FilesystemCachedImage(mi)
maps.images = append(maps.images, mi)
}
} else {
Expand Down

0 comments on commit 686683a

Please sign in to comment.