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

Last change on this file since 14216 was 13901, checked in by Don-vip, 6 years ago

add new XmlUtils class with more "safe factories" methods

File size: 10.0 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 Response response = connection.connect();
71 if (response.getResponseCode() >= 400) {
72 throw new IOException(response.getResponseMessage() + ": " + response.fetchContent());
73 }
74 try (Reader reader = response.getContentReader()) {
75 return parseSearchResults(reader);
76 } catch (ParserConfigurationException | SAXException ex) {
77 throw new UncheckedParseException(ex);
78 }
79 }
80
81 /**
82 * Parse search results as returned by Nominatim.
83 * @param reader reader
84 * @return search results
85 * @throws ParserConfigurationException if a parser cannot be created which satisfies the requested configuration.
86 * @throws SAXException for SAX errors.
87 * @throws IOException if any IO error occurs.
88 */
89 public static List<SearchResult> parseSearchResults(Reader reader) throws IOException, ParserConfigurationException, SAXException {
90 InputSource inputSource = new InputSource(reader);
91 NameFinderResultParser parser = new NameFinderResultParser();
92 XmlUtils.parseSafeSAX(inputSource, parser);
93 return parser.getResult();
94 }
95
96 /**
97 * Data storage for search results.
98 */
99 public static class SearchResult {
100 private String name;
101 private String info;
102 private String nearestPlace;
103 private String description;
104 private double lat;
105 private double lon;
106 private int zoom;
107 private Bounds bounds;
108 private PrimitiveId osmId;
109
110 /**
111 * Returns the name.
112 * @return the name
113 */
114 public final String getName() {
115 return name;
116 }
117
118 /**
119 * Returns the info.
120 * @return the info
121 */
122 public final String getInfo() {
123 return info;
124 }
125
126 /**
127 * Returns the nearest place.
128 * @return the nearest place
129 */
130 public final String getNearestPlace() {
131 return nearestPlace;
132 }
133
134 /**
135 * Returns the description.
136 * @return the description
137 */
138 public final String getDescription() {
139 return description;
140 }
141
142 /**
143 * Returns the latitude.
144 * @return the latitude
145 */
146 public final double getLat() {
147 return lat;
148 }
149
150 /**
151 * Returns the longitude.
152 * @return the longitude
153 */
154 public final double getLon() {
155 return lon;
156 }
157
158 /**
159 * Returns the zoom.
160 * @return the zoom
161 */
162 public final int getZoom() {
163 return zoom;
164 }
165
166 /**
167 * Returns the bounds.
168 * @return the bounds
169 */
170 public final Bounds getBounds() {
171 return bounds;
172 }
173
174 /**
175 * Returns the OSM id.
176 * @return the OSM id
177 */
178 public final PrimitiveId getOsmId() {
179 return osmId;
180 }
181
182 /**
183 * Returns the download area.
184 * @return the download area
185 */
186 public Bounds getDownloadArea() {
187 return bounds != null ? bounds : OsmUrlToBounds.positionToBounds(lat, lon, zoom);
188 }
189 }
190
191 /**
192 * A very primitive parser for the name finder's output.
193 * Structure of xml described here: http://wiki.openstreetmap.org/index.php/Name_finder
194 */
195 private static class NameFinderResultParser extends DefaultHandler {
196 private SearchResult currentResult;
197 private StringBuilder description;
198 private int depth;
199 private final List<SearchResult> data = new LinkedList<>();
200
201 /**
202 * Detect starting elements.
203 */
204 @Override
205 public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
206 throws SAXException {
207 depth++;
208 try {
209 if ("searchresults".equals(qName)) {
210 // do nothing
211 } else if (depth == 2 && "named".equals(qName)) {
212 currentResult = new SearchResult();
213 currentResult.name = atts.getValue("name");
214 currentResult.info = atts.getValue("info");
215 if (currentResult.info != null) {
216 currentResult.info = tr(currentResult.info);
217 }
218 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
219 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
220 currentResult.zoom = Integer.parseInt(atts.getValue("zoom"));
221 data.add(currentResult);
222 } else if (depth == 3 && "description".equals(qName)) {
223 description = new StringBuilder();
224 } else if (depth == 4 && "named".equals(qName)) {
225 // this is a "named" place in the nearest places list.
226 String info = atts.getValue("info");
227 if ("city".equals(info) || "town".equals(info) || "village".equals(info)) {
228 currentResult.nearestPlace = atts.getValue("name");
229 }
230 } else if ("place".equals(qName) && atts.getValue("lat") != null) {
231 currentResult = new SearchResult();
232 currentResult.name = atts.getValue("display_name");
233 currentResult.description = currentResult.name;
234 currentResult.info = atts.getValue("class");
235 if (currentResult.info != null) {
236 currentResult.info = tr(currentResult.info);
237 }
238 currentResult.nearestPlace = tr(atts.getValue("type"));
239 currentResult.lat = Double.parseDouble(atts.getValue("lat"));
240 currentResult.lon = Double.parseDouble(atts.getValue("lon"));
241 String[] bbox = atts.getValue("boundingbox").split(",");
242 currentResult.bounds = new Bounds(
243 Double.parseDouble(bbox[0]), Double.parseDouble(bbox[2]),
244 Double.parseDouble(bbox[1]), Double.parseDouble(bbox[3]));
245 final String osmId = atts.getValue("osm_id");
246 final String osmType = atts.getValue("osm_type");
247 if (osmId != null && osmType != null) {
248 currentResult.osmId = new SimplePrimitiveId(Long.parseLong(osmId), OsmPrimitiveType.from(osmType));
249 }
250 data.add(currentResult);
251 }
252 } catch (NumberFormatException ex) {
253 Logging.error(ex); // SAXException does not chain correctly
254 throw new SAXException(ex.getMessage(), ex);
255 } catch (NullPointerException ex) { // NOPMD
256 Logging.error(ex); // SAXException does not chain correctly
257 throw new SAXException(tr("Null pointer exception, possibly some missing tags."), ex);
258 }
259 }
260
261 /**
262 * Detect ending elements.
263 */
264 @Override
265 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
266 if (description != null && "description".equals(qName)) {
267 currentResult.description = description.toString();
268 description = null;
269 }
270 depth--;
271 }
272
273 /**
274 * Read characters for description.
275 */
276 @Override
277 public void characters(char[] data, int start, int length) throws SAXException {
278 if (description != null) {
279 description.append(data, start, length);
280 }
281 }
282
283 public List<SearchResult> getResult() {
284 return Collections.unmodifiableList(data);
285 }
286 }
287}
Note: See TracBrowser for help on using the repository browser.