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.InputStream;
007
008import javax.xml.stream.XMLStreamConstants;
009import javax.xml.stream.XMLStreamException;
010
011import org.openstreetmap.josm.data.Bounds;
012import org.openstreetmap.josm.data.DataSource;
013import org.openstreetmap.josm.data.osm.DataSet;
014import org.openstreetmap.josm.gui.progress.ProgressMonitor;
015import org.openstreetmap.josm.tools.Utils;
016
017/**
018 * Read content from an Overpass server.
019 *
020 * @since 8744
021 */
022public class OverpassDownloadReader extends BoundingBoxDownloader {
023
024    final String overpassServer;
025    final String overpassQuery;
026
027    /**
028     * Constructs a new {@code OverpassDownloadReader}.
029     *
030     * @param downloadArea   The area to download
031     * @param overpassServer The Overpass server to use
032     * @param overpassQuery  The Overpass query
033     */
034    public OverpassDownloadReader(Bounds downloadArea, String overpassServer, String overpassQuery) {
035        super(downloadArea);
036        this.overpassServer = overpassServer;
037        this.overpassQuery = overpassQuery.trim();
038    }
039
040    @Override
041    protected String getBaseUrl() {
042        return overpassServer;
043    }
044
045    @Override
046    protected String getRequestForBbox(double lon1, double lat1, double lon2, double lat2) {
047        if (overpassQuery.isEmpty())
048            return super.getRequestForBbox(lon1, lat1, lon2, lat2);
049        else {
050            String realQuery = completeOverpassQuery(overpassQuery);
051            return "interpreter?data=" + Utils.encodeUrl(realQuery)
052                    + "&bbox=" + lon1 + ',' + lat1 + ',' + lon2 + ',' + lat2;
053        }
054    }
055
056    private static String completeOverpassQuery(String query) {
057        int firstColon = query.indexOf(';');
058        if (firstColon == -1) {
059            return "[bbox];" + query;
060        }
061        int bboxPos = query.indexOf("[bbox");
062        if (bboxPos > -1 && bboxPos < firstColon) {
063            return query;
064        }
065
066        int bracketCount = 0;
067        int pos = 0;
068        for (; pos < firstColon; ++pos) {
069            if (query.charAt(pos) == '[')
070                ++bracketCount;
071            else if (query.charAt(pos) == ']')
072                --bracketCount;
073            else if (bracketCount == 0) {
074                if (!Character.isWhitespace(query.charAt(pos)))
075                    break;
076            }
077        }
078
079        if (pos < firstColon) {
080            // We start with a statement, not with declarations
081            return "[bbox];" + query;
082        }
083
084        // We start with declarations. Add just one more declaration in this case.
085        return "[bbox]" + query;
086    }
087
088    @Override
089    protected InputStream getInputStreamRaw(String urlStr, ProgressMonitor progressMonitor, String reason,
090                                            boolean uncompressAccordingToContentDisposition) throws OsmTransferException {
091        try {
092            return super.getInputStreamRaw(urlStr, progressMonitor, reason, uncompressAccordingToContentDisposition);
093        } catch (OsmApiException ex) {
094            final String errorIndicator = "Error</strong>: ";
095            if (ex.getMessage() != null && ex.getMessage().contains(errorIndicator)) {
096                final String errorPlusRest = ex.getMessage().split(errorIndicator)[1];
097                if (errorPlusRest != null) {
098                    final String error = errorPlusRest.split("</")[0];
099                    ex.setErrorHeader(error);
100                }
101            }
102            throw ex;
103        }
104    }
105
106    @Override
107    protected String getTaskName() {
108        return tr("Contacting Server...");
109    }
110
111    @Override
112    protected DataSet parseDataSet(InputStream source, ProgressMonitor progressMonitor) throws IllegalDataException {
113        return new OsmReader() {
114            @Override
115            protected void parseUnknown(boolean printWarning) throws XMLStreamException {
116                if ("remark".equals(parser.getLocalName())) {
117                    if (parser.getEventType() == XMLStreamConstants.START_ELEMENT) {
118                        final String text = parser.getElementText();
119                        if (text.contains("runtime error")) {
120                            throw new XMLStreamException(text);
121                        }
122                    }
123                }
124                super.parseUnknown(printWarning);
125            }
126        }.doParseDataSet(source, progressMonitor);
127    }
128
129    @Override
130    public DataSet parseOsm(ProgressMonitor progressMonitor) throws OsmTransferException {
131
132        DataSet ds = super.parseOsm(progressMonitor);
133
134        // add bounds if necessary (note that Overpass API does not return bounds in the response XML)
135        if (ds != null && ds.dataSources.isEmpty()) {
136            if (crosses180th) {
137                Bounds bounds = new Bounds(lat1, lon1, lat2, 180.0);
138                DataSource src = new DataSource(bounds, getBaseUrl());
139                ds.dataSources.add(src);
140
141                bounds = new Bounds(lat1, -180.0, lat2, lon2);
142                src = new DataSource(bounds, getBaseUrl());
143                ds.dataSources.add(src);
144            } else {
145                Bounds bounds = new Bounds(lat1, lon1, lat2, lon2);
146                DataSource src = new DataSource(bounds, getBaseUrl());
147                ds.dataSources.add(src);
148            }
149        }
150
151        return ds;
152    }
153}