package cgeo.geocaching.maps.mapsforge; import cgeo.geocaching.R; import cgeo.geocaching.location.Geopoint; import cgeo.geocaching.location.Viewport; import cgeo.geocaching.maps.CachesOverlay; import cgeo.geocaching.maps.PositionAndScaleOverlay; import cgeo.geocaching.maps.interfaces.GeneralOverlay; import cgeo.geocaching.maps.interfaces.GeoPointImpl; import cgeo.geocaching.maps.interfaces.MapControllerImpl; import cgeo.geocaching.maps.interfaces.MapProjectionImpl; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import android.support.annotation.NonNull; import org.mapsforge.v3.android.maps.MapView; import org.mapsforge.v3.android.maps.Projection; import org.mapsforge.v3.android.maps.mapgenerator.MapGenerator; import org.mapsforge.v3.android.maps.mapgenerator.MapGeneratorFactory; import org.mapsforge.v3.android.maps.mapgenerator.MapGeneratorInternal; import org.mapsforge.v3.android.maps.overlay.Overlay; import org.mapsforge.v3.core.GeoPoint; import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import android.view.MotionEvent; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; public class MapsforgeMapView extends MapView implements MapViewImpl { private GestureDetector gestureDetector; private OnMapDragListener onDragListener; private final MapsforgeMapController mapController = new MapsforgeMapController(getController(), getMapGenerator().getZoomLevelMax()); public MapsforgeMapView(final Context context, final AttributeSet attrs) { super(context, attrs); initialize(context); } private void initialize(final Context context) { if (isInEditMode()) { return; } gestureDetector = new GestureDetector(context, new GestureListener()); if (Settings.isScaleMapsforgeText()) { this.setTextScale(getResources().getDisplayMetrics().density); } } @Override public void draw(final Canvas canvas) { try { // Google Maps and OSM Maps use different zoom levels for the same view. // Here we don't want the Google Maps compatible zoom level, but the actual one. if (getActualMapZoomLevel() > 22) { // to avoid too close zoom level (mostly on Samsung Galaxy S series) getController().setZoom(22); } super.draw(canvas); } catch (final Exception e) { Log.e("MapsforgeMapView.draw", e); } } @Override public void displayZoomControls(final boolean takeFocus) { // nothing to do here } @Override public MapControllerImpl getMapController() { return mapController; } @Override @NonNull public GeoPointImpl getMapViewCenter() { final GeoPoint point = getMapPosition().getMapCenter(); return new MapsforgeGeoPoint(point.latitudeE6, point.longitudeE6); } @Override public Viewport getViewport() { return new Viewport(getMapViewCenter(), getLatitudeSpan() / 1e6, getLongitudeSpan() / 1e6); } @Override public void clearOverlays() { getOverlays().clear(); } @Override public MapProjectionImpl getMapProjection() { return new MapsforgeMapProjection(getProjection()); } @Override public CachesOverlay createAddMapOverlay(final Context context, final Drawable drawable) { final MapsforgeCacheOverlay ovl = new MapsforgeCacheOverlay(context, drawable); getOverlays().add(ovl); return ovl.getBase(); } @Override public PositionAndScaleOverlay createAddPositionAndScaleOverlay(final Geopoint coords, final String geocode) { final MapsforgeOverlay ovl = new MapsforgeOverlay(this, coords, geocode); getOverlays().add(ovl); return (PositionAndScaleOverlay) ovl.getBase(); } @Override public int getLatitudeSpan() { int span = 0; final Projection projection = getProjection(); if (projection != null && getHeight() > 0) { final GeoPoint low = projection.fromPixels(0, 0); final GeoPoint high = projection.fromPixels(0, getHeight()); if (low != null && high != null) { span = Math.abs(high.latitudeE6 - low.latitudeE6); } } return span; } @Override public int getLongitudeSpan() { int span = 0; final Projection projection = getProjection(); if (projection != null && getWidth() > 0) { final GeoPoint low = projection.fromPixels(0, 0); final GeoPoint high = projection.fromPixels(getWidth(), 0); if (low != null && high != null) { span = Math.abs(high.longitudeE6 - low.longitudeE6); } } return span; } @Override public void preLoad() { // Nothing to do here } /** * Get the map zoom level which is compatible with Google Maps. * * @return the current map zoom level +1 */ @Override public int getMapZoomLevel() { // Google Maps and OSM Maps use different zoom levels for the same view. // All OSM Maps zoom levels are offset by 1 so they match Google Maps. return getMapPosition().getZoomLevel() + 1; } /** * Get the actual map zoom level * * @return the current map zoom level with no adjustments */ private int getActualMapZoomLevel() { return getMapPosition().getZoomLevel(); } @Override public void setMapSource() { MapGeneratorInternal newMapType = MapGeneratorInternal.MAPNIK; final MapSource mapSource = Settings.getMapSource(); if (mapSource instanceof MapsforgeMapSource) { newMapType = ((MapsforgeMapSource) mapSource).getGenerator(); } final MapGenerator mapGenerator = MapGeneratorFactory.createMapGenerator(newMapType); // When swapping map sources, make sure we aren't exceeding max zoom. See bug #1535 final int maxZoom = mapGenerator.getZoomLevelMax(); if (getMapPosition().getZoomLevel() > maxZoom) { getController().setZoom(maxZoom); } setMapGenerator(mapGenerator); if (!mapGenerator.requiresInternetConnection()) { if (!new File(Settings.getMapFile()).exists()) { Toast.makeText( getContext(), getContext().getString(R.string.warn_nonexistant_mapfile), Toast.LENGTH_LONG) .show(); return; } setMapFile(new File(Settings.getMapFile())); if (!Settings.isValidMapFile(Settings.getMapFile())) { Toast.makeText( getContext(), getContext().getString(R.string.warn_invalid_mapfile), Toast.LENGTH_LONG) .show(); } } if (hasMapThemes()) { setMapTheme(); } } @Override public boolean hasMapThemes() { return !getMapGenerator().requiresInternetConnection(); } @Override public void setMapTheme() { final String customRenderTheme = Settings.getCustomRenderThemeFilePath(); if (StringUtils.isNotEmpty(customRenderTheme)) { try { setRenderTheme(new File(customRenderTheme)); } catch (final FileNotFoundException ignored) { Toast.makeText( getContext(), getContext().getString(R.string.warn_rendertheme_missing), Toast.LENGTH_LONG) .show(); } } else { setRenderTheme(DEFAULT_RENDER_THEME); } } @Override public void repaintRequired(final GeneralOverlay overlay) { if (overlay == null) { invalidate(); } else { try { final Overlay ovl = (Overlay) overlay.getOverlayImpl(); if (ovl != null) { ovl.requestRedraw(); } } catch (final Exception e) { Log.e("MapsforgeMapView.repaintRequired", e); } } } @Override public void setOnDragListener(final OnMapDragListener onDragListener) { this.onDragListener = onDragListener; } @Override public boolean onTouchEvent(final MotionEvent ev) { gestureDetector.onTouchEvent(ev); return super.onTouchEvent(ev); } private class GestureListener extends SimpleOnGestureListener { @Override public boolean onDoubleTap(final MotionEvent e) { if (onDragListener != null) { onDragListener.onDrag(); } return true; } @Override public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) { if (onDragListener != null) { onDragListener.onDrag(); } return super.onScroll(e1, e2, distanceX, distanceY); } } @Override public boolean needsInvertedColors() { return false; } }