mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Generate Nearby list using Wikidata Query
Plus, * remove duplicate location service manager * add option to switch data sources of Nearby * faster sorting of places * minor coding style improvents
This commit is contained in:
parent
eb99350caf
commit
8731987385
11 changed files with 302 additions and 82 deletions
|
|
@ -8,12 +8,7 @@ import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
|||
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.codec.net.URLCodec;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
|
|
@ -28,6 +23,7 @@ import java.text.SimpleDateFormat;
|
|||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.transform.Transformer;
|
||||
|
|
@ -38,7 +34,13 @@ import javax.xml.transform.TransformerFactoryConfigurationError;
|
|||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.codec.net.URLCodec;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
|
||||
public class Utils {
|
||||
|
||||
|
|
@ -72,7 +74,7 @@ public class Utils {
|
|||
} catch (IOException e) {
|
||||
Log.e(TAG, "IO Exception", e);
|
||||
return "";
|
||||
} finally {
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException e) {
|
||||
|
|
@ -81,6 +83,22 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips localization symbols from a string.
|
||||
* Removes the suffix after "@" and quotes.
|
||||
*
|
||||
* @param s string possibly containing localization symbols
|
||||
* @return stripped string
|
||||
*/
|
||||
public static String stripLocalizedString(String s) {
|
||||
Matcher matcher = Pattern.compile("\\\"(.*)\\\"(@\\w+)?").matcher(s);
|
||||
if (matcher.find()) {
|
||||
return matcher.group(1);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public static Date parseMWDate(String mwDate) {
|
||||
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
|
||||
try {
|
||||
|
|
@ -128,8 +146,9 @@ public class Utils {
|
|||
}
|
||||
|
||||
private static DisplayImageOptions.Builder defaultImageOptionsBuilder;
|
||||
|
||||
public static DisplayImageOptions.Builder getGenericDisplayOptions() {
|
||||
if(defaultImageOptionsBuilder == null) {
|
||||
if (defaultImageOptionsBuilder == null) {
|
||||
defaultImageOptionsBuilder = new DisplayImageOptions.Builder().cacheInMemory()
|
||||
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
|
|
@ -159,7 +178,7 @@ public class Utils {
|
|||
public static long countBytes(InputStream stream) throws IOException {
|
||||
long count = 0;
|
||||
BufferedInputStream bis = new BufferedInputStream(stream);
|
||||
while(bis.read() != -1) {
|
||||
while (bis.read() != -1) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
|
|
@ -168,11 +187,11 @@ public class Utils {
|
|||
public static String makeThumbUrl(String imageUrl, String filename, int width) {
|
||||
// Ugly Hack!
|
||||
// Update: OH DEAR GOD WHAT A HORRIBLE HACK I AM SO SORRY
|
||||
if(imageUrl.endsWith("webm")) {
|
||||
if (imageUrl.endsWith("webm")) {
|
||||
return imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px--" + filename.replaceAll("File:", "").replaceAll(" ", "_") + ".jpg";
|
||||
} else {
|
||||
String thumbUrl = imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px-" + filename.replaceAll("File:", "").replaceAll(" ", "_");
|
||||
if(thumbUrl.endsWith("jpg") || thumbUrl.endsWith("png") || thumbUrl.endsWith("jpeg")) {
|
||||
if (thumbUrl.endsWith("jpg") || thumbUrl.endsWith("png") || thumbUrl.endsWith("jpeg")) {
|
||||
return thumbUrl;
|
||||
} else {
|
||||
return thumbUrl + ".png";
|
||||
|
|
@ -181,50 +200,49 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static String capitalize(String string) {
|
||||
return string.substring(0,1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
||||
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
||||
}
|
||||
|
||||
public static String licenseTemplateFor(String license) {
|
||||
if(license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
return "{{self|cc-by-3.0}}";
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
return "{{self|cc-by-4.0}}";
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
return "{{self|cc-by-sa-3.0}}";
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
return "{{self|cc-by-sa-4.0}}";
|
||||
} else if(license.equals(Prefs.Licenses.CC0)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
||||
return "{{self|cc-zero}}";
|
||||
}
|
||||
throw new RuntimeException("Unrecognized license value");
|
||||
}
|
||||
|
||||
public static int licenseNameFor(String license) {
|
||||
if(license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
return R.string.license_name_cc_by;
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
return R.string.license_name_cc_by_four;
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
return R.string.license_name_cc_by_sa;
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
return R.string.license_name_cc_by_sa_four;
|
||||
} else if(license.equals(Prefs.Licenses.CC0)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
||||
return R.string.license_name_cc0;
|
||||
}
|
||||
throw new RuntimeException("Unrecognized license value");
|
||||
}
|
||||
|
||||
public static String licenseUrlFor(String license) {
|
||||
if(license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
return "https://creativecommons.org/licenses/by/3.0/";
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
return "https://creativecommons.org/licenses/by/4.0/";
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
return "https://creativecommons.org/licenses/by-sa/3.0/";
|
||||
} else if(license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
return "https://creativecommons.org/licenses/by-sa/4.0/";
|
||||
}
|
||||
else if(license.equals(Prefs.Licenses.CC0)) {
|
||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
||||
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||
}
|
||||
throw new RuntimeException("Unrecognized license value");
|
||||
|
|
|
|||
|
|
@ -32,12 +32,14 @@ public class NearbyActivity extends BaseActivity {
|
|||
ft.add(R.id.container, fragment);
|
||||
ft.commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu (Menu menu) {
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.menu_nearby, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle item selection
|
||||
|
|
@ -49,19 +51,23 @@ public class NearbyActivity extends BaseActivity {
|
|||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume(){
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
protected void refreshView()
|
||||
{
|
||||
protected void refreshView() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.container, new NearbyListFragment()).commit();
|
||||
}
|
||||
|
||||
public LocationServiceManager getLocationManager() {
|
||||
return locationManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy(){
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
locationManager.unregisterLocationManager();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class NearbyAdapter extends ArrayAdapter<Place> {
|
|||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// Get the data item for this position
|
||||
Place place = getItem(position);
|
||||
Log.d("NearbyAdapter", "Place " + place.name);
|
||||
Log.v("NearbyAdapter", "" + place);
|
||||
|
||||
// Check if an existing view is being reused, otherwise inflate the view
|
||||
if (convertView == null) {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
|||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -20,16 +22,18 @@ import butterknife.ButterKnife;
|
|||
import butterknife.OnItemClick;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||
|
||||
private static final int MAX_RESULTS = 1000;
|
||||
private NearbyAsyncTask nearbyAsyncTask;
|
||||
private NearbyAdapter mAdapter;
|
||||
|
||||
@BindView(R.id.listview) ListView listview;
|
||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||
|
|
@ -37,7 +41,6 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
private boolean isTaskRunning = false;
|
||||
|
||||
private List<Place> places;
|
||||
private LatLng mLatestLocation;
|
||||
|
||||
private static final String TAG = NearbyListFragment.class.getName();
|
||||
|
||||
|
|
@ -64,8 +67,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
|
||||
//Check that this is the first time view is created, to avoid double list when screen orientation changed
|
||||
if(savedInstanceState == null) {
|
||||
mLatestLocation = new LocationServiceManager(getActivity()).getLatestLocation();
|
||||
if (savedInstanceState == null) {
|
||||
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||
nearbyAsyncTask.execute();
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
|
@ -116,7 +118,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
super.onDestroy();
|
||||
|
||||
// See http://stackoverflow.com/questions/18264408/incomplete-asynctask-crashes-my-app
|
||||
if(nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
|
||||
if (nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
|
||||
nearbyAsyncTask.cancel(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +127,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
|
||||
private final TaskListener listener;
|
||||
|
||||
public NearbyAsyncTask (TaskListener listener) {
|
||||
public NearbyAsyncTask(TaskListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +145,9 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
|
||||
@Override
|
||||
protected List<Place> doInBackground(Void... params) {
|
||||
places = loadAttractionsFromLocation(mLatestLocation);
|
||||
places = loadAttractionsFromLocation(
|
||||
((NearbyActivity)getActivity()).getLocationManager().getLatestLocation()
|
||||
);
|
||||
return places;
|
||||
}
|
||||
|
||||
|
|
@ -151,18 +155,17 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
protected void onPostExecute(List<Place> result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
if(isCancelled()) {
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
NearbyAdapter adapter = new NearbyAdapter(getActivity(), places);
|
||||
|
||||
mAdapter = new NearbyAdapter(getActivity(), places);
|
||||
|
||||
listview.setAdapter(mAdapter);
|
||||
listview.setAdapter(adapter);
|
||||
|
||||
listener.onTaskFinished(result);
|
||||
mAdapter.notifyDataSetChanged();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -188,31 +191,38 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
}
|
||||
}
|
||||
|
||||
private List<Place> loadAttractionsFromLocation(final LatLng curLatLng) {
|
||||
|
||||
List<Place> places = NearbyPlaces.get();
|
||||
private List<Place> loadAttractionsFromLocation(LatLng curLatLng) {
|
||||
Log.d(TAG, "Loading attractions near " + curLatLng);
|
||||
if (curLatLng == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
List<Place> places = prefs.getBoolean("useWikidata", true)
|
||||
? NearbyPlaces.getInstance().getFromWikidataQuery(
|
||||
curLatLng, Locale.getDefault().getLanguage())
|
||||
: NearbyPlaces.getInstance().getFromWikiNeedsPictures();
|
||||
if (curLatLng != null) {
|
||||
Log.d(TAG, "Sorting places by distance...");
|
||||
final Map<Place, Double> distances = new HashMap<>();
|
||||
for (Place place: places) {
|
||||
distances.put(place, computeDistanceBetween(place.location, curLatLng));
|
||||
}
|
||||
Collections.sort(places,
|
||||
new Comparator<Place>() {
|
||||
@Override
|
||||
public int compare(Place lhs, Place rhs) {
|
||||
double lhsDistance = computeDistanceBetween(
|
||||
lhs.location, curLatLng);
|
||||
double rhsDistance = computeDistanceBetween(
|
||||
rhs.location, curLatLng);
|
||||
double lhsDistance = distances.get(lhs);
|
||||
double rhsDistance = distances.get(rhs);
|
||||
return (int) (lhsDistance - rhsDistance);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (places.size() > 0) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Place place = places.get(i);
|
||||
String distance = formatDistanceBetween(mLatestLocation, place.location);
|
||||
place.setDistance(distance);
|
||||
}
|
||||
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
|
||||
for (Place place: places) {
|
||||
String distance = formatDistanceBetween(curLatLng, place.location);
|
||||
place.setDistance(distance);
|
||||
}
|
||||
return places;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,166 @@
|
|||
package fr.free.nrw.commons.nearby;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.StrictMode;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
public class NearbyPlaces {
|
||||
|
||||
private static final String TAG = NearbyPlaces.class.getName();
|
||||
static List<Place> places = null;
|
||||
private static final int MIN_RESULTS = 40;
|
||||
private static final double INITIAL_RADIUS = 1.0;
|
||||
private static final double MAX_RADIUS = 300.0;
|
||||
private static final double RADIUS_MULTIPLIER = 1.618;
|
||||
private static final String WIKIDATA_QUERY_URL = "https://query.wikidata.org/sparql?query=${QUERY}";
|
||||
private static final String WIKIDATA_QUERY_TEMPLATE = "SELECT\n" +
|
||||
" (SAMPLE(?location) as ?location)\n" +
|
||||
" ?item\n" +
|
||||
" (SAMPLE(COALESCE(?item_label_preferred_language, ?item_label_any_language)) as ?label)\n" +
|
||||
" (SAMPLE(?classId) as ?class)\n" +
|
||||
" (SAMPLE(COALESCE(?class_label_preferred_language, ?class_label_any_language, \"?\")) as ?class_label)\n" +
|
||||
" (SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)\n" +
|
||||
" (SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji)\n" +
|
||||
"WHERE {\n" +
|
||||
" # Around given location...\n" +
|
||||
" SERVICE wikibase:around {\n" +
|
||||
" ?item wdt:P625 ?location.\n" +
|
||||
" bd:serviceParam wikibase:center \"Point(${LONG} ${LAT})\"^^geo:wktLiteral. \n" +
|
||||
" bd:serviceParam wikibase:radius \"${RADIUS}\" . # Radius in kilometers.\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" # ... and without an image.\n" +
|
||||
" MINUS {?item wdt:P18 []}\n" +
|
||||
" \n" +
|
||||
" # Get the label in the preferred language of the user, or any other language if no label is available in that language.\n" +
|
||||
" OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = \"${LANG}\")}\n" +
|
||||
" OPTIONAL {?item rdfs:label ?item_label_any_language}\n" +
|
||||
" \n" +
|
||||
" # Get the class label in the preferred language of the user, or any other language if no label is available in that language.\n" +
|
||||
" OPTIONAL {\n" +
|
||||
" ?item p:P31/ps:P31 ?classId.\n" +
|
||||
" OPTIONAL {?classId rdfs:label ?class_label_preferred_language. FILTER (lang(?class_label_preferred_language) = \"${LANG}\")}\n" +
|
||||
" OPTIONAL {?classId rdfs:label ?class_label_any_language}\n" +
|
||||
"\n" +
|
||||
" # Get icon\n" +
|
||||
" OPTIONAL { ?classId wdt:P2910 ?icon0. }\n" +
|
||||
" OPTIONAL { ?classId wdt:P279*/wdt:P2910 ?icon1. }\n" +
|
||||
" # Get emoji\n" +
|
||||
" OPTIONAL { ?classId wdt:P487 ?emoji0. }\n" +
|
||||
" OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. }\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"GROUP BY ?item\n";
|
||||
private static NearbyPlaces singleton;
|
||||
private double radius = INITIAL_RADIUS;
|
||||
private List<Place> places;
|
||||
|
||||
public static List<Place> get() {
|
||||
if(places != null) {
|
||||
return places;
|
||||
private NearbyPlaces(){
|
||||
}
|
||||
|
||||
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) {
|
||||
List<Place> places = Collections.emptyList();
|
||||
|
||||
try {
|
||||
// increase the radius gradually to find a satisfactory number of nearby places
|
||||
while (radius < MAX_RADIUS) {
|
||||
places = getFromWikidataQuery(curLatLng, lang, radius);
|
||||
Log.d(TAG, places.size() + " results at radius: " + radius);
|
||||
if (places.size() >= MIN_RESULTS) {
|
||||
break;
|
||||
} else {
|
||||
radius *= RADIUS_MULTIPLIER;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "" + e.toString());
|
||||
// errors tend to be caused by too many results (and time out)
|
||||
// try a small radius next time
|
||||
Log.d(TAG, "back to initial radius: " + radius);
|
||||
radius = INITIAL_RADIUS;
|
||||
}
|
||||
else {
|
||||
return places;
|
||||
}
|
||||
|
||||
private List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius)
|
||||
throws IOException {
|
||||
List<Place> places = new ArrayList<>();
|
||||
String query = WIKIDATA_QUERY_TEMPLATE.replace("${RADIUS}", "" + radius)
|
||||
.replace("${LAT}", "" + String.format("%.3f", cur.latitude))
|
||||
.replace("${LONG}", "" + String.format("%.3f", cur.longitude))
|
||||
.replace("${LANG}", "" + lang);
|
||||
query = URLEncoder.encode(query, "utf-8").replace("+", "%20");
|
||||
String url = WIKIDATA_QUERY_URL.replace("${QUERY}", query);
|
||||
Log.d(TAG, url.toString());
|
||||
URLConnection conn = new URL(url).openConnection();
|
||||
conn.setRequestProperty("Accept", "text/tab-separated-values");
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
|
||||
String line;
|
||||
Log.d(TAG, "Reading from query result...");
|
||||
while ((line = in.readLine()) != null) {
|
||||
Log.v(TAG, line);
|
||||
line = line + "\n"; // to pad columns and make fields a fixed size
|
||||
if (!line.startsWith("\"Point")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String[] fields = line.split("\t");
|
||||
String point = fields[0];
|
||||
String name = Utils.stripLocalizedString(fields[2]);
|
||||
String type = Utils.stripLocalizedString(fields[4]);
|
||||
String icon = fields[5];
|
||||
|
||||
double latitude = 0;
|
||||
double longitude = 0;
|
||||
Matcher matcher =
|
||||
Pattern.compile("Point\\(([^ ]+) ([^ ]+)\\)").matcher(point);
|
||||
if (!matcher.find()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
longitude = Double.parseDouble(matcher.group(1));
|
||||
latitude = Double.parseDouble(matcher.group(2));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new RuntimeException("LatLng parse error: " + point);
|
||||
}
|
||||
|
||||
places.add(new Place(
|
||||
name,
|
||||
type, // list
|
||||
type, // details
|
||||
Uri.parse(icon),
|
||||
new LatLng(latitude, longitude)
|
||||
));
|
||||
}
|
||||
in.close();
|
||||
|
||||
return places;
|
||||
}
|
||||
|
||||
List<Place> getFromWikiNeedsPictures() {
|
||||
if (places != null) {
|
||||
return places;
|
||||
} else {
|
||||
try {
|
||||
places = new ArrayList<>();
|
||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||
StrictMode.ThreadPolicy policy
|
||||
= new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||
StrictMode.setThreadPolicy(policy);
|
||||
|
||||
URL file = new URL("https://tools.wmflabs.org/wiki-needs-pictures/data/data.csv");
|
||||
|
|
@ -45,7 +180,7 @@ public class NearbyPlaces {
|
|||
}
|
||||
|
||||
String[] fields = line.split(",");
|
||||
String name = fields[0];
|
||||
String name = Utils.stripLocalizedString(fields[0]);
|
||||
|
||||
double latitude;
|
||||
double longitude;
|
||||
|
|
@ -73,10 +208,22 @@ public class NearbyPlaces {
|
|||
in.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.d(TAG, e.toString());
|
||||
}
|
||||
}
|
||||
return places;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the singleton instance of this class.
|
||||
* The instance is created upon the first invocation of this method, and then reused.
|
||||
*
|
||||
* @return The singleton instance
|
||||
*/
|
||||
public static synchronized NearbyPlaces getInstance() {
|
||||
if (singleton == null) {
|
||||
singleton = new NearbyPlaces();
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,8 @@ public class NearbyViewHolder implements ViewHolder<Place> {
|
|||
|
||||
@Override
|
||||
public void bindModel(Context context, Place place) {
|
||||
String quotelessName = place.name.replaceAll("^\"|\"$", "");
|
||||
|
||||
// Populate the data into the template view using the data object
|
||||
tvName.setText(quotelessName);
|
||||
tvName.setText(place.name);
|
||||
tvDesc.setText(place.description);
|
||||
distance.setText(place.distance);
|
||||
|
||||
|
|
|
|||
|
|
@ -7,18 +7,16 @@ import fr.free.nrw.commons.location.LatLng;
|
|||
|
||||
public class Place {
|
||||
|
||||
public String name;
|
||||
public String description;
|
||||
public String longDescription;
|
||||
public Uri secondaryImageUrl;
|
||||
public LatLng location;
|
||||
public final String name;
|
||||
public final String description;
|
||||
public final String longDescription;
|
||||
public final Uri secondaryImageUrl;
|
||||
public final LatLng location;
|
||||
|
||||
public Bitmap image;
|
||||
public Bitmap secondaryImage;
|
||||
public String distance;
|
||||
|
||||
public Place() {}
|
||||
|
||||
public Place(String name, String description, String longDescription,
|
||||
Uri secondaryImageUrl, LatLng location) {
|
||||
this.name = name;
|
||||
|
|
@ -32,4 +30,24 @@ public class Place {
|
|||
this.distance = distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Place) {
|
||||
Place that = (Place)o;
|
||||
return this.name.equals(that.name) && this.location.equals(that.location);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.name.hashCode() * 31 + this.location.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Place(%s@%s)", name, location);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,14 @@ public class LengthUtils {
|
|||
return numberFormat.format(distance) + "m";
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the distance between two points.
|
||||
* @param from one of the two end points
|
||||
* @param to one of the two end points
|
||||
* @return distance between the points in meter
|
||||
*/
|
||||
public static double computeDistanceBetween(LatLng from, LatLng to) {
|
||||
return computeAngleBetween(from, to) * 6371009.0D;
|
||||
return computeAngleBetween(from, to) * 6371009.0D; // Earth's radius in meter
|
||||
}
|
||||
|
||||
private static double computeAngleBetween(LatLng from, LatLng to) {
|
||||
|
|
|
|||
|
|
@ -165,4 +165,6 @@ Tap this message (or hit back) to skip this step.</string>
|
|||
<string name="become_a_tester_title">Become a Beta Tester</string>
|
||||
<string name="become_a_tester_description">Opt-in to our beta channel on Google Play and get early access to new features and bug fixes</string>
|
||||
<string name="beta_opt_in_link">https://play.google.com/apps/testing/fr.free.nrw.commons</string>
|
||||
<string name="use_wikidata">Use Wikidata</string>
|
||||
<string name="use_wikidata_summary">(Warning: disabling this may cause large mobile data consumption)</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -31,4 +31,11 @@
|
|||
android:key="theme"
|
||||
/>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="useWikidata"
|
||||
android:title="@string/use_wikidata"
|
||||
android:defaultValue="true"
|
||||
android:summary="@string/use_wikidata_summary"
|
||||
/>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package fr.free.nrw.commons;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
public class UtilsTest {
|
||||
|
|
@ -11,7 +11,7 @@ public class UtilsTest {
|
|||
assertThat(Utils.fixExtension("SampleFile.jpeg", "jpeg"), is("SampleFile.jpg"));
|
||||
}
|
||||
|
||||
@Test public void fixExtensionJPEGToJpg() {
|
||||
@Test public void fixExtensionJpegToJpg() {
|
||||
assertThat(Utils.fixExtension("SampleFile.JPEG", null), is("SampleFile.jpg"));
|
||||
}
|
||||
|
||||
|
|
@ -46,4 +46,12 @@ public class UtilsTest {
|
|||
@Test public void fixExtensionJpegNotExtension() {
|
||||
assertThat(Utils.fixExtension("SAMPLE.jpeg.SAMPLE", "jpg"), is("SAMPLE.jpeg.SAMPLE.jpg"));
|
||||
}
|
||||
|
||||
@Test public void stripLocalizedStringPass() {
|
||||
assertThat(Utils.stripLocalizedString("Hello"), is("Hello"));
|
||||
}
|
||||
|
||||
@Test public void stripLocalizedStringJa() {
|
||||
assertThat(Utils.stripLocalizedString("\"こんにちは\"@ja"), is("こんにちは"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue