/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.openstreetmap.impl; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Envelope; public class OSMDownloader { private static final Logger LOG = LoggerFactory.getLogger(OSMDownloader.class); private double _latYStep = 0.04; private double _lonXStep = 0.04; private double _overlap = 0.001; private File _cacheDirectory; private int _updateIfOlderThanDuration = 0; private String _apiBaseUrl = "http://open.mapquestapi.com/xapi/api/0.6/"; public void setLatStep(double latStep) { _latYStep = latStep; } public void setLonStep(double lonStep) { _lonXStep = lonStep; } public void setOverlap(double overlap) { _overlap = overlap; } public void setCacheDirectory(File cacheDirectory) { _cacheDirectory = cacheDirectory; } public void setUpdateIfOlderThanDuration(int durationInMilliseconds) { _updateIfOlderThanDuration = durationInMilliseconds; } public void visitRegion(Envelope rectangle, OSMDownloaderListener listener) throws IOException { double minY = floor(rectangle.getMinY(), _latYStep); double maxY = ceil(rectangle.getMaxY(), _latYStep); double minX = floor(rectangle.getMinX(), _lonXStep); double maxX = ceil(rectangle.getMaxX(), _lonXStep); for (double y = minY; y < maxY; y += _latYStep) { for (double x = minX; x < maxX; x += _lonXStep) { String key = getKey(x, y); File path = getPathToUpToDateMapTile(y, x, key); try { listener.handleMapTile(key, y, x, path); } catch (IllegalStateException e) { LOG.debug("trying to re-download"); path.delete(); path = getPathToUpToDateMapTile(y, x, key); listener.handleMapTile(key, y, x, path); } } } } public static double floor(double value, double step) { return step * Math.floor(value / step); } public static double ceil(double value, double step) { return step * Math.ceil(value / step); } private String formatNumberWithoutLocale(double number) { return String.format((Locale) null, "%.4f", number); } private String getKey(double x, double y) { return formatNumberWithoutLocale(y) + "_" + formatNumberWithoutLocale(x) + "_" + formatNumberWithoutLocale(_latYStep) + "_" + formatNumberWithoutLocale(_lonXStep) + "_" + formatNumberWithoutLocale(_overlap); } private File getPathToUpToDateMapTile(double lat, double lon, String key) throws IOException { File path = getPathToMapTile(key); if (needsUpdate(path)) { Envelope r = new Envelope(lon - _overlap, lon + _lonXStep + _overlap, lat - _overlap, lat + _latYStep + _overlap); LOG.debug("downloading osm tile: " + key + " from path " + path + " e " + path.exists()); URL url = constructUrl(r); LOG.warn("downloading from " + url.toString()); InputStream in = url.openStream(); FileOutputStream out = new FileOutputStream(path); try { byte[] data = new byte[4096]; while (true) { int numBytes = in.read(data); if (numBytes == -1) { break; } out.write(data, 0, numBytes); } in.close(); out.close(); } catch (RuntimeException e) { out.close(); LOG.info("Removing half-written file " + path); path.delete(); //clean up any half-written files throw e; } } return path; } private File getPathToMapTile(String key) throws IOException { if( _cacheDirectory == null) { File tmpDir = new File(System.getProperty("java.io.tmpdir")); _cacheDirectory = new File(tmpDir,"osm-tiles"); } if (!_cacheDirectory.exists()) { if (!_cacheDirectory.mkdirs()) { throw new RuntimeException("Failed to create directory " + _cacheDirectory); } } File path = new File(_cacheDirectory, "map-" + key + ".osm"); return path; } private boolean needsUpdate(File path) { if (!path.exists()) return true; if (_updateIfOlderThanDuration > 0) { if (System.currentTimeMillis() - path.lastModified() > _updateIfOlderThanDuration) return true; } return false; } private URL constructUrl(Envelope r) { double left = r.getMinX(); double right = r.getMaxX(); double bottom = r.getMinY(); double top = r.getMaxY(); try { return new URL(getApiBaseUrl() + "map?bbox=" + left + "," + bottom + "," + right + "," + top); } catch (MalformedURLException e) { throw new IllegalStateException(e); } } /** * Set the base OSM API URL from which OSM tiles will be downloaded. */ public void setApiBaseUrl(String apiBaseUrl) { this._apiBaseUrl = apiBaseUrl; } public String getApiBaseUrl() { if (_apiBaseUrl == null) throw new IllegalStateException("Map API base URL must be set before building a URL."); return _apiBaseUrl; } }