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