mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +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.assist.ImageScaleType;
|
||||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Hex;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
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 java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -28,6 +23,7 @@ import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.xml.transform.Transformer;
|
import javax.xml.transform.Transformer;
|
||||||
|
|
@ -38,7 +34,13 @@ import javax.xml.transform.TransformerFactoryConfigurationError;
|
||||||
import javax.xml.transform.dom.DOMSource;
|
import javax.xml.transform.dom.DOMSource;
|
||||||
import javax.xml.transform.stream.StreamResult;
|
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 {
|
public class Utils {
|
||||||
|
|
||||||
|
|
@ -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) {
|
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
|
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH); // Assuming MW always gives me UTC
|
||||||
try {
|
try {
|
||||||
|
|
@ -128,8 +146,9 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DisplayImageOptions.Builder defaultImageOptionsBuilder;
|
private static DisplayImageOptions.Builder defaultImageOptionsBuilder;
|
||||||
|
|
||||||
public static DisplayImageOptions.Builder getGenericDisplayOptions() {
|
public static DisplayImageOptions.Builder getGenericDisplayOptions() {
|
||||||
if(defaultImageOptionsBuilder == null) {
|
if (defaultImageOptionsBuilder == null) {
|
||||||
defaultImageOptionsBuilder = new DisplayImageOptions.Builder().cacheInMemory()
|
defaultImageOptionsBuilder = new DisplayImageOptions.Builder().cacheInMemory()
|
||||||
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
|
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
|
@ -159,7 +178,7 @@ public class Utils {
|
||||||
public static long countBytes(InputStream stream) throws IOException {
|
public static long countBytes(InputStream stream) throws IOException {
|
||||||
long count = 0;
|
long count = 0;
|
||||||
BufferedInputStream bis = new BufferedInputStream(stream);
|
BufferedInputStream bis = new BufferedInputStream(stream);
|
||||||
while(bis.read() != -1) {
|
while (bis.read() != -1) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
|
|
@ -168,11 +187,11 @@ public class Utils {
|
||||||
public static String makeThumbUrl(String imageUrl, String filename, int width) {
|
public static String makeThumbUrl(String imageUrl, String filename, int width) {
|
||||||
// Ugly Hack!
|
// Ugly Hack!
|
||||||
// Update: OH DEAR GOD WHAT A HORRIBLE HACK I AM SO SORRY
|
// 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";
|
return imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px--" + filename.replaceAll("File:", "").replaceAll(" ", "_") + ".jpg";
|
||||||
} else {
|
} else {
|
||||||
String thumbUrl = imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px-" + filename.replaceAll("File:", "").replaceAll(" ", "_");
|
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;
|
return thumbUrl;
|
||||||
} else {
|
} else {
|
||||||
return thumbUrl + ".png";
|
return thumbUrl + ".png";
|
||||||
|
|
@ -181,50 +200,49 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String capitalize(String string) {
|
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) {
|
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}}";
|
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}}";
|
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}}";
|
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}}";
|
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}}";
|
return "{{self|cc-zero}}";
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unrecognized license value");
|
throw new RuntimeException("Unrecognized license value");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int licenseNameFor(String license) {
|
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;
|
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;
|
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;
|
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;
|
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;
|
return R.string.license_name_cc0;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unrecognized license value");
|
throw new RuntimeException("Unrecognized license value");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String licenseUrlFor(String license) {
|
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/";
|
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/";
|
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/";
|
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/";
|
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/";
|
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unrecognized license value");
|
throw new RuntimeException("Unrecognized license value");
|
||||||
|
|
|
||||||
|
|
@ -32,12 +32,14 @@ public class NearbyActivity extends BaseActivity {
|
||||||
ft.add(R.id.container, fragment);
|
ft.add(R.id.container, fragment);
|
||||||
ft.commit();
|
ft.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu (Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.menu_nearby, menu);
|
inflater.inflate(R.menu.menu_nearby, menu);
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
// Handle item selection
|
// Handle item selection
|
||||||
|
|
@ -49,19 +51,23 @@ public class NearbyActivity extends BaseActivity {
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume(){
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void refreshView()
|
protected void refreshView() {
|
||||||
{
|
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.replace(R.id.container, new NearbyListFragment()).commit();
|
.replace(R.id.container, new NearbyListFragment()).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LocationServiceManager getLocationManager() {
|
||||||
|
return locationManager;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy(){
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
locationManager.unregisterLocationManager();
|
locationManager.unregisterLocationManager();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public class NearbyAdapter extends ArrayAdapter<Place> {
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
// Get the data item for this position
|
// Get the data item for this position
|
||||||
Place place = getItem(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
|
// Check if an existing view is being reused, otherwise inflate the view
|
||||||
if (convertView == null) {
|
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 static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v4.app.ListFragment;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -20,16 +22,18 @@ import butterknife.ButterKnife;
|
||||||
import butterknife.OnItemClick;
|
import butterknife.OnItemClick;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class NearbyListFragment extends ListFragment implements TaskListener {
|
public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
|
|
||||||
|
private static final int MAX_RESULTS = 1000;
|
||||||
private NearbyAsyncTask nearbyAsyncTask;
|
private NearbyAsyncTask nearbyAsyncTask;
|
||||||
private NearbyAdapter mAdapter;
|
|
||||||
|
|
||||||
@BindView(R.id.listview) ListView listview;
|
@BindView(R.id.listview) ListView listview;
|
||||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||||
|
|
@ -37,7 +41,6 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
private boolean isTaskRunning = false;
|
private boolean isTaskRunning = false;
|
||||||
|
|
||||||
private List<Place> places;
|
private List<Place> places;
|
||||||
private LatLng mLatestLocation;
|
|
||||||
|
|
||||||
private static final String TAG = NearbyListFragment.class.getName();
|
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) {
|
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
|
//Check that this is the first time view is created, to avoid double list when screen orientation changed
|
||||||
if(savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
mLatestLocation = new LocationServiceManager(getActivity()).getLatestLocation();
|
|
||||||
nearbyAsyncTask = new NearbyAsyncTask(this);
|
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||||
nearbyAsyncTask.execute();
|
nearbyAsyncTask.execute();
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
|
|
@ -116,7 +118,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|
||||||
// See http://stackoverflow.com/questions/18264408/incomplete-asynctask-crashes-my-app
|
// 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);
|
nearbyAsyncTask.cancel(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -125,7 +127,7 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
|
|
||||||
private final TaskListener listener;
|
private final TaskListener listener;
|
||||||
|
|
||||||
public NearbyAsyncTask (TaskListener listener) {
|
public NearbyAsyncTask(TaskListener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,7 +145,9 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Place> doInBackground(Void... params) {
|
protected List<Place> doInBackground(Void... params) {
|
||||||
places = loadAttractionsFromLocation(mLatestLocation);
|
places = loadAttractionsFromLocation(
|
||||||
|
((NearbyActivity)getActivity()).getLocationManager().getLatestLocation()
|
||||||
|
);
|
||||||
return places;
|
return places;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -151,18 +155,17 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
protected void onPostExecute(List<Place> result) {
|
protected void onPostExecute(List<Place> result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
|
|
||||||
if(isCancelled()) {
|
if (isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressBar.setVisibility(View.GONE);
|
progressBar.setVisibility(View.GONE);
|
||||||
|
NearbyAdapter adapter = new NearbyAdapter(getActivity(), places);
|
||||||
|
|
||||||
mAdapter = new NearbyAdapter(getActivity(), places);
|
listview.setAdapter(adapter);
|
||||||
|
|
||||||
listview.setAdapter(mAdapter);
|
|
||||||
|
|
||||||
listener.onTaskFinished(result);
|
listener.onTaskFinished(result);
|
||||||
mAdapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,32 +191,39 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Place> loadAttractionsFromLocation(final LatLng curLatLng) {
|
private List<Place> loadAttractionsFromLocation(LatLng curLatLng) {
|
||||||
|
Log.d(TAG, "Loading attractions near " + curLatLng);
|
||||||
List<Place> places = NearbyPlaces.get();
|
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) {
|
if (curLatLng != null) {
|
||||||
Log.d(TAG, "Sorting places by distance...");
|
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,
|
Collections.sort(places,
|
||||||
new Comparator<Place>() {
|
new Comparator<Place>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(Place lhs, Place rhs) {
|
public int compare(Place lhs, Place rhs) {
|
||||||
double lhsDistance = computeDistanceBetween(
|
double lhsDistance = distances.get(lhs);
|
||||||
lhs.location, curLatLng);
|
double rhsDistance = distances.get(rhs);
|
||||||
double rhsDistance = computeDistanceBetween(
|
|
||||||
rhs.location, curLatLng);
|
|
||||||
return (int) (lhsDistance - rhsDistance);
|
return (int) (lhsDistance - rhsDistance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (places.size() > 0) {
|
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
|
||||||
for (int i = 0; i < 100; i++) {
|
for (Place place: places) {
|
||||||
Place place = places.get(i);
|
String distance = formatDistanceBetween(curLatLng, place.location);
|
||||||
String distance = formatDistanceBetween(mLatestLocation, place.location);
|
|
||||||
place.setDistance(distance);
|
place.setDistance(distance);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return places;
|
return places;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,166 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
|
||||||
public class NearbyPlaces {
|
public class NearbyPlaces {
|
||||||
|
|
||||||
private static final String TAG = NearbyPlaces.class.getName();
|
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() {
|
private NearbyPlaces(){
|
||||||
if(places != null) {
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
return places;
|
return places;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
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 {
|
try {
|
||||||
places = new ArrayList<>();
|
places = new ArrayList<>();
|
||||||
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
StrictMode.ThreadPolicy policy
|
||||||
|
= new StrictMode.ThreadPolicy.Builder().permitAll().build();
|
||||||
StrictMode.setThreadPolicy(policy);
|
StrictMode.setThreadPolicy(policy);
|
||||||
|
|
||||||
URL file = new URL("https://tools.wmflabs.org/wiki-needs-pictures/data/data.csv");
|
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[] fields = line.split(",");
|
||||||
String name = fields[0];
|
String name = Utils.stripLocalizedString(fields[0]);
|
||||||
|
|
||||||
double latitude;
|
double latitude;
|
||||||
double longitude;
|
double longitude;
|
||||||
|
|
@ -73,10 +208,22 @@ public class NearbyPlaces {
|
||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
Log.d(TAG, e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return places;
|
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
|
@Override
|
||||||
public void bindModel(Context context, Place place) {
|
public void bindModel(Context context, Place place) {
|
||||||
String quotelessName = place.name.replaceAll("^\"|\"$", "");
|
|
||||||
|
|
||||||
// Populate the data into the template view using the data object
|
// Populate the data into the template view using the data object
|
||||||
tvName.setText(quotelessName);
|
tvName.setText(place.name);
|
||||||
tvDesc.setText(place.description);
|
tvDesc.setText(place.description);
|
||||||
distance.setText(place.distance);
|
distance.setText(place.distance);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,16 @@ import fr.free.nrw.commons.location.LatLng;
|
||||||
|
|
||||||
public class Place {
|
public class Place {
|
||||||
|
|
||||||
public String name;
|
public final String name;
|
||||||
public String description;
|
public final String description;
|
||||||
public String longDescription;
|
public final String longDescription;
|
||||||
public Uri secondaryImageUrl;
|
public final Uri secondaryImageUrl;
|
||||||
public LatLng location;
|
public final LatLng location;
|
||||||
|
|
||||||
public Bitmap image;
|
public Bitmap image;
|
||||||
public Bitmap secondaryImage;
|
public Bitmap secondaryImage;
|
||||||
public String distance;
|
public String distance;
|
||||||
|
|
||||||
public Place() {}
|
|
||||||
|
|
||||||
public Place(String name, String description, String longDescription,
|
public Place(String name, String description, String longDescription,
|
||||||
Uri secondaryImageUrl, LatLng location) {
|
Uri secondaryImageUrl, LatLng location) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
@ -32,4 +30,24 @@ public class Place {
|
||||||
this.distance = distance;
|
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";
|
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) {
|
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) {
|
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_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="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="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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,11 @@
|
||||||
android:key="theme"
|
android:key="theme"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:key="useWikidata"
|
||||||
|
android:title="@string/use_wikidata"
|
||||||
|
android:defaultValue="true"
|
||||||
|
android:summary="@string/use_wikidata_summary"
|
||||||
|
/>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
|
||||||
public class UtilsTest {
|
public class UtilsTest {
|
||||||
|
|
@ -11,7 +11,7 @@ public class UtilsTest {
|
||||||
assertThat(Utils.fixExtension("SampleFile.jpeg", "jpeg"), is("SampleFile.jpg"));
|
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"));
|
assertThat(Utils.fixExtension("SampleFile.JPEG", null), is("SampleFile.jpg"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,4 +46,12 @@ public class UtilsTest {
|
||||||
@Test public void fixExtensionJpegNotExtension() {
|
@Test public void fixExtensionJpegNotExtension() {
|
||||||
assertThat(Utils.fixExtension("SAMPLE.jpeg.SAMPLE", "jpg"), is("SAMPLE.jpeg.SAMPLE.jpg"));
|
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