package cgeo.geocaching.maps; import cgeo.geocaching.CachePopup; import cgeo.geocaching.R; import cgeo.geocaching.WaypointPopup; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.connector.gc.GCMap; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.CoordinatesType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.location.Geopoint; import cgeo.geocaching.location.IConversion; import cgeo.geocaching.maps.interfaces.CachesOverlayItemImpl; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.ItemizedOverlayImpl; import cgeo.geocaching.maps.interfaces.MapItemFactory; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.models.Geocache; import cgeo.geocaching.models.IWaypoint; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.storage.DataStore; import cgeo.geocaching.utils.Log; import android.content.Context; import android.content.res.Resources.NotFoundException; import android.graphics.Canvas; import android.graphics.DashPathEffect; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.PaintFlagsDrawFilter; import android.graphics.Point; import android.location.Location; import android.support.annotation.NonNull; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.commons.lang3.StringUtils; public class CachesOverlay extends AbstractItemizedOverlay { private List<CachesOverlayItemImpl> items = new ArrayList<>(); private Context context = null; private boolean displayCircles = false; private final Progress progress = new Progress(); private Paint blockedCircle = null; private PaintFlagsDrawFilter setFilter = null; private PaintFlagsDrawFilter removeFilter = null; private MapItemFactory mapItemFactory = null; public CachesOverlay(final ItemizedOverlayImpl ovlImpl, final Context contextIn) { super(ovlImpl); populate(); context = contextIn; final MapProvider mapProvider = Settings.getMapProvider(); mapItemFactory = mapProvider.getMapItemFactory(); } void updateItems(final CachesOverlayItemImpl item) { final List<CachesOverlayItemImpl> itemsPre = new ArrayList<>(); itemsPre.add(item); updateItems(itemsPre); } void updateItems(final List<CachesOverlayItemImpl> itemsPre) { if (itemsPre == null) { return; } for (final CachesOverlayItemImpl item : itemsPre) { item.setMarker(boundCenterBottom(item.getMarker(0))); } // ensure no interference between the draw and content changing routines getOverlayImpl().lock(); try { items = new ArrayList<>(itemsPre); setLastFocusedItemIndex(-1); // to reset tap during data change populate(); } finally { getOverlayImpl().unlock(); } } boolean getCircles() { return displayCircles; } void switchCircles() { displayCircles = !displayCircles; } @Override public void draw(final Canvas canvas, final MapViewImpl mapView, final boolean shadow) { // prevent content changes getOverlayImpl().lock(); try { drawInternal(canvas, mapView.getMapProjection()); super.draw(canvas, mapView, false); } finally { getOverlayImpl().unlock(); } } @Override public void drawOverlayBitmap(final Canvas canvas, final Point drawPosition, final MapProjectionImpl projection, final byte drawZoomLevel) { drawInternal(canvas, projection); super.drawOverlayBitmap(canvas, drawPosition, projection, drawZoomLevel); } private void drawInternal(final Canvas canvas, final MapProjectionImpl projection) { if (!displayCircles || items.isEmpty()) { return; } lazyInitializeDrawingObjects(); canvas.setDrawFilter(setFilter); final int height = canvas.getHeight(); final int width = canvas.getWidth(); final int radius = calculateDrawingRadius(projection); final Point center = new Point(); for (final CachesOverlayItemImpl item : items) { if (item.applyDistanceRule()) { final Geopoint itemCoord = item.getCoord().getCoords(); final GeoPointImpl itemGeo = mapItemFactory.getGeoPointBase(itemCoord); projection.toPixels(itemGeo, center); if (center.x > -radius && center.y > -radius && center.x < width + radius && center.y < height + radius) { // dashed circle around the waypoint blockedCircle.setColor(0x66BB0000); blockedCircle.setStyle(Style.STROKE); canvas.drawCircle(center.x, center.y, radius, blockedCircle); // filling the circle area with a transparent color blockedCircle.setColor(0x44BB0000); blockedCircle.setStyle(Style.FILL); canvas.drawCircle(center.x, center.y, radius, blockedCircle); } } } canvas.setDrawFilter(removeFilter); } /** * Calculate the radius of the circle to be drawn for the first item only. Those circles are only 528 feet * (approximately 161 meters) in reality and therefore the minor changes due to the projection will not make any * visible difference at the zoom levels which are used to see the circles. * */ private int calculateDrawingRadius(final MapProjectionImpl projection) { final float[] distanceArray = new float[1]; final Geopoint itemCoord = items.get(0).getCoord().getCoords(); Location.distanceBetween(itemCoord.getLatitude(), itemCoord.getLongitude(), itemCoord.getLatitude(), itemCoord.getLongitude() + 1, distanceArray); final float longitudeLineDistance = distanceArray[0]; final GeoPointImpl itemGeo = mapItemFactory.getGeoPointBase(itemCoord); final Geopoint leftCoords = new Geopoint(itemCoord.getLatitude(), itemCoord.getLongitude() - 528.0 * IConversion.FEET_TO_KILOMETER * 1000.0 / longitudeLineDistance); final GeoPointImpl leftGeo = mapItemFactory.getGeoPointBase(leftCoords); final Point center = new Point(); projection.toPixels(itemGeo, center); final Point left = new Point(); projection.toPixels(leftGeo, left); return center.x - left.x; } private void lazyInitializeDrawingObjects() { if (blockedCircle == null) { blockedCircle = new Paint(); blockedCircle.setAntiAlias(true); blockedCircle.setStrokeWidth(2.0f); blockedCircle.setARGB(127, 0, 0, 0); blockedCircle.setPathEffect(new DashPathEffect(new float[] { 3, 2 }, 0)); } if (setFilter == null) { setFilter = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG); } if (removeFilter == null) { removeFilter = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0); } } @Override public boolean onTap(final int index) { try { if (items.size() <= index) { return false; } progress.show(context, context.getString(R.string.map_live), context.getString(R.string.cache_dialog_loading_details), true, null); // prevent concurrent changes getOverlayImpl().lock(); CachesOverlayItemImpl item = null; try { if (index < items.size()) { item = items.get(index); } } finally { getOverlayImpl().unlock(); } if (item == null) { return false; } final IWaypoint coordinate = item.getCoord(); final CoordinatesType coordType = coordinate.getCoordType(); if (coordType == CoordinatesType.CACHE && StringUtils.isNotBlank(coordinate.getGeocode())) { final Geocache cache = DataStore.loadCache(coordinate.getGeocode(), LoadFlags.LOAD_CACHE_OR_DB); if (cache != null) { final RequestDetailsThread requestDetailsThread = new RequestDetailsThread(cache); if (!requestDetailsThread.requestRequired()) { // don't show popup if we have enough details progress.dismiss(); } requestDetailsThread.start(); return true; } progress.dismiss(); return false; } if (coordType == CoordinatesType.WAYPOINT && coordinate.getId() >= 0) { CGeoMap.markCacheAsDirty(coordinate.getGeocode()); WaypointPopup.startActivity(context, coordinate.getId(), coordinate.getGeocode()); } else { progress.dismiss(); return false; } progress.dismiss(); } catch (final NotFoundException e) { Log.e("CachesOverlay.onTap", e); progress.dismiss(); } return true; } @Override public CachesOverlayItemImpl createItem(final int index) { try { return items.get(index); } catch (final Exception e) { Log.w("CachesOverlay.createItem", e); } return null; } @Override public int size() { return items.size(); } private class RequestDetailsThread extends Thread { @NonNull private final Geocache cache; RequestDetailsThread(@NonNull final Geocache cache) { this.cache = cache; } public boolean requestRequired() { return cache.getType() == CacheType.UNKNOWN || cache.getDifficulty() == 0; } @Override public void run() { if (requestRequired()) { try { /* final SearchResult search = */GCMap.searchByGeocodes(Collections.singleton(cache.getGeocode())); } catch (final Exception ex) { Log.w("Error requesting cache popup info", ex); ActivityMixin.showToast(context, R.string.err_request_popup_info); } } CGeoMap.markCacheAsDirty(cache.getGeocode()); CachePopup.startActivity(context, cache.getGeocode()); progress.dismiss(); } } }