├── .gitignore
├── .travis.yml
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── mbtiles4j
│ │ ├── MBTiles4jContextListener.java
│ │ ├── MBTilesUtils.java
│ │ └── TileServlet.java
├── resources
│ └── mbtiles4j.properties
└── webapp
│ └── WEB-INF
│ └── web.xml
└── test
├── java
└── mbtiles4j
│ └── MBTilesUtilsTest.java
└── resources
├── mbtiles4j.properties
└── mbtiles4j
└── mbtiles4j-test.mbtiles
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .classpath
3 | .project
4 | .settings
5 | target/
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | mbtiles4j
2 | =========
3 |
4 | An [MBTiles](https://github.com/mapbox/mbtiles-spec) server written in Java.
5 |
6 | [](https://travis-ci.org/jtreml/mbtiles4j)
7 |
8 | Configuration
9 | -------------
10 |
11 | Edit the ``mbtiles4j.properties`` file in ``src/main/resources`` to configure your [MBTiles](https://github.com/mapbox/mbtiles-spec) databases:
12 |
13 | ```properties
14 | tile-dbs = db1,db2,db3
15 |
16 | db1.path = /path/to/your/database1.mbtiles
17 | db2.path = /path/to/your/database2.mbtiles
18 | db3.path = /path/to/your/database3.mbtiles
19 | ```
20 |
21 | The name specified in this file will be the name used in the URL to access the tiles, e.g. the _db1_ tiles in the example configuration will be available at ``htp://
:/mbtiles4j/db1/z/x/y.png``.
22 |
23 | Build & Deployment
24 | ------------------
25 |
26 | Type in
27 |
28 | ```
29 | mvn package
30 | ```
31 |
32 | and deploy the ``mbtiles4j.war`` file found in ``target/`` to an application server of your choice.
33 |
34 | Usage Example (Leaflet)
35 | -----------------------
36 |
37 | This is an example of how to use the tile server as a source for a [Leaflet](http://leafletjs.com/)-based web app. Keep in mind that tiles are served according to [TMS](http://en.wikipedia.org/wiki/Tile_Map_Service) spec, i.e. you'll get a twisted map with mixed-up tiles unless you configure your maps solution accordingly.
38 |
39 | ```javascript
40 |
41 |
47 | ```
48 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | mbtiles4j
5 | 0.0.1-SNAPSHOT
6 | 4.0.0
7 | mbtiles4j
8 | war
9 | mbtiles4j
10 |
11 |
12 | 2.2.1
13 |
14 |
15 |
16 | UTF-8
17 |
18 |
19 |
20 |
21 | commons-io
22 | commons-io
23 | 2.4
24 |
25 |
26 | org.apache.commons
27 | commons-lang3
28 | 3.1
29 |
30 |
31 | javax.servlet
32 | javax.servlet-api
33 | 3.1.0
34 | provided
35 |
36 |
37 | org.xerial
38 | sqlite-jdbc
39 | 3.7.2
40 |
41 |
42 | junit
43 | junit
44 | 4.11
45 | test
46 |
47 |
48 |
49 |
50 | mbtiles4j
51 |
52 |
53 | org.apache.maven.plugins
54 | maven-compiler-plugin
55 | 3.1
56 |
57 | 1.6
58 | 1.6
59 |
60 |
61 |
62 | org.apache.maven.plugins
63 | maven-war-plugin
64 | 2.3
65 |
66 | **/client/**
67 |
68 |
69 | true
70 | true
71 |
72 |
73 | src/main/webapp/WEB-INF/web.xml
74 |
75 |
76 | ${basedir}/src/main/webapp/WEB-INF
77 | true
78 | WEB-INF
79 |
80 | web.xml
81 |
82 |
83 |
84 |
85 |
86 |
87 | org.codehaus.mojo
88 | versions-maven-plugin
89 | 2.0
90 |
91 |
92 |
93 |
94 | scm:git:git@github.com:jtreml/mbtiles4j.git
95 | scm:git:git@github.com:jtreml/mbtiles4j.git
96 | https://github.com/jtreml/mbtiles4j
97 |
98 |
99 | GitHub Issues
100 | https://github.com/jtreml/mbtiles4j/issues
101 |
102 |
103 | Jürgen Treml
104 | http://www.juergentreml.de
105 |
106 |
107 | https://travis-ci.org/jtreml/mbtiles4j
108 | Travis CI
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/main/java/mbtiles4j/MBTiles4jContextListener.java:
--------------------------------------------------------------------------------
1 | package mbtiles4j;
2 |
3 | import javax.servlet.ServletContextEvent;
4 | import javax.servlet.ServletContextListener;
5 |
6 | public class MBTiles4jContextListener implements ServletContextListener {
7 |
8 | @Override
9 | public void contextInitialized(ServletContextEvent sce) {
10 | MBTilesUtils.connect();
11 | }
12 |
13 | @Override
14 | public void contextDestroyed(ServletContextEvent sce) {
15 | MBTilesUtils.disconnect();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/mbtiles4j/MBTilesUtils.java:
--------------------------------------------------------------------------------
1 | package mbtiles4j;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.sql.Connection;
6 | import java.sql.DriverManager;
7 | import java.sql.PreparedStatement;
8 | import java.sql.ResultSet;
9 | import java.sql.SQLException;
10 | import java.util.HashMap;
11 | import java.util.Map;
12 | import java.util.Properties;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.regex.Pattern;
15 |
16 | import org.apache.commons.lang3.StringUtils;
17 |
18 | public class MBTilesUtils {
19 |
20 | private static final ConcurrentHashMap INSTANCES = new ConcurrentHashMap();
21 |
22 | private final Connection conn;
23 |
24 | private final PreparedStatement ps;
25 |
26 | private MBTilesUtils(String db) {
27 | try {
28 | Class.forName("org.sqlite.JDBC");
29 | } catch (ClassNotFoundException e) {
30 | throw new RuntimeException(e);
31 | }
32 |
33 | if (db == null || !new File(db).exists()) {
34 | throw new RuntimeException("No database");
35 | }
36 |
37 | try {
38 | conn = DriverManager.getConnection("jdbc:sqlite:" + db);
39 | } catch (SQLException e) {
40 | throw new RuntimeException(e);
41 | }
42 |
43 | try {
44 | ps = conn.prepareStatement("SELECT tile_data FROM tiles "
45 | + "WHERE zoom_level = ? AND tile_column = ? "
46 | + "AND tile_row = ?;");
47 | } catch (SQLException e) {
48 | throw new RuntimeException(e);
49 | }
50 | }
51 |
52 | static Map getDatabases() {
53 | Properties configuration = new Properties();
54 | try {
55 | configuration.load(MBTilesUtils.class.getClassLoader()
56 | .getResourceAsStream("mbtiles4j.properties"));
57 | } catch (IOException e) {
58 | throw new RuntimeException(e);
59 | }
60 |
61 | HashMap result = new HashMap();
62 |
63 | String dbs = configuration.getProperty("tile-dbs");
64 | if (StringUtils.isEmpty(dbs)) {
65 | return result;
66 | }
67 |
68 | String[] split = dbs.split(Pattern.quote(","));
69 | for (String entry : split) {
70 | String path = configuration.getProperty(entry + ".path");
71 | if (!StringUtils.isEmpty(path)) {
72 | result.put(entry, path);
73 | }
74 | }
75 |
76 | return result;
77 | }
78 |
79 | public static MBTilesUtils getInstance(String db) {
80 | return INSTANCES.get(db);
81 | }
82 |
83 | public synchronized byte[] getTiles(int x, int y, int z) {
84 | int index = 1;
85 |
86 | ResultSet rs = null;
87 | try {
88 | ps.setInt(index++, z);
89 | ps.setInt(index++, x);
90 | ps.setInt(index++, y);
91 |
92 | rs = ps.executeQuery();
93 | if (rs.next()) {
94 | return rs.getBytes(1);
95 | }
96 | } catch (SQLException e) {
97 | throw new RuntimeException(e);
98 | } finally {
99 | try {
100 | if (rs != null) {
101 | rs.close();
102 | }
103 | } catch (SQLException e) {
104 | e.printStackTrace();
105 | }
106 | }
107 |
108 | return null;
109 | }
110 |
111 | private synchronized void close() {
112 | if (ps != null) {
113 | try {
114 | ps.close();
115 | } catch (SQLException e) {
116 | e.printStackTrace();
117 | }
118 | }
119 | if (conn != null) {
120 | try {
121 | conn.close();
122 | } catch (SQLException e) {
123 | e.printStackTrace();
124 | }
125 | }
126 | }
127 |
128 | public static void connect() {
129 | Map dbs = getDatabases();
130 | for (String db : dbs.keySet()) {
131 | if (!INSTANCES.containsKey(db)) {
132 | INSTANCES.put(db, new MBTilesUtils(dbs.get(db)));
133 | }
134 | }
135 | }
136 |
137 | public static void disconnect() {
138 | for (MBTilesUtils entry : INSTANCES.values()) {
139 | entry.close();
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/main/java/mbtiles4j/TileServlet.java:
--------------------------------------------------------------------------------
1 | package mbtiles4j;
2 |
3 | import java.io.IOException;
4 | import java.util.regex.Pattern;
5 |
6 | import javax.servlet.ServletException;
7 | import javax.servlet.ServletOutputStream;
8 | import javax.servlet.http.HttpServlet;
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.servlet.http.HttpServletResponse;
11 |
12 | import org.apache.commons.io.IOUtils;
13 | import org.apache.commons.lang3.StringUtils;
14 |
15 | @SuppressWarnings("serial")
16 | public class TileServlet extends HttpServlet {
17 |
18 | @Override
19 | protected void doGet(HttpServletRequest request,
20 | HttpServletResponse response) throws ServletException, IOException {
21 | String path = request.getPathInfo();
22 | if (StringUtils.isEmpty(path)) {
23 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
24 | return;
25 | }
26 |
27 | path = path.substring(1);
28 | String[] split = path.split(Pattern.quote("/"));
29 |
30 | if (split.length != 4) {
31 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
32 | return;
33 | }
34 |
35 | if (!split[3].toLowerCase().endsWith(".png")) {
36 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
37 | return;
38 | }
39 |
40 | split[3] = split[3].replace(".png", "");
41 |
42 | MBTilesUtils mbtu = MBTilesUtils.getInstance(split[0]);
43 | if (mbtu == null) {
44 | response.setStatus(HttpServletResponse.SC_NOT_FOUND);
45 | return;
46 | }
47 |
48 | int z;
49 | int y;
50 | int x;
51 | try {
52 | z = Integer.parseInt(split[1]);
53 | x = Integer.parseInt(split[2]);
54 | y = Integer.parseInt(split[3]);
55 | } catch (NumberFormatException e) {
56 | response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
57 | return;
58 | }
59 |
60 | byte[] tile = mbtu.getTiles(x, y, z);
61 | if (tile == null) {
62 | response.setStatus(HttpServletResponse.SC_NOT_FOUND);
63 | return;
64 | }
65 |
66 | response.setContentType("image/png");
67 | response.setContentLength(tile.length);
68 |
69 | ServletOutputStream oStream = response.getOutputStream();
70 | IOUtils.write(tile, oStream);
71 | oStream.flush();
72 | oStream.close();
73 | }
74 |
75 | @Override
76 | protected void doPost(HttpServletRequest request,
77 | HttpServletResponse response) throws ServletException, IOException {
78 | doGet(request, response);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/resources/mbtiles4j.properties:
--------------------------------------------------------------------------------
1 | tile-dbs = db1,db2,db3
2 |
3 | db1.path = /path/to/your/database.mbtiles
4 | db2.path = /path/to/your/database.mbtiles
5 | db3.path = /path/to/your/database.mbtiles
6 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 | mbtiles4j
7 |
8 | Tile Servlet
9 | mbtiles4j.TileServlet
10 |
11 |
12 | Tile Servlet
13 | /*
14 |
15 |
16 | mbtiles4j.MBTiles4jContextListener
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/test/java/mbtiles4j/MBTilesUtilsTest.java:
--------------------------------------------------------------------------------
1 | package mbtiles4j;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.OutputStream;
8 | import java.util.Map;
9 |
10 | import org.apache.commons.io.IOUtils;
11 | import org.junit.AfterClass;
12 | import org.junit.Assert;
13 | import org.junit.BeforeClass;
14 | import org.junit.Test;
15 |
16 | public class MBTilesUtilsTest {
17 |
18 | private static MBTilesUtils mbtu;
19 |
20 | private static String path;
21 |
22 | @BeforeClass
23 | public static void extractTestDatabase() throws IOException {
24 | Map dbs = MBTilesUtils.getDatabases();
25 | Assert.assertTrue(dbs.size() == 1);
26 |
27 | String db = dbs.keySet().iterator().next();
28 | path = dbs.get(db);
29 |
30 | InputStream is = null;
31 | OutputStream os = null;
32 | try {
33 | is = MBTilesUtilsTest.class.getResourceAsStream(path);
34 | os = new FileOutputStream(path);
35 | IOUtils.copy(is, os);
36 | os.flush();
37 | } finally {
38 | if (os != null) {
39 | os.close();
40 | }
41 | if (is != null) {
42 | is.close();
43 | }
44 | }
45 |
46 | MBTilesUtils.connect();
47 | mbtu = MBTilesUtils.getInstance(db);
48 | }
49 |
50 | @Test
51 | public void retrieveTiles() {
52 | check(0, 0, 0);
53 | checkNot(0, 1, 0);
54 | check(0, 1, 1);
55 | checkNot(0, 0, 3);
56 | }
57 |
58 | @AfterClass
59 | public static void deleteTestDatabase() {
60 | MBTilesUtils.disconnect();
61 | new File(path).delete();
62 | }
63 |
64 | private void check(int x, int y, int z) {
65 | byte[] tile = mbtu.getTiles(x, y, z);
66 | Assert.assertTrue(tile != null && tile.length > 0);
67 | }
68 |
69 | private void checkNot(int x, int y, int z) {
70 | byte[] tile = mbtu.getTiles(x, y, z);
71 | Assert.assertTrue(tile == null);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/resources/mbtiles4j.properties:
--------------------------------------------------------------------------------
1 | tile-dbs = mbtiles4j-test
2 |
3 | mbtiles4j-test.path = mbtiles4j-test.mbtiles
4 |
--------------------------------------------------------------------------------
/src/test/resources/mbtiles4j/mbtiles4j-test.mbtiles:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtreml/mbtiles4j/54d8dc37da621e8a363cc6a9a7848a30e5a2846b/src/test/resources/mbtiles4j/mbtiles4j-test.mbtiles
--------------------------------------------------------------------------------