All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.osmdroid.views.overlay.compass.CompassOverlay Maven / Gradle / Ivy

There is a newer version: 6.1.20
Show newest version
// Created by plusminus on 22:01:11 - 29.09.2008
package org.osmdroid.views.overlay.compass;

import org.osmdroid.DefaultResourceProxyImpl;
import org.osmdroid.ResourceProxy;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.IOverlayMenuProvider;
import org.osmdroid.views.overlay.SafeDrawOverlay;
import org.osmdroid.views.safecanvas.ISafeCanvas;
import org.osmdroid.views.safecanvas.SafePaint;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Picture;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.FloatMath;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.WindowManager;

/**
 * 
 * @author Marc Kurtz
 * @author Manuel Stahl
 * 
 */
public class CompassOverlay extends SafeDrawOverlay implements IOverlayMenuProvider, IOrientationConsumer
{
    protected final MapView mMapView;
    private final Display mDisplay;

    public IOrientationProvider mOrientationProvider;

    protected final SafePaint mPaint = new SafePaint();
    protected final Picture mCompassFrame = new Picture();
    protected final Picture mCompassRose = new Picture();
    private final Matrix mCompassMatrix = new Matrix();
    private boolean mIsCompassEnabled;

    /**
     * The bearing, in degrees east of north, or NaN if none has been set.
     */
    private float mAzimuth = Float.NaN;

    private float mCompassCenterX = 35.0f;
    private float mCompassCenterY = 35.0f;
    private final float mCompassRadius = 20.0f;

    protected final float mCompassFrameCenterX;
    protected final float mCompassFrameCenterY;
    protected final float mCompassRoseCenterX;
    protected final float mCompassRoseCenterY;

    public static final int MENU_COMPASS = getSafeMenuId();

    private boolean mOptionsMenuEnabled = true;

    // ===========================================================
    // Constructors
    // ===========================================================

    public CompassOverlay(final Context context, IOrientationProvider orientationProvider,
            final MapView mapView)
    {
        this(context, orientationProvider, mapView, new DefaultResourceProxyImpl(context));
    }

    public CompassOverlay(final Context context, IOrientationProvider orientationProvider,
            final MapView mapView, final ResourceProxy pResourceProxy)
    {
        super(pResourceProxy);
        mOrientationProvider = orientationProvider;
        mMapView = mapView;
        final WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        mDisplay = windowManager.getDefaultDisplay();

        createCompassFramePicture();
        createCompassRosePicture();

        mCompassFrameCenterX = mCompassFrame.getWidth() / 2 - 0.5f;
        mCompassFrameCenterY = mCompassFrame.getHeight() / 2 - 0.5f;
        mCompassRoseCenterX = mCompassRose.getWidth() / 2 - 0.5f;
        mCompassRoseCenterY = mCompassRose.getHeight() / 2 - 0.5f;
    }

    @Override
    public void onDetach(MapView mapView) {
        this.disableCompass();
        super.onDetach(mapView);
    }

