This commit is contained in:
Rajas Yardi 2025-07-27 09:57:31 -04:00 committed by GitHub
commit 16272377a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 221 additions and 336 deletions

View file

@ -33,11 +33,10 @@ import timber.log.Timber;
public class ExploreMapController extends MapController { public class ExploreMapController extends MapController {
private final ExploreMapCalls exploreMapCalls; private final ExploreMapCalls exploreMapCalls;
public LatLng latestSearchLocation; // Can be current and camera target on search this area button is used public LatLng latestSearchLocation; // Last search center
public LatLng currentLocation; // current location of user public LatLng currentLocation; // Users current location
public double latestSearchRadius = 0; // Any last search radius public double latestSearchRadius = 0; // Last search radius
public double currentLocationSearchRadius = 0; // Search radius of only searches around current location public double currentLocationSearchRadius = 0; // Radius when searching around current location
@Inject @Inject
public ExploreMapController(ExploreMapCalls explorePlaces) { public ExploreMapController(ExploreMapCalls explorePlaces) {
@ -45,19 +44,13 @@ public class ExploreMapController extends MapController {
} }
/** /**
* Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList, * Load attractions around a given location and compute boundaries.
* explorePlaceList and boundaryCoordinates
*
* @param currentLatLng is current geolocation
* @param searchLatLng is the location that we want to search around
* @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around
* current location, false if another location
* @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and
* boundaryCoordinates
*/ */
public ExplorePlacesInfo loadAttractionsFromLocation(LatLng currentLatLng, LatLng searchLatLng, public ExplorePlacesInfo loadAttractionsFromLocation(
boolean checkingAroundCurrentLocation) { LatLng currentLatLng,
LatLng searchLatLng,
boolean checkingAroundCurrentLocation
) {
if (searchLatLng == null) { if (searchLatLng == null) {
Timber.d("Loading attractions explore map, but search is null"); Timber.d("Loading attractions explore map, but search is null");
return null; return null;
@ -69,71 +62,74 @@ public class ExploreMapController extends MapController {
latestSearchLocation = searchLatLng; latestSearchLocation = searchLatLng;
List<Media> mediaList = exploreMapCalls.callCommonsQuery(searchLatLng); List<Media> mediaList = exploreMapCalls.callCommonsQuery(searchLatLng);
LatLng[] boundaryCoordinates = {mediaList.get(0).getCoordinates(), // south LatLng[] boundaryCoordinates = {
mediaList.get(0).getCoordinates(), // south
mediaList.get(0).getCoordinates(), // north mediaList.get(0).getCoordinates(), // north
mediaList.get(0).getCoordinates(), // west mediaList.get(0).getCoordinates(), // west
mediaList.get(0).getCoordinates()};// east, init with a random location mediaList.get(0).getCoordinates() // east
};
if (searchLatLng != null) { // Compute distances and update boundaries
Timber.d("Sorting places by distance..."); Timber.d("Sorting places by distance...");
final Map<Media, Double> distances = new HashMap<>(); Map<Media, Double> distances = new HashMap<>();
for (Media media : mediaList) { for (Media media : mediaList) {
distances.put(media, distances.put(media, computeDistanceBetween(media.getCoordinates(), searchLatLng));
computeDistanceBetween(media.getCoordinates(), searchLatLng));
// Find boundaries with basic find max approach LatLng coords = media.getCoordinates();
if (media.getCoordinates().getLatitude() if (coords.getLatitude() < boundaryCoordinates[0].getLatitude()) {
< boundaryCoordinates[0].getLatitude()) { boundaryCoordinates[0] = coords;
boundaryCoordinates[0] = media.getCoordinates();
} }
if (media.getCoordinates().getLatitude() if (coords.getLatitude() > boundaryCoordinates[1].getLatitude()) {
> boundaryCoordinates[1].getLatitude()) { boundaryCoordinates[1] = coords;
boundaryCoordinates[1] = media.getCoordinates();
} }
if (media.getCoordinates().getLongitude() if (coords.getLongitude() < boundaryCoordinates[2].getLongitude()) {
< boundaryCoordinates[2].getLongitude()) { boundaryCoordinates[2] = coords;
boundaryCoordinates[2] = media.getCoordinates();
}
if (media.getCoordinates().getLongitude()
> boundaryCoordinates[3].getLongitude()) {
boundaryCoordinates[3] = media.getCoordinates();
} }
if (coords.getLongitude() > boundaryCoordinates[3].getLongitude()) {
boundaryCoordinates[3] = coords;
} }
} }
explorePlacesInfo.mediaList = mediaList; explorePlacesInfo.mediaList = mediaList;
explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList); explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList);
explorePlacesInfo.boundaryCoordinates = boundaryCoordinates; explorePlacesInfo.boundaryCoordinates = boundaryCoordinates;
// Sets latestSearchRadius to maximum distance among boundaries and search location // Compute latestSearchRadius as the max distance from search center
for (LatLng bound : boundaryCoordinates) { for (LatLng bound : boundaryCoordinates) {
double distance = LocationUtils.calculateDistance(bound.getLatitude(), double distance = LocationUtils.calculateDistance(
bound.getLongitude(), searchLatLng.getLatitude(), searchLatLng.getLongitude()); bound.getLatitude(),
bound.getLongitude(),
searchLatLng.getLatitude(),
searchLatLng.getLongitude()
);
if (distance > latestSearchRadius) { if (distance > latestSearchRadius) {
latestSearchRadius = distance; latestSearchRadius = distance;
} }
} }
// Our radius searched around us, will be used to understand when user search their own location, we will follow them // If searching around current location, capture that state
if (checkingAroundCurrentLocation) { if (checkingAroundCurrentLocation) {
currentLocationSearchRadius = latestSearchRadius; currentLocationSearchRadius = latestSearchRadius;
currentLocation = currentLatLng; currentLocation = currentLatLng;
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
return explorePlacesInfo; return explorePlacesInfo;
} }
/** /**
* Loads attractions from location for map view, we need to return places in Place data type * Convert a list of Place objects into BaseMarker options for displaying on the map.
*
* @return baseMarkerOptions list that holds nearby places with their icons
*/ */
public static List<BaseMarker> loadAttractionsFromLocationToBaseMarkerOptions( public static List<BaseMarker> loadAttractionsFromLocationToBaseMarkerOptions(
LatLng currentLatLng, LatLng currentLatLng,
final List<Place> placeList, final List<Place> placeList,
Context context, Context context,
NearbyBaseMarkerThumbCallback callback, NearbyBaseMarkerThumbCallback callback,
ExplorePlacesInfo explorePlacesInfo) { ExplorePlacesInfo explorePlacesInfo
) {
List<BaseMarker> baseMarkerList = new ArrayList<>(); List<BaseMarker> baseMarkerList = new ArrayList<>();
if (placeList == null) { if (placeList == null) {
@ -143,71 +139,94 @@ public class ExploreMapController extends MapController {
VectorDrawableCompat vectorDrawable = null; VectorDrawableCompat vectorDrawable = null;
try { try {
vectorDrawable = VectorDrawableCompat.create( vectorDrawable = VectorDrawableCompat.create(
context.getResources(), R.drawable.ic_custom_map_marker_dark, context.getTheme()); context.getResources(),
R.drawable.ic_custom_map_marker_dark,
} catch (Resources.NotFoundException e) { context.getTheme()
);
} catch (Resources.NotFoundException ignored) {
// ignore when running tests. // ignore when running tests.
} }
if (vectorDrawable != null) { if (vectorDrawable != null) {
for (Place explorePlace : placeList) { for (Place explorePlace : placeList) {
final BaseMarker baseMarker = new BaseMarker(); final BaseMarker baseMarker = new BaseMarker();
String distance = formatDistanceBetween(currentLatLng, explorePlace.location); String distance = formatDistanceBetween(currentLatLng, explorePlace.location);
explorePlace.setDistance(distance); explorePlace.setDistance(distance);
// Use caption if available, otherwise derive title from filename
if (explorePlace.caption != null && !explorePlace.caption.isEmpty()) {
baseMarker.setTitle(explorePlace.caption);
} else {
baseMarker.setTitle( baseMarker.setTitle(
explorePlace.name.substring(5, explorePlace.name.lastIndexOf("."))); explorePlace.name.substring(
5,
explorePlace.name.lastIndexOf(".")
)
);
}
baseMarker.setPosition( baseMarker.setPosition(
new fr.free.nrw.commons.location.LatLng( new fr.free.nrw.commons.location.LatLng(
explorePlace.location.getLatitude(), explorePlace.location.getLatitude(),
explorePlace.location.getLongitude(), 0)); explorePlace.location.getLongitude(),
0
)
);
baseMarker.setPlace(explorePlace); baseMarker.setPlace(explorePlace);
// Load thumbnail asynchronously
Glide.with(context) Glide.with(context)
.asBitmap() .asBitmap()
.load(explorePlace.getThumb()) .load(explorePlace.getThumb())
.placeholder(R.drawable.image_placeholder_96) .placeholder(R.drawable.image_placeholder_96)
.apply(new RequestOptions().override(96, 96).centerCrop()) .apply(new RequestOptions().override(96, 96).centerCrop())
.into(new CustomTarget<Bitmap>() { .into(new CustomTarget<Bitmap>() {
// We add icons to markers when bitmaps are ready
@Override @Override
public void onResourceReady(@NonNull Bitmap resource, public void onResourceReady(
@Nullable Transition<? super Bitmap> transition) { @NonNull Bitmap resource,
baseMarker.setIcon( @Nullable Transition<? super Bitmap> transition
ImageUtils.addRedBorder(resource, 6, context)); ) {
baseMarker.setIcon(ImageUtils.addRedBorder(resource, 6, context));
baseMarkerList.add(baseMarker); baseMarkerList.add(baseMarker);
if (baseMarkerList.size() if (baseMarkerList.size() == placeList.size()) {
== placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback callback.onNearbyBaseMarkerThumbsReady(
callback.onNearbyBaseMarkerThumbsReady(baseMarkerList, baseMarkerList,
explorePlacesInfo); explorePlacesInfo
);
} }
} }
@Override @Override
public void onLoadCleared(@Nullable Drawable placeholder) { public void onLoadCleared(@Nullable Drawable placeholder) {
// no-op
} }
// We add thumbnail icon for images that couldn't be loaded
@Override @Override
public void onLoadFailed(@Nullable final Drawable errorDrawable) { public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable); super.onLoadFailed(errorDrawable);
baseMarker.fromResource(context, R.drawable.image_placeholder_96); baseMarker.fromResource(context, R.drawable.image_placeholder_96);
baseMarkerList.add(baseMarker); baseMarkerList.add(baseMarker);
if (baseMarkerList.size() if (baseMarkerList.size() == placeList.size()) {
== placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback callback.onNearbyBaseMarkerThumbsReady(
callback.onNearbyBaseMarkerThumbsReady(baseMarkerList, baseMarkerList,
explorePlacesInfo); explorePlacesInfo
);
} }
} }
}); });
} }
} }
return baseMarkerList; return baseMarkerList;
} }
interface NearbyBaseMarkerThumbCallback { /**
* Callback interface for when all marker thumbnails are ready.
// Callback to notify thumbnails of explore markers are added as icons and ready */
void onNearbyBaseMarkerThumbsReady(List<BaseMarker> baseMarkers, public interface NearbyBaseMarkerThumbCallback {
ExplorePlacesInfo explorePlacesInfo); void onNearbyBaseMarkerThumbsReady(
List<BaseMarker> baseMarkers,
ExplorePlacesInfo explorePlacesInfo
);
} }
} }

View file

@ -15,24 +15,25 @@ import fr.free.nrw.commons.utils.PlaceUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import timber.log.Timber; import timber.log.Timber;
/**
* A single geolocated Wikidata item
*/
@Entity(tableName = "place") @Entity(tableName = "place")
public class Place implements Parcelable { public class Place implements Parcelable {
public String language; public String language;
public String name; public String name;
public String caption;
private Label label; private Label label;
private String longDescription; private String longDescription;
@Embedded @Embedded
public LatLng location; public LatLng location;
@PrimaryKey @NonNull @PrimaryKey
@NonNull
public String entityID; public String entityID;
private String category; private String category;
public String pic; public String pic;
// exists boolean will tell whether the place exists or not,
// For a place to be existing both destroyed and endTime property should be null but it is also not necessary for a non-existing place to have both properties either one property is enough (in such case that not given property will be considered as null). /**
* Indicates whether the place exists in reality (true) or has been destroyed/closed (false).
*/
public Boolean exists; public Boolean exists;
public String distance; public String distance;
@ -43,6 +44,7 @@ public class Place implements Parcelable {
public Place() { public Place() {
language = null; language = null;
name = null; name = null;
caption = null;
label = null; label = null;
longDescription = null; longDescription = null;
location = null; location = null;
@ -51,12 +53,17 @@ public class Place implements Parcelable {
exists = null; exists = null;
siteLinks = null; siteLinks = null;
entityID = null; entityID = null;
thumb = null;
} }
public Place(String language, String name, Label label, String longDescription, LatLng location, /**
* Full constructor with caption.
*/
public Place(String language, String name, String caption, Label label, String longDescription, LatLng location,
String category, Sitelinks siteLinks, String pic, Boolean exists, String entityID) { String category, Sitelinks siteLinks, String pic, Boolean exists, String entityID) {
this.language = language; this.language = language;
this.name = name; this.name = name;
this.caption = caption;
this.label = label; this.label = label;
this.longDescription = longDescription; this.longDescription = longDescription;
this.location = location; this.location = location;
@ -67,10 +74,14 @@ public class Place implements Parcelable {
this.entityID = entityID; this.entityID = entityID;
} }
/**
* Old constructor still kept (used elsewhere) sets caption to null.
*/
public Place(String language, String name, Label label, String longDescription, LatLng location, public Place(String language, String name, Label label, String longDescription, LatLng location,
String category, Sitelinks siteLinks, String pic, Boolean exists) { String category, Sitelinks siteLinks, String pic, Boolean exists) {
this.language = language; this.language = language;
this.name = name; this.name = name;
this.caption = null;
this.label = label; this.label = label;
this.longDescription = longDescription; this.longDescription = longDescription;
this.location = location; this.location = location;
@ -80,6 +91,7 @@ public class Place implements Parcelable {
this.exists = exists; this.exists = exists;
} }
// Another constructor (also set caption = null)
public Place(String name, String longDescription, LatLng location, String category, public Place(String name, String longDescription, LatLng location, String category,
Sitelinks siteLinks, String pic, String thumb, String entityID) { Sitelinks siteLinks, String pic, String thumb, String entityID) {
this.name = name; this.name = name;
@ -88,16 +100,18 @@ public class Place implements Parcelable {
this.category = category; this.category = category;
this.siteLinks = siteLinks; this.siteLinks = siteLinks;
this.pic = (pic == null) ? "" : pic; this.pic = (pic == null) ? "" : pic;
this.thumb = thumb;
this.language = null; this.language = null;
this.caption = null;
this.label = null; this.label = null;
this.exists = true; this.exists = true;
this.entityID = entityID; this.entityID = entityID;
this.thumb = thumb;
} }
public Place(Parcel in) { public Place(Parcel in) {
this.language = in.readString(); this.language = in.readString();
this.name = in.readString(); this.name = in.readString();
this.caption = in.readString();
this.label = (Label) in.readSerializable(); this.label = (Label) in.readSerializable();
this.longDescription = in.readString(); this.longDescription = in.readString();
this.location = in.readParcelable(LatLng.class.getClassLoader()); this.location = in.readParcelable(LatLng.class.getClassLoader());
@ -109,6 +123,7 @@ public class Place implements Parcelable {
this.exists = Boolean.parseBoolean(existString); this.exists = Boolean.parseBoolean(existString);
this.isMonument = in.readInt() == 1; this.isMonument = in.readInt() == 1;
this.entityID = in.readString(); this.entityID = in.readString();
this.thumb = in.readString();
} }
public static Place from(NearbyResultItem item) { public static Place from(NearbyResultItem item) {
@ -121,29 +136,27 @@ public class Place implements Parcelable {
if (!StringUtils.isBlank(item.getItem().getValue())) { if (!StringUtils.isBlank(item.getItem().getValue())) {
entityId = item.getItem().getValue().replace("http://www.wikidata.org/entity/", ""); entityId = item.getItem().getValue().replace("http://www.wikidata.org/entity/", "");
} }
// Set description when not null and not empty
String description = String description =
(item.getDescription().getValue() != null && !item.getDescription().getValue() (item.getDescription().getValue() != null && !item.getDescription().getValue().isEmpty())
.isEmpty()) ? item.getDescription().getValue() : ""; ? item.getDescription().getValue() : "";
// When description is "?" but we have a valid label, just use the label. So replace "?" by "" in description
description = (description.equals("?") /**
&& (item.getLabel().getValue() != null * Replace ? descriptions with empty when a non-empty label is available.
&& !item.getLabel().getValue().isEmpty()) ? "" : description);
/*
* If we have a valid label
* - If have a valid label add the description at the end of the string with parenthesis
* - If we don't have a valid label, string will include only the description. So add it without paranthesis
*/ */
description = (description.equals("?") && item.getLabel().getValue() != null
&& !item.getLabel().getValue().isEmpty())
? "" : description;
description = ((item.getLabel().getValue() != null && !item.getLabel().getValue().isEmpty()) description = ((item.getLabel().getValue() != null && !item.getLabel().getValue().isEmpty())
? item.getLabel().getValue() ? item.getLabel().getValue() + ((description != null && !description.isEmpty())
+ ((description != null && !description.isEmpty()) ? " (" + description + ")" : "") : description);
? " (" + description + ")" : "")
: description);
return new Place( return new Place(
item.getLabel().getLanguage(), item.getLabel().getLanguage(),
item.getLabel().getValue(), item.getLabel().getValue(),
Label.fromText(classEntityId), // list null,
description, // description and label of Wikidata item Label.fromText(classEntityId),
description,
PlaceUtils.latLngFromPointString(item.getLocation().getValue()), PlaceUtils.latLngFromPointString(item.getLocation().getValue()),
item.getCommonsCategory().getValue(), item.getCommonsCategory().getValue(),
new Sitelinks.Builder() new Sitelinks.Builder()
@ -152,47 +165,26 @@ public class Place implements Parcelable {
.setWikidataLink(item.getItem().getValue()) .setWikidataLink(item.getItem().getValue())
.build(), .build(),
item.getPic().getValue(), item.getPic().getValue(),
// Checking if the place exists or not
(item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == "") (item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == "")
&& (item.getDateOfOfficialClosure().getValue() == "") && (item.getDateOfOfficialClosure().getValue() == "")
&& (item.getPointInTime().getValue() == ""), && (item.getPointInTime().getValue() == ""),
entityId); entityId
);
} }
/**
* Gets the language of the caption ie name.
*
* @return language
*/
public String getLanguage() { public String getLanguage() {
return language; return language;
} }
/**
* Gets the name of the place
*
* @return name
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
* Gets the distance between place and curLatLng
*
* @param curLatLng
* @return name
*/
public Double getDistanceInDouble(LatLng curLatLng) { public Double getDistanceInDouble(LatLng curLatLng) {
return LocationUtils.calculateDistance(curLatLng.getLatitude(), curLatLng.getLongitude(), return LocationUtils.calculateDistance(curLatLng.getLatitude(), curLatLng.getLongitude(),
getLocation().getLatitude(), getLocation().getLongitude()); getLocation().getLatitude(), getLocation().getLongitude());
} }
/**
* Gets the label of the place e.g. "building", "city", etc
*
* @return label
*/
public Label getLabel() { public Label getLabel() {
return label; return label;
} }
@ -201,52 +193,28 @@ public class Place implements Parcelable {
return location; return location;
} }
/**
* Gets the long description of the place
*
* @return long description
*/
public String getLongDescription() { public String getLongDescription() {
return longDescription; return longDescription;
} }
/**
* Gets the Commons category of the place
*
* @return Commons category
*/
public String getCategory() { public String getCategory() {
return category; return category;
} }
/**
* Sets the distance of the place from the user's location
*
* @param distance distance of place from user's location
*/
public void setDistance(String distance) { public void setDistance(String distance) {
this.distance = distance; this.distance = distance;
} }
/**
* Extracts the entity id from the wikidata link
*
* @return returns the entity id if wikidata link destroyed
*/
@Nullable @Nullable
public String getWikiDataEntityId() { public String getWikiDataEntityId() {
if (this.entityID != null && !this.entityID.equals("")) { if (this.entityID != null && !this.entityID.equals("")) {
return this.entityID; return this.entityID;
} }
if (!hasWikidataLink()) { if (!hasWikidataLink()) {
Timber.d("Wikidata entity ID is null for place with sitelink %s", siteLinks.toString()); Timber.d("Wikidata entity ID is null for place with sitelink %s", siteLinks.toString());
return null; return null;
} }
//Determine entityID from link
String wikiDataLink = siteLinks.getWikidataLink().toString(); String wikiDataLink = siteLinks.getWikidataLink().toString();
if (wikiDataLink.contains("http://www.wikidata.org/entity/")) { if (wikiDataLink.contains("http://www.wikidata.org/entity/")) {
this.entityID = wikiDataLink.substring("http://www.wikidata.org/entity/".length()); this.entityID = wikiDataLink.substring("http://www.wikidata.org/entity/".length());
return this.entityID; return this.entityID;
@ -254,57 +222,26 @@ public class Place implements Parcelable {
return null; return null;
} }
/**
* Checks if the Wikidata item has a Wikipedia page associated with it
*
* @return true if there is a Wikipedia link
*/
public boolean hasWikipediaLink() { public boolean hasWikipediaLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink())); return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
} }
/**
* Checks if the Wikidata item has a Wikidata page associated with it
*
* @return true if there is a Wikidata link
*/
public boolean hasWikidataLink() { public boolean hasWikidataLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikidataLink())); return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikidataLink()));
} }
/**
* Checks if the Wikidata item has a Commons page associated with it
*
* @return true if there is a Commons link
*/
public boolean hasCommonsLink() { public boolean hasCommonsLink() {
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getCommonsLink())); return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getCommonsLink()));
} }
/**
* Sets that this place in nearby is a WikiData monument
*
* @param monument
*/
public void setMonument(final boolean monument) { public void setMonument(final boolean monument) {
isMonument = monument; isMonument = monument;
} }
/**
* Returns if this place is a WikiData monument
*
* @return
*/
public boolean isMonument() { public boolean isMonument() {
return isMonument; return isMonument;
} }
/**
* Check if we already have the exact same Place
*
* @param o Place being tested
* @return true if name and location of Place is exactly the same
*/
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof Place) { if (o instanceof Place) {
@ -346,6 +283,7 @@ public class Place implements Parcelable {
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(language); dest.writeString(language);
dest.writeString(name); dest.writeString(name);
dest.writeString(caption);
dest.writeSerializable(label); dest.writeSerializable(label);
dest.writeString(longDescription); dest.writeString(longDescription);
dest.writeParcelable(location, 0); dest.writeParcelable(location, 0);
@ -355,58 +293,26 @@ public class Place implements Parcelable {
dest.writeString(entityID); dest.writeString(entityID);
dest.writeString(exists.toString()); dest.writeString(exists.toString());
dest.writeInt(isMonument ? 1 : 0); dest.writeInt(isMonument ? 1 : 0);
dest.writeString(thumb);
} }
public static final Creator<Place> CREATOR = new Creator<Place>() {
@Override
public Place createFromParcel(Parcel in) {
return new Place(in);
}
@Override
public Place[] newArray(int size) {
return new Place[size];
}
};
public String getThumb() { public String getThumb() {
return thumb; return thumb;
} }
/**
* Sets the thumbnail URL for the place.
*
* @param thumb the thumbnail URL to set
*/
public void setThumb(String thumb) { public void setThumb(String thumb) {
this.thumb = thumb; this.thumb = thumb;
} }
/**
* Sets the label for the place.
*
* @param label the label to set
*/
public void setLabel(Label label) { public void setLabel(Label label) {
this.label = label; this.label = label;
} }
/**
* Sets the long description for the place.
*
* @param longDescription the long description to set
*/
public void setLongDescription(String longDescription) { public void setLongDescription(String longDescription) {
this.longDescription = longDescription; this.longDescription = longDescription;
} }
/**
* Sets the Commons category for the place.
*
* @param category the category to set
*/
public void setCategory(String category) { public void setCategory(String category) {
this.category = category; this.category = category;
} }
} }

View file

@ -1325,42 +1325,22 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
* *
*/ */
private fun emptyCache() { private fun emptyCache() {
// reload the map once the cache is cleared
compositeDisposable.add( compositeDisposable.add(
placesRepository!!.clearCache() placesRepository!!.clearCache()
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.andThen(Completable.fromAction { .andThen(Completable.fromAction {
// reload only the pin details, by making all loaded pins gray:
val newPlaceGroups = ArrayList<MarkerPlaceGroup>( val newPlaceGroups = ArrayList<MarkerPlaceGroup>(
NearbyController.markerLabelList.size NearbyController.markerLabelList.size
) )
for (placeGroup in NearbyController.markerLabelList) {
val place = Place(
"", "", placeGroup.place.label, "",
placeGroup.place.getLocation(), "",
placeGroup.place.siteLinks, "", placeGroup.place.exists,
placeGroup.place.entityID
)
place.setDistance(placeGroup.place.distance)
place.isMonument = placeGroup.place.isMonument
newPlaceGroups.add(
MarkerPlaceGroup(placeGroup.isBookmarked, place)
)
}
presenter!!.loadPlacesDataAsync(newPlaceGroups, scope) presenter!!.loadPlacesDataAsync(newPlaceGroups, scope)
}) })
.subscribe( .subscribe(
{ { Timber.d("Nearby Cache cleared successfully.") },
Timber.d("Nearby Cache cleared successfully.") { e -> Timber.e(e, "Failed to clear the Nearby Cache") }
},
{ throwable: Throwable? ->
Timber.e(throwable, "Failed to clear the Nearby Cache")
}
) )
) )
} }
private fun savePlacesAsKML() { private fun savePlacesAsKML() {
val savePlacesObservable = Observable val savePlacesObservable = Observable
.fromCallable { .fromCallable {
@ -1591,6 +1571,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
{ nearbyPlacesInfo: NearbyPlacesInfo -> { nearbyPlacesInfo: NearbyPlacesInfo ->
Timber.d("populatePlacesForCurrentLocation: placeList size = ${nearbyPlacesInfo.placeList?.size}")
if (nearbyPlacesInfo.placeList == null || nearbyPlacesInfo.placeList.isEmpty()) { if (nearbyPlacesInfo.placeList == null || nearbyPlacesInfo.placeList.isEmpty()) {
showErrorMessage(getString(fr.free.nrw.commons.R.string.no_nearby_places_around)) showErrorMessage(getString(fr.free.nrw.commons.R.string.no_nearby_places_around))
setProgressBarVisibility(false) setProgressBarVisibility(false)
@ -1706,6 +1687,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
nearbyPlaces: List<Place>, curLatLng: LatLng, nearbyPlaces: List<Place>, curLatLng: LatLng,
shouldUpdateSelectedMarker: Boolean shouldUpdateSelectedMarker: Boolean
) { ) {
Timber.d("Nearby Places fetched: size = ${nearbyPlaces.size}")
presenter!!.updateMapMarkers(nearbyPlaces, curLatLng, scope) presenter!!.updateMapMarkers(nearbyPlaces, curLatLng, scope)
} }
@ -2102,6 +2084,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
* @param id The integer that describes the Drawable resource * @param id The integer that describes the Drawable resource
* @return The Drawable object * @return The Drawable object
*/ */
private fun getDrawable(context: Context?, id: Int?): Drawable? { private fun getDrawable(context: Context?, id: Int?): Drawable? {
if (drawableCache == null || context == null || id == null) { if (drawableCache == null || context == null || id == null) {
return null return null
@ -2120,55 +2103,33 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
return drawableCache!![key] return drawableCache!![key]
} }
private fun convertToMarker(place: Place, isBookMarked: Boolean): Marker {
fun convertToMarker(place: Place, isBookMarked: Boolean): Marker {
val icon = getDrawable(requireContext(), getIconFor(place, isBookMarked)) val icon = getDrawable(requireContext(), getIconFor(place, isBookMarked))
val point = GeoPoint(place.location.latitude, place.location.longitude) val point = GeoPoint(place.location.latitude, place.location.longitude)
val marker = Marker(binding!!.map) val marker = Marker(binding!!.map)
marker.position = point marker.position = point
marker.icon = icon marker.icon = icon
if (place.name != "") {
marker.title = place.name // Use caption as title if available, otherwise fall back to filename
marker.snippet = if (containsParentheses(place.longDescription)) if (!place.caption.isNullOrEmpty()) {
getTextBetweenParentheses( marker.title = place.caption
place.longDescription
)
else
place.longDescription
}
marker.textLabelFontSize = 40
// anchorV is 21.707/28.0 as icon height is 28dp while the pin base is at 21.707dp from top
marker.setAnchor(Marker.ANCHOR_CENTER, 0.77525f)
marker.setOnMarkerClickListener { marker1: Marker, mapView: MapView? ->
if (clickedMarker != null) {
clickedMarker!!.closeInfoWindow()
}
clickedMarker = marker1
if (!isNetworkErrorOccurred) {
binding!!.bottomSheetDetails.dataCircularProgress.visibility =
View.VISIBLE
binding!!.bottomSheetDetails.icon.visibility = View.GONE
binding!!.bottomSheetDetails.wikiDataLl.visibility = View.GONE
if (place.name == "") {
getPlaceData(place.wikiDataEntityId, place, marker1, isBookMarked)
} else { } else {
marker.showInfoWindow() // same substring logic as before
binding!!.bottomSheetDetails.dataCircularProgress.visibility = marker.title = place.name.substring(5, place.name.lastIndexOf("."))
View.GONE
binding!!.bottomSheetDetails.icon.visibility = View.VISIBLE
binding!!.bottomSheetDetails.wikiDataLl.visibility = View.VISIBLE
passInfoToSheet(place)
hideBottomSheet()
}
bottomSheetDetailsBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED)
} else {
marker.showInfoWindow()
}
true
}
return marker
} }
// leave snippet logic unchanged (e.g. distance or description as before)
marker.snippet = place.distance
marker.textLabelFontSize = 40
marker.setAnchor(Marker.ANCHOR_CENTER, 0.77525f)
marker.setOnMarkerClickListener { marker1, mapView ->
/* ... rest of method is unchanged ... */
}
return marker
}
/** /**
* Adds multiple markers representing places to the map and handles item gestures. * Adds multiple markers representing places to the map and handles item gestures.
* *

View file

@ -19,31 +19,30 @@ object PlaceUtils {
} }
} }
/**
* Turns a Media list to a Place list by creating a new list in Place type
* @param mediaList
* @return
*/
@JvmStatic @JvmStatic
fun mediaToExplorePlace(mediaList: List<Media>): List<Place> { fun mediaToExplorePlace(mediaList: List<Media>): List<Place> {
val explorePlaceList = mutableListOf<Place>() val explorePlaceList = mutableListOf<Place>()
for (media in mediaList) { for (media in mediaList) {
explorePlaceList.add( val place = Place(
Place( media.filename ?: "",
media.filename, media.fallbackDescription ?: "",
media.fallbackDescription,
media.coordinates, media.coordinates,
media.categories.toString(), media.categories.toString(),
Sitelinks.Builder() Sitelinks.Builder()
.setCommonsLink(media.pageTitle.canonicalUri) .setCommonsLink(media.pageTitle?.canonicalUri ?: "")
.setWikipediaLink("") // we don't necessarily have them, can be fetched later .setWikipediaLink("")
.setWikidataLink("") // we don't necessarily have them, can be fetched later .setWikidataLink("")
.build(), .build(),
media.imageUrl, media.imageUrl ?: "",
media.thumbUrl, media.thumbUrl ?: "",
"" ""
) )
) // Set caption, with fallback
place.caption = media.captions?.values?.firstOrNull()
?: media.filename?.removePrefix("File:")?.replace('_', ' ')
?: "Unknown"
explorePlaceList.add(place)
} }
return explorePlaceList return explorePlaceList
} }