Skip to content

Commit

Permalink
Refactor Mbtiles into own package, rename to DB
Browse files Browse the repository at this point in the history
Updates consbio#40
  • Loading branch information
fawick committed Oct 28, 2017
1 parent 26cbf59 commit 4f1f36c
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 59 deletions.
9 changes: 5 additions & 4 deletions arcgis.go
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strings"

"github.com/consbio/mbtileserver/mbtiles"
log "github.com/sirupsen/logrus"

"github.com/golang/groupcache"
Expand Down Expand Up @@ -85,7 +86,7 @@ func GetArcGISService(c echo.Context) error {
}

tileset := tilesets[id]
imgFormat := TileFormatStr[tileset.tileformat]
imgFormat := tileset.TileFormatString()
metadata, err := tileset.ReadMetadata()
if err != nil {
log.Errorf("Could not read metadata for tileset %v", id)
Expand Down Expand Up @@ -284,7 +285,7 @@ func GetArcGISTile(c echo.Context) error {
tileset := tilesets[id]

if len(data) <= 1 {
if tileset.tileformat == PBF {
if tileset.TileFormat() == mbtiles.PBF {
// If pbf, return 404 w/ json, consistent w/ mapbox
return c.JSON(http.StatusNotFound, struct {
Message string `json:"message"`
Expand All @@ -294,13 +295,13 @@ func GetArcGISTile(c echo.Context) error {
data = blankPNG
contentType = "image/png"
} else {
contentType = TileContentType[tileset.tileformat]
contentType = tileset.ContentType()
}

res := c.Response()
res.Header().Add("Content-Type", contentType)

if tileset.tileformat == PBF {
if tileset.TileFormat() == mbtiles.PBF {
res.Header().Add("Content-Encoding", "gzip")
}

Expand Down
32 changes: 17 additions & 15 deletions main.go
Expand Up @@ -24,6 +24,8 @@ import (
_ "github.com/mattn/go-sqlite3"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/consbio/mbtileserver/mbtiles"
)

type ServiceInfo struct {
Expand All @@ -47,7 +49,7 @@ type TemplateParams struct {
var (
blankPNG []byte
cache *groupcache.Group
tilesets map[string]Mbtiles
tilesets map[string]mbtiles.DB
startuptime = time.Now()
)

Expand Down Expand Up @@ -160,7 +162,7 @@ func serve() {

log.Infof("Found %v mbtiles files in %s\n", len(filenames), tilePath)

tilesets = make(map[string]Mbtiles)
tilesets = make(map[string]mbtiles.DB)
urlDirs := make(map[string]bool)
for _, filename := range filenames {
subpath, err := filepath.Rel(tilePath, filename)
Expand All @@ -175,7 +177,7 @@ func serve() {
dir := strings.Join(parts[:len(parts)-1], "/")
urlDirs[dir] = true

tileset, err := NewMbtiles(filename)
tileset, err := mbtiles.NewDB(filename)
if err != nil {
log.Errorf("could not open mbtiles file: %s\n%v", filename, err)
continue
Expand Down Expand Up @@ -308,7 +310,7 @@ func cacheGetter(ctx groupcache.Context, key string, dest groupcache.Sink) error
log.Errorf("Error encountered reading tile for z=%v, x=%v, y=%v, \n%v", z, x, y, err)
return err
}
} else if tileType == "grid" && tileset.hasUTFGrid {
} else if tileType == "grid" && tileset.HasUTFGrid() {
err := tileset.ReadGrid(z, x, y, &data)
if err != nil {
log.Errorf("Error encountered reading grid for z=%v, x=%v, y=%v, \n%v", z, x, y, err)
Expand Down Expand Up @@ -357,7 +359,7 @@ func ListServices(c echo.Context) error {
i := 0
for id, tileset := range tilesets {
services[i] = ServiceInfo{
ImageType: TileFormatStr[tileset.tileformat],
ImageType: tileset.TileFormatString(),
URL: fmt.Sprintf("%s/%s", rootURL, id),
}
i++
Expand All @@ -376,7 +378,7 @@ func GetServiceInfo(c echo.Context) error {
svcURL := fmt.Sprintf("%s%s", getRootURL(c), c.Request().URL)

tileset := tilesets[id]
imgFormat := TileFormatStr[tileset.tileformat]
imgFormat := tileset.TileFormatString()

out := map[string]interface{}{
"tilejson": "2.1.0",
Expand Down Expand Up @@ -412,7 +414,7 @@ func GetServiceInfo(c echo.Context) error {
}
}

if tileset.hasUTFGrid {
if tileset.HasUTFGrid() {
out["grids"] = []string{fmt.Sprintf("%s/tiles/{z}/{x}/{y}.json", svcURL)}
}

Expand All @@ -430,7 +432,7 @@ func GetServiceHTML(c echo.Context) error {
ID: id,
}

if tilesets[id].tileformat == PBF {
if tilesets[id].TileFormat() == mbtiles.PBF {
return c.Render(http.StatusOK, "map_gl", p)
}

Expand Down Expand Up @@ -466,15 +468,15 @@ func GetTile(c echo.Context) error {
res := c.Response()

if data == nil || len(data) <= 1 {
switch tileset.tileformat {
case PNG, JPG, WEBP:
switch tileset.TileFormat() {
case mbtiles.PNG, mbtiles.JPG, mbtiles.WEBP:
// Return blank PNG for all image types
res.Header().Add("Content-Type", "image/png")
res.WriteHeader(http.StatusOK)
_, err = io.Copy(res, bytes.NewReader(blankPNG))
return err

case PBF:
case mbtiles.PBF:
// Return 204
res.WriteHeader(http.StatusNoContent) // this must be after setting other headers
return nil
Expand All @@ -490,15 +492,15 @@ func GetTile(c echo.Context) error {
if tileType == "grid" {
contentType = "application/json"

if tileset.utfgridCompression == ZLIB {
if tileset.UTFGridCompression() == mbtiles.ZLIB {
res.Header().Add("Content-Encoding", "deflate")
} else {
res.Header().Add("Content-Encoding", "gzip")
}
} else {
contentType = TileContentType[tileset.tileformat]
contentType = tileset.ContentType()

if tileset.tileformat == PBF {
if tileset.TileFormat() == mbtiles.PBF {
res.Header().Add("Content-Encoding", "gzip")
}
}
Expand All @@ -525,7 +527,7 @@ func NotModifiedMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
var lastModified time.Time
id := c.Param("id")
if _, exists := tilesets[id]; exists {
lastModified = tilesets[id].timestamp
lastModified = tilesets[id].TimeStamp()
} else {
lastModified = startuptime // startup time of server
}
Expand Down
6 changes: 2 additions & 4 deletions main_test.go
@@ -1,11 +1,9 @@
package main

import (
"net/http"
"testing"
//
"github.com/labstack/echo"
"github.com/labstack/echo/engine/standard"
//"github.com/labstack/echo/test"
//"github.com/stretchr/testify/assert"
// "net/http/httptest"
Expand All @@ -25,9 +23,9 @@ func TestGetServices(t *testing.T) {
//assert := assert.New(t)

e := echo.New()
req := new(http.Request)
req := httptest.NewRequest("GET", "/services", nil)
rec := httptest.NewRecorder()
c := e.NewContext(standard.NewRequest(req, e.Logger()), standard.NewResponse(rec, e.Logger()))
c := e.NewContext(req, rec)

//c := e.NewContext(standard.NewRequest(req), standard.NewResponse(rec))

Expand Down
107 changes: 84 additions & 23 deletions mbtiles.go → mbtiles/mbtiles.go
@@ -1,4 +1,4 @@
package main
package mbtiles

import (
"bytes"
Expand All @@ -7,6 +7,7 @@ import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
Expand All @@ -29,21 +30,37 @@ const (
WEBP
)

var TileFormatStr = map[TileFormat]string{
PNG: "png",
JPG: "jpg",
PBF: "pbf",
WEBP: "webp",
func (t TileFormat) String() string {
switch t {
case PNG:
return "png"
case JPG:
return "jpg"
case PBF:
return "pbf"
case WEBP:
return "webp"
default:
return ""
}
}

var TileContentType = map[TileFormat]string{
PNG: "image/png",
JPG: "image/jpeg",
PBF: "application/x-protobuf", // Content-Encoding header must be gzip
WEBP: "image/webp",
func (t TileFormat) ContentType() string {
switch t {
case PNG:
return "image/png"
case JPG:
return "image/jpeg"
case PBF:
return "application/x-protobuf" // Content-Encoding header must be gzip
case WEBP:
return "image/webp"
default:
return ""
}
}

type Mbtiles struct {
type DB struct {
filename string
db *sql.DB
tileformat TileFormat // tile format: PNG, JPG, PBF
Expand All @@ -53,9 +70,9 @@ type Mbtiles struct {
hasUTFGridData bool
}

// Creates a new Mbtiles instance.
// Creates a new DB instance.
// Connection is closed by runtime on application termination or by calling .Close() method.
func NewMbtiles(filename string) (*Mbtiles, error) {
func NewDB(filename string) (*DB, error) {
_, id := filepath.Split(filename)
id = strings.Split(id, ".")[0]

Expand Down Expand Up @@ -84,7 +101,7 @@ func NewMbtiles(filename string) (*Mbtiles, error) {
if tileformat == GZIP {
tileformat = PBF // GZIP masks PBF, which is only expected type for tiles in GZIP format
}
out := Mbtiles{
out := DB{
db: db,
tileformat: tileformat,
timestamp: fileStat.ModTime().Round(time.Second), // round to nearest second
Expand Down Expand Up @@ -132,7 +149,7 @@ func NewMbtiles(filename string) (*Mbtiles, error) {
}

// Reads a grid at z, x, y into provided *[]byte.
func (tileset *Mbtiles) ReadTile(z uint8, x uint64, y uint64, data *[]byte) error {
func (tileset *DB) ReadTile(z uint8, x uint64, y uint64, data *[]byte) error {
err := tileset.db.QueryRow("select tile_data from tiles where zoom_level = ? and tile_column = ? and tile_row = ?", z, x, y).Scan(data)
if err != nil {
if err == sql.ErrNoRows {
Expand All @@ -147,7 +164,7 @@ func (tileset *Mbtiles) ReadTile(z uint8, x uint64, y uint64, data *[]byte) erro
// Reads a grid at z, x, y into provided *[]byte.
// This merges in grid key data, if any exist
// The data is returned in the original compression encoding (zlib or gzip)
func (tileset *Mbtiles) ReadGrid(z uint8, x uint64, y uint64, data *[]byte) error {
func (tileset *DB) ReadGrid(z uint8, x uint64, y uint64, data *[]byte) error {
if !tileset.hasUTFGrid {
return errors.New("Tileset does not contain UTFgrids")
}
Expand Down Expand Up @@ -234,7 +251,7 @@ func (tileset *Mbtiles) ReadGrid(z uint8, x uint64, y uint64, data *[]byte) erro
}

// Read the metadata table into a map, casting their values into the appropriate type
func (tileset *Mbtiles) ReadMetadata() (map[string]interface{}, error) {
func (tileset *DB) ReadMetadata() (map[string]interface{}, error) {
var (
key string
value string
Expand All @@ -252,11 +269,20 @@ func (tileset *Mbtiles) ReadMetadata() (map[string]interface{}, error) {

switch key {
case "maxzoom", "minzoom":
metadata[key], _ = strconv.Atoi(value)
metadata[key], err = strconv.Atoi(value)
if err != nil {
return nil, fmt.Errorf("cannot read metadata item %s: %v", key, err)
}
case "bounds", "center":
metadata[key] = stringToFloats(value)
metadata[key], err = stringToFloats(value)
if err != nil {
return nil, fmt.Errorf("cannot read metadata item %s: %v", key, err)
}
case "json":
json.Unmarshal([]byte(value), &metadata)
err = json.Unmarshal([]byte(value), &metadata)
if err != nil {
return nil, fmt.Errorf("unable to parse JSON metadata item: %v", err)
}
default:
metadata[key] = value
}
Expand All @@ -278,8 +304,43 @@ func (tileset *Mbtiles) ReadMetadata() (map[string]interface{}, error) {
return metadata, nil
}

// Close the MBtiles database connection
func (tileset *Mbtiles) Close() error {
// TileFormatreturns the TileFormat of the DB.
func (d DB) TileFormat() TileFormat {
return d.tileformat
}

// TileFormatString returns the string representation of the TileFormat of the DB.
func (d DB) TileFormatString() string {
return d.tileformat.String()
}

// ContentType returns the content-type string of the TileFormat of the DB.
func (d DB) ContentType() string {
return d.tileformat.ContentType()
}

// HasUTFGrid returns whether the DB has a UTF grid.
func (d DB) HasUTFGrid() bool {
return d.hasUTFGrid
}

// HasUTFGridData returns whether the DB has UTF grid data.
func (d DB) HasUTFGridData() bool {
return d.hasUTFGridData
}

// UTFGridCompression returns whether the TileFormat of the UTF Grid compression of the DB.
func (d DB) UTFGridCompression() TileFormat {
return d.utfgridCompression
}

// TimeStamp returns the time stamp of the DB.
func (d DB) TimeStamp() time.Time {
return d.timestamp
}

// Close closes the DB database connection
func (tileset *DB) Close() error {
return tileset.db.Close()
}

Expand Down
File renamed without changes.
File renamed without changes.
22 changes: 22 additions & 0 deletions mbtiles/utils.go
@@ -0,0 +1,22 @@
package mbtiles

import (
"fmt"
"strconv"
"strings"
)

// stringToFloats converts a commma-delimited string of floats to a slice of
// float64 and returns it and the first error that was encountered.
func stringToFloats(str string) ([]float64, error) {
split := strings.Split(str, ",")
var out []float64
for _, v := range split {
value, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
if err != nil {
return out, fmt.Errorf("could not parse %q to floats: %v", str, err)
}
out = append(out, value)
}
return out, nil
}

0 comments on commit 4f1f36c

Please sign in to comment.