    private void invalidateCompass()
    {
        Rect screenRect = mMapView.getProjection().getScreenRect();
        final int frameLeft = screenRect.left + (mMapView.getWidth() / 2)
                + (int) FloatMath.ceil((mCompassCenterX - mCompassFrameCenterX) * mScale);
        final int frameTop = screenRect.top + (mMapView.getHeight() / 2)
                + (int) FloatMath.ceil((mCompassCenterY - mCompassFrameCenterY) * mScale);
        final int frameRight = screenRect.left + (mMapView.getWidth() / 2)
                + (int) FloatMath.ceil((mCompassCenterX + mCompassFrameCenterX) * mScale);
        final int frameBottom = screenRect.top + (mMapView.getHeight() / 2)
                + (int) FloatMath.ceil((mCompassCenterY + mCompassFrameCenterY) * mScale);

        // Expand by 2 to cover stroke width
        mMapView.postInvalidate(frameLeft - 2, frameTop - 2, frameRight + 2, frameBottom + 2);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public void setCompassCenter(final float x, final float y)
    {
        mCompassCenterX = x;
        mCompassCenterY = y;
    }

    public IOrientationProvider getOrientationProvider()
    {
        return mOrientationProvider;
    }

    protected void setOrientationProvider(IOrientationProvider orientationProvider)
    {
        if (mOrientationProvider != null)
            mOrientationProvider.stopOrientationProvider();

        mOrientationProvider = orientationProvider;
    }

    protected void drawCompass(final ISafeCanvas canvas, final float bearing, final Rect screenRect)
    {
        final float centerX = mCompassCenterX * mScale;
        final float centerY = mCompassCenterY * mScale + (canvas.getHeight() - mMapView.getHeight());

        mCompassMatrix.setTranslate(-mCompassFrameCenterX, -mCompassFrameCenterY);
        mCompassMatrix.postTranslate(centerX, centerY);

        canvas.save();
        canvas.setMatrix(mCompassMatrix);
        canvas.drawPicture(mCompassFrame);

        mCompassMatrix.setRotate(-bearing, mCompassRoseCenterX, mCompassRoseCenterY);
        mCompassMatrix.postTranslate(-mCompassRoseCenterX, -mCompassRoseCenterY);
        mCompassMatrix.postTranslate(centerX, centerY);

        canvas.setMatrix(mCompassMatrix);
        canvas.drawPicture(mCompassRose);
        canvas.restore();
    }

    // ===========================================================
    // Methods from SuperClass/Interfaces
    // ===========================================================

    @Override
    protected void drawSafe(ISafeCanvas canvas, MapView mapView, boolean shadow)
    {
        if (shadow) {
            return;
        }

        if (isCompassEnabled() && !Float.isNaN(mAzimuth)) {
            drawCompass(canvas, mAzimuth + getDisplayOrientation(), mapView.getProjection().getScreenRect());
        }
    }

    // ===========================================================
    // Menu handling methods
    // ===========================================================

    @Override
    public void setOptionsMenuEnabled(final boolean pOptionsMenuEnabled)
    {
        this.mOptionsMenuEnabled = pOptionsMenuEnabled;
    }

    @Override
    public boolean isOptionsMenuEnabled()
    {
        return this.mOptionsMenuEnabled;
    }

	@Override
	public boolean onCreateOptionsMenu(final Menu pMenu, final int pMenuIdOffset,
			final MapView pMapView)
	{
		pMenu.add(0, MENU_COMPASS + pMenuIdOffset, Menu.NONE,
				mResourceProxy.getString(ResourceProxy.string.compass))
				.setIcon(mResourceProxy.getDrawable(ResourceProxy.bitmap.ic_menu_compass))
				.setCheckable(true);

		return true;
	}

    @Override
    public boolean onPrepareOptionsMenu(final Menu pMenu, final int pMenuIdOffset, final MapView pMapView)
    {
		pMenu.findItem(MENU_COMPASS + pMenuIdOffset).setChecked(this.isCompassEnabled());
        return false;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem pItem, final int pMenuIdOffset, final MapView pMapView)
    {
        final int menuId = pItem.getItemId() - pMenuIdOffset;
        if (menuId == MENU_COMPASS) {
            if (this.isCompassEnabled()) {
                this.disableCompass();
            } else {
                this.enableCompass(this.getOrientationProvider());
            }
            return true;
        } else {
            return false;
        }
    }

    // ===========================================================
    // Methods
    // ===========================================================

    @Override
    public void onOrientationChanged(float orientation, IOrientationProvider source)
    {
        mAzimuth = orientation;
        this.invalidateCompass();
    }

    /**
     * Enable receiving orientation updates from the provided IOrientationProvider and show a compass on the
     * map. You will likely want to call enableCompass() from your Activity's Activity.onResume() method, to
     * enable the features of this overlay. Remember to call the corresponding disableCompass() in your
     * Activity's Activity.onPause() method to turn off updates when in the background.
     */
    public boolean enableCompass(IOrientationProvider orientationProvider)
    {
        boolean result = true;

        if (orientationProvider == null)
            throw new RuntimeException("You must pass an IOrientationProvider to enableCompass()");

        this.setOrientationProvider(orientationProvider);

        result = mOrientationProvider.startOrientationProvider(this);
        mIsCompassEnabled = result;

        // Update the screen to see changes take effect
        if (mMapView != null) {
            this.invalidateCompass();
        }

        return result;
    }

    /**
     * Disable orientation updates
     */
    public void disableCompass()
    {
        mIsCompassEnabled = false;

        if (mOrientationProvider != null) {
            mOrientationProvider.stopOrientationProvider();
        }

        // Reset values
        mAzimuth = Float.NaN;

        // Update the screen to see changes take effect
        if (mMapView != null) {
            this.invalidateCompass();
        }
    }

    /**
     * If enabled, the map is receiving orientation updates and drawing your location on the map.
     * 
     * @return true if enabled, false otherwise
     */
    public boolean isCompassEnabled()
    {
        return mIsCompassEnabled;
    }

    public float getOrientation()
    {
        return mAzimuth;
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    private Point calculatePointOnCircle(final float centerX, final float centerY, final float radius,
            final float degrees)
    {
        // for trigonometry, 0 is pointing east, so subtract 90
        // compass degrees are the wrong way round
        final double dblRadians = Math.toRadians(-degrees + 90);

        final int intX = (int) (radius * Math.cos(dblRadians));
        final int intY = (int) (radius * Math.sin(dblRadians));

        return new Point((int) centerX + intX, (int) centerY - intY);
    }

    private void drawTriangle(final Canvas canvas, final float x, final float y, final float radius,
            final float degrees, final Paint paint)
    {
        canvas.save();
        final Point point = this.calculatePointOnCircle(x, y, radius, degrees);
        canvas.rotate(degrees, point.x, point.y);
        final Path p = new Path();
        p.moveTo(point.x - 2 * mScale, point.y);
        p.lineTo(point.x + 2 * mScale, point.y);
        p.lineTo(point.x, point.y - 5 * mScale);
        p.close();
        canvas.drawPath(p, paint);
        canvas.restore();
    }

    private int getDisplayOrientation()
    {
        switch (mDisplay.getOrientation()) {
            case Surface.ROTATION_90:
                return 90;
            case Surface.ROTATION_180:
                return 180;
            case Surface.ROTATION_270:
                return 270;
            default:
                return 0;
        }
    }

    private void createCompassFramePicture()
    {
        // The inside of the compass is white and transparent
        final Paint innerPaint = new Paint();
        innerPaint.setColor(Color.WHITE);
        innerPaint.setAntiAlias(true);
        innerPaint.setStyle(Style.FILL);
        innerPaint.setAlpha(200);

        // The outer part (circle and little triangles) is gray and transparent
        final Paint outerPaint = new Paint();
        outerPaint.setColor(Color.GRAY);
        outerPaint.setAntiAlias(true);
        outerPaint.setStyle(Style.STROKE);
        outerPaint.setStrokeWidth(2.0f);
        outerPaint.setAlpha(200);

        final int picBorderWidthAndHeight = (int) ((mCompassRadius + 5) * 2);
        final int center = picBorderWidthAndHeight / 2;

        final Canvas canvas = mCompassFrame.beginRecording(picBorderWidthAndHeight, picBorderWidthAndHeight);

        // draw compass inner circle and border
        canvas.drawCircle(center, center, mCompassRadius * mScale, innerPaint);
        canvas.drawCircle(center, center, mCompassRadius * mScale, outerPaint);

        // Draw little triangles north, south, west and east (don't move)
        // to make those move use "-bearing + 0" etc. (Note: that would mean to draw the triangles
        // in the onDraw() method)
        drawTriangle(canvas, center, center, mCompassRadius * mScale, 0, outerPaint);
        drawTriangle(canvas, center, center, mCompassRadius * mScale, 90, outerPaint);
        drawTriangle(canvas, center, center, mCompassRadius * mScale, 180, outerPaint);
        drawTriangle(canvas, center, center, mCompassRadius * mScale, 270, outerPaint);

        mCompassFrame.endRecording();
    }

    private void createCompassRosePicture()
    {
        // Paint design of north triangle (it's common to paint north in red color)
        final Paint northPaint = new Paint();
        northPaint.setColor(0xFFA00000);
        northPaint.setAntiAlias(true);
        northPaint.setStyle(Style.FILL);
        northPaint.setAlpha(220);

        // Paint design of south triangle (black)
        final Paint southPaint = new Paint();
        southPaint.setColor(Color.BLACK);
        southPaint.setAntiAlias(true);
        southPaint.setStyle(Style.FILL);
        southPaint.setAlpha(220);

        // Create a little white dot in the middle of the compass rose
        final Paint centerPaint = new Paint();
        centerPaint.setColor(Color.WHITE);
        centerPaint.setAntiAlias(true);
        centerPaint.setStyle(Style.FILL);
        centerPaint.setAlpha(220);

        // final int picBorderWidthAndHeight = (int) ((mCompassRadius + 5) * 2 * mScale);
        final int picBorderWidthAndHeight = (int) ((mCompassRadius + 5) * 2);
        final int center = picBorderWidthAndHeight / 2;

        final Canvas canvas = mCompassRose.beginRecording(picBorderWidthAndHeight, picBorderWidthAndHeight);

        // Blue triangle pointing north
        final Path pathNorth = new Path();
        pathNorth.moveTo(center, center - (mCompassRadius - 3) * mScale);
        pathNorth.lineTo(center + 4 * mScale, center);
        pathNorth.lineTo(center - 4 * mScale, center);
        pathNorth.lineTo(center, center - (mCompassRadius - 3) * mScale);
        pathNorth.close();
        canvas.drawPath(pathNorth, northPaint);

        // Red triangle pointing south
        final Path pathSouth = new Path();
        pathSouth.moveTo(center, center + (mCompassRadius - 3) * mScale);
        pathSouth.lineTo(center + 4 * mScale, center);
        pathSouth.lineTo(center - 4 * mScale, center);
        pathSouth.lineTo(center, center + (mCompassRadius - 3) * mScale);
        pathSouth.close();
        canvas.drawPath(pathSouth, southPaint);

        // Draw a little white dot in the middle
        canvas.drawCircle(center, center, 2, centerPaint);

        mCompassRose.endRecording();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy