AI2 Component  (Version nb184)
NativeOpenStreetMapController.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright © 2016-2020 Massachusetts Institute of Technology, All rights reserved.
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 package com.google.appinventor.components.runtime.util;
7 
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;
47 import java.io.File;
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;
56 import java.util.Map;
57 import java.util.Set;
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;
87 
88 class NativeOpenStreetMapController implements MapController, MapListener {
89  /* copied from SVG */
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;
95  /* end copied from SVG */
96 
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;
117 
121  private Set<MapFeatureCollection> hiddenFeatureCollections = new HashSet<>();
122 
128  private Set<MapFeature> hiddenFeatures = new HashSet<>();
129 
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 };
132 
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;
139 
140  @Override
141  public void setSource(LocationSensor source) {
142  if (this.source == source) {
143  return; // nothing to do here
144  }
145  if (this.source != null) {
146  this.source.Enabled(false);
147  }
148  this.source = source;
149  if (this.source != null) {
150  this.source.Enabled(enabled);
151  }
152  }
153 
154  @Override
155  public void onTimeIntervalChanged(int time) {
156  }
157 
158  @Override
159  public void onDistanceIntervalChanged(int distance) {
160  }
161 
162  @Override
163  public void onLocationChanged(Location location) {
164  lastLocation = location;
165  if (consumer != null) {
166  consumer.onLocationChanged(location, this);
167  }
168  }
169 
170  @Override
171  public void onStatusChanged(String s, int i, Bundle bundle) {
172  }
173 
174  @Override
175  public void onProviderEnabled(String s) {
176  }
177 
178  @Override
179  public void onProviderDisabled(String s) {
180  }
181 
182  @Override
183  public boolean startLocationProvider(IMyLocationConsumer consumer) {
184  this.consumer = consumer;
185  if (source != null) {
186  source.Enabled(true);
187  enabled = true;
188  }
189  return enabled;
190  }
191 
192  @Override
193  public void stopLocationProvider() {
194  if (source != null) {
195  source.Enabled(false);
196  }
197  enabled = false;
198  }
199 
200  @Override
201  public Location getLastKnownLocation() {
202  return lastLocation;
203  }
204 
205  @Override
206  public void destroy() {
207  this.consumer = null;
208  }
209  }
210 
211  private class TouchOverlay extends Overlay {
212  private boolean scrollEnabled = true;
213 
214  @Override
215  public void draw(Canvas arg0, MapView arg1, boolean arg2) {}
216 
217  @Override
218  public boolean onFling(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY, MapView mapView) {
219  return !scrollEnabled;
220  }
221 
222  @Override
223  public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY, MapView mapView) {
224  return !scrollEnabled;
225  }
226 
227  @Override
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);
234  }
235  return false; // We don't want to cancel propagation to other overlays
236  }
237  }
238 
239  private class MapReadyHandler extends Handler {
240 
241  @Override
242  public void handleMessage(final Message msg) {
243  switch (msg.what) {
244  case MapTile.MAPTILE_SUCCESS_ID:
245  if (!ready && form.canDispatchEvent(null, "MapReady")) {
246  ready = true;
247  form.runOnUiThread(new Runnable() {
248  @Override
249  public void run() {
250  for (MapEventListener l : eventListeners) {
251  l.onReady(NativeOpenStreetMapController.this);
252  }
253  }
254  });
255  }
256  view.invalidate();
257  break;
258  }
259  }
260  }
261 
262  private class CustomMapView extends MapView {
263  public CustomMapView(Context context) {
264  super(context, null, new MapReadyHandler());
265  }
266 
267  @Override
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);
271  }
272 
273  @Override
274  public void onDetach() {
275  // Suppress call to parent onDetach
276  }
277  }
278 
279  private final AppInventorLocationSensorAdapter locationProvider;
280 
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);
289  caches = true;
290  }
291  }
292  this.form = form;
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() {
301  @Override
302  public void onSingleTap(MapView view, double latitude, double longitude) {
303  for (MapEventListener listener : eventListeners) {
304  listener.onSingleTap(latitude, longitude);
305  }
306  }
307 
308  @Override
309  public void onDoubleTap(MapView view, double latitude, double longitude) {
310  for (MapEventListener listener : eventListeners) {
311  listener.onDoubleTap(latitude, longitude);
312  }
313  }
314  });
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);
322 
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); // not shown by default
328  }
329 
330  @Override
331  public View getView() {
332  return containerView;
333  }
334 
335  @Override
336  public double getLatitude() {
337  return view.getMapCenter().getLatitude();
338  }
339 
340  @Override
341  public double getLongitude() {
342  return view.getMapCenter().getLongitude();
343  }
344 
345  @Override
346  public void setCenter(double latitude, double longitude) {
347  view.getController().setCenter(new GeoPoint(latitude, longitude));
348  }
349 
350  @Override
351  public void setZoom(int zoom) {
352  view.getController().setZoom((double) zoom);
353  zoomControls.updateButtons();
354  }
355 
356  @Override
357  public int getZoom() {
358  // We pass pending as true here so that when a user sets ZoomLevel
359  // and then reads it back it should be reflected.
360  return (int) view.getZoomLevel(true);
361  }
362 
363  @Override
364  public void setZoomEnabled(boolean enable) {
365  this.zoomEnabled = enable;
366  view.setMultiTouchControls(enable);
367  }
368 
369  @Override
370  public boolean isZoomEnabled() {
371  return zoomEnabled;
372  }
373 
374  @Override
375  public void setMapType(MapType type) {
376  switch (type) {
377  case ROADS:
378  tileType = type;
379  view.setTileSource(TileSourceFactory.MAPNIK);
380  break;
381  case AERIAL:
382  tileType = type;
383  view.setTileSource(TileSourceFactory.USGS_SAT);
384  break;
385  case TERRAIN:
386  tileType = type;
387  view.setTileSource(TileSourceFactory.USGS_TOPO);
388  break;
389  case UNKNOWN:
390  default:
391  break;
392  }
393  }
394 
395  @Override
396  public MapType getMapType() {
397  return tileType;
398  }
399 
400  @Override
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() {
405  @Override
406  public boolean onPreDraw() {
407  float density = view.getContext().getResources().getDisplayMetrics().density;
408  compass.setCompassCenter(view.getMeasuredWidth() / density - 35, 35);
409  return true;
410  }
411  });
412  view.getOverlayManager().add(compass);
413  }
414  if (compass != null) {
415  if (enabled) {
416  if (compass.getOrientationProvider() != null) {
417  compass.enableCompass();
418  } else {
419  compass.enableCompass(new InternalCompassOrientationProvider(view.getContext()));
420  }
421  compass.onOrientationChanged(lastAzimuth, null);
422  } else {
423  lastAzimuth = compass.getOrientation();
424  compass.disableCompass();
425  }
426  }
427  }
428 
429  @Override
430  public boolean isCompassEnabled() {
431  return compass != null && compass.isCompassEnabled();
432  }
433 
434  @Override
435  public void setZoomControlEnabled(boolean enabled) {
436  if (zoomControlEnabled != enabled) {
437  zoomControls.setVisibility(enabled ? View.VISIBLE : View.GONE);
438  zoomControlEnabled = enabled;
439  containerView.invalidate();
440  }
441  }
442 
443  @Override
444  public boolean isZoomControlEnabled() {
445  return zoomControlEnabled;
446  }
447 
448  @Override
449  public void setShowUserEnabled(boolean enable) {
450  userLocation.setEnabled(enable);
451  if (enable) {
452  userLocation.enableMyLocation();
453  view.getOverlayManager().add( userLocation );
454  } else {
455  userLocation.disableMyLocation();
456  view.getOverlayManager().remove( userLocation );
457  }
458  }
459 
460  @Override
461  public boolean isShowUserEnabled() {
462  return userLocation != null && userLocation.isEnabled();
463  }
464 
465  @Override
466  public void setRotationEnabled(boolean enabled) {
467  if (enabled && rotation == null) {
468  rotation = new RotationGestureOverlay(view);
469  }
470  if (rotation != null) {
471  rotation.setEnabled(enabled);
472  if (enabled) {
473  view.getOverlayManager().add( rotation );
474  } else {
475  view.getOverlayManager().remove( rotation );
476  }
477  }
478  }
479 
480  @Override
481  public boolean isRotationEnabled() {
482  return rotation != null && rotation.isEnabled();
483  }
484 
485  @Override
486  public void setPanEnabled(boolean enable) {
487  touch.scrollEnabled = enable;
488  }
489 
490  @Override
491  public boolean isPanEnabled() {
492  return touch.scrollEnabled;
493  }
494 
495  @Override
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));
502  }
503  }
504  }
505 
506  @Override
507  public void addEventListener(MapEventListener listener) {
508  eventListeners.add(listener);
509  if ((ready || ViewCompat.isAttachedToWindow(view)) && form.canDispatchEvent(null, "MapReady")) {
510  ready = true;
511  listener.onReady(this);
512  }
513  }
514 
515  @Override
516  public void addFeature(final MapMarker aiMarker) {
517  createNativeMarker(aiMarker, new AsyncCallbackPair<Marker>() {
518  @Override
519  public void onFailure(String message) {
520  Log.e(TAG, "Unable to create marker: " + message);
521  }
522 
523  @Override
524  public void onSuccess(Marker overlay) {
525  overlay.setOnMarkerClickListener(new OnMarkerClickListener() {
526  @Override
527  public boolean onMarkerClick(Marker marker, MapView mapView) {
528  for (MapEventListener listener : eventListeners) {
529  listener.onFeatureClick(aiMarker);
530  }
531  if (aiMarker.EnableInfobox()) {
532  marker.showInfoWindow();
533  }
534  return false;
535  }
536  @Override
537  public boolean onMarkerLongPress(Marker marker, MapView mapView) {
538  for (MapEventListener listener : eventListeners) {
539  listener.onFeatureLongPress(aiMarker);
540  }
541  return false;
542  }
543  });
544  overlay.setOnMarkerDragListener(new OnMarkerDragListener() {
545  @Override
546  public void onMarkerDrag(Marker marker) {
547  for (MapEventListener listener : eventListeners) {
548  listener.onFeatureDrag(aiMarker);
549  }
550  }
551 
552  @Override
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);
558  }
559  }
560 
561  @Override
562  public void onMarkerDragStart(Marker marker) {
563  for (MapEventListener listener : eventListeners) {
564  listener.onFeatureStartDrag(aiMarker);
565  }
566  }
567  });
568  if (aiMarker.Visible()) {
569  showOverlay(overlay);
570  } else {
571  hideOverlay(overlay);
572  }
573  }
574 
575  });
576  }
577 
578  @Override
579  public void addFeature(final MapLineString aiPolyline) {
580  Polyline polyline = createNativePolyline(aiPolyline);
581  featureOverlays.put(aiPolyline, polyline);
582  polyline.setOnClickListener(new Polyline.OnClickListener() {
583  @Override
584  public boolean onClick(Polyline arg0, MapView arg1, GeoPoint arg2) {
585  for (MapEventListener listener : eventListeners) {
586  listener.onFeatureClick(aiPolyline);
587  }
588  if (aiPolyline.EnableInfobox()) {
589  arg0.showInfoWindow(arg2);
590  }
591  return true;
592  }
593 
594  @Override
595  public boolean onLongClick(Polyline arg0, MapView arg1, GeoPoint arg2) {
596  for (MapEventListener listener : eventListeners) {
597  listener.onFeatureLongPress(aiPolyline);
598  }
599  return true;
600  }
601  });
602  polyline.setOnDragListener(new Polyline.OnDragListener() {
603  @Override
604  public void onDragStart(Polyline polyline) {
605  for (MapEventListener listener : eventListeners) {
606  listener.onFeatureStartDrag(aiPolyline);
607  }
608  }
609 
610  @Override
611  public void onDrag(Polyline polyline) {
612  for (MapEventListener listener : eventListeners) {
613  listener.onFeatureDrag(aiPolyline);
614  }
615  }
616 
617  @Override
618  public void onDragEnd(Polyline polyline) {
619  aiPolyline.updatePoints(polyline.getPoints());
620  for (MapEventListener listener : eventListeners) {
621  listener.onFeatureStopDrag(aiPolyline);
622  }
623  }
624  });
625  if (aiPolyline.Visible()) {
626  showOverlay(polyline);
627  } else {
628  hideOverlay(polyline);
629  }
630  }
631 
632  private void configurePolygon(final MapFeature component, Polygon polygon) {
633  featureOverlays.put(component, polygon);
634  polygon.setOnClickListener(new Polygon.OnClickListener() {
635  @Override
636  public boolean onLongClick(Polygon arg0, MapView arg1, GeoPoint arg2) {
637  for (MapEventListener listener : eventListeners) {
638  listener.onFeatureLongPress(component);
639  }
640  return true;
641  }
642 
643  @Override
644  public boolean onClick(Polygon arg0, MapView arg1, GeoPoint arg2) {
645  for (MapEventListener listener : eventListeners) {
646  listener.onFeatureClick(component);
647  }
648  if (component.EnableInfobox()) {
649  arg0.showInfoWindow(arg2);
650  }
651  return true;
652  }
653  });
654  polygon.setOnDragListener(new Polygon.OnDragListener() {
655  @Override
656  public void onDragStart(Polygon polygon) {
657  for (MapEventListener listener : eventListeners) {
658  listener.onFeatureStartDrag(component);
659  }
660  }
661 
662  @Override
663  public void onDrag(Polygon polygon) {
664  for (MapEventListener listener : eventListeners) {
665  listener.onFeatureDrag(component);
666  }
667  }
668 
669  @Override
670  public void onDragEnd(Polygon polygon) {
671  if (component instanceof MapCircle) {
672  double latitude = 0, longitude = 0;
673  int count = polygon.getPoints().size();
674  // Note that this approximates the centroid of the Circle.
675  for (GeoPoint p : polygon.getPoints()) {
676  latitude += p.getLatitude();
677  longitude += p.getLongitude();
678  }
679  if (count > 0) {
680  ((MapCircle) component).updateCenter(latitude / count, longitude / count);
681  } else {
682  ((MapCircle) component).updateCenter(0, 0);
683  }
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);
693  }
694  ((MapRectangle) component).updateBounds(north, west, south, east);
695  } else {
696  ((MapPolygon) component).updatePoints(((MultiPolygon) polygon).getMultiPoints());
697  ((MapPolygon) component).updateHolePoints(((MultiPolygon) polygon).getMultiHoles());
698  }
699  for (MapEventListener listener : eventListeners) {
700  listener.onFeatureStopDrag(component);
701  }
702  }
703  });
704  if (component.Visible()) {
705  showOverlay(polygon);
706  } else {
707  hideOverlay(polygon);
708  }
709  }
710 
711  @Override
712  public void addFeature(final MapPolygon aiPolygon) {
713  configurePolygon(aiPolygon, createNativePolygon(aiPolygon));
714  }
715 
716  @Override
717  public void addFeature(MapCircle aiCircle) {
718  configurePolygon(aiCircle, createNativeCircle(aiCircle));
719  }
720 
721  @Override
722  public void addFeature(MapRectangle aiRectangle) {
723  configurePolygon(aiRectangle, createNativeRectangle(aiRectangle));
724  }
725 
726  @Override
727  public void removeFeature(MapFeature aiFeature) {
728  view.getOverlayManager().remove(featureOverlays.get(aiFeature));
729  featureOverlays.remove(aiFeature);
730  }
731 
732  @Override
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()));
739  view.invalidate();
740  }
741  }
742 
743  @Override
744  public void updateFeaturePosition(MapLineString aiPolyline) {
745  Polyline overlay = (Polyline) featureOverlays.get(aiPolyline);
746  if (overlay != null) {
747  overlay.setPoints(aiPolyline.getPoints());
748  view.invalidate();
749  }
750  }
751 
752  @Override
753  public void updateFeaturePosition(MapPolygon aiPolygon) {
754  MultiPolygon polygon = (MultiPolygon) featureOverlays.get(aiPolygon);
755  if (polygon != null) {
756  polygon.setMultiPoints(aiPolygon.getPoints());
757  view.invalidate();
758  }
759  }
760 
761  @Override
762  public void updateFeatureHoles(MapPolygon aiPolygon) {
763  MultiPolygon polygon = (MultiPolygon) featureOverlays.get(aiPolygon);
764  if (polygon != null) {
765  polygon.setMultiHoles(aiPolygon.getHolePoints());
766  view.invalidate();
767  }
768  }
769 
770  @Override
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);
777  view.invalidate();
778  }
779  }
780 
781  @Override
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);
789  view.invalidate();
790  }
791  }
792 
793  @Override
794  public void updateFeatureFill(final HasFill aiFeature) {
795  OverlayWithIW overlay = featureOverlays.get(aiFeature);
796  if (overlay == null) {
797  return; // not yet initialized
798  }
799  overlay.accept(new OverlayWithIWVisitor() {
800  @Override
801  public void visit(final Marker marker) {
802  getMarkerDrawable((MapMarker) aiFeature, new AsyncCallbackPair<Drawable>() {
803  @Override
804  public void onFailure(String message) {
805  Log.e(TAG, "Unable to update fill color for marker: " + message);
806  }
807 
808  @Override
809  public void onSuccess(Drawable result) {
810  marker.setIcon(result);
811  view.invalidate();
812  }
813  });
814  }
815 
816  @Override
817  public void visit(Polyline polyline) {
818  // polylines do not have fills
819  }
820 
821  @Override
822  public void visit(Polygon polygon) {
823  polygon.setFillColor(aiFeature.FillColor());
824  view.invalidate();
825  }
826 
827  });
828  }
829 
830  @Override
831  public void updateFeatureStroke(final HasStroke aiFeature) {
832  OverlayWithIW overlay = featureOverlays.get(aiFeature);
833  if (overlay == null) {
834  return; // not yet initialized
835  }
836  overlay.accept(new OverlayWithIWVisitor() {
837  @Override
838  public void visit(final Marker marker) {
839  getMarkerDrawable((MapMarker) aiFeature, new AsyncCallbackPair<Drawable>() {
840  @Override
841  public void onFailure(String message) {
842  Log.e(TAG, "Unable to update stroke color for marker: " + message);
843  }
844 
845  @Override
846  public void onSuccess(Drawable result) {
847  marker.setIcon(result);
848  view.invalidate();
849  }
850  });
851  }
852 
853  @Override
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);
859  view.invalidate();
860  }
861 
862  @Override
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);
868  view.invalidate();
869  }
870  });
871  }
872 
873  @Override
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());
879  }
880  }
881 
882  @Override
883  public void updateFeatureDraggable(MapFeature aiFeature) {
884  OverlayWithIW overlay = featureOverlays.get(aiFeature);
885  if (overlay != null) {
886  overlay.setDraggable(aiFeature.Draggable());
887  }
888  }
889 
890  @Override
891  public void updateFeatureImage(MapMarker aiMarker) {
892  final Marker marker = (Marker)featureOverlays.get(aiMarker);
893  if (marker == null) {
894  return; // not yet initialized
895  }
896  getMarkerDrawable(aiMarker, new AsyncCallbackPair<Drawable>() {
897  @Override
898  public void onFailure(String message) {
899  Log.e(TAG, "Unable to update feature image: " + message);
900  }
901 
902  @Override
903  public void onSuccess(Drawable result) {
904  marker.setIcon(result);
905  view.invalidate();
906  }
907  });
908  }
909 
910  @Override
911  public void updateFeatureSize(MapMarker aiMarker) {
912  final Marker marker = (Marker)featureOverlays.get(aiMarker);
913  if (marker == null) {
914  return;
915  }
916  getMarkerDrawable(aiMarker, new AsyncCallbackPair<Drawable>() {
917  @Override
918  public void onFailure(String message) {
919  Log.wtf(TAG, "Cannot find default marker");
920  }
921 
922  @Override
923  public void onSuccess(Drawable result) {
924  marker.setIcon(result);
925  view.invalidate();
926  }
927  });
928  }
929 
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);
935  } else {
936  getMarkerDrawableRaster(aiMarker, callback);
937  }
938  }
939 
940  private void getMarkerDrawableVector(MapMarker aiMarker,
941  AsyncCallbackPair<Drawable> callback) {
942  SVG markerSvg = null;
943  if (defaultMarkerSVG == null) {
944  try {
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);
950  }
951  if (defaultMarkerSVG == null || defaultMarkerSVG.getRootElement() == null) {
952  throw new IllegalStateException("Unable to load SVG from assets");
953  }
954  }
955  final String markerAsset = aiMarker.ImageAsset();
956  if (markerAsset != null && markerAsset.length() != 0) {
957  try {
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);
963  }
964  if (markerSvg == null) {
965  // Attempt to retrieve asset from ReplForm storage location
966  InputStream is = null;
967  try {
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);
974  } finally {
975  IOUtils.closeQuietly(TAG, is);
976  }
977  }
978  }
979  if (markerSvg == null) {
980  markerSvg = defaultMarkerSVG;
981  }
982  try {
983  callback.onSuccess(rasterizeSVG(aiMarker, markerSvg));
984  } catch(Exception e) {
985  callback.onFailure(e.getMessage());
986  }
987  }
988 
989  private void getMarkerDrawableRaster(final MapMarker aiMarker,
990  final AsyncCallbackPair<Drawable> callback) {
991  MediaUtil.getBitmapDrawableAsync(form, aiMarker.ImageAsset(),
992  new AsyncCallbackPair<BitmapDrawable>() {
993  @Override
994  public void onFailure(String message) {
995  callback.onSuccess(getDefaultMarkerDrawable(aiMarker));
996  }
997 
998  @Override
999  public void onSuccess(BitmapDrawable result) {
1000  result.setAlpha((int) Math.round(aiMarker.FillOpacity() * 255.0f));
1001  callback.onSuccess(result);
1002  }
1003  });
1004  }
1005 
1006  private Drawable getDefaultMarkerDrawable(MapMarker aiMarker) {
1007  return rasterizeSVG(aiMarker, defaultMarkerSVG);
1008  }
1009 
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;
1015  } else {
1016  return ComponentConstants.MARKER_PREFERRED_WIDTH;
1017  }
1018  }
1019 
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;
1025  } else {
1026  return ComponentConstants.MARKER_PREFERRED_HEIGHT;
1027  }
1028  }
1029 
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);
1038 
1039  // update fill color of SVG <path>
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;
1058  }
1059  if ((path.style.specifiedFlags & SPECIFIED_FILL_OPACITY) == 0) {
1060  path.style.fillOpacity = fillPaint.getAlpha()/255.0f;
1061  path.style.specifiedFlags |= SPECIFIED_FILL_OPACITY;
1062  }
1063  if ((path.style.specifiedFlags & SPECIFIED_STROKE) == 0) {
1064  path.style.stroke = new SVG.Colour(strokePaint.getColor());
1065  path.style.specifiedFlags |= SPECIFIED_STROKE;
1066  }
1067  if ((path.style.specifiedFlags & SPECIFIED_STROKE_OPACITY) == 0) {
1068  path.style.strokeOpacity = strokePaint.getAlpha()/255.0f;
1069  path.style.specifiedFlags |= SPECIFIED_STROKE_OPACITY;
1070  }
1071  if ((path.style.specifiedFlags & SPECIFIED_STROKE_WIDTH) == 0) {
1072  path.style.strokeWidth = strokeWidth;
1073  path.style.specifiedFlags |= SPECIFIED_STROKE_WIDTH;
1074  }
1075  }
1076  }
1077  }
1078 
1079  // draw SVG to Picture and create a BitmapDrawable for rendering
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);
1089  }
1090 
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) {
1101  @Override
1102  public void onFailure(String message) {
1103  callback.onFailure(message);
1104  }
1105 
1106  @Override
1107  public void onSuccess(Drawable result) {
1108  osmMarker.setIcon(result);
1109  callback.onSuccess(osmMarker);
1110  }
1111  });
1112  }
1113 
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);
1123  return osmLine;
1124  }
1125 
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);
1134  }
1135 
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());
1141  return osmPolygon;
1142  }
1143 
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()));
1148  return osmPolygon;
1149  }
1150 
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)));
1157  return osmPolygon;
1158  }
1159 
1160  @Override
1161  public void showFeature(MapFeature feature) {
1162  if (!hiddenFeatures.contains(feature)) {
1163  showOverlay(featureOverlays.get(feature));
1164  }
1165  }
1166 
1167  protected void showOverlay(OverlayWithIW overlay) {
1168  view.getOverlayManager().add(overlay);
1169  view.invalidate();
1170  }
1171 
1172  @Override
1173  public void hideFeature(MapFeature feature) {
1174  hideOverlay(featureOverlays.get(feature));
1175  }
1176 
1177  protected void hideOverlay(OverlayWithIW overlay) {
1178  view.getOverlayManager().remove(overlay);
1179  view.invalidate();
1180  }
1181 
1182  @Override
1183  public boolean isFeatureVisible(MapFeature feature) {
1184  OverlayWithIW overlay = featureOverlays.get(feature);
1185  return overlay != null && view.getOverlayManager().contains(overlay);
1186  }
1187 
1188  @Override
1189  public boolean isFeatureCollectionVisible(MapFeatureCollection collection) {
1190  return !hiddenFeatureCollections.contains(collection);
1191  }
1192 
1193  @Override
1194  public void setFeatureCollectionVisible(MapFeatureCollection collection, boolean visible) {
1195  if ((!visible && hiddenFeatureCollections.contains(collection))
1196  || (visible && !hiddenFeatureCollections.contains(collection))) {
1197  // Nothing to do
1198  return;
1199  }
1200  if (visible) {
1201  hiddenFeatureCollections.remove(collection);
1202  for (MapFeature feature : collection) {
1203  hiddenFeatures.remove(feature);
1204  if (feature.Visible()) {
1205  showFeature(feature);
1206  }
1207  }
1208  } else {
1209  hiddenFeatureCollections.add(collection);
1210  for (MapFeature feature : collection) {
1211  hiddenFeatures.add(feature);
1212  hideFeature(feature);
1213  }
1214  }
1215  }
1216 
1217  @Override
1218  public void showInfobox(MapFeature feature) {
1219  OverlayWithIW overlay = featureOverlays.get(feature);
1220  overlay.showInfoWindow();
1221  }
1222 
1223  @Override
1224  public void hideInfobox(MapFeature feature) {
1225  OverlayWithIW overlay = featureOverlays.get(feature);
1226  overlay.closeInfoWindow();
1227  }
1228 
1229  @Override
1230  public boolean isInfoboxVisible(MapFeature feature) {
1231  OverlayWithIW overlay = featureOverlays.get(feature);
1232  return overlay != null && overlay.isInfoWindowOpen();
1233  }
1234 
1235  @Override
1236  public BoundingBox getBoundingBox() {
1237  return view.getBoundingBox();
1238  }
1239 
1240  @Override
1241  public void setBoundingBox(BoundingBox bbox) {
1242  view.getController().setCenter(bbox.getCenter());
1243  view.getController().zoomToSpan(bbox.getLatitudeSpan(), bbox.getLongitudeSpan());
1244  }
1245 
1246  @Override
1247  public boolean onScroll(ScrollEvent event) {
1248  for (MapEventListener listener : eventListeners) {
1249  listener.onBoundsChanged();
1250  }
1251  return true;
1252  }
1253 
1254  @Override
1255  public boolean onZoom(ZoomEvent event) {
1256  zoomControls.updateButtons();
1257  for (MapEventListener listener : eventListeners) {
1258  listener.onZoom();
1259  }
1260  return true;
1261  }
1262 
1263  @Override
1264  public LocationSensor.LocationSensorListener getLocationListener() {
1265  return locationProvider;
1266  }
1267 
1268  @Override
1269  public int getOverlayCount() {
1270  System.err.println(view.getOverlays());
1271  return view.getOverlays().size();
1272  }
1273 
1274  @Override
1275  public void setRotation(float Rotation) {
1276  view.setMapOrientation(Rotation);
1277  }
1278 
1279  @Override
1280  public float getRotation() {
1281  return view.getMapOrientation();
1282  }
1283 
1284  @Override
1285  public void setScaleVisible(boolean show) {
1286  scaleBar.setEnabled(show);
1287  view.invalidate();
1288  }
1289 
1290  @Override
1291  public boolean isScaleVisible() {
1292  return scaleBar.isEnabled();
1293  }
1294 
1295  @Override
1296  public void setScaleUnits(MapScaleUnits units) {
1297  switch (units) {
1298  case METRIC:
1299  scaleBar.setUnitsOfMeasure(UnitsOfMeasure.metric);
1300  break;
1301  case IMPERIAL:
1302  scaleBar.setUnitsOfMeasure(UnitsOfMeasure.imperial);
1303  break;
1304  default:
1305  throw new IllegalArgumentException("Unallowable unit system: " + units);
1306  }
1307  view.invalidate();
1308  }
1309 
1310  @Override
1311  public MapScaleUnits getScaleUnits() {
1312  switch (scaleBar.getUnitsOfMeasure()) {
1313  case imperial:
1314  return MapScaleUnits.IMPERIAL;
1315  case metric:
1316  return MapScaleUnits.METRIC;
1317  default:
1318  throw new IllegalStateException("Somehow we have an unallowed unit system");
1319  }
1320  }
1321 
1322  static class MultiPolygon extends Polygon {
1323 
1324  private List<Polygon> children = new ArrayList<Polygon>();
1325  private boolean draggable;
1326  private OnClickListener clickListener;
1327  private OnDragListener dragListener;
1328 
1329  @Override
1330  public void showInfoWindow() {
1331  if (children.size() > 0) {
1332  children.get(0).showInfoWindow();
1333  }
1334  }
1335 
1336  @Override
1337  public void draw(Canvas canvas, MapView mapView, boolean b) {
1338  for (Polygon child : children) {
1339  child.draw(canvas, mapView, b);
1340  }
1341  }
1342 
1343  public List<List<GeoPoint>> getMultiPoints() {
1344  List<List<GeoPoint>> result = new ArrayList<>();
1345  for (Polygon p : children) {
1346  result.add(p.getPoints());
1347  }
1348  return result;
1349  }
1350 
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());
1356  }
1357  while (polygonIterator.hasNext()) {
1358  polygonIterator.next();
1359  polygonIterator.remove();
1360  }
1361  while (pointIterator.hasNext()) {
1362  Polygon p = new Polygon();
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);
1371  children.add(p);
1372  }
1373  }
1374 
1375  @SuppressWarnings("unchecked") // upcasting nested ArrayList to List
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());
1380  }
1381  return result;
1382  }
1383 
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());
1388  }
1389  } else if (holes.size() != children.size()) {
1390  throw new IllegalArgumentException("Holes and points are not of the same arity.");
1391  } else {
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());
1396  }
1397  }
1398  }
1399 
1400  @Override
1401  public void setDraggable(boolean draggable) {
1402  super.setDraggable(draggable);
1403  this.draggable = draggable;
1404  for (Polygon child : children) {
1405  child.setDraggable(draggable);
1406  }
1407  }
1408 
1409  @Override
1410  public void setOnClickListener(OnClickListener listener) {
1411  super.setOnClickListener(listener);
1412  clickListener = listener;
1413  for (Polygon child : children) {
1414  child.setOnClickListener(listener);
1415  }
1416  }
1417 
1418  @Override
1419  public void setOnDragListener(OnDragListener listener) {
1420  super.setOnDragListener(listener);
1421  dragListener = listener;
1422  for (Polygon child : children) {
1423  child.setOnDragListener(listener);
1424  }
1425  }
1426 
1427  @Override
1428  public void setStrokeWidth(float strokeWidth) {
1429  super.setStrokeWidth(strokeWidth);
1430  for (Polygon child : children) {
1431  child.setStrokeWidth(strokeWidth);
1432  }
1433  }
1434 
1435  @Override
1436  public void setStrokeColor(int strokeColor) {
1437  super.setStrokeColor(strokeColor);
1438  for (Polygon child : children) {
1439  child.setStrokeColor(strokeColor);
1440  }
1441  }
1442 
1443  @Override
1444  public void setFillColor(int fillColor) {
1445  super.setFillColor(fillColor);
1446  for (Polygon child : children) {
1447  child.setFillColor(fillColor);
1448  }
1449  }
1450 
1451  @Override
1452  public void setTitle(String title) {
1453  super.setTitle(title);
1454  for (Polygon child : children) {
1455  child.setTitle(title);
1456  }
1457  }
1458 
1459  @Override
1460  public void setSnippet(String snippet) {
1461  super.setSnippet(snippet);
1462  for (Polygon child : children) {
1463  child.setSnippet(snippet);
1464  }
1465  }
1466 
1467  @Override
1468  public boolean onSingleTapConfirmed(MotionEvent event, MapView mapView) {
1469  for (Polygon child : children) {
1470  if (child.onSingleTapConfirmed(event, mapView)) {
1471  return true;
1472  }
1473  }
1474  return false;
1475  }
1476 
1477  @Override
1478  public boolean contains(MotionEvent event) {
1479  for (Polygon child : children) {
1480  if (child.contains(event)) {
1481  return true;
1482  }
1483  }
1484  return false;
1485  }
1486 
1487  @Override
1488  public boolean onLongPress(MotionEvent event, MapView mapView) {
1489  boolean touched = contains(event);
1490  if (touched){
1491  if (mDraggable){
1492  mIsDragged = true;
1493  closeInfoWindow();
1494  mDragStartPoint = event;
1495  if (mOnDragListener != null) {
1496  mOnDragListener.onDragStart( this );
1497  }
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() ) );
1503  }
1504  }
1505  return touched;
1506  }
1507 
1508  @Override
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);
1513  }
1514  }
1515 
1516  @Override
1517  public void finishMove(final MotionEvent start, final MotionEvent end, final MapView view) {
1518  for (Polygon child : children) {
1519  child.finishMove(start, end, view);
1520  }
1521  }
1522 
1523  @Override
1524  public boolean onTouchEvent(MotionEvent event, MapView mapView) {
1525  if (mDraggable && mIsDragged){
1526  if (event.getAction() == MotionEvent.ACTION_UP) {
1527  mIsDragged = false;
1528  finishMove(mDragStartPoint, event, mapView);
1529  if (mOnDragListener != null) {
1530  mOnDragListener.onDragEnd( this );
1531  }
1532  return true;
1533  } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
1534  moveToEventPosition( event, mDragStartPoint, mapView );
1535  if (mOnDragListener != null) {
1536  mOnDragListener.onDrag( this );
1537  }
1538  return true;
1539  }
1540  }
1541  return false;
1542  }
1543  }
1544 }
com.google.appinventor.components.runtime.util.MapFactory.MapCircle
Definition: MapFactory.java:1028
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.runtime.util.MapFactory
Definition: MapFactory.java:30
com.google.appinventor.components.runtime.Polygon.Polygon
Polygon(MapFactory.MapFeatureContainer container)
Definition: Polygon.java:110
com.google.appinventor.components.runtime.util.MapFactory.HasFill
Definition: MapFactory.java:888
com.google.appinventor.components.runtime.util.MapFactory.MapScaleUnits
Definition: MapFactory.java:1567
com.google.appinventor.components
com.google.appinventor.components.runtime.util.MapFactory.MapType
Definition: MapFactory.java:1541
com.google.appinventor.components.runtime.util.MapFactory.MapLineString
Definition: MapFactory.java:1373
com.google.appinventor.components.runtime.util.MapFactory.MapPolygon
Definition: MapFactory.java:1410
com.google.appinventor.components.runtime.util.MapFactory.MapRectangle
Definition: MapFactory.java:1099
com.google.appinventor.components.runtime.util.MapFactory.MapMarker
Definition: MapFactory.java:1205
com.google.appinventor.components.runtime.util.MapFactory.MapController
Definition: MapFactory.java:134
com.google.appinventor.components.runtime.util.MapFactory.MapFeature
Definition: MapFactory.java:588
com.google.appinventor.components.runtime.util.MapFactory.MapFeatureCollection
Definition: MapFactory.java:963
com.google.appinventor.components.runtime.util.MapFactory.HasStroke
Definition: MapFactory.java:919
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.view.ZoomControlView
Definition: ZoomControlView.java:20
com.google.appinventor.components.runtime.LocationSensor
Definition: LocationSensor.java:81
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google
com
com.google.appinventor.components.runtime.util.MapFactory.MapEventListener
Definition: MapFactory.java:37
com.google.appinventor.components.common.ComponentConstants
Definition: ComponentConstants.java:13
com.google.appinventor.components.runtime.Form
Definition: Form.java:126
com.google.appinventor
com.google.appinventor.components.runtime.view
Definition: ZoomControlView.java:6