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"); } } } }