001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.IOException; 007import java.io.InputStream; 008import java.net.SocketException; 009import java.util.List; 010 011import org.openstreetmap.josm.Main; 012import org.openstreetmap.josm.data.Bounds; 013import org.openstreetmap.josm.data.DataSource; 014import org.openstreetmap.josm.data.gpx.GpxData; 015import org.openstreetmap.josm.data.notes.Note; 016import org.openstreetmap.josm.data.osm.DataSet; 017import org.openstreetmap.josm.gui.progress.ProgressMonitor; 018import org.openstreetmap.josm.tools.CheckParameterUtil; 019import org.xml.sax.SAXException; 020 021/** 022 * Read content from OSM server for a given bounding box 023 * @since 627 024 */ 025public class BoundingBoxDownloader extends OsmServerReader { 026 027 /** 028 * The boundings of the desired map data. 029 */ 030 protected final double lat1; 031 protected final double lon1; 032 protected final double lat2; 033 protected final double lon2; 034 protected final boolean crosses180th; 035 036 /** 037 * Constructs a new {@code BoundingBoxDownloader}. 038 * @param downloadArea The area to download 039 */ 040 public BoundingBoxDownloader(Bounds downloadArea) { 041 CheckParameterUtil.ensureParameterNotNull(downloadArea, "downloadArea"); 042 this.lat1 = downloadArea.getMinLat(); 043 this.lon1 = downloadArea.getMinLon(); 044 this.lat2 = downloadArea.getMaxLat(); 045 this.lon2 = downloadArea.getMaxLon(); 046 this.crosses180th = downloadArea.crosses180thMeridian(); 047 } 048 049 private GpxData downloadRawGps(Bounds b, ProgressMonitor progressMonitor) throws IOException, OsmTransferException, SAXException { 050 boolean done = false; 051 GpxData result = null; 052 String url = "trackpoints?bbox="+b.getMinLon()+','+b.getMinLat()+','+b.getMaxLon()+','+b.getMaxLat()+"&page="; 053 for (int i = 0; !done && !isCanceled(); ++i) { 054 progressMonitor.subTask(tr("Downloading points {0} to {1}...", i * 5000, (i + 1) * 5000)); 055 try (InputStream in = getInputStream(url+i, progressMonitor.createSubTaskMonitor(1, true))) { 056 if (in == null) { 057 break; 058 } 059 progressMonitor.setTicks(0); 060 GpxReader reader = new GpxReader(in); 061 gpxParsedProperly = reader.parse(false); 062 GpxData currentGpx = reader.getGpxData(); 063 if (result == null) { 064 result = currentGpx; 065 } else if (currentGpx.hasTrackPoints()) { 066 result.mergeFrom(currentGpx); 067 } else { 068 done = true; 069 } 070 } catch (OsmTransferException | SocketException ex) { 071 if (isCanceled()) { 072 final OsmTransferCanceledException canceledException = new OsmTransferCanceledException("Operation canceled"); 073 canceledException.initCause(ex); 074 Main.warn(canceledException); 075 } 076 } 077 activeConnection = null; 078 } 079 if (result != null) { 080 result.fromServer = true; 081 result.dataSources.add(new DataSource(b, "OpenStreetMap server")); 082 } 083 return result; 084 } 085 086 @Override 087 public GpxData parseRawGps(ProgressMonitor progressMonitor) throws OsmTransferException { 088 progressMonitor.beginTask("", 1); 089 try { 090 progressMonitor.indeterminateSubTask(getTaskName()); 091 if (crosses180th) { 092 // API 0.6 does not support requests crossing the 180th meridian, so make two requests 093 GpxData result = downloadRawGps(new Bounds(lat1, lon1, lat2, 180.0), progressMonitor); 094 result.mergeFrom(downloadRawGps(new Bounds(lat1, -180.0, lat2, lon2), progressMonitor)); 095 return result; 096 } else { 097 // Simple request 098 return downloadRawGps(new Bounds(lat1, lon1, lat2, lon2), progressMonitor); 099 } 100 } catch (IllegalArgumentException e) { 101 // caused by HttpUrlConnection in case of illegal stuff in the response 102 if (cancel) 103 return null; 104 throw new OsmTransferException("Illegal characters within the HTTP-header response.", e); 105 } catch (IOException e) { 106 if (cancel) 107 return null; 108 throw new OsmTransferException(e); 109 } catch (SAXException e) { 110 throw new OsmTransferException(e); 111 } catch (OsmTransferException e) { 112 throw e; 113 } catch (RuntimeException e) { 114 if (cancel) 115 return null; 116 throw e; 117 } finally { 118 progressMonitor.finishTask(); 119 } 120 } 121 122 /** 123 * Returns the name of the download task to be displayed in the {@link ProgressMonitor}. 124 * @return task name 125 */ 126 protected String getTaskName() { 127 return tr("Contacting OSM Server..."); 128 } 129 130 /** 131 * Builds the request part for the bounding box. 132 * @param lon1 left 133 * @param lat1 bottom 134 * @param lon2 right 135 * @param lat2 top 136 * @return "map?bbox=left,bottom,right,top" 137 */ 138 protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) { 139 return "map?bbox=" + lon1 + ',' + lat1 + ',' + lon2 + ',' + lat2; 140 } 141 142 /** 143 * Parse the given input source and return the dataset. 144 * @param source input stream 145 * @param progressMonitor progress monitor 146 * @return dataset 147 * @throws IllegalDataException if an error was found while parsing the OSM data 148 * 149 * @see OsmReader#parseDataSet(InputStream, ProgressMonitor) 150 */ 151 protected DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException { 152 return OsmReader.parseDataSet(source, progressMonitor); 153 } 154 155 @Override 156 public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException { 157 progressMonitor.beginTask(getTaskName(), 10); 158 try { 159 DataSet ds = null; 160 progressMonitor.indeterminateSubTask(null); 161 if (crosses180th) { 162 // API 0.6 does not support requests crossing the 180th meridian, so make two requests 163 DataSet ds2 = null; 164 165 try (InputStream in = getInputStream(getRequestForBbox(lon1, lat1, 180.0, lat2), 166 progressMonitor.createSubTaskMonitor(9, false))) { 167 if (in == null) 168 return null; 169 ds = parseDataSet(in, progressMonitor.createSubTaskMonitor(1, false)); 170 } 171 172 try (InputStream in = getInputStream(getRequestForBbox(-180.0, lat1, lon2, lat2), 173 progressMonitor.createSubTaskMonitor(9, false))) { 174 if (in == null) 175 return null; 176 ds2 = parseDataSet(in, progressMonitor.createSubTaskMonitor(1, false)); 177 } 178 if (ds2 == null) 179 return null; 180 ds.mergeFrom(ds2); 181 182 } else { 183 // Simple request 184 try (InputStream in = getInputStream(getRequestForBbox(lon1, lat1, lon2, lat2), 185 progressMonitor.createSubTaskMonitor(9, false))) { 186 if (in == null) 187 return null; 188 ds = parseDataSet(in, progressMonitor.createSubTaskMonitor(1, false)); 189 } 190 } 191 return ds; 192 } catch (OsmTransferException e) { 193 throw e; 194 } catch (Exception e) { 195 throw new OsmTransferException(e); 196 } finally { 197 progressMonitor.finishTask(); 198 activeConnection = null; 199 } 200 } 201 202 @Override 203 public List<Note> parseNotes(int noteLimit, int daysClosed, ProgressMonitor progressMonitor) 204 throws OsmTransferException, MoreNotesException { 205 progressMonitor.beginTask(tr("Downloading notes")); 206 CheckParameterUtil.ensureThat(noteLimit > 0, "Requested note limit is less than 1."); 207 // see result_limit in https://github.com/openstreetmap/openstreetmap-website/blob/master/app/controllers/notes_controller.rb 208 CheckParameterUtil.ensureThat(noteLimit <= 10000, "Requested note limit is over API hard limit of 10000."); 209 CheckParameterUtil.ensureThat(daysClosed >= -1, "Requested note limit is less than -1."); 210 String url = "notes?limit=" + noteLimit + "&closed=" + daysClosed + "&bbox=" + lon1 + ',' + lat1 + ',' + lon2 + ',' + lat2; 211 try { 212 InputStream is = getInputStream(url, progressMonitor.createSubTaskMonitor(1, false)); 213 NoteReader reader = new NoteReader(is); 214 final List<Note> notes = reader.parse(); 215 if (notes.size() == noteLimit) { 216 throw new MoreNotesException(notes, noteLimit); 217 } 218 return notes; 219 } catch (IOException | SAXException e) { 220 throw new OsmTransferException(e); 221 } finally { 222 progressMonitor.finishTask(); 223 } 224 } 225 226 /** 227 * Indicates that the number of fetched notes equals the specified limit. Thus there might be more notes to download. 228 */ 229 public static class MoreNotesException extends RuntimeException { 230 /** 231 * The downloaded notes 232 */ 233 public final transient List<Note> notes; 234 /** 235 * The download limit sent to the server. 236 */ 237 public final int limit; 238 239 public MoreNotesException(List<Note> notes, int limit) { 240 this.notes = notes; 241 this.limit = limit; 242 } 243 } 244 245}