From 8731987385fd61da9797e6c222df37babe488088 Mon Sep 17 00:00:00 2001 From: Yusuke Matsubara Date: Thu, 13 Apr 2017 18:40:36 +0900 Subject: [PATCH] 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 --- .../main/java/fr/free/nrw/commons/Utils.java | 76 ++++---- .../nrw/commons/nearby/NearbyActivity.java | 16 +- .../nrw/commons/nearby/NearbyAdapter.java | 2 +- .../commons/nearby/NearbyListFragment.java | 62 ++++--- .../free/nrw/commons/nearby/NearbyPlaces.java | 163 +++++++++++++++++- .../nrw/commons/nearby/NearbyViewHolder.java | 4 +- .../fr/free/nrw/commons/nearby/Place.java | 32 +++- .../free/nrw/commons/utils/LengthUtils.java | 8 +- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 7 + .../java/fr/free/nrw/commons/UtilsTest.java | 12 +- 11 files changed, 302 insertions(+), 82 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/Utils.java b/app/src/main/java/fr/free/nrw/commons/Utils.java index 3f8ebd22b..0433958fa 100644 --- a/app/src/main/java/fr/free/nrw/commons/Utils.java +++ b/app/src/main/java/fr/free/nrw/commons/Utils.java @@ -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"); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java index 1440917c0..380d256fd 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyActivity.java @@ -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(); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapter.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapter.java index ced4dab81..c642e9602 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyAdapter.java @@ -33,7 +33,7 @@ public class NearbyAdapter extends ArrayAdapter { 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) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java index 57002118c..47d3dada1 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyListFragment.java @@ -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 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 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 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 loadAttractionsFromLocation(final LatLng curLatLng) { - - List places = NearbyPlaces.get(); + private List loadAttractionsFromLocation(LatLng curLatLng) { + Log.d(TAG, "Loading attractions near " + curLatLng); + if (curLatLng == null) { + return Collections.emptyList(); + } + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); + List 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 distances = new HashMap<>(); + for (Place place: places) { + distances.put(place, computeDistanceBetween(place.location, curLatLng)); + } Collections.sort(places, new Comparator() { @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; } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java index 7d4d5efaf..ec3d97c94 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java @@ -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 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 places; - public static List get() { - if(places != null) { - return places; + private NearbyPlaces(){ + } + + List getFromWikidataQuery(LatLng curLatLng, String lang) { + List 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 getFromWikidataQuery(LatLng cur, String lang, double radius) + throws IOException { + List 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 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; + } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyViewHolder.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyViewHolder.java index 1203b9cb0..19e008068 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyViewHolder.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyViewHolder.java @@ -23,10 +23,8 @@ public class NearbyViewHolder implements ViewHolder { @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); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java index a4db5e597..222a992cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/Place.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/Place.java @@ -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); + } + } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java index 191da6983..3a8cc6aa5 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/LengthUtils.java @@ -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) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 16c4c0751..67ddd2396 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -165,4 +165,6 @@ Tap this message (or hit back) to skip this step. Become a Beta Tester Opt-in to our beta channel on Google Play and get early access to new features and bug fixes https://play.google.com/apps/testing/fr.free.nrw.commons + Use Wikidata + (Warning: disabling this may cause large mobile data consumption) diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3c697c082..847f01e1c 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -31,4 +31,11 @@ android:key="theme" /> + + \ No newline at end of file diff --git a/app/src/test/java/fr/free/nrw/commons/UtilsTest.java b/app/src/test/java/fr/free/nrw/commons/UtilsTest.java index 6e78ab928..b684c7bbf 100644 --- a/app/src/test/java/fr/free/nrw/commons/UtilsTest.java +++ b/app/src/test/java/fr/free/nrw/commons/UtilsTest.java @@ -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("こんにちは")); + } }