Skip to content

Commit 2a18c5f

Browse files
committed
add moveMarker and addTextMarker
1 parent 2349ab9 commit 2a18c5f

8 files changed

Lines changed: 788 additions & 2 deletions

File tree

android/src/main/java/com/google/android/react/navsdk/MapViewController.java

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515

1616
import android.annotation.SuppressLint;
1717
import android.app.Activity;
18+
import android.graphics.Bitmap;
19+
import android.graphics.Canvas;
1820
import android.graphics.Color;
21+
import android.graphics.Paint;
22+
import android.graphics.Rect;
23+
import android.graphics.Typeface;
1924
import androidx.core.util.Supplier;
2025
import com.facebook.react.bridge.UiThreadUtil;
2126
import com.google.android.gms.maps.CameraUpdateFactory;
@@ -42,9 +47,12 @@
4247
import java.net.HttpURLConnection;
4348
import java.net.URL;
4449
import java.util.ArrayList;
50+
import java.util.HashMap;
4551
import java.util.List;
4652
import java.util.Map;
4753
import java.util.concurrent.Executors;
54+
import android.animation.ValueAnimator;
55+
import android.view.animation.AccelerateDecelerateInterpolator;
4856

4957
public class MapViewController {
5058
private GoogleMap mGoogleMap;
@@ -304,6 +312,62 @@ public GroundOverlay addGroundOverlay(Map<String, Object> map) {
304312
return groundOverlay;
305313
}
306314

315+
/**
316+
* Animates a marker to a new position with smooth movement.
317+
*
318+
* @param markerId The ID of the marker to move
319+
* @param newPosition The new position to move the marker to
320+
* @param duration The duration of the animation in milliseconds
321+
*/
322+
public void moveMarker(String markerId, Map<String, Object> newPosition, int duration) {
323+
if (mGoogleMap == null) {
324+
return;
325+
}
326+
327+
UiThreadUtil.runOnUiThread(() -> {
328+
// Find the marker
329+
Marker markerToMove = null;
330+
for (Marker marker : markerList) {
331+
if (marker.getId().equals(markerId)) {
332+
markerToMove = marker;
333+
break;
334+
}
335+
}
336+
337+
if (markerToMove == null) {
338+
return; // Marker not found
339+
}
340+
341+
final Marker finalMarker = markerToMove;
342+
final LatLng startPosition = finalMarker.getPosition();
343+
final LatLng endPosition = ObjectTranslationUtil.getLatLngFromMap(newPosition);
344+
345+
346+
// Create smooth animation
347+
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
348+
animator.setDuration(duration > 0 ? duration : 1000); // Default 1 second
349+
animator.setInterpolator(new AccelerateDecelerateInterpolator());
350+
351+
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
352+
@Override
353+
public void onAnimationUpdate(ValueAnimator animation) {
354+
float progress = (float) animation.getAnimatedValue();
355+
356+
// Calculate intermediate position using linear interpolation
357+
double lat = startPosition.latitude + (endPosition.latitude - startPosition.latitude) * progress;
358+
double lng = startPosition.longitude + (endPosition.longitude - startPosition.longitude) * progress;
359+
LatLng intermediatePosition = new LatLng(lat, lng);
360+
361+
// Update marker position
362+
finalMarker.setPosition(intermediatePosition);
363+
}
364+
});
365+
366+
animator.start();
367+
});
368+
}
369+
370+
307371
public void removeMarker(String id) {
308372
UiThreadUtil.runOnUiThread(
309373
() -> {
@@ -357,6 +421,82 @@ public void removeGroundOverlay(String id) {
357421
}
358422
}
359423

424+
/**
425+
* Adds a text marker on the map using a custom bitmap with text and background.
426+
*
427+
* @param optionsMap Configuration map containing text, position, styling options
428+
* @return The created Marker containing the text with background
429+
*/
430+
public Marker addTextMarker(Map<String, Object> optionsMap) {
431+
if (mGoogleMap == null) {
432+
return null;
433+
}
434+
435+
// Extract text marker parameters
436+
String text = CollectionUtil.getString("text", optionsMap);
437+
if (text == null || text.isEmpty()) {
438+
return null; // Text is required
439+
}
440+
441+
// Position parameters
442+
LatLng position = ObjectTranslationUtil.getLatLngFromMap((Map<String, Object>) optionsMap.get("position"));
443+
if (position == null) {
444+
return null; // Position is required
445+
}
446+
447+
// Styling parameters with defaults
448+
float fontSize = (float) CollectionUtil.getDouble("fontSize", optionsMap, 14.0);
449+
String textColor = CollectionUtil.getString("textColor", optionsMap);
450+
int textColorInt = Color.BLACK;
451+
try {
452+
if (textColor != null) textColorInt = Color.parseColor(textColor);
453+
} catch (IllegalArgumentException ignored) {}
454+
455+
String backgroundColor = CollectionUtil.getString("backgroundColor", optionsMap);
456+
int bgColor = Color.WHITE;
457+
try {
458+
if (backgroundColor != null) bgColor = Color.parseColor(backgroundColor);
459+
} catch (IllegalArgumentException ignored) {}
460+
461+
float padding = (float) CollectionUtil.getDouble("padding", optionsMap, 8.0);
462+
463+
// Border styling parameters
464+
String borderColorStr = CollectionUtil.getString("borderColor", optionsMap);
465+
int borderColor = Color.BLACK;
466+
try {
467+
if (borderColorStr != null) borderColor = Color.parseColor(borderColorStr);
468+
} catch (IllegalArgumentException ignored) {}
469+
470+
// Label text (optional)
471+
String label = CollectionUtil.getString("label", optionsMap);
472+
473+
// Generate bitmap with text and circle background
474+
BitmapDescriptor bitmapDescriptor = createTextBitmap(text, textColorInt, bgColor, fontSize, padding, borderColor, label);
475+
if (bitmapDescriptor == null) {
476+
return null;
477+
}
478+
479+
// Calculate anchor point to position circle center at the specified coordinates
480+
float anchorU = 0.5f; // Center horizontally
481+
float anchorV = calculateAnchorV(text, fontSize, padding, label);
482+
483+
// Create marker options with custom text bitmap
484+
MarkerOptions options = new MarkerOptions();
485+
options.icon(bitmapDescriptor);
486+
options.position(position);
487+
options.anchor(anchorU, anchorV);
488+
489+
// Add optional title if provided
490+
String title = CollectionUtil.getString("title", optionsMap);
491+
if (title != null) {
492+
options.title(title);
493+
}
494+
495+
Marker textMarker = mGoogleMap.addMarker(options);
496+
markerList.add(textMarker);
497+
return textMarker;
498+
}
499+
360500
public void setMapStyle(String url) {
361501
Executors.newSingleThreadExecutor()
362502
.execute(
@@ -518,6 +658,7 @@ public void clearMapView() {
518658
return;
519659
}
520660

661+
521662
mGoogleMap.clear();
522663
}
523664

@@ -579,4 +720,171 @@ private LatLng createLatLng(Map<String, Object> map) {
579720

580721
return new LatLng(lat, lng);
581722
}
723+
724+
725+
726+
727+
728+
/**
729+
* Calculates the vertical anchor point to position the circle center at the marker position.
730+
*
731+
* @param text The text to render in the circle
732+
* @param fontSize The font size in pixels
733+
* @param padding The padding around the text
734+
* @param label Optional label text (can be null)
735+
* @return The vertical anchor value (0-1) where 0 is top and 1 is bottom of the bitmap
736+
*/
737+
private float calculateAnchorV(String text, float fontSize, float padding, String label) {
738+
try {
739+
// Create paint to measure text
740+
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
741+
textPaint.setTextSize(fontSize);
742+
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
743+
744+
// Measure text dimensions
745+
Rect textBounds = new Rect();
746+
textPaint.getTextBounds(text, 0, text.length(), textBounds);
747+
int textWidth = textBounds.width();
748+
int textHeight = textBounds.height();
749+
750+
// Calculate circle dimensions
751+
int circleDiameter = (int) ((textWidth > textHeight ? textWidth : textHeight) + (padding * 2));
752+
float borderWidth = circleDiameter * 0.08f;
753+
754+
// Calculate label height if label exists
755+
int labelRectHeight = 0;
756+
if (label != null && !label.isEmpty()) {
757+
Paint labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
758+
labelPaint.setTextSize(fontSize * 0.6f);
759+
Rect labelBounds = new Rect();
760+
labelPaint.getTextBounds(label, 0, label.length(), labelBounds);
761+
labelRectHeight = (int) (labelBounds.height() + (padding * 0.8f));
762+
}
763+
764+
// Calculate total bitmap height and circle center position
765+
float bitmapHeight = circleDiameter + (borderWidth * 2) + labelRectHeight;
766+
float circleCenterY = (circleDiameter + (borderWidth * 2)) / 2f;
767+
768+
// Return anchor V (fraction of bitmap height where circle center is)
769+
return circleCenterY / bitmapHeight;
770+
} catch (Exception e) {
771+
// Default to center if calculation fails
772+
return 0.5f;
773+
}
774+
}
775+
776+
/**
777+
* Creates a bitmap with text on a background circle with border.
778+
* Optionally draws a label text on a rectangle below the circle.
779+
* The border width is automatically calculated as 8% of the circle diameter.
780+
*
781+
* @param text The text to render in the circle
782+
* @param textColor The color of the text
783+
* @param bgColor The background circle color
784+
* @param fontSize The font size in pixels
785+
* @param padding The padding around the text
786+
* @param borderColor The border circle color
787+
* @param label Optional label text to display below the circle (can be null)
788+
* @return BitmapDescriptor containing the rendered text with circle background
789+
*/
790+
private BitmapDescriptor createTextBitmap(String text, int textColor, int bgColor,
791+
float fontSize, float padding, int borderColor, String label) {
792+
try {
793+
// Create paint for text
794+
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
795+
textPaint.setColor(textColor);
796+
textPaint.setTextSize(fontSize);
797+
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
798+
799+
// Create paint for background
800+
Paint bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
801+
bgPaint.setColor(bgColor);
802+
803+
// Measure main text dimensions
804+
Rect textBounds = new Rect();
805+
textPaint.getTextBounds(text, 0, text.length(), textBounds);
806+
807+
int textWidth = textBounds.width();
808+
int textHeight = textBounds.height();
809+
810+
// Calculate circle dimensions
811+
int circleDiameter = (int) ((textWidth > textHeight ? textWidth : textHeight) + (padding * 2));
812+
float borderWidth = circleDiameter * 0.08f;
813+
814+
// Calculate label dimensions if label exists
815+
boolean hasLabel = label != null && !label.isEmpty();
816+
int labelRectHeight = 0;
817+
int labelRectWidth = 0;
818+
Rect labelBounds = new Rect();
819+
Paint labelPaint = null;
820+
821+
if (hasLabel) {
822+
// Create paint for label text (smaller font size)
823+
labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
824+
labelPaint.setColor(textColor);
825+
labelPaint.setTextSize(fontSize * 0.6f); // 60% of main text size
826+
labelPaint.setTypeface(Typeface.DEFAULT);
827+
labelPaint.getTextBounds(label, 0, label.length(), labelBounds);
828+
829+
labelRectHeight = (int) (labelBounds.height() + (padding * 0.8f)); // Smaller padding for label
830+
labelRectWidth = (int) Math.max(circleDiameter + (borderWidth * 2), labelBounds.width() + padding);
831+
}
832+
833+
// Calculate total bitmap dimensions
834+
int bitmapWidth = hasLabel ? labelRectWidth : (int) (circleDiameter + (borderWidth * 2));
835+
int bitmapHeight = (int) (circleDiameter + (borderWidth * 2) + labelRectHeight);
836+
837+
// Create bitmap and canvas
838+
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
839+
Canvas canvas = new Canvas(bitmap);
840+
841+
// Calculate circle center (at top of bitmap)
842+
float centerX = bitmapWidth / 2f;
843+
float centerY = (circleDiameter + (borderWidth * 2)) / 2f;
844+
float radius = (circleDiameter - borderWidth) / 2f;
845+
846+
// Draw background circle
847+
canvas.drawCircle(centerX, centerY, radius, bgPaint);
848+
849+
// Draw border circle
850+
if (borderWidth > 0) {
851+
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
852+
borderPaint.setColor(borderColor);
853+
borderPaint.setStyle(Paint.Style.STROKE);
854+
borderPaint.setStrokeWidth(borderWidth);
855+
canvas.drawCircle(centerX, centerY, radius, borderPaint);
856+
}
857+
858+
// Draw text centered on the circle
859+
float textX = centerX - (textBounds.width() / 2f);
860+
float textY = centerY + (textBounds.height() / 2f);
861+
canvas.drawText(text, textX, textY, textPaint);
862+
863+
// Draw label rectangle and text if label exists
864+
if (hasLabel) {
865+
float rectTop = circleDiameter + (borderWidth * 2);
866+
float rectLeft = (bitmapWidth - labelRectWidth) / 2f;
867+
float rectRight = rectLeft + labelRectWidth;
868+
float rectBottom = rectTop + labelRectHeight;
869+
float cornerRadius = padding * 0.5f;
870+
871+
// Draw rounded rectangle for label
872+
Paint rectPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
873+
rectPaint.setColor(bgColor);
874+
canvas.drawRoundRect(rectLeft, rectTop, rectRight, rectBottom, cornerRadius, cornerRadius, rectPaint);
875+
876+
// Draw label text centered on rectangle
877+
float labelX = centerX - (labelBounds.width() / 2f);
878+
float labelY = rectTop + (labelRectHeight / 2f) - labelBounds.exactCenterY();
879+
canvas.drawText(label, labelX, labelY, labelPaint);
880+
}
881+
882+
883+
return BitmapDescriptorFactory.fromBitmap(bitmap);
884+
} catch (Exception e) {
885+
// Return null if bitmap creation fails
886+
return null;
887+
}
888+
}
889+
582890
}

0 commit comments

Comments
 (0)