6 package com.google.appinventor.components.runtime.util;
8 import android.content.Context;
9 import android.graphics.Canvas;
10 import android.graphics.Paint;
11 import android.graphics.Picture;
12 import android.graphics.drawable.BitmapDrawable;
13 import android.graphics.drawable.Drawable;
14 import android.graphics.drawable.PictureDrawable;
15 import android.location.Location;
16 import android.os.Bundle;
17 import android.os.Handler;
18 import android.os.Message;
19 import android.util.DisplayMetrics;
20 import android.util.Log;
21 import android.view.MotionEvent;
22 import android.view.View;
23 import android.view.ViewGroup;
24 import android.view.ViewTreeObserver;
25 import android.view.animation.Animation;
26 import android.widget.RelativeLayout;
27 import androidx.core.view.ViewCompat;
28 import com.caverock.androidsvg.SVG;
29 import com.caverock.androidsvg.SVGParseException;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.List;
58 import org.osmdroid.api.IGeoPoint;
59 import org.osmdroid.config.Configuration;
60 import org.osmdroid.events.MapListener;
61 import org.osmdroid.events.ScrollEvent;
62 import org.osmdroid.events.ZoomEvent;
63 import org.osmdroid.tileprovider.MapTile;
64 import org.osmdroid.tileprovider.constants.OpenStreetMapTileProviderConstants;
65 import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
66 import org.osmdroid.util.BoundingBox;
67 import org.osmdroid.util.GeoPoint;
68 import org.osmdroid.views.MapView;
69 import org.osmdroid.views.MapView.OnTapListener;
70 import org.osmdroid.views.overlay.Marker;
71 import org.osmdroid.views.overlay.Marker.OnMarkerClickListener;
72 import org.osmdroid.views.overlay.Marker.OnMarkerDragListener;
73 import org.osmdroid.views.overlay.Overlay;
74 import org.osmdroid.views.overlay.OverlayWithIW;
75 import org.osmdroid.views.overlay.OverlayWithIWVisitor;
76 import org.osmdroid.views.overlay.Polygon;
77 import org.osmdroid.views.overlay.Polyline;
78 import org.osmdroid.views.overlay.ScaleBarOverlay;
79 import org.osmdroid.views.overlay.ScaleBarOverlay.UnitsOfMeasure;
80 import org.osmdroid.views.overlay.compass.CompassOverlay;
81 import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider;
82 import org.osmdroid.views.overlay.gestures.RotationGestureOverlay;
83 import org.osmdroid.views.overlay.infowindow.OverlayInfoWindow;
84 import org.osmdroid.views.overlay.mylocation.IMyLocationConsumer;
85 import org.osmdroid.views.overlay.mylocation.IMyLocationProvider;
86 import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
88 class NativeOpenStreetMapController
implements MapController, MapListener {
90 private static final long SPECIFIED_FILL = 1;
91 private static final long SPECIFIED_FILL_OPACITY = 1<<2;
92 private static final long SPECIFIED_STROKE = 1<<3;
93 private static final long SPECIFIED_STROKE_OPACITY = 1<<4;
94 private static final long SPECIFIED_STROKE_WIDTH = 1<<5;
97 private static final String TAG = NativeOpenStreetMapController.class.getSimpleName();
98 private boolean caches;
99 private final Form form;
100 private RelativeLayout containerView;
101 private MapView view;
102 private MapType tileType;
103 private boolean zoomEnabled;
104 private boolean zoomControlEnabled;
105 private CompassOverlay compass =
null;
106 private final MyLocationNewOverlay userLocation;
107 private RotationGestureOverlay rotation =
null;
108 private Set<MapEventListener> eventListeners =
new HashSet<MapEventListener>();
109 private Map<MapFeature, OverlayWithIW> featureOverlays =
new HashMap<MapFeature, OverlayWithIW>();
110 private SVG defaultMarkerSVG =
null;
111 private TouchOverlay touch =
null;
112 private OverlayInfoWindow defaultInfoWindow =
null;
113 private boolean ready =
false;
114 private ZoomControlView zoomControls =
null;
115 private float lastAzimuth = Float.NaN;
116 private ScaleBarOverlay scaleBar;
121 private Set<MapFeatureCollection> hiddenFeatureCollections =
new HashSet<>();
128 private Set<MapFeature> hiddenFeatures =
new HashSet<>();
130 private static final float[] ANCHOR_HORIZONTAL = { Float.NaN, 0.0f, 1.0f, 0.5f };
131 private static final float[] ANCHOR_VERTICAL = { Float.NaN, 0.0f, 0.5f, 1.0f };
133 private static class AppInventorLocationSensorAdapter
implements IMyLocationProvider,
134 LocationSensor.LocationSensorListener {
135 private LocationSensor source;
136 private Location lastLocation;
137 private IMyLocationConsumer consumer;
138 private boolean enabled =
false;
141 public void setSource(LocationSensor source) {
142 if (this.source == source) {
145 if (this.source !=
null) {
146 this.source.Enabled(
false);
148 this.source = source;
149 if (this.source !=
null) {
150 this.source.Enabled(enabled);
155 public void onTimeIntervalChanged(
int time) {
159 public void onDistanceIntervalChanged(
int distance) {
163 public void onLocationChanged(Location location) {
164 lastLocation = location;
165 if (consumer !=
null) {
166 consumer.onLocationChanged(location,
this);
171 public void onStatusChanged(String s,
int i, Bundle bundle) {
175 public void onProviderEnabled(String s) {
179 public void onProviderDisabled(String s) {
183 public boolean startLocationProvider(IMyLocationConsumer consumer) {
184 this.consumer = consumer;
185 if (source !=
null) {
186 source.Enabled(
true);
193 public void stopLocationProvider() {
194 if (source !=
null) {
195 source.Enabled(
false);
201 public Location getLastKnownLocation() {
206 public void destroy() {
207 this.consumer =
null;
211 private class TouchOverlay
extends Overlay {
212 private boolean scrollEnabled =
true;
215 public void draw(Canvas arg0, MapView arg1,
boolean arg2) {}
218 public boolean onFling(MotionEvent event1, MotionEvent event2,
float distanceX,
float distanceY, MapView mapView) {
219 return !scrollEnabled;
223 public boolean onScroll(MotionEvent event1, MotionEvent event2,
float distanceX,
float distanceY, MapView mapView) {
224 return !scrollEnabled;
228 public boolean onLongPress(
final MotionEvent pEvent,
final MapView pMapView) {
229 IGeoPoint p = pMapView.getProjection().fromPixels((
int) pEvent.getX(), (int) pEvent.getY());
230 final double lat = p.getLatitude();
231 final double lng = p.getLongitude();
232 for (MapEventListener l : eventListeners) {
233 l.onLongPress(lat, lng);
239 private class MapReadyHandler
extends Handler {
242 public void handleMessage(
final Message msg) {
244 case MapTile.MAPTILE_SUCCESS_ID:
245 if (!ready && form.canDispatchEvent(
null,
"MapReady")) {
247 form.runOnUiThread(
new Runnable() {
250 for (MapEventListener l : eventListeners) {
251 l.onReady(NativeOpenStreetMapController.this);
262 private class CustomMapView
extends MapView {
263 public CustomMapView(Context context) {
264 super(context,
null,
new MapReadyHandler());
268 protected void onSizeChanged(
int w,
int h,
int oldw,
int oldh) {
269 scrollTo(getScrollX() + (oldw - w) / 2, getScrollY() + (oldh - h) / 2);
270 super.onSizeChanged(w, h, oldw, oldh);
274 public void onDetach() {
279 private final AppInventorLocationSensorAdapter locationProvider;
281 NativeOpenStreetMapController(
final Form form) {
282 OpenStreetMapTileProviderConstants.setUserAgentValue(form.getApplication().getPackageName());
283 File osmdroid =
new File(form.getCacheDir(),
"osmdroid");
284 if (osmdroid.exists() || osmdroid.mkdirs()) {
285 Configuration.getInstance().setOsmdroidBasePath(osmdroid);
286 File osmdroidTiles =
new File(osmdroid,
"tiles");
287 if (osmdroidTiles.exists() || osmdroidTiles.mkdirs()) {
288 Configuration.getInstance().setOsmdroidTileCache(osmdroidTiles);
293 this.touch =
new TouchOverlay();
294 view =
new CustomMapView(form.getApplicationContext());
295 locationProvider =
new AppInventorLocationSensorAdapter();
296 defaultInfoWindow =
new OverlayInfoWindow(view);
297 view.setTilesScaledToDpi(
true);
298 view.setMapListener(
this);
299 view.getOverlayManager().add(touch);
300 view.addOnTapListener(
new OnTapListener() {
302 public void onSingleTap(MapView view,
double latitude,
double longitude) {
303 for (MapEventListener listener : eventListeners) {
304 listener.onSingleTap(latitude, longitude);
309 public void onDoubleTap(MapView view,
double latitude,
double longitude) {
310 for (MapEventListener listener : eventListeners) {
311 listener.onDoubleTap(latitude, longitude);
315 zoomControls =
new ZoomControlView(view);
316 userLocation =
new MyLocationNewOverlay(locationProvider, view);
317 scaleBar =
new ScaleBarOverlay(view);
318 scaleBar.setAlignBottom(
true);
319 scaleBar.setAlignRight(
true);
320 scaleBar.disableScaleBar();
321 view.getOverlayManager().add(scaleBar);
323 containerView =
new RelativeLayout(form);
324 containerView.setClipChildren(
true);
325 containerView.addView(view,
new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
326 containerView.addView(zoomControls);
327 zoomControls.setVisibility(View.GONE);
331 public View getView() {
332 return containerView;
336 public double getLatitude() {
337 return view.getMapCenter().getLatitude();
341 public double getLongitude() {
342 return view.getMapCenter().getLongitude();
346 public void setCenter(
double latitude,
double longitude) {
347 view.getController().setCenter(
new GeoPoint(latitude, longitude));
351 public void setZoom(
int zoom) {
352 view.getController().setZoom((
double) zoom);
353 zoomControls.updateButtons();
357 public int getZoom() {
360 return (
int) view.getZoomLevel(
true);
364 public void setZoomEnabled(
boolean enable) {
365 this.zoomEnabled = enable;
366 view.setMultiTouchControls(enable);
370 public boolean isZoomEnabled() {
375 public void setMapType(MapType type) {
379 view.setTileSource(TileSourceFactory.MAPNIK);
383 view.setTileSource(TileSourceFactory.USGS_SAT);
387 view.setTileSource(TileSourceFactory.USGS_TOPO);
396 public MapType getMapType() {
401 public void setCompassEnabled(
boolean enabled) {
402 if (enabled && compass ==
null) {
403 compass =
new CompassOverlay(view.getContext(), view);
404 view.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
406 public boolean onPreDraw() {
407 float density = view.getContext().getResources().getDisplayMetrics().density;
408 compass.setCompassCenter(view.getMeasuredWidth() / density - 35, 35);
412 view.getOverlayManager().add(compass);
414 if (compass !=
null) {
416 if (compass.getOrientationProvider() !=
null) {
417 compass.enableCompass();
419 compass.enableCompass(
new InternalCompassOrientationProvider(view.getContext()));
421 compass.onOrientationChanged(lastAzimuth,
null);
423 lastAzimuth = compass.getOrientation();
424 compass.disableCompass();
430 public boolean isCompassEnabled() {
431 return compass !=
null && compass.isCompassEnabled();
435 public void setZoomControlEnabled(
boolean enabled) {
436 if (zoomControlEnabled != enabled) {
437 zoomControls.setVisibility(enabled ? View.VISIBLE : View.GONE);
438 zoomControlEnabled = enabled;
439 containerView.invalidate();
444 public boolean isZoomControlEnabled() {
445 return zoomControlEnabled;
449 public void setShowUserEnabled(
boolean enable) {
450 userLocation.setEnabled(enable);
452 userLocation.enableMyLocation();
453 view.getOverlayManager().add( userLocation );
455 userLocation.disableMyLocation();
456 view.getOverlayManager().remove( userLocation );
461 public boolean isShowUserEnabled() {
462 return userLocation !=
null && userLocation.isEnabled();
466 public void setRotationEnabled(
boolean enabled) {
467 if (enabled && rotation ==
null) {
468 rotation =
new RotationGestureOverlay(view);
470 if (rotation !=
null) {
471 rotation.setEnabled(enabled);
473 view.getOverlayManager().add( rotation );
475 view.getOverlayManager().remove( rotation );
481 public boolean isRotationEnabled() {
482 return rotation !=
null && rotation.isEnabled();
486 public void setPanEnabled(
boolean enable) {
487 touch.scrollEnabled = enable;
491 public boolean isPanEnabled() {
492 return touch.scrollEnabled;
496 public void panTo(
double latitude,
double longitude,
int zoom,
double seconds) {
497 view.getController().animateTo(
new GeoPoint(latitude, longitude));
498 if (view.getController().zoomTo((
double) zoom)) {
499 Animation animation = view.getAnimation();
500 if (animation !=
null) {
501 animation.setDuration((
long) (1000 * seconds));
507 public void addEventListener(MapEventListener listener) {
508 eventListeners.add(listener);
509 if ((ready || ViewCompat.isAttachedToWindow(view)) && form.canDispatchEvent(
null,
"MapReady")) {
511 listener.onReady(
this);
516 public void addFeature(
final MapMarker aiMarker) {
517 createNativeMarker(aiMarker,
new AsyncCallbackPair<Marker>() {
519 public void onFailure(String message) {
520 Log.e(TAG,
"Unable to create marker: " + message);
524 public void onSuccess(Marker overlay) {
525 overlay.setOnMarkerClickListener(
new OnMarkerClickListener() {
527 public boolean onMarkerClick(Marker marker, MapView mapView) {
528 for (MapEventListener listener : eventListeners) {
529 listener.onFeatureClick(aiMarker);
531 if (aiMarker.EnableInfobox()) {
532 marker.showInfoWindow();
537 public boolean onMarkerLongPress(Marker marker, MapView mapView) {
538 for (MapEventListener listener : eventListeners) {
539 listener.onFeatureLongPress(aiMarker);
544 overlay.setOnMarkerDragListener(
new OnMarkerDragListener() {
546 public void onMarkerDrag(Marker marker) {
547 for (MapEventListener listener : eventListeners) {
548 listener.onFeatureDrag(aiMarker);
553 public void onMarkerDragEnd(Marker marker) {
554 IGeoPoint point = marker.getPosition();
555 aiMarker.updateLocation(point.getLatitude(), point.getLongitude());
556 for (MapEventListener listener : eventListeners) {
557 listener.onFeatureStopDrag(aiMarker);
562 public void onMarkerDragStart(Marker marker) {
563 for (MapEventListener listener : eventListeners) {
564 listener.onFeatureStartDrag(aiMarker);
568 if (aiMarker.Visible()) {
569 showOverlay(overlay);
571 hideOverlay(overlay);
579 public void addFeature(
final MapLineString aiPolyline) {
580 Polyline polyline = createNativePolyline(aiPolyline);
581 featureOverlays.put(aiPolyline, polyline);
582 polyline.setOnClickListener(
new Polyline.OnClickListener() {
584 public boolean onClick(Polyline arg0, MapView arg1, GeoPoint arg2) {
585 for (MapEventListener listener : eventListeners) {
586 listener.onFeatureClick(aiPolyline);
588 if (aiPolyline.EnableInfobox()) {
589 arg0.showInfoWindow(arg2);
595 public boolean onLongClick(Polyline arg0, MapView arg1, GeoPoint arg2) {
596 for (MapEventListener listener : eventListeners) {
597 listener.onFeatureLongPress(aiPolyline);
602 polyline.setOnDragListener(
new Polyline.OnDragListener() {
604 public void onDragStart(Polyline polyline) {
605 for (MapEventListener listener : eventListeners) {
606 listener.onFeatureStartDrag(aiPolyline);
611 public void onDrag(Polyline polyline) {
612 for (MapEventListener listener : eventListeners) {
613 listener.onFeatureDrag(aiPolyline);
618 public void onDragEnd(Polyline polyline) {
619 aiPolyline.updatePoints(polyline.getPoints());
620 for (MapEventListener listener : eventListeners) {
621 listener.onFeatureStopDrag(aiPolyline);
625 if (aiPolyline.Visible()) {
626 showOverlay(polyline);
628 hideOverlay(polyline);
632 private void configurePolygon(
final MapFeature component, Polygon polygon) {
633 featureOverlays.put(component, polygon);
634 polygon.setOnClickListener(
new Polygon.OnClickListener() {
636 public boolean onLongClick(Polygon arg0, MapView arg1, GeoPoint arg2) {
637 for (MapEventListener listener : eventListeners) {
638 listener.onFeatureLongPress(component);
644 public boolean onClick(Polygon arg0, MapView arg1, GeoPoint arg2) {
645 for (MapEventListener listener : eventListeners) {
646 listener.onFeatureClick(component);
648 if (component.EnableInfobox()) {
649 arg0.showInfoWindow(arg2);
654 polygon.setOnDragListener(
new Polygon.OnDragListener() {
656 public void onDragStart(Polygon polygon) {
657 for (MapEventListener listener : eventListeners) {
658 listener.onFeatureStartDrag(component);
663 public void onDrag(Polygon polygon) {
664 for (MapEventListener listener : eventListeners) {
665 listener.onFeatureDrag(component);
670 public void onDragEnd(Polygon polygon) {
671 if (component instanceof MapCircle) {
672 double latitude = 0, longitude = 0;
673 int count = polygon.getPoints().size();
675 for (GeoPoint p : polygon.getPoints()) {
676 latitude += p.getLatitude();
677 longitude += p.getLongitude();
680 ((MapCircle) component).updateCenter(latitude / count, longitude / count);
682 ((MapCircle) component).updateCenter(0, 0);
684 }
else if (component instanceof MapRectangle) {
685 double north = -90, east = -180, west = 180, south = 90;
686 for (GeoPoint p : polygon.getPoints()) {
687 double lat = p.getLatitude();
688 double lng = p.getLongitude();
689 north = Math.max(north, lat);
690 south = Math.min(south, lat);
691 east = Math.max(east, lng);
692 west = Math.min(west, lng);
694 ((MapRectangle) component).updateBounds(north, west, south, east);
696 ((MapPolygon) component).updatePoints(((MultiPolygon) polygon).getMultiPoints());
697 ((MapPolygon) component).updateHolePoints(((MultiPolygon) polygon).getMultiHoles());
699 for (MapEventListener listener : eventListeners) {
700 listener.onFeatureStopDrag(component);
704 if (component.Visible()) {
705 showOverlay(polygon);
707 hideOverlay(polygon);
712 public void addFeature(
final MapPolygon aiPolygon) {
713 configurePolygon(aiPolygon, createNativePolygon(aiPolygon));
717 public void addFeature(MapCircle aiCircle) {
718 configurePolygon(aiCircle, createNativeCircle(aiCircle));
722 public void addFeature(MapRectangle aiRectangle) {
723 configurePolygon(aiRectangle, createNativeRectangle(aiRectangle));
727 public void removeFeature(MapFeature aiFeature) {
728 view.getOverlayManager().remove(featureOverlays.get(aiFeature));
729 featureOverlays.remove(aiFeature);
733 public void updateFeaturePosition(MapMarker aiMarker) {
734 Marker marker = (Marker)featureOverlays.get(aiMarker);
735 if (marker !=
null) {
736 marker.setAnchor(ANCHOR_HORIZONTAL[aiMarker.AnchorHorizontal()],
737 ANCHOR_VERTICAL[aiMarker.AnchorVertical()]);
738 marker.setPosition(
new GeoPoint(aiMarker.Latitude(), aiMarker.Longitude()));
744 public void updateFeaturePosition(MapLineString aiPolyline) {
745 Polyline overlay = (Polyline) featureOverlays.get(aiPolyline);
746 if (overlay !=
null) {
747 overlay.setPoints(aiPolyline.getPoints());
753 public void updateFeaturePosition(MapPolygon aiPolygon) {
754 MultiPolygon polygon = (MultiPolygon) featureOverlays.get(aiPolygon);
755 if (polygon !=
null) {
756 polygon.setMultiPoints(aiPolygon.getPoints());
762 public void updateFeatureHoles(MapPolygon aiPolygon) {
763 MultiPolygon polygon = (MultiPolygon) featureOverlays.get(aiPolygon);
764 if (polygon !=
null) {
765 polygon.setMultiHoles(aiPolygon.getHolePoints());
771 public void updateFeaturePosition(MapCircle aiCircle) {
772 GeoPoint center =
new GeoPoint(aiCircle.Latitude(), aiCircle.Longitude());
773 Polygon polygon = (Polygon) featureOverlays.get(aiCircle);
774 if (polygon !=
null) {
775 List<GeoPoint> geopoints = Polygon.pointsAsCircle(center, aiCircle.Radius());
776 polygon.setPoints(geopoints);
782 @SuppressWarnings(
"unchecked")
783 public
void updateFeaturePosition(MapRectangle aiRectangle) {
784 Polygon polygon = (Polygon) featureOverlays.get(aiRectangle);
785 if (polygon !=
null) {
786 List<GeoPoint> geopoints = (List) Polygon.pointsAsRect(
new BoundingBox(aiRectangle.NorthLatitude(),
787 aiRectangle.EastLongitude(), aiRectangle.SouthLatitude(), aiRectangle.WestLongitude()));
788 polygon.setPoints(geopoints);
794 public void updateFeatureFill(
final HasFill aiFeature) {
795 OverlayWithIW overlay = featureOverlays.get(aiFeature);
796 if (overlay ==
null) {
799 overlay.accept(
new OverlayWithIWVisitor() {
801 public void visit(
final Marker marker) {
802 getMarkerDrawable((MapMarker) aiFeature,
new AsyncCallbackPair<Drawable>() {
804 public void onFailure(String message) {
805 Log.e(TAG,
"Unable to update fill color for marker: " + message);
809 public void onSuccess(Drawable result) {
810 marker.setIcon(result);
817 public void visit(Polyline polyline) {
822 public void visit(Polygon polygon) {
823 polygon.setFillColor(aiFeature.FillColor());
831 public void updateFeatureStroke(
final HasStroke aiFeature) {
832 OverlayWithIW overlay = featureOverlays.get(aiFeature);
833 if (overlay ==
null) {
836 overlay.accept(
new OverlayWithIWVisitor() {
838 public void visit(
final Marker marker) {
839 getMarkerDrawable((MapMarker) aiFeature,
new AsyncCallbackPair<Drawable>() {
841 public void onFailure(String message) {
842 Log.e(TAG,
"Unable to update stroke color for marker: " + message);
846 public void onSuccess(Drawable result) {
847 marker.setIcon(result);
854 public void visit(Polyline polyline) {
855 DisplayMetrics metrics =
new DisplayMetrics();
856 form.getWindowManager().getDefaultDisplay().getMetrics(metrics);
857 polyline.setColor(aiFeature.StrokeColor());
858 polyline.setWidth(aiFeature.StrokeWidth() * metrics.density);
863 public void visit(Polygon polygon) {
864 DisplayMetrics metrics =
new DisplayMetrics();
865 form.getWindowManager().getDefaultDisplay().getMetrics(metrics);
866 polygon.setStrokeColor(aiFeature.StrokeColor());
867 polygon.setStrokeWidth(aiFeature.StrokeWidth() * metrics.density);
874 public void updateFeatureText(MapFeature aiFeature) {
875 OverlayWithIW overlay = featureOverlays.get(aiFeature);
876 if (overlay !=
null) {
877 overlay.setTitle(aiFeature.Title());
878 overlay.setSnippet(aiFeature.Description());
883 public void updateFeatureDraggable(MapFeature aiFeature) {
884 OverlayWithIW overlay = featureOverlays.get(aiFeature);
885 if (overlay !=
null) {
886 overlay.setDraggable(aiFeature.Draggable());
891 public void updateFeatureImage(MapMarker aiMarker) {
892 final Marker marker = (Marker)featureOverlays.get(aiMarker);
893 if (marker ==
null) {
896 getMarkerDrawable(aiMarker,
new AsyncCallbackPair<Drawable>() {
898 public void onFailure(String message) {
899 Log.e(TAG,
"Unable to update feature image: " + message);
903 public void onSuccess(Drawable result) {
904 marker.setIcon(result);
911 public void updateFeatureSize(MapMarker aiMarker) {
912 final Marker marker = (Marker)featureOverlays.get(aiMarker);
913 if (marker ==
null) {
916 getMarkerDrawable(aiMarker,
new AsyncCallbackPair<Drawable>() {
918 public void onFailure(String message) {
919 Log.wtf(TAG,
"Cannot find default marker");
923 public void onSuccess(Drawable result) {
924 marker.setIcon(result);
930 private void getMarkerDrawable(
final MapMarker aiMarker,
931 final AsyncCallbackPair<Drawable> callback) {
932 final String assetPath = aiMarker.ImageAsset();
933 if (assetPath ==
null || assetPath.length() == 0 || assetPath.endsWith(
".svg")) {
934 getMarkerDrawableVector(aiMarker, callback);
936 getMarkerDrawableRaster(aiMarker, callback);
940 private void getMarkerDrawableVector(MapMarker aiMarker,
941 AsyncCallbackPair<Drawable> callback) {
942 SVG markerSvg =
null;
943 if (defaultMarkerSVG ==
null) {
945 defaultMarkerSVG = SVG.getFromAsset(view.getContext().getAssets(),
"marker.svg");
946 }
catch (SVGParseException e) {
947 Log.e(TAG,
"Invalid SVG in Marker asset", e);
948 }
catch (IOException e) {
949 Log.e(TAG,
"Unable to read Marker asset", e);
951 if (defaultMarkerSVG ==
null || defaultMarkerSVG.getRootElement() ==
null) {
952 throw new IllegalStateException(
"Unable to load SVG from assets");
955 final String markerAsset = aiMarker.ImageAsset();
956 if (markerAsset !=
null && markerAsset.length() != 0) {
958 markerSvg = SVG.getFromAsset(view.getContext().getAssets(), markerAsset);
959 }
catch (SVGParseException e) {
960 Log.e(TAG,
"Invalid SVG in Marker asset", e);
961 }
catch (IOException e) {
962 Log.e(TAG,
"Unable to read Marker asset", e);
964 if (markerSvg ==
null) {
966 InputStream is =
null;
968 is = MediaUtil.openMedia(form, markerAsset);
969 markerSvg = SVG.getFromInputStream(is);
970 }
catch (SVGParseException e) {
971 Log.e(TAG,
"Invalid SVG in Marker asset", e);
972 }
catch (IOException e) {
973 Log.e(TAG,
"Unable to read Marker asset", e);
975 IOUtils.closeQuietly(TAG, is);
979 if (markerSvg ==
null) {
980 markerSvg = defaultMarkerSVG;
983 callback.onSuccess(rasterizeSVG(aiMarker, markerSvg));
984 }
catch(Exception e) {
985 callback.onFailure(e.getMessage());
989 private void getMarkerDrawableRaster(
final MapMarker aiMarker,
990 final AsyncCallbackPair<Drawable> callback) {
991 MediaUtil.getBitmapDrawableAsync(form, aiMarker.ImageAsset(),
992 new AsyncCallbackPair<BitmapDrawable>() {
994 public void onFailure(String message) {
995 callback.onSuccess(getDefaultMarkerDrawable(aiMarker));
999 public void onSuccess(BitmapDrawable result) {
1000 result.setAlpha((
int) Math.round(aiMarker.FillOpacity() * 255.0f));
1001 callback.onSuccess(result);
1006 private Drawable getDefaultMarkerDrawable(MapMarker aiMarker) {
1007 return rasterizeSVG(aiMarker, defaultMarkerSVG);
1010 private static float getBestGuessWidth(SVG.Svg svg) {
1011 if (svg.width !=
null) {
1012 return svg.width.floatValue();
1013 }
else if (svg.viewBox !=
null) {
1014 return svg.viewBox.width;
1016 return ComponentConstants.MARKER_PREFERRED_WIDTH;
1020 private static float getBestGuessHeight(SVG.Svg svg) {
1021 if (svg.height !=
null) {
1022 return svg.height.floatValue();
1023 }
else if (svg.viewBox !=
null) {
1024 return svg.viewBox.height;
1026 return ComponentConstants.MARKER_PREFERRED_HEIGHT;
1030 private Drawable rasterizeSVG(MapMarker aiMarker, SVG markerSvg) {
1031 SVG.Svg svg = markerSvg.getRootElement();
1032 final float density = view.getContext().getResources().getDisplayMetrics().density;
1033 float height = aiMarker.Height() <= 0 ? getBestGuessHeight(svg) : aiMarker.Height();
1034 float width = aiMarker.Width() <= 0 ? getBestGuessWidth(svg) : aiMarker.Width();
1035 float scaleH = height / getBestGuessHeight(svg);
1036 float scaleW = width / getBestGuessWidth(svg);
1037 float scale = (float) Math.sqrt(scaleH * scaleH + scaleW * scaleW);
1040 Paint fillPaint =
new Paint();
1041 Paint strokePaint =
new Paint();
1042 PaintUtil.changePaint(fillPaint, aiMarker.FillColor());
1043 PaintUtil.changePaint(strokePaint, aiMarker.StrokeColor());
1044 SVG.Length strokeWidth =
new SVG.Length(aiMarker.StrokeWidth() / scale);
1045 for (SVG.SvgObject element : svg.getChildren()) {
1046 if (element instanceof SVG.SvgConditionalElement) {
1047 SVG.SvgConditionalElement path = (SVG.SvgConditionalElement) element;
1048 path.baseStyle.fill =
new SVG.Colour(fillPaint.getColor());
1049 path.baseStyle.fillOpacity = fillPaint.getAlpha()/255.0f;
1050 path.baseStyle.stroke =
new SVG.Colour(strokePaint.getColor());
1051 path.baseStyle.strokeOpacity = strokePaint.getAlpha()/255.0f;
1052 path.baseStyle.strokeWidth = strokeWidth;
1053 path.baseStyle.specifiedFlags = 0x3d;
1054 if (path.style !=
null) {
1055 if ((path.style.specifiedFlags & SPECIFIED_FILL) == 0) {
1056 path.style.fill =
new SVG.Colour(fillPaint.getColor());
1057 path.style.specifiedFlags |= SPECIFIED_FILL;
1059 if ((path.style.specifiedFlags & SPECIFIED_FILL_OPACITY) == 0) {
1060 path.style.fillOpacity = fillPaint.getAlpha()/255.0f;
1061 path.style.specifiedFlags |= SPECIFIED_FILL_OPACITY;
1063 if ((path.style.specifiedFlags & SPECIFIED_STROKE) == 0) {
1064 path.style.stroke =
new SVG.Colour(strokePaint.getColor());
1065 path.style.specifiedFlags |= SPECIFIED_STROKE;
1067 if ((path.style.specifiedFlags & SPECIFIED_STROKE_OPACITY) == 0) {
1068 path.style.strokeOpacity = strokePaint.getAlpha()/255.0f;
1069 path.style.specifiedFlags |= SPECIFIED_STROKE_OPACITY;
1071 if ((path.style.specifiedFlags & SPECIFIED_STROKE_WIDTH) == 0) {
1072 path.style.strokeWidth = strokeWidth;
1073 path.style.specifiedFlags |= SPECIFIED_STROKE_WIDTH;
1080 Picture picture = markerSvg.renderToPicture();
1081 Picture scaledPicture =
new Picture();
1082 Canvas canvas = scaledPicture.beginRecording((
int)((width + 2.0f * aiMarker.StrokeWidth()) * density),
1083 (int)((height + 2.0f * aiMarker.StrokeWidth()) * density));
1084 canvas.scale(density * scaleW, density * scaleH);
1085 canvas.translate(strokeWidth.floatValue(), strokeWidth.floatValue());
1086 picture.draw(canvas);
1087 scaledPicture.endRecording();
1088 return new PictureDrawable(scaledPicture);
1091 private void createNativeMarker(
final MapMarker aiMarker,
1092 AsyncCallbackPair<Marker> callback) {
1093 final Marker osmMarker =
new Marker(view);
1094 featureOverlays.put(aiMarker, osmMarker);
1095 osmMarker.setDraggable(aiMarker.Draggable());
1096 osmMarker.setTitle(aiMarker.Title());
1097 osmMarker.setSnippet(aiMarker.Description());
1098 osmMarker.setPosition(
new GeoPoint(aiMarker.Latitude(), aiMarker.Longitude()));
1099 osmMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
1100 getMarkerDrawable(aiMarker,
new AsyncCallbackFacade<Drawable, Marker>(callback) {
1102 public void onFailure(String message) {
1103 callback.onFailure(message);
1107 public void onSuccess(Drawable result) {
1108 osmMarker.setIcon(result);
1109 callback.onSuccess(osmMarker);
1114 private Polyline createNativePolyline(
final MapLineString aiLineString) {
1115 final Polyline osmLine =
new Polyline();
1116 osmLine.setDraggable(aiLineString.Draggable());
1117 osmLine.setTitle(aiLineString.Title());
1118 osmLine.setSnippet(aiLineString.Description());
1119 osmLine.setPoints(aiLineString.getPoints());
1120 osmLine.setColor(aiLineString.StrokeColor());
1121 osmLine.setWidth(aiLineString.StrokeWidth());
1122 osmLine.setInfoWindow(defaultInfoWindow);
1126 private void createPolygon(
final Polygon osmPolygon,
final MapFeature aiFeature) {
1127 osmPolygon.setDraggable(aiFeature.Draggable());
1128 osmPolygon.setTitle(aiFeature.Title());
1129 osmPolygon.setSnippet(aiFeature.Description());
1130 osmPolygon.setStrokeColor(((HasStroke) aiFeature).StrokeColor());
1131 osmPolygon.setStrokeWidth(((HasStroke) aiFeature).StrokeWidth());
1132 osmPolygon.setFillColor(((HasFill) aiFeature).FillColor());
1133 osmPolygon.setInfoWindow(defaultInfoWindow);
1136 private MultiPolygon createNativePolygon(
final MapPolygon aiPolygon) {
1137 final MultiPolygon osmPolygon =
new MultiPolygon();
1138 createPolygon(osmPolygon, aiPolygon);
1139 osmPolygon.setMultiPoints(aiPolygon.getPoints());
1140 osmPolygon.setMultiHoles(aiPolygon.getHolePoints());
1144 private Polygon createNativeCircle(
final MapCircle aiCircle) {
1145 final Polygon osmPolygon =
new Polygon();
1146 createPolygon(osmPolygon, aiCircle);
1147 osmPolygon.setPoints(Polygon.pointsAsCircle(
new GeoPoint(aiCircle.Latitude(), aiCircle.Longitude()), aiCircle.Radius()));
1151 private Polygon createNativeRectangle(
final MapRectangle aiRectangle) {
1152 BoundingBox bbox =
new BoundingBox(aiRectangle.NorthLatitude(), aiRectangle.EastLongitude(),
1153 aiRectangle.SouthLatitude(), aiRectangle.WestLongitude());
1154 final Polygon osmPolygon =
new Polygon();
1155 createPolygon(osmPolygon, aiRectangle);
1156 osmPolygon.setPoints(
new ArrayList<GeoPoint>((List) Polygon.pointsAsRect(bbox)));
1161 public void showFeature(MapFeature feature) {
1162 if (!hiddenFeatures.contains(feature)) {
1163 showOverlay(featureOverlays.get(feature));
1167 protected void showOverlay(OverlayWithIW overlay) {
1168 view.getOverlayManager().add(overlay);
1173 public void hideFeature(MapFeature feature) {
1174 hideOverlay(featureOverlays.get(feature));
1177 protected void hideOverlay(OverlayWithIW overlay) {
1178 view.getOverlayManager().remove(overlay);
1183 public boolean isFeatureVisible(MapFeature feature) {
1184 OverlayWithIW overlay = featureOverlays.get(feature);
1185 return overlay !=
null && view.getOverlayManager().contains(overlay);
1189 public boolean isFeatureCollectionVisible(MapFeatureCollection collection) {
1190 return !hiddenFeatureCollections.contains(collection);
1194 public void setFeatureCollectionVisible(MapFeatureCollection collection,
boolean visible) {
1195 if ((!visible && hiddenFeatureCollections.contains(collection))
1196 || (visible && !hiddenFeatureCollections.contains(collection))) {
1201 hiddenFeatureCollections.remove(collection);
1202 for (MapFeature feature : collection) {
1203 hiddenFeatures.remove(feature);
1204 if (feature.Visible()) {
1205 showFeature(feature);
1209 hiddenFeatureCollections.add(collection);
1210 for (MapFeature feature : collection) {
1211 hiddenFeatures.add(feature);
1212 hideFeature(feature);
1218 public void showInfobox(MapFeature feature) {
1219 OverlayWithIW overlay = featureOverlays.get(feature);
1220 overlay.showInfoWindow();
1224 public void hideInfobox(MapFeature feature) {
1225 OverlayWithIW overlay = featureOverlays.get(feature);
1226 overlay.closeInfoWindow();
1230 public boolean isInfoboxVisible(MapFeature feature) {
1231 OverlayWithIW overlay = featureOverlays.get(feature);
1232 return overlay !=
null && overlay.isInfoWindowOpen();
1236 public BoundingBox getBoundingBox() {
1237 return view.getBoundingBox();
1241 public void setBoundingBox(BoundingBox bbox) {
1242 view.getController().setCenter(bbox.getCenter());
1243 view.getController().zoomToSpan(bbox.getLatitudeSpan(), bbox.getLongitudeSpan());
1247 public boolean onScroll(ScrollEvent event) {
1248 for (MapEventListener listener : eventListeners) {
1249 listener.onBoundsChanged();
1255 public boolean onZoom(ZoomEvent event) {
1256 zoomControls.updateButtons();
1257 for (MapEventListener listener : eventListeners) {
1264 public LocationSensor.LocationSensorListener getLocationListener() {
1265 return locationProvider;
1269 public int getOverlayCount() {
1270 System.err.println(view.getOverlays());
1271 return view.getOverlays().size();
1275 public void setRotation(
float Rotation) {
1276 view.setMapOrientation(Rotation);
1280 public float getRotation() {
1281 return view.getMapOrientation();
1285 public void setScaleVisible(
boolean show) {
1286 scaleBar.setEnabled(show);
1291 public boolean isScaleVisible() {
1292 return scaleBar.isEnabled();
1296 public void setScaleUnits(MapScaleUnits units) {
1299 scaleBar.setUnitsOfMeasure(UnitsOfMeasure.metric);
1302 scaleBar.setUnitsOfMeasure(UnitsOfMeasure.imperial);
1305 throw new IllegalArgumentException(
"Unallowable unit system: " + units);
1311 public MapScaleUnits getScaleUnits() {
1312 switch (scaleBar.getUnitsOfMeasure()) {
1314 return MapScaleUnits.IMPERIAL;
1316 return MapScaleUnits.METRIC;
1318 throw new IllegalStateException(
"Somehow we have an unallowed unit system");
1322 static class MultiPolygon
extends Polygon {
1324 private List<Polygon> children =
new ArrayList<Polygon>();
1325 private boolean draggable;
1326 private OnClickListener clickListener;
1327 private OnDragListener dragListener;
1330 public void showInfoWindow() {
1331 if (children.size() > 0) {
1332 children.get(0).showInfoWindow();
1337 public void draw(Canvas canvas, MapView mapView,
boolean b) {
1338 for (Polygon child : children) {
1339 child.draw(canvas, mapView, b);
1343 public List<List<GeoPoint>> getMultiPoints() {
1344 List<List<GeoPoint>> result =
new ArrayList<>();
1345 for (Polygon p : children) {
1346 result.add(p.getPoints());
1351 public void setMultiPoints(List<List<GeoPoint>> points) {
1352 Iterator<Polygon> polygonIterator = children.iterator();
1353 Iterator<List<GeoPoint>> pointIterator = points.iterator();
1354 while (polygonIterator.hasNext() && pointIterator.hasNext()) {
1355 polygonIterator.next().setPoints(pointIterator.next());
1357 while (polygonIterator.hasNext()) {
1358 polygonIterator.next();
1359 polygonIterator.remove();
1361 while (pointIterator.hasNext()) {
1363 p.setPoints(pointIterator.next());
1364 p.setStrokeColor(getStrokeColor());
1365 p.setFillColor(getFillColor());
1366 p.setStrokeWidth(getStrokeWidth());
1367 p.setInfoWindow(getInfoWindow());
1368 p.setDraggable(draggable);
1369 p.setOnClickListener(clickListener);
1370 p.setOnDragListener(dragListener);
1375 @SuppressWarnings(
"unchecked")
1376 public List<List<List<GeoPoint>>> getMultiHoles() {
1377 List<List<List<GeoPoint>>> result =
new ArrayList<>();
1378 for (Polygon p : children) {
1379 result.add((List) p.getHoles());
1384 public void setMultiHoles(List<List<List<GeoPoint>>> holes) {
1385 if (holes ==
null || holes.isEmpty()) {
1386 for (Polygon child : children) {
1387 child.setHoles(Collections.<List<GeoPoint>>emptyList());
1389 }
else if (holes.size() != children.size()) {
1390 throw new IllegalArgumentException(
"Holes and points are not of the same arity.");
1392 Iterator<Polygon> polygonIterator = children.iterator();
1393 Iterator<List<List<GeoPoint>>> holeIterator = holes.iterator();
1394 while (polygonIterator.hasNext() && holeIterator.hasNext()) {
1395 polygonIterator.next().setHoles(holeIterator.next());
1401 public void setDraggable(
boolean draggable) {
1402 super.setDraggable(draggable);
1403 this.draggable = draggable;
1404 for (Polygon child : children) {
1405 child.setDraggable(draggable);
1410 public void setOnClickListener(OnClickListener listener) {
1411 super.setOnClickListener(listener);
1412 clickListener = listener;
1413 for (Polygon child : children) {
1414 child.setOnClickListener(listener);
1419 public void setOnDragListener(OnDragListener listener) {
1420 super.setOnDragListener(listener);
1421 dragListener = listener;
1422 for (Polygon child : children) {
1423 child.setOnDragListener(listener);
1428 public void setStrokeWidth(
float strokeWidth) {
1429 super.setStrokeWidth(strokeWidth);
1430 for (Polygon child : children) {
1431 child.setStrokeWidth(strokeWidth);
1436 public void setStrokeColor(
int strokeColor) {
1437 super.setStrokeColor(strokeColor);
1438 for (Polygon child : children) {
1439 child.setStrokeColor(strokeColor);
1444 public void setFillColor(
int fillColor) {
1445 super.setFillColor(fillColor);
1446 for (Polygon child : children) {
1447 child.setFillColor(fillColor);
1452 public void setTitle(String title) {
1453 super.setTitle(title);
1454 for (Polygon child : children) {
1455 child.setTitle(title);
1460 public void setSnippet(String snippet) {
1461 super.setSnippet(snippet);
1462 for (Polygon child : children) {
1463 child.setSnippet(snippet);
1468 public boolean onSingleTapConfirmed(MotionEvent event, MapView mapView) {
1469 for (Polygon child : children) {
1470 if (child.onSingleTapConfirmed(event, mapView)) {
1478 public boolean contains(MotionEvent event) {
1479 for (Polygon child : children) {
1480 if (child.contains(event)) {
1488 public boolean onLongPress(MotionEvent event, MapView mapView) {
1489 boolean touched = contains(event);
1494 mDragStartPoint = event;
1495 if (mOnDragListener !=
null) {
1496 mOnDragListener.onDragStart(
this );
1498 moveToEventPosition(event, mDragStartPoint, mapView);
1499 }
else if (mOnClickListener !=
null) {
1500 mOnClickListener.onLongClick(
this, mapView,
1501 (GeoPoint) mapView.getProjection().fromPixels( (
int) event.getX(),
1502 (int) event.getY() ) );
1509 public void moveToEventPosition(
final MotionEvent event,
final MotionEvent start,
1510 final MapView view) {
1511 for (Polygon child : children) {
1512 child.moveToEventPosition(event, start, view);
1517 public void finishMove(
final MotionEvent start,
final MotionEvent end,
final MapView view) {
1518 for (Polygon child : children) {
1519 child.finishMove(start, end, view);
1524 public boolean onTouchEvent(MotionEvent event, MapView mapView) {
1525 if (mDraggable && mIsDragged){
1526 if (event.getAction() == MotionEvent.ACTION_UP) {
1528 finishMove(mDragStartPoint, event, mapView);
1529 if (mOnDragListener !=
null) {
1530 mOnDragListener.onDragEnd(
this );
1533 }
else if (event.getAction() == MotionEvent.ACTION_MOVE) {
1534 moveToEventPosition( event, mDragStartPoint, mapView );
1535 if (mOnDragListener !=
null) {
1536 mOnDragListener.onDrag(
this );