Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC - position indicator rotation performance issue #497

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion positioning/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 33
Expand All @@ -25,9 +26,24 @@ repositories {
flatDir {
dirs 'libs'
}
mavenCentral()
}

dependencies {
implementation(name: 'HERE-sdk', ext: 'aar')
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation "androidx.core:core-ktx:1.9.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.1"


annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.4.1"
implementation "androidx.lifecycle:lifecycle-common-java8:2.4.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
//implementation "androidx.lifecycle:lifecycle-extensions:2.4.1"

}
6 changes: 3 additions & 3 deletions positioning/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@
at https://developer.here.com/develop/mobile-sdks-->
<meta-data
android:name="com.here.android.maps.appid"
android:value="{YOUR_APP_ID}" />
android:value="${hereAppId}" />
<meta-data
android:name="com.here.android.maps.apptoken"
android:value="{YOUR_APP_CODE}" />
android:value="${hereAppToken}" />
<meta-data
android:name="com.here.android.maps.license.key"
android:value="{YOUR_LICENSE_KEY}" />
android:value="${hereKey}" />
<!--
Embed the HERE Positioning Service.
For more information, see the HERE Mobile SDK Developer's Guide
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.here.android.example.basicpositioningsolution

data class ArrowState(@JvmField val angle: Float, @JvmField val tilt: Float)
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,42 @@

package com.here.android.example.basicpositioningsolution;

import static com.here.android.example.basicpositioningsolution.HereHelperKt.getArrowBitmap;

import android.Manifest;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.ViewModelProvider;

import com.here.android.mpa.common.GeoCoordinate;
import com.here.android.mpa.common.GeoPosition;
import com.here.android.mpa.common.Image;
import com.here.android.mpa.common.LocationDataSourceHERE;
import com.here.android.mpa.common.OnEngineInitListener;
import com.here.android.mpa.common.PositioningManager;
import com.here.android.mpa.mapping.Map;
import com.here.android.mpa.guidance.MapMatcherMode;
import com.here.android.mpa.guidance.NavigationManager;
import com.here.android.mpa.mapping.AndroidXMapFragment;
import com.here.android.mpa.mapping.Map;
import com.here.android.mpa.mapping.MapState;
import com.here.android.mpa.mapping.PositionIndicator;
import com.here.android.positioning.StatusListener;

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Locale;

Expand Down Expand Up @@ -81,10 +88,19 @@ public class BasicPositioningActivity extends AppCompatActivity implements Posit
// text view instance for showing location information
private TextView mLocationInfo;

private MapViewModel mapViewModel;
private Bitmap arrowBitmap;
private Image positionImage;
private PositionIndicator positionIndicator;
private NavigationManager navigationManager;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

arrowBitmap = getArrowBitmap(this);
mapViewModel = new ViewModelProvider(this).get(MapViewModel.class);

