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

Last change on this file since 12557 was 12557, checked in by Don-vip, 3 months ago

see #15102 - first batch of HTTP unit tests mocking, using WireMock 2.7.1

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