Android - How to make google Maps display a polyline that animates sequenctial flashing dots - google-maps-markers

I am searching for a way to animate the dots between two markers on a google map in android device.
So what i want in the end is the following line between the two images:
and it would be used like this typical google polyline implementation:
lets say there is a point A and a Point B. if im directing the user to point B, then the line animates to from point A to point B so the user knows to walk in this direction.
to achieve this i thought i could get the points out of the polyLine and remove them and add them back
rapidily. so lets say i had 5 points in the polyLine, i would remove position 1 , then put it back, then remove position 2, and put it back, to simulate this animation.
but it does not work . once hte polyline is set it seems i cannot alter it. you have any suggestions ?
val dotPattern = Arrays.asList(Dot(), Gap(convertDpToPixel(7).toFloat()))
val polyLineOptions: PolylineOptions = PolylineOptions()
.add(usersLocation)
.add(users_destination)
.pattern(dotPattern)
.width(convertDpToPixel(6).toFloat())
dottedPolyLine = googleMap.addPolyline(polyLineOptions)
dottedPolyLine?.points?.removeAt(1) // here as a test if my idea i try removing a point but it looks like a point here means current location or destination so there always 2. i thought a point would be one of the dots.

You can use MapView-based custom view View Canvas animationlike in this answer:
This approach requires
MapView-based
custom
view,
that implements:
drawing over the MapView canvas;
customizing line styles (circles instead of a simple line);
binding path to Lat/Lon coordinates of map
performing animation.
Drawing over the MapView needs to override dispatchDraw().
Customizing line styles needs
setPathEffect()
method of
Paint
class that allows to create create path for "circle stamp" (in
pixels), which will repeated every "advance" (in pixels too),
something like that:
mCircleStampPath = new Path(); mCircleStampPath.addCircle(0,0,
CIRCLE_RADIUS, Path.Direction.CCW); mCircleStampPath.close();
For binding path on screen to Lat/Lon coordinates
Projection.toScreenLocation()
needed, that requires
GoogleMap
object so custom view should implements OnMapReadyCallback for
receive it. For continuous animation
postInvalidateDelayed()
can be used.
but not draw path directly from point A to point B, but from point A to point C that animated from A to B. To get current position of point C you can use SphericalUtil.interpolate() from Google Maps Android API Utility Library. Something like that:
public class EnhancedMapView extends MapView implements OnMapReadyCallback {
private static final float CIRCLE_RADIUS = 10;
private static final float CIRCLE_ADVANCE = 3.5f * CIRCLE_RADIUS; // spacing between each circle stamp
private static final int FRAMES_PER_SECOND = 30;
private static final int ANIMATION_DURATION = 10000;
private OnMapReadyCallback mMapReadyCallback;
private GoogleMap mGoogleMap;
private LatLng mPointA;
private LatLng mPointB;
private LatLng mPointC;
private float mCirclePhase = 0; // amount to offset before the first circle is stamped
private Path mCircleStampPath;
private Paint mPaintLine;
private final Path mPathFromAtoC = new Path();
private long mStartTime;
private long mElapsedTime;
public EnhancedMapView(#NonNull Context context) {
super(context);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public EnhancedMapView(#NonNull Context context, #Nullable GoogleMapOptions options) {
super(context, options);
init();
}
#Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.save();
drawLineFomAtoB(canvas);
canvas.restore();
// perform one shot animation
mElapsedTime = System.currentTimeMillis() - mStartTime;
if (mElapsedTime < ANIMATION_DURATION) {
postInvalidateDelayed(1000 / FRAMES_PER_SECOND);
}
}
private void drawLineFomAtoB(Canvas canvas) {
if (mGoogleMap == null || mPointA == null || mPointB == null) {
return;
}
// interpolate current position
mPointC = SphericalUtil.interpolate(mPointA, mPointB, (float) mElapsedTime / (float)ANIMATION_DURATION);
final Projection mapProjection = mGoogleMap.getProjection();
final Point pointA = mapProjection.toScreenLocation(mPointA);
final Point pointC = mapProjection.toScreenLocation(mPointC);
mPathFromAtoC.rewind();
mPathFromAtoC.moveTo(pointC.x, pointC.y);
mPathFromAtoC.lineTo(pointA.x, pointA.y);
// change phase for circles shift
mCirclePhase = (mCirclePhase < CIRCLE_ADVANCE)
? mCirclePhase + 1.0f
: 0;
mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));
canvas.drawPath(mPathFromAtoC, mPaintLine);
}
private void init() {
setWillNotDraw(false);
mCircleStampPath = new Path();
mCircleStampPath.addCircle(0,0, CIRCLE_RADIUS, Path.Direction.CCW);
mCircleStampPath.close();
mPaintLine = new Paint();
mPaintLine.setColor(Color.BLACK);
mPaintLine.setStrokeWidth(1);
mPaintLine.setStyle(Paint.Style.STROKE);
mPaintLine.setPathEffect(new PathDashPathEffect(mCircleStampPath, CIRCLE_ADVANCE, mCirclePhase, PathDashPathEffect.Style.ROTATE));
// start animation
mStartTime = System.currentTimeMillis();
postInvalidate();
}
#Override
public void getMapAsync(OnMapReadyCallback callback) {
mMapReadyCallback = callback;
super.getMapAsync(this);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
mGoogleMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
invalidate();
}
});
if (mMapReadyCallback != null) {
mMapReadyCallback.onMapReady(googleMap);
}
}
public void setPoints(LatLng pointA, LatLng pointB) {
mPointA = pointA;
mPointB = pointB;
}
}
NB! This is just idea, not full tested code.

