6 package com.google.appinventor.components.runtime;
17 import org.osmdroid.util.BoundingBox;
41 import android.util.Log;
42 import android.view.View;
44 import java.io.IOException;
45 import java.util.ArrayList;
46 import java.util.List;
63 @SuppressWarnings(
"WeakerAccess")
64 @DesignerComponent(version = YaVersion.MAP_COMPONENT_VERSION,
65 category = ComponentCategory.MAPS,
67 description =
"<p>A two-dimensional container that renders map tiles in the background and " +
68 "allows for multiple Marker elements to identify points on the map. Map tiles are supplied " +
69 "by OpenStreetMap contributors and the United States Geological Survey.</p>" +
70 "<p>The Map component provides three utilities for manipulating its boundaries within App " +
71 "Inventor. First, a locking mechanism is provided to allow the map to be moved relative to " +
72 "other components on the Screen. Second, when unlocked, the user can pan the Map to any " +
73 "location. At this new location, the "Set Initial Boundary" button can be pressed " +
74 "to save the current Map coordinates to its properties. Lastly, if the Map is moved to a " +
75 "different location, for example to add Markers off-screen, then the "Reset Map to " +
76 "Initial Bounds" button can be used to re-center the Map at the starting location.</p>")
78 @UsesAssets(fileNames =
"location.png, marker.svg")
79 @UsesPermissions(permissionNames =
"android.permission.INTERNET, " +
"android.permission.ACCESS_FINE_LOCATION, "
80 +
"android.permission.ACCESS_COARSE_LOCATION, " +
"android.permission.ACCESS_WIFI_STATE, "
81 +
"android.permission.ACCESS_NETWORK_STATE, " +
"android.permission.WRITE_EXTERNAL_STORAGE, "
82 +
"android.permission.READ_EXTERNAL_STORAGE")
83 @UsesLibraries(libraries =
"osmdroid.aar, osmdroid.jar, androidsvg.jar, jts.jar")
85 private static final String TAG =
Map.class.getSimpleName();
87 private static final String ERROR_INVALID_NUMBER =
"%s is not a valid number.";
88 private static final String ERROR_LATITUDE_OUT_OF_BOUNDS =
"Latitude %f is out of bounds.";
89 private static final String ERROR_LONGITUDE_OUT_OF_BOUNDS =
"Longitude %f is out of bounds.";
105 Log.d(TAG,
"Map.<init>");
106 container.
$add(
this);
109 CenterFromString(
"42.359144, -71.093612");
118 EnableRotation(
false);
124 if (mapController ==
null) {
128 return mapController.
getView();
142 @SuppressWarnings(
"squid:S00100")
146 description =
"<p>Set the initial center coordinate of the map. The value is specified as " +
147 "a comma-separated pair of decimal latitude and longitude coordinates, for example, " +
148 "<code>42.359144, -71.093612</code>.</p><p>In blocks code, it is recommended for " +
149 "performance reasons to use SetCenter with numerical latitude and longitude rather " +
150 "than convert to the string representation for use with this property.</p>")
151 public
void CenterFromString(String center) {
152 String[] parts = center.split(
",");
153 if (parts.length != 2) {
154 Log.e(TAG, center +
" is not a valid point.");
155 InvalidPoint(center +
" is not a valid point.");
161 latitude = Double.parseDouble(parts[0].trim());
162 }
catch (NumberFormatException e) {
163 Log.e(TAG, String.format(ERROR_INVALID_NUMBER, parts[0]));
164 InvalidPoint(String.format(ERROR_INVALID_NUMBER, parts[0]));
168 longitude = Double.parseDouble(parts[1].trim());
169 }
catch (NumberFormatException e) {
170 Log.e(TAG, String.format(ERROR_INVALID_NUMBER, parts[1]));
171 InvalidPoint(String.format(ERROR_INVALID_NUMBER, parts[1]));
174 if (latitude > 90.0 || latitude < -90.0) {
175 InvalidPoint(String.format(ERROR_LATITUDE_OUT_OF_BOUNDS, latitude));
176 }
else if (longitude > 180.0 || longitude < -180.0) {
177 InvalidPoint(String.format(ERROR_LONGITUDE_OUT_OF_BOUNDS, longitude));
179 Log.i(TAG,
"Setting center to " + latitude +
", " + longitude);
180 mapController.
setCenter(latitude, longitude);
190 @SuppressWarnings(
"squid:S00100")
192 description =
"The latitude of the center of the map.")
193 public
double Latitude() {
203 @SuppressWarnings(
"squid:S00100")
205 description =
"The longitude of the center of the map.")
206 public
double Longitude() {
234 description =
"The zoom level of the map. Valid values of ZoomLevel are " +
235 "dependent on the tile provider and the latitude and longitude of the map. For " +
236 "example, zoom levels are more constrained over oceans than dense city centers to " +
237 "conserve space for storing tiles, so valid values may be 1-7 over ocean and 1-18 " +
238 "over cities. Tile providers may send warning or error tiles if the zoom level is too " +
239 "great for the server to support.")
240 public
int ZoomLevel() {
241 return mapController.
getZoom();
251 @SuppressWarnings(
"WeakerAccess")
264 description =
"If this property is set to true, multitouch zoom gestures are allowed on " +
265 "the map. Otherwise, the map zoom cannot be changed by the user except via the zoom " +
267 public
boolean EnableZoom() {
281 public
float Rotation() {
317 description =
"The type of tile layer to use as the base of the map. Valid values " +
318 "are: 1 (Roads), 2 (Aerial), 3 (Terrain)")
319 public
int MapType() {
330 defaultValue =
"False")
343 description =
"Show a compass icon rotated based on user orientation.")
344 public
boolean ShowCompass() {
356 defaultValue =
"False")
369 description =
"Show zoom buttons on the map.")
370 public
boolean ShowZoom() {
382 defaultValue =
"False")
395 description =
"Show the user's location on the map.")
396 public
boolean ShowUser() {
406 defaultValue =
"False")
418 description =
"If set to true, the user can use multitouch gestures to rotate the map " +
419 "around its current center.")
420 public
boolean EnableRotation() {
434 description =
"Enable two-finger panning of the Map")
435 public
boolean EnablePan() {
454 description =
"Bounding box for the map stored as [[North, West], [South, East]].")
465 return super.Features();
473 description =
"Uses the provided LocationSensor for user location data rather than the " +
474 "built-in location provider.")
478 if (this.sensor !=
null) {
481 this.sensor = sensor;
482 if (this.sensor !=
null) {
483 this.sensor.addListener(listener);
502 description =
"Shows a scale reference on the map.")
503 public
boolean ShowScale() {
518 $form().dispatchErrorOccurredEvent(
this,
"ScaleUnits",
536 description =
"Returns the user's latitude if ShowUser is enabled.")
537 public
double UserLatitude() {
538 return sensor ==
null ? -999 : sensor.
Latitude();
542 description =
"Returns the user's longitude if ShowUser is enabled.")
543 public
double UserLongitude() {
544 return sensor ==
null ? -999 : sensor.
Longitude();
547 @
SimpleFunction(description =
"Pans the map center to the given latitude and longitude and " +
548 "adjust the zoom level to the specified zoom.")
549 public
void PanTo(
double latitude,
double longitude,
int zoom) {
550 mapController.
panTo(latitude, longitude, zoom, 1);
560 @
SimpleFunction(description =
"Create a new marker with default properties at the specified " +
561 "latitude and longitude.")
562 public
Marker CreateMarker(
double latitude,
double longitude) {
571 @
SimpleFunction(description =
"Save the contents of the Map to the specified path.")
572 public
void Save(final String path) {
573 final List<MapFeature> featuresToSave =
new ArrayList<MapFeature>(features);
579 }
catch(
final IOException e) {
580 final Form form = $form();
581 form.runOnUiThread(
new Runnable() {
596 @SuppressWarnings({
"WeakerAccess",
"squid:S00100"})
597 @SimpleEvent(description =
"Map has been initialized and is ready for user interaction.")
598 public
void Ready() {
606 @
SimpleEvent(description =
"User has changed the map bounds by panning or zooming the map.")
607 public
void BoundsChange() {
615 @
SimpleEvent(description =
"User has changed the zoom level of the map.")
616 public
void ZoomChange() {
628 @
SimpleEvent(description =
"An invalid coordinate was supplied during a maps operation. The " +
629 "message parameter will have more details about the issue.")
630 public
void InvalidPoint(String message) {
639 @
SimpleEvent(description =
"The user tapped at a point on the map.")
640 public
void TapAtPoint(
double latitude,
double longitude) {
649 @
SimpleEvent(description =
"The user double-tapped at a point on the map. This event will be " +
650 "followed by a ZoomChanged event if zooming gestures are enabled and the map is not at " +
651 "the highest possible zoom level.")
652 public
void DoubleTapAtPoint(
double latitude,
double longitude) {
662 @
SimpleEvent(description =
"The user long-pressed at a point on the map.")
663 public
void LongPressAtPoint(
double latitude,
double longitude) {
668 return mapController;
674 container.$form().runOnUiThread(
new Runnable() {
684 container.$form().runOnUiThread(
new Runnable() {
694 container.$form().runOnUiThread(
new Runnable() {
703 public void onSingleTap(
final double latitude,
final double longitude) {
704 container.$form().runOnUiThread(
new Runnable() {
713 public void onDoubleTap(
final double latitude,
final double longitude) {
714 container.$form().runOnUiThread(
new Runnable() {
723 public void onLongPress(
final double latitude,
final double longitude) {
724 container.$form().runOnUiThread(
new Runnable() {
734 container.$form().runOnUiThread(
new Runnable() {
744 container.$form().runOnUiThread(
new Runnable() {
754 container.$form().runOnUiThread(
new Runnable() {
764 container.$form().runOnUiThread(
new Runnable() {
774 container.$form().runOnUiThread(
new Runnable() {
791 features.add(marker);
797 void addFeature(MapLineString lineString) {
798 features.add(lineString);
799 lineString.setMap(
this);
804 void addFeature(MapPolygon polygon) {
805 features.add(polygon);
806 polygon.setMap(
this);
811 void addFeature(MapRectangle rectangle) {
812 features.add(rectangle);
813 rectangle.setMap(
this);
818 void addFeature(MapCircle circle) {
819 features.add(circle);
826 features.remove(feature);