├── .github └── workflows │ └── main.yml ├── .golangci.yml ├── LICENSE ├── README.md ├── cmd └── mbtiles-server │ └── main.go ├── go.mod ├── go.sum ├── mbtiles.go ├── reader.go ├── reader_test.go ├── testdata └── openstreetmap.org.mbtiles ├── types.go ├── writer.go └── writer_test.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v* 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 14 | - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed 15 | - run: go build ./... 16 | - run: go test ./... 17 | lint: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 21 | - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed 22 | - uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 23 | with: 24 | version: v1.62.2 25 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | go: '1.23' 3 | 4 | linters: 5 | enable: 6 | - asasalint 7 | - asciicheck 8 | - bidichk 9 | - bodyclose 10 | - canonicalheader 11 | - containedctx 12 | - contextcheck 13 | - copyloopvar 14 | - decorder 15 | - dogsled 16 | - dupl 17 | - dupword 18 | - durationcheck 19 | - err113 20 | - errcheck 21 | - errchkjson 22 | - errname 23 | - errorlint 24 | - fatcontext 25 | - forbidigo 26 | - forcetypeassert 27 | - gci 28 | - gocheckcompilerdirectives 29 | - gochecksumtype 30 | - gocritic 31 | - godot 32 | - gofmt 33 | - gofumpt 34 | - goheader 35 | - goimports 36 | - gomoddirectives 37 | - gomodguard 38 | - goprintffuncname 39 | - gosimple 40 | - gosmopolitan 41 | - govet 42 | - grouper 43 | - iface 44 | - importas 45 | - inamedparam 46 | - ineffassign 47 | - interfacebloat 48 | - intrange 49 | - ireturn 50 | - loggercheck 51 | - makezero 52 | - mirror 53 | - misspell 54 | - musttag 55 | - nilerr 56 | - nolintlint 57 | - nosprintfhostport 58 | - perfsprint 59 | - prealloc 60 | - predeclared 61 | - promlinter 62 | - protogetter 63 | - reassign 64 | - revive 65 | - rowserrcheck 66 | - sloglint 67 | - spancheck 68 | - sqlclosecheck 69 | - staticcheck 70 | - stylecheck 71 | - tagalign 72 | - tenv 73 | - testableexamples 74 | - testifylint 75 | - thelper 76 | - typecheck 77 | - unconvert 78 | - unparam 79 | - unused 80 | - usestdlibvars 81 | - wastedassign 82 | - whitespace 83 | - zerologlint 84 | disable: 85 | - cyclop 86 | - depguard 87 | - exhaustive 88 | - exhaustruct 89 | - funlen 90 | - ginkgolinter 91 | - gochecknoglobals 92 | - gochecknoinits 93 | - gocognit 94 | - goconst 95 | - gocyclo 96 | - godox 97 | - gosec 98 | - lll 99 | - maintidx 100 | - nakedret 101 | - nestif 102 | - nilnil 103 | - nlreturn 104 | - noctx 105 | - nonamedreturns 106 | - paralleltest 107 | - recvcheck 108 | - tagliatelle 109 | - testpackage 110 | - tparallel 111 | - varnamelen 112 | - wrapcheck 113 | - wsl 114 | 115 | linters-settings: 116 | forbidigo: 117 | forbid: 118 | - ^archive/zip\. 119 | - ^compress/gzip\. 120 | - ^fmt\.Print.*$ 121 | - ^ioutil\..*$ 122 | - ^os\.(DirEntry|ErrExist|ErrNotExist|FileInfo|FileMode|Is.*|Mode.*)$ 123 | gci: 124 | sections: 125 | - standard 126 | - default 127 | - prefix(github.com/twpayne/go-mbtiles) 128 | gocritic: 129 | enable-all: true 130 | disabled-checks: 131 | - emptyFallthrough 132 | - hugeParam 133 | - rangeValCopy 134 | - unnamedResult 135 | - whyNoLint 136 | gofumpt: 137 | extra-rules: true 138 | module-path: github.com/twpayne/go-mbtiles 139 | goimports: 140 | local-prefixes: github.com/twpayne/go-mbtiles 141 | govet: 142 | disable: 143 | - fieldalignment 144 | - shadow 145 | enable-all: true 146 | misspell: 147 | locale: US 148 | ignore-words: 149 | - ackward 150 | stylecheck: 151 | checks: 152 | - all 153 | 154 | issues: 155 | include: 156 | - EXC0011 # include issues about comments from `stylecheck` 157 | exclude-rules: 158 | - linters: 159 | - err113 160 | text: do not define dynamic errors, use wrapped static errors instead 161 | - linters: 162 | - revive 163 | text: unused-parameter 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Tom Payne 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-mbtiles 2 | 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/twpayne/go-mbtiles)](https://pkg.go.dev/github.com/twpayne/go-mbtiles) 4 | 5 | Package `mbtiles` reads and writes files in the [MBTiles 6 | format](https://github.com/mapbox/mbtiles-spec). 7 | 8 | ## Running the http server 9 | 10 | ``` 11 | go install github.com/twpayne/go-mbtiles/cmd/mbtiles-server 12 | go run github.com/twpayne/go-mbtiles/cmd/mbtiles-server -addr localhost:9091 -dsn ./testdata/openstreetmap.org.mbtiles 13 | ``` 14 | 15 | ## Reading mbtiles files 16 | 17 | MBTiles files are SQLite databases, and opened using a DSN string. 18 | 19 | Create a reader and read a tile with: 20 | 21 | ```golang 22 | reader, err := mbtiles.NewReader("./testdata/openstreetmap.org.mbtiles") 23 | if err != nil { 24 | panic(err) 25 | } 26 | tile, err := reader.SelectTile(0, 0, 0) 27 | if err != nil { 28 | if errors.Is(err, sql.ErrNoRows) { 29 | fmt.Printf("tile doesn't exist: %d, %d, %d\n", 0, 0, 0) 30 | return 31 | } else { 32 | panic(err) 33 | } 34 | } 35 | fmt.Printf("tile data: %+v\n", tile) 36 | ``` 37 | 38 | Note that SQLite will happily open a non-existent file to read without 39 | throwing an error. It is recommend that you check for existence of the file 40 | first, if you're using a file path as your DSN. For example: 41 | 42 | ```golang 43 | if _, err := os.Stat(filename); errors.Is(err, os.ErrNotExist) { 44 | panic(fmt.Sprintf("mbtiles file doesn't exist (%s): %v", filename, err)) 45 | } 46 | ``` 47 | 48 | The `Reader` type includes a `ServeHTTP` function. You can use it to create a 49 | reader for your mbtiles. 50 | 51 | ## Writing mbtiles files 52 | 53 | This package supports writing files in the [MBTiles 54 | v1.3](https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md) format. 55 | 56 | The library will not fill out all the required fields in the MBTiles 57 | specification, instead it provides the tools to be compliant. 58 | 59 | Of note: 60 | * The caller is responsible for populating the correct metadata into the 61 | metadata according to the spec. 62 | * The caller is responsible for gzip'ing the tile data before calling 63 | `InsertTile` or `BulkInsertTile`. The spec requires tiles to be compressed 64 | with gzip. How the caller implements the compression is outside the scope of 65 | this package. 66 | * For the `json` key in the metadata table, helper types are provided in this 67 | package as `mbtiles.MetadataJSON`. This type can be marshaled to a string 68 | and inserted into the metadata table for spec compliance for vector MBTiles 69 | files. 70 | * go-mbtiles will invert the Y coordinate to TMS to be compliant with the 71 | mbtiles spec. 72 | * go-mbtiles will create a metadata table if it doesn't exist, the first time 73 | `InsertMetadata` is called. 74 | * go-mbtiles will create a tiles table if it doesn't exist, the first time 75 | `InsertTile` or `BulkInsertTile` is called. 76 | 77 | 78 | ### Performance Tips 79 | 80 | MBTiles files are SQLite databases. To quickly bulk insert a large number 81 | of rows into the database, certain optimizations may be necessary to improve 82 | write performance. 83 | 84 | SQLite is a a single writer database, which means only one write can occur at 85 | a time. The database will otherwise be locked. Because of this, any write that 86 | is not in a transaction will automatically be wrapped in a transaction, which 87 | is slow. Consider the `BulkInsertTile` command, which will wrap all of the 88 | inserts in a single transaction. 89 | 90 | There are other optimizations exposed through the `Writer` interface. You 91 | should understand their implications for your use case before turning them on. 92 | * `JournalModeMemory` switches the journal mode from disk to memory. In bulk 93 | import scenarios, this is likely a very safe performance optimization to turn 94 | on. 95 | * `SynchronousOff` allows SQLite to continue processing as soon as data is 96 | handed off to the operating system to be written (instead of wait for 97 | confirmation that the write was successful). This is likely safe for bulk 98 | writes, but likely will result in a corrupted database if the process is 99 | interrupted or computer loses power. 100 | 101 | The performance improvements in go-mbtiles are motivated by the research in 102 | this [StackOverflow post about SQLite INSERT 103 | performance](https://stackoverflow.com/questions/1711631/improve-insert-per-second-performance-of-sqlite). 104 | 105 | ## License 106 | 107 | BSD-2-Clause in [LICENSE](./LICENSE). 108 | 109 | ## Contributors 110 | 111 | * Tom Payne (@twpayne) 112 | * Joe Polastre (@polastre), FlightAware 113 | -------------------------------------------------------------------------------- /cmd/mbtiles-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "html/template" 6 | "log" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/gorilla/handlers" 12 | "github.com/gorilla/mux" 13 | _ "modernc.org/sqlite" // Register sqlite database driver. 14 | 15 | "github.com/twpayne/go-mbtiles" 16 | ) 17 | 18 | var indexHTML = template.Must(template.New("index.html").Parse(` 19 | 20 | mbtiles-server 21 | 22 | 23 | 24 | 25 |
26 | 42 | 43 | 44 | `)) 45 | 46 | var ( 47 | addr = flag.String("addr", "localhost:8080", "addr") 48 | dsn = flag.String("dsn", "", "dsn") 49 | ) 50 | 51 | type mapServer struct { 52 | TilePrefix string 53 | } 54 | 55 | func (ms *mapServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 56 | if err := indexHTML.Execute(w, ms); err != nil { 57 | log.Print(err) 58 | } 59 | } 60 | 61 | func run() error { 62 | flag.Parse() 63 | mbtr, err := mbtiles.NewReader("sqlite", *dsn) 64 | if err != nil { 65 | return err 66 | } 67 | defer func() { 68 | if err := mbtr.Close(); err != nil { 69 | log.Print(err) 70 | } 71 | }() 72 | r := mux.NewRouter() 73 | tilePrefix := "/" + filepath.Base(*dsn) + "/" 74 | ms := &mapServer{ 75 | TilePrefix: tilePrefix, 76 | } 77 | r.PathPrefix(tilePrefix).Handler(http.StripPrefix(tilePrefix, mbtr)) 78 | r.PathPrefix("/").Handler(http.StripPrefix("/", ms)) 79 | http.Handle("/", handlers.LoggingHandler(os.Stdout, r)) 80 | return http.ListenAndServe(*addr, nil) 81 | } 82 | 83 | func main() { 84 | if err := run(); err != nil { 85 | log.Fatal(err) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/twpayne/go-mbtiles 2 | 3 | go 1.22.0 4 | 5 | toolchain go1.23.3 6 | 7 | require ( 8 | github.com/alecthomas/assert/v2 v2.3.0 9 | github.com/gorilla/handlers v1.5.2 10 | github.com/gorilla/mux v1.8.1 11 | modernc.org/sqlite v1.34.1 12 | ) 13 | 14 | require ( 15 | github.com/alecthomas/repr v0.2.0 // indirect 16 | github.com/dustin/go-humanize v1.0.1 // indirect 17 | github.com/felixge/httpsnoop v1.0.4 // indirect 18 | github.com/google/uuid v1.6.0 // indirect 19 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 20 | github.com/hexops/gotextdiff v1.0.3 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | github.com/ncruces/go-strftime v0.1.9 // indirect 23 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 24 | golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect 25 | golang.org/x/sys v0.27.0 // indirect 26 | modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 // indirect 27 | modernc.org/libc v1.61.3 // indirect 28 | modernc.org/mathutil v1.6.0 // indirect 29 | modernc.org/memory v1.8.0 // indirect 30 | modernc.org/strutil v1.2.0 // indirect 31 | modernc.org/token v1.1.0 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= 2 | github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= 3 | github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= 4 | github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= 5 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 6 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 7 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 8 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 9 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= 10 | github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= 11 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 12 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 13 | github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= 14 | github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= 15 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 16 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 17 | github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= 18 | github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= 19 | github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= 20 | github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= 21 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 22 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 23 | github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= 24 | github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= 25 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 26 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 27 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 28 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 29 | golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= 30 | golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= 31 | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= 32 | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 33 | golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= 34 | golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 35 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= 37 | golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 38 | golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= 39 | golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= 40 | modernc.org/cc/v4 v4.23.1 h1:WqJoPL3x4cUufQVHkXpXX7ThFJ1C4ik80i2eXEXbhD8= 41 | modernc.org/cc/v4 v4.23.1/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= 42 | modernc.org/ccgo/v4 v4.23.0 h1:axUpVd/3FOjzCOhoJ1qpN7LzegJTqmDk0g12L5Sq4B4= 43 | modernc.org/ccgo/v4 v4.23.0/go.mod h1:Ed0L1+tHOh+3jGRQbXpgXgrTDRFe9+U0yNbxqvd/xEM= 44 | modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= 45 | modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= 46 | modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= 47 | modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= 48 | modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852 h1:IYXPPTTjjoSHvUClZIYexDiO7g+4x+XveKT4gCIAwiY= 49 | modernc.org/gc/v3 v3.0.0-20241004144649-1aea3fae8852/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= 50 | modernc.org/libc v1.61.3 h1:D1gpZODpSnRpSnXxEsPjplrKDZIbtgWvslE5BOsPv5Q= 51 | modernc.org/libc v1.61.3/go.mod h1:Aw9YglLu+WSCq098BoLHmCALpVxwGU5KASDyzFkYTmQ= 52 | modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= 53 | modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= 54 | modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= 55 | modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= 56 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 57 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 58 | modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= 59 | modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= 60 | modernc.org/sqlite v1.34.1 h1:u3Yi6M0N8t9yKRDwhXcyp1eS5/ErhPTBggxWFuR6Hfk= 61 | modernc.org/sqlite v1.34.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= 62 | modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= 63 | modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= 64 | modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= 65 | modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 66 | -------------------------------------------------------------------------------- /mbtiles.go: -------------------------------------------------------------------------------- 1 | // Package mbtiles reads and writes files in the MBTiles format. 2 | // See https://github.com/mapbox/mbtiles-spec. 3 | package mbtiles 4 | -------------------------------------------------------------------------------- /reader.go: -------------------------------------------------------------------------------- 1 | package mbtiles 2 | 3 | import ( 4 | "database/sql" 5 | "net/http" 6 | "regexp" 7 | "strconv" 8 | ) 9 | 10 | var zxyRegexp = regexp.MustCompile(`\A(\d+)/(\d+)/(\d+)\z`) 11 | 12 | // A Reader reads a tileset. 13 | type Reader struct { 14 | db *sql.DB 15 | tileSelectStmt *sql.Stmt 16 | metadataSelectStmt *sql.Stmt 17 | } 18 | 19 | // NewReader returns a new Reader. 20 | func NewReader(driverName, dsn string) (*Reader, error) { 21 | db, err := sql.Open(driverName, dsn) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return NewReaderWithDB(db) 26 | } 27 | 28 | // NewReaderWithDB returns a new Reader initialized with a sql.Database. 29 | // This is useful for instantiating alternative implementations of sqlite. 30 | func NewReaderWithDB(db *sql.DB) (*Reader, error) { 31 | return &Reader{db: db}, nil 32 | } 33 | 34 | // Close releases all resources associated with r. 35 | func (r *Reader) Close() error { 36 | var err error 37 | if r.tileSelectStmt != nil { 38 | if err2 := r.tileSelectStmt.Close(); err2 != nil { 39 | err = err2 40 | } 41 | } 42 | if r.db != nil { 43 | if err2 := r.db.Close(); err2 != nil { 44 | err = err2 45 | } 46 | } 47 | return err 48 | } 49 | 50 | // SelectTile returns the tile at (z, x, y). 51 | func (r *Reader) SelectTile(z, x, y int) ([]byte, error) { 52 | if r.tileSelectStmt == nil { 53 | var err error 54 | r.tileSelectStmt, err = r.db.Prepare("SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?;") 55 | if err != nil { 56 | return nil, err 57 | } 58 | } 59 | var tileData []byte 60 | err := r.tileSelectStmt.QueryRow(z, x, 1<