Related

LibGDX: Scale Movement Projection Line to Always be the Same Length

I'm working on a game in LibGDX. Right now, I am working on drawing a line from a moving entity's body current position in the direction that it is moving. Maybe I didn't word that correctly, so here's my very artistic representation of what I'm talking about.
The problem that I'm having is that vertical lines are always much longer than diagonal lines, and diagonal lines are always much longer than horizontal lines. What I'm wanting is for the line being projected from the entity to always be the same length regardless of the direction.
Below is the code used for drawing lines from the center of an entity's body. As you can see, I am scaling the line (e.g., by 25.0f). Maybe there's a formula that I could use to dynamically change this scalar depending on the direction?
public class BodyMovementProjection implements Updatable {
public final Body body;
public final ShapeRenderer shapeRenderer;
public boolean debugProjection = false;
public float scalar = 25.0f;
private final Vector2 posThisFrame = new Vector2();
private final Vector2 posLastFrame = new Vector2();
private final Vector2 projection = new Vector2();
private float[] debugColorVals = new float[4];
public BodyMovementProjection(Body body) {
this.body = body;
this.shapeRenderer = body.entity.gameScreen.shapeRenderer;
} // BodyMovementProjection constructor
#Override
public void update() {
body.aabb.getCenter(posThisFrame);
posLastFrame.set(posThisFrame).sub(body.bodyMovementTracker.getSpritePosDelta());
projection.set(posThisFrame).sub(posLastFrame).scl(scalar).add(posLastFrame);
if (debugProjection) {
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(debugColorVals[0], debugColorVals[1], debugColorVals[2], debugColorVals[3]);
shapeRenderer.line(posLastFrame, projection);
shapeRenderer.end();
} // if
} // update
public void setDebugColorVals(float r, float g, float b, float a) {
debugColorVals[0] = r;
debugColorVals[1] = g;
debugColorVals[2] = b;
debugColorVals[3] = a;
} // setDebugColorVals
} // BodyMovementProjection
Normalize before you scale.
By normalizing you are making the directional vector 1 unit in length, and by scaling it after by 25 you get a vector that is 25 units every time, regardless of how far apart thw current and previous positions are.

Google Maps Android No Cluster/DeCluster on Zoom In/Out

