package cgeo.geocaching.maps.mapsforge.v6.caches;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.Viewport;
import cgeo.geocaching.maps.mapsforge.v6.MapHandlers;
import cgeo.geocaching.maps.mapsforge.v6.MfMapView;
import cgeo.geocaching.maps.mapsforge.v6.NewMap;
import cgeo.geocaching.maps.mapsforge.v6.TapHandler;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.models.Waypoint;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.storage.DataStore;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MapUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.mapsforge.core.graphics.Bitmap;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.Layer;
import org.mapsforge.map.layer.Layers;
public abstract class AbstractCachesOverlay {
private final int overlayId;
private final Set<GeoEntry> geoEntries;
private final WeakReference<MfMapView> mapViewRef;
private final Layer anchorLayer;
private final GeoitemLayers layerList = new GeoitemLayers();
private final MapHandlers mapHandlers;
private boolean invalidated = true;
public AbstractCachesOverlay(final int overlayId, final Set<GeoEntry> geoEntries, final MfMapView mapView, final Layer anchorLayer, final MapHandlers mapHandlers) {
this.overlayId = overlayId;
this.geoEntries = geoEntries;
this.mapViewRef = new WeakReference<>(mapView);
this.anchorLayer = anchorLayer;
this.mapHandlers = mapHandlers;
}
public void onDestroy() {
clearLayers();
}
public Set<String> getVisibleGeocodes() {
final Set<String> geocodesInViewport = new HashSet<>();
final MfMapView mapView = mapViewRef.get();
if (mapView != null) {
final Collection<Geocache> cachesInViewport = mapView.getViewport().filter(DataStore.loadCaches(getGeocodes(), LoadFlags.LOAD_CACHE_OR_DB));
for (final Geocache cache : cachesInViewport) {
geocodesInViewport.add(cache.getGeocode());
}
}
return geocodesInViewport;
}
public int getVisibleItemsCount() {
final MfMapView mapView = mapViewRef.get();
if (mapView == null) {
return 0;
}
return mapView.getViewport().count(DataStore.loadCaches(getGeocodes(), LoadFlags.LOAD_CACHE_OR_DB));
}
public int getItemsCount() {
return layerList.size();
}
public void invalidate() {
invalidated = true;
}
public void invalidate(final Collection<String> invalidGeocodes) {
removeItems(invalidGeocodes);
invalidate();
}
protected boolean isInvalidated() {
return invalidated;
}
protected void refreshed() {
invalidated = false;
}
protected void fill(final Set<Geocache> caches) {
final Collection<String> removeCodes = getGeocodes();
final Collection<String> newCodes = new HashSet<>();
// display caches
final Set<Geocache> cachesToDisplay = caches;
if (!cachesToDisplay.isEmpty()) {
// Only show waypoints when less than showWaypointsthreshold Caches shown
final boolean showWaypoints = cachesToDisplay.size() < Settings.getWayPointsThreshold();
Log.d(String.format(Locale.ENGLISH, "CachesToDisplay: %d, showWaypoints: %b", cachesToDisplay.size(), showWaypoints));
for (final Geocache cache : cachesToDisplay) {
if (cache == null) {
continue;
}
if (showWaypoints) {
final List<Waypoint> waypoints = cache.getWaypoints();
for (final Waypoint waypoint : waypoints) {
if (waypoint == null || waypoint.getCoords() == null) {
continue;
}
if (removeCodes.contains(waypoint.getGpxId())) {
removeCodes.remove(waypoint.getGpxId());
} else {
if (addItem(waypoint)) {
newCodes.add(waypoint.getGpxId());
}
}
}
}
if (cache.getCoords() == null) {
continue;
}
if (removeCodes.contains(cache.getGeocode())) {
removeCodes.remove(cache.getGeocode());
} else {
if (addItem(cache)) {
newCodes.add(cache.getGeocode());
}
}
}
}
syncLayers(removeCodes, newCodes);
repaint();
}
protected final boolean addItem(final Geocache cache) {
final GeoEntry entry = new GeoEntry(cache.getGeocode(), overlayId);
if (geoEntries.add(entry)) {
layerList.add(getCacheItem(cache, this.mapHandlers.getTapHandler()));
Log.d(String.format(Locale.ENGLISH, "Cache %s for id %d added, geoEntries: %d", entry.geocode, overlayId, geoEntries.size()));
return true;
}
Log.d(String.format(Locale.ENGLISH, "Cache %s for id %d not added, geoEntries: %d", entry.geocode, overlayId, geoEntries.size()));
return false;
}
protected final boolean addItem(final Waypoint waypoint) {
final GeoEntry entry = new GeoEntry(waypoint.getGpxId(), overlayId);
final GeoitemLayer waypointItem = getWaypointItem(waypoint, this.mapHandlers.getTapHandler());
if (waypointItem != null && geoEntries.add(entry)) {
layerList.add(waypointItem);
Log.d(String.format(Locale.ENGLISH, "Waypoint %s for id %d added, geoEntries: %d", entry.geocode, overlayId, geoEntries.size()));
return true;
}
Log.d(String.format(Locale.ENGLISH, "Waypoint %s for id %d not added, geoEntries: %d", entry.geocode, overlayId, geoEntries.size()));
return false;
}
protected void addLayers() {
final MapView mapView = mapViewRef.get();
if (mapView == null) {
return;
}
final Layers layers = mapView.getLayerManager().getLayers();
final int index = layers.indexOf(anchorLayer) + 1;
layers.addAll(index, layerList.getAsLayers());
}
protected Collection<String> getGeocodes() {
return layerList.getGeocodes();
}
protected Viewport getViewport() {
final MfMapView mapView = this.mapViewRef.get();
if (mapView == null) {
return null;
}
return mapView.getViewport();
}
protected int getMapZoomLevel() {
final MfMapView mapView = this.mapViewRef.get();
if (mapView == null) {
return 0;
}
return mapView.getMapZoomLevel();
}
protected void showProgress() {
mapHandlers.sendEmptyProgressMessage(NewMap.SHOW_PROGRESS);
}
protected void hideProgress() {
mapHandlers.sendEmptyProgressMessage(NewMap.HIDE_PROGRESS);
}
protected void repaint() {
mapHandlers.sendEmptyDisplayMessage(NewMap.INVALIDATE_MAP);
mapHandlers.sendEmptyDisplayMessage(NewMap.UPDATE_TITLE);
}
protected void clearLayers() {
final MfMapView mapView = this.mapViewRef.get();
if (mapView == null) {
return;
}
final Layers layers = mapView.getLayerManager().getLayers();
for (final GeoitemLayer layer : layerList) {
geoEntries.remove(new GeoEntry(layer.getItemCode(), overlayId));
layers.remove(layer);
}
layerList.clear();
Log.d(String.format(Locale.ENGLISH, "Layers for id %d cleared, remaining geoEntries: %d", overlayId, geoEntries.size()));
}
protected void syncLayers(final Collection<String> removeCodes, final Collection<String> newCodes) {
final MfMapView mapView = this.mapViewRef.get();
if (mapView == null) {
return;
}
removeItems(removeCodes);
final Layers layers = mapView.getLayerManager().getLayers();
final int index = layers.indexOf(anchorLayer) + 1;
layers.addAll(index, layerList.getMatchingLayers(newCodes));
Log.d(String.format(Locale.ENGLISH, "Layers for id %d synced. Codes removed: %d, new codes: %d, geoEntries: %d", overlayId, removeCodes.size(), newCodes.size(), geoEntries.size()));
}
private void removeItems(final Collection<String> removeCodes) {
final MfMapView mapView = this.mapViewRef.get();
if (mapView == null) {
return;
}
final Layers layers = mapView.getLayerManager().getLayers();
for (final String code : removeCodes) {
final GeoitemLayer item = layerList.getItem(code);
if (item != null) {
geoEntries.remove(new GeoEntry(code, overlayId));
layers.remove(item);
layerList.remove(item);
}
}
}
static boolean mapMoved(final Viewport referenceViewport, final Viewport newViewport) {
return Math.abs(newViewport.getLatitudeSpan() - referenceViewport.getLatitudeSpan()) > 50e-6 ||
Math.abs(newViewport.getLongitudeSpan() - referenceViewport.getLongitudeSpan()) > 50e-6 ||
Math.abs(newViewport.center.getLatitude() - referenceViewport.center.getLatitude()) > referenceViewport.getLatitudeSpan() / 4 ||
Math.abs(newViewport.center.getLongitude() - referenceViewport.center.getLongitude()) > referenceViewport.getLongitudeSpan() / 4;
}
static synchronized void filter(final Collection<Geocache> caches) {
final boolean excludeMine = Settings.isExcludeMyCaches();
final boolean excludeDisabled = Settings.isExcludeDisabledCaches();
final List<Geocache> removeList = new ArrayList<>();
for (final Geocache cache : caches) {
if ((excludeMine && cache.isFound()) || (excludeMine && cache.isOwner()) || (excludeDisabled && cache.isDisabled()) || (excludeDisabled && cache.isArchived())) {
removeList.add(cache);
}
}
caches.removeAll(removeList);
}
private static GeoitemLayer getCacheItem(final Geocache cache, final TapHandler tapHandler) {
final Geopoint target = cache.getCoords();
final Bitmap marker = AndroidGraphicFactory.convertToBitmap(MapUtils.getCacheMarker(CgeoApplication.getInstance().getResources(), cache));
return new GeoitemLayer(cache.getGeoitemRef(), tapHandler, new LatLong(target.getLatitude(), target.getLongitude()), marker, 0, -marker.getHeight() / 2);
}
private static GeoitemLayer getWaypointItem(final Waypoint waypoint, final TapHandler tapHandler) {
final Geopoint target = waypoint.getCoords();
if (target != null) {
final Bitmap marker = AndroidGraphicFactory.convertToBitmap(MapUtils.getWaypointMarker(CgeoApplication.getInstance().getResources(), waypoint));
return new GeoitemLayer(waypoint.getGeoitemRef(), tapHandler, new LatLong(target.getLatitude(), target.getLongitude()), marker, 0, -marker.getHeight() / 2);
}
return null;
}
}