[android] Add ImageSource bindings

This commit is contained in:
Asheem Mamoowala
2017-05-25 14:36:05 -07:00
parent ab5b310a9e
commit b7c7d3fdab
18 changed files with 528 additions and 29 deletions

View File

@@ -0,0 +1,87 @@
package com.mapbox.mapboxsdk.geometry;
import android.os.Parcel;
import android.os.Parcelable;
/**
* A geographical area representing a non-aligned quadrilateral
* <p>
* This class does not wrap values to the world bounds
* </p>
*/
public class LatLngQuad implements Parcelable {
private final LatLng topLeft;
private final LatLng topRight;
private final LatLng bottomRight;
private final LatLng bottomLeft;
/**
* Construct a new LatLngQuad based on its corners,
* in order top left, top right, bottom left, bottom right
*/
public LatLngQuad(final LatLng topLeft, final LatLng topRight, final LatLng bottomRight, final LatLng bottomLeft) {
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomRight = bottomRight;
this.bottomLeft = bottomLeft;
}
public LatLng getTopLeft() {
return this.topLeft;
}
public LatLng getTopRight() {
return this.topRight;
}
public LatLng getBottomRight() {
return this.bottomRight;
}
public LatLng getBottomLeft() {
return this.bottomLeft;
}
public static final Parcelable.Creator<LatLngQuad> CREATOR = new Parcelable.Creator<LatLngQuad>() {
@Override
public LatLngQuad createFromParcel(final Parcel in) {
return readFromParcel(in);
}
@Override
public LatLngQuad[] newArray(final int size) {
return new LatLngQuad[size];
}
};
@Override
public int hashCode() {
int code = topLeft.hashCode();
code = (code ^ code >>> 31) + topRight.hashCode();
code = (code ^ code >>> 31) + bottomRight.hashCode();
code = (code ^ code >>> 31) + bottomLeft.hashCode();
return code;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel out, final int arg1) {
topLeft.writeToParcel(out, arg1);
topRight.writeToParcel(out, arg1);
bottomRight.writeToParcel(out, arg1);
bottomLeft.writeToParcel(out, arg1);
}
private static LatLngQuad readFromParcel(final Parcel in) {
final LatLng topLeft = new LatLng(in);
final LatLng topRight = new LatLng(in);
final LatLng bottomRight = new LatLng(in);
final LatLng bottomLeft = new LatLng(in);
return new LatLngQuad(topLeft, topRight, bottomRight, bottomLeft);
}
}

View File

@@ -0,0 +1,137 @@
package com.mapbox.mapboxsdk.style.sources;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.v4.content.ContextCompat;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.geometry.LatLngQuad;
import java.net.URL;
/**
* Image source, allows a georeferenced raster image to be shown on the map.
* <p>
* The georeferenced image scales and rotates as the user zooms and rotates the map.
* The geographic location of the raster image content, supplied with `LatLngQuad`,
* can be non-axis aligned.
* </p>
* * @see <a href="https://www.mapbox.com/mapbox-gl-style-spec/#sources-image">the style specification</a>
*/
@UiThread
public class ImageSource extends Source {
/**
* Internal use
*
* @param nativePtr - pointer to native peer
*/
public ImageSource(long nativePtr) {
super(nativePtr);
}
/**
* Create an ImageSource from coordinates and an image URL
*
* @param id The source id
* @param coordinates The Latitude and Longitude of the four corners of the image
* @param url remote json file
*/
public ImageSource(String id, LatLngQuad coordinates, URL url) {
initialize(id, coordinates);
setUrl(url);
}
/**
* Create an ImageSource from coordinates and a bitmap image
*
* @param id The source id
* @param coordinates The Latitude and Longitude of the four corners of the image
* @param bitmap A Bitmap image
*/
public ImageSource(String id, LatLngQuad coordinates, @NonNull android.graphics.Bitmap bitmap) {
initialize(id, coordinates);
setImage(bitmap);
}
/**
* Create an ImageSource from coordinates and a bitmap image resource
*
* @param id The source id
* @param coordinates The Latitude and Longitude of the four corners of the image
* @param resourceId The resource ID of a Bitmap image
*/
public ImageSource(String id, LatLngQuad coordinates, @DrawableRes int resourceId) {
initialize(id, coordinates);
setImage(resourceId);
}
/**
* Updates the source image url
*
* @param url An Image url
*/
public void setUrl(URL url) {
setUrl(url.toExternalForm());
}
/**
* Updates the source image url
*
* @param url An image url
*/
public void setUrl(String url) {
nativeSetUrl(url);
}
/**
* Updates the source image to a bitmap
*
* @param bitmap A Bitmap image
*/
public void setImage(@NonNull android.graphics.Bitmap bitmap) {
nativeSetImage(bitmap);
}
/**
* Updates the source image to a bitmap image resource
*
* @param resourceId The resource ID of a Bitmap image
*/
public void setImage(@DrawableRes int resourceId) throws IllegalArgumentException {
Context context = Mapbox.getApplicationContext();
Drawable drawable = ContextCompat.getDrawable(context, resourceId);
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
nativeSetImage(bitmapDrawable.getBitmap());
} else {
throw new IllegalArgumentException("Failed to decode image. The resource provided must be a Bitmap.");
}
}
/**
* @return The url or null
*/
@Nullable
public String getUrl() {
return nativeGetUrl();
}
protected native void initialize(String layerId, LatLngQuad payload);
protected native void nativeSetUrl(String url);
protected native String nativeGetUrl();
protected native void nativeSetImage(Bitmap bitmap);
@Override
protected native void finalize() throws Throwable;
}

