Made Nearby show all pins that could be presented on the screen, rather than a circle (#5553)

* Changed nearby from circle to rectangle

* Fixed bug

* Removed MAPBOX Token

* Fixed minor issues

* Fixed minor issues

* Changed query files

* Changed monuments query file

* Fixed Unit Tests

* Fixed failing tests

* Fixed errors due to merging
This commit is contained in:
Kanahia 2024-03-24 19:54:39 +05:30 committed by GitHub
parent f404ac9b47
commit 724e4db0fd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 576 additions and 219 deletions

View file

@ -83,19 +83,27 @@ import io.reactivex.schedulers.Schedulers;
import timber.log.Timber;
public class ContributionsFragment
extends CommonsDaggerSupportFragment
implements
OnBackStackChangedListener,
LocationUpdateListener,
extends CommonsDaggerSupportFragment
implements
OnBackStackChangedListener,
LocationUpdateListener,
MediaDetailProvider,
SensorEventListener,
ICampaignsView, ContributionsContract.View, Callback{
@Inject @Named("default_preferences") JsonKvStore store;
@Inject NearbyController nearbyController;
@Inject OkHttpJsonApiClient okHttpJsonApiClient;
@Inject CampaignsPresenter presenter;
@Inject LocationServiceManager locationManager;
@Inject NotificationController notificationController;
ICampaignsView, ContributionsContract.View, Callback {
@Inject
@Named("default_preferences")
JsonKvStore store;
@Inject
NearbyController nearbyController;
@Inject
OkHttpJsonApiClient okHttpJsonApiClient;
@Inject
CampaignsPresenter presenter;
@Inject
LocationServiceManager locationManager;
@Inject
NotificationController notificationController;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@ -129,29 +137,31 @@ public class ContributionsFragment
private SensorManager mSensorManager;
private Sensor mLight;
private float direction;
private ActivityResultLauncher<String[]> nearbyLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
boolean areAllGranted = true;
for (final boolean b : result.values()) {
areAllGranted = areAllGranted && b;
}
private ActivityResultLauncher<String[]> nearbyLocationPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
boolean areAllGranted = true;
for (final boolean b : result.values()) {
areAllGranted = areAllGranted && b;
}
if (areAllGranted) {
onLocationPermissionGranted();
} else {
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
&& store.getBoolean("displayLocationPermissionForCardView", true)
&& !store.getBoolean("doNotAskForLocationPermission", false)
&& (((MainActivity) getActivity()).activeFragment == ActiveFragment.CONTRIBUTIONS)) {
binding.cardViewNearby.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
showNearbyCardPermissionRationale();
if (areAllGranted) {
onLocationPermissionGranted();
} else {
displayYouWontSeeNearbyMessage();
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
&& store.getBoolean("displayLocationPermissionForCardView", true)
&& !store.getBoolean("doNotAskForLocationPermission", false)
&& (((MainActivity) getActivity()).activeFragment == ActiveFragment.CONTRIBUTIONS)) {
binding.cardViewNearby.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
showNearbyCardPermissionRationale();
} else {
displayYouWontSeeNearbyMessage();
}
}
}
}
});
});
@NonNull
public static ContributionsFragment newInstance() {
@ -175,7 +185,8 @@ public class ContributionsFragment
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = FragmentContributionsBinding.inflate(inflater, container, false);
@ -192,6 +203,7 @@ public class ContributionsFragment
}
});
if (savedInstanceState != null) {
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
@ -206,9 +218,9 @@ public class ContributionsFragment
}else {
upDateUploadCount();
}
if(shouldShowMediaDetailsFragment){
if (shouldShowMediaDetailsFragment) {
showMediaDetailPagerFragment();
}else{
} else {
if (mediaDetailPagerFragment != null) {
removeFragment(mediaDetailPagerFragment);
}
@ -234,10 +246,13 @@ public class ContributionsFragment
}
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
public void onCreateOptionsMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
// Removing contributions menu items for ProfileActivity
if (getActivity() instanceof ProfileActivity) { return; }
if (getActivity() instanceof ProfileActivity) {
return;
}
inflater.inflate(R.menu.contribution_activity_notification_menu, menu);
@ -259,7 +274,7 @@ public class ContributionsFragment
throwable -> Timber.e(throwable, "Error occurred while loading notifications")));
}
public void scrollToTop( ){
public void scrollToTop() {
if (contributionsListFragment != null) {
contributionsListFragment.scrollToTop();
}
@ -329,13 +344,15 @@ public class ContributionsFragment
binding.cardViewNearby.setVisibility(View.GONE);
}
}
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG, mediaDetailPagerFragment);
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG,
mediaDetailPagerFragment);
}
private void showMediaDetailPagerFragment() {
// hide nearby card view on media detail is visible
setupViewForMediaDetails();
showFragment(mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG, contributionsListFragment);
showFragment(mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG,
contributionsListFragment);
}
private void setupViewForMediaDetails() {
@ -363,7 +380,8 @@ public class ContributionsFragment
showContributionsListFragment();
}
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG, mediaDetailPagerFragment);
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG,
mediaDetailPagerFragment);
}
/**
@ -386,7 +404,7 @@ public class ContributionsFragment
transaction.addToBackStack(tag);
transaction.commit();
getChildFragmentManager().executePendingTransactions();
}else if (!fragment.isAdded() && otherFragment != null ) {
} else if (!fragment.isAdded() && otherFragment != null) {
transaction.hide(otherFragment);
transaction.add(R.id.root_frame, fragment, tag);
transaction.addToBackStack(tag);
@ -411,21 +429,21 @@ public class ContributionsFragment
@SuppressWarnings("ConstantConditions")
private void setUploadCount() {
compositeDisposable.add(okHttpJsonApiClient
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::displayUploadCount,
t -> Timber.e(t, "Fetching upload count failed")
));
.getUploadCount(((MainActivity) getActivity()).sessionManager.getCurrentAccount().name)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::displayUploadCount,
t -> Timber.e(t, "Fetching upload count failed")
));
}
private void displayUploadCount(Integer uploadCount) {
if (getActivity().isFinishing()
|| getResources() == null) {
|| getResources() == null) {
return;
}
((MainActivity)getActivity()).setNumOfUploads(uploadCount);
((MainActivity) getActivity()).setNumOfUploads(uploadCount);
}
@ -460,7 +478,7 @@ public class ContributionsFragment
if (mediaDetailPagerFragment == null && !isUserProfile) {
if (store.getBoolean("displayNearbyCardView", true)) {
checkPermissionsAndShowNearbyCardView();
// Calling nearby card to keep showing it even when user clicks on it and comes back
try {
updateClosestNearbyCardViewInfo();
@ -489,9 +507,9 @@ public class ContributionsFragment
if (PermissionUtils.hasPermission(getActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION})) {
onLocationPermissionGranted();
} else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
&& store.getBoolean("displayLocationPermissionForCardView", true)
&& !store.getBoolean("doNotAskForLocationPermission", false)
&& (((MainActivity) getActivity()).activeFragment == ActiveFragment.CONTRIBUTIONS)) {
&& store.getBoolean("displayLocationPermissionForCardView", true)
&& !store.getBoolean("doNotAskForLocationPermission", false)
&& (((MainActivity) getActivity()).activeFragment == ActiveFragment.CONTRIBUTIONS)) {
binding.cardViewNearby.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
showNearbyCardPermissionRationale();
}
@ -508,16 +526,17 @@ public class ContributionsFragment
private void showNearbyCardPermissionRationale() {
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::requestLocationPermission,
this::displayYouWontSeeNearbyMessage,
checkBoxView,
false);
getString(R.string.nearby_card_permission_title),
getString(R.string.nearby_card_permission_explanation),
this::requestLocationPermission,
this::displayYouWontSeeNearbyMessage,
checkBoxView,
false);
}
private void displayYouWontSeeNearbyMessage() {
ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.unable_to_display_nearest_place));
ViewUtil.showLongToast(getActivity(),
getResources().getString(R.string.unable_to_display_nearest_place));
store.putBoolean("doNotAskForLocationPermission", true);
}
@ -525,18 +544,21 @@ public class ContributionsFragment
private void updateClosestNearbyCardViewInfo() {
curLatLng = locationManager.getLastLocation();
compositeDisposable.add(Observable.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curLatLng, curLatLng, true, false, false)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNearbyNotification,
throwable -> {
Timber.d(throwable);
updateNearbyNotification(null);
}));
.loadAttractionsFromLocation(curLatLng, curLatLng, true,
false)) // thanks to boolean, it will only return closest result
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateNearbyNotification,
throwable -> {
Timber.d(throwable);
updateNearbyNotification(null);
}));
}
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
private void updateNearbyNotification(
@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null
&& nearbyPlacesInfo.placeList.size() > 0) {
Place closestNearbyPlace = null;
// Find the first nearby place that has no image and exists
for (Place place : nearbyPlacesInfo.placeList) {
@ -546,9 +568,9 @@ public class ContributionsFragment
}
}
if(closestNearbyPlace == null) {
if (closestNearbyPlace == null) {
binding.cardViewNearby.setVisibility(View.GONE);
}else{
} else {
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
closestNearbyPlace.setDistance(distance);
direction = (float) computeBearing(curLatLng, closestNearbyPlace.location);
@ -567,7 +589,7 @@ public class ContributionsFragment
@Override
public void onDestroy() {
try{
try {
compositeDisposable.clear();
getChildFragmentManager().removeOnBackStackChangedListener(this);
locationManager.unregisterLocationManager();
@ -587,7 +609,7 @@ public class ContributionsFragment
@Override
public void onLocationChangedSlightly(LatLng latLng) {
/* Update closest nearby notification card onLocationChangedSlightly
*/
*/
try {
updateClosestNearbyCardViewInfo();
} catch (Exception e) {
@ -601,7 +623,8 @@ public class ContributionsFragment
updateClosestNearbyCardViewInfo();
}
@Override public void onViewCreated(@NonNull View view,
@Override
public void onViewCreated(@NonNull View view,
@Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
@ -626,11 +649,13 @@ public class ContributionsFragment
}
}
@Override public void showMessage(String message) {
@Override
public void showMessage(String message) {
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
}
@Override public void showCampaigns(Campaign campaign) {
@Override
public void showCampaigns(Campaign campaign) {
if (campaign != null && !isUserProfile) {
if (binding!=null) {
binding.campaignsView.setCampaign(campaign);
@ -638,7 +663,8 @@ public class ContributionsFragment
}
}
@Override public void onDestroyView() {
@Override
public void onDestroyView() {
super.onDestroyView();
presenter.onDetachView();
}
@ -652,6 +678,7 @@ public class ContributionsFragment
/**
* Restarts the upload process for a contribution
*
* @param contribution
*/
public void restartUpload(Contribution contribution) {
@ -659,6 +686,7 @@ public class ContributionsFragment
contributionsPresenter.saveContribution(contribution);
Timber.d("Restarting for %s", contribution.toString());
}
/**
* Retry upload when it is failed
*
@ -667,7 +695,8 @@ public class ContributionsFragment
@Override
public void retryUpload(Contribution contribution) {
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
if (contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
if (contribution.getState() == STATE_PAUSED
|| contribution.getState() == Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
restartUpload(contribution);
} else if (contribution.getState() == STATE_FAILED) {
int retries = contribution.getRetries();
@ -675,9 +704,10 @@ public class ContributionsFragment
/* Limit the number of retries for a failed upload
to handle cases like invalid filename as such uploads
will never be successful */
if(retries < MAX_RETRIES) {
if (retries < MAX_RETRIES) {
contribution.setRetries(retries + 1);
Timber.d("Retried uploading %s %d times", contribution.getMedia().getFilename(), retries + 1);
Timber.d("Retried uploading %s %d times", contribution.getMedia().getFilename(),
retries + 1);
restartUpload(contribution);
} else {
// TODO: Show the exact reason for failure
@ -695,6 +725,7 @@ public class ContributionsFragment
/**
* Pauses the upload
*
* @param contribution
*/
@Override
@ -718,15 +749,15 @@ public class ContributionsFragment
/**
* Replace whatever is in the current contributionsFragmentContainer view with
* mediaDetailPagerFragment, and preserve previous state in back stack. Called when user selects a
* contribution.
* mediaDetailPagerFragment, and preserve previous state in back stack. Called when user selects
* a contribution.
*/
@Override
public void showDetail(int position, boolean isWikipediaButtonDisplayed) {
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true);
if(isUserProfile) {
((ProfileActivity)getActivity()).setScroll(false);
if (isUserProfile) {
((ProfileActivity) getActivity()).setScroll(false);
}
showMediaDetailPagerFragment();
}
@ -758,17 +789,19 @@ public class ContributionsFragment
binding.cardViewNearby.setVisibility(View.GONE);
}
removeFragment(mediaDetailPagerFragment);
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG, mediaDetailPagerFragment);
if(isUserProfile) {
showFragment(contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG,
mediaDetailPagerFragment);
if (isUserProfile) {
// Fragment is associated with ProfileActivity
// Enable ParentViewPager Scroll
((ProfileActivity)getActivity()).setScroll(true);
}else {
((ProfileActivity) getActivity()).setScroll(true);
} else {
fetchCampaigns();
}
if (getActivity() instanceof MainActivity) {
// Fragment is associated with MainActivity
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
((BaseActivity) getActivity()).getSupportActionBar()
.setDisplayHomeAsUpEnabled(false);
((MainActivity) getActivity()).showTabs();
}
return true;
@ -788,11 +821,11 @@ public class ContributionsFragment
void upDateUploadCount() {
WorkManager.getInstance(getContext())
.getWorkInfosForUniqueWorkLiveData(UploadWorker.class.getSimpleName()).observe(
getViewLifecycleOwner(), workInfos -> {
if (workInfos.size() > 0) {
setUploadCount();
}
});
getViewLifecycleOwner(), workInfos -> {
if (workInfos.size() > 0) {
setUploadCount();
}
});
}
@ -803,7 +836,7 @@ public class ContributionsFragment
*/
@Override
public void refreshNominatedMedia(int index) {
if(mediaDetailPagerFragment != null && !contributionsListFragment.isVisible()) {
if (mediaDetailPagerFragment != null && !contributionsListFragment.isVisible()) {
removeFragment(mediaDetailPagerFragment);
mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true);
mediaDetailPagerFragment.showImage(index);
@ -811,20 +844,20 @@ public class ContributionsFragment
}
}
// click listener to toggle description that means uses can press the limited connection
// banner and description will hide. Tap again to show description.
private View.OnClickListener toggleDescriptionListener = new View.OnClickListener() {
// click listener to toggle description that means uses can press the limited connection
// banner and description will hide. Tap again to show description.
private View.OnClickListener toggleDescriptionListener = new View.OnClickListener() {
@Override
public void onClick(View view) {
View view2 = binding.limitedConnectionDescriptionTextView;
if (view2.getVisibility() == View.GONE) {
view2.setVisibility(View.VISIBLE);
} else {
view2.setVisibility(View.GONE);
}
}
};
@Override
public void onClick(View view) {
View view2 = binding.limitedConnectionDescriptionTextView;
if (view2.getVisibility() == View.GONE) {
view2.setVisibility(View.VISIBLE);
} else {
view2.setVisibility(View.GONE);
}
}
};
/**
* When the device rotates, rotate the Nearby banner's compass arrow in tandem.

View file

@ -269,16 +269,15 @@ public class OkHttpJsonApiClient {
/**
* Make API Call to get Nearby Places
*
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @param shouldQueryForMonuments : Should we query for monuments
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @return
* @throws Exception
*/
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final boolean shouldQueryForMonuments, final String customQuery)
final String customQuery)
throws Exception {
Timber.d("Fetching nearby items at radius %s", radius);
@ -286,10 +285,9 @@ public class OkHttpJsonApiClient {
final String wikidataQuery;
if (customQuery != null) {
wikidataQuery = customQuery;
} else if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
} else {
wikidataQuery = FileUtils.readFromResource("/queries/nearby_query_monuments.rq");
wikidataQuery = FileUtils.readFromResource(
"/queries/radius_query_for_upload_wizard.rq");
}
final String query = wikidataQuery
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
@ -307,6 +305,74 @@ public class OkHttpJsonApiClient {
.url(urlBuilder.build())
.build();
final Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
final String json = response.body().string();
final NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
final List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
final List<Place> places = new ArrayList<>();
for (final NearbyResultItem item : bindings) {
final Place placeFromNearbyItem = Place.from(item);
placeFromNearbyItem.setMonument(false);
places.add(placeFromNearbyItem);
}
return places;
}
throw new Exception(response.message());
}
/**
* Retrieves nearby places based on screen coordinates and optional query parameters.
*
* @param screenTopRight The top right corner of the screen (latitude, longitude).
* @param screenBottomLeft The bottom left corner of the screen (latitude, longitude).
* @param language The language for the query.
* @param shouldQueryForMonuments Flag indicating whether to include monuments in the query.
* @param customQuery Optional custom SPARQL query to use instead of default
* queries.
* @return A list of nearby places.
* @throws Exception If an error occurs during the retrieval process.
*/
@Nullable
public List<Place> getNearbyPlaces(
final fr.free.nrw.commons.location.LatLng screenTopRight,
final fr.free.nrw.commons.location.LatLng screenBottomLeft, final String language,
final boolean shouldQueryForMonuments, final String customQuery)
throws Exception {
Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null));
final String wikidataQuery;
if (customQuery != null) {
wikidataQuery = customQuery;
} else if (!shouldQueryForMonuments) {
wikidataQuery = FileUtils.readFromResource("/queries/rectangle_query_for_nearby.rq");
} else {
wikidataQuery = FileUtils.readFromResource(
"/queries/rectangle_query_for_nearby_monuments.rq");
}
final double westCornerLat = screenTopRight.getLatitude();
final double westCornerLong = screenTopRight.getLongitude();
final double eastCornerLat = screenBottomLeft.getLatitude();
final double eastCornerLong = screenBottomLeft.getLongitude();
final String query = wikidataQuery
.replace("${LAT_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLat))
.replace("${LONG_WEST}", String.format(Locale.ROOT, "%.4f", westCornerLong))
.replace("${LAT_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLat))
.replace("${LONG_EAST}", String.format(Locale.ROOT, "%.4f", eastCornerLong))
.replace("${LANG}", language);
final HttpUrl.Builder urlBuilder = HttpUrl
.parse(sparqlQueryUrl)
.newBuilder()
.addQueryParameter("query", query)
.addQueryParameter("format", "json");
final Request request = new Request.Builder()
.url(urlBuilder.build())
.build();
final Response response = okHttpClient.newCall(request).execute();
if (response.body() != null && response.isSuccessful()) {
final String json = response.body().string();
@ -330,18 +396,16 @@ public class OkHttpJsonApiClient {
/**
* Make API Call to get Nearby Places Implementation does not expects a custom query
*
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @param shouldQueryForMonuments : Should we query for monuments
* @param cur Search lat long
* @param language Language
* @param radius Search Radius
* @return
* @throws Exception
*/
@Nullable
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius,
final boolean shouldQueryForMonuments)
public List<Place> getNearbyPlaces(final LatLng cur, final String language, final double radius)
throws Exception {
return getNearbyPlaces(cur, language, radius, shouldQueryForMonuments, null);
return getNearbyPlaces(cur, language, radius, null);
}
/**

View file

@ -39,34 +39,34 @@ public class NearbyController extends MapController {
/**
* Prepares Place list to make their distance information update later.
*
* @param curLatLng current location for user
* @param searchLatLng the location user wants to search around
* @param curLatLng current location for user
* @param searchLatLng the location user wants to search around
* @param returnClosestResult if this search is done to find closest result or all results
* @param customQuery if this search is done via an advanced query
* @return NearbyPlacesInfo a variable holds Place list without distance information
* and boundary coordinates of current Place List
* @param customQuery if this search is done via an advanced query
* @return NearbyPlacesInfo a variable holds Place list without distance information and
* boundary coordinates of current Place List
*/
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng, final LatLng searchLatLng,
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng,
final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation,
final boolean shouldQueryForMonuments, @Nullable final String customQuery) throws Exception {
@Nullable final String customQuery) throws Exception {
Timber.d("Loading attractions near %s", searchLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
if (searchLatLng == null) {
Timber.d("Loading attractions nearby, but curLatLng is null");
return null;
}
List<Place> places = nearbyPlaces
.radiusExpander(searchLatLng, Locale.getDefault().getLanguage(), returnClosestResult,
shouldQueryForMonuments, customQuery);
customQuery);
if (null != places && places.size() > 0) {
LatLng[] boundaryCoordinates = {places.get(0).location, // south
places.get(0).location, // north
places.get(0).location, // west
places.get(0).location};// east, init with a random location
LatLng[] boundaryCoordinates = {
places.get(0).location, // south
places.get(0).location, // north
places.get(0).location, // west
places.get(0).location};// east, init with a random location
if (curLatLng != null) {
Timber.d("Sorting places by distance...");
@ -88,11 +88,11 @@ public class NearbyController extends MapController {
}
}
Collections.sort(places,
(lhs, rhs) -> {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
(lhs, rhs) -> {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
);
}
nearbyPlacesInfo.curLatLng = curLatLng;
@ -104,11 +104,11 @@ public class NearbyController extends MapController {
if (!returnClosestResult) {
// To remember latest search either around user or any point on map
latestSearchLocation = searchLatLng;
latestSearchRadius = nearbyPlaces.radius*1000; // to meter
latestSearchRadius = nearbyPlaces.radius * 1000; // to meter
// Our radius searched around us, will be used to understand when user search their own location, we will follow them
if (checkingAroundCurrentLocation) {
currentLocationSearchRadius = nearbyPlaces.radius*1000; // to meter
currentLocationSearchRadius = nearbyPlaces.radius * 1000; // to meter
currentLocation = curLatLng;
}
}
@ -118,6 +118,98 @@ public class NearbyController extends MapController {
return nearbyPlacesInfo;
}
/**
* Prepares Place list to make their distance information update later.
*
* @param curLatLng The current latitude and longitude.
* @param screenTopRight The top right corner of the screen (latitude,
* longitude).
* @param screenBottomLeft The bottom left corner of the screen (latitude,
* longitude).
* @param searchLatLng The latitude and longitude of the search location.
* @param returnClosestResult Flag indicating whether to return the closest result.
* @param checkingAroundCurrentLocation Flag indicating whether to check around the current
* location.
* @param shouldQueryForMonuments Flag indicating whether to include monuments in the
* query.
* @param customQuery Optional custom SPARQL query to use instead of default
* queries.
* @return An object containing information about nearby places.
* @throws Exception If an error occurs during the retrieval process.
*/
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng,
final fr.free.nrw.commons.location.LatLng screenTopRight,
final fr.free.nrw.commons.location.LatLng screenBottomLeft, final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation,
final boolean shouldQueryForMonuments, @Nullable final String customQuery)
throws Exception {
Timber.d("Loading attractions near %s", searchLatLng);
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
if (searchLatLng == null) {
Timber.d("Loading attractions nearby, but curLatLng is null");
return null;
}
List<Place> places = nearbyPlaces.getFromWikidataQuery(screenTopRight, screenBottomLeft,
Locale.getDefault().getLanguage(), shouldQueryForMonuments, customQuery);
if (null != places && places.size() > 0) {
LatLng[] boundaryCoordinates = {
places.get(0).location, // south
places.get(0).location, // north
places.get(0).location, // west
places.get(0).location};// east, init with a random location
if (curLatLng != null) {
Timber.d("Sorting places by distance...");
final Map<Place, Double> distances = new HashMap<>();
for (Place place : places) {
distances.put(place, computeDistanceBetween(place.location, curLatLng));
// Find boundaries with basic find max approach
if (place.location.getLatitude() < boundaryCoordinates[0].getLatitude()) {
boundaryCoordinates[0] = place.location;
}
if (place.location.getLatitude() > boundaryCoordinates[1].getLatitude()) {
boundaryCoordinates[1] = place.location;
}
if (place.location.getLongitude() < boundaryCoordinates[2].getLongitude()) {
boundaryCoordinates[2] = place.location;
}
if (place.location.getLongitude() > boundaryCoordinates[3].getLongitude()) {
boundaryCoordinates[3] = place.location;
}
}
Collections.sort(places,
(lhs, rhs) -> {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
);
}
nearbyPlacesInfo.curLatLng = curLatLng;
nearbyPlacesInfo.searchLatLng = searchLatLng;
nearbyPlacesInfo.placeList = places;
nearbyPlacesInfo.boundaryCoordinates = boundaryCoordinates;
// Returning closes result means we use the controller for nearby card. So no need to set search this area flags
if (!returnClosestResult) {
// To remember latest search either around user or any point on map
latestSearchLocation = searchLatLng;
latestSearchRadius = nearbyPlaces.radius * 1000; // to meter
// Our radius searched around us, will be used to understand when user search their own location, we will follow them
if (checkingAroundCurrentLocation) {
currentLocationSearchRadius = nearbyPlaces.radius * 1000; // to meter
currentLocation = curLatLng;
}
}
}
return nearbyPlacesInfo;
}
/**
* Prepares Place list to make their distance information update later.
*
@ -129,10 +221,10 @@ public class NearbyController extends MapController {
*/
public NearbyPlacesInfo loadAttractionsFromLocation(final LatLng curLatLng,
final LatLng searchLatLng,
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation,
final boolean shouldQueryForMonuments) throws Exception {
final boolean returnClosestResult, final boolean checkingAroundCurrentLocation)
throws Exception {
return loadAttractionsFromLocation(curLatLng, searchLatLng, returnClosestResult,
checkingAroundCurrentLocation, shouldQueryForMonuments, null);
checkingAroundCurrentLocation, null);
}
/**
@ -171,12 +263,14 @@ public class NearbyController extends MapController {
/**
* Updates makerLabelList item isBookmarked value
* @param place place which is bookmarked
*
* @param place place which is bookmarked
* @param isBookmarked true is bookmarked, false if bookmark removed
*/
@MainThread
public static void updateMarkerLabelListBookmark(Place place, boolean isBookmarked) {
for (ListIterator<MarkerPlaceGroup> iter = markerLabelList.listIterator(); iter.hasNext();) {
for (ListIterator<MarkerPlaceGroup> iter = markerLabelList.listIterator();
iter.hasNext(); ) {
MarkerPlaceGroup markerPlaceGroup = iter.next();
if (markerPlaceGroup.getPlace().getWikiDataEntityId().equals(place.getWikiDataEntityId())) {
iter.set(new MarkerPlaceGroup(isBookmarked, place));

View file

@ -27,6 +27,7 @@ public class NearbyPlaces {
/**
* Reads Wikidata query to check nearby wikidata items which needs picture, with a circular
* search. As a point is center of a circle with a radius will be set later.
*
* @param okHttpJsonApiClient
*/
@Inject
@ -36,15 +37,15 @@ public class NearbyPlaces {
/**
* Expands the radius as needed for the Wikidata query
* @param curLatLng coordinates of search location
* @param lang user's language
*
* @param curLatLng coordinates of search location
* @param lang user's language
* @param returnClosestResult true if only the nearest point is desired
* @param customQuery
* @return list of places obtained
*/
List<Place> radiusExpander(final LatLng curLatLng, final String lang,
final boolean returnClosestResult
, final boolean shouldQueryForMonuments, @Nullable final String customQuery) throws Exception {
final boolean returnClosestResult, @Nullable final String customQuery) throws Exception {
final int minResults;
final double maxRadius;
@ -63,15 +64,15 @@ public class NearbyPlaces {
radius = INITIAL_RADIUS;
}
// Increase the radius gradually to find a satisfactory number of nearby places
while (radius <= maxRadius) {
places = getFromWikidataQuery(curLatLng, lang, radius, shouldQueryForMonuments, customQuery);
Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= minResults) {
break;
}
radius *= RADIUS_MULTIPLIER;
// Increase the radius gradually to find a satisfactory number of nearby places
while (radius <= maxRadius) {
places = getFromWikidataQuery(curLatLng, lang, radius, customQuery);
Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= minResults) {
break;
}
radius *= RADIUS_MULTIPLIER;
}
// make sure we will be able to send at least one request next time
if (radius > maxRadius) {
radius = maxRadius;
@ -81,18 +82,41 @@ public class NearbyPlaces {
/**
* Runs the Wikidata query to populate the Places around search location
* @param cur coordinates of search location
* @param lang user's language
* @param radius radius for search, as determined by radiusExpander()
* @param shouldQueryForMonuments should the query include properites for monuments
*
* @param cur coordinates of search location
* @param lang user's language
* @param radius radius for search, as determined by radiusExpander()
* @param customQuery
* @return list of places obtained
* @throws IOException if query fails
*/
public List<Place> getFromWikidataQuery(final LatLng cur, final String lang,
final double radius, final boolean shouldQueryForMonuments,
final double radius,
@Nullable final String customQuery) throws Exception {
return okHttpJsonApiClient
.getNearbyPlaces(cur, lang, radius, shouldQueryForMonuments, customQuery);
.getNearbyPlaces(cur, lang, radius, customQuery);
}
/**
* Retrieves a list of places from a Wikidata query based on screen coordinates and optional
* parameters.
*
* @param screenTopRight The top right corner of the screen (latitude, longitude).
* @param screenBottomLeft The bottom left corner of the screen (latitude, longitude).
* @param lang The language for the query.
* @param shouldQueryForMonuments Flag indicating whether to include monuments in the query.
* @param customQuery Optional custom SPARQL query to use instead of default
* queries.
* @return A list of places obtained from the Wikidata query.
* @throws Exception If an error occurs during the retrieval process.
*/
public List<Place> getFromWikidataQuery(
final fr.free.nrw.commons.location.LatLng screenTopRight,
final fr.free.nrw.commons.location.LatLng screenBottomLeft, final String lang,
final boolean shouldQueryForMonuments,
@Nullable final String customQuery) throws Exception {
return okHttpJsonApiClient
.getNearbyPlaces(screenTopRight, screenBottomLeft, lang, shouldQueryForMonuments,
customQuery);
}
}

View file

@ -116,6 +116,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.osmdroid.api.IGeoPoint;
import org.osmdroid.events.MapEventsReceiver;
import org.osmdroid.events.MapListener;
import org.osmdroid.events.ScrollEvent;
@ -489,7 +490,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
final AdvanceQueryFragment fragment = new AdvanceQueryFragment();
final Bundle bundle = new Bundle();
try {
bundle.putString("query", FileUtils.readFromResource("/queries/nearby_query.rq"));
bundle.putString("query", FileUtils.readFromResource("/queries/radius_query_for_upload_wizard.rq"));
} catch (IOException e) {
Timber.e(e);
}
@ -1119,11 +1120,52 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void populatePlaces(final fr.free.nrw.commons.location.LatLng curlatLng) {
if (curlatLng.equals(getLastMapFocus())) { // Means we are checking around current location
populatePlacesForCurrentLocation(getLastMapFocus(), curlatLng, null);
IGeoPoint screenTopRight = mapView.getProjection().fromPixels(mapView.getWidth(), 0);
IGeoPoint screenBottomLeft = mapView.getProjection().fromPixels(0, mapView.getHeight());
fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(
screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0);
fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(
screenTopRight.getLatitude(), screenTopRight.getLongitude(), 0);
// When the nearby fragment is opened immediately upon app launch, the {screenTopRightLatLng}
// and {screenBottomLeftLatLng} variables return {LatLng(0.0,0.0)} as output.
// To address this issue, A small delta value {delta = 0.02} is used to adjust the latitude
// and longitude values for {ZOOM_LEVEL = 14f}.
// This adjustment helps in calculating the east and west corner LatLng accurately.
// Note: This only happens when the nearby fragment is opened immediately upon app launch,
// otherwise {screenTopRightLatLng} and {screenBottomLeftLatLng} are used to determine
// the east and west corner LatLng.
if (screenTopRightLatLng.getLatitude() == 0.0 && screenTopRightLatLng.getLongitude() == 0.0
&& screenBottomLeftLatLng.getLatitude() == 0.0
&& screenBottomLeftLatLng.getLongitude() == 0.0) {
final double delta = 0.02;
final double westCornerLat = curlatLng.getLatitude() - delta;
final double westCornerLong = curlatLng.getLongitude() - delta;
final double eastCornerLat = curlatLng.getLatitude() + delta;
final double eastCornerLong = curlatLng.getLongitude() + delta;
screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(westCornerLat,
westCornerLong, 0);
screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(eastCornerLat,
eastCornerLong, 0);
if (curlatLng.equals(
getLastMapFocus())) { // Means we are checking around current location
populatePlacesForCurrentLocation(getLastMapFocus(), screenTopRightLatLng,
screenBottomLeftLatLng, curlatLng, null);
} else {
populatePlacesForAnotherLocation(getLastMapFocus(), screenTopRightLatLng,
screenBottomLeftLatLng, curlatLng, null);
}
} else {
populatePlacesForAnotherLocation(getLastMapFocus(), curlatLng, null);
if (curlatLng.equals(
getLastMapFocus())) { // Means we are checking around current location
populatePlacesForCurrentLocation(getLastMapFocus(), screenTopRightLatLng,
screenBottomLeftLatLng, curlatLng, null);
} else {
populatePlacesForAnotherLocation(getLastMapFocus(), screenTopRightLatLng,
screenBottomLeftLatLng, curlatLng, null);
}
}
if (recenterToUserLocation) {
recenterToUserLocation = false;
}
@ -1136,12 +1178,20 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
populatePlaces(curlatLng);
return;
}
IGeoPoint screenTopRight = mapView.getProjection().fromPixels(mapView.getWidth(), 0);
IGeoPoint screenBottomLeft = mapView.getProjection().fromPixels(0, mapView.getHeight());
fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(
screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0);
fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(
screenTopRight.getLatitude(), screenTopRight.getLongitude(), 0);
if (curlatLng.equals(lastFocusLocation) || lastFocusLocation == null
|| recenterToUserLocation) { // Means we are checking around current location
populatePlacesForCurrentLocation(lastKnownLocation, curlatLng, customQuery);
populatePlacesForCurrentLocation(lastKnownLocation, screenTopRightLatLng,
screenBottomLeftLatLng, curlatLng, customQuery);
} else {
populatePlacesForAnotherLocation(lastKnownLocation, curlatLng, customQuery);
populatePlacesForAnotherLocation(lastKnownLocation, screenTopRightLatLng,
screenBottomLeftLatLng, curlatLng, customQuery);
}
if (recenterToUserLocation) {
recenterToUserLocation = false;
@ -1150,11 +1200,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private void populatePlacesForCurrentLocation(
final fr.free.nrw.commons.location.LatLng curlatLng,
final fr.free.nrw.commons.location.LatLng screenTopRight,
final fr.free.nrw.commons.location.LatLng screenBottomLeft,
final fr.free.nrw.commons.location.LatLng searchLatLng,
@Nullable final String customQuery) {
final Observable<NearbyController.NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng,
.loadAttractionsFromLocation(curlatLng, screenTopRight, screenBottomLeft,
searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
compositeDisposable.add(nearbyPlacesInfoObservable
@ -1182,11 +1235,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private void populatePlacesForAnotherLocation(
final fr.free.nrw.commons.location.LatLng curlatLng,
final fr.free.nrw.commons.location.LatLng screenTopRight,
final fr.free.nrw.commons.location.LatLng screenBottomLeft,
final fr.free.nrw.commons.location.LatLng searchLatLng,
@Nullable final String customQuery) {
final Observable<NearbyPlacesInfo> nearbyPlacesInfoObservable = Observable
.fromCallable(() -> nearbyController
.loadAttractionsFromLocation(curlatLng, searchLatLng,
.loadAttractionsFromLocation(curlatLng, screenTopRight, screenBottomLeft,
searchLatLng,
false, true, Utils.isMonumentsEnabled(new Date()), customQuery));
compositeDisposable.add(nearbyPlacesInfoObservable

View file

@ -345,6 +345,7 @@ public class UploadRepository {
/**
* Returns nearest place matching the passed latitude and longitude
*
* @param decLatitude
* @param decLongitude
* @return
@ -354,11 +355,11 @@ public class UploadRepository {
try {
final List<Place> fromWikidataQuery = nearbyPlaces.getFromWikidataQuery(new LatLng(
decLatitude, decLongitude, 0.0f),
Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS, false, null);
Locale.getDefault().getLanguage(),
NEARBY_RADIUS_IN_KILO_METERS, null);
return (fromWikidataQuery != null && fromWikidataQuery.size() > 0) ? fromWikidataQuery
.get(0) : null;
}catch (final Exception e) {
} catch (final Exception e) {
Timber.e("Error fetching nearby places: %s", e.getMessage());
return null;
}

View file

@ -49,8 +49,10 @@ class FileProcessor @Inject constructor(
/**
* Processes filePath coordinates, either from EXIF data or user location
*/
fun processFileCoordinates(similarImageInterface: SimilarImageInterface,
filePath: String?, inAppPictureLocation: LatLng?)
fun processFileCoordinates(
similarImageInterface: SimilarImageInterface,
filePath: String?, inAppPictureLocation: LatLng?
)
: ImageCoordinates {
val exifInterface: ExifInterface? = try {
ExifInterface(filePath!!)
@ -207,8 +209,7 @@ class FileProcessor @Inject constructor(
okHttpJsonApiClient.getNearbyPlaces(
imageCoordinates.latLng,
Locale.getDefault().language,
it,
false
it
)
}
}

View file

@ -85,7 +85,7 @@ public class FileUtils {
/**
* Read and return the content of a resource filePath as string.
*
* @param fileName asset filePath's path (e.g. "/queries/nearby_query.rq")
* @param fileName asset filePath's path (e.g. "/queries/radius_query_for_upload_wizard.rq")
* @return the content of the filePath
*/
public static String readFromResource(String fileName) throws IOException {