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

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

add new XmlUtils class with more "safe factories" methods

File size: 10.0 KB
RevLine 
[11002]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;
[11016]9import java.util.Collections;
[11002]10import java.util.LinkedList;
11import java.util.List;
12
13import javax.xml.parsers.ParserConfigurationException;
14
15import org.openstreetmap.josm.data.Bounds;
[11003]16import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
17import org.openstreetmap.josm.data.osm.PrimitiveId;
18import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
[12557]19import org.openstreetmap.josm.data.preferences.StringProperty;
[11002]20import org.openstreetmap.josm.tools.HttpClient;
[12557]21import org.openstreetmap.josm.tools.HttpClient.Response;
[12620]22import org.openstreetmap.josm.tools.Logging;
[11002]23import org.openstreetmap.josm.tools.OsmUrlToBounds;
24import org.openstreetmap.josm.tools.UncheckedParseException;
25import org.openstreetmap.josm.tools.Utils;
[13901]26import org.openstreetmap.josm.tools.XmlUtils;
[11002]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 */
[11009]36public final class NameFinder {
[11002]37
[11009]38 /**
[12557]39 * Nominatim default URL.
[11009]40 */
[11002]41 public static final String NOMINATIM_URL = "https://nominatim.openstreetmap.org/search?format=xml&q=";
42
[12557]43 /**
44 * Nominatim URL property.
[12558]45 * @since 12557
[12557]46 */
47 public static final StringProperty NOMINATIM_URL_PROP = new StringProperty("nominatim-url", NOMINATIM_URL);
48
[11002]49 private NameFinder() {
50 }
51
[11016]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 */
[11002]58 public static List<SearchResult> queryNominatim(final String searchExpression) throws IOException {
[12557]59 return query(new URL(NOMINATIM_URL_PROP.get() + Utils.encodeUrl(searchExpression)));
[11002]60 }
61
[11016]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 */
[11002]68 public static List<SearchResult> query(final URL url) throws IOException {
69 final HttpClient connection = HttpClient.create(url);
[12557]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()) {
[11002]75 return parseSearchResults(reader);
76 } catch (ParserConfigurationException | SAXException ex) {
77 throw new UncheckedParseException(ex);
78 }
79 }
80
[11016]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 */
[11002]89 public static List<SearchResult> parseSearchResults(Reader reader) throws IOException, ParserConfigurationException, SAXException {
90 InputSource inputSource = new InputSource(reader);
91 NameFinderResultParser parser = new NameFinderResultParser();
[13901]92 XmlUtils.parseSafeSAX(inputSource, parser);
[11002]93 return parser.getResult();
94 }
95
96 /**
97 * Data storage for search results.
98 */
99 public static class SearchResult {
[11009]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;
[11002]109
[11009]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 */
[11002]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
[11452]211 } else if (depth == 2 && "named".equals(qName)) {
[11002]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);
[11452]222 } else if (depth == 3 && "description".equals(qName)) {
[11002]223 description = new StringBuilder();
[11452]224 } else if (depth == 4 && "named".equals(qName)) {
[11002]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]));
[11003]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 }
[11002]250 data.add(currentResult);
251 }
[11620]252 } catch (NumberFormatException ex) {
[12620]253 Logging.error(ex); // SAXException does not chain correctly
[11620]254 throw new SAXException(ex.getMessage(), ex);
[11746]255 } catch (NullPointerException ex) { // NOPMD
[12620]256 Logging.error(ex); // SAXException does not chain correctly
[11620]257 throw new SAXException(tr("Null pointer exception, possibly some missing tags."), ex);
[11002]258 }
259 }
260
261 /**
262 * Detect ending elements.
263 */
264 @Override
265 public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
[11452]266 if (description != null && "description".equals(qName)) {
[11002]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() {
[11016]284 return Collections.unmodifiableList(data);
[11002]285 }
286 }
287}
Note: See TracBrowser for help on using the repository browser.