From 724e4db0fd4513cbdcff2a0d5715a77975b3dcbc Mon Sep 17 00:00:00 2001 From: Kanahia <114223204+kanahia1@users.noreply.github.com> Date: Sun, 24 Mar 2024 19:54:39 +0530 Subject: [PATCH] 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 --- .../contributions/ContributionsFragment.java | 249 ++++++++++-------- .../commons/mwapi/OkHttpJsonApiClient.java | 94 +++++-- .../nrw/commons/nearby/NearbyController.java | 146 ++++++++-- .../free/nrw/commons/nearby/NearbyPlaces.java | 60 +++-- .../fragments/NearbyParentFragment.java | 72 ++++- .../commons/repository/UploadRepository.java | 7 +- .../free/nrw/commons/upload/FileProcessor.kt | 9 +- .../fr/free/nrw/commons/upload/FileUtils.java | 2 +- ...y.rq => radius_query_for_upload_wizard.rq} | 0 .../queries/rectangle_query_for_nearby.rq | 56 ++++ ...> rectangle_query_for_nearby_monuments.rq} | 8 +- .../nrw/commons/OkHttpJsonApiClientTests.kt | 6 +- .../commons/nearby/NearbyControllerTest.kt | 55 ++-- .../nrw/commons/nearby/NearbyPlacesTest.kt | 3 +- .../upload/UploadRepositoryUnitTest.kt | 28 +- 15 files changed, 576 insertions(+), 219 deletions(-) rename app/src/main/resources/queries/{nearby_query.rq => radius_query_for_upload_wizard.rq} (100%) create mode 100644 app/src/main/resources/queries/rectangle_query_for_nearby.rq rename app/src/main/resources/queries/{nearby_query_monuments.rq => rectangle_query_for_nearby_monuments.rq} (91%) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index 7c2520390..3ac5c8197 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -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 nearbyLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { - @Override - public void onActivityResult(Map result) { - boolean areAllGranted = true; - for (final boolean b : result.values()) { - areAllGranted = areAllGranted && b; - } + private ActivityResultLauncher nearbyLocationPermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestMultiplePermissions(), + new ActivityResultCallback>() { + @Override + public void onActivityResult(Map 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. diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java index f2b253dae..dc567088c 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java @@ -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 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 bindings = nearbyResponse.getResults().getBindings(); + final List 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 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 getNearbyPlaces(final LatLng cur, final String language, final double radius, - final boolean shouldQueryForMonuments) + public List 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); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java index 2cdc0f233..37717e1a4 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java @@ -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 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 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 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 iter = markerLabelList.listIterator(); iter.hasNext();) { + for (ListIterator iter = markerLabelList.listIterator(); + iter.hasNext(); ) { MarkerPlaceGroup markerPlaceGroup = iter.next(); if (markerPlaceGroup.getPlace().getWikiDataEntityId().equals(place.getWikiDataEntityId())) { iter.set(new MarkerPlaceGroup(isBookmarked, place)); 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 24e5e3810..7fb69277a 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 @@ -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 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 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 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); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 189ba100c..7195e14cf 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -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 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 nearbyPlacesInfoObservable = Observable .fromCallable(() -> nearbyController - .loadAttractionsFromLocation(curlatLng, searchLatLng, + .loadAttractionsFromLocation(curlatLng, screenTopRight, screenBottomLeft, + searchLatLng, false, true, Utils.isMonumentsEnabled(new Date()), customQuery)); compositeDisposable.add(nearbyPlacesInfoObservable diff --git a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java index 0bfe90036..bde9984cd 100644 --- a/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java +++ b/app/src/main/java/fr/free/nrw/commons/repository/UploadRepository.java @@ -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 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; } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt index 4bd4797c4..d73384b35 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.kt @@ -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 ) } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java index f2c22fa9b..b45e4b57d 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java @@ -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 { diff --git a/app/src/main/resources/queries/nearby_query.rq b/app/src/main/resources/queries/radius_query_for_upload_wizard.rq similarity index 100% rename from app/src/main/resources/queries/nearby_query.rq rename to app/src/main/resources/queries/radius_query_for_upload_wizard.rq diff --git a/app/src/main/resources/queries/rectangle_query_for_nearby.rq b/app/src/main/resources/queries/rectangle_query_for_nearby.rq new file mode 100644 index 000000000..585541d56 --- /dev/null +++ b/app/src/main/resources/queries/rectangle_query_for_nearby.rq @@ -0,0 +1,56 @@ +SELECT + ?item + (SAMPLE(?location) as ?location) + (SAMPLE(?label) AS ?label) + (SAMPLE(?description) AS ?description) + (SAMPLE(?class) AS ?class) + (SAMPLE(?classLabel) AS ?classLabel) + (SAMPLE(?pic) AS ?pic) + (SAMPLE(?destroyed) AS ?destroyed) + (SAMPLE(?endTime) AS ?endTime) + (SAMPLE(?wikipediaArticle) AS ?wikipediaArticle) + (SAMPLE(?commonsArticle) AS ?commonsArticle) + (SAMPLE(?commonsCategory) AS ?commonsCategory) +WHERE { + # Around given location + SERVICE wikibase:box { + ?item wdt:P625 ?location. + bd:serviceParam wikibase:cornerWest "Point(${LONG_WEST} ${LAT_WEST})"^^geo:wktLiteral. + bd:serviceParam wikibase:cornerEast "Point(${LONG_EAST} ${LAT_EAST})"^^geo:wktLiteral. + } + + OPTIONAL { + ?item p:P31/ps:P31 ?class. + } + + # Get picture + OPTIONAL {?item wdt:P18 ?pic} + + # Get existence + OPTIONAL {?item wdt:P576 ?destroyed} + OPTIONAL {?item wdt:P582 ?endTime} + + # Get Commons category + OPTIONAL {?item wdt:P373 ?commonsCategory} + + # Get Wikipedia article + OPTIONAL { + ?wikipediaArticle schema:about ?item. + ?wikipediaArticle schema:isPartOf . + } + + # Get Commons article + OPTIONAL { + ?commonsArticle schema:about ?item. + ?commonsArticle schema:isPartOf . + } + + # Labels and descriptions + SERVICE wikibase:label { + bd:serviceParam wikibase:language "${LANG},en,fr,de,es,ja,ru,it,zh,pt,ar,fa,pl,nl,id,uk,he,sv,cs,ko,vi,ca,no,fi,hu,tr,th,hi,bn,ceb,ro,sw,kk,da,eo,sr,lt,sk,bg,sl,eu,et,hr,ms,el,arz,ur,ta,te,nn,gl,az,af,bs,be,ml,ka,is,sq,uz,la,br,mk,lv,azb,mr,sh,tl,cy,ckb,ast,be-tarask,zh-yue,hy,pa,as,my,kn,ne,si,tt,ha,war,zh-min-nan,vo,min,lmo,ht,lb,gu,tg,sco,ku,new,bpy,nds,io,pms,su,oc,jv,nap,ba,scn,wa,bar,an,ksh,szl,fy,frr,als,ia,ga,yi,mg,gd,vec,ce,sa,mai,xmf,sd,wuu,mrj,mhr,km,roa-tara,am,roa-rup,map-bms,bh,mnw,shn,bcl,co,cv,dv,nds-nl,fo,hif,fur,gan,glk,hak,ilo,pam,csb,avk,lij,li,gv,mi,mt,nah,nrm,se,nov,qu,os,pi,pag,ps,pdc,rm,bat-smg,sc,to,tk,hsb,fiu-vro,vls,yo,diq,zh-classical,frp,lad,kw,mn,haw,ang,ln,ie,wo,tpi,ty,crh,nv,jbo,ay,pcd,zea,eml,ky,ig,or,cbk-zam,kg,arc,rmy,ab,gn,so,kab,ug,stq,udm,ext,mzn,pap,cu,sah,tet,sn,lo,pnb,iu,na,got,bo,dsb,chr,cdo,om,sm,ee,ti,av,bm,zu,pnt,cr,pih,ss,ve,bi,rw,ch,xh,kl,ik,bug,dz,ts,tn,kv,tum,xal,st,tw,bxr,ak,ny,fj,lbe,za,ks,ff,lg,sg,rn,chy,mwl,lez,bjn,gom,tyv,vep,nso,kbd,ltg,rue,pfl,gag,koi,krc,ace,olo,kaa,mdf,myv,srn,ady,jam,tcy,dty,atj,kbp,din,lfn,gor,inh,sat,hyw,nqo,ban,szy,awa,ary,lld,smn,skr,mad,dag,shi,nia,ki,gcr". + ?item rdfs:label ?label. + ?item schema:description ?description. + ?class rdfs:label ?classLabel. + } +} +GROUP BY ?item \ No newline at end of file diff --git a/app/src/main/resources/queries/nearby_query_monuments.rq b/app/src/main/resources/queries/rectangle_query_for_nearby_monuments.rq similarity index 91% rename from app/src/main/resources/queries/nearby_query_monuments.rq rename to app/src/main/resources/queries/rectangle_query_for_nearby_monuments.rq index cd74b4d98..820c0bf24 100644 --- a/app/src/main/resources/queries/nearby_query_monuments.rq +++ b/app/src/main/resources/queries/rectangle_query_for_nearby_monuments.rq @@ -14,10 +14,10 @@ SELECT (SAMPLE(?monument) AS ?monument) WHERE { # Around given location - SERVICE wikibase:around { - ?item wdt:P625 ?location. - bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral. # Longitude latitude - bd:serviceParam wikibase:radius "${RAD}". # Radius in kilometers. + SERVICE wikibase:box { + ?item wdt:P625 ?location. + bd:serviceParam wikibase:cornerWest "Point(${LONG_WEST} ${LAT_WEST})"^^geo:wktLiteral. + bd:serviceParam wikibase:cornerEast "Point(${LONG_EAST} ${LAT_EAST})"^^geo:wktLiteral. } OPTIONAL { diff --git a/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt index 17734e3ce..98db40fd0 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt @@ -62,7 +62,8 @@ class OkHttpJsonApiClientTests { fun testGetNearbyPlacesCustomQuery() { Mockito.`when`(response.message).thenReturn("test") try { - okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, true, "test") + okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, "test") + okHttpJsonApiClient.getNearbyPlaces(latLng, latLng, "test", true, "test") } catch (e: Exception) { assert(e.message.equals("test")) } @@ -75,7 +76,8 @@ class OkHttpJsonApiClientTests { fun testGetNearbyPlaces() { Mockito.`when`(response.message).thenReturn("test") try { - okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, true) + okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, null) + okHttpJsonApiClient.getNearbyPlaces(latLng, latLng, "test", true, null) } catch (e: Exception) { assert(e.message.equals("test")) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyControllerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyControllerTest.kt index a8e4a8ee2..253c094b0 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyControllerTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyControllerTest.kt @@ -35,6 +35,12 @@ class NearbyControllerTest { @Mock private lateinit var nearbyPlaces: NearbyPlaces + @Mock + private lateinit var screenTopRight: LatLng + + @Mock + private lateinit var screenBottomLeft: LatLng + @Mock private lateinit var searchLatLong: LatLng @@ -56,41 +62,48 @@ class NearbyControllerTest { @Test fun testLoadAttractionsForLocationTest() { - `when`(nearbyPlaces.radiusExpander(any(), any(), any(), any(), any())) + `when`(nearbyPlaces.radiusExpander(any(), any(), any(), any())) .thenReturn(Collections.emptyList()) nearbyController.loadAttractionsFromLocation( - searchLatLong, currentLatLng, - false, + searchLatLong, false, true, customQuery ) + nearbyController.loadAttractionsFromLocation( + currentLatLng, + screenTopRight, + screenBottomLeft, + searchLatLong, + false, + true, + false, + customQuery + ) Mockito.verify(nearbyPlaces).radiusExpander( - eq(currentLatLng), + eq(searchLatLong), any(String::class.java), eq(false), - eq(true), eq(customQuery) ) } @Test fun testLoadAttractionsForLocationTestNoQuery() { - `when`(nearbyPlaces.radiusExpander(any(), any(), any(), any(), anyOrNull())) + `when`(nearbyPlaces.radiusExpander(any(), any(), any(), anyOrNull())) .thenReturn(Collections.emptyList()) nearbyController.loadAttractionsFromLocation( - searchLatLong, currentLatLng, + searchLatLong, false, - false, - true + true, + null ) Mockito.verify(nearbyPlaces).radiusExpander( - eq(currentLatLng), + eq(searchLatLong), any(String::class.java), eq(false), - eq(true), eq(null) ) } @@ -102,8 +115,7 @@ class NearbyControllerTest { currentLatLng, null, false, - false, - false, + true, customQuery ), null ) @@ -136,7 +148,7 @@ class NearbyControllerTest { `when`( nearbyPlaces.radiusExpander( searchLatLong, Locale.getDefault().language, false, - false, customQuery + customQuery ) ).thenReturn(mutableListOf(place1, place2)) val result = nearbyController.loadAttractionsFromLocation( @@ -144,6 +156,15 @@ class NearbyControllerTest { searchLatLong, false, true, + customQuery + ) + nearbyController.loadAttractionsFromLocation( + currentLatLng, + screenTopRight, + screenBottomLeft, + searchLatLong, + false, + true, false, customQuery ) @@ -178,7 +199,7 @@ class NearbyControllerTest { `when`( nearbyPlaces.radiusExpander( searchLatLong, Locale.getDefault().language, false, - false, customQuery + customQuery ) ).thenReturn(mutableListOf(place1, place2)) val result = nearbyController.loadAttractionsFromLocation( @@ -186,7 +207,6 @@ class NearbyControllerTest { searchLatLong, false, true, - false, customQuery ) assertEquals(result.curLatLng, currentLatLng) @@ -220,7 +240,7 @@ class NearbyControllerTest { `when`( nearbyPlaces.radiusExpander( searchLatLong, Locale.getDefault().language, false, - false, customQuery + customQuery ) ).thenReturn(mutableListOf(place1, place2)) val result = nearbyController.loadAttractionsFromLocation( @@ -228,7 +248,6 @@ class NearbyControllerTest { searchLatLong, false, true, - false, customQuery ) assertEquals(result.curLatLng, currentLatLng) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyPlacesTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyPlacesTest.kt index cf03db462..13fa2eac3 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyPlacesTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/nearby/NearbyPlacesTest.kt @@ -28,12 +28,11 @@ class NearbyPlacesTest { @Test fun testRadiusExpander() { - nearbyPlaces.radiusExpander(currentLatLong, "test", true, true, "test") + nearbyPlaces.radiusExpander(currentLatLong, "test", true, "test") verify(okHttpJsonApiClient, times(5)).getNearbyPlaces( eq(currentLatLong), eq("test"), any(), - eq(true), eq("test") ) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadRepositoryUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadRepositoryUnitTest.kt index a31f588e6..a5850bf91 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadRepositoryUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadRepositoryUnitTest.kt @@ -217,8 +217,10 @@ class UploadRepositoryUnitTest { @Test fun testSetSelectedExistingDepictions() { - assertEquals(repository.setSelectedExistingDepictions(listOf("")), - uploadModel.setSelectedExistingDepictions(listOf(""))) + assertEquals( + repository.setSelectedExistingDepictions(listOf("")), + uploadModel.setSelectedExistingDepictions(listOf("")) + ) } @Test @@ -264,7 +266,7 @@ class UploadRepositoryUnitTest { nearbyPlaces.getFromWikidataQuery( LatLng(0.0, 0.0, 0.0f), java.util.Locale.getDefault().language, 0.1, - false, null + null ) ).thenReturn(listOf(place)) assertEquals( @@ -287,7 +289,7 @@ class UploadRepositoryUnitTest { nearbyPlaces.getFromWikidataQuery( LatLng(0.0, 0.0, 0.0f), java.util.Locale.getDefault().language, 0.1, - false, null + null ) ).thenThrow(Exception()) assertEquals( @@ -348,19 +350,25 @@ class UploadRepositoryUnitTest { @Test fun testGetSelectedExistingCategories() { - assertEquals(repository.selectedExistingCategories, - categoriesModel.getSelectedExistingCategories()) + assertEquals( + repository.selectedExistingCategories, + categoriesModel.getSelectedExistingCategories() + ) } @Test fun testSetSelectedExistingCategories() { - assertEquals(repository.setSelectedExistingCategories(listOf("Test")), - categoriesModel.setSelectedExistingCategories(mutableListOf("Test"))) + assertEquals( + repository.setSelectedExistingCategories(listOf("Test")), + categoriesModel.setSelectedExistingCategories(mutableListOf("Test")) + ) } @Test fun testGetCategories() { - assertEquals(repository.getCategories(listOf("Test")), - categoriesModel.getCategoriesByName(mutableListOf("Test"))) + assertEquals( + repository.getCategories(listOf("Test")), + categoriesModel.getCategoriesByName(mutableListOf("Test")) + ) } } \ No newline at end of file