package de.blau.android.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.stream.JsonReader;
import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AlertDialog.Builder;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.AppCompatDialog;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import de.blau.android.App;
import de.blau.android.R;
import de.blau.android.dialogs.Progress;
import de.blau.android.dialogs.ProgressDialog;
import de.blau.android.osm.BoundingBox;
import de.blau.android.prefs.AdvancedPrefDatabase.Geocoder;
import de.blau.android.presets.Preset;
import de.blau.android.presets.Preset.PresetItem;
import de.blau.android.util.mapbox.geojson.Feature;
import de.blau.android.util.mapbox.geojson.FeatureCollection;
import de.blau.android.util.mapbox.geojson.Geometry;
import de.blau.android.util.mapbox.geojson.Point;
import de.blau.android.util.mapbox.models.Position;
import okhttp3.Call;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* Search with nominatim, photon and maybe others
* @author simon
*
*/
public class Search {
private AppCompatActivity activity;
private SearchItemFoundCallback callback;
public class SearchResult {
private double lat;
private double lon;
String display_name;
@Override
public String toString() {
return "lat: " + getLat() + " lon: " + getLon() + " " + display_name;
}
/**
* @return the lat
*/
public double getLat() {
return lat;
}
/**
* @param lat the lat to set
*/
public void setLat(double lat) {
this.lat = lat;
}
/**
* @return the lon
*/
public double getLon() {
return lon;
}
/**
* @param lon the lon to set
*/
public void setLon(double lon) {
this.lon = lon;
}
}
/**
* Constructor
* @param appCompatActivity
* @param callback will be called when search result is selected
*/
public Search(AppCompatActivity appCompatActivity, SearchItemFoundCallback callback) {
this.activity = appCompatActivity;
this.callback = callback;
}
/**
* Query and then display a list of results to pick from
* @param q
*/
public void find(Geocoder geocoder, String q, BoundingBox bbox) {
Query querier = null;
boolean multiline = false;
switch (geocoder.type) {
case PHOTON:
querier = new QueryPhoton(geocoder.url, bbox);
multiline = true;
break;
case NOMINATIM:
default:
querier = new QueryNominatim(geocoder.url, bbox);
multiline = false;
break;
}
querier.execute(q);
try {
ArrayList<SearchResult> result = querier.get(20, TimeUnit.SECONDS);
if (result != null && result.size() > 0) {
AppCompatDialog sr = createSearchResultsDialog(result, multiline ? R.layout.search_results_item_multi_line : R.layout.search_results_item);
sr.show();
} else {
Snack.barInfo(activity, R.string.toast_nothing_found);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
Snack.barError(activity, R.string.toast_timeout);
}
}
private class Query extends AsyncTask<String, Void, ArrayList<SearchResult>> {
AlertDialog progress = null;
final BoundingBox bbox;
final String url;
public Query(String url, BoundingBox bbox) {
this.url = url;
this.bbox = bbox;
}
@Override
protected void onPreExecute() {
progress = ProgressDialog.get(activity, Progress.PROGRESS_LOADING);
progress.show();
}
@Override
protected ArrayList<SearchResult> doInBackground(String... params) {
return null;
}
@Override
protected void onPostExecute(ArrayList<SearchResult> res) {
try {
progress.dismiss();
} catch (Exception ex) {
Log.e("Search", "dismiss dialog failed with " + ex);
}
}
}
private class QueryNominatim extends Query {
public QueryNominatim() {
super(null, null);
}
public QueryNominatim(String url, BoundingBox bbox) {
super(url, bbox);
}
@Override
protected ArrayList<SearchResult> doInBackground(String... params) {
String query = params[0];
Uri.Builder builder = Uri.parse(url)
.buildUpon()
.appendPath("search")
.appendQueryParameter("q", query);
if (bbox != null) {
String viewBoxCoordinates = bbox.getLeft()/1E7D
+ "," + bbox.getBottom()/1E7D
+ "," + bbox.getRight()/1E7D
+ "," + bbox.getTop()/1E7D;
builder.appendQueryParameter("viewboxlbrt", viewBoxCoordinates);
}
Uri uriBuilder = builder.appendQueryParameter("format", "jsonv2").build();
String urlString = uriBuilder.toString();
Log.d("Search", "urlString: " + urlString);
InputStream inputStream = null;
JsonReader reader = null;
ResponseBody responseBody = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
Request request = new Request.Builder()
.url(urlString)
.build();
Call searchCall = App.getHttpClient().newCall(request);
Response searchCallResponse = searchCall.execute();
if (searchCallResponse.isSuccessful()) {
responseBody = searchCallResponse.body();
inputStream = responseBody.byteStream();
}
} else { //FIXME 2.2/API 8 support
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", App.userAgent);
inputStream = conn.getInputStream();
}
if (inputStream != null) {
reader = new JsonReader(new InputStreamReader(inputStream));
ArrayList<SearchResult> result = new ArrayList<SearchResult>();
reader.beginArray();
while (reader.hasNext()) {
SearchResult searchResult = readNominatimResult(reader);
if (searchResult != null) { //TODO handle deprecated
result.add(searchResult);
Log.d("Search", "received: " + searchResult.toString());
}
}
reader.endArray();
return result;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (responseBody != null) {
responseBody.close();
}
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
private SearchResult readNominatimResult(JsonReader reader) {
SearchResult result = new SearchResult();
try {
reader.beginObject();
while (reader.hasNext()) {
String jsonName = reader.nextName();
if (jsonName.equals("lat")) {
result.setLat(reader.nextDouble());
} else if (jsonName.equals("lon")) {
result.setLon(reader.nextDouble());
} else if (jsonName.equals("display_name")) {
result.display_name = reader.nextString();
}else {
reader.skipValue();
}
}
reader.endObject();
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private class QueryPhoton extends Query {
public QueryPhoton() {
super(null, null);
}
public QueryPhoton(String url, BoundingBox bbox) {
super(url, bbox);
}
@Override
protected ArrayList<SearchResult> doInBackground(String... params) {
String query = params[0];
Uri.Builder builder = Uri.parse(url)
.buildUpon()
.appendPath("api")
.appendQueryParameter("q", query);
if (bbox != null) {
double lat = bbox.getCenterLat();
double lon = (bbox.getLeft() + (bbox.getRight()-bbox.getLeft())/2)/1E7D;
builder.appendQueryParameter("lat", Double.toString(lat));
builder.appendQueryParameter("lon", Double.toString(lon));
}
builder.appendQueryParameter("limit", Integer.toString(10));
Uri uriBuilder = builder.build();
String urlString = uriBuilder.toString();
Log.d("Search", "urlString: " + urlString);
InputStream inputStream = null;
JsonReader reader = null;
ResponseBody responseBody = null;
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
Request request = new Request.Builder()
.url(urlString)
.build();
Call searchCall = App.getHttpClient().newCall(request);
Response searchCallResponse = searchCall.execute();
if (searchCallResponse.isSuccessful()) {
responseBody = searchCallResponse.body();
inputStream = responseBody.byteStream();
}
} else { //FIXME 2.2/API 8 support
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("User-Agent", App.userAgent);
inputStream = conn.getInputStream();
}
if (inputStream != null) {
BufferedReader rd = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
StringBuilder sb = new StringBuilder();
int cp;
while ((cp = rd.read()) != -1) {
sb.append((char) cp);
}
inputStream.close();
ArrayList<SearchResult> result = new ArrayList<SearchResult>();
FeatureCollection fc = FeatureCollection.fromJson(sb.toString());
for (Feature f:fc.getFeatures()) {
SearchResult searchResult = readPhotonResult(f);
if (searchResult != null) {
result.add(searchResult);
Log.d("Search", "received: " + searchResult.toString());
}
}
return result;
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (responseBody != null) {
responseBody.close();
}
if (reader != null) {
reader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
private SearchResult readPhotonResult(Feature f) {
SearchResult result = new SearchResult();
try {
JsonObject properties = f.getProperties();
Geometry g = f.getGeometry();
if (g instanceof Point) {
Point p = (Point)g;
Position pos = p.getCoordinates();
result.setLat(pos.getLatitude());
result.setLon(pos.getLongitude());
StringBuilder sb = new StringBuilder();
JsonElement name = properties.get("name");
if (name != null) {
sb.append(name.getAsString());
JsonElement osmKey = properties.get("osm_key");
JsonElement osmValue = properties.get("osm_value");
if (osmKey != null && osmValue != null) {
String key = osmKey.getAsString();
String value = osmValue.getAsString();
Map<String,String> tag = new HashMap<String,String>();
tag.put(key,value);
PresetItem preset = Preset.findBestMatch(App.getCurrentPresets(activity), tag, false);
if (preset != null) {
sb.append(" [" + preset.getTranslatedName() +"]");
} else {
sb.append(" [" + key + "=" + value +"]");
}
}
StringBuilder sb2 = new StringBuilder();
JsonElement street = properties.get("street");
if (street != null) {
sb2.append(street.getAsString());
JsonElement housenumber = properties.get("housenumber");
if (housenumber != null) {
sb2.append( " " + housenumber.getAsString());
}
}
JsonElement postcode = properties.get("postcode");
if (postcode != null) {
if (sb2.length() > 0) {
sb2.append(", ");
}
sb2.append(postcode.getAsString());
}
JsonElement state = properties.get("state");
if (state != null) {
if (sb2.length() > 0) {
sb2.append(", ");
}
sb2.append(state.getAsString());
}
JsonElement country = properties.get("country");
if (country != null) {
if (sb2.length() > 0) {
sb2.append(", ");
}
sb2.append(country.getAsString());
}
if (sb2.length() > 0) {
sb.append("\n");
sb.append(sb2);
}
}
result.display_name = sb.toString();
return result;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@SuppressLint("InflateParams")
private AppCompatDialog createSearchResultsDialog(final ArrayList<SearchResult> searchResults, int itemLayout) {
//
Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.search_results_title);
final LayoutInflater inflater = ThemeUtils.getLayoutInflater(activity);
ListView lv = (ListView) inflater.inflate(R.layout.search_results, null);
builder.setView(lv);
ArrayList<String> ar = new ArrayList<String>();
for (SearchResult sr:searchResults) {
ar.add(sr.display_name);
}
lv.setAdapter(new ArrayAdapter<String>(activity, itemLayout, ar));
lv.setSelection(0);
builder.setNegativeButton(R.string.cancel, null);
final AppCompatDialog dialog = builder.create();
lv.setOnItemClickListener( new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
// Log.d("Search","Result at pos " + position + " clicked");
callback.onItemFound(searchResults.get(position));
dialog.dismiss();
}
});
return dialog;
}
}