diff --git a/src/org/oscim/utils/overpass/OverpassAPIReader.java b/src/org/oscim/utils/overpass/OverpassAPIReader.java new file mode 100644 index 00000000..488cfd7a --- /dev/null +++ b/src/org/oscim/utils/overpass/OverpassAPIReader.java @@ -0,0 +1,415 @@ +/* + * Copyright 2013 Hannes Janetzek + * + * This program is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with + * this program. If not, see . + */ + +package org.oscim.utils.overpass; + +import static org.oscim.core.osm.TagGroup.EMPTY_TAG_GROUP; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import org.oscim.core.osm.Bound; +import org.oscim.core.osm.OSMData; +import org.oscim.core.osm.OSMElement; +import org.oscim.core.osm.OSMMember; +import org.oscim.core.osm.OSMNode; +import org.oscim.core.osm.OSMRelation; +import org.oscim.core.osm.OSMWay; +import org.oscim.core.osm.TagGroup; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +public class OverpassAPIReader { + private static final String OVERPASS_API = "http://city.informatik.uni-bremen.de/oapi/interpreter"; + private static final int RESPONSECODE_OK = 200; + + /** + * The timeout we use for the HttpURLConnection. + */ + private static final int TIMEOUT = 15000; + + /** + * The base url of the server. Defaults to. + * "http://www.openstreetmap.org/api/0.5". + */ + private final String myBaseUrl = OVERPASS_API; + + /** + * The http connection used to retrieve data. + */ + private HttpURLConnection myActiveConnection; + + /** + * The stream providing response data. + */ + private InputStream responseStream; + + private final String query; + + /** + * Creates a new instance with the specified geographical coordinates. + * + * @param left + * The longitude marking the left edge of the bounding box. + * @param right + * The longitude marking the right edge of the bounding box. + * @param top + * The latitude marking the top edge of the bounding box. + * @param bottom + * The latitude marking the bottom edge of the bounding box. + * @param baseUrl + * (optional) The base url of the server (eg. + * http://www.openstreetmap.org/api/0.5). + */ + public OverpassAPIReader(final double left, final double right, + final double top, final double bottom, final String baseUrl, + final String query) { + + String bbox = "(" + Math.min(top, bottom) + "," + Math.min(left, right) + + "," + Math.max(top, bottom) + "," + Math.max(left, right) + + ")"; + + this.query = query.replaceAll("\\{\\{bbox\\}\\}", bbox); + + } + + /** + * Open a connection to the given url and return a reader on the input + * stream from that connection. + * + * @param pUrlStr + * The exact url to connect to. + * @return An reader reading the input stream (servers answer) or + * null. + * @throws IOException + * on io-errors + */ + private InputStream getInputStream(final String pUrlStr) throws IOException { + URL url; + int responseCode; + String encoding; + + url = new URL(pUrlStr); + myActiveConnection = (HttpURLConnection) url.openConnection(); + + myActiveConnection.setRequestProperty("Accept-Encoding", + "gzip, deflate"); + + responseCode = myActiveConnection.getResponseCode(); + + if (responseCode != RESPONSECODE_OK) { + String message; + String apiErrorMessage; + + apiErrorMessage = myActiveConnection.getHeaderField("Error"); + + if (apiErrorMessage != null) { + message = "Received API HTTP response code " + responseCode + + " with message \"" + apiErrorMessage + + "\" for URL \"" + pUrlStr + "\"."; + } else { + message = "Received API HTTP response code " + responseCode + + " for URL \"" + pUrlStr + "\"."; + } + + throw new IOException(message); + } + + myActiveConnection.setConnectTimeout(TIMEOUT); + + encoding = myActiveConnection.getContentEncoding(); + + responseStream = myActiveConnection.getInputStream(); + if (encoding != null && encoding.equalsIgnoreCase("gzip")) { + responseStream = new GZIPInputStream(responseStream); + } else if (encoding != null && encoding.equalsIgnoreCase("deflate")) { + responseStream = new InflaterInputStream(responseStream, + new Inflater(true)); + } + + return responseStream; + } + + class TmpRelation { + Long id; + String type; + String role; + } + + private final List bounds = new ArrayList(); + private Map nodesById = new HashMap(); + private Map waysById = new HashMap(); + private Map relationsById = new HashMap(); + private Map> relationMembersForRelation = + new HashMap>(); + + private final Collection ownNodes = new ArrayList(10000); + private final Collection ownWays = new ArrayList(1000); + private final Collection ownRelations = new ArrayList( + 100); + + public void parse(InputStream in) throws IOException { + JsonFactory jsonFactory = new JsonFactory(); + try { + JsonParser jp = jsonFactory.createJsonParser(in); + + JsonToken t; + while ((t = jp.nextToken()) != null) { + if (t == JsonToken.START_OBJECT) { + jp.nextToken(); + + String name = jp.getCurrentName(); + jp.nextToken(); + + if ("type".equals(name)) { + String type = jp.getText(); + + if ("node".equals(type)) + parseNode(jp); + + else if ("way".equals(type)) + parseWay(jp); + + else if ("relation".equals(type)) + parseRelation(jp); + } + } + } + } catch (JsonParseException e) { + e.printStackTrace(); + } + } + + private void parseNode(JsonParser jp) throws JsonParseException, + IOException { + + long id = 0; + double lat = 0, lon = 0; + TagGroup tags = EMPTY_TAG_GROUP; + + while (jp.nextToken() != JsonToken.END_OBJECT) { + + String name = jp.getCurrentName(); + jp.nextToken(); + + if ("id".equals(name)) + id = jp.getLongValue(); + + else if ("lat".equals(name)) + lat = jp.getDoubleValue(); + + else if ("lon".equals(name)) + lon = jp.getDoubleValue(); + + else if ("tags".equals(name)) + tags = parseTags(jp); + + } + + // log("node: "+id + " "+ lat + " " + lon); + OSMNode node = new OSMNode(lat, lon, tags, id); + ownNodes.add(node); + nodesById.put(Long.valueOf(id), node); + } + + private void parseWay(JsonParser jp) throws JsonParseException, IOException { + + long id = 0; + TagGroup tags = EMPTY_TAG_GROUP; + ArrayList wayNodes = new ArrayList(); + + while (jp.nextToken() != JsonToken.END_OBJECT) { + + String name = jp.getCurrentName(); + jp.nextToken(); + + if ("id".equals(name)) + id = jp.getLongValue(); + + else if ("nodes".equals(name)) { + while (jp.nextToken() != JsonToken.END_ARRAY) { + Long nodeId = Long.valueOf(jp.getLongValue()); + + OSMNode node = nodesById.get(nodeId); + if (node != null) + // log("missing node " + nodeId); + // else + wayNodes.add(node); + } + } else if ("tags".equals(name)) + tags = parseTags(jp); + } + + // log("way: "+ id + " " + wayNodes.size()); + OSMWay way = new OSMWay(tags, id, wayNodes); + ownWays.add(way); + waysById.put(Long.valueOf(id), way); + } + + private void parseRelation(JsonParser jp) throws JsonParseException, + IOException { + + long id = 0; + TagGroup tags = EMPTY_TAG_GROUP; + ArrayList members = new ArrayList(); + + while (jp.nextToken() != JsonToken.END_OBJECT) { + + String name = jp.getCurrentName(); + jp.nextToken(); + + if ("id".equals(name)) + id = jp.getLongValue(); + + else if ("members".equals(name)) { + while (jp.nextToken() != JsonToken.END_ARRAY) { + TmpRelation member = new TmpRelation(); + + while (jp.nextToken() != JsonToken.END_OBJECT) { + name = jp.getCurrentName(); + jp.nextToken(); + + if ("type".equals(name)) + member.type = jp.getText(); + + else if ("ref".equals(name)) + member.id = Long.valueOf(jp.getLongValue()); + + else if ("role".equals(name)) + member.role = jp.getText(); + } + members.add(member); + } + } else if ("tags".equals(name)) + tags = parseTags(jp); + } + + OSMRelation relation = new OSMRelation(tags, id, members.size()); + ownRelations.add(relation); + relationsById.put(Long.valueOf(id), relation); + relationMembersForRelation.put(relation, members); + } + + private static TagGroup parseTags(JsonParser jp) throws JsonParseException, + IOException { + + Map tagMap = null; + + while (jp.nextToken() != JsonToken.END_OBJECT) { + String key = jp.getCurrentName(); + jp.nextToken(); + String val = jp.getText(); + if (tagMap == null) + tagMap = new HashMap(10); + + tagMap.put(key, val); + + } + if (tagMap == null) + return EMPTY_TAG_GROUP; + + return new TagGroup(tagMap); + } + + private static void log(String msg) { + System.out.println(msg); + } + + + public OSMData getData() { + + String encoded; + try { + encoded = URLEncoder.encode(this.query, "utf-8"); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); + return null; + } + System.out.println(myBaseUrl + "?data=" + encoded); + + InputStream inputStream = null; + + try { + inputStream = getInputStream(myBaseUrl + "?data=[out:json];" + encoded); + + parse(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (inputStream != null) + try { + inputStream.close(); + } catch (IOException e) { + //... + } + inputStream = null; + } + + for (Entry> entry : relationMembersForRelation + .entrySet()) { + + OSMRelation relation = entry.getKey(); + + for (TmpRelation member : entry.getValue()) { + + OSMElement memberObject = null; + + if ("node".equals(member)) { + memberObject = nodesById.get(member.id); + } else if ("way".equals(member)) { + memberObject = waysById.get(member.id); + } else if ("relation".equals(member)) { + memberObject = relationsById.get(member.id); + } else { + // log("missing relation " + member.id); + continue; + } + + if (memberObject != null) { + OSMMember ownMember = new OSMMember(member.role, + memberObject); + + relation.relationMembers.add(ownMember); + } + } + } + log("nodes: " + ownNodes.size() + " ways: " + ownWays.size() + + " relations: " + ownRelations.size()); + + // give up references to original collections + nodesById = null; + waysById = null; + relationsById = null; + relationMembersForRelation = null; + + return new OSMData(bounds, ownNodes, ownWays, ownRelations); + } +}