if (hasPermissions(this, RUNTIME_PERMISSIONS)) {
initializeMapsAndPositioning();
} else {
Expand Down Expand Up @@ -136,6 +152,7 @@ public boolean onOptionsItemSelected(MenuItem item) {

@Override
public void onPositionUpdated(final PositioningManager.LocationMethod locationMethod, final GeoPosition geoPosition, final boolean mapMatched) {
mapViewModel.onHeading(geoPosition.getHeading());
final GeoCoordinate coordinate = geoPosition.getCoordinate();
if (mTransforming) {
mPendingUpdate = new Runnable() {
Expand Down Expand Up @@ -225,6 +242,17 @@ private AndroidXMapFragment getMapFragment() {
return (AndroidXMapFragment)getSupportFragmentManager().findFragmentById(R.id.mapfragment);
}

public void onArrowCallback(ArrowState arrowState) {
Log.d("ArrowState", "Rotating: " + (arrowState.angle) + ", Tilt: " + arrowState.tilt);
if (arrowBitmap != null) {
if (arrowState.angle == 0f && arrowState.tilt == 0f) {
positionImage.setBitmap(arrowBitmap);
} else {
HereHelperKt.rotateAndSkewImage(positionImage, arrowBitmap, arrowState.angle, arrowState.tilt);
}
}
positionIndicator.setMarker(positionImage);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment causes performance issue.

}
/**
* Initializes HERE Maps and HERE Positioning. Called after permission check.
*/
Expand All @@ -239,9 +267,20 @@ private void initializeMapsAndPositioning() {
public void onEngineInitializationCompleted(OnEngineInitListener.Error error) {
if (error == OnEngineInitListener.Error.NONE) {
map = mapFragment.getMap();
mapViewModel.addMap(map);
mapViewModel.setArrowCallback(arrowState -> {
onArrowCallback(arrowState);
return null;
});
positionImage = new Image();
positionImage.setBitmap(arrowBitmap);
map.setCenter(new GeoCoordinate(61.497961, 23.763606, 0.0), Map.Animation.NONE);
map.setZoomLevel(map.getMaxZoomLevel() - 1);
map.addTransformListener(BasicPositioningActivity.this);
mapFragment.getMapGesture().addOnGestureListener(new MapGestureListener((userAction) -> {
mapViewModel.onUserAction(userAction);
return null;
}), 0, false);
mPositioningManager = PositioningManager.getInstance();
mHereLocation = LocationDataSourceHERE.getInstance(
new StatusListener() {
Expand Down Expand Up @@ -305,12 +344,20 @@ public void onWifiIndoorPositioningDegraded() {
Toast.makeText(BasicPositioningActivity.this, "LocationDataSourceHERE.getInstance(): failed, exiting", Toast.LENGTH_LONG).show();
finish();
}

// Need this to get heading
navigationManager = NavigationManager.getInstance();
navigationManager.startTracking(MapMatcherMode.CAR);

mPositioningManager.setDataSource(mHereLocation);
mPositioningManager.addListener(new WeakReference<PositioningManager.OnPositionChangedListener>(
BasicPositioningActivity.this));
// start position updates, accepting GPS, network or indoor positions
if (mPositioningManager.start(PositioningManager.LocationMethod.GPS_NETWORK_INDOOR)) {
mapFragment.getPositionIndicator().setVisible(true);
positionIndicator = mapFragment.getPositionIndicator();
positionIndicator.setVisible(true);
positionIndicator.setSmoothPositionChange(true);
positionIndicator.setMarker(positionImage);
} else {
Toast.makeText(BasicPositioningActivity.this, "PositioningManager.start: failed, exiting", Toast.LENGTH_LONG).show();
finish();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.here.android.example.basicpositioningsolution

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Camera
import android.graphics.Canvas
import android.graphics.Matrix
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import com.here.android.mpa.common.Image
import com.here.android.mpa.mapping.Map
import com.here.android.mpa.mapping.MapState
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.scan

fun Context.getBitmapFromVectorDrawable(@DrawableRes drawableId: Int): Bitmap {
val drawable = ContextCompat.getDrawable(this, drawableId)
val bitmap = Bitmap.createBitmap(
drawable!!.intrinsicWidth,
drawable.intrinsicHeight, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}

fun Image.rotateAndSkewImage(srcBitmap: Bitmap, angle: Float, tilt: Float) {
val old = bitmap
setBitmap(srcBitmap.rotateAndSkewBitmap(angle, tilt))
old?.recycle()
}

fun Bitmap.rotateAndSkewBitmap(angle: Float, tilt: Float): Bitmap {
val transformationCamera = Camera()
val matrix = Matrix()
transformationCamera.rotateX(-tilt);
transformationCamera.rotateY(0f);
transformationCamera.rotateZ(0f);
transformationCamera.getMatrix(matrix);

val centerX = this.width / 2f;
val centerY = this.height / 2f;
matrix.preRotate(angle)
matrix.preTranslate(-centerX, -centerY)
matrix.postTranslate(centerX, centerY)

return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
}

fun getArrowBitmap(context: Context):Bitmap {
return context.getBitmapFromVectorDrawable(R.drawable.ic_map_arrow)
}

val Map.mapTransformationStateFlow: Flow<MapTransformationState>
get() = trackMapTransform()
.let { combine(it, it.scanCountActiveTransform(), ::Pair) }
.conflate()
.map { (event, count) ->
when (event) {
MapTransformEvent.MapTransformStart ->
MapTransformationState(
activeTransformationCount = count.toInt(),
mapState = null
)
is MapTransformEvent.MapTransformEnd -> {
MapTransformationState(
activeTransformationCount = count.toInt(),
mapState = event.mapState
)
}
}
}

data class MapTransformationState(
val activeTransformationCount: Int,
val mapState: MapState? = null
)

sealed class MapTransformEvent {
object MapTransformStart : MapTransformEvent()
data class MapTransformEnd(val mapState: MapState) : MapTransformEvent()
}


fun Map.trackMapTransform(): Flow<MapTransformEvent> =
callbackFlow {
val listener = object : Map.OnTransformListener {
override fun onMapTransformStart() {
trySend(MapTransformEvent.MapTransformStart)
}

override fun onMapTransformEnd(mapState: MapState) {
trySend(MapTransformEvent.MapTransformEnd(mapState))
}
}
addTransformListener(listener)
awaitClose { removeTransformListener(listener) }
}

fun Flow<MapTransformEvent>.scanCountActiveTransform(): Flow<Long> =
scan(0L) { acc, event ->
return@scan when (event) {
is MapTransformEvent.MapTransformEnd -> (acc - 1).coerceAtLeast(0)
MapTransformEvent.MapTransformStart -> acc + 1
}
}.conflate()
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.here.android.example.basicpositioningsolution

import android.graphics.PointF
import com.here.android.mpa.common.ViewObject
import com.here.android.mpa.mapping.MapGesture

internal class MapGestureListener(
private val userAction: (mapUserAction: MapUserAction) -> Unit
) : MapGesture.OnGestureListener {


override fun onPanStart() {
userAction(MapUserAction.CONTINUOUS_ACTION_STARTED)
}

override fun onPanEnd() {
userAction(MapUserAction.CONTINUOUS_ACTION_FINISHED)
}

override fun onMultiFingerManipulationStart() {
userAction(MapUserAction.CONTINUOUS_ACTION_STARTED)
}

override fun onMultiFingerManipulationEnd() {
userAction(MapUserAction.CONTINUOUS_ACTION_FINISHED)
}

override fun onMapObjectsSelected(p0: MutableList<ViewObject>): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onTapEvent(p0: PointF): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onDoubleTapEvent(p0: PointF): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onPinchLocked() {
userAction(MapUserAction.ONE_TIME_ACTION)
}

override fun onPinchZoomEvent(p0: Float, p1: PointF): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onRotateLocked() {
userAction(MapUserAction.ONE_TIME_ACTION)
}

override fun onRotateEvent(p0: Float): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onTiltEvent(p0: Float): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onLongPressEvent(p0: PointF): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}

override fun onLongPressRelease() {
userAction(MapUserAction.ONE_TIME_ACTION)
}

override fun onTwoFingerTapEvent(p0: PointF): Boolean {
userAction(MapUserAction.ONE_TIME_ACTION)
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.here.android.example.basicpositioningsolution

enum class MapUserAction {
CONTINUOUS_ACTION_STARTED,
CONTINUOUS_ACTION_FINISHED,
ONE_TIME_ACTION
}
Loading