source: josm/trunk/src/org/openstreetmap/josm/io/NameFinder.java@ 15969

Last change on this file since 15969 was 15969, checked in by simon04, 4 years ago

see #18812 - OsmApi, OsmServerReader, NameFinder: specify Accept=application/xml, */*;q=0.8

File size: 10.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.IOException;
7import java.io.Reader;
8import java.net.URL;
9import java.util.Collections;
10import java.util.LinkedList;
11import java.util.List;
12
13import javax.xml.parsers.ParserConfigurationException;
14
15import org.openstreetmap.josm.data.Bounds;
16import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
17import org.openstreetmap.josm.data.osm.PrimitiveId;
18import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
19import org.openstreetmap.josm.data.preferences.StringProperty;
20import org.openstreetmap.josm.tools.HttpClient;
21import org.openstreetmap.josm.tools.HttpClient.Response;
22import org.openstreetmap.josm.tools.Logging;
23import org.openstreetmap.josm.tools.OsmUrlToBounds;
24import org.openstreetmap.josm.tools.UncheckedParseException;
25import org.openstreetmap.josm.tools.Utils;
26import org.openstreetmap.josm.tools.XmlUtils;
27import org.xml.sax.Attributes;
28import org.xml.sax.InputSource;
29import org.xml.sax.SAXException;
30import org.xml.sax.helpers.DefaultHandler;
31
32/**
33 * Search for names and related items.
34 * @since 11002
35 */
36public final class NameFinder {
37
38 /**
39 * Nominatim default URL.
40 */
41 public static final String NOMINATIM_URL = "https://nominatim.openstreetmap.org/search?format=xml&q=";
42
43 /**
44 * Nominatim URL property.
45 * @since 12557
46 */
47 public static final StringProperty NOMINATIM_URL_PROP = new StringProperty("nominatim-url", NOMINATIM_URL);
48
49 private NameFinder() {
50 }
51
52 /**
53 * Performs a Nominatim search.
54 * @param searchExpression Nominatim search expression
55 * @return search results
56 * @throws IOException if any IO error occurs.
57 */
58 public static List<SearchResult> queryNominatim(final String searchExpression) throws IOException {
59 return query(new URL(NOMINATIM_URL_PROP.get() + Utils.encodeUrl(searchExpression)));
60 }
61
62 /**
63 * Performs a custom search.
64 * @param url search URL to any Nominatim instance
65 * @return search results
66 * @throws IOException if any IO error occurs.
67 */
68 public static List<SearchResult> query(final URL url) throws IOException {
69 final HttpClient connection = HttpClient.create(url)
70 .setAccept("application/xml, */*;q=0.8");
71 Response response = connection.connect();
72 if (response.getResponseCode() >= 400) {
73 throw new IOException(response.getResponseMessage() + ": " + response.fetchContent());
74 }
75 try (Reader reader = response.getContentReader()) {
76 return parseSearchResults(reader);
77 } catch (ParserConfigurationException | SAXException ex) {
78 throw new UncheckedParseException(ex);
79 }
80 }
81
82 /**
83 * Parse search results as returned by Nominatim.
84 * @param reader reader
85 * @return search results
86 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
87 * @throws SAXException for SAX errors.
88 * @throws IOException if any IO error occurs.
89 */
90 public static List<SearchResult> parseSearchResults(Reader reader) throws IOException, ParserConfigurationException, SAXException {
91 InputSource inputSource = new InputSource(reader);
92 NameFinderResultParser parser = new NameFinderResultParser();
93 XmlUtils.parseSafeSAX(inputSource, parser);
94 return parser.getResult();
95 }
96
97 /**
98 * Data storage for search results.
99 */
100 public static class SearchResult {
101 private String name;
102 private String info;
103 private String nearestPlace;
104 private String description;
105 private double lat;
106 private double lon;
107 private int zoom;
108 private Bounds bounds;
109 private PrimitiveId osmId;
110
111 /**
112 * Returns the name.
113 * @return the name
114 */
115 public final String getName() {
116 return name;
117 }
118
119 /**
120 * Returns the info.
121 * @return the info
122 */
123 public final String getInfo() {
124 return info;
125 }
126
127 /**
128 * Returns the nearest place.
129 * @return the nearest place
130 */
131 public final String getNearestPlace() {
132 return nearestPlace;
133 }
134
135 /**
136 * Returns the description.
137 * @return the description
138 */
139 public final String getDescription() {
140 return description;
141 }
142
143 /**
144 * Returns the latitude.
145 * @return the latitude
146 */
147 public final double getLat() {
148 return lat;
149 }
150
151 /**
152 * Returns the longitude.
153 * @return the longitude
154 */
155 public final double getLon() {
156 return lon;
157 }
158
159 /**
160 * Returns the zoom.
161 * @return the zoom
162 */
163 public final int getZoom() {
164 return zoom;
165 }
166
167 /**
168 * Returns the bounds.
169 * @return the bounds
170 */
171 public final Bounds getBounds() {
172 return bounds;
173 }
174
175 /**
176 * Returns the OSM id.
177 * @return the OSM id
178 */
179 public final PrimitiveId getOsmId() {
180 return osmId;
181 }
182
183 /**
184 * Returns the download area.
185 * @return the download area
186 */
187 public Bounds getDownloadArea() {
188 return bounds != null ? bounds : OsmUrlToBounds.positionToBounds(lat, lon, zoom);
189 }
190 }
191
192 /**
193 * A very primitive parser for the name finder's output.
194 * Structure of xml described here: http://wiki.openstreetmap.org/index.php/Name_finder
195 */
196 private static class NameFinderResultParser extends DefaultHandler {
197 private SearchResult currentResult;
198 private StringBuilder description;
199 private int depth;
200 private final List<SearchResult> data = new LinkedList<>();
201
202 /**
203 * Detect starting elements.
204 */
205 @Override
206 public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
207 throws SAXException {
208 depth++;
209 try {
210 if ("searchresults".equals(qName)) {
211 // do nothing
212 } else if (depth == 2 && "named".equals(qName)) {
213 currentResult = new SearchResult();
214 currentResult.name = atts.getValue("name");
215 currentResult.info = atts.getValue("info");
216 if (currentResult.info != null) {
217 currentResult.info = tr(currentResult.info);
218 }
219 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
220 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
221 currentResult.zoom = Integer.parseInt(atts.getValue("zoom"));
222 data.add(currentResult);
223 } else if (depth == 3 && "description".equals(qName)) {
224 description = new StringBuilder();
225 } else if (depth == 4 && "named".equals(qName)) {
226 // this is a "named" place in the nearest places list.
227 String info = atts.getValue("info");
228 if ("city".equals(info) || "town".equals(info) || "village".equals(info)) {
229 currentResult.nearestPlace = atts.getValue("name");
230 }
231 } else if ("place".equals(qName) && atts.getValue("lat") != null) {
232 currentResult = new SearchResult();
233 currentResult.name = atts.getValue("display_name");
234 currentResult.description = currentResult.name;
235 currentResult.info = atts.getValue("class");
236 if (currentResult.info != null) {
237 currentResult.info = tr(currentResult.info);
238 }
239 currentResult.nearestPlace = tr(atts.getValue("type"));
240 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
241 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
242 String[] bbox = atts.getValue("boundingbox").split(",");
243 currentResult.bounds = new Bounds(
244 Double.parseDouble(bbox[0]), Double.parseDouble(bbox[2]),
245 Double.parseDouble(bbox[1]), Double.parseDouble(bbox[3]));
246 final String osmId = atts.getValue("osm_id");
247 final String osmType = atts.getValue("osm_type");
248 if (osmId != null && osmType != null) {
249 currentResult.osmId = new SimplePrimitiveId(Long.parseLong(osmId), OsmPrimitiveType.from(osmType));
250 }
251 data.add(currentResult);
252 }
253 } catch (NumberFormatException ex) {
254 Logging.error(ex); // SAXException does not chain correctly
255 throw new SAXException(ex.getMessage(), ex);
256 } catch (NullPointerException ex) { // NOPMD
257 Logging.error(ex); // SAXException does not chain correctly
258 throw new SAXException(tr("Null pointer exception, possibly some missing tags."), ex);
259 }
260 }
261
262 /**
263 * Detect ending elements.
264 */
265 @Override
266 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
267 if (description != null && "description".equals(qName)) {
268 currentResult.description = description.toString();
269 description = null;
270 }
271 depth--;
272 }
273
274 /**
275 * Read characters for description.
276 */
277 @Override
278 public void characters(char[] data, int start, int length) throws SAXException {
279 if (description != null) {
280 description.append(data, start, length);
281 }
282 }
283
284 public List<SearchResult> getResult() {
285 return Collections.unmodifiableList(data);
286 }
287 }
288}
Note: See TracBrowser for help on using the repository browser.