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