/* Copyright 2013 The jeo project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jeo.mbtiles;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import io.jeo.data.Cursor;
import io.jeo.data.Driver;
import io.jeo.data.FileData;
import io.jeo.geom.Bounds;
import io.jeo.tile.Tile;
import io.jeo.tile.TileDataset;
import io.jeo.tile.TilePyramid;
import io.jeo.tile.TilePyramidBuilder;
import io.jeo.proj.Proj;
import io.jeo.util.Key;
import io.jeo.util.Util;
import io.jeo.sql.Backend;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MBTileSet implements TileDataset, FileData {
protected static Logger LOG = LoggerFactory.getLogger(MBTileSet.class);
static final String METADATA = "metadata";
static final String TILES = "tiles";
static final String PNG = "image/png";
static final String JPEG = "image/jpeg";
Backend backend;
MBTilesOpts opts;
String tileFormat;
public MBTileSet(Backend backend, MBTilesOpts opts) {
this.backend = backend;
this.opts = opts;
tileFormat = queryForTileFormat();
}
public MBTileSet(File file) {
this(new JDBCBackend(new MBTilesOpts(file)), new MBTilesOpts(file));
}
public String getTileFormat() {
return tileFormat;
}
String queryForTileFormat() {
try {
String sql = String.format(Locale.ROOT,"SELECT value FROM %s WHERE name = ?", METADATA);
Backend.Results results = backend.queryPrepared(sql, "format");
try {
if (results.next()) {
String format = results.getString(0);
return "jpg".equalsIgnoreCase(format) || JPEG.equalsIgnoreCase(format) ? JPEG : PNG;
}
} finally {
results.close();
}
}
catch(IOException e) {
LOG.error("Error querying for tile format!", e);
}
return PNG;
}
@Override
public File file() {
return opts.file();
}
@Override
public Driver<?> driver() {
return new MBTiles();
}
@Override
public Map<Key<?>, Object> driverOptions() {
return (Map) Collections.singletonMap(MBTiles.FILE, file());
}
@Override
public String name() {
return Util.base(file().getName());
}
public String title() {
try {
String sql = String.format(Locale.ROOT,"SELECT value FROM %s WHERE name = ?", METADATA);
Backend.Results results = backend.queryPrepared(sql, "name");
try {
if (results.next()) {
return results.getString(0);
}
} finally {
results.close();
}
}
catch(IOException e) {
LOG.error("Error querying for tile name!", e);
}
return null;
}
public String description() {
try {
String sql = String.format(Locale.ROOT,"SELECT value FROM %s WHERE name = ?", METADATA);
Backend.Results results = backend.queryPrepared(sql, "description");
try {
if (results.next()) {
return results.getString(0);
}
} finally {
results.close();
}
}
catch(IOException e) {
LOG.error("Error querying for tile description!", e);
}
return null;
}
@Override
public CoordinateReferenceSystem crs() throws IOException {
return Proj.EPSG_900913;
}
@Override
public Bounds bounds() throws IOException {
try {
String sql = String.format(Locale.ROOT,"SELECT value FROM %s WHERE name = ?", METADATA);
Backend.Results results = backend.queryPrepared(sql, "bounds");
try {
if (results.next()) {
Bounds b = Bounds.parse(results.getString(0), true);
return b;
}
} finally {
results.close();
}
}
catch(IOException e) {
LOG.error("Error querying for tile bounds!", e);
}
// fall back to bounds of crs
return Proj.bounds(crs());
}
@Override
public TilePyramid pyramid() throws IOException {
TilePyramidBuilder tpb = TilePyramid.build().crs(Proj.EPSG_900913);
tpb.bounds(bounds());
try {
String sql = String.format(Locale.ROOT,"SELECT DISTINCT(zoom_level) FROM %s", TILES);
Backend.Results results = backend.queryPrepared(sql);
try {
while(results.next()) {
int z = results.getInt(0);
int d = (int) Math.pow(2, z);
tpb.grid(z, d, d);
}
} finally {
results.close();
}
}
catch(IOException e) {
LOG.error("Error querying for tile zoom levels!", e);
}
return tpb.pyramid();
}
@Override
public Tile read(long z, long x, long y) throws IOException {
String sql = String.format(Locale.ROOT,"SELECT tile_data FROM %s WHERE zoom_level = %d " +
"AND tile_column = %d AND tile_row = %d", TILES, z, x, y);
try {
Backend.Results results = backend.queryPrepared(sql);
try {
if (results.next()) {
return new Tile((int)z, (int)x, (int)y, results.getBytes(0), tileFormat);
}
} finally {
results.close();
}
}
catch(IOException e) {
LOG.error(String.format(Locale.ROOT,"Error reading tile %s/%s/%s!", z, x, y), e);
}
return null;
}
@Override
public Cursor<Tile> read(long z1, long z2, long x1, long x2, long y1, long y2) throws IOException {
final List<String> q = new ArrayList<String>();
if (z1 > -1) {
q.add("zoom_level >= " + z1);
}
if (z2 > -1) {
q.add("zoom_level <= " + z2);
}
if (x1 > -1) {
q.add("tile_column >= " + x1);
}
if (x2 > -1) {
q.add("tile_column <= " + x2);
}
if (y1 > -1) {
q.add("tile_row >= " + y1);
}
if (y2 > -1) {
q.add("tile_row <= " + y2);
}
StringBuilder where = new StringBuilder();
if (!q.isEmpty()) {
for (String s : q) {
where.append(s).append(" AND ");
}
where.setLength(where.length()-5);
}
String sql = String.format(Locale.ROOT,"SELECT zoom_level, tile_column, tile_row, tile_data FROM %s WHERE %s ORDER BY zoom_level", TILES, where);
Backend.Results results = backend.query(sql);
return new TileCursor(results);
}
@Override
public void close() {
try {
backend.close();
} catch(IOException e) {
// LOG
}
}
}