diff --git a/library/src/main/assets/google_map.html b/library/src/main/assets/google_map.html index c3c10f3..6a2fd43 100644 --- a/library/src/main/assets/google_map.html +++ b/library/src/main/assets/google_map.html @@ -19,6 +19,7 @@ var infoWindowContent = {}; var polylines = {}; var polygons = {}; +var groundOverlays = {}; var moveTimeout; var GeoMarker = null; var markerIconPrefix; @@ -226,6 +227,27 @@ } } +function addGroundOverlay(id, north, south, east, west, image) { + var imageBounds = { + north: north, + south: south, + east: east, + west: west + }; + + var overlay = new google.maps.GroundOverlay(image, imageBounds); + overlay.setMap(map); + + groundOverlays[id] = overlay; +} + +function removeGroundOverlay(id) { + var overlay = groundOverlays[id]; + if (overlay != null) { + overlay.setMap(null); + } +} + function addCircle(lat, lng, radius, strokeColor, strokeWeight, fillColor) { var position = new google.maps.LatLng(lat, lng); var populationOptions = { diff --git a/library/src/main/assets/mapbox.html b/library/src/main/assets/mapbox.html index e1de6eb..1ae84c2 100644 --- a/library/src/main/assets/mapbox.html +++ b/library/src/main/assets/mapbox.html @@ -21,6 +21,7 @@ var markers = {}; var polylines = {}; var polygons = {}; +var groundOverlays = {}; L.mapbox.accessToken = 'MAPBOX_ACCESS_TOKEN'; map = L.mapbox.map('map', 'MAPBOX_MAPID').setView([0, 0], 10); @@ -177,6 +178,22 @@ } } +function addGroundOverlay(id, north, south, east, west, image) { + var imageBounds = [[south, west], [north, east]]; + + var overlay = L.imageOverlay(image, imageBounds); + overlay.addTo(map); + + groundOverlays[id] = overlay; +} + +function removeGroundOverlay(id) { + var overlay = groundOverlays[id]; + if (overlay != null) { + map.removeLayer(overlay); + } +} + function addCircle(lat, lng, radius, strokeColor, strokeWeight, fillColor) { strokeColor = formatColor(strokeColor); fillColor = formatColor(fillColor); diff --git a/library/src/main/java/com/airbnb/android/airmapview/AirMapGroundOverlay.java b/library/src/main/java/com/airbnb/android/airmapview/AirMapGroundOverlay.java new file mode 100644 index 0000000..dab7af8 --- /dev/null +++ b/library/src/main/java/com/airbnb/android/airmapview/AirMapGroundOverlay.java @@ -0,0 +1,135 @@ +package com.airbnb.android.airmapview; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.support.annotation.DrawableRes; + +import com.google.android.gms.maps.model.BitmapDescriptorFactory; +import com.google.android.gms.maps.model.GroundOverlay; +import com.google.android.gms.maps.model.GroundOverlayOptions; +import com.google.android.gms.maps.model.LatLngBounds; + +public class AirMapGroundOverlay { + + private LatLngBounds bounds; + + private String imageUrl; + private Bitmap bitmap; + private @DrawableRes int imageId; + + private final float bearing; + private final float zIndex; + private final boolean visible; + private GroundOverlay googleOverlay; + private long id; + + private AirMapGroundOverlay(long id, LatLngBounds bounds, String imageUrl, @DrawableRes int imageId, Bitmap bitmap, float bearing, float zIndex, boolean visible) { + this.id = id; + this.bounds = bounds; + this.imageUrl = imageUrl; + this.imageId = imageId; + this.bitmap = bitmap; + this.bearing = bearing; + this.zIndex = zIndex; + this.visible = visible; + } + + public GroundOverlayOptions getOverlayOptions() { + return new GroundOverlayOptions() + .positionFromBounds(bounds) + .image(imageId != 0 ? BitmapDescriptorFactory.fromResource(imageId) : BitmapDescriptorFactory.fromBitmap(bitmap)) + .bearing(bearing) + .zIndex(zIndex) + .visible(visible); + } + + + + void setGoogleOverlay(GroundOverlay googleOverlay) { + this.googleOverlay = googleOverlay; + } + + GroundOverlay getGoogleOverlay() { + return googleOverlay; + } + + public long getId() { + return id; + } + + public LatLngBounds getBounds() { + return bounds; + } + + public String getImageUrl() { + return imageUrl; + } + + public Bitmap getBitmap(Resources resources) { + return imageId != 0 ? BitmapFactory.decodeResource(resources, imageId) : bitmap; + } + + public static class Builder { + private long id; + private LatLngBounds bounds; + private String imageUrl; + private @DrawableRes int imageId; + private Bitmap bitmap; + private float bearing; + private float zIndex; + private boolean visible = true; + + public Builder() { + + } + + public Builder id(long id) { + this.id = id; + return this; + } + + public Builder positionFromBounds(LatLngBounds bounds) { + this.bounds = bounds; + return this; + } + + // TODO: imageUrl will not work for native maps + public Builder imageUrl(String imageUrl) { + this.imageUrl = imageUrl; + return this; + } + + public Builder imageId(@DrawableRes int imageId) { + this.imageId = imageId; + return this; + } + + public Builder bitmap(Bitmap bitmap) { + this.bitmap = bitmap; + return this; + } + + public Builder bearing(float bearing) { + this.bearing = bearing; + return this; + } + + public Builder zIndex(float zIndex) { + this.zIndex = zIndex; + return this; + } + + public Builder visible(boolean visible) { + this.visible = visible; + return this; + } + + public AirMapGroundOverlay build() { + if (imageId == 0 && imageUrl == null) { + throw new IllegalStateException("You should provide an imageId or an imageUrl to your ground overlay."); + } + return new AirMapGroundOverlay(id, bounds, imageUrl, imageId, bitmap, bearing, zIndex, visible); + } + } +} diff --git a/library/src/main/java/com/airbnb/android/airmapview/AirMapInterface.java b/library/src/main/java/com/airbnb/android/airmapview/AirMapInterface.java index b2ffcf0..71dd960 100644 --- a/library/src/main/java/com/airbnb/android/airmapview/AirMapInterface.java +++ b/library/src/main/java/com/airbnb/android/airmapview/AirMapInterface.java @@ -217,4 +217,18 @@ public interface AirMapInterface { /** Get a Bitmap snapshot of the current */ void getSnapshot(OnSnapshotReadyListener listener); + + /** + * Add the given ground overlay to the map + * + * @param overlay {@link AirMapGroundOverlay} instance to add + */ + void addGroundOverlay(AirMapGroundOverlay overlay); + + /** + * Remove the given {@link AirMapGroundOverlay} + * + * @param overlay the {@link AirMapGroundOverlay} to remove + */ + void removeGroundOverlay(AirMapGroundOverlay overlay); } diff --git a/library/src/main/java/com/airbnb/android/airmapview/AirMapView.java b/library/src/main/java/com/airbnb/android/airmapview/AirMapView.java index 46e68bc..689ab38 100644 --- a/library/src/main/java/com/airbnb/android/airmapview/AirMapView.java +++ b/library/src/main/java/com/airbnb/android/airmapview/AirMapView.java @@ -316,6 +316,22 @@ public void clearGeoJsonLayer() { mapInterface.clearGeoJsonLayer(); } + public boolean addGroundOverlay(AirMapGroundOverlay overlay) { + if (isInitialized()) { + mapInterface.addGroundOverlay(overlay); + return true; + } + return false; + } + + public boolean removeGroundOverlay(AirMapGroundOverlay overlay) { + if (isInitialized()) { + mapInterface.removeGroundOverlay(overlay); + return true; + } + return false; + } + public boolean isInitialized() { return mapInterface != null && mapInterface.isInitialized(); } diff --git a/library/src/main/java/com/airbnb/android/airmapview/NativeGoogleMapFragment.java b/library/src/main/java/com/airbnb/android/airmapview/NativeGoogleMapFragment.java index 9a53c45..9a2bae7 100644 --- a/library/src/main/java/com/airbnb/android/airmapview/NativeGoogleMapFragment.java +++ b/library/src/main/java/com/airbnb/android/airmapview/NativeGoogleMapFragment.java @@ -26,6 +26,8 @@ import com.google.android.gms.maps.UiSettings; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.CircleOptions; +import com.google.android.gms.maps.model.GroundOverlay; +import com.google.android.gms.maps.model.GroundOverlayOptions; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; @@ -312,6 +314,16 @@ public void onMapClick(LatLng latLng) { } } + @Override public void addGroundOverlay(AirMapGroundOverlay overlay) { + GroundOverlayOptions overlayOptions = overlay.getOverlayOptions(); + GroundOverlay groundOverlay = googleMap.addGroundOverlay(overlayOptions); + overlay.setGoogleOverlay(groundOverlay); + } + + @Override public void removeGroundOverlay(AirMapGroundOverlay overlay) { + overlay.getGoogleOverlay().remove(); + } + @Override public void setMapType(MapType type) { int nativeType; if (type == MapType.MAP_TYPE_NORMAL) { diff --git a/library/src/main/java/com/airbnb/android/airmapview/WebViewMapFragment.java b/library/src/main/java/com/airbnb/android/airmapview/WebViewMapFragment.java index c09ebc3..67f8329 100644 --- a/library/src/main/java/com/airbnb/android/airmapview/WebViewMapFragment.java +++ b/library/src/main/java/com/airbnb/android/airmapview/WebViewMapFragment.java @@ -8,6 +8,7 @@ import android.os.Looper; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; +import android.util.Base64; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -37,6 +38,7 @@ import org.json.JSONObject; import java.util.HashMap; +import java.io.ByteArrayOutputStream; import java.util.Locale; import java.util.Map; @@ -293,6 +295,27 @@ public void setOnMarkerClickListener(OnMapMarkerClickListener listener) { webView.loadUrl(String.format(Locale.US, "javascript:removePolygon(%1$d);", polygon.getId())); } + private String bitmapToBase64String(Bitmap bitmap) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 0, byteArrayOutputStream); + byte[] byteArray = byteArrayOutputStream.toByteArray(); + String imgageBase64 = Base64.encodeToString(byteArray, Base64.DEFAULT); + return "data:image/png;base64," + imgageBase64; + } + + @Override public void addGroundOverlay(AirMapGroundOverlay overlay) { + webView.loadUrl(String.format(Locale.US, + "javascript:addGroundOverlay(%1$d, %2$f, %3$f, %4$f, %5$f, '%6$s');", overlay.getId(), + overlay.getBounds().northeast.latitude, overlay.getBounds().southwest.latitude, + overlay.getBounds().northeast.longitude, overlay.getBounds().southwest.longitude, + overlay.getImageUrl() != null ? overlay.getImageUrl() : bitmapToBase64String(overlay.getBitmap(getResources())))); + } + + @Override public void removeGroundOverlay(AirMapGroundOverlay overlay) { + webView.loadUrl(String.format(Locale.US, + "javascript:removeGroundOverlay(%1$d);", overlay.getId())); + } + @Override public void setOnMapClickListener(final OnMapClickListener listener) { onMapClickListener = listener; } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 38ee8da..bd274f0 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" - android:theme="@style/AppTheme" > + android:theme="@style/AppTheme.NoActionBar" > diff --git a/sample/src/main/java/com/airbnb/airmapview/sample/BaseDemoFragment.java b/sample/src/main/java/com/airbnb/airmapview/sample/BaseDemoFragment.java new file mode 100644 index 0000000..9c42544 --- /dev/null +++ b/sample/src/main/java/com/airbnb/airmapview/sample/BaseDemoFragment.java @@ -0,0 +1,239 @@ +package com.airbnb.airmapview.sample; + +import android.graphics.Bitmap; +import android.graphics.Point; +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import com.airbnb.android.airmapview.AirMapGeoJsonLayer; +import com.airbnb.android.airmapview.AirMapInterface; +import com.airbnb.android.airmapview.AirMapMarker; +import com.airbnb.android.airmapview.AirMapView; +import com.airbnb.android.airmapview.AirMapViewTypes; +import com.airbnb.android.airmapview.DefaultAirMapViewBuilder; +import com.airbnb.android.airmapview.GoogleChinaMapType; +import com.airbnb.android.airmapview.MapType; +import com.airbnb.android.airmapview.WebAirMapViewBuilder; +import com.airbnb.android.airmapview.listeners.OnCameraChangeListener; +import com.airbnb.android.airmapview.listeners.OnCameraMoveListener; +import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener; +import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback; +import com.airbnb.android.airmapview.listeners.OnMapClickListener; +import com.airbnb.android.airmapview.listeners.OnMapInitializedListener; +import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener; +import com.airbnb.android.airmapview.listeners.OnSnapshotReadyListener; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; + +import org.json.JSONException; + +public abstract class BaseDemoFragment extends Fragment + implements OnCameraChangeListener, + OnMapClickListener, OnCameraMoveListener, OnMapMarkerClickListener, + OnInfoWindowClickListener, OnLatLngScreenLocationCallback { + + AirMapView map; + DefaultAirMapViewBuilder mapViewBuilder; + LogsAdapter adapter = new LogsAdapter(); + RecyclerView logsRecyclerView; + + protected int getLayoutId() { + return R.layout.fragment_map; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mapViewBuilder = new DefaultAirMapViewBuilder(getContext()); + setHasOptionsMenu(true); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(getLayoutId(), container, false); + + logsRecyclerView = (RecyclerView) view.findViewById(R.id.logs); + ((LinearLayoutManager) logsRecyclerView.getLayoutManager()).setReverseLayout(true); + logsRecyclerView.setAdapter(adapter); + + associateButtonToMapType(view, R.id.btnMapTypeNormal, MapType.MAP_TYPE_NORMAL); + associateButtonToMapType(view, R.id.btnMapTypeSattelite, MapType.MAP_TYPE_SATELLITE); + associateButtonToMapType(view, R.id.btnMapTypeTerrain, MapType.MAP_TYPE_TERRAIN); + + return view; + } + + private void associateButtonToMapType(View view, @IdRes int buttonId, final MapType mapType) { + Button btnMapTypeNormal = (Button) view.findViewById(buttonId); + if (btnMapTypeNormal != null) { + btnMapTypeNormal.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(@NonNull View v) { + getMap().setMapType(mapType); + } + }); + } + } + + @Override + public void onResume() { + super.onResume(); + setUpMapIfNeeded(); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_main, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + AirMapInterface airMapInterface = null; + + switch (id) { + case R.id.action_native_map: + try { + airMapInterface = mapViewBuilder.builder(AirMapViewTypes.NATIVE).build(); + } catch (UnsupportedOperationException e) { + Toast.makeText(getContext(), "Sorry, native Google Maps are not supported by this device. " + + "Please make sure you have Google Play Services installed.", + Toast.LENGTH_SHORT).show(); + } + break; + case R.id.action_mapbox_map: + airMapInterface = mapViewBuilder.builder(AirMapViewTypes.WEB).build(); + break; + case R.id.action_google_web_map: + // force Google Web maps since otherwise AirMapViewTypes.WEB returns MapBox by default. + airMapInterface = new WebAirMapViewBuilder().build(); + break; + case R.id.action_google_china_web_map: + airMapInterface = new WebAirMapViewBuilder().withOptions(new GoogleChinaMapType()).build(); + break; + case R.id.action_clear_logs: + adapter.clearLogs(); + break; + case R.id.take_snapshot: + map.getMapInterface().getSnapshot(new OnSnapshotReadyListener() { + @Override + public void onSnapshotReady(@Nullable Bitmap bitmap) { + if (bitmap != null) { + appendBitmap(bitmap); + } else { + appendLog("Null bitmap"); + } + } + }); + + default: + break; + } + + if (airMapInterface != null) { + map.initialize(getChildFragmentManager(), airMapInterface); + } + + return super.onOptionsItemSelected(item); + } + + private void setUpMapIfNeeded() { + if (map != null || getView() == null) { + return; + } + map = (AirMapView) getView().findViewById(R.id.map); + if (map != null) { + map.setOnMapInitializedListener(new OnMapInitializedListener() { + @Override + public void onMapInitialized() { + map.setOnMapClickListener(BaseDemoFragment.this); + map.setOnCameraChangeListener(BaseDemoFragment.this); + map.setOnCameraMoveListener(BaseDemoFragment.this); + map.setOnMarkerClickListener(BaseDemoFragment.this); + map.setOnInfoWindowClickListener(BaseDemoFragment.this); + startDemo(); + } + }); + DefaultAirMapViewBuilder mapViewBuilder = new DefaultAirMapViewBuilder(getActivity()); + AirMapInterface airMapInterface = mapViewBuilder.builder(AirMapViewTypes.NATIVE).build(); + if (airMapInterface != null) { + map.initialize(getChildFragmentManager(), airMapInterface); + } + } + } + + protected abstract void startDemo(); + + protected AirMapView getMap() { + setUpMapIfNeeded(); + return map; + } + + void appendLog(String msg) { + adapter.addString(msg); + logsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); + } + + void appendBitmap(Bitmap bitmap) { + adapter.addBitmap(bitmap); + logsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); + } + + @Override + public void onCameraChanged(LatLng latLng, int zoom) { + appendLog("Map onCameraChanged triggered with lat: " + latLng.latitude + ", lng: " + + latLng.longitude); + } + + @Override + public void onMapClick(LatLng latLng) { + if (latLng != null) { + appendLog( + "Map onMapClick triggered with lat: " + latLng.latitude + ", lng: " + + latLng.longitude); + + getMap().getMapInterface().getScreenLocation(latLng, this); + } else { + appendLog("Map onMapClick triggered with null latLng"); + } + } + + @Override + public void onCameraMove() { + appendLog("Map onCameraMove triggered"); + } + + @Override + public void onMapMarkerClick(AirMapMarker airMarker) { + appendLog("Map onMapMarkerClick triggered with id " + airMarker.getId()); + } + + @Override + public void onInfoWindowClick(AirMapMarker airMarker) { + appendLog("Map onInfoWindowClick triggered with marker " + airMarker.getId()); + } + + @Override + public void onLatLngScreenLocationReady(Point point) { + appendLog("LatLng location on screen (x,y): (" + point.x + "," + point.y + ")"); + } +} diff --git a/sample/src/main/java/com/airbnb/airmapview/sample/DefaultDemoFragment.java b/sample/src/main/java/com/airbnb/airmapview/sample/DefaultDemoFragment.java new file mode 100644 index 0000000..d1d5ef3 --- /dev/null +++ b/sample/src/main/java/com/airbnb/airmapview/sample/DefaultDemoFragment.java @@ -0,0 +1,53 @@ +package com.airbnb.airmapview.sample; + +import com.airbnb.android.airmapview.AirMapMarker; +import com.airbnb.android.airmapview.AirMapPolygon; +import com.airbnb.android.airmapview.AirMapPolyline; +import com.google.android.gms.maps.model.LatLng; + +import java.util.Arrays; + +public class DefaultDemoFragment extends BaseDemoFragment { + + @Override + protected void startDemo() { + final LatLng airbnbLatLng = new LatLng(37.771883, -122.405224); + addMarker("Airbnb HQ", airbnbLatLng, 1); + addMarker("Performance Bikes", new LatLng(37.773975, -122.40205), 2); + addMarker("REI", new LatLng(37.772127, -122.404411), 3); + addMarker("Mapbox", new LatLng(37.77572, -122.41354), 4); + getMap().animateCenterZoom(airbnbLatLng, 10); + + // Add Polylines + LatLng[] latLngs = { + new LatLng(37.77977, -122.38937), + new LatLng(37.77811, -122.39160), + new LatLng(37.77787, -122.38864)}; + + getMap().addPolyline(new AirMapPolyline(Arrays.asList(latLngs), 5)); + + // Add Polygons + LatLng[] polygonLatLngs = { + new LatLng(37.784, -122.405), + new LatLng(37.784, -122.406), + new LatLng(37.785, -122.406), + new LatLng(37.785, -122.405) + }; + getMap().addPolygon(new AirMapPolygon.Builder().add(polygonLatLngs).strokeWidth(3.f).build()); + + // Add Circle + getMap().drawCircle(new LatLng(37.78443, -122.40805), 1000); + + // enable my location + getMap().setMyLocationEnabled(true); + } + + private void addMarker(String title, LatLng latLng, int id) { + getMap().addMarker(new AirMapMarker.Builder() + .id(id) + .position(latLng) + .title(title) + .iconId(R.mipmap.icon_location_pin) + .build()); + } +} diff --git a/sample/src/main/java/com/airbnb/airmapview/sample/GeoJsonDemoFragment.java b/sample/src/main/java/com/airbnb/airmapview/sample/GeoJsonDemoFragment.java new file mode 100644 index 0000000..e051c64 --- /dev/null +++ b/sample/src/main/java/com/airbnb/airmapview/sample/GeoJsonDemoFragment.java @@ -0,0 +1,33 @@ +package com.airbnb.airmapview.sample; + +import android.support.v4.content.ContextCompat; +import android.util.Log; + +import com.airbnb.android.airmapview.AirMapGeoJsonLayer; +import com.google.android.gms.maps.model.LatLng; + +import org.json.JSONException; + +public class GeoJsonDemoFragment extends BaseDemoFragment { + + private static final String TAG = GeoJsonDemoFragment.class.getSimpleName(); + + @Override + protected void startDemo() { + // Draws a layer on top of Australia + String geoJsonString = Util.readFromRawResource(getContext(), R.raw.google); + AirMapGeoJsonLayer layer = new AirMapGeoJsonLayer.Builder(geoJsonString) + .strokeColor(ContextCompat.getColor(getContext(), android.R.color.holo_green_dark)) + .strokeWidth(10) + .fillColor(ContextCompat.getColor(getContext(), android.R.color.holo_green_light)) + .build(); + + try { + map.getMapInterface().setGeoJsonLayer(layer); + } catch (JSONException e) { + Log.e(TAG, "Failed to add GeoJson layer", e); + } + + getMap().animateCenterZoom(new LatLng(-25., 133.), 4); + } +} diff --git a/sample/src/main/java/com/airbnb/airmapview/sample/GroundOverlayDemoFragment.java b/sample/src/main/java/com/airbnb/airmapview/sample/GroundOverlayDemoFragment.java new file mode 100644 index 0000000..698538b --- /dev/null +++ b/sample/src/main/java/com/airbnb/airmapview/sample/GroundOverlayDemoFragment.java @@ -0,0 +1,18 @@ +package com.airbnb.airmapview.sample; + +import com.airbnb.android.airmapview.AirMapGroundOverlay; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; + +public class GroundOverlayDemoFragment extends BaseDemoFragment { + @Override + protected void startDemo() { + getMap().addGroundOverlay(new AirMapGroundOverlay.Builder() + .imageId(R.drawable.newark_nj_1922) + .imageUrl("https://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg") + .positionFromBounds(new LatLngBounds(new LatLng(40.712216, -74.22655), new LatLng(40.773941, -74.12544))) + .build()); + + getMap().animateCenterZoom(new LatLng(40.740, -74.18), 12); + } +} diff --git a/sample/src/main/java/com/airbnb/airmapview/sample/MainActivity.java b/sample/src/main/java/com/airbnb/airmapview/sample/MainActivity.java index cd29cfc..419fbdc 100644 --- a/sample/src/main/java/com/airbnb/airmapview/sample/MainActivity.java +++ b/sample/src/main/java/com/airbnb/airmapview/sample/MainActivity.java @@ -1,255 +1,147 @@ package com.airbnb.airmapview.sample; import android.graphics.Bitmap; -import android.graphics.Point; import android.os.Bundle; -import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.Toolbar; import android.util.Log; -import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.widget.Button; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ListView; +import android.widget.TextView; import android.widget.Toast; import com.airbnb.android.airmapview.AirMapGeoJsonLayer; import com.airbnb.android.airmapview.AirMapInterface; import com.airbnb.android.airmapview.AirMapMarker; -import com.airbnb.android.airmapview.AirMapPolygon; -import com.airbnb.android.airmapview.AirMapPolyline; -import com.airbnb.android.airmapview.AirMapView; import com.airbnb.android.airmapview.AirMapViewTypes; -import com.airbnb.android.airmapview.DefaultAirMapViewBuilder; import com.airbnb.android.airmapview.GoogleChinaMapType; -import com.airbnb.android.airmapview.MapType; import com.airbnb.android.airmapview.WebAirMapViewBuilder; -import com.airbnb.android.airmapview.listeners.OnCameraChangeListener; -import com.airbnb.android.airmapview.listeners.OnCameraMoveListener; -import com.airbnb.android.airmapview.listeners.OnInfoWindowClickListener; -import com.airbnb.android.airmapview.listeners.OnLatLngScreenLocationCallback; -import com.airbnb.android.airmapview.listeners.OnMapClickListener; -import com.airbnb.android.airmapview.listeners.OnMapInitializedListener; -import com.airbnb.android.airmapview.listeners.OnMapMarkerClickListener; import com.airbnb.android.airmapview.listeners.OnSnapshotReadyListener; -import com.google.android.gms.maps.model.LatLng; import org.json.JSONException; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; -public class MainActivity extends AppCompatActivity - implements OnCameraChangeListener, OnMapInitializedListener, - OnMapClickListener, OnCameraMoveListener, OnMapMarkerClickListener, - OnInfoWindowClickListener, OnLatLngScreenLocationCallback { +public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { + private DemoAdapter demoAdapter = new DemoAdapter(); + private List demos = new ArrayList<>(); + private ListView listView; - private final LogsAdapter adapter = new LogsAdapter(); - - private static final String TAG = MainActivity.class.getSimpleName(); - private AirMapView map; - private DefaultAirMapViewBuilder mapViewBuilder; - private RecyclerView logsRecyclerView; - - @Override protected void onCreate(Bundle savedInstanceState) { + @Override + protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - mapViewBuilder = new DefaultAirMapViewBuilder(this); - map = (AirMapView) findViewById(R.id.map); - logsRecyclerView = (RecyclerView) findViewById(R.id.logs); - ((LinearLayoutManager) logsRecyclerView.getLayoutManager()).setReverseLayout(true); - logsRecyclerView.setAdapter(adapter); - Button btnMapTypeNormal = (Button) findViewById(R.id.btnMapTypeNormal); - Button btnMapTypeSattelite = (Button) findViewById(R.id.btnMapTypeSattelite); - Button btnMapTypeTerrain = (Button) findViewById(R.id.btnMapTypeTerrain); - - btnMapTypeNormal.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(@NonNull View v) { - map.setMapType(MapType.MAP_TYPE_NORMAL); + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, + R.string.drawer_open, R.string.drawer_close); + drawerLayout.addDrawerListener(drawerToggle); + drawerToggle.syncState(); + + listView = (ListView) findViewById(android.R.id.list); + listView.setAdapter(demoAdapter); + listView.setOnItemClickListener(this); + + addDemo("Default", new DemoFragmentFactory() { + @Override + public BaseDemoFragment make() { + return new DefaultDemoFragment(); } }); - - btnMapTypeSattelite.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(@NonNull View v) { - map.setMapType(MapType.MAP_TYPE_SATELLITE); + addDemo("Ground Overlay", new DemoFragmentFactory() { + @Override + public BaseDemoFragment make() { + return new GroundOverlayDemoFragment(); } }); - - btnMapTypeTerrain.setOnClickListener(new View.OnClickListener() { - @Override public void onClick(@NonNull View v) { - map.setMapType(MapType.MAP_TYPE_TERRAIN); + addDemo("GeoJson", new DemoFragmentFactory() { + @Override + public BaseDemoFragment make() { + return new GeoJsonDemoFragment(); } }); - map.setOnMapClickListener(this); - map.setOnCameraChangeListener(this); - map.setOnCameraMoveListener(this); - map.setOnMarkerClickListener(this); - map.setOnMapInitializedListener(this); - map.setOnInfoWindowClickListener(this); - map.initialize(getSupportFragmentManager()); - } - - @Override public boolean onCreateOptionsMenu(Menu menu) { - // Inflate the menu; this adds items to the action bar if it is present. - getMenuInflater().inflate(R.menu.menu_main, menu); - return true; - } - - @Override public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - - AirMapInterface airMapInterface = null; - - switch (id) { - case R.id.action_native_map: - try { - airMapInterface = mapViewBuilder.builder(AirMapViewTypes.NATIVE).build(); - } catch (UnsupportedOperationException e) { - Toast.makeText(this, "Sorry, native Google Maps are not supported by this device. " + - "Please make sure you have Google Play Services installed.", - Toast.LENGTH_SHORT).show(); - } - break; - case R.id.action_mapbox_map: - airMapInterface = mapViewBuilder.builder(AirMapViewTypes.WEB).build(); - break; - case R.id.action_google_web_map: - // force Google Web maps since otherwise AirMapViewTypes.WEB returns MapBox by default. - airMapInterface = new WebAirMapViewBuilder().build(); - break; - case R.id.action_google_china_web_map: - airMapInterface = new WebAirMapViewBuilder().withOptions(new GoogleChinaMapType()).build(); - break; - case R.id.action_clear_logs: - adapter.clearLogs(); - break; - case R.id.add_geojson_layer: - // Draws a layer on top of Australia - String geoJsonString = Util.readFromRawResource(this, R.raw.google); - AirMapGeoJsonLayer layer = new AirMapGeoJsonLayer.Builder(geoJsonString) - .strokeColor(getResources().getColor(android.R.color.holo_green_dark)) - .strokeWidth(10) - .fillColor(getResources().getColor(android.R.color.holo_green_light)) - .build(); - try { - map.getMapInterface().setGeoJsonLayer(layer); - } catch (JSONException e) { - Log.e(TAG, "Failed to add GeoJson layer", e); - } - - break; - case R.id.remove_geojson_layer: - map.getMapInterface().clearGeoJsonLayer(); - break; - case R.id.take_snapshot: - map.getMapInterface().getSnapshot(new OnSnapshotReadyListener() { - @Override - public void onSnapshotReady(@Nullable Bitmap bitmap) { - if (bitmap != null) { - appendBitmap(bitmap); - } else { - appendLog("Null bitmap"); - } - } - }); - break; - - default: - break; - } - - if (airMapInterface != null) { - map.initialize(getSupportFragmentManager(), airMapInterface); - } - - return super.onOptionsItemSelected(item); - } - - @Override public void onCameraChanged(LatLng latLng, int zoom) { - appendLog("Map onCameraChanged triggered with lat: " + latLng.latitude + ", lng: " - + latLng.longitude); + startDemo(demos.get(0)); } - @Override public void onMapInitialized() { - appendLog("Map onMapInitialized triggered"); - final LatLng airbnbLatLng = new LatLng(37.771883, -122.405224); - addMarker("Airbnb HQ", airbnbLatLng, 1); - addMarker("Performance Bikes", new LatLng(37.773975, -122.40205), 2); - addMarker("REI", new LatLng(37.772127, -122.404411), 3); - addMarker("Mapbox", new LatLng(37.77572, -122.41354), 4); - map.animateCenterZoom(airbnbLatLng, 10); - - // Add Polylines - LatLng[] latLngs = { - new LatLng(37.77977, -122.38937), - new LatLng(37.77811, -122.39160), - new LatLng(37.77787, -122.38864) }; - - map.addPolyline(new AirMapPolyline(Arrays.asList(latLngs), 5)); - - // Add Polygons - LatLng[] polygonLatLngs = { - new LatLng(37.784, -122.405), - new LatLng(37.784, -122.406), - new LatLng(37.785, -122.406), - new LatLng(37.785, -122.405) - }; - map.addPolygon(new AirMapPolygon.Builder().add(polygonLatLngs).strokeWidth(3.f).build()); - - // Add Circle - map.drawCircle(new LatLng(37.78443, -122.40805), 1000); - - // enable my location - map.setMyLocationEnabled(true); + private interface DemoFragmentFactory { + BaseDemoFragment make(); } - private void addMarker(String title, LatLng latLng, int id) { - map.addMarker(new AirMapMarker.Builder() - .id(id) - .position(latLng) - .title(title) - .iconId(R.mipmap.icon_location_pin) - .build()); + private void startDemo (Demo demo){ + getSupportFragmentManager() + .beginTransaction() + .replace(R.id.content, demo.fragmentFactory.make()) + .commit(); + ((DrawerLayout) findViewById(R.id.drawer_layout)).closeDrawer(GravityCompat.START); } - @Override public void onMapClick(LatLng latLng) { - if (latLng != null) { - appendLog( - "Map onMapClick triggered with lat: " + latLng.latitude + ", lng: " - + latLng.longitude); - - map.getMapInterface().getScreenLocation(latLng, this); + @Override + public void onBackPressed () { + DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); } else { - appendLog("Map onMapClick triggered with null latLng"); + super.onBackPressed(); } } - @Override public void onCameraMove() { - appendLog("Map onCameraMove triggered"); + private void addDemo(String demoName, DemoFragmentFactory fragmentFactory) { + demos.add(new Demo(demoName, fragmentFactory)); } - private void appendLog(String msg) { - adapter.addString(msg); - logsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); + @Override + public void onItemClick (AdapterView < ? > parent, View view,int position, long id){ + Demo demo = demoAdapter.getItem(position); + startDemo(demo); + listView.setSelection(position); } - private void appendBitmap(Bitmap bitmap) { - adapter.addBitmap(bitmap); - logsRecyclerView.smoothScrollToPosition(adapter.getItemCount() - 1); - } + private class DemoAdapter extends BaseAdapter { + @Override + public int getCount() { + return demos.size(); + } - @Override public void onMapMarkerClick(AirMapMarker airMarker) { - appendLog("Map onMapMarkerClick triggered with id " + airMarker.getId()); - } + @Override + public Demo getItem(int position) { + return demos.get(position); + } - @Override public void onInfoWindowClick(AirMapMarker airMarker) { - appendLog("Map onInfoWindowClick triggered with id " + airMarker.getId()); - } + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = getLayoutInflater().inflate(android.R.layout.simple_list_item_1, parent, false); + } + Demo demo = getItem(position); + ((TextView) convertView.findViewById(android.R.id.text1)).setText(demo.demoName); + return convertView; + } + } - @Override public void onLatLngScreenLocationReady(Point point) { - appendLog("LatLng location on screen (x,y): (" + point.x + "," + point.y + ")"); + private static class Demo { + private final String demoName; + private final DemoFragmentFactory fragmentFactory; + + public Demo(String demoName, DemoFragmentFactory fragmentFactory) { + this.demoName = demoName; + this.fragmentFactory = fragmentFactory; + } } } diff --git a/sample/src/main/res/drawable-xhdpi/newark_nj_1922.jpg b/sample/src/main/res/drawable-xhdpi/newark_nj_1922.jpg new file mode 100644 index 0000000..1f4ae59 Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/newark_nj_1922.jpg differ diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index 909d0b8..21659d8 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -1,56 +1,36 @@ - + android:layout_height="wrap_content" + android:background="?attr/colorPrimary" + android:theme="@style/AppTheme.AppBarOverlay"/> - + - - -