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