View File

@@ -535,6 +535,17 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>
</activity>
<activity
android:name=".activity.style.AnimatedImageSourceActivity"
android:label="@string/activity_animated_image_source"
android:description="@string/description_animated_image_source">
<meta-data
android:name="@string/category"
android:value="@string/category_style"/>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activity.FeatureOverviewActivity"/>
</activity>
<!-- Features -->
<activity

View File

@@ -0,0 +1,132 @@
package com.mapbox.mapboxsdk.testapp.activity.style;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.geometry.LatLngQuad;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.style.layers.RasterLayer;
import com.mapbox.mapboxsdk.style.sources.ImageSource;
import com.mapbox.mapboxsdk.testapp.R;
/**
* Test activity showing how to use a series of images to create an animation
* with an ImageSource
* <p>
* GL-native equivalent of https://www.mapbox.com/mapbox-gl-js/example/animate-images/
* </p>
*/
public class AnimatedImageSourceActivity extends AppCompatActivity implements OnMapReadyCallback {
private static final String ID_IMAGE_SOURCE = "animated_image_source";
private static final String ID_IMAGE_LAYER = "animated_image_layer";
private MapView mapView;
private MapboxMap mapboxMap;
private Handler handler;
private Runnable runnable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animated_image_source);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
}
@Override
public void onMapReady(@NonNull final MapboxMap map) {
mapboxMap = map;
// add source
LatLngQuad quad = new LatLngQuad(
new LatLng(46.437, -80.425),
new LatLng(46.437, -71.516),
new LatLng(37.936, -71.516),
new LatLng(37.936, -80.425));
mapboxMap.addSource(new ImageSource(ID_IMAGE_SOURCE, quad, R.drawable.southeast_radar_0));
// add layer
RasterLayer layer = new RasterLayer(ID_IMAGE_LAYER, ID_IMAGE_SOURCE);
mapboxMap.addLayer(layer);
// loop refresh geojson
handler = new Handler();
runnable = new RefreshImageRunnable(mapboxMap, handler);
handler.postDelayed(runnable, 100);
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
public void onResume() {
super.onResume();
mapView.onResume();
}
@Override
public void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
handler.removeCallbacks(runnable);
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
private static class RefreshImageRunnable implements Runnable {
private MapboxMap mapboxMap;
private Handler handler;
private int[] drawables;
private int drawableIndex;
RefreshImageRunnable(MapboxMap mapboxMap, Handler handler) {
this.mapboxMap = mapboxMap;
this.handler = handler;
drawables = new int[4];
drawables[0] = R.drawable.southeast_radar_0;
drawables[1] = R.drawable.southeast_radar_1;
drawables[2] = R.drawable.southeast_radar_2;
drawables[3] = R.drawable.southeast_radar_3;
drawableIndex = 1;
}
@Override
public void run() {
((ImageSource) mapboxMap.getSource(ID_IMAGE_SOURCE)).setImage(drawables[drawableIndex++]);
if (drawableIndex > 3) {
drawableIndex = 0;
}
handler.postDelayed(this, 1000);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.mapbox.mapboxsdk.maps.MapView
android:id="@id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:mapbox_cameraTargetLat="46.437"
app:mapbox_cameraTargetLng="-80.425"
app:mapbox_cameraZoom="3"
app:mapbox_styleUrl="@string/mapbox_style_mapbox_streets"/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fabStartStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="@dimen/fab_margin"
android:layout_marginRight="@dimen/fab_margin"
android:src="@android:drawable/ic_media_play"
app:backgroundTint="@color/primary"/>
</RelativeLayout>

View File

@@ -59,6 +59,7 @@
<string name="activity_restricted_bounds">Restrict camera to a bounds</string>
<string name="activity_fill_extrusion_layer">Fill extrusions</string>
<string name="activity_building_fill_extrusion_layer">Building layer</string>
<string name="activity_animated_image_source">Animated Image Source</string>
<!--Description-->
<string name="description_user_location_tracking">Tracks the location of the user</string>
@@ -117,6 +118,7 @@
<string name="description_restricted_bounds">Limit viewport to Iceland</string>
<string name="description_fill_extrusion_layer">Shows how to add 3D extruded shapes</string>
<string name="description_building_fill_extrusion_layer">Shows how to show 3D extruded buildings</string>
<string name="description_animated_image_source">Shows how to animate georeferenced images</string>
<!--Categories-->
<string name="category">category</string>

View File

@@ -158,6 +158,8 @@ add_library(mbgl-android STATIC
platform/android/src/style/sources/unknown_source.hpp
platform/android/src/style/sources/vector_source.cpp
platform/android/src/style/sources/vector_source.hpp
platform/android/src/style/sources/image_source.hpp
platform/android/src/style/sources/image_source.cpp
platform/android/src/style/functions/stop.cpp
platform/android/src/style/functions/stop.hpp
platform/android/src/style/functions/categorical_stops.cpp
@@ -222,6 +224,8 @@ add_library(mbgl-android STATIC
platform/android/src/geometry/lat_lng.hpp
platform/android/src/geometry/lat_lng_bounds.cpp
platform/android/src/geometry/lat_lng_bounds.hpp
platform/android/src/geometry/lat_lng_quad.cpp
platform/android/src/geometry/lat_lng_quad.hpp
platform/android/src/geometry/projected_meters.cpp
platform/android/src/geometry/projected_meters.hpp

View File

@@ -0,0 +1,39 @@
#include "lat_lng_quad.hpp"
#include "lat_lng.hpp"
namespace mbgl {
namespace android {
jni::Object<LatLngQuad> LatLngQuad::New(jni::JNIEnv& env, std::array<mbgl::LatLng, 4> coordinates) {
static auto quadConstructor = LatLngQuad::javaClass.GetConstructor<jni::Object<LatLng>, jni::Object<LatLng>, jni::Object<LatLng>, jni::Object<LatLng>>(env);
return LatLngQuad::javaClass.New(env, quadConstructor,
LatLng::New(env, coordinates[0]),
LatLng::New(env, coordinates[1]),
LatLng::New(env, coordinates[2]),
LatLng::New(env, coordinates[3]));
}
std::array<mbgl::LatLng, 4> LatLngQuad::getLatLngArray(jni::JNIEnv& env, jni::Object<LatLngQuad> quad) {
static auto topLeftField = LatLngQuad::javaClass.GetField <jni::Object<LatLng>>(env, "topLeft");
static auto topRightField = LatLngQuad::javaClass.GetField <jni::Object<LatLng>>(env, "topRight");
static auto bottomRightField = LatLngQuad::javaClass.GetField <jni::Object<LatLng>>(env, "bottomRight");
static auto bottomLeftField = LatLngQuad::javaClass.GetField <jni::Object<LatLng>>(env, "bottomLeft");
return std::array < mbgl::LatLng, 4 > {{
LatLng::getLatLng(env, quad.Get(env, topLeftField)),
LatLng::getLatLng(env, quad.Get(env, topRightField)),
LatLng::getLatLng(env, quad.Get(env, bottomRightField)),
LatLng::getLatLng(env, quad.Get(env, bottomLeftField))
}};
}
void LatLngQuad::registerNative(jni::JNIEnv& env) {
// Lookup the class
LatLngQuad::javaClass = *jni::Class<LatLngQuad>::Find(env).NewGlobalRef(env).release();
}
jni::Class<LatLngQuad> LatLngQuad::javaClass;
} // namespace android
} // namespace mbgl

View File

@@ -0,0 +1,30 @@
#pragma once
#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/geometry.hpp>
#include <jni/jni.hpp>
#include <array>
namespace mbgl {
namespace android {
class LatLngQuad : private mbgl::util::noncopyable {
public:
static constexpr auto Name() { return "com/mapbox/mapboxsdk/geometry/LatLngQuad"; };
static jni::Object<LatLngQuad> New(jni::JNIEnv&, std::array<mbgl::LatLng, 4>);
static std::array<mbgl::LatLng, 4> getLatLngArray(jni::JNIEnv&, jni::Object<LatLngQuad>);
static jni::Class<LatLngQuad> javaClass;
static void registerNative(jni::JNIEnv&);
};
} // namespace android
} // namespace mbgl

View File

@@ -23,6 +23,7 @@
#include "geojson/position.hpp"
#include "geometry/lat_lng.hpp"
#include "geometry/lat_lng_bounds.hpp"
#include "geometry/lat_lng_quad.hpp"
#include "geometry/projected_meters.hpp"
#include "graphics/pointf.hpp"
#include "graphics/rectf.hpp"
@@ -127,6 +128,7 @@ void registerNatives(JavaVM *vm) {
// Geometry
LatLng::registerNative(env);
LatLngBounds::registerNative(env);
LatLngQuad::registerNative(env);
ProjectedMeters::registerNative(env);
// GSon

View File

@@ -0,0 +1,24 @@
#pragma once
#include <mapbox/geojson.hpp>
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/conversion/geojson.hpp>
#include <jni/jni.hpp>
namespace mbgl {
namespace style {
namespace conversion {
template <>
optional<std::array<LatLng, 4>> Converter<std::array<LatLng, 4>>::operator()(const mbgl::android::Value& value, Error& error) const {
if (value.isNull() || !value.isArray()) {
error = { "value cannot be converted to LatLng array" };
return {};
}
return convert<GeoJSON>(value.toString(), error);
}
} // namespace conversion
} // namespace style
} // namespace mbgl

View File

@@ -6,15 +6,19 @@
// C++ -> Java conversion
#include "../../conversion/conversion.hpp"
#include <mbgl/style/conversion.hpp>
#include <mbgl/util/premultiply.hpp>
#include "../../bitmap.hpp"
#include <string>
#include <array>
namespace mbgl {
namespace android {
ImageSource::ImageSource(jni::JNIEnv& env, jni::String sourceId)
ImageSource::ImageSource(jni::JNIEnv& env, jni::String sourceId, jni::Object<LatLngQuad> coordinatesObject)
: Source(env, std::make_unique<mbgl::style::ImageSource>(
jni::Make<std::string>(env, sourceId)
jni::Make<std::string>(env, sourceId),
LatLngQuad::getLatLngArray(env, coordinatesObject)
)
) {
}
@@ -25,31 +29,20 @@ namespace android {
ImageSource::~ImageSource() = default;
void GeoJSONSource::setGeoJSONString(jni::JNIEnv& env, jni::String json) {
using namespace mbgl::style::conversion;
// Convert the jni object
Error error;
optional<GeoJSON> converted = convert<GeoJSON>(Value(env, json), error);
if(!converted) {
mbgl::Log::Error(mbgl::Event::JNI, "Error setting geo json: " + error.message);
return;
}
// Update the core source
source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::setGeoJSON(*converted);
}
void ImageSource::setURL(jni::JNIEnv& env, jni::String url) {
// Update the core source
source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::setURL(jni::Make<std::string>(env, url));
source.as<mbgl::style::ImageSource>()->ImageSource::setURL(jni::Make<std::string>(env, url));
}
jni::String ImageSource::getURL(jni::JNIEnv& env) {
std::string url = source.as<mbgl::style::ImageSource>()->ImageSource::getURL();
return !url.empty() ? jni::Make<jni::String>(env, url) : jni::String();
optional<std::string> url = source.as<mbgl::style::ImageSource>()->ImageSource::getURL();
return url ? jni::Make<jni::String>(env, *url) : jni::String();
}
void ImageSource::setImage(jni::JNIEnv& env, jni::Object<Bitmap> bitmap) {
UnassociatedImage image = util::unpremultiply(Bitmap::GetImage(env, bitmap));
source.as<mbgl::style::ImageSource>()->setImage(std:: move(image));
}
jni::Class<ImageSource> ImageSource::javaClass;
@@ -66,14 +59,13 @@ namespace android {
// Register the peer
jni::RegisterNativePeer<ImageSource>(
env, ImageSource::javaClass, "nativePtr",
std::make_unique<ImageSource, JNIEnv&, jni::String, jni::Object<>>,
"initialize",
"finalize",
METHOD(&ImageSource::setGeoJSONString, "nativeSetGeoJsonString"),
METHOD(&ImageSource::setURL, "nativeSetUrl"),
METHOD(&ImageSource::getURL, "nativeGetUrl"),
METHOD(&ImageSource::getURL, "nativeGetUrl"),
env, ImageSource::javaClass, "nativePtr",
std::make_unique<ImageSource, JNIEnv&, jni::String, jni::Object<LatLngQuad>>,
"initialize",
"finalize",
METHOD(&ImageSource::setURL, "nativeSetUrl"),
METHOD(&ImageSource::getURL, "nativeGetUrl"),
METHOD(&ImageSource::setImage, "nativeSetImage")
);
}

View File

@@ -1,12 +1,15 @@
#pragma once
#include "source.hpp"
#include "../../geometry/lat_lng_quad.hpp"
#include <mbgl/style/sources/image_source.hpp>
#include <jni/jni.hpp>
namespace mbgl {
namespace android {
class Bitmap;
class ImageSource : public Source {
public:
@@ -16,7 +19,7 @@ public:
static void registerNative(jni::JNIEnv&);
ImageSource(jni::JNIEnv&, jni::String, jni::Object<>);
ImageSource(jni::JNIEnv&, jni::String, jni::Object<LatLngQuad>);
ImageSource(mbgl::Map&, mbgl::style::ImageSource&);
@@ -25,6 +28,8 @@ public:
void setURL(jni::JNIEnv&, jni::String);
jni::String getURL(jni::JNIEnv&);
void setImage(jni::JNIEnv&, jni::Object<Bitmap>);
jni::jobject* createJavaPeer(jni::JNIEnv&);
}; // class ImageSource

View File

@@ -2,11 +2,13 @@
#include <mbgl/style/source.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/sources/image_source.hpp>
#include <mbgl/style/sources/raster_source.hpp>
#include <mbgl/style/sources/vector_source.hpp>
#include "source.hpp"
#include "geojson_source.hpp"
#include "image_source.hpp"
#include "raster_source.hpp"
#include "unknown_source.hpp"
#include "vector_source.hpp"
@@ -22,6 +24,8 @@ Source* initializeSourcePeer(mbgl::Map& map, mbgl::style::Source& coreSource) {
source = new RasterSource(map, *coreSource.as<mbgl::style::RasterSource>());
} else if (coreSource.is<mbgl::style::GeoJSONSource>()) {
source = new GeoJSONSource(map, *coreSource.as<mbgl::style::GeoJSONSource>());
} else if (coreSource.is<mbgl::style::ImageSource>()) {
source = new ImageSource(map, *coreSource.as<mbgl::style::ImageSource>());
} else {
source = new UnknownSource(map, coreSource);
}
@@ -39,6 +43,7 @@ jni::jobject* createJavaSourcePeer(jni::JNIEnv& env, mbgl::Map& map, mbgl::style
void registerNativeSources(jni::JNIEnv& env) {
Source::registerNative(env);
GeoJSONSource::registerNative(env);
ImageSource::registerNative(env);
RasterSource::registerNative(env);
UnknownSource::registerNative(env);
VectorSource::registerNative(env);