diff --git a/platform/android/CHANGELOG.md b/platform/android/CHANGELOG.md index a9d31426c30..d8a58fe1403 100644 --- a/platform/android/CHANGELOG.md +++ b/platform/android/CHANGELOG.md @@ -2,6 +2,9 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to do so please see the [`Contributing Guide`](https://github.com/mapbox/mapbox-gl-native/blob/master/CONTRIBUTING.md) first to get started. +## master +* Add support for ImageSource [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) + ## 5.1.0 - TBA * Fix tracking mode + camera race condition [#9133](https://github.com/mapbox/mapbox-gl-native/pull/9133) * Harden orientation changes [#9128](https://github.com/mapbox/mapbox-gl-native/pull/9128) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngQuad.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngQuad.java new file mode 100644 index 00000000000..e374eee8f3e --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngQuad.java @@ -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 + *

+ * This class does not wrap values to the world bounds + *

+ */ +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 CREATOR = new Parcelable.Creator() { + @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); + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java new file mode 100644 index 00000000000..84e5e96fa4b --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/ImageSource.java @@ -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. + *

+ * 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. + *

+ * * @see the style specification + */ +@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; +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index 1a70e4548a1..d57136755fa 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -535,6 +535,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> + + + + + * GL-native equivalent of https://www.mapbox.com/mapbox-gl-js/example/animate-images/ + *

+ */ +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); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_0.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_0.png new file mode 100644 index 00000000000..c304b619c4c Binary files /dev/null and b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_0.png differ diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_1.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_1.png new file mode 100644 index 00000000000..ed09fffbe18 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_1.png differ diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_2.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_2.png new file mode 100644 index 00000000000..fee630f8638 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_2.png differ diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_3.png b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_3.png new file mode 100644 index 00000000000..c4c7146afa6 Binary files /dev/null and b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/drawable-mdpi/southeast_radar_3.png differ diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_image_source.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_image_source.xml new file mode 100644 index 00000000000..ac1f08e821d --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_animated_image_source.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 5b6cbb8c42d..0dd0b343fb8 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -59,6 +59,7 @@ Restrict camera to a bounds Fill extrusions Building layer + Animated Image Source Tracks the location of the user @@ -117,6 +118,7 @@ Limit viewport to Iceland Shows how to add 3D extruded shapes Shows how to show 3D extruded buildings + Shows how to animate georeferenced images category diff --git a/platform/android/config.cmake b/platform/android/config.cmake index 84591d644bc..a7370da5fd4 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -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 diff --git a/platform/android/src/geometry/lat_lng_quad.cpp b/platform/android/src/geometry/lat_lng_quad.cpp new file mode 100644 index 00000000000..2b36139e180 --- /dev/null +++ b/platform/android/src/geometry/lat_lng_quad.cpp @@ -0,0 +1,39 @@ +#include "lat_lng_quad.hpp" +#include "lat_lng.hpp" + +namespace mbgl { +namespace android { + +jni::Object LatLngQuad::New(jni::JNIEnv& env, std::array coordinates) { + static auto quadConstructor = LatLngQuad::javaClass.GetConstructor, jni::Object, jni::Object, jni::Object>(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 LatLngQuad::getLatLngArray(jni::JNIEnv& env, jni::Object quad) { + static auto topLeftField = LatLngQuad::javaClass.GetField >(env, "topLeft"); + static auto topRightField = LatLngQuad::javaClass.GetField >(env, "topRight"); + static auto bottomRightField = LatLngQuad::javaClass.GetField >(env, "bottomRight"); + static auto bottomLeftField = LatLngQuad::javaClass.GetField >(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::Find(env).NewGlobalRef(env).release(); +} + +jni::Class LatLngQuad::javaClass; + + +} // namespace android +} // namespace mbgl \ No newline at end of file diff --git a/platform/android/src/geometry/lat_lng_quad.hpp b/platform/android/src/geometry/lat_lng_quad.hpp new file mode 100644 index 00000000000..8f8c9abeef3 --- /dev/null +++ b/platform/android/src/geometry/lat_lng_quad.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include +#include + +namespace mbgl { +namespace android { + +class LatLngQuad : private mbgl::util::noncopyable { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/geometry/LatLngQuad"; }; + + static jni::Object New(jni::JNIEnv&, std::array); + + static std::array getLatLngArray(jni::JNIEnv&, jni::Object); + + static jni::Class javaClass; + + static void registerNative(jni::JNIEnv&); + +}; + + +} // namespace android +} // namespace mbgl \ No newline at end of file diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 6c490fad5ce..db8dd1dbdf7 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -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 diff --git a/platform/android/src/style/conversion/latlngquad.hpp b/platform/android/src/style/conversion/latlngquad.hpp new file mode 100644 index 00000000000..9d1a83e164a --- /dev/null +++ b/platform/android/src/style/conversion/latlngquad.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +optional> Converter>::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(value.toString(), error); +} + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/platform/android/src/style/sources/image_source.cpp b/platform/android/src/style/sources/image_source.cpp new file mode 100644 index 00000000000..cc7e1e74048 --- /dev/null +++ b/platform/android/src/style/sources/image_source.cpp @@ -0,0 +1,73 @@ +#include "image_source.hpp" + +// Java -> C++ conversion +#include "../android_conversion.hpp" + +// C++ -> Java conversion +#include "../../conversion/conversion.hpp" +#include +#include + +#include "../../bitmap.hpp" +#include +#include + +namespace mbgl { +namespace android { + + ImageSource::ImageSource(jni::JNIEnv& env, jni::String sourceId, jni::Object coordinatesObject) + : Source(env, std::make_unique( + jni::Make(env, sourceId), + LatLngQuad::getLatLngArray(env, coordinatesObject) + ) + ) { + } + + ImageSource::ImageSource(mbgl::Map& map, mbgl::style::ImageSource& coreSource) + : Source(map, coreSource) { + } + + ImageSource::~ImageSource() = default; + + void ImageSource::setURL(jni::JNIEnv& env, jni::String url) { + // Update the core source + source.as()->ImageSource::setURL(jni::Make(env, url)); + } + + jni::String ImageSource::getURL(jni::JNIEnv& env) { + optional url = source.as()->ImageSource::getURL(); + return url ? jni::Make(env, *url) : jni::String(); + } + + void ImageSource::setImage(jni::JNIEnv& env, jni::Object bitmap) { + UnassociatedImage image = util::unpremultiply(Bitmap::GetImage(env, bitmap)); + source.as()->setImage(std:: move(image)); + } + + jni::Class ImageSource::javaClass; + + jni::jobject* ImageSource::createJavaPeer(jni::JNIEnv& env) { + static auto constructor = ImageSource::javaClass.template GetConstructor(env); + return ImageSource::javaClass.New(env, constructor, reinterpret_cast(this)); + } + + void ImageSource::registerNative(jni::JNIEnv& env) { + // Lookup the class + ImageSource::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + + #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer( + env, ImageSource::javaClass, "nativePtr", + std::make_unique>, + "initialize", + "finalize", + METHOD(&ImageSource::setURL, "nativeSetUrl"), + METHOD(&ImageSource::getURL, "nativeGetUrl"), + METHOD(&ImageSource::setImage, "nativeSetImage") + ); + } + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/sources/image_source.hpp b/platform/android/src/style/sources/image_source.hpp new file mode 100644 index 00000000000..309d17a2996 --- /dev/null +++ b/platform/android/src/style/sources/image_source.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "source.hpp" +#include "../../geometry/lat_lng_quad.hpp" +#include +#include + +namespace mbgl { +namespace android { + +class Bitmap; + +class ImageSource : public Source { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/sources/ImageSource"; }; + + static jni::Class javaClass; + + static void registerNative(jni::JNIEnv&); + + ImageSource(jni::JNIEnv&, jni::String, jni::Object); + + ImageSource(mbgl::Map&, mbgl::style::ImageSource&); + + ~ImageSource(); + + void setURL(jni::JNIEnv&, jni::String); + jni::String getURL(jni::JNIEnv&); + + void setImage(jni::JNIEnv&, jni::Object); + + jni::jobject* createJavaPeer(jni::JNIEnv&); + +}; // class ImageSource + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/sources/sources.cpp b/platform/android/src/style/sources/sources.cpp index b4e70202b43..7ca6328e717 100644 --- a/platform/android/src/style/sources/sources.cpp +++ b/platform/android/src/style/sources/sources.cpp @@ -2,11 +2,13 @@ #include #include +#include #include #include #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()); } else if (coreSource.is()) { source = new GeoJSONSource(map, *coreSource.as()); + } else if (coreSource.is()) { + source = new ImageSource(map, *coreSource.as()); } 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); diff --git a/platform/darwin/docs/guides/For Style Authors.md.ejs b/platform/darwin/docs/guides/For Style Authors.md.ejs index 34c1b5dddcb..153639371ce 100644 --- a/platform/darwin/docs/guides/For Style Authors.md.ejs +++ b/platform/darwin/docs/guides/For Style Authors.md.ejs @@ -178,8 +178,9 @@ In style JSON | In the SDK `geojson` | `MGLShapeSource` `raster` | `MGLRasterSource` `vector` | `MGLVectorSource` +`image` | `MGLImageSource` -`canvas`, `image`, and `video` sources are not supported. +`canvas` and `video` sources are not supported. ### Tile sources @@ -220,6 +221,12 @@ To create a shape source from local GeoJSON data, first [convert the GeoJSON data into a shape](working-with-geojson-data.html#converting-geojson-data-into-shape-objects), then use the `-[MGLShapeSource initWithIdentifier:shape:options:]` method. +### Image sources + +Image sources accept a non-axis aligned quadrilateral as their geographic coordinates. +These coordinates, in `MGLCoordinateQuad`, are described in counterclockwise order, +in contrast to the clockwise order defined in the style specification. + ## Configuring the map content’s appearance Each layer defined by the style JSON file is represented at runtime by a style diff --git a/platform/darwin/src/MGLGeometry.h b/platform/darwin/src/MGLGeometry.h index 9fcb9dd37cb..3a3e59fb3ec 100644 --- a/platform/darwin/src/MGLGeometry.h +++ b/platform/darwin/src/MGLGeometry.h @@ -45,6 +45,24 @@ typedef struct MGLCoordinateBounds { CLLocationCoordinate2D ne; } MGLCoordinateBounds; +/** + A quadrilateral area as measured on a two-dimensional map projection. + `MGLCoordinateQuad` differs from `MGLCoordinateBounds` in that it allows + representation of non-axis aligned bounds and non-rectangular quadrilaterals. + The coordinates are described in counter clockwise order from top left. + */ +typedef struct MGLCoordinateQuad { + /** Coordinate at the top left corner. */ + CLLocationCoordinate2D topLeft; + /** Coordinate at the bottom left corner. */ + CLLocationCoordinate2D bottomLeft; + /** Coordinate at the bottom right corner. */ + CLLocationCoordinate2D bottomRight; + /** Coordinate at the top right corner. */ + CLLocationCoordinate2D topRight; +} MGLCoordinateQuad; + + /** Creates a new `MGLCoordinateBounds` structure from the given southwest and northeast coordinates. @@ -56,6 +74,33 @@ NS_INLINE MGLCoordinateBounds MGLCoordinateBoundsMake(CLLocationCoordinate2D sw, return bounds; } +/** + Creates a new `MGLCoordinateQuad` structure from the given top left, + bottom left, bottom right, and top right coordinates. + */ +NS_INLINE MGLCoordinateQuad MGLCoordinateQuadMake(CLLocationCoordinate2D topLeft, CLLocationCoordinate2D bottomLeft, CLLocationCoordinate2D bottomRight, CLLocationCoordinate2D topRight) { + MGLCoordinateQuad quad; + quad.topLeft = topLeft; + quad.bottomLeft = bottomLeft; + quad.bottomRight = bottomRight; + quad.topRight = topRight; + return quad; +} + +/** + Creates a new `MGLCoordinateQuad` structure from the given `MGLCoordinateBounds`. + The returned quad uses the bounds' northeast coordinate as the top right, and the + southwest coordinate at the bottom left. + */ +NS_INLINE MGLCoordinateQuad MGLCoordinateQuadFromCoordinateBounds(MGLCoordinateBounds bounds) { + MGLCoordinateQuad quad; + quad.topLeft = CLLocationCoordinate2DMake(bounds.ne.latitude, bounds.sw.longitude); + quad.bottomLeft = bounds.sw; + quad.bottomRight = CLLocationCoordinate2DMake(bounds.sw.latitude, bounds.ne.longitude); + quad.topRight = bounds.ne; + return quad; +} + /** Returns `YES` if the two coordinate bounds are equal to each other. */ NS_INLINE BOOL MGLCoordinateBoundsEqualToCoordinateBounds(MGLCoordinateBounds bounds1, MGLCoordinateBounds bounds2) { return (bounds1.sw.latitude == bounds2.sw.latitude && @@ -117,6 +162,15 @@ NS_INLINE NSString *MGLStringFromCoordinateBounds(MGLCoordinateBounds bounds) { bounds.ne.latitude, bounds.ne.longitude]; } +/** Returns a formatted string for the given coordinate quad. */ +NS_INLINE NSString *MGLStringFromCoordinateQuad(MGLCoordinateQuad quad) { + return [NSString stringWithFormat:@"{ topleft = {%.1f, %.1f}, bottomleft = {%.1f, %.1f}}, bottomright = {%.1f, %.1f}, topright = {%.1f, %.1f}", + quad.topLeft.latitude, quad.topLeft.longitude, + quad.bottomLeft.latitude, quad.bottomLeft.longitude, + quad.bottomRight.latitude, quad.bottomRight.longitude, + quad.topRight.latitude, quad.topRight.longitude]; +} + /** Returns radians, converted from degrees. */ NS_INLINE CGFloat MGLRadiansFromDegrees(CLLocationDegrees degrees) { return (CGFloat)(degrees * M_PI) / 180; diff --git a/platform/darwin/src/MGLGeometry_Private.h b/platform/darwin/src/MGLGeometry_Private.h index 7ad8314a792..88fcf5b5762 100644 --- a/platform/darwin/src/MGLGeometry_Private.h +++ b/platform/darwin/src/MGLGeometry_Private.h @@ -8,6 +8,7 @@ #import #import +#import typedef double MGLLocationRadians; typedef double MGLRadianDistance; typedef double MGLRadianDirection; @@ -56,6 +57,20 @@ NS_INLINE mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBo MGLLatLngFromLocationCoordinate2D(coordinateBounds.ne)); } +NS_INLINE std::array MGLLatLngArrayFromCoordinateQuad(MGLCoordinateQuad quad) { + return { MGLLatLngFromLocationCoordinate2D(quad.topLeft), + MGLLatLngFromLocationCoordinate2D(quad.topRight), + MGLLatLngFromLocationCoordinate2D(quad.bottomRight), + MGLLatLngFromLocationCoordinate2D(quad.bottomLeft) }; +} + +NS_INLINE MGLCoordinateQuad MGLCoordinateQuadFromLatLngArray(std::array quad) { + return { MGLLocationCoordinate2DFromLatLng(quad[0]), + MGLLocationCoordinate2DFromLatLng(quad[3]), + MGLLocationCoordinate2DFromLatLng(quad[2]), + MGLLocationCoordinate2DFromLatLng(quad[1]) }; +} + #if TARGET_OS_IPHONE NS_INLINE mbgl::EdgeInsets MGLEdgeInsetsFromNSEdgeInsets(UIEdgeInsets insets) { return { insets.top, insets.left, insets.bottom, insets.right }; diff --git a/platform/darwin/src/MGLImageSource.h b/platform/darwin/src/MGLImageSource.h new file mode 100644 index 00000000000..21487d9739e --- /dev/null +++ b/platform/darwin/src/MGLImageSource.h @@ -0,0 +1,94 @@ +#import "MGLSource.h" + +#import "MGLFoundation.h" +#import "MGLTypes.h" +#import "MGLGeometry.h" + +NS_ASSUME_NONNULL_BEGIN + +MGL_EXPORT +/** + `MGLImageSource` is a content source that is used for a georeferenced raster + image to be shown on the map. The georeferenced image scales and rotates as the + user zooms and rotates the map. Images may also be used as icons or patterns + in a style layer. To register an image for use as an icon or pattern, + use the `-[MGLStyle setImage:forName:]` method. To configure a point + annotation’s image, use the `MGLAnnotationImage` class. + + The geographic location of the raster image content, supplied with + `MGLCoordinateQuad`, can be non-axis aligned. + `MGLImageSource` supports raster content from `NSURL`, `NSImage` (macOS), or + `UIImage` (iOS). + An image source is added to an `MGLStyle` object along with one or more + `MGLRasterStyleLayer` objects. Use a raster style layer to control the + appearance of content supplied by the image source. + + Each + image + source defined by the style JSON file is represented at runtime by an + `MGLImageSource` object that you can use to initialize new style layers. You + can also add and remove sources dynamically using methods such as + `-[MGLStyle addSource:]` and `-[MGLStyle sourceWithIdentifier:]`. + + ### Example + + ```swift + let coordinates = MGLCoordinateQuad( + topLeft: CLLocationCoordinate2D(latitude: 46.437, longitude: -80.425), + bottomLeft: CLLocationCoordinate2D(latitude: 37.936, longitude: -80.425), + bottomRight: CLLocationCoordinate2D(latitude: 37.936, longitude: -71.516), + topRight: CLLocationCoordinate2D(latitude: 46.437, longitude: -71.516)) + let source = MGLImageSource(identifier: "radar", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!) + mapView.style?.addSource(source) + ``` + */ +MGL_EXPORT +@interface MGLImageSource : MGLSource + +#pragma mark Initializing a Source + +/** + Returns a georeferenced image source with an identifier, coordinates and a URL. + + @param identifier A string that uniquely identifies the source. + @param coordinateQuad the top left, top right, bottom right, and bottom left coordinates for the image. + @param url An HTTP(S) URL, absolute file URL, or local file URL relative to the + current application’s resource bundle. + @return An initialized shape source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MGLCoordinateQuad)coordinateQuad URL:(NSURL *)url; + +/** + Returns a georeferenced image source with an identifier, coordinates and an image. + + @param identifier A string that uniquely identifies the source. + @param coordinateQuad The top left, top right, bottom right, and bottom left coordinates for the image. + @param image The image to display for the source. + @return An initialized shape source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MGLCoordinateQuad)coordinateQuad image:(MGLImage *)image; + +#pragma mark Accessing a Source’s Content + +/** + The URL to the source image. + + If the receiver was initialized using `-initWithIdentifier:coordinateQuad:image:` or + the `image` property is set, this property is set to `nil`. + */ +@property (nonatomic, copy, nullable)NSURL *URL; + +/** + The source image. + + If the receiver was initialized using `-initWithIdentifier:coordinateQuad:URL:` or if the `URL` property is set, this property is set to `nil`. + */ +@property (nonatomic, retain, nullable)MGLImage *image; + +/** + The coordinates at which the corners of the source image will be placed. + */ +@property (nonatomic) MGLCoordinateQuad coordinates; +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLImageSource.mm b/platform/darwin/src/MGLImageSource.mm new file mode 100644 index 00000000000..0a2dc2713ff --- /dev/null +++ b/platform/darwin/src/MGLImageSource.mm @@ -0,0 +1,93 @@ +#import "MGLImageSource.h" + +#import "MGLGeometry_Private.h" +#import "MGLSource_Private.h" +#import "MGLTileSource_Private.h" +#import "NSURL+MGLAdditions.h" +#if TARGET_OS_IPHONE + #import "UIImage+MGLAdditions.h" +#else + #import "NSImage+MGLAdditions.h" +#endif + +#include +#include + +@interface MGLImageSource () +- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MGLCoordinateQuad)coordinateQuad NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly) mbgl::style::ImageSource *rawSource; + +@end + +@implementation MGLImageSource + +- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MGLCoordinateQuad)coordinateQuad { + + const auto coordsArray = MGLLatLngArrayFromCoordinateQuad(coordinateQuad); + auto source = std::make_unique(identifier.UTF8String, coordsArray); + return self = [super initWithPendingSource:std::move(source)]; +} + + +- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MGLCoordinateQuad)coordinateQuad URL:(NSURL *)url { + self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad]; + self.URL = url; + return self; +} + + +- (instancetype)initWithIdentifier:(NSString *)identifier coordinateQuad:(MGLCoordinateQuad)coordinateQuad image:(MGLImage *)image { + self = [self initWithIdentifier:identifier coordinateQuad: coordinateQuad]; + self.image = image; + + return self; +} + +- (NSURL *)URL { + auto url = self.rawSource->getURL(); + return url ? [NSURL URLWithString:@(url->c_str())] : nil; +} + +- (void)setURL:(NSURL *)url { + if (url) { + self.rawSource->setURL(url.mgl_URLByStandardizingScheme.absoluteString.UTF8String); + _image = nil; + } else { + self.image = nullptr; + } +} + +- (void)setImage:(MGLImage *)image { + if (image != nullptr) { + mbgl::UnassociatedImage unassociatedImage = mbgl::util::unpremultiply(image.mgl_premultipliedImage); + self.rawSource->setImage(std::move(unassociatedImage)); + } else { + self.rawSource->setImage(mbgl::UnassociatedImage({0,0})); + } + _image = image; +} + +- (MGLCoordinateQuad)coordinates { + return MGLCoordinateQuadFromLatLngArray(self.rawSource->getCoordinates()); +} + +- (void)setCoordinates: (MGLCoordinateQuad)coordinateQuad { + self.rawSource->setCoordinates(MGLLatLngArrayFromCoordinateQuad(coordinateQuad)); +} + +- (NSString *)description { + return [NSString stringWithFormat:@"<%@: %p; identifier = %@; coordinates = %@; URL = %@; image = %@>", + NSStringFromClass([self class]), (void *)self, self.identifier, MGLStringFromCoordinateQuad(self.coordinates), self.URL, self.image]; +} + +- (mbgl::style::ImageSource *)rawSource { + return (mbgl::style::ImageSource *)super.rawSource; +} + +- (NSString *)attributionHTMLString { + auto attribution = self.rawSource->getAttribution(); + return attribution ? @(attribution->c_str()) : nil; +} + +@end diff --git a/platform/darwin/src/MGLOfflineStorage.h b/platform/darwin/src/MGLOfflineStorage.h index 16f134adb16..b009f893b34 100644 --- a/platform/darwin/src/MGLOfflineStorage.h +++ b/platform/darwin/src/MGLOfflineStorage.h @@ -154,6 +154,8 @@ typedef NS_ENUM(NSUInteger, MGLResourceKind) { /** JSON part of a sprite sheet. It is constructed of the prefix in https://www.mapbox.com/mapbox-gl-js/style-spec/#root-sprite and a JSON file extension. */ MGLResourceKindSpriteJSON, + /** Image data for a georeferenced image source. **/ + MGLResourceKindImage, }; /** diff --git a/platform/darwin/src/MGLOfflineStorage.mm b/platform/darwin/src/MGLOfflineStorage.mm index 81774ad3cb5..040b36f8be8 100644 --- a/platform/darwin/src/MGLOfflineStorage.mm +++ b/platform/darwin/src/MGLOfflineStorage.mm @@ -99,6 +99,9 @@ - (void)setDelegate:(id)newValue { case mbgl::Resource::Kind::SpriteJSON: kind = MGLResourceKindSpriteJSON; break; + case mbgl::Resource::Kind::Image: + kind = MGLResourceKindImage; + break; case mbgl::Resource::Kind::Unknown: kind = MGLResourceKindUnknown; break; diff --git a/platform/darwin/src/MGLSource.h b/platform/darwin/src/MGLSource.h index 8bf48907cc0..f990aedd676 100644 --- a/platform/darwin/src/MGLSource.h +++ b/platform/darwin/src/MGLSource.h @@ -17,8 +17,9 @@ NS_ASSUME_NONNULL_BEGIN `-[MGLStyle addSource:]` and `-[MGLStyle sourceWithIdentifier:]`. Do not create instances of this class directly, and do not create your own - subclasses of this class. Instead, create instances of `MGLShapeSource` and the - concrete subclasses of `MGLTileSource`, `MGLVectorSource` and `MGLRasterSource`. + subclasses of this class. Instead, create instances of `MGLShapeSource`, + `MGLImageSource` and the concrete subclasses of `MGLTileSource`, + `MGLVectorSource` and `MGLRasterSource`. */ MGL_EXPORT @interface MGLSource : NSObject diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 2e6f2bc29a6..592ab86780b 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -23,6 +23,7 @@ #import "MGLVectorSource.h" #import "MGLRasterSource.h" #import "MGLShapeSource.h" +#import "MGLImageSource.h" #import "MGLAttributionInfo_Private.h" @@ -41,6 +42,7 @@ #include #include #include +#include #if TARGET_OS_IPHONE #import "UIImage+MGLAdditions.h" @@ -178,6 +180,8 @@ - (MGLSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource { return [[MGLShapeSource alloc] initWithRawSource:geoJSONSource]; } else if (auto rasterSource = rawSource->as()) { return [[MGLRasterSource alloc] initWithRawSource:rasterSource]; + } else if (auto imageSource = rawSource->as()) { + return [[MGLImageSource alloc] initWithRawSource:imageSource]; } else { return [[MGLSource alloc] initWithRawSource:rawSource]; } diff --git a/platform/darwin/src/NSValue+MGLAdditions.h b/platform/darwin/src/NSValue+MGLAdditions.h index 0aaa2a337a4..f3026a389f3 100644 --- a/platform/darwin/src/NSValue+MGLAdditions.h +++ b/platform/darwin/src/NSValue+MGLAdditions.h @@ -56,6 +56,20 @@ NS_ASSUME_NONNULL_BEGIN */ @property (readonly) MGLCoordinateBounds MGLCoordinateBoundsValue; +/** + Creates a new value object containing the specified Mapbox coordinate + quad structure. + + @param quad The value for the new object. + @return A new value object that contains the coordinate quad information. + */ ++ (instancetype)valueWithMGLCoordinateQuad:(MGLCoordinateQuad)quad; + +/** + The Mapbox coordinate quad structure representation of the value. + */ +- (MGLCoordinateQuad)MGLCoordinateQuadValue; + #pragma mark Working with Offline Map Values /** diff --git a/platform/darwin/src/NSValue+MGLAdditions.m b/platform/darwin/src/NSValue+MGLAdditions.m index ef894f0eb47..1383056944a 100644 --- a/platform/darwin/src/NSValue+MGLAdditions.m +++ b/platform/darwin/src/NSValue+MGLAdditions.m @@ -34,6 +34,16 @@ - (MGLCoordinateBounds)MGLCoordinateBoundsValue { return bounds; } ++ (instancetype)valueWithMGLCoordinateQuad:(MGLCoordinateQuad)quad { + return [self valueWithBytes:&quad objCType:@encode(MGLCoordinateQuad)]; +} + +- (MGLCoordinateQuad)MGLCoordinateQuadValue { + MGLCoordinateQuad quad; + [self getValue:&quad]; + return quad; +} + #pragma mark Offline maps + (NSValue *)valueWithMGLOfflinePackProgress:(MGLOfflinePackProgress)progress { diff --git a/platform/darwin/test/MGLDocumentationExampleTests.swift b/platform/darwin/test/MGLDocumentationExampleTests.swift index 48e6b17f445..ae72b35d82e 100644 --- a/platform/darwin/test/MGLDocumentationExampleTests.swift +++ b/platform/darwin/test/MGLDocumentationExampleTests.swift @@ -104,6 +104,20 @@ class MGLDocumentationExampleTests: XCTestCase, MGLMapViewDelegate { XCTAssertNotNil(mapView.style?.source(withIdentifier: "pois")) } + func testMGLImageSource() { + //#-example-code + let coordinates = MGLCoordinateQuad( + topLeft: CLLocationCoordinate2D(latitude: 46.437, longitude: -80.425), + bottomLeft: CLLocationCoordinate2D(latitude: 37.936, longitude: -80.425), + bottomRight: CLLocationCoordinate2D(latitude: 37.936, longitude: -71.516), + topRight: CLLocationCoordinate2D(latitude: 46.437, longitude: -71.516)) + let source = MGLImageSource(identifier: "radar", coordinateQuad: coordinates, url: URL(string: "https://www.mapbox.com/mapbox-gl-js/assets/radar.gif")!) + mapView.style?.addSource(source) + //#-end-example-code + + XCTAssertNotNil(mapView.style?.source(withIdentifier: "radar")) + } + func testMGLCircleStyleLayer() { let population = MGLVectorSource(identifier: "population", configurationURL: URL(string: "https://example.com/style.json")!) mapView.style?.addSource(population) diff --git a/platform/darwin/test/MGLGeometryTests.mm b/platform/darwin/test/MGLGeometryTests.mm index 220a837643c..1c854701884 100644 --- a/platform/darwin/test/MGLGeometryTests.mm +++ b/platform/darwin/test/MGLGeometryTests.mm @@ -144,4 +144,23 @@ - (void)testGeoJSONSerialization { XCTAssertEqualObjects(serializedGeoJSON, geoJSON, @"MGLPointFeature should serialize as a GeoJSON point feature."); } +- (void)testMGLCoordinateBoundsToMGLCoordinateQuad { + MGLCoordinateBounds bounds = MGLCoordinateBoundsMake(CLLocationCoordinate2DMake(37.936, -80.425), + CLLocationCoordinate2DMake(46.437, -71.516)); + + MGLCoordinateQuad quad = MGLCoordinateQuadFromCoordinateBounds(bounds); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:bounds.sw], + [NSValue valueWithMGLCoordinate:quad.bottomLeft], + @"Bounds southwest should be bottom left of quad."); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:bounds.ne], + [NSValue valueWithMGLCoordinate:quad.topRight], + @"Bounds northeast should be top right of quad."); + + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(46.437, -80.425)], + [NSValue valueWithMGLCoordinate:quad.topLeft], + @"Quad top left should be computed correctly."); + XCTAssertEqualObjects([NSValue valueWithMGLCoordinate:CLLocationCoordinate2DMake(37.936, -71.516)], + [NSValue valueWithMGLCoordinate:quad.bottomRight], + @"Quad bottom right should be computed correctly."); +} @end diff --git a/platform/darwin/test/MGLImageSourceTests.m b/platform/darwin/test/MGLImageSourceTests.m new file mode 100644 index 00000000000..38fcd38709b --- /dev/null +++ b/platform/darwin/test/MGLImageSourceTests.m @@ -0,0 +1,42 @@ +#import + +#import + +@interface MGLImageSourceTests : XCTestCase + +@end + +@implementation MGLImageSourceTests + + +- (void)testMGLImageSourceWithImageURL { + + MGLCoordinateQuad quad = { { 80, 37}, { 81, 37}, { 81, 39}, { 80, 39}}; + MGLImageSource *source = [[MGLImageSource alloc] initWithIdentifier:@"source-id" coordinateQuad:quad URL:[NSURL URLWithString:@"http://host/image.png"]]; + + XCTAssertNotNil(source.URL); + XCTAssertEqualObjects(source.URL.absoluteString, @"http://host/image.png"); + XCTAssertNil(source.image); +} + +- (void)testMGLImageSourceWithImage { + + NSString *imageName = @"RadarImage"; +#if TARGET_OS_IPHONE + MGLImage *image = [MGLImage imageNamed:imageName + inBundle:[NSBundle bundleForClass:[self class]] + compatibleWithTraitCollection:nil]; +#else + MGLImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:imageName]; +#endif + XCTAssertNotNil(image); + + MGLCoordinateQuad quad = { { 80, 37}, { 81, 37}, { 81, 39}, { 80, 39}}; + MGLImageSource *source = [[MGLImageSource alloc] initWithIdentifier:@"source-id" coordinateQuad:quad image:image]; + + XCTAssertNotNil(source.image); + XCTAssertEqualObjects(source.image, image); + XCTAssertNil(source.URL); +} + +@end diff --git a/platform/darwin/test/MGLStyleTests.mm b/platform/darwin/test/MGLStyleTests.mm index d93483ea6e9..608cfdfd2df 100644 --- a/platform/darwin/test/MGLStyleTests.mm +++ b/platform/darwin/test/MGLStyleTests.mm @@ -210,9 +210,9 @@ - (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier { MGLRasterSource *rasterSource = [[MGLRasterSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; [self.style addSource:rasterSource]; - // Attempt to remove a shape source with the same identifier as the raster source - MGLShapeSource *shapeSource = [[MGLShapeSource alloc] initWithIdentifier:@"some-identifier" shape:nil options:nil]; - [self.style removeSource:shapeSource]; + // Attempt to remove an image source with the same identifier as the raster source + MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"some-identifier" coordinateQuad: { } URL:[NSURL URLWithString:@"http://host/image.png"]]; + [self.style removeSource:imageSource]; // The raster source should still be added XCTAssertTrue([[self.style sourceWithIdentifier:rasterSource.identifier] isMemberOfClass:[MGLRasterSource class]]); @@ -220,16 +220,16 @@ - (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier { [self.style removeSource:rasterSource]; // Add the shape source - [self.style addSource:shapeSource]; + [self.style addSource:imageSource]; // Attempt to remove a vector source with the same identifer as the shape source MGLVectorSource *vectorSource = [[MGLVectorSource alloc] initWithIdentifier:@"some-identifier" tileURLTemplates:@[] options:nil]; [self.style removeSource:vectorSource]; - // The shape source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:shapeSource.identifier] isMemberOfClass:[MGLShapeSource class]]); + // The image source should still be added + XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MGLImageSource class]]); - // Remove the shape source - [self.style removeSource:shapeSource]; + // Remove the image source + [self.style removeSource:imageSource]; // Add the vector source [self.style addSource:vectorSource]; @@ -237,7 +237,7 @@ - (void)testAddingSourceOfTypeABeforeSourceOfTypeBWithSameIdentifier { // Attempt to remove the previously created raster source that has the same identifer as the shape source [self.style removeSource:rasterSource]; // The vector source should still be added - XCTAssertTrue([[self.style sourceWithIdentifier:shapeSource.identifier] isMemberOfClass:[MGLVectorSource class]]); + XCTAssertTrue([[self.style sourceWithIdentifier:imageSource.identifier] isMemberOfClass:[MGLVectorSource class]]); } - (void)testRemovingSourceInUse { diff --git a/platform/darwin/test/Media.xcassets/RadarImage.imageset/Contents.json b/platform/darwin/test/Media.xcassets/RadarImage.imageset/Contents.json new file mode 100644 index 00000000000..79be9ed970c --- /dev/null +++ b/platform/darwin/test/Media.xcassets/RadarImage.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "radar.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/darwin/test/Media.xcassets/RadarImage.imageset/radar.png b/platform/darwin/test/Media.xcassets/RadarImage.imageset/radar.png new file mode 100644 index 00000000000..e23697f42a8 Binary files /dev/null and b/platform/darwin/test/Media.xcassets/RadarImage.imageset/radar.png differ diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 977dd1b4187..ce56d157c14 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -2,7 +2,11 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONTRIBUTING.md](../../CONTRIBUTING.md) to get started. -## 3.6.0 +## master + +### Styles + +* Added suuport for diplaying georeferenced images via the `MGLImageSource`. [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) ### Packaging diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 672bb3b9970..29c5c65012f 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -72,6 +72,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingUpdateShapeSourceFeatures, MBXSettingsRuntimeStylingVectorSource, MBXSettingsRuntimeStylingRasterSource, + MBXSettingsRuntimeStylingImageSource, MBXSettingsRuntimeStylingCountryLabels, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, @@ -345,6 +346,7 @@ - (void)dismissSettings:(__unused id)sender @"Update Shape Source: Features", @"Style Vector Source", @"Style Raster Source", + @"Style Image Source", [NSString stringWithFormat:@"Label Countries in %@", (_usingLocaleBasedCountryLabels ? @"Local Language" : [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:[self bestLanguageForUser]])], @"Add Route Line", @"Dynamically Style Polygon", @@ -585,6 +587,9 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath case MBXSettingsRuntimeStylingRasterSource: [self styleRasterSource]; break; + case MBXSettingsRuntimeStylingImageSource: + [self styleImageSource]; + break; case MBXSettingsRuntimeStylingCountryLabels: [self styleCountryLabelsLanguage]; break; @@ -1308,6 +1313,39 @@ - (void)styleRasterSource [self.mapView.style addLayer:rasterLayer]; } +- (void)styleImageSource +{ + MGLCoordinateQuad coordinateQuad = { + { 46.437, -80.425 }, + { 37.936, -80.425 }, + { 37.936, -71.516 }, + { 46.437, -71.516 } }; + + MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"style-image-source-id" coordinateQuad:coordinateQuad URL:[NSURL URLWithString:@"https://www.mapbox.com/mapbox-gl-js/assets/radar0.gif"]]; + + [self.mapView.style addSource:imageSource]; + + MGLRasterStyleLayer *rasterLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"style-raster-image-layer-id" source:imageSource]; + [self.mapView.style addLayer:rasterLayer]; + + [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(updateAnimatedImageSource:) + userInfo:imageSource + repeats:YES]; +} + + +- (void)updateAnimatedImageSource:(NSTimer *)timer { + static int radarSuffix = 0; + MGLImageSource *imageSource = (MGLImageSource *)timer.userInfo; + NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://www.mapbox.com/mapbox-gl-js/assets/radar%d.gif", radarSuffix++]]; + [imageSource setValue:url forKey:@"URL"]; + if (radarSuffix > 3) { + radarSuffix = 0; + } +} + -(void)styleCountryLabelsLanguage { NSArray *labelLayers = @[ diff --git a/platform/ios/docs/guides/For Style Authors.md b/platform/ios/docs/guides/For Style Authors.md index 734e0dc9ee8..8b7bc05f2c0 100644 --- a/platform/ios/docs/guides/For Style Authors.md +++ b/platform/ios/docs/guides/For Style Authors.md @@ -127,8 +127,9 @@ In style JSON | In the SDK `geojson` | `MGLShapeSource` `raster` | `MGLRasterSource` `vector` | `MGLVectorSource` +`image` | `MGLImageSource` -`canvas`, `image`, and `video` sources are not supported. +`canvas` and `video` sources are not supported. ### Tile sources @@ -169,6 +170,12 @@ To create a shape source from local GeoJSON data, first [convert the GeoJSON data into a shape](working-with-geojson-data.html#converting-geojson-data-into-shape-objects), then use the `-[MGLShapeSource initWithIdentifier:shape:options:]` method. +### Image sources + +Image sources accept a non-axis aligned quadrilateral as their geographic coordinates. +These coordinates, in `MGLCoordinateQuad`, are described in counterclockwise order, +in contrast to the clockwise order defined in the style specification. + ## Configuring the map content’s appearance Each layer defined by the style JSON file is represented at runtime by a style diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index 0cfe72de813..a906c4fd777 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -7,6 +7,11 @@ objects = { /* Begin PBXBuildFile section */ + 071BBAFF1EE7613E001FB02A /* MGLImageSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 071BBAFD1EE75CD4001FB02A /* MGLImageSource.mm */; }; + 071BBB001EE7613F001FB02A /* MGLImageSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 071BBAFD1EE75CD4001FB02A /* MGLImageSource.mm */; }; + 071BBB031EE76146001FB02A /* MGLImageSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 071BBB071EE77631001FB02A /* MGLImageSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 071BBB051EE7761A001FB02A /* MGLImageSourceTests.m */; }; 1753ED421E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1753ED431E53CE6F00A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */; }; 1F06668A1EC64F8E001C16D7 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -538,6 +543,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLImageSource.h; sourceTree = ""; }; + 071BBAFD1EE75CD4001FB02A /* MGLImageSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLImageSource.mm; sourceTree = ""; }; + 071BBB051EE7761A001FB02A /* MGLImageSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLImageSourceTests.m; path = ../../darwin/test/MGLImageSourceTests.m; sourceTree = ""; }; 1753ED411E53CE6F00A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F0666881EC64F8E001C16D7 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; 1F0666891EC64F8E001C16D7 /* MGLLight.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLLight.mm; sourceTree = ""; }; @@ -976,6 +984,8 @@ 35136D491D4277EA00C20EFD /* Sources */ = { isa = PBXGroup; children = ( + 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */, + 071BBAFD1EE75CD4001FB02A /* MGLImageSource.mm */, 3566C76A1D4A8DFA008152BC /* MGLRasterSource.h */, DAF0D80F1DFE0EA000B28378 /* MGLRasterSource_Private.h */, 3566C76B1D4A8DFA008152BC /* MGLRasterSource.mm */, @@ -1133,6 +1143,7 @@ 40CFA64E1D78754A008103BD /* Sources */ = { isa = PBXGroup; children = ( + 071BBB051EE7761A001FB02A /* MGLImageSourceTests.m */, 40CFA6501D787579008103BD /* MGLShapeSourceTests.mm */, 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */, 4085AF081D933DEA00F11B22 /* MGLTileSetTests.mm */, @@ -1665,6 +1676,7 @@ DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */, 353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */, DAAF722D1DA903C700312FA4 /* MGLStyleValue_Private.h in Headers */, + 071BBB031EE76146001FB02A /* MGLImageSource.h in Headers */, DA8847F41CBAFA5100AB86E3 /* MGLOfflinePack.h in Headers */, DA88482E1CBAFA6200AB86E3 /* NSException+MGLAdditions.h in Headers */, DA8848551CBAFB9800AB86E3 /* MGLLocationManager.h in Headers */, @@ -1748,6 +1760,7 @@ 3566C7671D4A77BA008152BC /* MGLShapeSource.h in Headers */, DA35A29F1CC9E94C00E826B2 /* MGLCoordinateFormatter.h in Headers */, 404C26E31D89B877000AA13D /* MGLTileSource.h in Headers */, + 071BBB041EE76147001FB02A /* MGLImageSource.h in Headers */, DABFB8611CBE99E500D62B32 /* MGLMultiPoint.h in Headers */, 3510FFF11D6D9D8C00F413B2 /* NSExpression+MGLAdditions.h in Headers */, 35D3A1E71E9BE7EC002B38EE /* MGLScaleBar.h in Headers */, @@ -1958,7 +1971,6 @@ TargetAttributes = { DA1DC9491CB6C1C2006E619F = { CreatedOnToolsVersion = 7.3; - DevelopmentTeam = GJZR2MEM28; LastSwiftMigration = 0820; }; DA25D5B81CCD9EDE00607828 = { @@ -2139,6 +2151,7 @@ 35D9DDE21DA25EEC00DAAD69 /* MGLCodingTests.m in Sources */, DA1F8F3D1EBD287B00367E42 /* MGLDocumentationGuideTests.swift in Sources */, 3598544D1E1D38AA00B29F84 /* MGLDistanceFormatterTests.m in Sources */, + 071BBB071EE77631001FB02A /* MGLImageSourceTests.m in Sources */, DA2DBBCE1D51E80400D38FF9 /* MGLStyleLayerTests.m in Sources */, DA35A2C61CCA9F8300E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */, DAE7DEC21E245455007505A6 /* MGLNSStringAdditionsTests.m in Sources */, @@ -2215,6 +2228,7 @@ DD0902A91DB1929D00C5BDCE /* MGLNetworkConfiguration.m in Sources */, 35D13AB91D3D15E300AFB4E0 /* MGLStyleLayer.mm in Sources */, DA35A2CB1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */, + 071BBB001EE7613F001FB02A /* MGLImageSource.mm in Sources */, DA8848321CBAFA6200AB86E3 /* NSString+MGLAdditions.m in Sources */, 408AA8581DAEDA1E00022900 /* NSDictionary+MGLAdditions.mm in Sources */, DA35A2A11CC9E95F00E826B2 /* MGLCoordinateFormatter.m in Sources */, @@ -2296,6 +2310,7 @@ DD0902AA1DB1929D00C5BDCE /* MGLNetworkConfiguration.m in Sources */, DA35A2B41CCA141D00E826B2 /* MGLCompassDirectionFormatter.m in Sources */, 35D13ABA1D3D15E300AFB4E0 /* MGLStyleLayer.mm in Sources */, + 071BBAFF1EE7613E001FB02A /* MGLImageSource.mm in Sources */, DA35A2CC1CCAAAD200E826B2 /* NSValue+MGLAdditions.m in Sources */, 408AA8591DAEDA1E00022900 /* NSDictionary+MGLAdditions.mm in Sources */, DAA4E4281CBB730400178DFB /* MGLTypes.m in Sources */, @@ -2616,6 +2631,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/app/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -2629,6 +2645,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = "$(SRCROOT)/app/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index 31380faa2c7..ba56c312eb8 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -75,6 +75,7 @@ custom_categories: children: - MGLSource - MGLTileSource + - MGLImageSource - MGLShapeSource - MGLRasterSource - MGLVectorSource @@ -108,6 +109,9 @@ custom_categories: - MGLCoordinateBoundsMake - MGLCoordinateBoundsOffset - MGLCoordinateInCoordinateBounds + - MGLCoordinateQuad + - MGLCoordinateQuadMake + - MGLCoordinateQuadFromCoordinateBounds - MGLCoordinateSpan - MGLCoordinateSpanEqualToCoordinateSpan - MGLCoordinateSpanMake @@ -115,6 +119,7 @@ custom_categories: - MGLDegreesFromRadians - MGLRadiansFromDegrees - MGLStringFromCoordinateBounds + - MGLStringFromCoordinateQuad - name: Formatters children: - MGLClockDirectionFormatter diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 67a26e8ed48..abe16cc3eee 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -52,6 +52,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLVectorSource.h" #import "MGLShapeSource.h" #import "MGLRasterSource.h" +#import "MGLImageSource.h" #import "MGLTilePyramidOfflineRegion.h" #import "MGLTypes.h" #import "MGLUserLocation.h" diff --git a/platform/ios/src/UIImage+MGLAdditions.h b/platform/ios/src/UIImage+MGLAdditions.h index 0b4cb4c015c..6e15e07cb55 100644 --- a/platform/ios/src/UIImage+MGLAdditions.h +++ b/platform/ios/src/UIImage+MGLAdditions.h @@ -10,6 +10,8 @@ NS_ASSUME_NONNULL_BEGIN - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier; +- (mbgl::PremultipliedImage)mgl_premultipliedImage; + @end NS_ASSUME_NONNULL_END diff --git a/platform/ios/src/UIImage+MGLAdditions.mm b/platform/ios/src/UIImage+MGLAdditions.mm index b10c48a62aa..5e28d181901 100644 --- a/platform/ios/src/UIImage+MGLAdditions.mm +++ b/platform/ios/src/UIImage+MGLAdditions.mm @@ -25,8 +25,11 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)style - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { BOOL isTemplate = self.renderingMode == UIImageRenderingModeAlwaysTemplate; return std::make_unique([identifier UTF8String], - MGLPremultipliedImageFromCGImage(self.CGImage), + self.mgl_premultipliedImage, float(self.scale), isTemplate); } +-(mbgl::PremultipliedImage)mgl_premultipliedImage { + return MGLPremultipliedImageFromCGImage(self.CGImage); +} @end diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 4301566258e..b4766eb45ab 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog for Mapbox macOS SDK -## 3.6.0 +## master + +### Styles +* Added suuport for diplaying georeferenced images via the `MGLImageSource`. [#9110](https://github.com/mapbox/mapbox-gl-native/pull/9110) ### Packaging diff --git a/platform/macos/app/Assets.xcassets/Radar/Contents.json b/platform/macos/app/Assets.xcassets/Radar/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Radar/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_0.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Radar/southeast_0.imageset/Contents.json new file mode 100644 index 00000000000..ea096b04b82 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Radar/southeast_0.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "southeast_radar_0.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_0.imageset/southeast_radar_0.png b/platform/macos/app/Assets.xcassets/Radar/southeast_0.imageset/southeast_radar_0.png new file mode 100644 index 00000000000..c304b619c4c Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Radar/southeast_0.imageset/southeast_radar_0.png differ diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_1.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Radar/southeast_1.imageset/Contents.json new file mode 100644 index 00000000000..a6a031ae2b3 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Radar/southeast_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "southeast_radar_1.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_1.imageset/southeast_radar_1.png b/platform/macos/app/Assets.xcassets/Radar/southeast_1.imageset/southeast_radar_1.png new file mode 100644 index 00000000000..ed09fffbe18 Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Radar/southeast_1.imageset/southeast_radar_1.png differ diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_2.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Radar/southeast_2.imageset/Contents.json new file mode 100644 index 00000000000..d607dda2984 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Radar/southeast_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "southeast_radar_2.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_2.imageset/southeast_radar_2.png b/platform/macos/app/Assets.xcassets/Radar/southeast_2.imageset/southeast_radar_2.png new file mode 100644 index 00000000000..fee630f8638 Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Radar/southeast_2.imageset/southeast_radar_2.png differ diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_3.imageset/Contents.json b/platform/macos/app/Assets.xcassets/Radar/southeast_3.imageset/Contents.json new file mode 100644 index 00000000000..9a110068a18 --- /dev/null +++ b/platform/macos/app/Assets.xcassets/Radar/southeast_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "southeast_radar_3.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/platform/macos/app/Assets.xcassets/Radar/southeast_3.imageset/southeast_radar_3.png b/platform/macos/app/Assets.xcassets/Radar/southeast_3.imageset/southeast_radar_3.png new file mode 100644 index 00000000000..c4c7146afa6 Binary files /dev/null and b/platform/macos/app/Assets.xcassets/Radar/southeast_3.imageset/southeast_radar_3.png differ diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 20a4f65b3f3..9a8cf05c166 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -554,6 +554,12 @@ + + + + + + diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 59844d363e9..94bf18dea15 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -617,6 +617,37 @@ - (void)updateAnimatedAnnotation:(NSTimer *)timer { cos(angle) * 20); } +- (IBAction) addAnimatedImageSource:(id)sender { + + MGLImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:@"southeast_0"]; + + MGLCoordinateBounds bounds = { {22.551103322318994, -90.24006072802854}, {36.928147474567794, -75.1441643681673} }; + MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"animated-radar-source" coordinateQuad:MGLCoordinateQuadFromCoordinateBounds(bounds) image:image]; + [self.mapView.style addSource:imageSource]; + + MGLRasterStyleLayer * imageLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"animated-radar-layer" source:imageSource]; + [self.mapView.style addLayer:imageLayer]; + + [NSTimer scheduledTimerWithTimeInterval:1.0 + target:self + selector:@selector(updateAnimatedImageSource:) + userInfo:imageSource + repeats:YES]; +} + + +- (void)updateAnimatedImageSource:(NSTimer *)timer { + static int radarSuffix = 0; + MGLImageSource *imageSource = (MGLImageSource *)timer.userInfo; + + MGLImage *image = [[NSBundle bundleForClass:[self class]] imageForResource:[NSString stringWithFormat:@"southeast_%d", radarSuffix++]]; + [imageSource setValue:image forKey:@"image"]; + + if(radarSuffix > 3) { + radarSuffix = 0 ; + } +} + - (IBAction)insertCustomStyleLayer:(id)sender { [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { [self removeCustomStyleLayer:sender]; @@ -695,40 +726,58 @@ - (IBAction)manipulateStyle:(id)sender { MGLTransition transition = { .duration = 5, .delay = 1 }; self.mapView.style.transition = transition; - MGLFillStyleLayer *fillStyleLayer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:@"water"]; - + MGLStyleLayer *waterLayer = [self.mapView.style layerWithIdentifier:@"water"]; MGLStyleValue *colorFunction = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:@{ @0.0: [MGLStyleValue valueWithRawValue:[NSColor redColor]], @10.0: [MGLStyleValue valueWithRawValue:[NSColor yellowColor]], @20.0: [MGLStyleValue valueWithRawValue:[NSColor blackColor]], } options:nil]; - fillStyleLayer.fillColor = colorFunction; + + if ([waterLayer respondsToSelector:@selector(fillColor)]) { + [waterLayer setValue:colorFunction forKey:@"fillColor"]; + } else if ([waterLayer respondsToSelector:@selector(lineColor)]) { + [waterLayer setValue:colorFunction forKey:@"lineColor"]; + } NSString *filePath = [[NSBundle bundleForClass:self.class] pathForResource:@"amsterdam" ofType:@"geojson"]; NSURL *geoJSONURL = [NSURL fileURLWithPath:filePath]; MGLShapeSource *source = [[MGLShapeSource alloc] initWithIdentifier:@"ams" URL:geoJSONURL options:nil]; [self.mapView.style addSource:source]; - MGLFillStyleLayer *fillLayer = [[MGLFillStyleLayer alloc] initWithIdentifier:@"test" source:source]; - fillLayer.fillColor = [MGLStyleValue valueWithRawValue:[NSColor greenColor]]; - fillLayer.predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"type", @"park"]; - [self.mapView.style addLayer:fillLayer]; + MGLCircleStyleLayer *circleLayer = [[MGLCircleStyleLayer alloc] initWithIdentifier:@"test" source:source]; + circleLayer.circleColor = [MGLStyleValue valueWithRawValue:[NSColor greenColor]]; + circleLayer.circleRadius = [MGLStyleValue valueWithRawValue:[NSNumber numberWithInteger:40]]; +// fillLayer.predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"type", @"park"]; + [self.mapView.style addLayer:circleLayer]; + MGLSource *streetsSource = [self.mapView.style sourceWithIdentifier:@"composite"]; + if (streetsSource) { NSImage *image = [NSImage imageNamed:NSImageNameIChatTheaterTemplate]; [self.mapView.style setImage:image forName:NSImageNameIChatTheaterTemplate]; - MGLSource *streetsSource = [self.mapView.style sourceWithIdentifier:@"composite"]; - MGLSymbolStyleLayer *theaterLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"theaters" source:streetsSource]; - theaterLayer.sourceLayerIdentifier = @"poi_label"; - theaterLayer.predicate = [NSPredicate predicateWithFormat:@"maki == 'theatre'"]; - theaterLayer.iconImageName = [MGLStyleValue valueWithRawValue:NSImageNameIChatTheaterTemplate]; - theaterLayer.iconScale = [MGLStyleValue valueWithRawValue:@2]; - theaterLayer.iconColor = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:@{ - @16.0: [MGLStyleValue valueWithRawValue:[NSColor redColor]], - @18.0: [MGLStyleValue valueWithRawValue:[NSColor yellowColor]], - @20.0: [MGLStyleValue valueWithRawValue:[NSColor blackColor]], - } options:nil]; - [self.mapView.style addLayer:theaterLayer]; + MGLSymbolStyleLayer *theaterLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"theaters" source:streetsSource]; + theaterLayer.sourceLayerIdentifier = @"poi_label"; + theaterLayer.predicate = [NSPredicate predicateWithFormat:@"maki == 'theatre'"]; + theaterLayer.iconImageName = [MGLStyleValue valueWithRawValue:NSImageNameIChatTheaterTemplate]; + theaterLayer.iconScale = [MGLStyleValue valueWithRawValue:@2]; + theaterLayer.iconColor = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential cameraStops:@{ + @16.0: [MGLStyleValue valueWithRawValue:[NSColor redColor]], + @18.0: [MGLStyleValue valueWithRawValue:[NSColor yellowColor]], + @20.0: [MGLStyleValue valueWithRawValue:[NSColor blackColor]], + } options:nil]; + [self.mapView.style addLayer:theaterLayer]; + } + + NSURL *imageURL = [NSURL URLWithString:@"https://www.mapbox.com/mapbox-gl-js/assets/radar.gif"]; + MGLCoordinateQuad quad = { {46.437, -80.425}, + {37.936, -80.425}, + {37.936, -71.516}, + {46.437, -71.516} }; + MGLImageSource *imageSource = [[MGLImageSource alloc] initWithIdentifier:@"radar-source" coordinateQuad:quad URL:imageURL]; + [self.mapView.style addSource:imageSource]; + + MGLRasterStyleLayer * imageLayer = [[MGLRasterStyleLayer alloc] initWithIdentifier:@"radar-layer" source:imageSource]; + [self.mapView.style addLayer:imageLayer]; } - (IBAction)dropPin:(NSMenuItem *)sender { @@ -937,6 +986,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if (menuItem.action == @selector(drawAnimatedAnnotation:)) { return !_isShowingAnimatedAnnotation; } + if (menuItem.action == @selector(addAnimatedImageSource:)) { + return YES; + } if (menuItem.action == @selector(insertCustomStyleLayer:)) { return ![self.mapView.style layerWithIdentifier:@"mbx-custom"]; } diff --git a/platform/macos/docs/guides/For Style Authors.md b/platform/macos/docs/guides/For Style Authors.md index 164b1b58bf9..3cacc813760 100644 --- a/platform/macos/docs/guides/For Style Authors.md +++ b/platform/macos/docs/guides/For Style Authors.md @@ -116,8 +116,9 @@ In style JSON | In the SDK `geojson` | `MGLShapeSource` `raster` | `MGLRasterSource` `vector` | `MGLVectorSource` +`image` | `MGLImageSource` -`canvas`, `image`, and `video` sources are not supported. +`canvas` and `video` sources are not supported. ### Tile sources @@ -158,6 +159,12 @@ To create a shape source from local GeoJSON data, first [convert the GeoJSON data into a shape](working-with-geojson-data.html#converting-geojson-data-into-shape-objects), then use the `-[MGLShapeSource initWithIdentifier:shape:options:]` method. +### Image sources + +Image sources accept a non-axis aligned quadrilateral as their geographic coordinates. +These coordinates, in `MGLCoordinateQuad`, are described in counterclockwise order, +in contrast to the clockwise order defined in the style specification. + ## Configuring the map content’s appearance Each layer defined by the style JSON file is represented at runtime by a style diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index 564d81afb2c..723bbc6f4be 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 0721493F1EE200E900085505 /* MGLImageSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07A019EB1ED662D800ACD43E /* MGLImageSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 07A019EF1ED665CD00ACD43E /* MGLImageSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07A019EC1ED662D800ACD43E /* MGLImageSource.mm */; }; + 07BA4CAC1EE21887004528F5 /* MGLImageSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BA4CAB1EE21887004528F5 /* MGLImageSourceTests.m */; }; 1753ED401E53CE6100A9FD90 /* MGLConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */; }; 1F7454A31ECFB00300021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */; }; 1F7454A41ECFB00300021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454A11ECFB00300021D39 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -271,6 +274,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 07A019EB1ED662D800ACD43E /* MGLImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLImageSource.h; sourceTree = ""; }; + 07A019EC1ED662D800ACD43E /* MGLImageSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLImageSource.mm; sourceTree = ""; }; + 07BA4CAB1EE21887004528F5 /* MGLImageSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLImageSourceTests.m; sourceTree = ""; }; 1753ED3F1E53CE5200A9FD90 /* MGLConversion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLConversion.h; sourceTree = ""; }; 1F7454A01ECFB00300021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A11ECFB00300021D39 /* MGLLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight.h; sourceTree = ""; }; @@ -664,6 +670,8 @@ DA8F25951D51CAC70010E6B5 /* MGLVectorSource.h */, DA7DC9801DED5F5C0027472F /* MGLVectorSource_Private.h */, DA8F25961D51CAC70010E6B5 /* MGLVectorSource.mm */, + 07A019EB1ED662D800ACD43E /* MGLImageSource.h */, + 07A019EC1ED662D800ACD43E /* MGLImageSource.mm */, ); name = Sources; sourceTree = ""; @@ -774,6 +782,7 @@ DA87A9961DC9D88400810D09 /* MGLShapeSourceTests.mm */, DA87A9971DC9D88400810D09 /* MGLTileSetTests.mm */, 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */, + 07BA4CAB1EE21887004528F5 /* MGLImageSourceTests.m */, ); name = Sources; sourceTree = ""; @@ -1166,6 +1175,7 @@ DAE6C3B41CC31EF300DB3429 /* MGLCompassCell.h in Headers */, DA87A99C1DC9D8DD00810D09 /* MGLShapeSource_Private.h in Headers */, 3537CA741D3F93A600380318 /* MGLStyle_Private.h in Headers */, + 0721493F1EE200E900085505 /* MGLImageSource.h in Headers */, DA8F259A1D51CAD00010E6B5 /* MGLSource_Private.h in Headers */, DA8F25931D51CA750010E6B5 /* MGLSymbolStyleLayer.h in Headers */, DAE6C3B91CC31EF300DB3429 /* MGLOpenGLLayer.h in Headers */, @@ -1386,6 +1396,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 07A019EF1ED665CD00ACD43E /* MGLImageSource.mm in Sources */, 40ABDB561DB0022100372083 /* NSImage+MGLAdditions.mm in Sources */, DAE6C3901CC31E2A00DB3429 /* MGLPointAnnotation.mm in Sources */, DAE6C3981CC31E2A00DB3429 /* NSBundle+MGLAdditions.m in Sources */, @@ -1478,6 +1489,7 @@ DA87A9A71DCACC5000810D09 /* MGLBackgroundStyleLayerTests.mm in Sources */, DAA999011E9F5EC5002E6EA6 /* MGLFillExtrusionStyleLayerTests.mm in Sources */, DA29875A1E1A4290002299F5 /* MGLDocumentationExampleTests.swift in Sources */, + 07BA4CAC1EE21887004528F5 /* MGLImageSourceTests.m in Sources */, DAE6C3D31CC34C9900DB3429 /* MGLOfflinePackTests.m in Sources */, DA87A9A51DCACC5000810D09 /* MGLLineStyleLayerTests.mm in Sources */, DA87A9A31DCACC5000810D09 /* MGLRasterStyleLayerTests.mm in Sources */, diff --git a/platform/macos/src/MGLMapView.mm b/platform/macos/src/MGLMapView.mm index a8a84522a53..d2cb5e7100f 100644 --- a/platform/macos/src/MGLMapView.mm +++ b/platform/macos/src/MGLMapView.mm @@ -20,6 +20,7 @@ #import "MGLPolyline.h" #import "MGLAnnotationImage.h" #import "MGLMapViewDelegate.h" +#import "MGLImageSource.h" #import #import @@ -943,13 +944,16 @@ - (void)mapViewDidFinishLoadingStyle { } } -- (void)sourceDidChange { +- (void)sourceDidChange:(MGLSource *)source { if (!_mbglMap) { return; } - - [self installAttributionView]; + // Attribution only applies to tiled sources + if ([source isKindOfClass:[MGLTileSource class]]) { + [self installAttributionView]; + } self.needsUpdateConstraints = YES; + self.needsDisplay = YES; } #pragma mark Printing @@ -2847,8 +2851,10 @@ void onDidFinishLoadingStyle() override { [nativeView mapViewDidFinishLoadingStyle]; } - void onSourceChanged(mbgl::style::Source&) override { - [nativeView sourceDidChange]; + void onSourceChanged(mbgl::style::Source& source) override { + NSString *identifier = @(source.getID().c_str()); + MGLSource * nativeSource = [nativeView.style sourceWithIdentifier:identifier]; + [nativeView sourceDidChange:nativeSource]; } mbgl::gl::ProcAddress initializeExtension(const char* name) override { diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index 0f47dace708..e4ad258b6ea 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -50,6 +50,7 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLVectorSource.h" #import "MGLShapeSource.h" #import "MGLRasterSource.h" +#import "MGLImageSource.h" #import "MGLTilePyramidOfflineRegion.h" #import "MGLTypes.h" #import "NSValue+MGLAdditions.h" diff --git a/platform/macos/src/NSImage+MGLAdditions.h b/platform/macos/src/NSImage+MGLAdditions.h index 1bcec00e8bb..c08fc57bea1 100644 --- a/platform/macos/src/NSImage+MGLAdditions.h +++ b/platform/macos/src/NSImage+MGLAdditions.h @@ -12,6 +12,8 @@ NS_ASSUME_NONNULL_BEGIN - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier; +- (mbgl::PremultipliedImage) mgl_premultipliedImage; + @end NS_ASSUME_NONNULL_END diff --git a/platform/macos/src/NSImage+MGLAdditions.mm b/platform/macos/src/NSImage+MGLAdditions.mm index ecd8aabe153..6abe53e9ae4 100644 --- a/platform/macos/src/NSImage+MGLAdditions.mm +++ b/platform/macos/src/NSImage+MGLAdditions.mm @@ -33,6 +33,15 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)style } - (std::unique_ptr)mgl_styleImageWithIdentifier:(NSString *)identifier { + mbgl::PremultipliedImage cPremultipliedImage = self.mgl_premultipliedImage; + auto imageWidth = cPremultipliedImage.size.width; + return std::make_unique([identifier UTF8String], + std::move(cPremultipliedImage), + (float)(imageWidth / self.size.width), + [self isTemplate]); +} + +- (mbgl::PremultipliedImage)mgl_premultipliedImage { // Create a bitmap image representation from the image, respecting backing // scale factor and any resizing done on the image at runtime. // http://www.cocoabuilder.com/archive/cocoa/82430-nsimage-getting-raw-bitmap-data.html#82431 @@ -42,10 +51,6 @@ - (nullable instancetype)initWithMGLStyleImage:(const mbgl::style::Image *)style mbgl::PremultipliedImage cPremultipliedImage({ static_cast(rep.pixelsWide), static_cast(rep.pixelsHigh) }); std::copy(rep.bitmapData, rep.bitmapData + cPremultipliedImage.bytes(), cPremultipliedImage.data.get()); - return std::make_unique([identifier UTF8String], - std::move(cPremultipliedImage), - (float)(rep.pixelsWide / self.size.width), - [self isTemplate]); + return cPremultipliedImage; } - @end diff --git a/platform/node/CHANGELOG.md b/platform/node/CHANGELOG.md index 76e1b5099d3..dee001c4265 100644 --- a/platform/node/CHANGELOG.md +++ b/platform/node/CHANGELOG.md @@ -1,5 +1,5 @@ # 3.5.4 - June 6, 2017 - +- Add support for ImageSource [#8968](https://github.com/mapbox/mapbox-gl-native/pull/8968) - Fixed an issue with `map.addImage()` which would cause added images to randomly be replaced with images found the style's sprite sheet ([#9119](https://github.com/mapbox/mapbox-gl-native/pull/9119)) # 3.5.3 - May 30, 2017 diff --git a/src/mbgl/renderer/buckets/raster_bucket.cpp b/src/mbgl/renderer/buckets/raster_bucket.cpp index 8c9c1658a1c..49ec0065c3f 100644 --- a/src/mbgl/renderer/buckets/raster_bucket.cpp +++ b/src/mbgl/renderer/buckets/raster_bucket.cpp @@ -13,7 +13,9 @@ RasterBucket::RasterBucket(UnassociatedImage&& image_) : image(std::move(image_) } void RasterBucket::upload(gl::Context& context) { - texture = context.createTexture(image); + if (!texture) { + texture = context.createTexture(image); + } if (!vertices.empty()) { vertexBuffer = context.createVertexBuffer(std::move(vertices)); indexBuffer = context.createIndexBuffer(std::move(indices)); @@ -27,7 +29,7 @@ void RasterBucket::clear() { segments.clear(); vertices.clear(); indices.clear(); - + uploaded = false; } void RasterBucket::render(Painter& painter, diff --git a/src/mbgl/renderer/sources/render_image_source.cpp b/src/mbgl/renderer/sources/render_image_source.cpp index 7fb7caa580a..f5068b9d7fd 100644 --- a/src/mbgl/renderer/sources/render_image_source.cpp +++ b/src/mbgl/renderer/sources/render_image_source.cpp @@ -135,12 +135,13 @@ void RenderImageSource::update(Immutable baseImpl_, geomCoords.push_back(gc); } - if (!bucket) { - UnassociatedImage img = impl().getImage().clone(); - if (!img.valid()) { - return; - } - bucket = std::make_unique(std::move(img)); + const UnassociatedImage& image = impl().getImage(); + if (!image.valid()) { + return; + } + + if (!bucket || image != bucket->image) { + bucket = std::make_unique(image.clone()); } else { bucket->clear(); } diff --git a/src/mbgl/style/sources/geojson_source.cpp b/src/mbgl/style/sources/geojson_source.cpp index d04c8ffce47..4e3478322de 100644 --- a/src/mbgl/style/sources/geojson_source.cpp +++ b/src/mbgl/style/sources/geojson_source.cpp @@ -33,6 +33,7 @@ void GeoJSONSource::setURL(const std::string& url_) { void GeoJSONSource::setGeoJSON(const mapbox::geojson::geojson& geoJSON) { req.reset(); baseImpl = makeMutable(impl(), geoJSON); + observer->onSourceChanged(*this); } optional GeoJSONSource::getURL() const { diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 94e003df516..d667ef656fa 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -258,6 +258,7 @@ void Style::onSourceLoaded(Source& source) { void Style::onSourceChanged(Source& source) { sources.update(source); observer->onSourceChanged(source); + observer->onUpdate(Update::Repaint); } void Style::onSourceError(Source& source, std::exception_ptr error) {