I have implemented Google Maps Clustering on the iOS version of my app and it works fine. I am now attempting to implement Clustering on the Android version using the android maps utility library and it is not Declustering/Clustering when I zoom in or out.
Here is the cluster item class:
public class MyClusterItem implements ClusterItem {
private final LatLng position;
private final BitmapDescriptor icon;
private final String tag;
public MyClusterItem(double lat, double lng, BitmapDescriptor icon, String tag) {
this.position = new LatLng(lat, lng);
this.icon = icon;
this.tag = tag;
}
#Override
public LatLng getPosition() {
return position;
}
#Override
public String getTitle() {
return null;
}
#Override
public String getSnippet() {
return null;
}
public BitmapDescriptor getIcon() {
return icon;
}
public String getTag() {
return tag;
}
}
Here is how the cluster manager is implemented:
public void onMapReady(GoogleMap googleMap) {
//Log.i ( TAG, "Google Map Ready Callback" );
mMap = googleMap;
//mMap.setOnMarkerClickListener(this);
// Point the map's listeners at the listeners implemented by the cluster
// manager.
mMap.setOnCameraIdleListener(mClusterManager);
mMap.getUiSettings().setMapToolbarEnabled(false);
mMap.getUiSettings().setMyLocationButtonEnabled(false);
mMap.getUiSettings().setRotateGesturesEnabled(false);
if (ActivityCompat.checkSelfPermission(MapsActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED && isLocationEnabled) {
mMap.setMyLocationEnabled(true);
}
float mCurrentZoom = googleMap.getCameraPosition().zoom;
// Initialize the manager with the context and the map.
// (Activity extends context, so we can pass 'this' in the constructor.)
mClusterManager = new ClusterManager<MyClusterItem>(this, mMap);
mClusterManager.setOnClusterItemClickListener(mClusterItemClickListener);
mMap.setOnMarkerClickListener(mClusterManager);
mClusterManager.setOnClusterClickListener(new ClusterManager.OnClusterClickListener<MyClusterItem>() {
#Override
public boolean onClusterClick(final Cluster<MyClusterItem> cluster) {
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
cluster.getPosition(), (float) Math.floor(mMap
.getCameraPosition().zoom + 1)), 300,
null);
return true;
}
});
}
Cluster Items are added from a Firebase database as follows:
markerRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
try {
tableCount = tableCount+1;
markerKey = dataSnapshot.getKey();
markerData = dataSnapshot.getValue(Markers.class);
MyClusterItem markerItem = new PicNickClusterItem(markerData.y, markerData.x, BitmapDescriptorFactory.fromResource(R.drawable.verified), markerKey);
mClusterItemMap.put(markerKey, markerItem);
mClusterManager.addItem(markerItem);
if(tableCount >= numTables) {
//cluster markers
mClusterManager.cluster();
}
} catch (Error error) {
noFirebaseAccessAlert();
}
}
}
Screenshot of the initial map:
Screenshot when zoomed out:
Screenshot when zoomed in:
Cluster Items are not being clustered/declustered when zooming out/in and there is no animation. As I zoom out I would expect the cluster items to move into clusters and as I zoom in Cluster Items to separate from clusters as they do in the iOS version of the app. Also when I tap on a cluster marker, the map zooms in, but the cluster does not expand into cluster items. None of the cluster items have the same lat/lng and are separated by significant distances.
In the iOS version I was able to set the algorithm (non hierarchical distance based) & several "buckets" for clustering (and specify custom icons for each). I need to have both versions looks and respond the same so that the user has the same experience on both iOS and Android devices.

LibGDX / Box2d - only one rectangle is being rendered by debugrenderer

