package com.bbn.openmap.dataAccess.mapTile;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipOutputStream;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.FileUtils;
/**
* A utility class to help manage tile trees. Use the builders to configure and
* launch the MapTileUtil.
*
* @author dietrick
*/
public class MapTileUtil {
static Logger logger = Logger.getLogger("com.bbn.openmap.dataAccess.mapTile");
public final static String SOURCE_PROPERTY = "source";
public final static String BOUNDS_PROPERTY = "bounds";
public final static String DESTINATION_PROPERTY = "destination";
public final static String IMAGEFORMAT_PROPERTY = "format";
public final static String ZOOMLEVEL_PROPERTY = "zoom";
public final static int ZOOM_LEVELS = 21;
MapTileCoordinateTransform mtcTransform;
List<double[]> boundsList;
boolean[] zoomLevels;
public MapTileUtil(Action builder) {
boundsList = builder.boundsList;
zoomLevels = builder.zoomLevels;
mtcTransform = builder.mtcTransform;
}
/**
* Figure out which tiles need action, based on settings. Calls
* Action.action() for each tile on the builder. Designed to be called from
* Action.go().
*
* @param builder
*/
public void grabTiles(Action builder) {
if (boundsList == null) {
boundsList = new ArrayList<double[]>();
boundsList.add(new double[] {
80,
-180,
-80,
180
});
}
for (int i = 0; i < ZOOM_LEVELS; i++) {
// Check the zoom level. If they aren't specified, only do 0-14
if (zoomLevels == null) {
if (i > 14)
continue;
} else {
if (!zoomLevels[i]) {
continue;
}
}
for (double[] bounds : boundsList) {
int[] uvBounds =
mtcTransform.getTileBoundsForProjection(new LatLonPoint.Double(bounds[0], bounds[1]),
new LatLonPoint.Double(bounds[2], bounds[3]), i);
int uvup = uvBounds[0];
int uvleft = uvBounds[1];
int uvbottom = uvBounds[2];
int uvright = uvBounds[3];
int uvleftM = (int) Math.min(uvleft, uvright);
int uvrightM = (int) Math.max(uvleft, uvright);
int uvupM = (int) Math.min(uvbottom, uvup);
int uvbottomM = (int) Math.max(uvbottom, uvup);
for (int x = uvleftM; x < uvrightM; x++) {
for (int y = uvupM; y < uvbottomM; y++) {
builder.action(x, y, i, this);
}
}
}
}
}
/**
* For instance...
*
* @param args
*/
public static void main(String[] args) {
// new URLGrabber("http://tah.openstreetmap.org/Tiles/tile", "/data/tiles").addZoomRange(0, 5).go();
// new Copy("/data/sourcetiles", "/data/desttiles").addZoom(17).addBounds(7.8696, 2.324, 2.899, 9.053).go();
new Jar("/data/sourcetiles", "/data/dest.jar").addZoomRange(0,17).addBounds(14.042,2.498,.809,15.215).go();
}
/**
* A generic builder Action that handles most configuration issues for the
* MapTileUtil. Extend to make MTU do what you want by overriding go and
* action.
*
* @author dietrick
*/
public abstract static class Action {
String source;
String destination;
// Optional
String format = "png";
List<double[]> boundsList;
boolean[] zoomLevels; // 0-20
MapTileCoordinateTransform mtcTransform = new OSMMapTileCoordinateTransform();
public Action(String source, String destination) {
this.source = source;
this.destination = destination;
}
public Action addBounds(double ulat, double llon, double llat, double rlon) {
if (boundsList == null) {
boundsList = new ArrayList<double[]>();
}
double[] bnds = new double[] {
ulat,
llon,
llat,
rlon
};
boundsList.add(bnds);
return this;
}
public Action addZoom(int zoom) {
if (zoomLevels == null) {
zoomLevels = new boolean[ZOOM_LEVELS];
}
try {
zoomLevels[zoom] = true;
} catch (ArrayIndexOutOfBoundsException aioobe) {
logger.warning("zoom level invalid, ignoring: " + zoom);
}
return this;
}
public Action addZoomRange(int zoom1, int zoom2) {
int min = Math.min(zoom1, zoom2);
int max = Math.max(zoom1, zoom2);
for (int z = min; z <= max; z++) {
addZoom(z);
}
return this;
}
public Action format(String format) {
this.format = format;
return this;
}
public Action transform(MapTileCoordinateTransform transform) {
mtcTransform = transform;
return this;
}
public abstract void go();
/**
* Called from within grabTiles, with the tile info. You can use this
* information to make a method call on mtu.
*
* @param x tile coordinate
* @param y tile coordinate
* @param zoomLevel tile zoom level
* @param mtu callback
*/
public abstract void action(int x, int y, int zoomLevel, MapTileUtil mtu);
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public List<double[]> getBoundsList() {
return boundsList;
}
public void setBoundsList(List<double[]> boundsList) {
this.boundsList = boundsList;
}
public boolean[] getZoomLevels() {
return zoomLevels;
}
public void setZoomLevels(boolean[] zoomLevels) {
this.zoomLevels = zoomLevels;
}
public MapTileCoordinateTransform getMtcTransform() {
return mtcTransform;
}
public void setMtcTransform(MapTileCoordinateTransform mtcTransform) {
this.mtcTransform = mtcTransform;
}
}
/**
* Action that copies tiles from one directory to another.
*
* @author dietrick
*/
public static class Copy
extends Action {
public Copy(String source, String destination) {
super(source, destination);
}
public void go() {
if (source != null && destination != null) {
new MapTileUtil(this).grabTiles(this);
} else {
logger.warning("Need a source and destination for tile locations");
}
}
public void action(int x, int y, int zoomLevel, MapTileUtil mtu) {
File sourceFile = new File(getSource() + "/" + zoomLevel + "/" + x + "/" + y + "." + format);
File destDir = new File(getDestination() + "/" + zoomLevel + "/" + x);
destDir.mkdirs();
File destFile = new File(destDir, y + "." + format);
try {
if (sourceFile.exists()) {
FileUtils.copy(sourceFile, destFile, 1024);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* Action that creates a jar file containing the specified files.
*
* @author dietrick
*/
public static class Jar
extends Action {
/** Will get instantiated if needed */
ZipOutputStream zoStream = null;
File destinationFile = null;
long fileCount = 0;
int zipTrim = 0;
public Jar(String source, String destination) {
super(source, destination);
}
public void go() {
if (source != null && destination != null) {
new MapTileUtil(this).grabTiles(this);
if (zoStream != null) {
try {
zoStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
logger.info("Created " + getDestination() + " with " + fileCount + " files.");
} else {
logger.warning("Need a source and destination for tile locations");
}
}
public void action(int x, int y, int zoomLevel, MapTileUtil mtu) {
File tile = new File(getSource() + "/" + zoomLevel + "/" + x + "/" + y + "." + format);
if (tile.exists()) {
try {
if (zoStream == null) {
/*
* We need to do this because we need to make sure a zip
* output stream is created only when a file is going to
* be written to it. you can't create a zip file and
* then put nothing into it.
*/
destinationFile = new File(getDestination());
FileOutputStream fos = new FileOutputStream(destinationFile);
zoStream = new ZipOutputStream(fos);
zipTrim = destinationFile.getParent().length() + 1;
logger.info("creating " + destinationFile);
File sourceParentFile = new File(getSource()).getParentFile();
if (sourceParentFile.exists()) {
File tileDescription = new File(sourceParentFile, StandardMapTileFactory.TILE_PROPERTIES);
if (tileDescription.exists()) {
FileUtils.writeZipEntry(tileDescription, zoStream, zipTrim);
fileCount++;
logger.info("adding " + tileDescription);
}
}
}
FileUtils.writeZipEntry(tile, zoStream, zipTrim);
fileCount++;
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
/**
* A Builder that knows how to download files from a website.
*
* @author dietrick
*/
public static class URLGrabber
extends Action {
public URLGrabber(String source, String destination) {
super(source, destination);
}
public void go() {
if (source != null && destination != null) {
new MapTileUtil(this).grabTiles(this);
} else {
logger.warning("Need a source and destination for tile locations");
}
}
public void action(int x, int y, int zoomLevel, MapTileUtil mtu) {
grabURLTile(x, y, zoomLevel);
}
/**
* An action method that will fetch a tile from a URL and copy it to the
* destination directory.
*
* @param x
* @param y
* @param zoomLevel
*/
public void grabURLTile(int x, int y, int zoomLevel) {
java.net.URL url = null;
String imagePath = source + "/" + zoomLevel + "/" + x + "/" + y + (format.startsWith(".") ? format : "." + format);
try {
url = new java.net.URL(imagePath);
java.net.HttpURLConnection urlc = (java.net.HttpURLConnection) url.openConnection();
if (logger.isLoggable(Level.FINER)) {
logger.finer("url content type: " + urlc.getContentType());
}
if (urlc == null) {
logger.warning("unable to connect to " + imagePath);
return;
}
if (urlc.getContentType().startsWith("image")) {
InputStream in = urlc.getInputStream();
// ------- Testing without this
ByteArrayOutputStream out = new ByteArrayOutputStream();
int buflen = 2048; // 2k blocks
byte buf[] = new byte[buflen];
int len = -1;
while ((len = in.read(buf, 0, buflen)) != -1) {
out.write(buf, 0, len);
}
out.flush();
out.close();
byte[] imageBytes = out.toByteArray();
if (destination != null) {
File localFile =
new File(destination + "/" + zoomLevel + "/" + x + "/" + y
+ (format.startsWith(".") ? format : "." + format));
File parentDir = localFile.getParentFile();
parentDir.mkdirs();
FileOutputStream fos = new FileOutputStream(localFile);
fos.write(imageBytes);
fos.flush();
fos.close();
}
} // end if image
} catch (java.net.MalformedURLException murle) {
logger.warning("WebImagePlugIn: URL \"" + imagePath + "\" is malformed.");
} catch (java.io.IOException ioe) {
logger.warning("Couldn't connect to " + imagePath + "Connection Problem");
}
}
}
}