diff --git a/OsmAnd-java/src/main/java/net/osmand/wiki/WikiCoreHelper.java b/OsmAnd-java/src/main/java/net/osmand/wiki/WikiCoreHelper.java index a9ad141c2ab..c3eacf9f45e 100644 --- a/OsmAnd-java/src/main/java/net/osmand/wiki/WikiCoreHelper.java +++ b/OsmAnd-java/src/main/java/net/osmand/wiki/WikiCoreHelper.java @@ -9,6 +9,7 @@ import net.osmand.data.Amenity; import net.osmand.data.QuadRect; import net.osmand.osm.io.NetworkUtils; +import net.osmand.shared.data.KQuadRect; import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; @@ -46,12 +47,12 @@ public class WikiCoreHelper { private static final List IMAGE_EXTENSIONS = new ArrayList<>(Arrays.asList(".jpeg", ".jpg", ".png", ".gif")); - public static List getExploreImageList(QuadRect mapRect, int zoom, String lang) { + public static List getExploreImageList(KQuadRect mapRect, int zoom, String lang) { List wikiImages = new ArrayList<>(); StringBuilder url = new StringBuilder(); String baseApiActionUrl = OSMAND_SEARCH_ENDPOINT + GET_WIKI_DATA_ACTION; - String northWest = String.format(Locale.US, "%f,%f", mapRect.top, mapRect.left); - String southEast = String.format(Locale.US, "%f,%f", mapRect.bottom, mapRect.right); + String northWest = String.format(Locale.US, "%f,%f", mapRect.getTop(), mapRect.getLeft()); + String southEast = String.format(Locale.US, "%f,%f", mapRect.getBottom(), mapRect.getRight()); url.append(baseApiActionUrl); try { url.append(String.format(Locale.US, "northWest=%s", URLEncoder.encode(northWest, "UTF-8"))); @@ -62,11 +63,12 @@ public static List getExploreImageList(QuadRect mapRect, i url.append("&"); url.append(String.format(Locale.US, "lang=%s", lang)); url.append("&"); - url.append(String.format(Locale.US, "filters=%s", "tourism%2Cleisure%2Centertainment")); + url.append(String.format(Locale.US, "filters=")); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } + LOG.debug("Download images " + url.toString()); getNearbyImagesOsmAndAPIRequest(url.toString(), wikiImages); return wikiImages; } @@ -336,6 +338,8 @@ public static class WikiDataProperties { private String wikiLang; public String wikiDesc; public Long osmid; + + public Double elo; private String osmtype; } diff --git a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/data/KQuadRect.kt b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/data/KQuadRect.kt index 7ae71026d9d..66277b7444a 100644 --- a/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/data/KQuadRect.kt +++ b/OsmAnd-shared/src/commonMain/kotlin/net/osmand/shared/data/KQuadRect.kt @@ -58,6 +58,10 @@ class KQuadRect { return contains(box.left, box.top, box.right, box.bottom) } + fun contains(point: KLatLon): Boolean { + return point.longitude in left..right && point.latitude in bottom..top + } + companion object { fun intersects(a: KQuadRect, b: KQuadRect): Boolean { return kotlin.math.min(a.left, a.right) <= kotlin.math.max(b.left, b.right) @@ -109,6 +113,24 @@ class KQuadRect { return left == 0.0 && right == 0.0 && top == 0.0 && bottom == 0.0 } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is KQuadRect) return false + + return left == other.left && + right == other.right && + top == other.top && + bottom == other.bottom + } + + override fun hashCode(): Int { + var result = left.hashCode() + result = 31 * result + right.hashCode() + result = 31 * result + top.hashCode() + result = 31 * result + bottom.hashCode() + return result + } + override fun toString(): String { return "[${left.toFloat()},${top.toFloat()} - ${right.toFloat()},${bottom.toFloat()}]" } diff --git a/OsmAnd/res/layout/search_nearby_item.xml b/OsmAnd/res/layout/search_nearby_item.xml index 9846d713a2e..fa57cc42fa0 100644 --- a/OsmAnd/res/layout/search_nearby_item.xml +++ b/OsmAnd/res/layout/search_nearby_item.xml @@ -46,6 +46,8 @@ android:maxLines="1" android:ellipsize="end" tools:text="Monument" /> + + \ No newline at end of file diff --git a/OsmAnd/res/layout/search_nearby_item_vertical.xml b/OsmAnd/res/layout/search_nearby_item_vertical.xml index fe5aa952023..0153e62ccc8 100644 --- a/OsmAnd/res/layout/search_nearby_item_vertical.xml +++ b/OsmAnd/res/layout/search_nearby_item_vertical.xml @@ -48,7 +48,7 @@ android:id="@+id/item_icon" android:layout_width="16dp" android:layout_height="16dp" - android:layout_gravity="center_vertical|top" + android:layout_gravity="center_vertical" android:layout_marginEnd="6dp" /> + + + + + + + + diff --git a/OsmAnd/src/net/osmand/data/NearbyPlacePoint.java b/OsmAnd/src/net/osmand/data/ExploreTopPlacePoint.java similarity index 90% rename from OsmAnd/src/net/osmand/data/NearbyPlacePoint.java rename to OsmAnd/src/net/osmand/data/ExploreTopPlacePoint.java index 2da811912ec..669baf6b746 100644 --- a/OsmAnd/src/net/osmand/data/NearbyPlacePoint.java +++ b/OsmAnd/src/net/osmand/data/ExploreTopPlacePoint.java @@ -14,11 +14,12 @@ import java.io.Serializable; -public class NearbyPlacePoint implements Serializable, LocationPoint { +public class ExploreTopPlacePoint implements Serializable, LocationPoint { private static final long serialVersionUID = 829654300829771466L; public static final BackgroundType DEFAULT_BACKGROUND_TYPE = BackgroundType.CIRCLE; + private static final double DEFAULT_ELO = 900; private final long id; private final String photoTitle; private final String wikiTitle; @@ -29,10 +30,12 @@ public class NearbyPlacePoint implements Serializable, LocationPoint { private final String imageStubUrl; private final double latitude; private final double longitude; + + private final double elo; @Nullable private Bitmap imageBitmap; - public NearbyPlacePoint(OsmandApiFeatureData featureData) { + public ExploreTopPlacePoint(OsmandApiFeatureData featureData) { this.id = featureData.properties.osmid; WikiImage wikiIMage = WikiCoreHelper.getImageData(featureData.properties.photoTitle); this.iconUrl = wikiIMage == null ? "" : wikiIMage.getImageIconUrl(); @@ -44,6 +47,7 @@ public NearbyPlacePoint(OsmandApiFeatureData featureData) { this.poitype = featureData.properties.poitype; this.wikiTitle = featureData.properties.wikiTitle; this.wikiDesc = featureData.properties.wikiDesc; + this.elo = featureData.properties.elo != null ? featureData.properties.elo : DEFAULT_ELO; } public long getId() { @@ -85,6 +89,10 @@ public PointDescription getPointDescription(@NonNull Context ctx) { return new PointDescription(PointDescription.POINT_TYPE_NEARBY_PLACE, wikiDesc); } + public double getElo() { + return elo; + } + public double getLatitude() { return latitude; } @@ -136,7 +144,7 @@ public boolean equals(Object o) { return false; } - NearbyPlacePoint point = (NearbyPlacePoint) o; + ExploreTopPlacePoint point = (ExploreTopPlacePoint) o; if (!Algorithms.stringsEqual(photoTitle, point.photoTitle)) { return false; } diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index 639568a1110..a960045bb08 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -37,6 +37,7 @@ import net.osmand.plus.configmap.tracks.TrackSortModesHelper; import net.osmand.plus.download.local.LocalIndexHelper; import net.osmand.plus.download.local.LocalItem; +import net.osmand.plus.exploreplaces.ExplorePlacesProviderJava; import net.osmand.plus.feedback.AnalyticsHelper; import net.osmand.plus.feedback.FeedbackHelper; import net.osmand.plus.helpers.*; @@ -48,7 +49,6 @@ import net.osmand.plus.mapmarkers.MapMarkersDbHelper; import net.osmand.plus.mapmarkers.MapMarkersHelper; import net.osmand.plus.myplaces.favorites.FavouritesHelper; -import net.osmand.plus.nearbyplaces.NearbyPlacesHelper; import net.osmand.plus.notifications.NotificationHelper; import net.osmand.plus.onlinerouting.OnlineRoutingHelper; import net.osmand.plus.plugins.PluginsHelper; @@ -337,9 +337,8 @@ public void onCreateApplication() { app.routeLayersHelper = startupInit(new RouteLayersHelper(app), RouteLayersHelper.class); app.model3dHelper = startupInit(new Model3dHelper(app), Model3dHelper.class); app.trackSortModesHelper = startupInit(new TrackSortModesHelper(app), TrackSortModesHelper.class); - + app.explorePlacesProvider = startupInit(new ExplorePlacesProviderJava(app), ExplorePlacesProviderJava.class); initOpeningHoursParser(); - NearbyPlacesHelper.INSTANCE.init(app); } private void initOpeningHoursParser() { diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index b6872a9124e..3177d7b91af 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -56,6 +56,8 @@ import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.download.DownloadService; import net.osmand.plus.download.IndexItem; +import net.osmand.plus.exploreplaces.ExplorePlacesProvider; +import net.osmand.plus.exploreplaces.ExplorePlacesProviderJava; import net.osmand.plus.feedback.AnalyticsHelper; import net.osmand.plus.feedback.FeedbackHelper; import net.osmand.plus.feedback.RateUsHelper; @@ -208,6 +210,7 @@ public class OsmandApplication extends MultiDexApplication { RouteLayersHelper routeLayersHelper; Model3dHelper model3dHelper; TrackSortModesHelper trackSortModesHelper; + ExplorePlacesProviderJava explorePlacesProvider; private final Map customRoutingConfigs = new ConcurrentHashMap<>(); private File externalStorageDirectory; @@ -618,6 +621,10 @@ public AverageSpeedComputer getAverageSpeedComputer() { return averageSpeedComputer; } + public ExplorePlacesProvider getExplorePlacesProvider() { + return explorePlacesProvider; + } + @NonNull public AverageGlideComputer getAverageGlideComputer() { return averageGlideComputer; diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 8c717111bb9..8c1d4748f77 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -88,7 +88,7 @@ import net.osmand.plus.measurementtool.GpxData; import net.osmand.plus.measurementtool.MeasurementEditingContext; import net.osmand.plus.measurementtool.MeasurementToolFragment; -import net.osmand.plus.nearbyplaces.NearbyPlacesFragment; +import net.osmand.plus.exploreplaces.ExplorePlacesFragment; import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.plugins.OsmandPlugin; import net.osmand.plus.plugins.PluginsHelper; @@ -518,7 +518,7 @@ public void onBackPressed() { if (backStackEntryCount == 0 && launchPrevActivityIntent()) { return; } - NearbyPlacesFragment nearbyPlacesFragment = fragmentsHelper.getNearbyPlacesFragment(); + ExplorePlacesFragment nearbyPlacesFragment = fragmentsHelper.getNearbyPlacesFragment(); if(nearbyPlacesFragment != null && nearbyPlacesFragment.onBackPress()) { return; } diff --git a/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesFragment.kt b/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesFragment.kt similarity index 73% rename from OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesFragment.kt rename to OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesFragment.kt index d4a1478b0e8..8ac54c831f5 100644 --- a/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesFragment.kt +++ b/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesFragment.kt @@ -1,4 +1,4 @@ -package net.osmand.plus.nearbyplaces +package net.osmand.plus.exploreplaces import android.os.Bundle import android.view.LayoutInflater @@ -11,21 +11,21 @@ import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import net.osmand.PlatformUtil -import net.osmand.data.NearbyPlacePoint +import net.osmand.data.ExploreTopPlacePoint +import net.osmand.data.QuadRect import net.osmand.plus.R import net.osmand.plus.activities.MapActivity import net.osmand.plus.base.BaseOsmAndFragment import net.osmand.plus.helpers.AndroidUiHelper -import net.osmand.plus.nearbyplaces.NearbyPlacesHelper.getDataCollection -import net.osmand.plus.nearbyplaces.NearbyPlacesHelper.showPointInContextMenu import net.osmand.plus.search.NearbyPlacesAdapter import net.osmand.plus.utils.AndroidUtils import net.osmand.plus.utils.ColorUtilities import org.apache.commons.logging.Log -class NearbyPlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyItemClickListener { +class ExplorePlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyItemClickListener { + private lateinit var visiblePlacesRect: QuadRect private val log: Log = PlatformUtil.getLog( - NearbyPlacesFragment::class.java) + ExplorePlacesFragment::class.java) private lateinit var verticalNearbyAdapter: NearbyPlacesAdapter @@ -44,6 +44,13 @@ class NearbyPlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyIte override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) AndroidUtils.addStatusBarPadding21v(requireActivity(), view) + arguments?.let { + val left = it.getDouble("left") + val right = it.getDouble("right") + val top = it.getDouble("top") + val bottom = it.getDouble("bottom") + visiblePlacesRect = QuadRect(left, top, right, bottom) // Create QuadRect + } setupShowAll(view) setupToolBar(view) setupVerticalNearbyList(view) @@ -57,7 +64,7 @@ class NearbyPlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyIte mapActivity?.contextMenu?.hideMenus() } else { activity?.supportFragmentManager?.beginTransaction() - ?.show(this@NearbyPlacesFragment) + ?.show(this@ExplorePlacesFragment) ?.commit() } return true @@ -65,17 +72,19 @@ class NearbyPlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyIte val quickSearchFragment = mapActivity?.fragmentsHelper?.quickSearchDialogFragment quickSearchFragment?.show() activity?.supportFragmentManager?.beginTransaction() - ?.remove(this@NearbyPlacesFragment) + ?.remove(this@ExplorePlacesFragment) ?.commit() return true } } private fun setupShowAll(view: View) { + view.findViewById(R.id.location_icon) .setImageDrawable(uiUtilities.getIcon(R.drawable.ic_action_marker_dark, nightMode)) + view.findViewById(R.id.show_on_map).setOnClickListener { - app.osmandMap.mapLayers.nearbyPlacesLayer.setCustomMapObjects(getDataCollection()) + app.osmandMap.mapLayers.explorePlacesLayer.enableLayer(true) hide() } } @@ -104,7 +113,7 @@ class NearbyPlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyIte private fun setupVerticalNearbyList(view: View) { val verticalNearbyList = view.findViewById(R.id.vertical_nearby_list) - val nearbyData = NearbyPlacesHelper.getDataCollection() + val nearbyData = app.explorePlacesProvider.getDataCollection(visiblePlacesRect) verticalNearbyAdapter = NearbyPlacesAdapter(app, nearbyData, true, this) verticalNearbyList.layoutManager = LinearLayoutManager(requireContext()) verticalNearbyList.adapter = verticalNearbyAdapter @@ -122,19 +131,27 @@ class NearbyPlacesFragment : BaseOsmAndFragment(), NearbyPlacesAdapter.NearbyIte } companion object { - val TAG: String = NearbyPlacesFragment::class.java.simpleName - fun showInstance(manager: FragmentManager) { + val TAG: String = ExplorePlacesFragment::class.java.simpleName + fun showInstance(manager: FragmentManager, visiblePlacesRect: QuadRect) { if (AndroidUtils.isFragmentCanBeAdded(manager, TAG)) { + val fragment = ExplorePlacesFragment() + val bundle = Bundle() + bundle.putDouble("left", visiblePlacesRect.left) + bundle.putDouble("right", visiblePlacesRect.right) + bundle.putDouble("top", visiblePlacesRect.top) + bundle.putDouble("bottom", visiblePlacesRect.bottom) + fragment.arguments = bundle manager.beginTransaction() - .replace(R.id.fragmentContainer, NearbyPlacesFragment(), TAG) + .replace(R.id.fragmentContainer, fragment, TAG) .commitAllowingStateLoss() } } + } - override fun onNearbyItemClicked(item: NearbyPlacePoint) { + override fun onNearbyItemClicked(item: ExploreTopPlacePoint) { mapActivity?.let { - showPointInContextMenu(it, item) + app.explorePlacesProvider.showPointInContextMenu(it, item) hide() } } diff --git a/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesProvider.java b/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesProvider.java new file mode 100644 index 00000000000..d1847f96a7b --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesProvider.java @@ -0,0 +1,39 @@ +package net.osmand.plus.exploreplaces; + +import net.osmand.data.Amenity; +import net.osmand.data.LatLon; +import net.osmand.data.ExploreTopPlacePoint; +import net.osmand.data.QuadRect; +import net.osmand.plus.activities.MapActivity; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface ExplorePlacesProvider { + + public final int MAX_LEVEL_ZOOM_CACHE = 13; + @NotNull List getDataCollection(QuadRect mapRect); + + @NotNull List getDataCollection(QuadRect mapRect, int limit); + + void showPointInContextMenu(@NotNull MapActivity it, @NotNull ExploreTopPlacePoint item); + + void addListener(ExplorePlacesListener listener); + + void removeListener(ExplorePlacesListener listener); + + Amenity getAmenity(LatLon latLon, long id); + + boolean isLoading(); + + // data version is increased once new data is downloaded + int getDataVersion(); + + interface ExplorePlacesListener { + // once new data is downloaded data version is increased + void onNewExplorePlacesDownloaded(); + + default void onPartialExplorePlacesDownloaded() {} + } +} diff --git a/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesProviderJava.java b/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesProviderJava.java new file mode 100644 index 00000000000..abe53b5b85a --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/exploreplaces/ExplorePlacesProviderJava.java @@ -0,0 +1,270 @@ +package net.osmand.plus.exploreplaces; + +import android.annotation.SuppressLint; + +import androidx.annotation.NonNull; + +import net.osmand.ResultMatcher; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.binary.ObfConstants; +import net.osmand.data.Amenity; +import net.osmand.data.LatLon; +import net.osmand.data.ExploreTopPlacePoint; +import net.osmand.data.QuadRect; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.search.GetNearbyPlacesImagesTask; +import net.osmand.search.core.SearchCoreFactory; +import net.osmand.shared.data.KQuadRect; +import net.osmand.util.Algorithms; +import net.osmand.util.CollectionUtils; +import net.osmand.util.MapUtils; +import net.osmand.wiki.WikiCoreHelper.OsmandApiFeatureData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +// TODO use gzip in loading +// TODO errors shouldn'go with empty response "" into cache! +// TODO remove checks poi type subtype null +// TODO display all data downloaded even if maps are not loaded +// TODO: why recreate provider when new points are loaded? that causes blinking +// TODO: scheduleImageRefreshes in layer is incorrect it starts downloading all images and stops interacting +// TODO images shouldn't be queried if they are not visible in all lists! size doesn't matter ! +// TODO show on map close button is not visible +// TODO layer sometimes becomes non-interactive - MAP FPS drops +// TODO Context menu doesn't work correctly and duplicates actual POI +// TODO compass is not rotating +// Extra: display new categories from web +public class ExplorePlacesProviderJava implements ExplorePlacesProvider { + + private static final int DEFAULT_LIMIT_POINTS = 200; + private static final int NEARBY_MIN_RADIUS = 50; + + + private static final int MAX_TILES_PER_QUAD_RECT = 12; + private static final double LOAD_ALL_TINY_RECT = 0.5; + + private OsmandApplication app; + private volatile int startedTasks = 0; + private volatile int finishedTasks = 0; + + private final Set loadingTiles = new HashSet<>(); // Track tiles being loaded + + public ExplorePlacesProviderJava(OsmandApplication app) { + this.app = app; + } + + private List listeners = Collections.emptyList(); + + public void addListener(ExplorePlacesListener listener) { + if (!listeners.contains(listener)) { + listeners = CollectionUtils.addToList(listeners, listener); + } + } + + public void removeListener(ExplorePlacesListener listener) { + listeners = CollectionUtils.removeFromList(listeners, listener); + } + + /** + * Notify listeners about new data being downloaded. + * + * @param isPartial Whether the notification is for a partial update or a full update. + */ + public void notifyListeners(boolean isPartial) { + app.runInUIThread(new Runnable() { + @Override + public void run() { + for (ExplorePlacesListener listener : listeners) { + if (isPartial) { + listener.onPartialExplorePlacesDownloaded(); // Notify for partial updates + } else { + listener.onNewExplorePlacesDownloaded(); // Notify for full updates + } + } + } + }); + } + + private String getLang() { + String preferredLang = app.getSettings().MAP_PREFERRED_LOCALE.get(); + if (Algorithms.isEmpty(preferredLang)) { + preferredLang = app.getLanguage(); + } + return preferredLang; + } + + public List getDataCollection(QuadRect rect) { + return getDataCollection(rect, DEFAULT_LIMIT_POINTS); + } + + public List getDataCollection(QuadRect rect, int limit) { + if (rect == null) { + return Collections.emptyList(); + } + // Calculate the initial zoom level + int zoom = MAX_LEVEL_ZOOM_CACHE; + while (zoom >= 0) { + int tileWidth = (int) (MapUtils.getTileNumberX(zoom, rect.right)) - + ((int) MapUtils.getTileNumberX(zoom, rect.left)) + 1; + int tileHeight = (int) (MapUtils.getTileNumberY(zoom, rect.bottom)) - + ((int) MapUtils.getTileNumberY(zoom, rect.top)) + 1; + if (tileWidth * tileHeight <= MAX_TILES_PER_QUAD_RECT) { + break; + } + zoom -= 3; + } + zoom = Math.max(zoom, 1); + // Calculate tile bounds for the QuadRect as float values + float minTileX = (float) MapUtils.getTileNumberX(zoom, rect.left); + float maxTileX = (float) MapUtils.getTileNumberX(zoom, rect.right); + float minTileY = (float) MapUtils.getTileNumberY(zoom, rect.top); + float maxTileY = (float) MapUtils.getTileNumberY(zoom, rect.bottom); + boolean loadAll = zoom == MAX_LEVEL_ZOOM_CACHE && + Math.abs(maxTileX - minTileX) <= LOAD_ALL_TINY_RECT || Math.abs(maxTileY - minTileY) <= LOAD_ALL_TINY_RECT; + + // Fetch data for all tiles within the bounds + PlacesDatabaseHelper dbHelper = new PlacesDatabaseHelper(app); + List filteredPoints = new ArrayList<>(); + Set uniqueIds = new HashSet<>(); // Use a Set to track unique IDs + final String queryLang = getLang(); + + // Iterate over the tiles and load data + for (int tileX = (int) minTileX; tileX <= (int) maxTileX; tileX++) { + for (int tileY = (int) minTileY; tileY <= (int) maxTileY; tileY++) { + if (!dbHelper.isDataExpired(zoom, tileX, tileY, queryLang)) { + List places = dbHelper.getPlaces(zoom, tileX, tileY, queryLang); + for (OsmandApiFeatureData item : places) { + // TODO remove checks poi type subtype null + if (Algorithms.isEmpty(item.properties.photoTitle) + || item.properties.poitype == null || item.properties.poisubtype == null) { + continue; + } + ExploreTopPlacePoint point = new ExploreTopPlacePoint(item); + double lat = point.getLatitude(); + double lon = point.getLongitude(); + if ((rect.contains(lon, lat, lon, lat) || loadAll) && uniqueIds.add(point.getId())) { + filteredPoints.add(point); + } + } + } else { + loadTile(zoom, tileX, tileY, queryLang, dbHelper); + } + } + } + + // Sort the points by Elo in descending order + filteredPoints.sort((p1, p2) -> Double.compare(p2.getElo(), p1.getElo())); + + // Limit the number of points + if (filteredPoints.size() > limit) { + filteredPoints = filteredPoints.subList(0, limit); + } + + return filteredPoints; + } + + @SuppressLint("DefaultLocale") + private void loadTile(int zoom, int tileX, int tileY, String queryLang, PlacesDatabaseHelper dbHelper) { + synchronized (loadingTiles) { + String tileKey = zoom + "_" + tileX + "_" + tileY; + if (loadingTiles.contains(tileKey)) { + return; + } + loadingTiles.add(tileKey); + } + double left = MapUtils.getLongitudeFromTile(zoom, tileX); + double right = MapUtils.getLongitudeFromTile(zoom, tileX + 1); + double top = MapUtils.getLatitudeFromTile(zoom, tileY); + double bottom = MapUtils.getLatitudeFromTile(zoom, tileY + 1); + + KQuadRect tileRect = new KQuadRect(left, top, right, bottom); + // Increment the task counter + synchronized (this) { + startedTasks++; + } + // Create and execute a task for the current tile + int ftileX = tileX; + int ftileY = tileY; + new GetNearbyPlacesImagesTask( + app, + tileRect, zoom, + queryLang, new GetNearbyPlacesImagesTask.GetImageCardsListener() { + @Override + public void onTaskStarted() { + } + + @Override + public void onFinish(@NonNull List result) { + synchronized (ExplorePlacesProviderJava.this) { + finishedTasks++; // Increment the finished task counter + notifyListeners(startedTasks != finishedTasks); + } + if (result != null) { + // Store the data in the database for the current tile + dbHelper.insertPlaces(zoom, ftileX, ftileY, queryLang, result); + } + // Remove the tile from the loading set + String tileKey = zoom + "_" + ftileX + "_" + ftileY; + synchronized (loadingTiles) { + loadingTiles.remove(tileKey); + } + } + }).execute(); + } + + public void showPointInContextMenu(MapActivity mapActivity, ExploreTopPlacePoint point) { + double latitude = point.getLatitude(); + double longitude = point.getLongitude(); + app.getSettings().setMapLocationToShow( + latitude, + longitude, + SearchCoreFactory.PREFERRED_NEARBY_POINT_ZOOM, + point.getPointDescription(app), + true, + point); + MapActivity.launchMapActivityMoveToTop(mapActivity); + } + + public Amenity getAmenity(LatLon latLon, long osmId) { + final Amenity[] foundAmenity = new Amenity[]{null}; + int radius = NEARBY_MIN_RADIUS; + QuadRect rect = MapUtils.calculateLatLonBbox(latLon.getLatitude(), latLon.getLongitude(), radius); + app.getResourceManager().searchAmenities( + BinaryMapIndexReader.ACCEPT_ALL_POI_TYPE_FILTER, + rect.top, rect.left, rect.bottom, rect.right, + -1, true, + new ResultMatcher() { + @Override + public boolean publish(Amenity amenity) { + long id = ObfConstants.getOsmObjectId(amenity); + if (osmId == id) { + foundAmenity[0] = amenity; + return true; + } + return false; + } + + @Override + public boolean isCancelled() { + return foundAmenity[0] != null; + } + }); + return foundAmenity[0]; + } + + @Override + public boolean isLoading() { + return startedTasks > finishedTasks; // Return true if any task is running + } + + @Override + public int getDataVersion() { + // data version is increased once new data is downloaded + return finishedTasks; + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/exploreplaces/PlacesDatabaseHelper.java b/OsmAnd/src/net/osmand/plus/exploreplaces/PlacesDatabaseHelper.java new file mode 100644 index 00000000000..34fbeaaafcc --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/exploreplaces/PlacesDatabaseHelper.java @@ -0,0 +1,110 @@ +package net.osmand.plus.exploreplaces; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import net.osmand.wiki.WikiCoreHelper; +import net.osmand.wiki.WikiCoreHelper.OsmandApiFeatureData; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class PlacesDatabaseHelper extends SQLiteOpenHelper { + + private static final String DATABASE_NAME = "places-1.db"; + + private static final long DATA_EXPIRATION_TIME = TimeUnit.DAYS.toMillis(30); // 1 month + + private static final int DATABASE_VERSION = 2; // Incremented version for schema changes + private static final String TABLE_PLACES = "places"; + private static final String COLUMN_ZOOM = "zoom"; + private static final String COLUMN_TILE_X = "tileX"; + private static final String COLUMN_TILE_Y = "tileY"; + private static final String COLUMN_LANG = "lang"; + private static final String COLUMN_DATA = "data"; + private static final String COLUMN_TIMESTAMP = "timestamp"; + + private static final String CREATE_TABLE_PLACES = "CREATE TABLE " + TABLE_PLACES + "(" + + COLUMN_ZOOM + " INTEGER," + + COLUMN_TILE_X + " INTEGER," + + COLUMN_TILE_Y + " INTEGER," + + COLUMN_LANG + " TEXT," + + COLUMN_DATA + " TEXT," + + COLUMN_TIMESTAMP + " INTEGER," + + "PRIMARY KEY (" + COLUMN_ZOOM + ", " + COLUMN_TILE_X + ", " + COLUMN_TILE_Y + ", " + COLUMN_LANG + ")" + + ")"; + + private Gson gson = new Gson(); + + public PlacesDatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(CREATE_TABLE_PLACES); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + if (oldVersion < 2) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_PLACES); + onCreate(db); + } + } + + public void insertPlaces(int zoom, int tileX, int tileY, String lang, List places) { + SQLiteDatabase db = this.getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(COLUMN_ZOOM, zoom); + values.put(COLUMN_TILE_X, tileX); + values.put(COLUMN_TILE_Y, tileY); + values.put(COLUMN_LANG, lang); + values.put(COLUMN_DATA, gson.toJson(places)); + values.put(COLUMN_TIMESTAMP, System.currentTimeMillis()); + db.insertWithOnConflict(TABLE_PLACES, null, values, SQLiteDatabase.CONFLICT_REPLACE); + } + + public List getPlaces(int zoom, int tileX, int tileY, String lang) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.query(TABLE_PLACES, new String[]{COLUMN_DATA, COLUMN_TIMESTAMP}, + COLUMN_ZOOM + "=? AND " + COLUMN_TILE_X + "=? AND " + COLUMN_TILE_Y + "=? AND " + COLUMN_LANG + "=?", + new String[]{String.valueOf(zoom), String.valueOf(tileX), String.valueOf(tileY), lang}, + null, null, null); + + List places = new ArrayList<>(); + if (cursor.moveToFirst()) { + int c = cursor.getColumnIndex(COLUMN_DATA); + String json = cursor.getString(c); + int t = cursor.getColumnIndex(COLUMN_TIMESTAMP); + long timestamp = cursor.getLong(t); + places = gson.fromJson(json, new TypeToken>(){}.getType()); + } + cursor.close(); + return places; + } + + public boolean isDataExpired(int zoom, int tileX, int tileY, String lang) { + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.query(TABLE_PLACES, new String[]{COLUMN_TIMESTAMP}, + COLUMN_ZOOM + "=? AND " + COLUMN_TILE_X + "=? AND " + COLUMN_TILE_Y + "=? AND " + COLUMN_LANG + "=?", + new String[]{String.valueOf(zoom), String.valueOf(tileX), String.valueOf(tileY), lang}, + null, null, null); + + int tc = cursor.getColumnIndex(COLUMN_TIMESTAMP); + if (cursor.moveToFirst()) { + long timestamp = cursor.getLong(tc); + long currentTime = System.currentTimeMillis(); + cursor.close(); + return (currentTime - timestamp) > DATA_EXPIRATION_TIME; // 1 month expiration + } + cursor.close(); + return true; // Data is expired if it doesn't exist + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/helpers/MapFragmentsHelper.java b/OsmAnd/src/net/osmand/plus/helpers/MapFragmentsHelper.java index 2ac8532bf25..c37fd098027 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/MapFragmentsHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/MapFragmentsHelper.java @@ -35,7 +35,7 @@ import net.osmand.plus.measurementtool.GpxApproximationFragment; import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.SnapTrackWarningFragment; -import net.osmand.plus.nearbyplaces.NearbyPlacesFragment; +import net.osmand.plus.exploreplaces.ExplorePlacesFragment; import net.osmand.plus.plugins.rastermaps.DownloadTilesFragment; import net.osmand.plus.plugins.weather.dialogs.WeatherForecastFragment; import net.osmand.plus.routepreparationmenu.ChooseRouteFragment; @@ -149,8 +149,8 @@ public QuickSearchDialogFragment getQuickSearchDialogFragment() { } @Nullable - public NearbyPlacesFragment getNearbyPlacesFragment() { - return getFragment(NearbyPlacesFragment.Companion.getTAG()); + public ExplorePlacesFragment getNearbyPlacesFragment() { + return getFragment(ExplorePlacesFragment.Companion.getTAG()); } @Nullable diff --git a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuController.java b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuController.java index 2d105200022..b61b93e27f1 100644 --- a/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuController.java +++ b/OsmAnd/src/net/osmand/plus/mapcontextmenu/MenuController.java @@ -43,7 +43,6 @@ import net.osmand.plus.mapcontextmenu.controllers.SelectedGpxMenuController.SelectedGpxPoint; import net.osmand.plus.mapcontextmenu.other.ShareMenu; import net.osmand.plus.mapmarkers.MapMarker; -import net.osmand.plus.nearbyplaces.NearbyPlacesHelper; import net.osmand.plus.plugins.OsmandPlugin; import net.osmand.plus.plugins.PluginsHelper; import net.osmand.plus.plugins.aistracker.AisObject; @@ -189,8 +188,9 @@ public static MenuController getMenuController(@NonNull MapActivity mapActivity, } } else if (object instanceof SearchHistoryHelper.HistoryEntry) { menuController = new HistoryMenuController(mapActivity, pointDescription, (SearchHistoryHelper.HistoryEntry) object); - } else if (object instanceof NearbyPlacePoint point) { - Amenity amenity = NearbyPlacesHelper.INSTANCE.getAmenity(new LatLon(point.getLatitude(), point.getLongitude()), point.getId()); + } else if (object instanceof ExploreTopPlacePoint point) { + Amenity amenity = mapActivity.getMyApplication().getExplorePlacesProvider().getAmenity( + new LatLon(point.getLatitude(), point.getLongitude()), point.getId()); if (amenity != null) { menuController = new AmenityMenuController(mapActivity, pointDescription, amenity); } diff --git a/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesHelper.kt b/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesHelper.kt deleted file mode 100644 index d163bad3a75..00000000000 --- a/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesHelper.kt +++ /dev/null @@ -1,149 +0,0 @@ -package net.osmand.plus.nearbyplaces - -import com.squareup.picasso.Picasso -import net.osmand.ResultMatcher -import net.osmand.binary.BinaryMapIndexReader -import net.osmand.binary.ObfConstants -import net.osmand.data.Amenity -import net.osmand.data.LatLon -import net.osmand.data.NearbyPlacePoint -import net.osmand.data.QuadRect -import net.osmand.plus.OsmandApplication -import net.osmand.plus.activities.MapActivity -import net.osmand.plus.search.GetNearbyPlacesImagesTask -import net.osmand.plus.views.layers.ContextMenuLayer -import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider -import net.osmand.search.core.SearchCoreFactory -import net.osmand.util.Algorithms -import net.osmand.util.CollectionUtils -import net.osmand.util.MapUtils -import net.osmand.wiki.WikiCoreHelper.OsmandApiFeatureData -import java.util.Collections -import kotlin.math.min - -object NearbyPlacesHelper { - private lateinit var app: OsmandApplication - private var lastModifiedTime: Long = 0 - private const val PLACES_LIMIT = 50 - private const val NEARBY_MIN_RADIUS: Int = 50 - - private var prevMapRect: QuadRect = QuadRect() - private var prevZoom = 0 - private var prevLang = "" - - fun init(app: OsmandApplication) { - this.app = app - } - - private var listeners: List = Collections.emptyList() - private var dataCollection: List? = null - - private val loadNearbyPlacesListener: GetNearbyPlacesImagesTask.GetImageCardsListener = - object : GetNearbyPlacesImagesTask.GetImageCardsListener { - override fun onTaskStarted() { - } - - override fun onFinish(result: List) { - dataCollection = result.filter { !Algorithms.isEmpty(it.properties.photoTitle) } - .map { NearbyPlacePoint(it) } - dataCollection?.let { - val newListSize = min(it.size, PLACES_LIMIT) - dataCollection = it.subList(0, newListSize) - } - dataCollection?.let { - for (point in it) { - Picasso.get() - .load(point.iconUrl) - .fetch() - } - } - updateLastModifiedTime() - notifyListeners() - } - } - - fun addListener(listener: NearbyPlacesListener) { - listeners = CollectionUtils.addToList(listeners, listener) - } - - fun removeListener(listener: NearbyPlacesListener) { - listeners = CollectionUtils.removeFromList(listeners, listener) - } - - fun notifyListeners() { - app.runInUIThread { - for (listener in listeners) { - listener.onNearbyPlacesUpdated() - } - } - } - - fun getDataCollection(): List { - return this.dataCollection ?: Collections.emptyList() - } - - fun startLoadingNearestPhotos() { - val mapView = app.osmandMap.mapView - val mapRect = mapView.currentRotatedTileBox.latLonBounds - var preferredLang = app.settings.MAP_PREFERRED_LOCALE.get() - if (Algorithms.isEmpty(preferredLang)) { - preferredLang = app.language - } - if (prevMapRect != mapRect || prevZoom != mapView.zoom || prevLang != preferredLang) { - prevMapRect = mapRect - prevZoom = mapView.zoom - prevLang = preferredLang - GetNearbyPlacesImagesTask( - app, - prevMapRect, prevZoom, - prevLang, loadNearbyPlacesListener).execute() - } else { - notifyListeners() - } - } - - private fun updateLastModifiedTime() { - lastModifiedTime = System.currentTimeMillis() - } - - fun getLastModifiedTime(): Long { - return lastModifiedTime - } - - fun showPointInContextMenu(mapActivity: MapActivity, point: NearbyPlacePoint) { - val latitude: Double = point.latitude - val longitude: Double = point.longitude - app.settings.setMapLocationToShow( - latitude, - longitude, - SearchCoreFactory.PREFERRED_NEARBY_POINT_ZOOM, - point.getPointDescription(app), - true, - point) - MapActivity.launchMapActivityMoveToTop(mapActivity) - } - - fun getAmenity(latLon: LatLon, osmId: Long): Amenity? { - var foundAmenity: Amenity? = null - val radius = NEARBY_MIN_RADIUS - val rect = MapUtils.calculateLatLonBbox(latLon.latitude, latLon.longitude, radius) - app.resourceManager.searchAmenities( - BinaryMapIndexReader.ACCEPT_ALL_POI_TYPE_FILTER, - rect.top, rect.left, rect.bottom, rect.right, - -1, true, - object : ResultMatcher { - override fun publish(amenity: Amenity?): Boolean { - val id = ObfConstants.getOsmObjectId(amenity) - if (osmId == id) { - foundAmenity = amenity - } - return false - } - - override fun isCancelled(): Boolean { - return foundAmenity != null - } - }) - return foundAmenity - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesListener.kt b/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesListener.kt deleted file mode 100644 index b2f5cc5af87..00000000000 --- a/OsmAnd/src/net/osmand/plus/nearbyplaces/NearbyPlacesListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.osmand.plus.nearbyplaces - -interface NearbyPlacesListener { - fun onNearbyPlacesUpdated() -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/search/GetNearbyPlacesImagesTask.kt b/OsmAnd/src/net/osmand/plus/search/GetNearbyPlacesImagesTask.kt index d5bea74c84c..c9141e07fed 100644 --- a/OsmAnd/src/net/osmand/plus/search/GetNearbyPlacesImagesTask.kt +++ b/OsmAnd/src/net/osmand/plus/search/GetNearbyPlacesImagesTask.kt @@ -1,9 +1,9 @@ package net.osmand.plus.search import android.net.TrafficStats -import net.osmand.data.QuadRect import net.osmand.plus.OsmandApplication import net.osmand.shared.KAsyncTask +import net.osmand.shared.data.KQuadRect import net.osmand.shared.util.LoggerFactory import net.osmand.wiki.WikiCoreHelper import net.osmand.wiki.WikiCoreHelper.OsmandApiFeatureData @@ -11,7 +11,7 @@ import java.util.Collections class GetNearbyPlacesImagesTask( val app: OsmandApplication, - val mapRect: QuadRect, + val mapRect: KQuadRect, val zoom: Int, val locale: String, val listener: GetImageCardsListener) : diff --git a/OsmAnd/src/net/osmand/plus/search/NearbyPlacesAdapter.kt b/OsmAnd/src/net/osmand/plus/search/NearbyPlacesAdapter.kt index 8b41a57469a..95c0b1a076e 100644 --- a/OsmAnd/src/net/osmand/plus/search/NearbyPlacesAdapter.kt +++ b/OsmAnd/src/net/osmand/plus/search/NearbyPlacesAdapter.kt @@ -7,34 +7,41 @@ import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.squareup.picasso.Callback import com.squareup.picasso.Picasso -import net.osmand.data.NearbyPlacePoint +import net.osmand.Location +import net.osmand.data.LatLon +import net.osmand.data.ExploreTopPlacePoint import net.osmand.plus.OsmandApplication import net.osmand.plus.R import net.osmand.plus.helpers.AndroidUiHelper import net.osmand.plus.render.RenderingIcons +import net.osmand.plus.utils.OsmAndFormatter import net.osmand.plus.utils.PicassoUtils import net.osmand.plus.utils.UiUtilities +import net.osmand.plus.utils.UpdateLocationUtils import net.osmand.util.Algorithms -import net.osmand.wiki.WikiImage class NearbyPlacesAdapter( val app: OsmandApplication, - var items: List, + var items: List, private var isVertical: Boolean, private val onItemClickListener: NearbyItemClickListener ) : RecyclerView.Adapter() { interface NearbyItemClickListener { - fun onNearbyItemClicked(item: NearbyPlacePoint) + fun onNearbyItemClicked(item: ExploreTopPlacePoint) } + // Initialize the UpdateLocationViewCache + private val updateLocationViewCache = UpdateLocationUtils.getUpdateLocationViewCache(app) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NearbyViewHolder { val inflater = UiUtilities.getInflater(parent.context, isNightMode()) val view = inflater.inflate( if (isVertical) R.layout.search_nearby_item_vertical else R.layout.search_nearby_item, parent, - false) - return NearbyViewHolder(view) + false + ) + return NearbyViewHolder(view, updateLocationViewCache) } private fun isNightMode(): Boolean { @@ -43,19 +50,24 @@ class NearbyPlacesAdapter( override fun onBindViewHolder(holder: NearbyViewHolder, position: Int) { val item = items[position] - holder.bind(item, onItemClickListener) + holder.bind(item, onItemClickListener, position) } override fun getItemCount(): Int = items.size - class NearbyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class NearbyViewHolder( + itemView: View, + private val updateLocationViewCache: UpdateLocationUtils.UpdateLocationViewCache + ) : RecyclerView.ViewHolder(itemView) { private val imageView: ImageView = itemView.findViewById(R.id.item_image) private val iconImageView: ImageView = itemView.findViewById(R.id.item_icon) private val titleTextView: TextView = itemView.findViewById(R.id.item_title) private val descriptionTextView: TextView? = itemView.findViewById(R.id.item_description) private val itemTypeTextView: TextView = itemView.findViewById(R.id.item_type) + private val distanceTextView: TextView? = itemView.findViewById(R.id.distance) + private val arrowImageView: ImageView? = itemView.findViewById(R.id.direction) - fun bind(item: NearbyPlacePoint, onItemClickListener: NearbyItemClickListener) { + fun bind(item: ExploreTopPlacePoint, onItemClickListener: NearbyItemClickListener, position: Int) { val app = imageView.context.applicationContext as OsmandApplication val poiTypes = app.poiTypes val subType = poiTypes.getPoiTypeByKey(item.poisubtype) @@ -66,8 +78,8 @@ class NearbyPlacesAdapter( uiUtilities.getRenderingIcon( app, subType.keyName, - nightMode) - + nightMode + ) } else { uiUtilities.getIcon(R.drawable.ic_action_info_dark, nightMode) } @@ -90,13 +102,51 @@ class NearbyPlacesAdapter( } }) } + + // Add row number to the title + titleTextView.text = "${position + 1}. ${item.wikiTitle}" + descriptionTextView?.text = item.wikiDesc descriptionTextView?.let { AndroidUiHelper.updateVisibility(it, !Algorithms.isEmpty(item.wikiDesc)) } - titleTextView.text = item.wikiTitle + itemTypeTextView.text = subType.translation + + // Calculate distance and show arrow + if (distanceTextView != null && arrowImageView != null) { + val distance = calculateDistance(app, item) + if (distance != null) { + distanceTextView.text = OsmAndFormatter.getFormattedDistance(distance, app) + distanceTextView.visibility = View.VISIBLE + arrowImageView.visibility = View.VISIBLE + + // Update compass icon rotation + val latLon = LatLon(item.latitude, item.longitude) + UpdateLocationUtils.updateLocationView(app, updateLocationViewCache, arrowImageView, distanceTextView, latLon) + } else { + distanceTextView.visibility = View.GONE + arrowImageView.visibility = View.GONE + } + } + itemView.setOnClickListener { onItemClickListener.onNearbyItemClicked(item) } } + + private fun calculateDistance(app: OsmandApplication, item: ExploreTopPlacePoint): Float? { + val currentLocation = app.locationProvider?.lastKnownLocation + if (currentLocation != null) { + val results = FloatArray(1) + Location.distanceBetween( + currentLocation.latitude, + currentLocation.longitude, + item.latitude, + item.longitude, + results + ) + return results[0] + } + return null + } } } \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchDialogFragment.java b/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchDialogFragment.java index 152f12879dc..65a6bc1c1cb 100644 --- a/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchDialogFragment.java +++ b/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchDialogFragment.java @@ -32,7 +32,6 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -973,7 +972,7 @@ public void onDismiss(@NonNull DialogInterface dialog) { fragmentManager.popBackStack(); } } - app.getOsmandMap().getMapLayers().getNearbyPlacesLayer().customObjectsDelegate.setCustomMapObjects(null); + app.getOsmandMap().getMapLayers().getExplorePlacesLayer().enableLayer(false); super.onDismiss(dialog); } diff --git a/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchHistoryListFragment.java b/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchHistoryListFragment.java index 3e4c58e3263..6ca366afa55 100644 --- a/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchHistoryListFragment.java +++ b/OsmAnd/src/net/osmand/plus/search/dialogs/QuickSearchHistoryListFragment.java @@ -10,11 +10,10 @@ import androidx.annotation.NonNull; import androidx.fragment.app.FragmentManager; -import net.osmand.data.NearbyPlacePoint; +import net.osmand.data.ExploreTopPlacePoint; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.helpers.SearchHistoryHelper.HistoryEntry; -import net.osmand.plus.nearbyplaces.NearbyPlacesHelper; import net.osmand.plus.search.NearbyPlacesAdapter; import net.osmand.plus.search.listitems.NearbyPlacesCard; import net.osmand.plus.search.listitems.QuickSearchListItem; @@ -30,10 +29,10 @@ public class QuickSearchHistoryListFragment extends QuickSearchListFragment impl private boolean selectionMode; private NearbyPlacesCard nearbyPlacesCard; - public void onNearbyItemClicked(@NonNull NearbyPlacePoint point) { + public void onNearbyItemClicked(@NonNull ExploreTopPlacePoint point) { MapActivity mapActivity = getMapActivity(); if (mapActivity != null) { - NearbyPlacesHelper.INSTANCE.showPointInContextMenu(mapActivity, point); + getMyApplication().getExplorePlacesProvider().showPointInContextMenu(mapActivity, point); getDialogFragment().hideToolbar(); getDialogFragment().hide(); } diff --git a/OsmAnd/src/net/osmand/plus/search/listitems/NearbyPlacesCard.java b/OsmAnd/src/net/osmand/plus/search/listitems/NearbyPlacesCard.java index 4d1596ae68f..67007aac2e3 100644 --- a/OsmAnd/src/net/osmand/plus/search/listitems/NearbyPlacesCard.java +++ b/OsmAnd/src/net/osmand/plus/search/listitems/NearbyPlacesCard.java @@ -9,23 +9,23 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import net.osmand.data.NearbyPlacePoint; +import net.osmand.data.ExploreTopPlacePoint; +import net.osmand.data.QuadRect; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.exploreplaces.ExplorePlacesProvider; import net.osmand.plus.helpers.AndroidUiHelper; -import net.osmand.plus.nearbyplaces.NearbyPlacesFragment; -import net.osmand.plus.nearbyplaces.NearbyPlacesHelper; -import net.osmand.plus.nearbyplaces.NearbyPlacesListener; +import net.osmand.plus.exploreplaces.ExplorePlacesFragment; import net.osmand.plus.search.NearbyPlacesAdapter; -import net.osmand.plus.search.dialogs.QuickSearchDialogFragment; import java.util.List; import me.zhanghai.android.materialprogressbar.MaterialProgressBar; -public class NearbyPlacesCard extends FrameLayout implements NearbyPlacesListener { +public class NearbyPlacesCard extends FrameLayout implements ExplorePlacesProvider.ExplorePlacesListener { + private static final int DISPLAY_ITEMS = 25; private boolean collapsed; private ImageView explicitIndicator; private View titleContainer; @@ -39,6 +39,7 @@ public class NearbyPlacesCard extends FrameLayout implements NearbyPlacesListene private View emptyView; private View cardContent; private boolean isLoadingItems; + private QuadRect visiblePlacesRect; public NearbyPlacesCard(@NonNull MapActivity mapActivity, @NonNull NearbyPlacesAdapter.NearbyItemClickListener clickListener) { super(mapActivity); @@ -72,7 +73,7 @@ private void init() { private void setupShowAllNearbyPlacesBtn() { findViewById(R.id.show_all_btn).setOnClickListener(v -> { - NearbyPlacesFragment.Companion.showInstance(mapActivity.getSupportFragmentManager()); + ExplorePlacesFragment.Companion.showInstance(mapActivity.getSupportFragmentManager(), visiblePlacesRect); }); } @@ -80,7 +81,8 @@ private void setupRecyclerView() { LinearLayoutManager layoutManager = new LinearLayoutManager(getContext(), RecyclerView.HORIZONTAL, false); nearByList.setLayoutManager(layoutManager); nearByList.setItemAnimator(null); - adapter = new NearbyPlacesAdapter(app, NearbyPlacesHelper.INSTANCE.getDataCollection(), false, clickListener); + visiblePlacesRect = app.getOsmandMap().getMapView().getCurrentRotatedTileBox().getLatLonBounds(); + adapter = new NearbyPlacesAdapter(app, app.getExplorePlacesProvider().getDataCollection(visiblePlacesRect, DISPLAY_ITEMS), false, clickListener); nearByList.setAdapter(adapter); } @@ -94,33 +96,29 @@ private void updateExpandState() { AndroidUiHelper.updateVisibility(emptyView, !collapsed && internetAvailable && !nearbyPointFound && !isLoadingItems); } - public void updateNearbyItems() { - isLoadingItems = false; - AndroidUiHelper.updateVisibility(progressBar, false); - adapter.setItems(NearbyPlacesHelper.INSTANCE.getDataCollection()); - adapter.notifyDataSetChanged(); - updateExpandState(); - } - private NearbyPlacesAdapter getNearbyAdapter() { if (adapter == null) { - List nearbyData = NearbyPlacesHelper.INSTANCE.getDataCollection(); + List nearbyData = app.getExplorePlacesProvider().getDataCollection(visiblePlacesRect, DISPLAY_ITEMS); adapter = new NearbyPlacesAdapter(app, nearbyData, false, clickListener); } return adapter; } @Override - public void onNearbyPlacesUpdated() { - updateNearbyItems(); + public void onNewExplorePlacesDownloaded() { + isLoadingItems = false; + AndroidUiHelper.updateVisibility(progressBar, app.getExplorePlacesProvider().isLoading()); + adapter.setItems(app.getExplorePlacesProvider().getDataCollection(visiblePlacesRect, DISPLAY_ITEMS)); + adapter.notifyDataSetChanged(); + updateExpandState(); } public void onResume() { - NearbyPlacesHelper.INSTANCE.addListener(this); + app.getExplorePlacesProvider().addListener(this); } public void onPause() { - NearbyPlacesHelper.INSTANCE.removeListener(this); + app.getExplorePlacesProvider().removeListener(this); } private void onNearbyPlacesCollapseChanged() { @@ -133,8 +131,11 @@ private void onNearbyPlacesCollapseChanged() { private void startLoadingNearbyPlaces() { isLoadingItems = true; - AndroidUiHelper.updateVisibility(progressBar, true); - NearbyPlacesHelper.INSTANCE.startLoadingNearestPhotos(); + app.getExplorePlacesProvider().getDataCollection( + app.getOsmandMap().getMapView().getCurrentRotatedTileBox().getLatLonBounds(), + DISPLAY_ITEMS + ); + AndroidUiHelper.updateVisibility(progressBar, app.getExplorePlacesProvider().isLoading()); } private void setupExpandNearbyPlacesIndicator() { diff --git a/OsmAnd/src/net/osmand/plus/views/MapLayers.java b/OsmAnd/src/net/osmand/plus/views/MapLayers.java index 05febe79bc6..3311101fffa 100644 --- a/OsmAnd/src/net/osmand/plus/views/MapLayers.java +++ b/OsmAnd/src/net/osmand/plus/views/MapLayers.java @@ -76,7 +76,7 @@ public class MapLayers { private PreviewRouteLineLayer previewRouteLineLayer; private POIMapLayer poiMapLayer; private FavouritesLayer mFavouritesLayer; - private NearbyPlacesLayer nearbyPlacesLayer; + private ExploreTopPlacesLayer explorePlacesLayer; private TransportStopsLayer transportStopsLayer; private PointLocationLayer locationLayer; private RadiusRulerControlLayer radiusRulerControlLayer; @@ -185,8 +185,8 @@ public void createLayers(@NonNull OsmandMapTileView mapView) { mapInfoLayer = new MapInfoLayer(app, routeLayer); mapView.addLayer(mapInfoLayer, 9); - nearbyPlacesLayer = new NearbyPlacesLayer(app); - mapView.addLayer(nearbyPlacesLayer, 4.1f); + explorePlacesLayer = new ExploreTopPlacesLayer(app); + mapView.addLayer(explorePlacesLayer, 4.1f); // 11. route info layer mapControlsLayer = new MapControlsLayer(app); mapView.addLayer(mapControlsLayer, 11); @@ -623,8 +623,8 @@ public FavouritesLayer getFavouritesLayer() { return mFavouritesLayer; } - public NearbyPlacesLayer getNearbyPlacesLayer() { - return nearbyPlacesLayer; + public ExploreTopPlacesLayer getExplorePlacesLayer() { + return explorePlacesLayer; } public MeasurementToolLayer getMeasurementToolLayer() { diff --git a/OsmAnd/src/net/osmand/plus/views/layers/ExploreTopPlacesLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/ExploreTopPlacesLayer.java new file mode 100644 index 00000000000..1b93708efd3 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/views/layers/ExploreTopPlacesLayer.java @@ -0,0 +1,407 @@ +package net.osmand.plus.views.layers; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; + +import net.osmand.PlatformUtil; +import net.osmand.core.android.MapRendererView; +import net.osmand.core.jni.PointI; +import net.osmand.data.LatLon; +import net.osmand.data.ExploreTopPlacePoint; +import net.osmand.data.PointDescription; +import net.osmand.data.QuadRect; +import net.osmand.data.QuadTree; +import net.osmand.data.RotatedTileBox; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.exploreplaces.ExplorePlacesProvider; +import net.osmand.plus.mapcontextmenu.MapContextMenu; +import net.osmand.plus.utils.NativeUtilities; +import net.osmand.plus.views.OsmandMapTileView; +import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider; +import net.osmand.plus.views.layers.base.OsmandMapLayer; +import net.osmand.plus.views.layers.core.ExploreTopPlacesTileProvider; +import net.osmand.util.Algorithms; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; +import java.util.List; + +public class ExploreTopPlacesLayer extends OsmandMapLayer implements IContextMenuProvider, ExplorePlacesProvider.ExplorePlacesListener { + + private static final int START_ZOOM = 2; + private static final Log LOG = PlatformUtil.getLog(ExploreTopPlacesLayer.class); + + private boolean nightMode; + private ExploreTopPlacePoint selectedObject; + + private Bitmap cachedSmallIconBitmap; + + private QuadRect requestQuadRect = null; // null means disabled + private int requestZoom = 0; // null means disabled + + private ExploreTopPlacesTileProvider topPlacesMapLayerProvider; + private ExploreTopPlacesTileProvider selectedTopPlacesMapLayerProvider; + private ExplorePlacesProvider explorePlacesProvider; + private int cachedExploreDataVersion; + private List places; + + + // To refresh images + public static final String LOAD_NEARBY_IMAGES_TAG = "load_nearby_images"; + private static final int TOP_LOAD_PHOTOS = 15; + private static final long DEBOUNCE_IMAGE_REFRESH = 5000; + + + private RotatedTileBox imagesDisplayedBox = null; + private int imagesUpdatedVersion; + private int imagesCachedVersion; + private long lastImageCacheRefreshed = 0; + + + public ExploreTopPlacesLayer(@NonNull Context ctx) { + super(ctx); + } + + @Override + public void initLayer(@NonNull OsmandMapTileView view) { + super.initLayer(view); + + nightMode = getApplication().getDaynightHelper().isNightMode(); + explorePlacesProvider = getApplication().getExplorePlacesProvider(); + explorePlacesProvider.addListener(this); + recreateBitmaps(); + } + + + + @Override + public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + } + + private void recreateBitmaps() { + cachedSmallIconBitmap = ExploreTopPlacesTileProvider.createSmallPointBitmap(getApplication()); + } + + @Override + protected void updateResources() { + super.updateResources(); + recreateBitmaps(); + } + + @Override + protected void cleanupResources() { + super.cleanupResources(); + deleteProvider(topPlacesMapLayerProvider); + deleteProvider(selectedTopPlacesMapLayerProvider); + topPlacesMapLayerProvider = null; + selectedTopPlacesMapLayerProvider = null; + explorePlacesProvider.removeListener(this); + } + + @Override + public boolean drawInScreenPixels() { + return true; + } + + @Override + public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { + super.onPrepareBufferImage(canvas, tileBox, settings); + boolean nightMode = settings != null && settings.isNightMode(); + boolean nightModeChanged = this.nightMode != nightMode; + this.nightMode = nightMode; + boolean placesUpdated = false; + if (requestQuadRect != null) { + int exploreDataVersion = explorePlacesProvider.getDataVersion(); + if (!requestQuadRect.contains(tileBox.getLatLonBounds()) || + cachedExploreDataVersion < exploreDataVersion || places == null || + (requestZoom < tileBox.getZoom() && requestZoom < ExplorePlacesProvider.MAX_LEVEL_ZOOM_CACHE)) { + placesUpdated = true; + RotatedTileBox extended = tileBox.copy(); + extended.increasePixelDimensions(tileBox.getPixWidth() / 2, tileBox.getPixHeight() / 2); + requestQuadRect = extended.getLatLonBounds(); + requestZoom = tileBox.getZoom(); + cachedExploreDataVersion = explorePlacesProvider.getDataVersion(); + places = explorePlacesProvider.getDataCollection(requestQuadRect); + } + } else { + if (places != null) { + placesUpdated = true; + places = null; + } + } + placesUpdated = placesUpdated || scheduleImageRefreshes(places, tileBox); + + ExploreTopPlacePoint selectedObject = getSelectedNearbyPlace(); + long selectedObjectId = selectedObject == null ? 0 : selectedObject.getId(); + long lastSelectedObjectId = this.selectedObject == null ? 0 : this.selectedObject.getId(); + boolean selectedObjectChanged = selectedObjectId != lastSelectedObjectId; + this.selectedObject = selectedObject; + if (hasMapRenderer()) { + if ((mapActivityInvalidated || mapRendererChanged + || nightModeChanged || placesUpdated)) { + initProviderWithPoints(places); + mapRendererChanged = false; + } + if (selectedObjectChanged) { + showSelectedNearbyPoint(); + } + } else { + if (places != null && tileBox.getZoom() >= START_ZOOM) { + float iconSize = getIconSize(view.getApplication()); + QuadTree boundIntersections = initBoundIntersections(tileBox); + QuadRect latLonBounds = tileBox.getLatLonBounds(); + List fullObjectsLatLon = new ArrayList<>(); + List smallObjectsLatLon = new ArrayList<>(); + drawPoints(places, latLonBounds, false, tileBox, boundIntersections, iconSize, canvas, + fullObjectsLatLon, smallObjectsLatLon); + this.fullObjectsLatLon = fullObjectsLatLon; + this.smallObjectsLatLon = smallObjectsLatLon; + } + } + mapActivityInvalidated = false; + } + + private void drawPoints(List pointsToDraw, QuadRect latLonBounds, boolean synced, RotatedTileBox tileBox, + QuadTree boundIntersections, float iconSize, Canvas canvas, + List fullObjectsLatLon, List smallObjectsLatLon) { + List fullObjects = new ArrayList<>(); + Paint pointPaint = new Paint(); + if (cachedSmallIconBitmap == null) { + cachedSmallIconBitmap = ExploreTopPlacesTileProvider.createSmallPointBitmap(getApplication()); + } + for (ExploreTopPlacePoint nearbyPoint : pointsToDraw) { + double lat = nearbyPoint.getLatitude(); + double lon = nearbyPoint.getLongitude(); + if (lat >= latLonBounds.bottom && lat <= latLonBounds.top + && lon >= latLonBounds.left && lon <= latLonBounds.right) { + float x = tileBox.getPixXFromLatLon(lat, lon); + float y = tileBox.getPixYFromLatLon(lat, lon); + if (intersects(boundIntersections, x, y, iconSize, iconSize) || nearbyPoint.getImageBitmap() == null) { + canvas.drawBitmap(cachedSmallIconBitmap, x, y, pointPaint); + smallObjectsLatLon.add(new LatLon(lat, lon)); + } else { + fullObjects.add(nearbyPoint); + fullObjectsLatLon.add(new LatLon(lat, lon)); + } + } + } + for (ExploreTopPlacePoint point : fullObjects) { + Bitmap bitmap = point.getImageBitmap(); + if (bitmap != null) { + Bitmap bigBitmap = ExploreTopPlacesTileProvider.createBigBitmap(getApplication(), bitmap, point.getId() == getSelectedObjectId()); + float x = tileBox.getPixXFromLatLon(point.getLatitude(), point.getLongitude()); + float y = tileBox.getPixYFromLatLon(point.getLatitude(), point.getLongitude()); + canvas.drawBitmap(bigBitmap, x - bigBitmap.getWidth() / 2, y - bigBitmap.getHeight() / 2, pointPaint); + } + } + } + + private long getSelectedObjectId() { + return selectedObject == null ? 0 : selectedObject.getId(); + } + + private void initProviderWithPoints(List points) { + MapRendererView mapRenderer = getMapRenderer(); + if (mapRenderer == null) { + return; + } + deleteProvider(topPlacesMapLayerProvider); + if (points == null) { + topPlacesMapLayerProvider = null; + return; + } + topPlacesMapLayerProvider = new ExploreTopPlacesTileProvider(getApplication(), + getPointsOrder(), + view.getDensity(), + getSelectedObjectId()); + for (ExploreTopPlacePoint nearbyPlacePoint : points) { + topPlacesMapLayerProvider.addToData(nearbyPlacePoint); + } + topPlacesMapLayerProvider.initProvider(mapRenderer); + } + + public synchronized void showSelectedNearbyPoint() { + MapRendererView mapRenderer = getMapRenderer(); + if (mapRenderer == null) { + return; + } + deleteProvider(selectedTopPlacesMapLayerProvider); + if (selectedObject != null) { + selectedTopPlacesMapLayerProvider = new ExploreTopPlacesTileProvider(getApplication(), + getPointsOrder() - 1, + view.getDensity(), + getSelectedObjectId()); + + selectedTopPlacesMapLayerProvider.addToData(selectedObject); + selectedTopPlacesMapLayerProvider.initProvider(mapRenderer); + } + } + + + public void deleteProvider(@Nullable ExploreTopPlacesTileProvider provider) { + MapRendererView mapRenderer = getMapRenderer(); + if (mapRenderer == null || provider == null) { + return; + } + provider.deleteProvider(mapRenderer); + } + + @Override + public boolean onLongPressEvent(@NonNull PointF point, @NonNull RotatedTileBox tileBox) { + return false; + } + + @Override + public PointDescription getObjectName(Object o) { + if (o instanceof ExploreTopPlacePoint) { + return ((ExploreTopPlacePoint) o).getPointDescription(getContext()); + } + return null; + } + + @Override + public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List res, + boolean unknownLocation, boolean excludeUntouchableObjects) { + List points = places; + if (points != null) { + getNearbyPlaceFromPoint(tileBox, point, res, points); + } + } + + private void getNearbyPlaceFromPoint(RotatedTileBox tb, PointF point, List res, List points) { + MapRendererView mapRenderer = getMapRenderer(); + float radius = getScaledTouchRadius(getApplication(), tb.getDefaultRadiusPoi()) * TOUCH_RADIUS_MULTIPLIER; + List touchPolygon31 = null; + if (mapRenderer != null) { + touchPolygon31 = NativeUtilities.getPolygon31FromPixelAndRadius(mapRenderer, point, radius); + if (touchPolygon31 == null) { + return; + } + } + + for (ExploreTopPlacePoint nearbyPoint : points) { + double lat = nearbyPoint.getLatitude(); + double lon = nearbyPoint.getLongitude(); + boolean add = mapRenderer != null + ? NativeUtilities.isPointInsidePolygon(lat, lon, touchPolygon31) + : tb.isLatLonNearPixel(lat, lon, point.x, point.y, radius); + if (add) { + res.add(nearbyPoint); + } + } + } + + @Nullable + private ExploreTopPlacePoint getSelectedNearbyPlace() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + MapContextMenu mapContextMenu = mapActivity.getContextMenu(); + Object object = mapContextMenu.getObject(); + if (object instanceof ExploreTopPlacePoint && mapContextMenu.isVisible()) { + return (ExploreTopPlacePoint) object; + } + } + return null; + } + + @Override + public LatLon getObjectLocation(Object o) { + if (o instanceof ExploreTopPlacePoint) { + return new LatLon(((ExploreTopPlacePoint) o).getLatitude(), ((ExploreTopPlacePoint) o).getLongitude()); + } + return null; + } + + private boolean scheduleImageRefreshes(List nearbyPlacePoints, RotatedTileBox tileBox) { + if (places == null) { + if (imagesDisplayedBox != null) { + LOG.info(String.format("Picasso cancel loading")); + Picasso.get().cancelTag(LOAD_NEARBY_IMAGES_TAG); + imagesDisplayedBox = null; + } + return false; + } + if (imagesDisplayedBox == null || imagesDisplayedBox.getZoom() != tileBox.getZoom() || + !imagesDisplayedBox.containsTileBox(tileBox)) { + imagesDisplayedBox = tileBox.copy(); + imagesDisplayedBox.increasePixelDimensions(tileBox.getPixWidth() / 2, tileBox.getPixHeight() / 2); + imagesUpdatedVersion++; + List placesToDisplayWithPhotos = new ArrayList<>(); + + boolean missingPhoto = false; + for (ExploreTopPlacePoint point : nearbyPlacePoints) { + if (imagesDisplayedBox.containsLatLon(point.getLatitude(), point.getLongitude()) && + !Algorithms.isEmpty(point.getIconUrl())) { + placesToDisplayWithPhotos.add(point); + if (point.getImageBitmap() == null) { + missingPhoto = true; + } + if (placesToDisplayWithPhotos.size() > TOP_LOAD_PHOTOS) { + break; + } + } + } + if (missingPhoto) { + Picasso.get().cancelTag(LOAD_NEARBY_IMAGES_TAG); + LOG.info(String.format("Picasso cancel loading")); + + for (ExploreTopPlacePoint point : placesToDisplayWithPhotos) { + if (point.getImageBitmap() != null) { + continue; + } + Target imgLoadTarget = new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + point.setImageBitmap(bitmap); + LOG.info(String.format("Picasso loaded %s", point.getIconUrl())); + imagesUpdatedVersion++; + } + + @Override + public void onBitmapFailed(Exception e, Drawable errorDrawable) { + } + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) { + } + }; + LOG.info(String.format("Picasso schedule %s", point.getIconUrl())); + Picasso.get() + .load(point.getIconUrl()) + .tag(LOAD_NEARBY_IMAGES_TAG) + .into(imgLoadTarget); + } + } + } + if (imagesCachedVersion != imagesUpdatedVersion && System.currentTimeMillis() - lastImageCacheRefreshed > + DEBOUNCE_IMAGE_REFRESH) { + lastImageCacheRefreshed = System.currentTimeMillis(); + imagesCachedVersion = imagesUpdatedVersion; + return true; + } + return false; + + + } + + public void enableLayer(boolean enable) { + requestQuadRect = enable ? + new QuadRect() : null; + } + + @Override + public void onNewExplorePlacesDownloaded() { + getApplication().getOsmandMap().refreshMap(); + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/NearbyPlacesLayer.java b/OsmAnd/src/net/osmand/plus/views/layers/NearbyPlacesLayer.java deleted file mode 100644 index 0ed7f513054..00000000000 --- a/OsmAnd/src/net/osmand/plus/views/layers/NearbyPlacesLayer.java +++ /dev/null @@ -1,323 +0,0 @@ -package net.osmand.plus.views.layers; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.drawable.Drawable; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Target; - -import net.osmand.PlatformUtil; -import net.osmand.core.android.MapRendererView; -import net.osmand.core.jni.PointI; -import net.osmand.data.LatLon; -import net.osmand.data.NearbyPlacePoint; -import net.osmand.data.PointDescription; -import net.osmand.data.QuadRect; -import net.osmand.data.QuadTree; -import net.osmand.data.RotatedTileBox; -import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.mapcontextmenu.MapContextMenu; -import net.osmand.plus.utils.NativeUtilities; -import net.osmand.plus.views.OsmandMapTileView; -import net.osmand.plus.views.PointImageUtils; -import net.osmand.plus.views.layers.ContextMenuLayer.IContextMenuProvider; -import net.osmand.plus.views.layers.base.OsmandMapLayer; -import net.osmand.plus.views.layers.core.NearbyPlacesTileProvider; -import net.osmand.util.Algorithms; -import net.osmand.wiki.WikiCoreHelper; - -import org.apache.commons.logging.Log; - -import java.util.ArrayList; -import java.util.List; - -public class NearbyPlacesLayer extends OsmandMapLayer implements IContextMenuProvider { - - private static final int START_ZOOM = 6; - private static final Log LOG = PlatformUtil.getLog(NearbyPlacesLayer.class); - public static final String LOAD_NEARBY_IMAGES_TAG = "load_nearby_images"; - - protected List cache = new ArrayList<>(); - private boolean showNearbyPoints; - private boolean nightMode; - private NearbyPlacePoint selectedObject; - - private Bitmap cachedSmallIconBitmap; - - public CustomMapObjects customObjectsDelegate = new OsmandMapLayer.CustomMapObjects<>(); - - private NearbyPlacesTileProvider nearbyPlacesMapLayerProvider; - private NearbyPlacesTileProvider selectedNearbyPlacesMapLayerProvider; - - public NearbyPlacesLayer(@NonNull Context ctx) { - super(ctx); - } - - @Override - public void initLayer(@NonNull OsmandMapTileView view) { - super.initLayer(view); - nightMode = getApplication().getDaynightHelper().isNightMode(); - recreateBitmaps(); - } - - @Override - public void onDraw(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { - } - - private void recreateBitmaps() { - cachedSmallIconBitmap = NearbyPlacesTileProvider.createSmallPointBitmap(getApplication()); - } - - @Override - protected void updateResources() { - super.updateResources(); - recreateBitmaps(); - } - - @Override - protected void cleanupResources() { - super.cleanupResources(); - clearNearbyPoints(nearbyPlacesMapLayerProvider); - clearNearbyPoints(selectedNearbyPlacesMapLayerProvider); - nearbyPlacesMapLayerProvider = null; - selectedNearbyPlacesMapLayerProvider = null; - } - - @Override - public boolean drawInScreenPixels() { - return true; - } - - @Override - public void onPrepareBufferImage(Canvas canvas, RotatedTileBox tileBox, DrawSettings settings) { - super.onPrepareBufferImage(canvas, tileBox, settings); - boolean nightMode = settings != null && settings.isNightMode(); - boolean nightModeChanged = this.nightMode != nightMode; - this.nightMode = nightMode; - boolean showNearbyPoints = !customObjectsDelegate.getMapObjects().isEmpty(); - boolean showNearbyPlacesChanged = this.showNearbyPoints != showNearbyPoints; - this.showNearbyPoints = showNearbyPoints; - NearbyPlacePoint selectedObject = getSelectedNearbyPlace(); - long selectedObjectId = selectedObject == null ? 0 : selectedObject.getId(); - long lastSelectedObjectId = this.selectedObject == null ? 0 : this.selectedObject.getId(); - boolean selectedObjectChanged = selectedObjectId != lastSelectedObjectId; - this.selectedObject = selectedObject; - if (hasMapRenderer()) { - if ((mapActivityInvalidated || mapRendererChanged - || nightModeChanged || showNearbyPlacesChanged - || customObjectsDelegate.isChanged())) { - showNearbyPoints(); - customObjectsDelegate.acceptChanges(); - mapRendererChanged = false; - } - if (selectedObjectChanged) { - showSelectedNearbyPoint(); - } - } else { - cache.clear(); - if (showNearbyPoints && tileBox.getZoom() >= START_ZOOM) { - float iconSize = getIconSize(view.getApplication()); - QuadTree boundIntersections = initBoundIntersections(tileBox); - QuadRect latLonBounds = tileBox.getLatLonBounds(); - List fullObjectsLatLon = new ArrayList<>(); - List smallObjectsLatLon = new ArrayList<>(); - drawPoints(customObjectsDelegate.getMapObjects(), latLonBounds, false, tileBox, boundIntersections, iconSize, canvas, - fullObjectsLatLon, smallObjectsLatLon); - this.fullObjectsLatLon = fullObjectsLatLon; - this.smallObjectsLatLon = smallObjectsLatLon; - } - } - mapActivityInvalidated = false; - } - - private void drawPoints(List pointsToDraw, QuadRect latLonBounds, boolean synced, RotatedTileBox tileBox, - QuadTree boundIntersections, float iconSize, Canvas canvas, - List fullObjectsLatLon, List smallObjectsLatLon) { - List fullObjects = new ArrayList<>(); - Paint pointPaint = new Paint(); - if (cachedSmallIconBitmap == null) { - cachedSmallIconBitmap = NearbyPlacesTileProvider.createSmallPointBitmap(getApplication()); - } - for (NearbyPlacePoint nearbyPoint : pointsToDraw) { - double lat = nearbyPoint.getLatitude(); - double lon = nearbyPoint.getLongitude(); - if (lat >= latLonBounds.bottom && lat <= latLonBounds.top - && lon >= latLonBounds.left && lon <= latLonBounds.right) { - cache.add(nearbyPoint); - float x = tileBox.getPixXFromLatLon(lat, lon); - float y = tileBox.getPixYFromLatLon(lat, lon); - if (intersects(boundIntersections, x, y, iconSize, iconSize) || nearbyPoint.getImageBitmap() == null) { - canvas.drawBitmap(cachedSmallIconBitmap, x, y, pointPaint); - smallObjectsLatLon.add(new LatLon(lat, lon)); - } else { - fullObjects.add(nearbyPoint); - fullObjectsLatLon.add(new LatLon(lat, lon)); - } - } - } - for (NearbyPlacePoint point : fullObjects) { - Bitmap bitmap = point.getImageBitmap(); - if (bitmap != null) { - Bitmap bigBitmap = NearbyPlacesTileProvider.createBigBitmap(getApplication(), bitmap, point.getId() == getSelectedObjectId()); - float x = tileBox.getPixXFromLatLon(point.getLatitude(), point.getLongitude()); - float y = tileBox.getPixYFromLatLon(point.getLatitude(), point.getLongitude()); - canvas.drawBitmap(bigBitmap, x - bigBitmap.getWidth() / 2, y - bigBitmap.getHeight() / 2, pointPaint); - } - } - } - - private long getSelectedObjectId() { - return selectedObject == null ? 0 : selectedObject.getId(); - } - - public synchronized void showNearbyPoints() { - MapRendererView mapRenderer = getMapRenderer(); - if (mapRenderer == null) { - return; - } - clearNearbyPoints(nearbyPlacesMapLayerProvider); - nearbyPlacesMapLayerProvider = new NearbyPlacesTileProvider(getApplication(), - getPointsOrder(), - view.getDensity(), - getSelectedObjectId()); - - List points = customObjectsDelegate.getMapObjects(); - showNearbyPoints(points); - nearbyPlacesMapLayerProvider.drawSymbols(mapRenderer); - } - - public synchronized void showSelectedNearbyPoint() { - MapRendererView mapRenderer = getMapRenderer(); - if (mapRenderer == null) { - return; - } - clearNearbyPoints(selectedNearbyPlacesMapLayerProvider); - if (selectedObject != null) { - selectedNearbyPlacesMapLayerProvider = new NearbyPlacesTileProvider(getApplication(), - getPointsOrder() - 1, - view.getDensity(), - getSelectedObjectId()); - - selectedNearbyPlacesMapLayerProvider.addToData(selectedObject); - selectedNearbyPlacesMapLayerProvider.drawSymbols(mapRenderer); - } - } - - private void showNearbyPoints(List points) { - for (NearbyPlacePoint nearbyPlacePoint : points) { - nearbyPlacesMapLayerProvider.addToData(nearbyPlacePoint); - } - } - - public void clearNearbyPoints(@Nullable NearbyPlacesTileProvider provider) { - MapRendererView mapRenderer = getMapRenderer(); - if (mapRenderer == null || provider == null) { - return; - } - provider.clearSymbols(mapRenderer); - } - - @Override - public boolean onLongPressEvent(@NonNull PointF point, @NonNull RotatedTileBox tileBox) { - return false; - } - - @Override - public PointDescription getObjectName(Object o) { - if (o instanceof NearbyPlacePoint) { - return ((NearbyPlacePoint) o).getPointDescription(getContext()); - } - return null; - } - - @Override - public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List res, - boolean unknownLocation, boolean excludeUntouchableObjects) { - List points = customObjectsDelegate.getMapObjects(); - if (!points.isEmpty()) { - getNearbyPlaceFromPoint(tileBox, point, res, points); - } - } - - private void getNearbyPlaceFromPoint(RotatedTileBox tb, PointF point, List res, List points) { - MapRendererView mapRenderer = getMapRenderer(); - float radius = getScaledTouchRadius(getApplication(), tb.getDefaultRadiusPoi()) * TOUCH_RADIUS_MULTIPLIER; - List touchPolygon31 = null; - if (mapRenderer != null) { - touchPolygon31 = NativeUtilities.getPolygon31FromPixelAndRadius(mapRenderer, point, radius); - if (touchPolygon31 == null) { - return; - } - } - - for (NearbyPlacePoint nearbyPoint : points) { - double lat = nearbyPoint.getLatitude(); - double lon = nearbyPoint.getLongitude(); - boolean add = mapRenderer != null - ? NativeUtilities.isPointInsidePolygon(lat, lon, touchPolygon31) - : tb.isLatLonNearPixel(lat, lon, point.x, point.y, radius); - if (add) { - res.add(nearbyPoint); - } - } - } - - @Nullable - private NearbyPlacePoint getSelectedNearbyPlace() { - MapActivity mapActivity = getMapActivity(); - if (mapActivity != null) { - MapContextMenu mapContextMenu = mapActivity.getContextMenu(); - Object object = mapContextMenu.getObject(); - if (object instanceof NearbyPlacePoint && mapContextMenu.isVisible()) { - return (NearbyPlacePoint) object; - } - } - return null; - } - - @Override - public LatLon getObjectLocation(Object o) { - if (o instanceof NearbyPlacePoint) { - return new LatLon(((NearbyPlacePoint) o).getLatitude(), ((NearbyPlacePoint) o).getLongitude()); - } - return null; - } - - public void setCustomMapObjects(List nearbyPlacePoints) { - Picasso.get().cancelTag(LOAD_NEARBY_IMAGES_TAG); - List nearbyPlacePointsList = new ArrayList<>(); - for (NearbyPlacePoint point : nearbyPlacePoints) { - nearbyPlacePointsList.add(point); - Target imgLoadTarget = new Target() { - @Override - public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { - point.setImageBitmap(bitmap); - customObjectsDelegate.onMapObjectUpdated(point); - } - - @Override - public void onBitmapFailed(Exception e, Drawable errorDrawable) { - } - - @Override - public void onPrepareLoad(Drawable placeHolderDrawable) { - } - }; - if (!Algorithms.isEmpty(point.getIconUrl())) { - Picasso.get() - .load(point.getIconUrl()) - .tag(LOAD_NEARBY_IMAGES_TAG) - .into(imgLoadTarget); - } - } - customObjectsDelegate.setCustomMapObjects(nearbyPlacePointsList); - getApplication().getOsmandMap().refreshMap(); - } -} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/views/layers/core/NearbyPlacesTileProvider.java b/OsmAnd/src/net/osmand/plus/views/layers/core/ExploreTopPlacesTileProvider.java similarity index 91% rename from OsmAnd/src/net/osmand/plus/views/layers/core/NearbyPlacesTileProvider.java rename to OsmAnd/src/net/osmand/plus/views/layers/core/ExploreTopPlacesTileProvider.java index 1c4907858ea..4b12fb9aba4 100644 --- a/OsmAnd/src/net/osmand/plus/views/layers/core/NearbyPlacesTileProvider.java +++ b/OsmAnd/src/net/osmand/plus/views/layers/core/ExploreTopPlacesTileProvider.java @@ -24,7 +24,7 @@ import net.osmand.core.jni.TileId; import net.osmand.core.jni.ZoomLevel; import net.osmand.core.jni.interface_MapTiledCollectionProvider; -import net.osmand.data.NearbyPlacePoint; +import net.osmand.data.ExploreTopPlacePoint; import net.osmand.data.PointDescription; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; @@ -32,7 +32,6 @@ import net.osmand.plus.utils.AndroidUtils; import net.osmand.plus.utils.ColorUtilities; import net.osmand.plus.utils.NativeUtilities; -import net.osmand.plus.views.PointImageUtils; import net.osmand.util.MapUtils; import org.apache.commons.logging.Log; @@ -41,9 +40,9 @@ import java.util.ArrayList; import java.util.List; -public class NearbyPlacesTileProvider extends interface_MapTiledCollectionProvider { +public class ExploreTopPlacesTileProvider extends interface_MapTiledCollectionProvider { - private static final Log log = LogFactory.getLog(NearbyPlacesTileProvider.class); + private static final Log log = LogFactory.getLog(ExploreTopPlacesTileProvider.class); private final QListPointI points31 = new QListPointI(); private final List mapLayerDataList = new ArrayList<>(); private Bitmap cachedSmallBitmap; @@ -78,7 +77,7 @@ private static Paint createBitmapPaint() { } - public NearbyPlacesTileProvider(@NonNull OsmandApplication context, int baseOrder, float density, long selectedObjectId) { + public ExploreTopPlacesTileProvider(@NonNull OsmandApplication context, int baseOrder, float density, long selectedObjectId) { this.app = context; this.baseOrder = baseOrder; this.density = density; @@ -86,14 +85,14 @@ public NearbyPlacesTileProvider(@NonNull OsmandApplication context, int baseOrde this.selectedObjectId = selectedObjectId; } - public void drawSymbols(@NonNull MapRendererView mapRenderer) { + public void initProvider(@NonNull MapRendererView mapRenderer) { if (providerInstance == null) { providerInstance = instantiateProxy(); } mapRenderer.addSymbolsProvider(providerInstance); } - public void clearSymbols(@NonNull MapRendererView mapRenderer) { + public void deleteProvider(@NonNull MapRendererView mapRenderer) { if (providerInstance != null) { mapRenderer.removeSymbolsProvider(providerInstance); providerInstance = null; @@ -142,7 +141,7 @@ public double getScale() { @Override public SingleSkImage getImageBitmap(int index, boolean isFullSize) { - NearbyPlacesTileProvider.MapLayerData data = index < mapLayerDataList.size() ? mapLayerDataList.get(index) : null; + ExploreTopPlacesTileProvider.MapLayerData data = index < mapLayerDataList.size() ? mapLayerDataList.get(index) : null; if (data == null) { return SwigUtilities.nullSkImage(); } @@ -199,7 +198,7 @@ public PointI getPinIconOffset() { return offset; } - public void addToData(@NonNull NearbyPlacePoint nearbyPlacePoint) throws IllegalStateException { + public void addToData(@NonNull ExploreTopPlacePoint nearbyPlacePoint) throws IllegalStateException { if (providerInstance != null) { throw new IllegalStateException("Provider already instantiated. Data cannot be modified at this stage."); } @@ -210,9 +209,9 @@ public void addToData(@NonNull NearbyPlacePoint nearbyPlacePoint) throws Illegal } private static class MapLayerData { - NearbyPlacePoint nearbyPlace; + ExploreTopPlacePoint nearbyPlace; - MapLayerData(@NonNull NearbyPlacePoint nearbyPlace) { + MapLayerData(@NonNull ExploreTopPlacePoint nearbyPlace) { this.nearbyPlace = nearbyPlace; } }