package de.westnordost.streetcomplete.data.osm.download; import android.util.Log; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import javax.inject.Inject; import javax.inject.Provider; import de.westnordost.osmapi.ApiRequestWriter; import de.westnordost.osmapi.OsmConnection; import de.westnordost.osmapi.common.errors.OsmApiException; import de.westnordost.osmapi.common.errors.OsmBadUserInputException; /** Get map data from overpass api */ public class OverpassMapDataDao { private static final String TAG = "OverpassMapDataDao"; private final OsmConnection osm; private final Provider<OverpassMapDataParser> parserProvider; @Inject public OverpassMapDataDao(OsmConnection osm, Provider<OverpassMapDataParser> parserProvider) { this.osm = osm; this.parserProvider = parserProvider; } /** * Feeds map data to the given MapDataWithGeometryHandler. * * @param query Query string. Either Overpass QL or Overpass XML query string * @param handler map data handler that is fed the map data and geometry * * @throws OsmTooManyRequestsException if the user is over his request quota. See getStatus, killMyQueries * @throws OsmBadUserInputException if there is an error if the query */ public synchronized void get(final String query, MapDataWithGeometryHandler handler) { OverpassMapDataParser parser = parserProvider.get(); parser.setHandler(handler); try { ApiRequestWriter writer = new ApiRequestWriter() { @Override public String getContentType() { return "application/x-www-form-urlencoded"; } @Override public void write(OutputStream out) throws IOException { String request = "data=" + urlEncode(query); out.write(request.getBytes()); } }; osm.makeRequest("interpreter", "POST", false, writer, parser); } catch(OsmApiException e) { if(e.getErrorCode() == 429) throw new OsmTooManyRequestsException(e.getErrorCode(), e.getErrorTitle(), e.getDescription()); else throw e; } } /** Same as get(String, MapDataWithGeometryHandler), only that it automatically waits until the * app is allowed to do requests again by request quota if it hits the request quota. * @param query Query string. Either Overpass QL or Overpass XML query string * @param handler map data handler that is fed the map data and geometry * @return false if it was interrupted while waiting for the quota to be replenished * * @throws OsmBadUserInputException if there is an error if the query */ public synchronized boolean getAndHandleQuota(String query, MapDataWithGeometryHandler handler) { try { get(query, handler); } catch(OsmTooManyRequestsException e) { OverpassStatus status = getStatus(); if(status.availableSlots == 0) { // rather wait 1s longer than required cause we only get the time in seconds int wait = (1 + status.nextAvailableSlotIn) * 1000; Log.i(TAG, "Hit Overpass quota. Waiting " + wait + "ms before continuing"); try { Thread.sleep(wait); } catch (InterruptedException ie) { Log.d(TAG, "Thread interrupted while waiting for Overpass quota to be replenished"); return false; } } return getAndHandleQuota(query, handler); } return true; } /** Kills all the queries sent from this IP. Useful if there is a runaway query that takes far * too much time and blocks the user from making any more queries */ public void killMyQueries() { osm.makeRequest("kill_my_queries", null); } /** Get info about how many queries the user may make until reaching his quota */ public OverpassStatus getStatus() { return osm.makeRequest("status", new OverpassStatusParser()); } private String urlEncode(String text) { try { return URLEncoder.encode(text, OsmConnection.CHARSET); } catch (UnsupportedEncodingException e) { // should never happen since we use UTF-8 throw new RuntimeException(e); } } }