/* * Copyright 2013 Ahmad Saleem * Copyright 2013 Hannes Janetzek * Copyright 2016-2017 devemux86 * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see . */ package org.oscim.app.location; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.view.animation.Animation; import android.view.animation.RotateAnimation; import android.widget.ImageView; import org.oscim.app.App; import org.oscim.app.R; import org.oscim.core.MapPosition; import org.oscim.event.Event; import org.oscim.layers.Layer; import org.oscim.map.Map; import org.oscim.renderer.LocationRenderer; @SuppressWarnings("deprecation") public class Compass extends Layer implements SensorEventListener, Map.UpdateListener, LocationRenderer.Callback { // final static Logger log = LoggerFactory.getLogger(Compass.class); public enum Mode { OFF, C2D, C3D, } private final SensorManager mSensorManager; private final ImageView mArrowView; // private final float[] mRotationM = new float[9]; private final float[] mRotationV = new float[3]; // private float[] mAccelV = new float[3]; // private float[] mMagnetV = new float[3]; // private boolean mLastAccelerometerSet; // private boolean mLastMagnetometerSet; private float mCurRotation; private float mCurTilt; private boolean mControlOrientation; private Mode mMode = Mode.OFF; private int mListeners; @Override public void onMapEvent(Event e, MapPosition mapPosition) { if (!mControlOrientation) { float rotation = -mapPosition.bearing; adjustArrow(rotation, rotation); } } public Compass(Context context, Map map) { super(map); mSensorManager = (SensorManager) context .getSystemService(Context.SENSOR_SERVICE); // List s = mSensorManager.getSensorList(Sensor.TYPE_ALL); // for (Sensor sensor : s) // log.debug(sensor.toString()); mArrowView = (ImageView) App.activity.findViewById(R.id.compass); setEnabled(false); } @Override public boolean hasRotation() { return true; } @Override public synchronized float getRotation() { return mCurRotation; } public void controlView(boolean enable) { mControlOrientation = enable; } public boolean controlView() { return mControlOrientation; } public void setMode(Mode mode) { if (mode == mMode) return; if (mode == Mode.OFF) { setEnabled(false); mMap.getEventLayer().enableRotation(true); mMap.getEventLayer().enableTilt(true); } else if (mMode == Mode.OFF) { setEnabled(true); } if (mode == Mode.C3D) { mMap.getEventLayer().enableRotation(false); mMap.getEventLayer().enableTilt(false); } else if (mode == Mode.C2D) { mMap.getEventLayer().enableRotation(false); mMap.getEventLayer().enableTilt(true); } mMode = mode; } public Mode getMode() { return mMode; } @Override public void setEnabled(boolean enabled) { mListeners += enabled ? 1 : -1; if (mListeners == 1) { resume(); } else if (mListeners == 0) { pause(); } else if (mListeners < 0) { // then bad mListeners = 0; } } public void resume() { if (mListeners <= 0) return; super.setEnabled(true); Sensor sensor; // Sensor sensor = // mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); // Sensor sensor = // mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); // sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); // mSensorManager.registerListener(this, sensor, // SensorManager.SENSOR_DELAY_UI); // sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD); // mSensorManager.registerListener(this, sensor, // SensorManager.SENSOR_DELAY_UI); sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI); // mLastAccelerometerSet = false; // mLastMagnetometerSet = false; } public void pause() { if (mListeners <= 0) return; super.setEnabled(false); mSensorManager.unregisterListener(this); } public void adjustArrow(float prev, float cur) { Animation an = new RotateAnimation(-prev, -cur, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); an.setDuration(100); an.setRepeatCount(0); an.setFillAfter(true); mArrowView.startAnimation(an); } @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() != Sensor.TYPE_ORIENTATION) return; System.arraycopy(event.values, 0, mRotationV, 0, event.values.length); // SensorManager.getRotationMatrixFromVector(mRotationM, event.values); // SensorManager.getOrientation(mRotationM, mRotationV); // int type = event.sensor.getType(); // if (type == Sensor.TYPE_ACCELEROMETER) { // System.arraycopy(event.values, 0, mAccelV, 0, event.values.length); // mLastAccelerometerSet = true; // } else if (type == Sensor.TYPE_MAGNETIC_FIELD) { // System.arraycopy(event.values, 0, mMagnetV, 0, event.values.length); // mLastMagnetometerSet = true; // } else { // return; // } // if (!mLastAccelerometerSet || !mLastMagnetometerSet) // return; // SensorManager.getRotationMatrix(mRotationM, null, mAccelV, mMagnetV); // SensorManager.getOrientation(mRotationM, mRotationV); // float rotation = (float) Math.toDegrees(mRotationV[0]); float rotation = mRotationV[0]; // handle(event); // if (!mOrientationOK) // return; // float rotation = (float) Math.toDegrees(mAzimuthRadians); float change = rotation - mCurRotation; if (change > 180) change -= 360; else if (change < -180) change += 360; // low-pass change *= 0.05; rotation = mCurRotation + change; if (rotation > 180) rotation -= 360; else if (rotation < -180) rotation += 360; // float tilt = (float) Math.toDegrees(mRotationV[1]); // float tilt = (float) Math.toDegrees(mPitchAxisRadians); float tilt = mRotationV[1]; mCurTilt = mCurTilt + 0.2f * (tilt - mCurTilt); if (mMode != Mode.OFF) { boolean redraw = false; if (Math.abs(change) > 0.01) { adjustArrow(mCurRotation, rotation); mMap.viewport().setRotation(-rotation); redraw = true; } if (mMode == Mode.C3D) redraw |= mMap.viewport().setTilt(-mCurTilt * 1.5f); if (redraw) mMap.updateMap(true); } mCurRotation = rotation; } // from http://stackoverflow.com/questions/16317599/android-compass-that- // can-compensate-for-tilt-and-pitch/16386066#16386066 // private int mGravityAccuracy; // private int mMagneticFieldAccuracy; // private float[] mGravityV = new float[3]; // private float[] mMagFieldV = new float[3]; // private float[] mEastV = new float[3]; // private float[] mNorthV = new float[3]; // // private float mNormGravity; // private float mNormMagField; // // private boolean mOrientationOK; // private float mAzimuthRadians; // private float mPitchRadians; // private float mPitchAxisRadians; // // private void handle(SensorEvent event) { // int SensorType = event.sensor.getType(); // switch (SensorType) { // case Sensor.TYPE_GRAVITY: // mLastAccelerometerSet = true; // System.arraycopy(event.values, 0, mGravityV, 0, mGravityV.length); // mNormGravity = (float) Math.sqrt(mGravityV[0] * mGravityV[0] // + mGravityV[1] * mGravityV[1] + mGravityV[2] // * mGravityV[2]); // for (int i = 0; i < mGravityV.length; i++) // mGravityV[i] /= mNormGravity; // break; // case Sensor.TYPE_MAGNETIC_FIELD: // mLastMagnetometerSet = true; // System.arraycopy(event.values, 0, mMagFieldV, 0, mMagFieldV.length); // mNormMagField = (float) Math.sqrt(mMagFieldV[0] * mMagFieldV[0] // + mMagFieldV[1] * mMagFieldV[1] + mMagFieldV[2] // * mMagFieldV[2]); // for (int i = 0; i < mMagFieldV.length; i++) // mMagFieldV[i] /= mNormMagField; // break; // } // if (!mLastAccelerometerSet || !mLastMagnetometerSet) // return; // // // first calculate the horizontal vector that points due east // float ex = mMagFieldV[1] * mGravityV[2] - mMagFieldV[2] * mGravityV[1]; // float ey = mMagFieldV[2] * mGravityV[0] - mMagFieldV[0] * mGravityV[2]; // float ez = mMagFieldV[0] * mGravityV[1] - mMagFieldV[1] * mGravityV[0]; // float normEast = (float) Math.sqrt(ex * ex + ey * ey + ez * ez); // // if (mNormGravity * mNormMagField * normEast < 0.1f) { // Typical values // are > 100. // // device is close to free fall (or in space?), or close to magnetic // north pole. // mOrientationOK = false; // return; // } // // mEastV[0] = ex / normEast; // mEastV[1] = ey / normEast; // mEastV[2] = ez / normEast; // // // next calculate the horizontal vector that points due north // float mdotG = (mGravityV[0] * mMagFieldV[0] // + mGravityV[1] * mMagFieldV[1] // + mGravityV[2] * mMagFieldV[2]); // // float nx = mMagFieldV[0] - mGravityV[0] * mdotG; // float ny = mMagFieldV[1] - mGravityV[1] * mdotG; // float nz = mMagFieldV[2] - mGravityV[2] * mdotG; // float normNorth = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); // // mNorthV[0] = nx / normNorth; // mNorthV[1] = ny / normNorth; // mNorthV[2] = nz / normNorth; // // // take account of screen rotation away from its natural rotation // //int rotation = // App.activity.getWindowManager().getDefaultDisplay().getRotation(); // float screenDirection = 0; // //switch(rotation) { // // case Surface.ROTATION_0: screenDirection = 0; break; // // case Surface.ROTATION_90: screenDirection = (float)Math.PI/2; break; // // case Surface.ROTATION_180: screenDirection = (float)Math.PI; break; // // case Surface.ROTATION_270: screenDirection = 3*(float)Math.PI/2; // break; // //} // // NB: the rotation matrix has now effectively been calculated. It // consists of // // the three vectors mEastV[], mNorthV[] and mGravityV[] // // // calculate all the required angles from the rotation matrix // // NB: see // http://math.stackexchange.com/questions/381649/whats-the-best-3d-angular- // // co-ordinate-system-for-working-with-smartfone-apps // float sin = mEastV[1] - mNorthV[0], cos = mEastV[0] + mNorthV[1]; // mAzimuthRadians = (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : // 0); // mPitchRadians = (float) Math.acos(mGravityV[2]); // // sin = -mEastV[1] - mNorthV[0]; // cos = mEastV[0] - mNorthV[1]; // // float aximuthPlusTwoPitchAxisRadians = // (float) (sin != 0 && cos != 0 ? Math.atan2(sin, cos) : 0); // // mPitchAxisRadians = (float) (aximuthPlusTwoPitchAxisRadians - // mAzimuthRadians) / 2; // mAzimuthRadians += screenDirection; // mPitchAxisRadians += screenDirection; // // mOrientationOK = true; // } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { // int type = sensor.getType(); // switch (type) { // case Sensor.TYPE_GRAVITY: // mGravityAccuracy = accuracy; // break; // case Sensor.TYPE_MAGNETIC_FIELD: // mMagneticFieldAccuracy = accuracy; // break; // } } }