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

@ -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