I'm revisiting LibGDX game programming and I am unfortunately having to re-learn stuff I used to know.
I'm currently using Tiled Map Editor to make a very simple Donkey Kong style level. I have around 20 rectangles in total for the level.
I've created a box2d world in my main GameScreen class and have a for loop to get the rectangle objects into the world and debugrenderer.
My problem is that only the bottom (and first) rectangle I drew is showing up. I have checked the scale, also I put a println() which tells me the object information has been parsed with all the rectangles info showing correct (ie. the rectangles x,y,w,h values) but as I say, only one rectangle shows up on the debugrenderer.
I've just got back into programming after around 6month break and so I'm hoping i've missed something simple. The same code in my old projects still works fine as I've tested some.
Here is my code, any help is massively appreciated. Thanks
public class GameScreen implements Screen {
SpriteBatch spriteBatch;
OrthographicCamera cam;
Viewport v;
TmxMapLoader mapLoader;
TiledMap map;
OrthogonalTiledMapRenderer mapRenderer;
World world;
Box2DDebugRenderer b2dr;
float mapScale = 10f/140f;
public GameScreen(){
spriteBatch = new SpriteBatch();
cam = new OrthographicCamera();
v = new FitViewport(Constants.V_WIDTH, Constants.V_HEIGHT, cam);
cam.setToOrtho(false, v.getWorldWidth(), v.getWorldHeight());
mapLoader = new TmxMapLoader();
map = mapLoader.load("level1.tmx");
mapRenderer = new OrthogonalTiledMapRenderer(map, mapScale);
world = new World(new Vector2(0,-9.8f), true);
b2dr = new Box2DDebugRenderer();
// box2d local variables
BodyDef bdef = new BodyDef();
PolygonShape shape = new PolygonShape();
FixtureDef fdef = new FixtureDef();
Body body;
// create platform object rectangles
for (MapObject object : map.getLayers().get(2).getObjects().getByType(RectangleMapObject.class)){
Rectangle rect = ((RectangleMapObject)object).getRectangle();
bdef.type = BodyDef.BodyType.StaticBody;
bdef.position.set(rect.getX() + rect.getWidth() / 2 * mapScale, rect.y + rect.getHeight() / 2 * mapScale);
body = world.createBody(bdef);
shape.setAsBox(rect.getWidth() / 2 * mapScale, rect.getHeight() / 2 * mapScale);
fdef.shape = shape;
body.createFixture(fdef);
}
}
#Override
public void show() {
}
#Override
public void render(float delta) {
update(delta);
clearScreen();
draw();
}
public void update(float dt){
mapRenderer.setView(cam);
}
public void clearScreen(){
Gdx.gl.glClearColor(1, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
public void draw(){
spriteBatch.setProjectionMatrix(cam.combined);
mapRenderer.render();
b2dr.render(world, cam.combined);
spriteBatch.begin();
spriteBatch.end();
}
#Override
public void resize(int width, int height) {
v.update(width, height);
}
#Override
public void pause() {
}
#Override
public void resume() {
}
#Override
public void hide() {
}
#Override
public void dispose() {
spriteBatch.dispose();
}
}
Sorry for wasting time here. I have fixed this.
It was just that I hadnt applied the scale of the map also to the x,y of the rectangles. So i change one line like so, and now it works:
bdef.position.set(rect.getX() * mapScale + rect.getWidth() / 2 * mapScale, rect.y * mapScale + rect.getHeight() / 2 * mapScale);

JavaFX: Mapping pixels on screen to a 2D matrix

I'm making an animation in which I need to keep track of explored/unexplored pixels on the screen.Initially the screen is black colored,then as the node(a circle) moves(over defined path) the explored pixels are set to white.For doing this task(color change) I'm using Canvas class of JavaFX as background and painting the path using an object of GraphicsContext class(see the createPathAnimation method),now I want to update the int 2D matrix as 0-unexplored,1-explored.
How can I use the changed() function inside createPathAnimation to update my matrix as that function is updating the pixel color to white and I need to update the same set of explored pixels to 1 in my matrix?
sample translation
I'm trying to use the inbuilt function because even if I know the initial and final pixel coordinates,its not easy to determine which all pixels will be set while the circle moves between them(for ex along one of the diagonals),since circle is a like a blob of tiny squares on a pixel level.
My motive is to find the number of white colored pixels after a diagonal translation.
public void start(Stage primaryStage)throws Exception{
Pane root=new Pane();
Path path1=createPath();
canvas=new Canvas(800,600);
root.getChildren().addAll(path1,canvas);
primaryStage.setScene(new Scene(root,800,600,Color.BLACK));
primaryStage.show();
Animation animation1=createPathAnimation(path1,Duration.seconds(10));
pt.getChildren().addAll(animation1);
pt.play();
}
private Path createPath(){
Path path=new Path();
path.setStroke(Color.BLACK);
path.setStrokeWidth(10);
path.getElements().add(new MoveTo(400,300));
path.getElements().add(new LineTo(600,500));
return path;
}
public int a,b;
private Animation createPathAnimation(Path path,Duration duration){
GraphicsContext gc=canvas.getGraphicsContext2D();
Circle pen=new Circle(0,0,10);
PathTransition pathTransition=new PathTransition(duration,path,pen);
pathTransition.currentTimeProperty().addListener(new ChangeListener<Duration>(){
Location oldLocation = null;
/**
* Draw a line from the old location to the new location
*/
#Override
public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {
if( oldValue == Duration.ZERO)
return;
// get current location
double x = pen.getTranslateX();
double y = pen.getTranslateY();
// initialize the location
if( oldLocation == null) {
oldLocation = new Location();
oldLocation.x = x;
oldLocation.y = y;
return;
}
// draw line
gc.setStroke(Color.WHITE);
gc.setLineWidth(30);
gc.strokeLine(oldLocation.x, oldLocation.y, x, y);
// update old location with current one
oldLocation.x = x;
oldLocation.y = y;
}
});
return pathTransition;
}
public static class Location {
double x;
double y;

How to get smooth animation with KeyPress event in javaFX?

I am trying to implement LEFT RIGHT movement of a rectangle shape in JavaFX. Below is my code:
public void start(Stage primaryStage) throws Exception {
AnchorPane ancPane = new AnchorPane();
final Rectangle rect = new Rectangle();
rect.setHeight(50);
rect.setWidth(50);
ancPane.getChildren().add(rect);
Scene scene = new Scene(ancPane, 400, 200, Color.GREEN);
primaryStage.setScene(scene);
primaryStage.show();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent keyEvent) {
System.out.println("hello");
if(keyEvent.getCode().toString() == "RIGHT"){
System.out.println("Move Right");
TranslateTransition translateTransitionRight = new TranslateTransition();
translateTransitionRight.setDuration(Duration.millis(200));
translateTransitionRight.setNode(rect);
translateTransitionRight.setFromX(rect.getTranslateX());
translateTransitionRight.setToX(rect.getTranslateX()+30);
translateTransitionRight.play();
}
if(keyEvent.getCode().toString() == "LEFT"){
System.out.println("Move Left");
TranslateTransition translateTransitionRight = new TranslateTransition();
translateTransitionRight.setDuration(Duration.millis(200));
translateTransitionRight.setNode(rect);
translateTransitionRight.setFromX(rect.getTranslateX());
translateTransitionRight.setToX(rect.getTranslateX()-30);
translateTransitionRight.play();
}
}
});
}
Here when I press either LEFT/RIGHT key continuously (i.e. I don't released the key, I hold it for some times) the rectangle moves but not continuously. It pauses for a small fraction of time just after animation started. After the pause the animation continues smoothly.
How can I get rid of this pausing of animation with KeyEvents?
I would use an AnimationTimer for moving the rectangle, and just update a property representing the velocity on key pressed or key released:
final Rectangle rect = ... ;
final double rectangleSpeed = 100 ; // pixels per second
final double minX = 0 ;
final double maxX = 800 ; // whatever the max value should be.. can use a property and bind to scene width if needed...
final DoubleProperty rectangleVelocity = new SimpleDoubleProperty();
final LongProperty lastUpdateTime = new SimpleLongProperty();
final AnimationTimer rectangleAnimation = new AnimationTimer() {
#Override
public void handle(long timestamp) {
if (lastUpdateTime.get() > 0) {
final double elapsedSeconds = (timestamp - lastUpdateTime.get()) / 1_000_000_000.0 ;
final double deltaX = elapsedSeconds * rectangleVelocity.get();
final double oldX = rect.getTranslateX();
final double newX = Math.max(minX, Math.min(maxX, oldX + deltaX));
rect.setTranslateX(newX);
}
lastUpdateTime.set(timestamp);
}
};
rectangleAnimation.start();
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode()==KeyCode.RIGHT) { // don't use toString here!!!
rectangleVelocity.set(rectangleSpeed);
} else if (event.getCode() == KeyCode.LEFT) {
rectangleVelocity.set(-rectangleSpeed);
}
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.RIGHT || event.getCode() == KeyCode.LEFT) {
rectangleVelocity.set(0);
}
}
});
UPDATE:
The AnimationTimer executes its handle method once each time a frame is rendered by the JavaFX mechanism. The long passed into the handle method is a timestamp of the render frame, in nanoseconds.
The way this works is that we keep track of the last update time. The handle(...) method computes the elapsed time since the last update, multiplies it by the rectangle's velocity, and updates the translateX of the rectangle by that amount. The AnimationTimer is always running, but initially the velocity is set to zero so the rectangle doesn't move.
The keyPressed handler simply changes the velocity: to a positive value if moving right and a negative value if moving left. The keyReleased handler sets the velocity back to zero.

Resources