├── .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 | [![Build Status](https://travis-ci.org/jtreml/mbtiles4j.png?branch=master)](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 --------------------------------------------------------------------------------