diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f88252754f..24ff7a989a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,6 @@ set_source_files_properties(src/mbgl/util/version.cpp PROPERTIES COMPILE_DEFINIT mason_use(geometry VERSION 0.9.2 HEADER_ONLY) mason_use(variant VERSION 1.1.4 HEADER_ONLY) -mason_use(any VERSION 8fef1e9 HEADER_ONLY) mason_use(unique_resource VERSION cba309e HEADER_ONLY) mason_use(rapidjson VERSION 1.1.0 HEADER_ONLY) mason_use(boost VERSION 1.62.0 HEADER_ONLY) diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index 2af07f48c31..d6c9493742a 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -242,6 +242,8 @@ set(MBGL_CORE_FILES src/mbgl/renderer/layers/render_symbol_layer.hpp # renderer/sources + src/mbgl/renderer/sources/render_custom_geometry_source.cpp + src/mbgl/renderer/sources/render_custom_geometry_source.hpp src/mbgl/renderer/sources/render_geojson_source.cpp src/mbgl/renderer/sources/render_geojson_source.hpp src/mbgl/renderer/sources/render_image_source.cpp @@ -334,6 +336,8 @@ set(MBGL_CORE_FILES include/mbgl/style/types.hpp include/mbgl/style/undefined.hpp src/mbgl/style/collection.hpp + src/mbgl/style/custom_tile_loader.cpp + src/mbgl/style/custom_tile_loader.hpp src/mbgl/style/image.cpp src/mbgl/style/image_impl.cpp src/mbgl/style/image_impl.hpp @@ -364,6 +368,7 @@ set(MBGL_CORE_FILES # style/conversion include/mbgl/style/conversion/constant.hpp include/mbgl/style/conversion/coordinate.hpp + include/mbgl/style/conversion/custom_geometry_source_options.hpp include/mbgl/style/conversion/data_driven_property_value.hpp include/mbgl/style/conversion/expression.hpp include/mbgl/style/conversion/filter.hpp @@ -506,10 +511,14 @@ set(MBGL_CORE_FILES src/mbgl/style/layers/symbol_layer_properties.hpp # style/sources + include/mbgl/style/sources/custom_geometry_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 + src/mbgl/style/sources/custom_geometry_source.cpp + src/mbgl/style/sources/custom_geometry_source_impl.cpp + src/mbgl/style/sources/custom_geometry_source_impl.hpp src/mbgl/style/sources/geojson_source.cpp src/mbgl/style/sources/geojson_source_impl.cpp src/mbgl/style/sources/geojson_source_impl.hpp @@ -555,6 +564,8 @@ set(MBGL_CORE_FILES # tile include/mbgl/tile/tile_id.hpp include/mbgl/tile/tile_necessity.hpp + src/mbgl/tile/custom_geometry_tile.cpp + src/mbgl/tile/custom_geometry_tile.hpp src/mbgl/tile/geojson_tile.cpp src/mbgl/tile/geojson_tile.hpp src/mbgl/tile/geojson_tile_data.hpp @@ -583,7 +594,6 @@ set(MBGL_CORE_FILES src/mbgl/tile/vector_tile_data.hpp # util - include/mbgl/util/any.hpp include/mbgl/util/async_request.hpp include/mbgl/util/async_task.hpp include/mbgl/util/char_array_buffer.hpp @@ -620,6 +630,7 @@ set(MBGL_CORE_FILES include/mbgl/util/timer.hpp include/mbgl/util/traits.hpp include/mbgl/util/type_list.hpp + include/mbgl/util/unique_any.hpp include/mbgl/util/unitbezier.hpp include/mbgl/util/util.hpp include/mbgl/util/variant.hpp diff --git a/cmake/core.cmake b/cmake/core.cmake index c4e711f5589..e1001787fad 100644 --- a/cmake/core.cmake +++ b/cmake/core.cmake @@ -14,7 +14,6 @@ target_include_directories(mbgl-core target_add_mason_package(mbgl-core PUBLIC geometry) target_add_mason_package(mbgl-core PUBLIC variant) -target_add_mason_package(mbgl-core PUBLIC any) target_add_mason_package(mbgl-core PRIVATE unique_resource) target_add_mason_package(mbgl-core PRIVATE rapidjson) target_add_mason_package(mbgl-core PRIVATE boost) diff --git a/cmake/filesource.cmake b/cmake/filesource.cmake index bb1b4e8c056..6251224d44f 100644 --- a/cmake/filesource.cmake +++ b/cmake/filesource.cmake @@ -26,7 +26,6 @@ add_library(mbgl-filesource STATIC target_add_mason_package(mbgl-filesource PUBLIC geometry) target_add_mason_package(mbgl-filesource PUBLIC variant) -target_add_mason_package(mbgl-filesource PUBLIC any) target_add_mason_package(mbgl-filesource PRIVATE rapidjson) target_add_mason_package(mbgl-filesource PRIVATE boost) target_add_mason_package(mbgl-filesource PRIVATE geojson) diff --git a/cmake/test-files.cmake b/cmake/test-files.cmake index b2ddc2b36dc..273852602fc 100644 --- a/cmake/test-files.cmake +++ b/cmake/test-files.cmake @@ -15,6 +15,7 @@ set(MBGL_TEST_FILES # api test/api/annotations.test.cpp test/api/api_misuse.test.cpp + test/api/custom_geometry_source.test.cpp test/api/custom_layer.test.cpp test/api/query.test.cpp test/api/recycle_map.cpp @@ -117,6 +118,7 @@ set(MBGL_TEST_FILES test/text/quads.test.cpp # tile + test/tile/custom_geometry_tile.test.cpp test/tile/geojson_tile.test.cpp test/tile/geometry_tile_data.test.cpp test/tile/raster_tile.test.cpp @@ -145,5 +147,6 @@ set(MBGL_TEST_FILES test/util/tile_cover.test.cpp test/util/timer.test.cpp test/util/token.test.cpp + test/util/unique_any.test.cpp test/util/url.test.cpp ) diff --git a/cmake/test.cmake b/cmake/test.cmake index c821d533160..ab498879e5e 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -27,7 +27,6 @@ target_link_libraries(mbgl-test target_add_mason_package(mbgl-test PRIVATE geometry) target_add_mason_package(mbgl-test PRIVATE variant) -target_add_mason_package(mbgl-test PRIVATE any) target_add_mason_package(mbgl-test PRIVATE unique_resource) target_add_mason_package(mbgl-test PRIVATE rapidjson) target_add_mason_package(mbgl-test PRIVATE gtest) diff --git a/include/mbgl/style/conversion/custom_geometry_source_options.hpp b/include/mbgl/style/conversion/custom_geometry_source_options.hpp new file mode 100644 index 00000000000..73b141e7992 --- /dev/null +++ b/include/mbgl/style/conversion/custom_geometry_source_options.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +namespace mbgl { +namespace style { +namespace conversion { + +template <> +struct Converter { + + template + optional operator()(const V& value, Error& error) const { + CustomGeometrySource::Options options; + + const auto minzoomValue = objectMember(value, "minzoom"); + if (minzoomValue) { + if (toNumber(*minzoomValue)) { + options.zoomRange.min = static_cast(*toNumber(*minzoomValue)); + } else { + error = { "GeoJSON source minzoom value must be a number" }; + return {}; + } + } + + const auto maxzoomValue = objectMember(value, "maxzoom"); + if (maxzoomValue) { + if (toNumber(*maxzoomValue)) { + options.zoomRange.max = static_cast(*toNumber(*maxzoomValue)); + } else { + error = { "GeoJSON source maxzoom value must be a number" }; + return {}; + } + } + + const auto bufferValue = objectMember(value, "buffer"); + if (bufferValue) { + if (toNumber(*bufferValue)) { + options.tileOptions.buffer = static_cast(*toNumber(*bufferValue)); + } else { + error = { "GeoJSON source buffer value must be a number" }; + return {}; + } + } + + const auto toleranceValue = objectMember(value, "tolerance"); + if (toleranceValue) { + if (toNumber(*toleranceValue)) { + options.tileOptions.tolerance = static_cast(*toNumber(*toleranceValue)); + } else { + error = { "GeoJSON source tolerance value must be a number" }; + return {}; + } + } + + return { options }; + } + +}; + +} // namespace conversion +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/layer.hpp b/include/mbgl/style/layer.hpp index c6a3c0e7355..eb2dbf830bd 100644 --- a/include/mbgl/style/layer.hpp +++ b/include/mbgl/style/layer.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include #include #include @@ -126,7 +126,7 @@ class Layer : public mbgl::util::noncopyable { // For use in SDK bindings, which store a reference to a platform-native peer // object here, so that separately-obtained references to this object share // identical platform-native peers. - any peer; + util::unique_any peer; }; } // namespace style diff --git a/include/mbgl/style/source.hpp b/include/mbgl/style/source.hpp index cec9619451f..0b6a6c72d92 100644 --- a/include/mbgl/style/source.hpp +++ b/include/mbgl/style/source.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -76,7 +76,7 @@ class Source : public mbgl::util::noncopyable { // For use in SDK bindings, which store a reference to a platform-native peer // object here, so that separately-obtained references to this object share // identical platform-native peers. - any peer; + util::unique_any peer; }; } // namespace style diff --git a/include/mbgl/style/sources/custom_geometry_source.hpp b/include/mbgl/style/sources/custom_geometry_source.hpp new file mode 100644 index 00000000000..a0b990b44b5 --- /dev/null +++ b/include/mbgl/style/sources/custom_geometry_source.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace mbgl { + +class OverscaledTileID; +class CanonicalTileID; +template +class Actor; + +namespace style { + +using TileFunction = std::function; + +class CustomTileLoader; + +class CustomGeometrySource : public Source { +public: + struct TileOptions { + double tolerance = 0.375; + uint16_t tileSize = util::tileSize; + uint16_t buffer = 128; + }; + + struct Options { + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + Range zoomRange = { 0, 18}; + TileOptions tileOptions; + }; +public: + CustomGeometrySource(std::string id, CustomGeometrySource::Options options); + ~CustomGeometrySource() final; + void loadDescription(FileSource&) final; + void setTileData(const CanonicalTileID&, const GeoJSON&); + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&); + // Private implementation + class Impl; + const Impl& impl() const; +private: + std::unique_ptr> loader; +}; + +template <> +inline bool Source::is() const { + return getType() == SourceType::CustomVector; +} + +} // namespace style +} // namespace mbgl diff --git a/include/mbgl/style/sources/geojson_source.hpp b/include/mbgl/style/sources/geojson_source.hpp index 5bdf1ef9570..372e7c7a780 100644 --- a/include/mbgl/style/sources/geojson_source.hpp +++ b/include/mbgl/style/sources/geojson_source.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace mbgl { @@ -14,6 +15,7 @@ struct GeoJSONOptions { // GeoJSON-VT options uint8_t minzoom = 0; uint8_t maxzoom = 18; + uint16_t tileSize = util::tileSize; uint16_t buffer = 128; double tolerance = 0.375; diff --git a/include/mbgl/style/types.hpp b/include/mbgl/style/types.hpp index 2ed95f08b8f..6fe457e181e 100644 --- a/include/mbgl/style/types.hpp +++ b/include/mbgl/style/types.hpp @@ -13,7 +13,8 @@ enum class SourceType : uint8_t { GeoJSON, Video, Annotations, - Image + Image, + CustomVector }; enum class VisibilityType : bool { diff --git a/include/mbgl/util/any.hpp b/include/mbgl/util/any.hpp deleted file mode 100644 index eea64b188af..00000000000 --- a/include/mbgl/util/any.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include - -namespace mbgl { - -using linb::any; -using linb::any_cast; - -} // namespace mbgl diff --git a/include/mbgl/util/unique_any.hpp b/include/mbgl/util/unique_any.hpp new file mode 100644 index 00000000000..d488930a035 --- /dev/null +++ b/include/mbgl/util/unique_any.hpp @@ -0,0 +1,275 @@ +#pragma once + +#include +#include +#include +namespace mbgl { +namespace util { + +class bad_any_cast : public std::bad_cast { +public: + const char* what() const noexcept override { + return "bad any_cast<>()"; + } +}; +/** + * A variant of `std::any` for non-copyable types. + * + * Use `unique_any` for non-copyable types (e.g. `std::unique_ptr`) + * or to ensure that no copies are made of copyable types that are + * moved in. + * + * `uniqe_any` differs from `std::any` in that it does not support copy construction + * or copy assignment. It also does not require the contained type to be copy + * constructible. + * + * The `any_cast()` methods work similar to `std::any_cast()` except that + * non-copyable types may only be cast to references. + * + * Example usage: + * unique_any u1(3); + * auto u2 = unique_any(std::move(u1)); // u1 is moved from + * int i = any_cast(u2); + * + * unique_any u2; + * u2 = std::unique_ptr(new int); + * std::unique_ptr iPtr = any_cast>(std::move(u2)); + * + * Inspired by linb::any (https://github.com/thelink2012/any) and the + * libc++ implementation (https://github.com/llvm-mirror/libcxx). + */ +class unique_any final +{ +public: + unique_any() = default; + + //Copy constructor (deleted) + unique_any(const unique_any& rhs) = delete; + + unique_any(unique_any&& rhs) : vtable(rhs.vtable) { + if (vtable) { + vtable->move(std::move(rhs.storage), storage); + } + rhs.vtable = nullptr; + } + + // Constructs with a direct-initilizated object of type ValueType + template , + typename = std::enable_if_t::value> > + unique_any(ValueType&& value) { + create(std::forward(value)); + } + + ~unique_any() { + reset(); + } + + unique_any& operator=(unique_any&& rhs) { + unique_any(std::move(rhs)).swap(*this); + return *this; + } + + template , unique_any>::value> > + unique_any& operator=(ValueType&& rhs) { + unique_any(std::forward(rhs)).swap(*this); + return *this; + } + + void reset() { + if (vtable) { + vtable->destroy(storage); + vtable = nullptr; + } + } + + void swap(unique_any& rhs) { + if (this == &rhs) { + return; + } else { + unique_any tmp(std::move(rhs)); + rhs.vtable = vtable; + if (rhs.vtable) { + rhs.vtable->move(std::move(storage), rhs.storage); + } + vtable = tmp.vtable; + if (vtable) { + vtable->move(std::move(tmp.storage), storage); + } + } + } + + const std::type_info& type() const { + return !has_value()? typeid(void) : vtable->type(); + } + + bool has_value() const { + return vtable != nullptr; + } + +private: + + union Storage { + using StackStorage = std::aligned_storage_t<3*sizeof(void*), std::alignment_of::value>; + Storage() = default; + + void * dynamic { nullptr }; + StackStorage stack; + }; + + template + struct AllocateOnStack : std::integral_constant::value <= std::alignment_of::value + && std::is_nothrow_move_constructible::value> { + }; + + struct VTable { + virtual ~VTable() = default; + virtual void move(Storage&& src, Storage& dest) = 0; + virtual void destroy(Storage&) = 0; + virtual const std::type_info& type() = 0; + }; + + template + struct VTableHeap : public VTable { + void move(Storage&& src, Storage& dest) override { + destroy(dest); + dest.dynamic = src.dynamic; + } + + void destroy(Storage& s) override { + if (s.dynamic) { + delete reinterpret_cast(s.dynamic); + } + s.dynamic = nullptr; + } + + const std::type_info& type() override { + return typeid(ValueType); + } + }; + + template + struct VTableStack : public VTable { + void move(Storage&& src, Storage& dest) override { + auto srcValue = reinterpret_cast(src.stack); + new (static_cast(&dest.stack)) ValueType(std::move(srcValue)); + srcValue.~ValueType(); + } + + void destroy(Storage& s) override { + reinterpret_cast(s.stack).~ValueType(); + } + + const std::type_info& type() override { + return typeid(ValueType); + } + }; + + template + static VTable* vtableForType() { + using VTableType = std::conditional_t::value, VTableStack, VTableHeap >; + static VTableType vtable; + return &vtable; + } + + template + std::enable_if_t::value> + createStorage(ValueType&& value) { + new (static_cast(&storage.stack)) _Vt(std::forward(value)); + } + + template + std::enable_if_t::value> + createStorage(ValueType&& value) { + storage.dynamic = static_cast(new _Vt(std::forward(value))); + } + + template + void create(ValueType&& value) { + using _Vt = std::decay_t; + vtable = vtableForType<_Vt>(); + createStorage(std::forward(value)); + } + + VTable* vtable { nullptr }; + Storage storage; + +protected: + template + friend const ValueType* any_cast(const unique_any* operand) ; + + template + friend ValueType* any_cast(unique_any* operand) ; + + template > + ValueType* cast() + { + return reinterpret_cast( + AllocateOnStack<_Vt>::value ? &storage.stack : storage.dynamic); + } +}; + +template +inline const ValueType* any_cast(const unique_any* any) +{ + return any_cast(const_cast(any)); +} + +template +inline ValueType* any_cast(unique_any* any) +{ + if(any == nullptr || any->type() != typeid(ValueType)) + return nullptr; + else + return any->cast(); +} + +template > +inline ValueType any_cast(const unique_any& any) +{ + static_assert(std::is_constructible::value, + "any_cast type can't construct copy of contained object"); + auto temp = any_cast<_Vt>(&any); + if (temp == nullptr) { + throw bad_any_cast(); + } + return static_cast(*temp); +} + +template > +inline ValueType any_cast(unique_any& any) +{ + static_assert(std::is_constructible::value, + "any_cast type can't construct copy of contained object"); + auto temp = any_cast<_Vt>(&any); + if (temp == nullptr) { + throw bad_any_cast(); + } + return static_cast(*temp); +} + +template > +inline ValueType any_cast(unique_any&& any) +{ + auto temp = any_cast<_Vt>(&any); + if (temp == nullptr) { + throw bad_any_cast(); + } + auto retValue = static_cast(std::move(*temp)); + any.reset(); + return std::move(retValue); +} + +} // namespace util +} // namespace mbgl + +namespace std { + +inline void swap(mbgl::util::unique_any& lhs, mbgl::util::unique_any& rhs) { + lhs.swap(rhs); +} + +} // namespace std diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java index 4fcb91033c4..4e22814a2c6 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/geometry/LatLngBounds.java @@ -231,6 +231,25 @@ public static LatLngBounds from(double latNorth, double lonEast, double latSouth return new LatLngBounds(latNorth, lonEast, latSouth, lonWest); } + private static double lat_(int z, int y) { + double n = Math.PI - 2.0 * Math.PI * y / Math.pow(2.0, z); + return Math.toDegrees(Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)))); + } + + private static double lon_(int z, int x) { + return x / Math.pow(2.0, z) * 360.0 - GeoConstants.MAX_LONGITUDE; + } + + /** + * Constructs a LatLngBounds from a Tile identifier. + * @param z Tile zoom level. + * @param x Tile X coordinate. + * @param y Tile Y coordinate. + */ + public static LatLngBounds from(int z, int x, int y) { + return new LatLngBounds(lat_(z, y), lon_(z, x + 1), lat_(z, y + 1), lon_(z, x)); + } + /** * Constructs a LatLngBounds from current bounds with an additional latitude-longitude pair. * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index e8eb7e87186..f1635c898fc 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -733,7 +733,7 @@ public void addSource(@NonNull Source source) { if (isDestroyedOn("addSource")) { return; } - nativeAddSource(source.getNativePtr()); + nativeAddSource(source, source.getNativePtr()); } @Nullable @@ -741,14 +741,15 @@ public Source removeSource(@NonNull String sourceId) { if (isDestroyedOn("removeSource")) { return null; } - return nativeRemoveSourceById(sourceId); + Source source = getSource(sourceId); + return removeSource(source); } public Source removeSource(@NonNull Source source) { if (isDestroyedOn("removeSource")) { return null; } - nativeRemoveSource(source.getNativePtr()); + nativeRemoveSource(source, source.getNativePtr()); return source; } @@ -1027,11 +1028,9 @@ private native void nativeFlyTo(double angle, double latitude, double longitude, private native Source nativeGetSource(String sourceId); - private native void nativeAddSource(long nativeSourcePtr) throws CannotAddSourceException; + private native void nativeAddSource(Source source, long sourcePtr) throws CannotAddSourceException; - private native Source nativeRemoveSourceById(String sourceId); - - private native void nativeRemoveSource(long sourcePtr); + private native void nativeRemoveSource(Source source, long sourcePtr); private native void nativeAddImage(String name, int width, int height, float pixelRatio, byte[] array); diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java new file mode 100644 index 00000000000..1ff1eeacc38 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/CustomGeometrySource.java @@ -0,0 +1,203 @@ +package com.mapbox.mapboxsdk.style.sources; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.support.annotation.WorkerThread; + +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.style.layers.Filter; +import com.mapbox.services.commons.geojson.Feature; +import com.mapbox.services.commons.geojson.FeatureCollection; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Custom Vector Source, allows using FeatureCollections. + * + */ +@UiThread +public class CustomGeometrySource extends Source { + private ExecutorService executor; + private GeometryTileProvider provider; + private final Map cancelledTileRequests = new ConcurrentHashMap<>(); + + /** + * Create a CustomGeometrySource + * + * @param id The source id. + * @param provider The tile provider that returns geometry data for this source. + */ + public CustomGeometrySource(String id, GeometryTileProvider provider) { + this(id, provider, new GeoJsonOptions()); + } + + /** + * Create a CustomGeometrySource with non-default GeoJsonOptions. + *

Supported options are minZoom, maxZoom, buffer, and tolerance.

+ * + * @param id The source id. + * @param provider The tile provider that returns geometry data for this source. + * @param options GeoJsonOptions. + */ + public CustomGeometrySource(String id, GeometryTileProvider provider, GeoJsonOptions options) { + this.provider = provider; + executor = Executors.newFixedThreadPool(4); + initialize(id, options); + } + + /** + * Invalidate previously provided features within a given bounds at all zoom levels. + * Invoking this method will result in new requests to `GeometryTileProvider` for regions + * that contain, include, or intersect with the provided bounds. + * + * @param bounds The region in which features should be invalidated at all zoom levels + */ + public void invalidateRegion(LatLngBounds bounds) { + nativeInvalidateBounds(bounds); + } + + /** + * Invalidate the geometry contents of a specific tile. Invoking this method will result + * in new requests to `GeometryTileProvider` for visible tiles. + * + * @param zoomLevel Tile zoom level. + * @param x Tile X coordinate. + * @param y Tile Y coordinate. + */ + public void invalidateTile(int zoomLevel, int x, int y) { + nativeInvalidateTile(zoomLevel, x, y); + } + + /** + * Set or update geometry contents of a specific tile. Use this method to update tiles + * for which `GeometryTileProvider` was previously invoked. This method can be called from + * background threads. + * + * @param zoomLevel Tile zoom level. + * @param x Tile X coordinate. + * @param y Tile Y coordinate. + * @param data Feature collection for the tile. + */ + public void setTileData(int zoomLevel, int x, int y, FeatureCollection data) { + nativeSetTileData(zoomLevel, x, y, data); + } + + /** + * Queries the source for features. + * + * @param filter an optional filter statement to filter the returned Features + * @return the features + */ + @NonNull + public List querySourceFeatures(@Nullable Filter.Statement filter) { + Feature[] features = querySourceFeatures(filter != null ? filter.toArray() : null); + return features != null ? Arrays.asList(features) : new ArrayList(); + } + + protected native void initialize(String sourceId, Object options); + + private native Feature[] querySourceFeatures(Object[] filter); + + private native void nativeSetTileData(int z, int x, int y, FeatureCollection data); + + private native void nativeInvalidateTile(int z, int x, int y); + + private native void nativeInvalidateBounds(LatLngBounds bounds); + + @Override + protected native void finalize() throws Throwable; + + private void setTileData(TileID tileId, FeatureCollection data) { + cancelledTileRequests.remove(tileId); + nativeSetTileData(tileId.z, tileId.x, tileId.y, data); + } + + @WorkerThread + private void fetchTile(int z, int x, int y) { + AtomicBoolean cancelFlag = new AtomicBoolean(false); + TileID tileID = new TileID(z, x, y); + cancelledTileRequests.put(tileID, cancelFlag); + GeometryTileRequest request = new GeometryTileRequest(tileID, provider, this, cancelFlag); + executor.execute(request); + } + + @WorkerThread + private void cancelTile(int z, int x, int y) { + AtomicBoolean cancelFlag = cancelledTileRequests.get(new TileID(z, x, y)); + if (cancelFlag != null) { + cancelFlag.compareAndSet(false, true); + } + } + + private static class TileID { + public int z; + public int x; + public int y; + + public TileID(int _z, int _x, int _y) { + z = _z; + x = _x; + y = _y; + } + + public int hashCode() { + return Arrays.hashCode(new int[]{z, x, y}); + } + + public boolean equals(Object object) { + if (object == this) { + return true; + } + + if (object == null || getClass() != object.getClass()) { + return false; + } + + if (object instanceof TileID) { + TileID other = (TileID)object; + return this.z == other.z && this.x == other.x && this.y == other.y; + } + return false; + } + } + + private static class GeometryTileRequest implements Runnable { + private TileID id; + private GeometryTileProvider provider; + private WeakReference sourceRef; + private AtomicBoolean cancelled; + + public GeometryTileRequest(TileID _id, GeometryTileProvider p, + CustomGeometrySource _source, AtomicBoolean _cancelled) { + id = _id; + provider = p; + sourceRef = new WeakReference<>(_source); + cancelled = _cancelled; + } + + public void run() { + if (isCancelled()) { + return; + } + + FeatureCollection data = provider.getFeaturesForBounds(LatLngBounds.from(id.z, id.x, id.y), id.z); + CustomGeometrySource source = sourceRef.get(); + if (!isCancelled() && source != null && !data.getFeatures().isEmpty()) { + source.setTileData(id, data); + } + } + + private Boolean isCancelled() { + return cancelled.get(); + } + } +} diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java index 1a1711e547b..81f7255b865 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeoJsonOptions.java @@ -10,6 +10,17 @@ */ public class GeoJsonOptions extends HashMap { + /** + * Maximum zoom level at which to create vector tiles (higher means greater detail at high zoom levels). + * + * @param maxZoom the maximum zoom - Defaults to 18. + * @return the current instance for chaining + */ + public GeoJsonOptions withMinZoom(int minZoom) { + this.put("minzoom", minZoom); + return this; + } + /** * Maximum zoom level at which to create vector tiles (higher means greater detail at high zoom levels). * diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java new file mode 100644 index 00000000000..3f1eb315d33 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/style/sources/GeometryTileProvider.java @@ -0,0 +1,22 @@ +package com.mapbox.mapboxsdk.style.sources; + +import android.support.annotation.WorkerThread; + +import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.services.commons.geojson.FeatureCollection; + +/** + * Interface that defines methods for working with {@link CustomGeometrySource}. + */ +public interface GeometryTileProvider { + + /*** + * Interface method called by {@link CustomGeometrySource} to request features for a tile. + * + * @param bounds {@link LatLngBounds} of the tile. + * @param zoomLevel Tile zoom level. + * @return Return a @{link FeatureCollection} to be displayed in the requested tile. + */ + @WorkerThread + FeatureCollection getFeaturesForBounds(LatLngBounds bounds, int zoomLevel); +} diff --git a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java index bb96c9939d5..4eb8e237fd9 100644 --- a/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java +++ b/platform/android/MapboxGLAndroidSDK/src/test/java/com/mapbox/mapboxsdk/geometry/LatLngBoundsTest.java @@ -4,6 +4,7 @@ import com.mapbox.mapboxsdk.exceptions.InvalidLatLngBoundsException; import com.mapbox.mapboxsdk.utils.MockParcel; +import com.mapbox.services.android.telemetry.constants.GeoConstants; import org.junit.Before; import org.junit.Test; @@ -272,4 +273,22 @@ public void testParcelable() { Parcelable parcel = MockParcel.obtain(latLngBounds); assertEquals("Parcel should match original object", parcel, latLngBounds); } + + @Test + public void fromTileID() { + //GeoConstants.MAX_LATITUDE is not defined to a high enough precision + double MAX_LATITUDE = 85.05112877980659; + LatLngBounds bounds = LatLngBounds.from(0, 0, 0); + assertEquals(-GeoConstants.MAX_LONGITUDE, bounds.getLonWest(), DELTA); + assertEquals(-MAX_LATITUDE, bounds.getLatSouth(), DELTA); + assertEquals(GeoConstants.MAX_LONGITUDE, bounds.getLonEast(), DELTA); + assertEquals(MAX_LATITUDE, bounds.getLatNorth(), DELTA); + + bounds = LatLngBounds.from(10, 288, 385); + assertEquals(-78.75, bounds.getLonWest(), DELTA); + assertEquals(40.446947059600497, bounds.getLatSouth(), DELTA); + assertEquals(-78.3984375, bounds.getLonEast(), DELTA); + assertEquals(40.713955826286039, bounds.getLatNorth(), DELTA); + + } } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index ee26f39f575..2ced75fc75b 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -595,7 +595,17 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activity.FeatureOverviewActivity"/> - + + + + features = new ArrayList<>(); + double gridSpacing; + if (zoom >= 13) { + gridSpacing = 0.01; + } else if (zoom >= 11) { + gridSpacing = 0.05; + } else if (zoom == 10) { + gridSpacing = .1; + } else if (zoom == 9) { + gridSpacing = 0.25; + } else if (zoom == 8) { + gridSpacing = 0.5; + } else if (zoom >= 6) { + gridSpacing = 1; + } else if (zoom == 5) { + gridSpacing = 2; + } else if (zoom >= 4) { + gridSpacing = 5; + } else if (zoom == 2) { + gridSpacing = 10; + } else { + gridSpacing = 20; + } + + List gridLines = new ArrayList(); + for (double y = Math.ceil(bounds.getLatNorth() / gridSpacing) * gridSpacing; + y >= Math.floor(bounds.getLatSouth() / gridSpacing) * gridSpacing; y -= gridSpacing) { + gridLines.add(Arrays.asList(Position.fromCoordinates(bounds.getLonWest(), y), + Position.fromCoordinates(bounds.getLonEast(), y))); + } + features.add(Feature.fromGeometry(MultiLineString.fromCoordinates(gridLines))); + + gridLines = new ArrayList(); + for (double x = Math.floor(bounds.getLonWest() / gridSpacing) * gridSpacing; + x <= Math.ceil(bounds.getLonEast() / gridSpacing) * gridSpacing; x += gridSpacing) { + gridLines.add(Arrays.asList(Position.fromCoordinates(x, bounds.getLatSouth()), + Position.fromCoordinates(x, bounds.getLatNorth()))); + } + features.add(Feature.fromGeometry(MultiLineString.fromCoordinates(gridLines))); + + return FeatureCollection.fromFeatures(features); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_grid_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 + CustomGeometrySource source = new CustomGeometrySource(ID_GRID_SOURCE, new GridProvider()); + mapboxMap.addSource(source); + + // add layer + LineLayer layer = new LineLayer(ID_GRID_LAYER, ID_GRID_SOURCE); + layer.setProperties( + lineColor(Color.parseColor("#000000")) + ); + + mapboxMap.addLayer(layer); + } + + @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(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_grid_source.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_grid_source.xml new file mode 100644 index 00000000000..26b40b9ab62 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_grid_source.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml index 33d96387127..d0aab04d937 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/descriptions.xml @@ -43,7 +43,7 @@ Query rendered feature properties on click Count all rendered features in box Count all rendered symbols in box - Hightligh buildings in box + Highlight buildings in box Query source for features Shows a simple map Logs map change events to Logcat @@ -67,4 +67,5 @@ Use TextureView to render the map Resize a map rendered on a TextureView Animate a map rendered on a TextureView + Example Custom Geometry Source \ No newline at end of file diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 15a916fac90..b2bae9279ff 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -1,4 +1,5 @@ Mapbox Android SDK TestApp + Grid Source diff --git a/platform/android/config.cmake b/platform/android/config.cmake index b3888a9418b..0175fa7ca6b 100644 --- a/platform/android/config.cmake +++ b/platform/android/config.cmake @@ -1,5 +1,4 @@ add_definitions(-DMBGL_USE_GLES2=1) - include(cmake/test-files.cmake) # Build thin archives. @@ -178,10 +177,10 @@ add_library(mbgl-android STATIC platform/android/src/style/layers/unknown_layer.hpp platform/android/src/style/sources/geojson_source.cpp platform/android/src/style/sources/geojson_source.hpp + platform/android/src/style/sources/custom_geometry_source.cpp + platform/android/src/style/sources/custom_geometry_source.hpp platform/android/src/style/sources/source.cpp platform/android/src/style/sources/source.hpp - platform/android/src/style/sources/sources.cpp - platform/android/src/style/sources/sources.hpp platform/android/src/style/sources/raster_source.cpp platform/android/src/style/sources/raster_source.hpp platform/android/src/style/sources/unknown_source.cpp diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index f4e57348618..63566e17722 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -47,7 +47,7 @@ #include "style/functions/interval_stops.hpp" #include "style/functions/stop.hpp" #include "style/layers/layers.hpp" -#include "style/sources/sources.hpp" +#include "style/sources/source.hpp" #include "style/light.hpp" #include "snapshotter/map_snapshotter.hpp" #include "snapshotter/map_snapshot.hpp" @@ -161,7 +161,7 @@ void registerNatives(JavaVM *vm) { // Style TransitionOptions::registerNative(env); registerNativeLayers(env); - registerNativeSources(env); + Source::registerNative(env); Light::registerNative(env); Position::registerNative(env); Stop::registerNative(env); diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 04cbb21927c..36a73fee358 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -856,9 +856,7 @@ jni::Array> NativeMapView::getSources(JNIEnv& env) { jni::Array> jSources = jni::Array>::New(env, sources.size(), Source::javaClass); int index = 0; for (auto source : sources) { - auto jSource = jni::Object(createJavaSourcePeer(env, *rendererFrontend, *source)); - jSources.Set(env, index, jSource); - jni::DeleteLocalRef(env, jSource); + jSources.Set(env, index, Source::peerForCoreSource(env, *source, *rendererFrontend)); index++; } @@ -874,38 +872,25 @@ jni::Object NativeMapView::getSource(JNIEnv& env, jni::String sourceId) } // Create and return the source's native peer - return jni::Object(createJavaSourcePeer(env, *rendererFrontend, *coreSource)); + return Source::peerForCoreSource(env, *coreSource, *rendererFrontend); } -void NativeMapView::addSource(JNIEnv& env, jni::jlong sourcePtr) { +void NativeMapView::addSource(JNIEnv& env, jni::Object obj, jlong sourcePtr) { assert(sourcePtr != 0); Source *source = reinterpret_cast(sourcePtr); try { - source->addToMap(*map); - source->setRendererFrontend(*rendererFrontend); + source->addToMap(env, obj, *map, *rendererFrontend); } catch (const std::runtime_error& error) { jni::ThrowNew(env, jni::FindClass(env, "com/mapbox/mapboxsdk/style/sources/CannotAddSourceException"), error.what()); } } -jni::Object NativeMapView::removeSourceById(JNIEnv& env, jni::String id) { - std::unique_ptr coreSource = map->getStyle().removeSource(jni::Make(env, id)); - if (coreSource) { - return jni::Object(createJavaSourcePeer(env, *rendererFrontend, *coreSource)); - } else { - return jni::Object(); - } -} - -void NativeMapView::removeSource(JNIEnv&, jlong sourcePtr) { +void NativeMapView::removeSource(JNIEnv& env, jni::Object obj, jlong sourcePtr) { assert(sourcePtr != 0); mbgl::android::Source *source = reinterpret_cast(sourcePtr); - std::unique_ptr coreSource = map->getStyle().removeSource(source->get().getID()); - if (coreSource) { - source->setSource(std::move(coreSource)); - } + source->removeFromMap(env, obj, *map); } void NativeMapView::addImage(JNIEnv& env, jni::String name, jni::jint w, jni::jint h, jni::jfloat scale, jni::Array pixels) { @@ -1048,7 +1033,6 @@ void NativeMapView::registerNative(jni::JNIEnv& env) { METHOD(&NativeMapView::getSources, "nativeGetSources"), METHOD(&NativeMapView::getSource, "nativeGetSource"), METHOD(&NativeMapView::addSource, "nativeAddSource"), - METHOD(&NativeMapView::removeSourceById, "nativeRemoveSourceById"), METHOD(&NativeMapView::removeSource, "nativeRemoveSource"), METHOD(&NativeMapView::addImage, "nativeAddImage"), METHOD(&NativeMapView::addImages, "nativeAddImages"), diff --git a/platform/android/src/native_map_view.hpp b/platform/android/src/native_map_view.hpp index d7e3b17b998..507d77ac5f0 100755 --- a/platform/android/src/native_map_view.hpp +++ b/platform/android/src/native_map_view.hpp @@ -19,7 +19,7 @@ #include "geometry/lat_lng.hpp" #include "geometry/projected_meters.hpp" #include "style/layers/layers.hpp" -#include "style/sources/sources.hpp" +#include "style/sources/source.hpp" #include "geometry/lat_lng_bounds.hpp" #include "map/camera_position.hpp" #include "map/image.hpp" @@ -230,11 +230,11 @@ class NativeMapView : public MapObserver { jni::Object getSource(JNIEnv&, jni::String); - void addSource(JNIEnv&, jni::jlong); + void addSource(JNIEnv&, jni::Object, jlong nativePtr); jni::Object removeSourceById(JNIEnv&, jni::String); - void removeSource(JNIEnv&, jlong); + void removeSource(JNIEnv&, jni::Object, jlong nativePtr); void addImage(JNIEnv&, jni::String, jni::jint, jni::jint, jni::jfloat, jni::Array); diff --git a/platform/android/src/style/sources/custom_geometry_source.cpp b/platform/android/src/style/sources/custom_geometry_source.cpp new file mode 100644 index 00000000000..a60b962e45b --- /dev/null +++ b/platform/android/src/style/sources/custom_geometry_source.cpp @@ -0,0 +1,143 @@ +#include "custom_geometry_source.hpp" + +#include + +// Java -> C++ conversion +#include "../android_conversion.hpp" +#include "../conversion/filter.hpp" + +// C++ -> Java conversion +#include "../../conversion/conversion.hpp" +#include "../../conversion/collection.hpp" +#include "../../geojson/conversion/feature.hpp" +#include + +#include + +namespace mbgl { +namespace android { + + // This conversion is expected not to fail because it's used only in contexts where + // the value was originally a GeoJsonOptions object on the Java side. If it fails + // to convert, it's a bug in our serialization or Java-side static typing. + static style::CustomGeometrySource::Options convertCustomGeometrySourceOptions(jni::JNIEnv& env, + jni::Object<> options, + style::TileFunction fetchFn, + style::TileFunction cancelFn) { + using namespace mbgl::style::conversion; + if (!options) { + return style::CustomGeometrySource::Options(); + } + Error error; + optional result = convert(Value(env, options), error); + if (!result) { + throw std::logic_error(error.message); + } + result->fetchTileFunction = fetchFn; + result->cancelTileFunction = cancelFn; + return *result; + } + + CustomGeometrySource::CustomGeometrySource(jni::JNIEnv& env, + jni::String sourceId, + jni::Object<> options) + : Source(env, std::make_unique( + jni::Make(env, sourceId), + convertCustomGeometrySourceOptions(env, options, + std::bind(&CustomGeometrySource::fetchTile, this, std::placeholders::_1), + std::bind(&CustomGeometrySource::cancelTile, this, std::placeholders::_1)))) { + } + + CustomGeometrySource::CustomGeometrySource(jni::JNIEnv& env, + mbgl::style::Source& coreSource, + AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend) { + } + + CustomGeometrySource::~CustomGeometrySource() = default; + + void CustomGeometrySource::fetchTile (const mbgl::CanonicalTileID& tileID) { + android::UniqueEnv _env = android::AttachEnv(); + + static auto fetchTile = javaClass.GetMethod(*_env, "fetchTile"); + + assert(javaPeer); + + auto peer = jni::Cast(*_env, *javaPeer, javaClass); + peer.Call(*_env, fetchTile, (int)tileID.z, (int)tileID.x, (int)tileID.y); + }; + + void CustomGeometrySource::cancelTile(const mbgl::CanonicalTileID& tileID) { + android::UniqueEnv _env = android::AttachEnv(); + + static auto cancelTile = javaClass.GetMethod(*_env, "cancelTile"); + + assert(javaPeer); + + auto peer = jni::Cast(*_env, *javaPeer, javaClass); + peer.Call(*_env, cancelTile, (int)tileID.z, (int)tileID.x, (int)tileID.y); + }; + + void CustomGeometrySource::setTileData(jni::JNIEnv& env, + jni::jint z, + jni::jint x, + jni::jint y, + jni::Object jFeatures) { + using namespace mbgl::android::geojson; + + // Convert the jni object + auto geometry = geojson::FeatureCollection::convert(env, jFeatures); + + // Update the core source + source.as()->CustomGeometrySource::setTileData(CanonicalTileID(z, x, y), GeoJSON(geometry)); + } + + void CustomGeometrySource::invalidateTile(jni::JNIEnv&, jni::jint z, jni::jint x, jni::jint y) { + source.as()->CustomGeometrySource::invalidateTile(CanonicalTileID(z, x, y)); + } + + void CustomGeometrySource::invalidateBounds(jni::JNIEnv& env, jni::Object jBounds) { + auto bounds = LatLngBounds::getLatLngBounds(env, jBounds); + source.as()->CustomGeometrySource::invalidateRegion(bounds); + } + + jni::Array> CustomGeometrySource::querySourceFeatures(jni::JNIEnv& env, + jni::Array> jfilter) { + using namespace mbgl::android::conversion; + using namespace mbgl::android::geojson; + + std::vector features; + if (rendererFrontend) { + features = rendererFrontend->querySourceFeatures(source.getID(), { {}, toFilter(env, jfilter) }); + } + return *convert>, std::vector>(env, features); + } + + jni::Class CustomGeometrySource::javaClass; + + jni::Object CustomGeometrySource::createJavaPeer(jni::JNIEnv& env) { + static auto constructor = CustomGeometrySource::javaClass.template GetConstructor(env); + return jni::Object(CustomGeometrySource::javaClass.New(env, constructor, reinterpret_cast(this)).Get()); + } + + void CustomGeometrySource::registerNative(jni::JNIEnv& env) { + // Lookup the class + CustomGeometrySource::javaClass = *jni::Class::Find(env).NewGlobalRef(env).release(); + + #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod(name) + + // Register the peer + jni::RegisterNativePeer( + env, CustomGeometrySource::javaClass, "nativePtr", + std::make_unique>, + "initialize", + "finalize", + METHOD(&CustomGeometrySource::querySourceFeatures, "querySourceFeatures"), + METHOD(&CustomGeometrySource::setTileData, "nativeSetTileData"), + METHOD(&CustomGeometrySource::invalidateTile, "nativeInvalidateTile"), + METHOD(&CustomGeometrySource::invalidateBounds, "nativeInvalidateBounds") + ); + } + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/sources/custom_geometry_source.hpp b/platform/android/src/style/sources/custom_geometry_source.hpp new file mode 100644 index 00000000000..1dc1c07b4fa --- /dev/null +++ b/platform/android/src/style/sources/custom_geometry_source.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "source.hpp" +#include +#include +#include +#include "../../geojson/geometry.hpp" +#include "../../geojson/feature.hpp" +#include "../../geojson/feature_collection.hpp" +#include "../../geometry/lat_lng_bounds.hpp" +#include + +namespace mbgl { +namespace android { + +class CustomGeometrySource : public Source { +public: + + static constexpr auto Name() { return "com/mapbox/mapboxsdk/style/sources/CustomGeometrySource"; }; + + static jni::Class javaClass; + + static void registerNative(jni::JNIEnv&); + + CustomGeometrySource(jni::JNIEnv&, jni::String, jni::Object<>); + + CustomGeometrySource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); + + ~CustomGeometrySource(); + + void fetchTile(const mbgl::CanonicalTileID& tileID); + void cancelTile(const mbgl::CanonicalTileID& tileID); + void setTileData(jni::JNIEnv& env, jni::jint z, jni::jint x, jni::jint y, jni::Object jf); + + void invalidateTile(jni::JNIEnv& env, jni::jint z, jni::jint x, jni::jint y); + void invalidateBounds(jni::JNIEnv& env, jni::Object bounds); + + jni::Array> querySourceFeatures(jni::JNIEnv&, + jni::Array> ); + +private: + jni::Object createJavaPeer(jni::JNIEnv&); + +}; // class CustomGeometrySource + +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/style/sources/geojson_source.cpp b/platform/android/src/style/sources/geojson_source.cpp index 4468b453f37..6d9ab9e22c5 100644 --- a/platform/android/src/style/sources/geojson_source.cpp +++ b/platform/android/src/style/sources/geojson_source.cpp @@ -43,8 +43,10 @@ namespace android { ) { } - GeoJSONSource::GeoJSONSource(mbgl::style::GeoJSONSource& coreSource) - : Source(coreSource) { + GeoJSONSource::GeoJSONSource(jni::JNIEnv& env, + mbgl::style::Source& coreSource, + AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend) { } GeoJSONSource::~GeoJSONSource() = default; @@ -118,9 +120,9 @@ namespace android { jni::Class GeoJSONSource::javaClass; - jni::jobject* GeoJSONSource::createJavaPeer(jni::JNIEnv& env) { + jni::Object GeoJSONSource::createJavaPeer(jni::JNIEnv& env) { static auto constructor = GeoJSONSource::javaClass.template GetConstructor(env); - return GeoJSONSource::javaClass.New(env, constructor, reinterpret_cast(this)); + return jni::Object(GeoJSONSource::javaClass.New(env, constructor, reinterpret_cast(this)).Get()); } void GeoJSONSource::registerNative(jni::JNIEnv& env) { diff --git a/platform/android/src/style/sources/geojson_source.hpp b/platform/android/src/style/sources/geojson_source.hpp index 52dd632bfad..c46519b04a2 100644 --- a/platform/android/src/style/sources/geojson_source.hpp +++ b/platform/android/src/style/sources/geojson_source.hpp @@ -21,7 +21,7 @@ class GeoJSONSource : public Source { GeoJSONSource(jni::JNIEnv&, jni::String, jni::Object<>); - GeoJSONSource(mbgl::style::GeoJSONSource&); + GeoJSONSource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); ~GeoJSONSource(); @@ -40,7 +40,8 @@ class GeoJSONSource : public Source { jni::String getURL(jni::JNIEnv&); - jni::jobject* createJavaPeer(jni::JNIEnv&); +private: + jni::Object createJavaPeer(jni::JNIEnv&); }; // class GeoJSONSource diff --git a/platform/android/src/style/sources/image_source.cpp b/platform/android/src/style/sources/image_source.cpp index d46b367c53a..0cd69959694 100644 --- a/platform/android/src/style/sources/image_source.cpp +++ b/platform/android/src/style/sources/image_source.cpp @@ -23,8 +23,10 @@ namespace android { ) { } - ImageSource::ImageSource(mbgl::style::ImageSource& coreSource) - : Source(coreSource) { + ImageSource::ImageSource(jni::JNIEnv& env, + mbgl::style::Source& coreSource, + AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend) { } ImageSource::~ImageSource() = default; @@ -45,9 +47,9 @@ namespace android { jni::Class ImageSource::javaClass; - jni::jobject* ImageSource::createJavaPeer(jni::JNIEnv& env) { + jni::Object ImageSource::createJavaPeer(jni::JNIEnv& env) { static auto constructor = ImageSource::javaClass.template GetConstructor(env); - return ImageSource::javaClass.New(env, constructor, reinterpret_cast(this)); + return jni::Object(ImageSource::javaClass.New(env, constructor, reinterpret_cast(this)).Get()); } void ImageSource::registerNative(jni::JNIEnv& env) { diff --git a/platform/android/src/style/sources/image_source.hpp b/platform/android/src/style/sources/image_source.hpp index 9787a7294f3..f0af28d3575 100644 --- a/platform/android/src/style/sources/image_source.hpp +++ b/platform/android/src/style/sources/image_source.hpp @@ -21,7 +21,7 @@ class ImageSource : public Source { ImageSource(jni::JNIEnv&, jni::String, jni::Object); - ImageSource(mbgl::style::ImageSource&); + ImageSource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); ~ImageSource(); @@ -30,7 +30,8 @@ class ImageSource : public Source { void setImage(jni::JNIEnv&, jni::Object); - jni::jobject* createJavaPeer(jni::JNIEnv&); +private: + jni::Object createJavaPeer(jni::JNIEnv&); }; // class ImageSource diff --git a/platform/android/src/style/sources/raster_source.cpp b/platform/android/src/style/sources/raster_source.cpp index d45342a1ad9..33223a5b690 100644 --- a/platform/android/src/style/sources/raster_source.cpp +++ b/platform/android/src/style/sources/raster_source.cpp @@ -22,8 +22,10 @@ namespace android { ) { } - RasterSource::RasterSource(mbgl::style::RasterSource& coreSource) - : Source(coreSource) { + RasterSource::RasterSource(jni::JNIEnv& env, + mbgl::style::Source& coreSource, + AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend) { } RasterSource::~RasterSource() = default; @@ -35,9 +37,9 @@ namespace android { jni::Class RasterSource::javaClass; - jni::jobject* RasterSource::createJavaPeer(jni::JNIEnv& env) { + jni::Object RasterSource::createJavaPeer(jni::JNIEnv& env) { static auto constructor = RasterSource::javaClass.template GetConstructor(env); - return RasterSource::javaClass.New(env, constructor, reinterpret_cast(this)); + return jni::Object(RasterSource::javaClass.New(env, constructor, reinterpret_cast(this)).Get()); } void RasterSource::registerNative(jni::JNIEnv& env) { diff --git a/platform/android/src/style/sources/raster_source.hpp b/platform/android/src/style/sources/raster_source.hpp index 84c49d73814..a1da22f40d9 100644 --- a/platform/android/src/style/sources/raster_source.hpp +++ b/platform/android/src/style/sources/raster_source.hpp @@ -18,13 +18,14 @@ class RasterSource : public Source { RasterSource(jni::JNIEnv&, jni::String, jni::Object<>, jni::jint); - RasterSource(mbgl::style::RasterSource&); + RasterSource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); ~RasterSource(); jni::String getURL(jni::JNIEnv&); - jni::jobject* createJavaPeer(jni::JNIEnv&); +private: + jni::Object createJavaPeer(jni::JNIEnv&); }; // class RasterSource diff --git a/platform/android/src/style/sources/source.cpp b/platform/android/src/style/sources/source.cpp index 447b13019d0..3b89b25d7dd 100644 --- a/platform/android/src/style/sources/source.cpp +++ b/platform/android/src/style/sources/source.cpp @@ -15,29 +15,70 @@ #include +// Core Sources +#include +#include +#include +#include + +// Android Source peers +#include "geojson_source.hpp" +#include "image_source.hpp" +#include "raster_source.hpp" +#include "unknown_source.hpp" +#include "vector_source.hpp" +#include "custom_geometry_source.hpp" + namespace mbgl { namespace android { - /** - * Invoked when the construction is initiated from the jvm through a subclass - */ - Source::Source(jni::JNIEnv&, std::unique_ptr coreSource) - : ownedSource(std::move(coreSource)) - , source(*ownedSource) { + static std::unique_ptr createSourcePeer(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend) { + if (coreSource.is()) { + return std::make_unique(env, *coreSource.as(), frontend); + } else if (coreSource.is()) { + return std::make_unique(env, *coreSource.as(), frontend); + } else if (coreSource.is()) { + return std::make_unique(env, *coreSource.as(), frontend); + } else if (coreSource.is()) { + return std::make_unique(env, *coreSource.as(), frontend); + } else { + return std::make_unique(env, coreSource, frontend); + } } - Source::Source(mbgl::style::Source& coreSource) - : source(coreSource) { + jni::Object Source::peerForCoreSource(jni::JNIEnv& env, mbgl::style::Source& coreSource, AndroidRendererFrontend& frontend) { + if (!coreSource.peer.has_value()) { + coreSource.peer = createSourcePeer(env, coreSource, frontend); + } + return *mbgl::util::any_cast>(&coreSource.peer)->get()->javaPeer; } - Source::~Source() = default; + Source::Source(jni::JNIEnv& env, mbgl::style::Source& coreSource, jni::Object obj, AndroidRendererFrontend& frontend) + : source(coreSource) + , javaPeer(obj.NewGlobalRef(env)) + , rendererFrontend(&frontend) { + } - style::Source& Source::get() { - return source; + Source::Source(jni::JNIEnv&, std::unique_ptr coreSource) + : ownedSource(std::move(coreSource)) + , source(*ownedSource) { } - void Source::setSource(std::unique_ptr coreSource) { - this->ownedSource = std::move(coreSource); + Source::~Source() { + // Before being added to a map, the Java peer owns this C++ peer and cleans + // up after itself correctly through the jni native peer bindings. + // After being added to the map, the ownership is flipped and the C++ peer has a strong reference + // to it's Java peer, preventing the Java peer from being GC'ed. + // In this case, the core source initiates the destruction, which requires releasing the Java peer, + // while also resetting it's nativePtr to 0 to prevent the subsequent GC of the Java peer from + // re-entering this dtor. + if (ownedSource.get() == nullptr && javaPeer.get() != nullptr) { + // Manually clear the java peer + android::UniqueEnv env = android::AttachEnv(); + static auto nativePtrField = javaClass.GetField(*env, "nativePtr"); + javaPeer->Set(*env, nativePtrField, (jlong) 0); + javaPeer.reset(); + } } jni::String Source::getId(jni::JNIEnv& env) { @@ -49,23 +90,48 @@ namespace android { return attribution ? jni::Make(env, attribution.value()) : jni::Make(env,""); } - void Source::addToMap(mbgl::Map& _map) { + void Source::addToMap(JNIEnv& env, jni::Object obj, mbgl::Map& map, AndroidRendererFrontend& frontend) { // Check to see if we own the source first if (!ownedSource) { throw std::runtime_error("Cannot add source twice"); } - // Add source to map - _map.getStyle().addSource(releaseCoreSource()); - } + // Add source to map and release ownership + map.getStyle().addSource(std::move(ownedSource)); + + // Add peer to core source + source.peer = std::unique_ptr(this); + + // Add strong reference to java source + javaPeer = obj.NewGlobalRef(env); - void Source::setRendererFrontend(AndroidRendererFrontend& frontend_) { - rendererFrontend = &frontend_; + rendererFrontend = &frontend; } - std::unique_ptr Source::releaseCoreSource() { - assert(ownedSource != nullptr); - return std::move(ownedSource); + void Source::removeFromMap(JNIEnv&, jni::Object, mbgl::Map& map) { + // Cannot remove if not attached yet + if (ownedSource) { + throw std::runtime_error("Cannot remove detached source"); + } + + // Remove the source from the map and take ownership + ownedSource = map.getStyle().removeSource(source.getID()); + + // The source may not be removed if any layers still reference it + if (!ownedSource) { + return; + } + + // Release the peer relationships. These will be re-established when the source is added to a map + assert(ownedSource->peer.has_value()); + util::any_cast>(&(ownedSource->peer))->release(); + ownedSource->peer.reset(); + + // Release the strong reference to the java peer + assert(javaPeer); + javaPeer.release(); + + rendererFrontend = nullptr; } jni::Class Source::javaClass; @@ -82,6 +148,13 @@ namespace android { METHOD(&Source::getAttribution, "nativeGetAttribution") ); + // Register subclasses + GeoJSONSource::registerNative(env); + ImageSource::registerNative(env); + RasterSource::registerNative(env); + UnknownSource::registerNative(env); + VectorSource::registerNative(env); + CustomGeometrySource::registerNative(env); } } // namespace android diff --git a/platform/android/src/style/sources/source.hpp b/platform/android/src/style/sources/source.hpp index 383017b66f7..718f60b3810 100644 --- a/platform/android/src/style/sources/source.hpp +++ b/platform/android/src/style/sources/source.hpp @@ -21,48 +21,40 @@ class Source : private mbgl::util::noncopyable { static void registerNative(jni::JNIEnv&); + static jni::Object peerForCoreSource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); + /* - * Called when a Java object is created on the c++ side + * Called when a Java object is created for a core source that belongs to a map. */ - Source(mbgl::style::Source&); + Source(jni::JNIEnv&, mbgl::style::Source&, jni::Object, AndroidRendererFrontend&); /* - * Called when a Java object was created from the jvm side + * Called when a Java object is created for a new core source that does not belong to a map. */ Source(jni::JNIEnv&, std::unique_ptr); virtual ~Source(); - /** - * Set core source (ie return ownership after remove) - */ - void setSource(std::unique_ptr); - - style::Source& get(); - - void addToMap(mbgl::Map&); + void addToMap(JNIEnv&, jni::Object, mbgl::Map&, AndroidRendererFrontend&); - void setRendererFrontend(AndroidRendererFrontend&); - - virtual jni::jobject* createJavaPeer(jni::JNIEnv&) = 0; + void removeFromMap(JNIEnv&, jni::Object, mbgl::Map&); jni::String getId(jni::JNIEnv&); jni::String getAttribution(jni::JNIEnv&); protected: - // Release the owned view and return it - std::unique_ptr releaseCoreSource(); - - // Set on newly created sources until added to the map + // Set on newly created sources until added to the map. std::unique_ptr ownedSource; - // Raw pointer that is valid until the source is removed from the map + // Raw pointer that is valid at all times. mbgl::style::Source& source; - // RendererFrontend pointer is valid only when - // added to the map - AndroidRendererFrontend* rendererFrontend; + // Set when the source is added to a map. + jni::UniqueObject javaPeer; + + // RendererFrontend pointer is valid only when added to the map. + AndroidRendererFrontend* rendererFrontend { nullptr }; }; } // namespace android diff --git a/platform/android/src/style/sources/sources.cpp b/platform/android/src/style/sources/sources.cpp deleted file mode 100644 index 9ab3ca8e840..00000000000 --- a/platform/android/src/style/sources/sources.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "sources.hpp" - -#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" - -namespace { - - using namespace mbgl::android; - - Source* initializeSourcePeer(mbgl::style::Source& coreSource) { - Source* source; - if (coreSource.is()) { - source = new VectorSource(*coreSource.as()); - } else if (coreSource.is()) { - source = new RasterSource(*coreSource.as()); - } else if (coreSource.is()) { - source = new GeoJSONSource(*coreSource.as()); - } else if (coreSource.is()) { - source = new ImageSource(*coreSource.as()); - } else { - source = new UnknownSource(coreSource); - } - - return source; - } -} // namespace - -namespace mbgl { -namespace android { - - -jni::jobject* createJavaSourcePeer(jni::JNIEnv& env, AndroidRendererFrontend& frontend, mbgl::style::Source& coreSource) { - std::unique_ptr peerSource = std::unique_ptr(initializeSourcePeer(coreSource)); - peerSource->setRendererFrontend(frontend); - jni::jobject* result = peerSource->createJavaPeer(env); - peerSource.release(); - return result; -} - -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/android/src/style/sources/sources.hpp b/platform/android/src/style/sources/sources.hpp deleted file mode 100644 index c7b36818b2c..00000000000 --- a/platform/android/src/style/sources/sources.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -#include "source.hpp" -#include "../../android_renderer_frontend.hpp" - -#include - -namespace mbgl { -namespace android { - - jni::jobject* createJavaSourcePeer(jni::JNIEnv&, AndroidRendererFrontend&, mbgl::style::Source&); - - void registerNativeSources(jni::JNIEnv&); - -} // namespace android -} // namespace mbgl diff --git a/platform/android/src/style/sources/unknown_source.cpp b/platform/android/src/style/sources/unknown_source.cpp index 79f27bdfbf0..4b5510c1dbb 100644 --- a/platform/android/src/style/sources/unknown_source.cpp +++ b/platform/android/src/style/sources/unknown_source.cpp @@ -12,15 +12,17 @@ namespace { namespace mbgl { namespace android { - UnknownSource::UnknownSource(mbgl::style::Source& coreSource) - : Source(coreSource) { + UnknownSource::UnknownSource(jni::JNIEnv& env, + mbgl::style::Source& coreSource, + AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend) { } jni::Class UnknownSource::javaClass; - jni::jobject* UnknownSource::createJavaPeer(jni::JNIEnv& env) { + jni::Object UnknownSource::createJavaPeer(jni::JNIEnv& env) { static auto constructor = UnknownSource::javaClass.template GetConstructor(env); - return UnknownSource::javaClass.New(env, constructor, reinterpret_cast(this)); + return jni::Object(UnknownSource::javaClass.New(env, constructor, reinterpret_cast(this)).Get()); } void UnknownSource::registerNative(jni::JNIEnv& env) { diff --git a/platform/android/src/style/sources/unknown_source.hpp b/platform/android/src/style/sources/unknown_source.hpp index 4a003c9a7fe..414d420c617 100644 --- a/platform/android/src/style/sources/unknown_source.hpp +++ b/platform/android/src/style/sources/unknown_source.hpp @@ -16,11 +16,12 @@ class UnknownSource : public Source { static void registerNative(jni::JNIEnv&); - UnknownSource(mbgl::style::Source&); + UnknownSource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); ~UnknownSource() = default; - jni::jobject* createJavaPeer(jni::JNIEnv&); +private: + jni::Object createJavaPeer(jni::JNIEnv&); }; // class UnknownSource diff --git a/platform/android/src/style/sources/vector_source.cpp b/platform/android/src/style/sources/vector_source.cpp index 7fe45441bdd..9a9548d2832 100644 --- a/platform/android/src/style/sources/vector_source.cpp +++ b/platform/android/src/style/sources/vector_source.cpp @@ -30,8 +30,10 @@ namespace android { ) { } - VectorSource::VectorSource(mbgl::style::VectorSource& coreSource) - : Source(coreSource) { + VectorSource::VectorSource(jni::JNIEnv& env, + mbgl::style::Source& coreSource, + AndroidRendererFrontend& frontend) + : Source(env, coreSource, createJavaPeer(env), frontend) { } VectorSource::~VectorSource() = default; @@ -56,9 +58,9 @@ namespace android { jni::Class VectorSource::javaClass; - jni::jobject* VectorSource::createJavaPeer(jni::JNIEnv& env) { + jni::Object VectorSource::createJavaPeer(jni::JNIEnv& env) { static auto constructor = VectorSource::javaClass.template GetConstructor(env); - return VectorSource::javaClass.New(env, constructor, reinterpret_cast(this)); + return jni::Object(VectorSource::javaClass.New(env, constructor, reinterpret_cast(this)).Get()); } void VectorSource::registerNative(jni::JNIEnv& env) { diff --git a/platform/android/src/style/sources/vector_source.hpp b/platform/android/src/style/sources/vector_source.hpp index 509fe068d17..16049f5c773 100644 --- a/platform/android/src/style/sources/vector_source.hpp +++ b/platform/android/src/style/sources/vector_source.hpp @@ -19,7 +19,7 @@ class VectorSource : public Source { VectorSource(jni::JNIEnv&, jni::String, jni::Object<>); - VectorSource(mbgl::style::VectorSource&); + VectorSource(jni::JNIEnv&, mbgl::style::Source&, AndroidRendererFrontend&); ~VectorSource(); @@ -28,7 +28,8 @@ class VectorSource : public Source { jni::String getURL(jni::JNIEnv&); - jni::jobject* createJavaPeer(jni::JNIEnv&); +private: + jni::Object createJavaPeer(jni::JNIEnv&); }; // class VectorSource diff --git a/platform/darwin/docs/theme/assets/css/jazzy.css.scss b/platform/darwin/docs/theme/assets/css/jazzy.css.scss index ad0a3b70822..103ba601dc3 100644 --- a/platform/darwin/docs/theme/assets/css/jazzy.css.scss +++ b/platform/darwin/docs/theme/assets/css/jazzy.css.scss @@ -386,6 +386,7 @@ pre code { .nav-group-task[data-name="MGLStyleFunction"], .nav-group-task[data-name="MGLStyleLayer"], .nav-group-task[data-name="MGLTileSource"], +.nav-group-task[data-name="MGLAbstractShapeSource"], .nav-group-task[data-name="MGLVectorStyleLayer"] { .nav-group-task-link::after { @extend %nav-group-task-gloss; diff --git a/platform/darwin/src/MGLAbstractShapeSource.h b/platform/darwin/src/MGLAbstractShapeSource.h new file mode 100644 index 00000000000..3b35986b3f8 --- /dev/null +++ b/platform/darwin/src/MGLAbstractShapeSource.h @@ -0,0 +1,99 @@ +#import "MGLSource.h" + +/** + Options for `MGLShapeSource` objects. + */ +typedef NSString *MGLShapeSourceOption NS_STRING_ENUM; + +/** + An `NSNumber` object containing a Boolean enabling or disabling clustering. + If the `shape` property contains point shapes, setting this option to + `YES` clusters the points by radius into groups. The default value is `NO`. + + This attribute corresponds to the + cluster + source property in the Mapbox Style Specification. + + This option only affects point features within a shape source. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClustered; + +/** + An `NSNumber` object containing an integer; specifies the radius of each + cluster if clustering is enabled. A value of 512 produces a radius equal to + the width of a tile. The default value is 50. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius; + +/** + An `NSNumber` object containing an integer; specifies the maximum zoom level at + which to cluster points if clustering is enabled. Defaults to one zoom level + less than the value of `MGLShapeSourceOptionMaximumZoomLevel` so that, at the + maximum zoom level, the shapes are not clustered. + + This attribute corresponds to the + clusterMaxZoom + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering; + +/** + An `NSNumber` object containing an integer; specifies the minimum zoom level at + which to create vector tiles. The default value is 0. + + This attribute corresponds to the + minzoom + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel; + +/** + An `NSNumber` object containing an integer; specifies the maximum zoom level at + which to create vector tiles. A greater value produces greater detail at high + zoom levels. The default value is 18. + + This attribute corresponds to the + maxzoom + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel; + +/** + An `NSNumber` object containing an integer; specifies the size of the tile + buffer on each side. A value of 0 produces no buffer. A value of 512 produces a + buffer as wide as the tile itself. Larger values produce fewer rendering + artifacts near tile edges and slower performance. The default value is 128. + + This attribute corresponds to the + buffer + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer; + +/** + An `NSNumber` object containing a double; specifies the Douglas-Peucker + simplification tolerance. A greater value produces simpler geometries and + improves performance. The default value is 0.375. + + This attribute corresponds to the + tolerance + source property in the Mapbox Style Specification. + */ +extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; + +/** + `MGLAbstractShapeSource` is an abstract base class for map content sources that + supply vector shapes to be shown on the map. A shape source is added to an + `MGLStyle` object along with an `MGLVectorStyleLayer` object. The vector style + layer defines the appearance of any content supplied by the shape source. + + + Do not create instances of this class directly, and do not create your own + subclasses of this class. Instead, create instances of `MGLShapeSource` or + `MGLComputedShapeSource`. + */ +MGL_EXPORT +@interface MGLAbstractShapeSource : MGLSource + + +@end diff --git a/platform/darwin/src/MGLAbstractShapeSource.mm b/platform/darwin/src/MGLAbstractShapeSource.mm new file mode 100644 index 00000000000..755d0403879 --- /dev/null +++ b/platform/darwin/src/MGLAbstractShapeSource.mm @@ -0,0 +1,117 @@ +#import "MGLAbstractShapeSource.h" +#import "MGLAbstractShapeSource_Private.h" + +const MGLShapeSourceOption MGLShapeSourceOptionBuffer = @"MGLShapeSourceOptionBuffer"; +const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius = @"MGLShapeSourceOptionClusterRadius"; +const MGLShapeSourceOption MGLShapeSourceOptionClustered = @"MGLShapeSourceOptionClustered"; +const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSourceOptionMaximumZoomLevel"; +const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering"; +const MGLShapeSourceOption MGLShapeSourceOptionMinimumZoomLevel = @"MGLShapeSourceOptionMinimumZoomLevel"; +const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance"; + +@interface MGLAbstractShapeSource () + +@end + +@implementation MGLAbstractShapeSource + +@end + +mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options) { + auto geoJSONOptions = mbgl::style::GeoJSONOptions(); + + if (NSNumber *value = options[MGLShapeSourceOptionMinimumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + geoJSONOptions.minzoom = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + geoJSONOptions.maxzoom = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionBuffer]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionBuffer must be an NSNumber."]; + } + geoJSONOptions.buffer = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionSimplificationTolerance]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionSimplificationTolerance must be an NSNumber."]; + } + geoJSONOptions.tolerance = value.doubleValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionClusterRadius]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClusterRadius must be an NSNumber."]; + } + geoJSONOptions.clusterRadius = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevelForClustering]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; + } + geoJSONOptions.clusterMaxZoom = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionClustered]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionClustered must be an NSNumber."]; + } + geoJSONOptions.cluster = value.boolValue; + } + + return geoJSONOptions; +} + +mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options) { + mbgl::style::CustomGeometrySource::Options sourceOptions; + + if (NSNumber *value = options[MGLShapeSourceOptionMinimumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; + } + sourceOptions.zoomRange.min = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevel]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; + } + sourceOptions.zoomRange.max = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionBuffer]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionBuffer must be an NSNumber."]; + } + sourceOptions.tileOptions.buffer = value.integerValue; + } + + if (NSNumber *value = options[MGLShapeSourceOptionSimplificationTolerance]) { + if (![value isKindOfClass:[NSNumber class]]) { + [NSException raise:NSInvalidArgumentException + format:@"MGLShapeSourceOptionSimplificationTolerance must be an NSNumber."]; + } + sourceOptions.tileOptions.tolerance = value.doubleValue; + } + return sourceOptions; +} diff --git a/platform/darwin/src/MGLAbstractShapeSource_Private.h b/platform/darwin/src/MGLAbstractShapeSource_Private.h new file mode 100644 index 00000000000..ddde55b1499 --- /dev/null +++ b/platform/darwin/src/MGLAbstractShapeSource_Private.h @@ -0,0 +1,22 @@ +#import "MGLAbstractShapeSource.h" + +#import "MGLFoundation.h" +#import "MGLTypes.h" +#import "MGLShape.h" + +#include +#include + +NS_ASSUME_NONNULL_BEGIN + +@interface MGLAbstractShapeSource (Private) + +MGL_EXPORT + +mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options); + +MGL_EXPORT +mbgl::style::CustomGeometrySource::Options MBGLCustomGeometrySourceOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options); + +@end +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLComputedShapeSource.h b/platform/darwin/src/MGLComputedShapeSource.h new file mode 100644 index 00000000000..f90f2c94b11 --- /dev/null +++ b/platform/darwin/src/MGLComputedShapeSource.h @@ -0,0 +1,113 @@ +#import "MGLAbstractShapeSource.h" + +#import "MGLFoundation.h" +#import "MGLGeometry.h" +#import "MGLTypes.h" +#import "MGLShape.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol MGLFeature; + +/** + Data source for `MGLComputedShapeSource`. This protocol defines two optional methods for fetching + data, one based on tile coordinates, and one based on a bounding box. Classes that implement this + protocol must implement one, and only one of the methods. Methods on this protocol will not be + called on main thread, they will be called on the caller's `requestQueue`. + */ +@protocol MGLComputedShapeSourceDataSource + +@optional +/** + Fetch features for a tile. This method will not be invoked on the main queue, it + will be invoked on the caller's `requestQueue`. + @param x Tile X coordinate. + @param y Tile Y coordinate. + @param zoomLevel Tile zoom level. + */ +- (NSArray *>*)featuresInTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel; + +/** + Fetch features for a tile. This method will not be invoked on the main queue, it + will be invoked on the caller's `requestQueue`. + @param bounds The bounds to fetch data for. + @param zoomLevel Tile zoom level. + */ +- (NSArray *>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoomLevel; + +@end + +/** + A source for vector data that is fetched one tile at a time. Useful for sources that are + too large to fit in memory, or are already divided into tiles, but not in Mapbox Vector Tile format. + + Supported options are `MGLShapeSourceOptionMinimumZoomLevel`, `MGLShapeSourceOptionMaximumZoomLevel`, + `MGLShapeSourceOptionBuffer`, and `MGLShapeSourceOptionSimplificationTolerance.` This source does + not support clustering. + */ +MGL_EXPORT +@interface MGLComputedShapeSource : MGLAbstractShapeSource + +/** + Returns a custom shape data source initialized with an identifier, and a + dictionary of options for the source according to the + style + specification. + + @param identifier A string that uniquely identifies the source. + @param options An `NSDictionary` of options for this source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options NS_DESIGNATED_INITIALIZER; + +/** + Returns a custom shape data source initialized with an identifier, data source, and a + dictionary of options for the source according to the + style + specification. + + @param identifier A string that uniquely identifies the source. + @param options An `NSDictionary` of options for this source. + */ +- (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id)dataSource options:(nullable NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options; + +/** + Invalidates all the features and properties intersecting with or contained in + the specified bounds. New fetch requests will immediately be invoked on the + `MGLComputedShapeSourceDataSource`. + @param bounds Coordinate bounds to invalidate. + */ +- (void) invalidateBounds:(MGLCoordinateBounds)bounds; + +/** + Invalidates all the feautres and properties of a given tile. A new fetch request + will immediately be invoked on the `MGLComputedShapeSourceDataSource`. + @param x Tile X coordinate. + @param y Tile Y coordinate. + @param zoomLevel Tile zoom level. + */ +- (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel; + +/** + Set a new set of features for a tile. This method can be invkoed from background threads. + For best performance, use this method only to update tiles that have already been requested + through `MGLComputedShapeSourceDataSource.` + @param features Features for the tile. + @param x Tile X coordinate. + @param y Tile Y coordinate. + @param zoomLevel Tile zoom level. + */ +- (void) setFeatures:(NSArray *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel; + +/** + An object that implements the `MGLComputedShapeSourceDataSource` protocol that will be queried for tile data. + */ +@property (nonatomic, weak, nullable) id dataSource; + +/** + A queue that calls to the data source will be made on. + */ +@property (nonatomic, readonly) NSOperationQueue *requestQueue; + +@end + +NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLComputedShapeSource.mm b/platform/darwin/src/MGLComputedShapeSource.mm new file mode 100644 index 00000000000..3176e61a723 --- /dev/null +++ b/platform/darwin/src/MGLComputedShapeSource.mm @@ -0,0 +1,164 @@ +#import "MGLComputedShapeSource.h" + +#import "MGLMapView_Private.h" +#import "MGLSource_Private.h" +#import "MGLShape_Private.h" +#import "MGLAbstractShapeSource_Private.h" +#import "MGLGeometry_Private.h" + +#include +#include +#include +#include + +@interface MGLComputedShapeSource () { + std::unique_ptr _pendingSource; +} + +@property (nonatomic, readwrite) NSDictionary *options; +@property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForTile; +@property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForBounds; + +@end + +@interface MGLComputedShapeSourceFetchOperation : NSOperation + +@property (nonatomic, readonly) uint8_t z; +@property (nonatomic, readonly) uint32_t x; +@property (nonatomic, readonly) uint32_t y; +@property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForTile; +@property (nonatomic, assign) BOOL dataSourceImplementsFeaturesForBounds; +@property (nonatomic, weak, nullable) id dataSource; +@property (nonatomic, nullable) mbgl::style::CustomGeometrySource *rawSource; + +- (instancetype)initForSource:(MGLComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileId; + +@end + +@implementation MGLComputedShapeSourceFetchOperation + +- (instancetype)initForSource:(MGLComputedShapeSource*)source tile:(const mbgl::CanonicalTileID&)tileID { + self = [super init]; + _z = tileID.z; + _x = tileID.x; + _y = tileID.y; + _dataSourceImplementsFeaturesForTile = source.dataSourceImplementsFeaturesForTile; + _dataSourceImplementsFeaturesForBounds = source.dataSourceImplementsFeaturesForBounds; + _dataSource = source.dataSource; + mbgl::style::CustomGeometrySource *rawSource = static_cast(source.rawSource); + _rawSource = rawSource; + return self; +} + +- (void)main { + if ([self isCancelled]) { + return; + } + + NSArray *> *data; + if(!self.dataSource) { + data = nil; + } else if(self.dataSourceImplementsFeaturesForTile) { + data = [self.dataSource featuresInTileAtX:self.x + y:self.y + zoomLevel:self.z]; + } else { + mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID(self.z, self.x, self.y); + mbgl::LatLngBounds tileBounds = mbgl::LatLngBounds(tileID); + data = [self.dataSource featuresInCoordinateBounds:MGLCoordinateBoundsFromLatLngBounds(tileBounds) + zoomLevel:self.z]; + } + + if(![self isCancelled]) { + mbgl::FeatureCollection featureCollection; + featureCollection.reserve(data.count); + for (MGLShape * feature in data) { + mbgl::Feature geoJsonObject = [feature geoJSONObject].get(); + featureCollection.push_back(geoJsonObject); + } + const auto geojson = mbgl::GeoJSON{featureCollection}; + if(![self isCancelled] && self.rawSource) { + self.rawSource->setTileData(mbgl::CanonicalTileID(self.z, self.x, self.y), geojson); + } + } +} + +- (void)cancel { + [super cancel]; + self.rawSource = NULL; +} + +@end + +@implementation MGLComputedShapeSource + +- (instancetype)initWithIdentifier:(NSString *)identifier options:(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { + _requestQueue = [[NSOperationQueue alloc] init]; + self.requestQueue.name = [NSString stringWithFormat:@"mgl.MGLComputedShapeSource.%@", identifier]; + self.requestQueue.qualityOfService = NSQualityOfServiceUtility; + self.requestQueue.maxConcurrentOperationCount = 4; + + auto sourceOptions = MBGLCustomGeometrySourceOptionsFromDictionary(options); + sourceOptions.fetchTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { + NSOperation *operation = [[MGLComputedShapeSourceFetchOperation alloc] initForSource:self tile:tileID]; + [self.requestQueue addOperation:operation]; + }; + + sourceOptions.cancelTileFunction = ^void(const mbgl::CanonicalTileID& tileID) { + for(MGLComputedShapeSourceFetchOperation *operation in [self.requestQueue operations]) { + if(operation.x == tileID.x && operation.y == tileID.y && operation.z == tileID.z) { + [operation cancel]; + } + } + }; + + auto source = std::make_unique(identifier.UTF8String, sourceOptions); + return self = [super initWithPendingSource:std::move(source)]; +} + +- (instancetype)initWithIdentifier:(NSString *)identifier dataSource:(id)dataSource options:(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *)options { + self = [self initWithIdentifier:identifier options:options]; + [self setDataSource:dataSource]; + return self; +} + +- (void)dealloc { + [self.requestQueue cancelAllOperations]; +} + +- (void)setFeatures:(NSArray *>*)features inTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)zoomLevel { + mbgl::CanonicalTileID tileID = mbgl::CanonicalTileID((uint8_t)zoomLevel, (uint32_t)x, (uint32_t)y); + mbgl::FeatureCollection featureCollection; + featureCollection.reserve(features.count); + for (MGLShape * feature in features) { + mbgl::Feature geoJsonObject = [feature geoJSONObject].get(); + featureCollection.push_back(geoJsonObject); + } + const auto geojson = mbgl::GeoJSON{featureCollection}; + static_cast(self.rawSource)->setTileData(tileID, geojson); +} + +- (void)setDataSource:(id)dataSource { + [self.requestQueue cancelAllOperations]; + // Check which method the datasource implements, to avoid having to check for each tile + self.dataSourceImplementsFeaturesForTile = [dataSource respondsToSelector:@selector(featuresInTileAtX:y:zoomLevel:)]; + self.dataSourceImplementsFeaturesForBounds = [dataSource respondsToSelector:@selector(featuresInCoordinateBounds:zoomLevel:)]; + + if(!self.dataSourceImplementsFeaturesForBounds && !self.dataSourceImplementsFeaturesForTile) { + [NSException raise:@"Invalid Datasource" format:@"Datasource does not implement any MGLComputedShapeSourceDataSource methods"]; + } else if(self.dataSourceImplementsFeaturesForBounds && self.dataSourceImplementsFeaturesForTile) { + [NSException raise:@"Invalid Datasource" format:@"Datasource implements multiple MGLComputedShapeSourceDataSource methods"]; + } + + _dataSource = dataSource; +} + +- (void) invalidateBounds:(MGLCoordinateBounds)bounds { + ((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateRegion(MGLLatLngBoundsFromCoordinateBounds(bounds)); +} + +- (void) invalidateTileAtX:(NSUInteger)x y:(NSUInteger)y zoomLevel:(NSUInteger)z { + ((mbgl::style::CustomGeometrySource *)self.rawSource)->invalidateTile(mbgl::CanonicalTileID(z, (unsigned int)x, (unsigned int)y)); +} + +@end diff --git a/platform/darwin/src/MGLShapeSource.h b/platform/darwin/src/MGLShapeSource.h index 7460c83f507..ca150edac15 100644 --- a/platform/darwin/src/MGLShapeSource.h +++ b/platform/darwin/src/MGLShapeSource.h @@ -1,4 +1,4 @@ -#import "MGLSource.h" +#import "MGLAbstractShapeSource.h" #import "MGLFoundation.h" #import "MGLTypes.h" @@ -13,72 +13,6 @@ NS_ASSUME_NONNULL_BEGIN */ typedef NSString *MGLShapeSourceOption NS_STRING_ENUM; -/** - An `NSNumber` object containing a Boolean enabling or disabling clustering. - If the `shape` property contains point shapes, setting this option to - `YES` clusters the points by radius into groups. The default value is `NO`. - - This attribute corresponds to the - cluster - source property in the Mapbox Style Specification. - - This option only affects point features within a shape source. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClustered; - -/** - An `NSNumber` object containing an integer; specifies the radius of each - cluster if clustering is enabled. A value of 512 produces a radius equal to - the width of a tile. The default value is 50. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius; - -/** - An `NSNumber` object containing an integer; specifies the maximum zoom level at - which to cluster points if clustering is enabled. Defaults to one zoom level - less than the value of `MGLShapeSourceOptionMaximumZoomLevel` so that, at the - maximum zoom level, the shapes are not clustered. - - This attribute corresponds to the - clusterMaxZoom - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering; - -/** - An `NSNumber` object containing an integer; specifies the maximum zoom level at - which to create vector tiles. A greater value produces greater detail at high - zoom levels. The default value is 18. - - This attribute corresponds to the - maxzoom - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel; - -/** - An `NSNumber` object containing an integer; specifies the size of the tile - buffer on each side. A value of 0 produces no buffer. A value of 512 produces a - buffer as wide as the tile itself. Larger values produce fewer rendering - artifacts near tile edges and slower performance. The default value is 128. - - This attribute corresponds to the - buffer - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionBuffer; - -/** - An `NSNumber` object containing a double; specifies the Douglas-Peucker - simplification tolerance. A greater value produces simpler geometries and - improves performance. The default value is 0.375. - - This attribute corresponds to the - tolerance - source property in the Mapbox Style Specification. - */ -extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance; - /** `MGLShapeSource` is a map content source that supplies vector shapes to be shown on the map. The shapes may be instances of `MGLShape` or `MGLFeature`, @@ -112,7 +46,7 @@ extern MGL_EXPORT const MGLShapeSourceOption MGLShapeSourceOptionSimplificationT ``` */ MGL_EXPORT -@interface MGLShapeSource : MGLSource +@interface MGLShapeSource : MGLAbstractShapeSource #pragma mark Initializing a Source diff --git a/platform/darwin/src/MGLShapeSource.mm b/platform/darwin/src/MGLShapeSource.mm index 571cbdcc627..8fbb9a18ef9 100644 --- a/platform/darwin/src/MGLShapeSource.mm +++ b/platform/darwin/src/MGLShapeSource.mm @@ -1,4 +1,5 @@ #import "MGLShapeSource_Private.h" +#import "MGLAbstractShapeSource_Private.h" #import "MGLStyle_Private.h" #import "MGLMapView_Private.h" @@ -13,12 +14,7 @@ #include #include -const MGLShapeSourceOption MGLShapeSourceOptionClustered = @"MGLShapeSourceOptionClustered"; -const MGLShapeSourceOption MGLShapeSourceOptionClusterRadius = @"MGLShapeSourceOptionClusterRadius"; -const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevelForClustering = @"MGLShapeSourceOptionMaximumZoomLevelForClustering"; -const MGLShapeSourceOption MGLShapeSourceOptionMaximumZoomLevel = @"MGLShapeSourceOptionMaximumZoomLevel"; -const MGLShapeSourceOption MGLShapeSourceOptionBuffer = @"MGLShapeSourceOptionBuffer"; -const MGLShapeSourceOption MGLShapeSourceOptionSimplificationTolerance = @"MGLShapeSourceOptionSimplificationTolerance"; + @interface MGLShapeSource () @@ -105,57 +101,3 @@ - (NSString *)description { } @end - -mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options) { - auto geoJSONOptions = mbgl::style::GeoJSONOptions(); - - if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevel]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionMaximumZoomLevel must be an NSNumber."]; - } - geoJSONOptions.maxzoom = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionBuffer]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionBuffer must be an NSNumber."]; - } - geoJSONOptions.buffer = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionSimplificationTolerance]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionSimplificationTolerance must be an NSNumber."]; - } - geoJSONOptions.tolerance = value.doubleValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionClusterRadius]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionClusterRadius must be an NSNumber."]; - } - geoJSONOptions.clusterRadius = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionMaximumZoomLevelForClustering]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionMaximumZoomLevelForClustering must be an NSNumber."]; - } - geoJSONOptions.clusterMaxZoom = value.integerValue; - } - - if (NSNumber *value = options[MGLShapeSourceOptionClustered]) { - if (![value isKindOfClass:[NSNumber class]]) { - [NSException raise:NSInvalidArgumentException - format:@"MGLShapeSourceOptionClustered must be an NSNumber."]; - } - geoJSONOptions.cluster = value.boolValue; - } - - return geoJSONOptions; -} diff --git a/platform/darwin/src/MGLShapeSource_Private.h b/platform/darwin/src/MGLShapeSource_Private.h index 84eb5deed4d..1ea2c39b8e6 100644 --- a/platform/darwin/src/MGLShapeSource_Private.h +++ b/platform/darwin/src/MGLShapeSource_Private.h @@ -12,7 +12,4 @@ namespace mbgl { @interface MGLShapeSource (Private) @end -MGL_EXPORT -mbgl::style::GeoJSONOptions MGLGeoJSONOptionsFromDictionary(NS_DICTIONARY_OF(MGLShapeSourceOption, id) *options); - NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 244fb94ef9b..71f2785a948 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -221,7 +221,7 @@ - (MGLSource *)sourceWithIdentifier:(NSString *)identifier } - (MGLSource *)sourceFromMBGLSource:(mbgl::style::Source *)rawSource { - if (MGLSource *source = rawSource->peer.empty() ? nil : mbgl::any_cast(rawSource->peer).source) { + if (MGLSource *source = rawSource->peer.has_value() ? mbgl::util::any_cast(rawSource->peer).source : nil) { return source; } @@ -383,7 +383,7 @@ - (MGLStyleLayer *)layerFromMBGLLayer:(mbgl::style::Layer *)rawLayer { NSParameterAssert(rawLayer); - if (MGLStyleLayer *layer = rawLayer->peer.empty() ? nil : mbgl::any_cast(rawLayer->peer).layer) { + if (MGLStyleLayer *layer = rawLayer->peer.has_value() ? mbgl::util::any_cast(&(rawLayer->peer))->layer : nil) { return layer; } diff --git a/platform/darwin/test/MGLComputedShapeSourceTests.m b/platform/darwin/test/MGLComputedShapeSourceTests.m new file mode 100644 index 00000000000..6eb45913d68 --- /dev/null +++ b/platform/darwin/test/MGLComputedShapeSourceTests.m @@ -0,0 +1,24 @@ +#import + +#import + + +@interface MGLComputedShapeSourceTests : XCTestCase +@end + +@implementation MGLComputedShapeSourceTests + +- (void)testInitializer { + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"id" options:@{}]; + XCTAssertNotNil(source); + XCTAssertNotNil(source.requestQueue); + XCTAssertNil(source.dataSource); +} + +- (void)testNilOptions { + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"id" options:nil]; + XCTAssertNotNil(source); +} + + +@end diff --git a/platform/darwin/test/MGLShapeSourceTests.mm b/platform/darwin/test/MGLShapeSourceTests.mm index 561af7f3d04..60500959b6d 100644 --- a/platform/darwin/test/MGLShapeSourceTests.mm +++ b/platform/darwin/test/MGLShapeSourceTests.mm @@ -2,6 +2,7 @@ #import #import "MGLFeature_Private.h" +#import "MGLAbstractShapeSource_Private.h" #import "MGLShapeSource_Private.h" #import "MGLSource_Private.h" diff --git a/platform/default/mbgl/storage/offline_download.cpp b/platform/default/mbgl/storage/offline_download.cpp index ff61114888a..8bb16993a57 100644 --- a/platform/default/mbgl/storage/offline_download.cpp +++ b/platform/default/mbgl/storage/offline_download.cpp @@ -129,6 +129,7 @@ OfflineRegionStatus OfflineDownload::getStatus() const { case SourceType::Video: case SourceType::Annotations: + case SourceType::CustomVector: break; } } @@ -214,6 +215,7 @@ void OfflineDownload::activateDownload() { case SourceType::Video: case SourceType::Annotations: + case SourceType::CustomVector: break; } } diff --git a/platform/ios/CHANGELOG.md b/platform/ios/CHANGELOG.md index 4e4e6079712..c25b9adacd0 100644 --- a/platform/ios/CHANGELOG.md +++ b/platform/ios/CHANGELOG.md @@ -11,6 +11,7 @@ Mapbox welcomes participation and contributions from everyone. Please read [CONT ### Styles * Fixed an issue preventing a dynamically-added `MGLRasterStyleLayer` from drawing until the map pans. ([#10270](https://github.com/mapbox/mapbox-gl-native/pull/10270)) +* Added `MGLComputedShapeSource` source class that allows applications to supply vector data on a per-tile basis. ## 3.7.0 diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m index 2c3d26b4897..43063540305 100644 --- a/platform/ios/app/MBXViewController.m +++ b/platform/ios/app/MBXViewController.m @@ -75,6 +75,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsRuntimeStylingRows) { MBXSettingsRuntimeStylingImageSource, MBXSettingsRuntimeStylingRouteLine, MBXSettingsRuntimeStylingDDSPolygon, + MBXSettingsRuntimeStylingCustomLatLonGrid, }; typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) { @@ -111,7 +112,8 @@ @implementation MBXSpriteBackedAnnotation @interface MBXViewController () + MGLMapViewDelegate, + MGLComputedShapeSourceDataSource> @property (nonatomic) IBOutlet MGLMapView *mapView; @@ -353,6 +355,7 @@ - (void)dismissSettings:(__unused id)sender @"Style Image Source", @"Add Route Line", @"Dynamically Style Polygon", + @"Add Custom Lat/Lon Grid", ]]; break; case MBXSettingsMiscellaneous: @@ -529,6 +532,9 @@ - (void)performActionForSettingAtIndexPath:(NSIndexPath *)indexPath case MBXSettingsRuntimeStylingDDSPolygon: [self stylePolygonWithDDS]; break; + case MBXSettingsRuntimeStylingCustomLatLonGrid: + [self addLatLonGrid]; + break; default: NSAssert(NO, @"All runtime styling setting rows should be implemented"); break; @@ -1445,6 +1451,54 @@ - (void)stylePolygonWithDDS { [self.mapView.style addLayer:fillStyleLayer]; } +- (void)addLatLonGrid +{ + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"latlon" + options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + source.dataSource = self; + [self.mapView.style addSource:source]; + MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"latlonlines" + source:source]; + [self.mapView.style addLayer:lineLayer]; + MGLSymbolStyleLayer *labelLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"latlonlabels" + source:source]; + labelLayer.text = [MGLStyleValue valueWithRawValue:@"{value}"]; + [self.mapView.style addLayer:labelLayer]; +} + +- (void)styleLabelLanguageForLayersNamed:(NSArray *)layers +{ + _usingLocaleBasedCountryLabels = !_usingLocaleBasedCountryLabels; + NSString *bestLanguageForUser = [NSString stringWithFormat:@"{name_%@}", [self bestLanguageForUser]]; + NSString *language = _usingLocaleBasedCountryLabels ? bestLanguageForUser : @"{name}"; + + for (NSString *layerName in layers) { + MGLSymbolStyleLayer *layer = (MGLSymbolStyleLayer *)[self.mapView.style layerWithIdentifier:layerName]; + + if ([layer isKindOfClass:[MGLSymbolStyleLayer class]]) { + if ([layer.text isKindOfClass:[MGLStyleConstantValue class]]) { + MGLStyleConstantValue *label = (MGLStyleConstantValue *)layer.text; + if ([label.rawValue hasPrefix:@"{name"]) { + layer.text = [MGLStyleValue valueWithRawValue:language]; + } + } + else if ([layer.text isKindOfClass:[MGLCameraStyleFunction class]]) { + MGLCameraStyleFunction *function = (MGLCameraStyleFunction *)layer.text; + NSMutableDictionary *stops = function.stops.mutableCopy; + [stops enumerateKeysAndObjectsUsingBlock:^(NSNumber *zoomLevel, MGLStyleConstantValue *stop, BOOL *done) { + if ([stop.rawValue hasPrefix:@"{name"]) { + stops[zoomLevel] = [MGLStyleValue valueWithRawValue:language]; + } + }]; + function.stops = stops; + layer.text = function; + } + } else { + NSLog(@"%@ is not a symbol style layer", layerName); + } + } +} + - (NSString *)bestLanguageForUser { // https://www.mapbox.com/vector-tiles/mapbox-streets-v7/#overview @@ -1910,4 +1964,52 @@ - (void)updateHUD { [self.hudLabel setTitle:hudString forState:UIControlStateNormal]; } +#pragma mark - MGLComputedShapeSourceDataSource + +- (NSArray>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoom { + double gridSpacing; + if(zoom >= 13) { + gridSpacing = 0.01; + } else if(zoom >= 11) { + gridSpacing = 0.05; + } else if(zoom == 10) { + gridSpacing = .1; + } else if(zoom == 9) { + gridSpacing = 0.25; + } else if(zoom == 8) { + gridSpacing = 0.5; + } else if (zoom >= 6) { + gridSpacing = 1; + } else if(zoom == 5) { + gridSpacing = 2; + } else if(zoom >= 4) { + gridSpacing = 5; + } else if(zoom == 2) { + gridSpacing = 10; + } else { + gridSpacing = 20; + } + + NSMutableArray > * features = [NSMutableArray array]; + CLLocationCoordinate2D coords[2]; + + for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); + coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(y)}; + [features addObject:feature]; + } + + for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); + coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(x)}; + [features addObject:feature]; + } + + return features; +} + @end diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index ad17e006738..a5e35c5cc76 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -12,6 +12,14 @@ 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 */; }; + 0778DD431F67556700A73B34 /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 0778DD401F67555F00A73B34 /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 0778DD441F67556C00A73B34 /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0778DD411F67555F00A73B34 /* MGLComputedShapeSource.mm */; }; + 07D8C6FB1F67560100381808 /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0778DD411F67555F00A73B34 /* MGLComputedShapeSource.mm */; }; + 07D8C6FC1F67560400381808 /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07D947501F67487E00E37934 /* MGLAbstractShapeSource.mm */; }; + 07D8C6FF1F67562C00381808 /* MGLComputedShapeSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 07D8C6FD1F67562800381808 /* MGLComputedShapeSourceTests.m */; }; + 07D947521F67488800E37934 /* MGLAbstractShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D9474F1F67487E00E37934 /* MGLAbstractShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 07D947531F67488E00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D9474E1F67487E00E37934 /* MGLAbstractShapeSource_Private.h */; }; + 07D947541F67489200E37934 /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07D947501F67487E00E37934 /* MGLAbstractShapeSource.mm */; }; 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, ); }; }; @@ -576,6 +584,12 @@ 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 = ""; }; + 0778DD401F67555F00A73B34 /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = ""; }; + 0778DD411F67555F00A73B34 /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; sourceTree = ""; }; + 07D8C6FD1F67562800381808 /* MGLComputedShapeSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MGLComputedShapeSourceTests.m; path = ../../darwin/test/MGLComputedShapeSourceTests.m; sourceTree = ""; }; + 07D9474E1F67487E00E37934 /* MGLAbstractShapeSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource_Private.h; sourceTree = ""; }; + 07D9474F1F67487E00E37934 /* MGLAbstractShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource.h; sourceTree = ""; }; + 07D947501F67487E00E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; 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 = ""; }; @@ -1056,6 +1070,11 @@ 35136D491D4277EA00C20EFD /* Sources */ = { isa = PBXGroup; children = ( + 07D9474F1F67487E00E37934 /* MGLAbstractShapeSource.h */, + 07D9474E1F67487E00E37934 /* MGLAbstractShapeSource_Private.h */, + 07D947501F67487E00E37934 /* MGLAbstractShapeSource.mm */, + 0778DD401F67555F00A73B34 /* MGLComputedShapeSource.h */, + 0778DD411F67555F00A73B34 /* MGLComputedShapeSource.mm */, 071BBAFC1EE75CD4001FB02A /* MGLImageSource.h */, 071BBAFD1EE75CD4001FB02A /* MGLImageSource.mm */, 3566C76A1D4A8DFA008152BC /* MGLRasterSource.h */, @@ -1217,6 +1236,7 @@ 40CFA64E1D78754A008103BD /* Sources */ = { isa = PBXGroup; children = ( + 07D8C6FD1F67562800381808 /* MGLComputedShapeSourceTests.m */, 071BBB051EE7761A001FB02A /* MGLImageSourceTests.m */, 40CFA6501D787579008103BD /* MGLShapeSourceTests.mm */, 920A3E5C1E6F995200C16EFC /* MGLSourceQueryTests.m */, @@ -1738,6 +1758,7 @@ 357FE2DD1E02D2B20068B753 /* NSCoder+MGLAdditions.h in Headers */, 7E016D7E1D9E86BE00A29A21 /* MGLPolyline+MGLAdditions.h in Headers */, 35D13AB71D3D15E300AFB4E0 /* MGLStyleLayer.h in Headers */, + 07D947531F67488E00E37934 /* MGLAbstractShapeSource_Private.h in Headers */, DA88488E1CBB047F00AB86E3 /* reachability.h in Headers */, 40F887701D7A1E58008ECB67 /* MGLShapeSource_Private.h in Headers */, 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, @@ -1754,6 +1775,7 @@ DA88485A1CBAFB9800AB86E3 /* MGLUserLocation_Private.h in Headers */, 966FCF531F3C322400F2B6DE /* MGLUserLocationHeadingArrowLayer.h in Headers */, DA27C24F1CBB4C11000B0ECD /* MGLAccountManager_Private.h in Headers */, + 07D947521F67488800E37934 /* MGLAbstractShapeSource.h in Headers */, DA8847FC1CBAFA5100AB86E3 /* MGLStyle.h in Headers */, DD9BE4F71EB263C50079A3AF /* UIViewController+MGLAdditions.h in Headers */, DAF0D8131DFE0EC500B28378 /* MGLVectorSource_Private.h in Headers */, @@ -1777,6 +1799,7 @@ DD0902AB1DB192A800C5BDCE /* MGLNetworkConfiguration.h in Headers */, DA8848571CBAFB9800AB86E3 /* MGLMapboxEvents.h in Headers */, 35D3A1E61E9BE7EB002B38EE /* MGLScaleBar.h in Headers */, + 0778DD431F67556700A73B34 /* MGLComputedShapeSource.h in Headers */, DA8848311CBAFA6200AB86E3 /* NSString+MGLAdditions.h in Headers */, 353933F81D3FB79F003F57D7 /* MGLLineStyleLayer.h in Headers */, 92F2C3ED1F0E3C3A00268EC0 /* MGLRendererFrontend.h in Headers */, @@ -2288,6 +2311,7 @@ 409D0A0D1ED614CE00C95D0C /* MGLAnnotationViewIntegrationTests.swift in Sources */, DA2E88621CC0382C00F24E7B /* MGLOfflinePackTests.m in Sources */, 55E2AD131E5B125400E8C587 /* MGLOfflineStorageTests.mm in Sources */, + 07D8C6FF1F67562C00381808 /* MGLComputedShapeSourceTests.m in Sources */, 920A3E5D1E6F995200C16EFC /* MGLSourceQueryTests.m in Sources */, DA5DB12A1FABF1EE001C2326 /* MGLMapAccessibilityElementTests.m in Sources */, FAE1CDCB1E9D79CB00C40B5B /* MGLFillExtrusionStyleLayerTests.mm in Sources */, @@ -2342,6 +2366,7 @@ 35136D451D42275100C20EFD /* MGLSymbolStyleLayer.mm in Sources */, 35599DED1D46F14E0048254D /* MGLStyleValue.mm in Sources */, DA8848211CBAFA6200AB86E3 /* MGLOfflinePack.mm in Sources */, + 0778DD441F67556C00A73B34 /* MGLComputedShapeSource.mm in Sources */, 3557F7B21E1D27D300CCA5E6 /* MGLDistanceFormatter.m in Sources */, DA8848591CBAFB9800AB86E3 /* MGLMapView.mm in Sources */, DA8848501CBAFB9800AB86E3 /* MGLAnnotationImage.m in Sources */, @@ -2364,6 +2389,7 @@ DA88482B1CBAFA6200AB86E3 /* MGLTypes.m in Sources */, FA68F14D1E9D656600F9F6C2 /* MGLFillExtrusionStyleLayer.mm in Sources */, 404C26E41D89B877000AA13D /* MGLTileSource.mm in Sources */, + 07D947541F67489200E37934 /* MGLAbstractShapeSource.mm in Sources */, 355AE0011E9281DA00F3939D /* MGLScaleBar.mm in Sources */, DA88481D1CBAFA6200AB86E3 /* MGLMapCamera.mm in Sources */, DA8848261CBAFA6200AB86E3 /* MGLPolygon.mm in Sources */, @@ -2454,6 +2480,7 @@ 404C26E51D89B877000AA13D /* MGLTileSource.mm in Sources */, 355AE0021E9281DA00F3939D /* MGLScaleBar.mm in Sources */, 4018B1C81CDC287F00F666AF /* MGLAnnotationView.mm in Sources */, + 07D8C6FB1F67560100381808 /* MGLComputedShapeSource.mm in Sources */, DAA4E4341CBB730400178DFB /* MGLFaux3DUserLocationAnnotationView.m in Sources */, 35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, 7E016D871D9E890300A29A21 /* MGLPolygon+MGLAdditions.m in Sources */, @@ -2465,6 +2492,7 @@ DAA4E42A1CBB730400178DFB /* NSProcessInfo+MGLAdditions.m in Sources */, DAA4E4211CBB730400178DFB /* MGLOfflineStorage.mm in Sources */, 4049C2A01DB6CD6C00B3F799 /* MGLPointCollection.mm in Sources */, + 07D8C6FC1F67560400381808 /* MGLAbstractShapeSource.mm in Sources */, 35136D401D42273000C20EFD /* MGLLineStyleLayer.mm in Sources */, DA704CC51F65A475004B3F28 /* MGLMapAccessibilityElement.mm in Sources */, DA72620E1DEEE3480043BB89 /* MGLOpenGLStyleLayer.mm in Sources */, diff --git a/platform/ios/jazzy.yml b/platform/ios/jazzy.yml index ba56c312eb8..955d1079f92 100644 --- a/platform/ios/jazzy.yml +++ b/platform/ios/jazzy.yml @@ -76,7 +76,9 @@ custom_categories: - MGLSource - MGLTileSource - MGLImageSource + - MGLAbstractShapeSource - MGLShapeSource + - MGLComputedShapeSource - MGLRasterSource - MGLVectorSource - name: Style Layers diff --git a/platform/ios/src/Mapbox.h b/platform/ios/src/Mapbox.h index 9b2c472cf64..ce9c4965d7c 100644 --- a/platform/ios/src/Mapbox.h +++ b/platform/ios/src/Mapbox.h @@ -51,6 +51,8 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLTileSource.h" #import "MGLVectorSource.h" #import "MGLShapeSource.h" +#import "MGLAbstractShapeSource.h" +#import "MGLComputedShapeSource.h" #import "MGLRasterSource.h" #import "MGLImageSource.h" #import "MGLTilePyramidOfflineRegion.h" diff --git a/platform/macos/CHANGELOG.md b/platform/macos/CHANGELOG.md index 45a13dad6e2..4a5172be642 100644 --- a/platform/macos/CHANGELOG.md +++ b/platform/macos/CHANGELOG.md @@ -9,6 +9,7 @@ ### Styles * Fixed an issue preventing a dynamically-added `MGLRasterStyleLayer` from drawing until the map pans. ([#10270](https://github.com/mapbox/mapbox-gl-native/pull/10270)) +* Added `MGLComputedShapeSource` source class that allows applications to supply vector data on a per-tile basis. ## v0.6.0 @@ -37,11 +38,8 @@ * Increased the default maximum zoom level from 20 to 22. ([#9835](https://github.com/mapbox/mapbox-gl-native/pull/9835)) * Added an `overlays` property to `MGLMapView`. ([#8617](https://github.com/mapbox/mapbox-gl-native/pull/8617)) * Added `-[MGLMapView cameraThatFitsShape:direction:edgePadding:]` to get a camera with zoom level and center coordinate computed to fit a shape. ([#10107](https://github.com/mapbox/mapbox-gl-native/pull/10107)) -<<<<<<< HEAD -======= * Added support selection of shape and polyline annotations.([#9984](https://github.com/mapbox/mapbox-gl-native/pull/9984)) * Fixed an issue where a shape annotation callout was not displayed if the centroid was not visible. ([#10255](https://github.com/mapbox/mapbox-gl-native/pull/10255)) ->>>>>>> release-agua ### Other changes diff --git a/platform/macos/app/Base.lproj/MainMenu.xib b/platform/macos/app/Base.lproj/MainMenu.xib index 32438388481..d014676caa6 100644 --- a/platform/macos/app/Base.lproj/MainMenu.xib +++ b/platform/macos/app/Base.lproj/MainMenu.xib @@ -564,6 +564,12 @@ + + + + + + @@ -574,8 +580,8 @@ -CA - + CA + diff --git a/platform/macos/app/MapDocument.m b/platform/macos/app/MapDocument.m index 0df6b105184..8f23a0248a2 100644 --- a/platform/macos/app/MapDocument.m +++ b/platform/macos/app/MapDocument.m @@ -50,7 +50,7 @@ return flattenedShapes; } -@interface MapDocument () +@interface MapDocument () @property (weak) IBOutlet NSArrayController *styleLayersArrayController; @property (weak) IBOutlet NSTableView *styleLayersTableView; @@ -698,6 +698,47 @@ - (IBAction)removeCustomStyleLayer:(id)sender { [self.mapView.style removeLayer:layer]; } +- (IBAction)insertGraticuleLayer:(id)sender { + [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { + [self removeGraticuleLayer:sender]; + }]; + + if (!self.undoManager.isUndoing) { + [self.undoManager setActionName:@"Add Graticule"]; + } + + MGLComputedShapeSource *source = [[MGLComputedShapeSource alloc] initWithIdentifier:@"graticule" + options:@{MGLShapeSourceOptionMaximumZoomLevel:@14}]; + source.dataSource = self; + [self.mapView.style addSource:source]; + MGLLineStyleLayer *lineLayer = [[MGLLineStyleLayer alloc] initWithIdentifier:@"graticule.lines" + source:source]; + [self.mapView.style addLayer:lineLayer]; + MGLSymbolStyleLayer *labelLayer = [[MGLSymbolStyleLayer alloc] initWithIdentifier:@"graticule.labels" + source:source]; + labelLayer.text = [MGLStyleValue valueWithRawValue:@"{value}"]; + [self.mapView.style addLayer:labelLayer]; +} + +- (IBAction)removeGraticuleLayer:(id)sender { + [self.undoManager registerUndoWithTarget:self handler:^(id _Nonnull target) { + [self insertGraticuleLayer:sender]; + }]; + + if (!self.undoManager.isUndoing) { + [self.undoManager setActionName:@"Delete Graticule"]; + } + + MGLStyleLayer *layer = [self.mapView.style layerWithIdentifier:@"graticule.lines"]; + [self.mapView.style removeLayer:layer]; + + layer = [self.mapView.style layerWithIdentifier:@"graticule.labels"]; + [self.mapView.style removeLayer:layer]; + + MGLSource *source = [self.mapView.style sourceWithIdentifier:@"graticule"]; + [self.mapView.style removeSource:source]; +} + #pragma mark Offline packs - (IBAction)addOfflinePack:(id)sender { @@ -1011,6 +1052,9 @@ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if (menuItem.action == @selector(insertCustomStyleLayer:)) { return ![self.mapView.style layerWithIdentifier:@"mbx-custom"]; } + if (menuItem.action == @selector(insertGraticuleLayer:)) { + return ![self.mapView.style sourceWithIdentifier:@"graticule"]; + } if (menuItem.action == @selector(showAllAnnotations:) || menuItem.action == @selector(removeAllAnnotations:)) { return self.mapView.annotations.count > 0; } @@ -1185,6 +1229,53 @@ - (CGFloat)mapView:(MGLMapView *)mapView alphaForShapeAnnotation:(MGLShape *)ann return 0.8; } +#pragma mark - MGLComputedShapeSourceDataSource +- (NSArray>*)featuresInCoordinateBounds:(MGLCoordinateBounds)bounds zoomLevel:(NSUInteger)zoom { + double gridSpacing; + if(zoom >= 13) { + gridSpacing = 0.01; + } else if(zoom >= 11) { + gridSpacing = 0.05; + } else if(zoom == 10) { + gridSpacing = .1; + } else if(zoom == 9) { + gridSpacing = 0.25; + } else if(zoom == 8) { + gridSpacing = 0.5; + } else if (zoom >= 6) { + gridSpacing = 1; + } else if(zoom == 5) { + gridSpacing = 2; + } else if(zoom >= 4) { + gridSpacing = 5; + } else if(zoom == 2) { + gridSpacing = 10; + } else { + gridSpacing = 20; + } + + NSMutableArray > * features = [NSMutableArray array]; + CLLocationCoordinate2D coords[2]; + + for (double y = ceil(bounds.ne.latitude / gridSpacing) * gridSpacing; y >= floor(bounds.sw.latitude / gridSpacing) * gridSpacing; y -= gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(y, bounds.sw.longitude); + coords[1] = CLLocationCoordinate2DMake(y, bounds.ne.longitude); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(y)}; + [features addObject:feature]; + } + + for (double x = floor(bounds.sw.longitude / gridSpacing) * gridSpacing; x <= ceil(bounds.ne.longitude / gridSpacing) * gridSpacing; x += gridSpacing) { + coords[0] = CLLocationCoordinate2DMake(bounds.sw.latitude, x); + coords[1] = CLLocationCoordinate2DMake(bounds.ne.latitude, x); + MGLPolylineFeature *feature = [MGLPolylineFeature polylineWithCoordinates:coords count:2]; + feature.attributes = @{@"value": @(x)}; + [features addObject:feature]; + } + + return features; +} + @end @interface ValidatedToolbarItem : NSToolbarItem diff --git a/platform/macos/jazzy.yml b/platform/macos/jazzy.yml index fcd4bfd3019..80c2a4694f4 100644 --- a/platform/macos/jazzy.yml +++ b/platform/macos/jazzy.yml @@ -61,7 +61,9 @@ custom_categories: children: - MGLSource - MGLTileSource + - MGLAbstractShapeSource - MGLShapeSource + - MGLComputedShapeSource - MGLRasterSource - MGLVectorSource - name: Style Layers diff --git a/platform/macos/macos.xcodeproj/project.pbxproj b/platform/macos/macos.xcodeproj/project.pbxproj index c839bfadd34..eabf669ce88 100644 --- a/platform/macos/macos.xcodeproj/project.pbxproj +++ b/platform/macos/macos.xcodeproj/project.pbxproj @@ -9,7 +9,13 @@ /* 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 */; }; + 07A240941F675674002C8210 /* MGLComputedShapeSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 07A240921F67566F002C8210 /* MGLComputedShapeSourceTests.m */; }; 07BA4CAC1EE21887004528F5 /* MGLImageSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 07BA4CAB1EE21887004528F5 /* MGLImageSourceTests.m */; }; + 07D9474B1F6743F000E37934 /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */; }; + 07D9474C1F67441500E37934 /* MGLAbstractShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947481F6741F500E37934 /* MGLAbstractShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */; }; + 07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */; }; 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, ); }; }; @@ -282,7 +288,13 @@ /* 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 = ""; }; + 07A240921F67566F002C8210 /* MGLComputedShapeSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLComputedShapeSourceTests.m; sourceTree = ""; }; 07BA4CAB1EE21887004528F5 /* MGLImageSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MGLImageSourceTests.m; sourceTree = ""; }; + 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource_Private.h; sourceTree = ""; }; + 07D947481F6741F500E37934 /* MGLAbstractShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource.h; sourceTree = ""; }; + 07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = ""; }; + 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLComputedShapeSource.h; sourceTree = ""; }; + 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLComputedShapeSource.mm; 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 = ""; }; @@ -681,6 +693,11 @@ 3527427E1D4C242B00A1ECE6 /* Sources */ = { isa = PBXGroup; children = ( + 07D947481F6741F500E37934 /* MGLAbstractShapeSource.h */, + 07D947471F6741F500E37934 /* MGLAbstractShapeSource_Private.h */, + 07D947491F6741F500E37934 /* MGLAbstractShapeSource.mm */, + 07F8E2F41F674C8000F794BB /* MGLComputedShapeSource.h */, + 07F8E2F51F674C8000F794BB /* MGLComputedShapeSource.mm */, 352742831D4C244700A1ECE6 /* MGLRasterSource.h */, DA7DC9821DED647F0027472F /* MGLRasterSource_Private.h */, 352742841D4C244700A1ECE6 /* MGLRasterSource.mm */, @@ -812,6 +829,7 @@ DA87A99A1DC9D88800810D09 /* Sources */ = { isa = PBXGroup; children = ( + 07A240921F67566F002C8210 /* MGLComputedShapeSourceTests.m */, DA87A9961DC9D88400810D09 /* MGLShapeSourceTests.mm */, DA87A9971DC9D88400810D09 /* MGLTileSetTests.mm */, 920A3E581E6F859D00C16EFC /* MGLSourceQueryTests.m */, @@ -1134,6 +1152,7 @@ buildActionMask = 2147483647; files = ( 556660C61E1BEA0100E2C41B /* MGLFoundation.h in Headers */, + 07F8E2F71F674C8800F794BB /* MGLComputedShapeSource.h in Headers */, DA8F258F1D51CA600010E6B5 /* MGLRasterStyleLayer.h in Headers */, 3508EC641D749D39009B0EE4 /* NSExpression+MGLAdditions.h in Headers */, DAE6C38D1CC31E2A00DB3429 /* MGLOfflineRegion_Private.h in Headers */, @@ -1171,6 +1190,7 @@ DAE6C3861CC31E2A00DB3429 /* MGLGeometry_Private.h in Headers */, DAE6C3841CC31E2A00DB3429 /* MGLAccountManager_Private.h in Headers */, DAE6C3691CC31E0400DB3429 /* MGLTypes.h in Headers */, + 07D9474D1F67441B00E37934 /* MGLAbstractShapeSource_Private.h in Headers */, DAE6C3991CC31E2A00DB3429 /* NSException+MGLAdditions.h in Headers */, DA8F25871D51C9E10010E6B5 /* MGLBackgroundStyleLayer.h in Headers */, 4049C2A51DB6CE7F00B3F799 /* MGLPointCollection.h in Headers */, @@ -1185,6 +1205,7 @@ 35602BFA1D3EA99F0050646F /* MGLFillStyleLayer.h in Headers */, DA35A2A41CC9EB1A00E826B2 /* MGLCoordinateFormatter.h in Headers */, 35C5D8491D6DD66D00E95907 /* NSCompoundPredicate+MGLAdditions.h in Headers */, + 07D9474C1F67441500E37934 /* MGLAbstractShapeSource.h in Headers */, DD0902B31DB1AC6400C5BDCE /* MGLNetworkConfiguration.h in Headers */, DAE6C3621CC31E0400DB3429 /* MGLOverlay.h in Headers */, DAE6C3651CC31E0400DB3429 /* MGLPolyline.h in Headers */, @@ -1461,6 +1482,7 @@ 558DE7A71E56161C00C7916D /* MGLFoundation.mm in Sources */, DAE6C39D1CC31E2A00DB3429 /* NSString+MGLAdditions.m in Sources */, 3598195A1E02F611008FC139 /* NSCoder+MGLAdditions.mm in Sources */, + 07D9474B1F6743F000E37934 /* MGLAbstractShapeSource.mm in Sources */, DAE6C3941CC31E2A00DB3429 /* MGLStyle.mm in Sources */, DAE6C3871CC31E2A00DB3429 /* MGLGeometry.mm in Sources */, 3527428E1D4C24AB00A1ECE6 /* MGLCircleStyleLayer.mm in Sources */, @@ -1472,6 +1494,7 @@ DA35A2D01CCAAED300E826B2 /* NSValue+MGLAdditions.m in Sources */, 3538AA241D542685008EC33D /* MGLStyleLayer.mm in Sources */, DA35A2C01CCA9B1A00E826B2 /* MGLClockDirectionFormatter.m in Sources */, + 07F8E2F81F674C9000F794BB /* MGLComputedShapeSource.mm in Sources */, DAE6C3BA1CC31EF300DB3429 /* MGLOpenGLLayer.mm in Sources */, DAE6C38A1CC31E2A00DB3429 /* MGLMultiPoint.mm in Sources */, DAE6C3961CC31E2A00DB3429 /* MGLTypes.m in Sources */, @@ -1516,6 +1539,7 @@ DAE6C3D41CC34C9900DB3429 /* MGLOfflineRegionTests.m in Sources */, DAE6C3D61CC34C9900DB3429 /* MGLStyleTests.mm in Sources */, 1F7454AB1ED1DDBD00021D39 /* MGLLightTest.mm in Sources */, + 07A240941F675674002C8210 /* MGLComputedShapeSourceTests.m in Sources */, DAEDC4371D606291000224FF /* MGLAttributionButtonTests.m in Sources */, 920A3E591E6F859D00C16EFC /* MGLSourceQueryTests.m in Sources */, DA35A2B61CCA14D700E826B2 /* MGLCompassDirectionFormatterTests.m in Sources */, diff --git a/platform/macos/src/Mapbox.h b/platform/macos/src/Mapbox.h index a082a4771e6..26c67d55500 100644 --- a/platform/macos/src/Mapbox.h +++ b/platform/macos/src/Mapbox.h @@ -49,6 +49,8 @@ FOUNDATION_EXPORT MGL_EXPORT const unsigned char MapboxVersionString[]; #import "MGLTileSource.h" #import "MGLVectorSource.h" #import "MGLShapeSource.h" +#import "MGLAbstractShapeSource.h" +#import "MGLComputedShapeSource.h" #import "MGLRasterSource.h" #import "MGLImageSource.h" #import "MGLTilePyramidOfflineRegion.h" diff --git a/src/mbgl/renderer/render_source.cpp b/src/mbgl/renderer/render_source.cpp index 7723a1c7ca5..6624bb7d964 100644 --- a/src/mbgl/renderer/render_source.cpp +++ b/src/mbgl/renderer/render_source.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace mbgl { @@ -27,6 +28,8 @@ std::unique_ptr RenderSource::create(Immutable impl) return std::make_unique(staticImmutableCast(impl)); case SourceType::Image: return std::make_unique(staticImmutableCast(impl)); + case SourceType::CustomVector: + return std::make_unique(staticImmutableCast(impl)); } // Not reachable, but placate GCC. diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.cpp b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp new file mode 100644 index 00000000000..111f0234edb --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include +#include + +namespace mbgl { + +using namespace style; + +RenderCustomGeometrySource::RenderCustomGeometrySource(Immutable impl_) + : RenderSource(impl_) { + tilePyramid.setObserver(this); +} + +const style::CustomGeometrySource::Impl& RenderCustomGeometrySource::impl() const { + return static_cast(*baseImpl); +} + +bool RenderCustomGeometrySource::isLoaded() const { + return tilePyramid.isLoaded(); +} + +void RenderCustomGeometrySource::update(Immutable baseImpl_, + const std::vector>& layers, + const bool needsRendering, + const bool needsRelayout, + const TileParameters& parameters) { + std::swap(baseImpl, baseImpl_); + + enabled = needsRendering; + + auto tileLoader = impl().getTileLoader(); + if (!tileLoader) { + return; + } + + tilePyramid.update(layers, + needsRendering, + needsRelayout, + parameters, + SourceType::CustomVector, + util::tileSize, + impl().getZoomRange(), + [&] (const OverscaledTileID& tileID) { + return std::make_unique(tileID, impl().id, parameters, impl().getTileOptions(), *tileLoader); + }); +} + +void RenderCustomGeometrySource::startRender(PaintParameters& parameters) { + parameters.clipIDGenerator.update(tilePyramid.getRenderTiles()); + tilePyramid.startRender(parameters); +} + +void RenderCustomGeometrySource::finishRender(PaintParameters& parameters) { + tilePyramid.finishRender(parameters); +} + +std::vector> RenderCustomGeometrySource::getRenderTiles() { + return tilePyramid.getRenderTiles(); +} + +std::unordered_map> +RenderCustomGeometrySource::queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const { + return tilePyramid.queryRenderedFeatures(geometry, transformState, layers, options, collisionIndex); +} + +std::vector RenderCustomGeometrySource::querySourceFeatures(const SourceQueryOptions& options) const { + return tilePyramid.querySourceFeatures(options); +} + +void RenderCustomGeometrySource::onLowMemory() { + tilePyramid.onLowMemory(); +} + +void RenderCustomGeometrySource::dumpDebugLogs() const { + tilePyramid.dumpDebugLogs(); +} + +} // namespace mbgl diff --git a/src/mbgl/renderer/sources/render_custom_geometry_source.hpp b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp new file mode 100644 index 00000000000..82e691d5c9b --- /dev/null +++ b/src/mbgl/renderer/sources/render_custom_geometry_source.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +class RenderCustomGeometrySource : public RenderSource { +public: + RenderCustomGeometrySource(Immutable); + + bool isLoaded() const final; + + void update(Immutable, + const std::vector>&, + bool needsRendering, + bool needsRelayout, + const TileParameters&) final; + + void startRender(PaintParameters&) final; + void finishRender(PaintParameters&) final; + + std::vector> getRenderTiles() final; + + std::unordered_map> + queryRenderedFeatures(const ScreenLineString& geometry, + const TransformState& transformState, + const std::vector& layers, + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) const final; + + std::vector + querySourceFeatures(const SourceQueryOptions&) const final; + + void onLowMemory() final; + void dumpDebugLogs() const final; + +private: + const style::CustomGeometrySource::Impl& impl() const; + + TilePyramid tilePyramid; +}; + +template <> +inline bool RenderSource::is() const { + return baseImpl->type == style::SourceType::CustomVector; +} + +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.cpp b/src/mbgl/style/custom_tile_loader.cpp new file mode 100644 index 00000000000..d5bebf00862 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.cpp @@ -0,0 +1,107 @@ +#include + +namespace mbgl { +namespace style { + +CustomTileLoader::CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn) { + fetchTileFunction = fetchTileFn; + cancelTileFunction = cancelTileFn; +} + +void CustomTileLoader::fetchTile(const OverscaledTileID& tileID, ActorRef callbackRef) { + auto cachedTileData = dataCache.find(tileID.canonical); + if (cachedTileData != dataCache.end()) { + callbackRef.invoke(&SetTileDataFunction::operator(), *(cachedTileData->second)); + } + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) { + auto tuple = std::make_tuple(tileID.overscaledZ, tileID.wrap, callbackRef); + tileCallbackMap.insert({ tileID.canonical, std::vector(1, tuple) }); + } else { + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + std::get<2>(*iter) = callbackRef; + return; + } + } + tileCallbacks->second.emplace_back(std::make_tuple(tileID.overscaledZ, tileID.wrap, callbackRef)); + } + if (cachedTileData == dataCache.end()) { + invokeTileFetch(tileID.canonical); + } +} + +void CustomTileLoader::cancelTile(const OverscaledTileID& tileID) { + if (tileCallbackMap.find(tileID.canonical) != tileCallbackMap.end()) { + invokeTileCancel(tileID.canonical); + } +} + +void CustomTileLoader::removeTile(const OverscaledTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID.canonical); + if (tileCallbacks == tileCallbackMap.end()) return; + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + if (std::get<0>(*iter) == tileID.overscaledZ && std::get<1>(*iter) == tileID.wrap ) { + tileCallbacks->second.erase(iter); + invokeTileCancel(tileID.canonical); + break; + } + } + if (tileCallbacks->second.size() == 0) { + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID.canonical); + } +} + +void CustomTileLoader::setTileData(const CanonicalTileID& tileID, const GeoJSON& data) { + + auto iter = tileCallbackMap.find(tileID); + if (iter == tileCallbackMap.end()) return; + dataCache[tileID] = std::make_unique(std::move(data));; + for (auto tuple : iter->second) { + auto actor = std::get<2>(tuple); + actor.invoke(&SetTileDataFunction::operator(), data); + } +} + +void CustomTileLoader::invalidateTile(const CanonicalTileID& tileID) { + auto tileCallbacks = tileCallbackMap.find(tileID); + if (tileCallbacks == tileCallbackMap.end()) { return; } + for (auto iter = tileCallbacks->second.begin(); iter != tileCallbacks->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&SetTileDataFunction::operator(), mapbox::geojson::feature_collection()); + invokeTileCancel(tileID); + } + tileCallbackMap.erase(tileCallbacks); + dataCache.erase(tileID); +} + +void CustomTileLoader::invalidateRegion(const LatLngBounds& bounds, Range ) { + for (auto idtuple= tileCallbackMap.begin(); idtuple != tileCallbackMap.end(); idtuple++) { + const LatLngBounds tileBounds(idtuple->first); + if (tileBounds.intersects(bounds) || bounds.contains(tileBounds) || tileBounds.contains(bounds)) { + for (auto iter = idtuple->second.begin(); iter != idtuple->second.end(); iter++) { + auto actor = std::get<2>(*iter); + actor.invoke(&SetTileDataFunction::operator(), mapbox::geojson::feature_collection()); + invokeTileCancel(idtuple->first); + dataCache.erase(idtuple->first); + } + idtuple->second.clear(); + } + } +} + +void CustomTileLoader::invokeTileFetch(const CanonicalTileID& tileID) { + if (fetchTileFunction != nullptr) { + fetchTileFunction(tileID); + } +} + +void CustomTileLoader::invokeTileCancel(const CanonicalTileID& tileID) { + if (cancelTileFunction != nullptr) { + cancelTileFunction(tileID); + } +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/custom_tile_loader.hpp b/src/mbgl/style/custom_tile_loader.hpp new file mode 100644 index 00000000000..149da69cfa0 --- /dev/null +++ b/src/mbgl/style/custom_tile_loader.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace mbgl { +namespace style { + +using SetTileDataFunction = std::function; + +class CustomTileLoader : private util::noncopyable { +public: + + using OverscaledIDFunctionTuple = std::tuple>; + + CustomTileLoader(const TileFunction& fetchTileFn, const TileFunction& cancelTileFn); + + void fetchTile(const OverscaledTileID& tileID, ActorRef callbackRef); + void cancelTile(const OverscaledTileID& tileID); + + void removeTile(const OverscaledTileID& tileID); + void setTileData(const CanonicalTileID& tileID, const GeoJSON& data); + + void invalidateTile(const CanonicalTileID&); + void invalidateRegion(const LatLngBounds&, Range); + +private: + void invokeTileFetch(const CanonicalTileID& tileID); + void invokeTileCancel(const CanonicalTileID& tileID); + + TileFunction fetchTileFunction; + TileFunction cancelTileFunction; + std::unordered_map> tileCallbackMap; + // Keep around a cache of tile data to serve back for wrapped and over-zooomed tiles + std::map> dataCache; + +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source.cpp b/src/mbgl/style/sources/custom_geometry_source.cpp new file mode 100644 index 00000000000..ab46843d380 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +CustomGeometrySource::CustomGeometrySource(std::string id, + const CustomGeometrySource::Options options) + : Source(makeMutable(std::move(id), options)), + loader(std::make_unique>(*sharedThreadPool(), options.fetchTileFunction, options.cancelTileFunction)) { +} + +CustomGeometrySource::~CustomGeometrySource() = default; + +const CustomGeometrySource::Impl& CustomGeometrySource::impl() const { + return static_cast(*baseImpl); +} + +void CustomGeometrySource::loadDescription(FileSource&) { + baseImpl = makeMutable(impl(), loader->self()); + loaded = true; +} + +void CustomGeometrySource::setTileData(const CanonicalTileID& tileID, + const GeoJSON& data) { + loader->invoke(&CustomTileLoader::setTileData, tileID, data); +} + +void CustomGeometrySource::invalidateTile(const CanonicalTileID& tileID) { + loader->invoke(&CustomTileLoader::invalidateTile, tileID); +} + +void CustomGeometrySource::invalidateRegion(const LatLngBounds& bounds) { + loader->invoke(&CustomTileLoader::invalidateRegion, bounds, impl().getZoomRange()); +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.cpp b/src/mbgl/style/sources/custom_geometry_source_impl.cpp new file mode 100644 index 00000000000..67d52bdc246 --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.cpp @@ -0,0 +1,40 @@ +#include +#include + +namespace mbgl { +namespace style { + +CustomGeometrySource::Impl::Impl(std::string id_, + const CustomGeometrySource::Options options) + : Source::Impl(SourceType::CustomVector, std::move(id_)), + tileOptions(options.tileOptions), + zoomRange(options.zoomRange), + loaderRef({}) { +} + +CustomGeometrySource::Impl::Impl(const Impl& impl, ActorRef loaderRef_) + : Source::Impl(impl), + tileOptions(impl.tileOptions), + zoomRange(impl.zoomRange), + loaderRef(loaderRef_){ + +} + +optional CustomGeometrySource::Impl::getAttribution() const { + return {}; +} + +CustomGeometrySource::TileOptions CustomGeometrySource::Impl::getTileOptions() const { + return tileOptions; +} + +Range CustomGeometrySource::Impl::getZoomRange() const { + return zoomRange; +} + +optional> CustomGeometrySource::Impl::getTileLoader() const { + return loaderRef; +} + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/sources/custom_geometry_source_impl.hpp b/src/mbgl/style/sources/custom_geometry_source_impl.hpp new file mode 100644 index 00000000000..ce7187202dc --- /dev/null +++ b/src/mbgl/style/sources/custom_geometry_source_impl.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +namespace mbgl { +namespace style { + +class CustomGeometrySource::Impl : public Source::Impl { +public: + Impl(std::string id, CustomGeometrySource::Options options); + Impl(const Impl&, ActorRef); + + optional getAttribution() const final; + + CustomGeometrySource::TileOptions getTileOptions() const; + Range getZoomRange() const; + optional> getTileLoader() const; + +private: + CustomGeometrySource::TileOptions tileOptions; + Range zoomRange; + optional> loaderRef; +}; + +} // namespace style +} // namespace mbgl diff --git a/src/mbgl/style/types.cpp b/src/mbgl/style/types.cpp index 0a1781e01b2..cd5e597fc09 100644 --- a/src/mbgl/style/types.cpp +++ b/src/mbgl/style/types.cpp @@ -12,6 +12,7 @@ MBGL_DEFINE_ENUM(SourceType, { { SourceType::Video, "video" }, { SourceType::Annotations, "annotations" }, { SourceType::Image, "image" }, + { SourceType::CustomVector, "customvector" } }); MBGL_DEFINE_ENUM(VisibilityType, { diff --git a/src/mbgl/tile/custom_geometry_tile.cpp b/src/mbgl/tile/custom_geometry_tile.cpp new file mode 100644 index 00000000000..0d0ff5be613 --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include + +#include + +namespace mbgl { + +CustomGeometryTile::CustomGeometryTile(const OverscaledTileID& overscaledTileID, + std::string sourceID_, + const TileParameters& parameters, + const style::CustomGeometrySource::TileOptions options_, + ActorRef loader_) + : GeometryTile(overscaledTileID, sourceID_, parameters), + necessity(TileNecessity::Optional), + options(options_), + loader(loader_), + actor(*Scheduler::GetCurrent(), std::bind(&CustomGeometryTile::setTileData, this, std::placeholders::_1)) { +} + +CustomGeometryTile::~CustomGeometryTile() { + loader.invoke(&style::CustomTileLoader::removeTile, id); +} + +void CustomGeometryTile::setTileData(const GeoJSON& geoJSON) { + + auto featureData = mapbox::geometry::feature_collection(); + if (geoJSON.is() && !geoJSON.get().empty()) { + const double scale = util::EXTENT / options.tileSize; + + mapbox::geojsonvt::TileOptions vtOptions; + vtOptions.extent = util::EXTENT; + vtOptions.buffer = std::round(scale * options.buffer); + vtOptions.tolerance = scale * options.tolerance; + featureData = mapbox::geojsonvt::geoJSONToTile(geoJSON, id.canonical.z, id.canonical.x, id.canonical.y, vtOptions).features; + } else { + setNecessity(TileNecessity::Optional); + } + setData(std::make_unique(std::move(featureData))); +} + +//Fetching tile data for custom sources is assumed to be an expensive operation. +// Only required tiles make fetchTile requests. Attempt to cancel a tile +// that is no longer required. +void CustomGeometryTile::setNecessity(TileNecessity newNecessity) { + if (newNecessity != necessity) { + necessity = newNecessity; + if (necessity == TileNecessity::Required) { + loader.invoke(&style::CustomTileLoader::fetchTile, id, actor.self()); + } else if (!isRenderable()) { + loader.invoke(&style::CustomTileLoader::cancelTile, id); + } + } +} + +void CustomGeometryTile::querySourceFeatures( + std::vector& result, + const SourceQueryOptions& queryOptions) { + + // Ignore the sourceLayer, there is only one + auto layer = getData()->getLayer({}); + + if (layer) { + auto featureCount = layer->featureCount(); + for (std::size_t i = 0; i < featureCount; i++) { + auto feature = layer->getFeature(i); + + // Apply filter, if any + if (queryOptions.filter && !(*queryOptions.filter)(*feature)) { + continue; + } + + result.push_back(convertFeature(*feature, id.canonical)); + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/tile/custom_geometry_tile.hpp b/src/mbgl/tile/custom_geometry_tile.hpp new file mode 100644 index 00000000000..66cc412e8c6 --- /dev/null +++ b/src/mbgl/tile/custom_geometry_tile.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +namespace mbgl { + +class TileParameters; + +class CustomGeometryTile: public GeometryTile { +public: + CustomGeometryTile(const OverscaledTileID&, + std::string sourceID, + const TileParameters&, + const style::CustomGeometrySource::TileOptions, + ActorRef loader); + ~CustomGeometryTile() override; + void setTileData(const GeoJSON& data); + + void setNecessity(TileNecessity) final; + + void querySourceFeatures( + std::vector& result, + const SourceQueryOptions&) override; + +private: + TileNecessity necessity; + const style::CustomGeometrySource::TileOptions options; + ActorRef loader; + Actor actor; +}; + +} // namespace mbgl diff --git a/test/api/custom_geometry_source.test.cpp b/test/api/custom_geometry_source.test.cpp new file mode 100644 index 00000000000..83d1543a0a9 --- /dev/null +++ b/test/api/custom_geometry_source.test.cpp @@ -0,0 +1,71 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace mbgl; +using namespace mbgl::style; + +TEST(CustomGeometrySource, Grid) { + util::RunLoop loop; + + DefaultFileSource fileSource(":memory:", "test/fixtures/api/assets"); + auto threadPool = sharedThreadPool(); + float pixelRatio { 1 }; + HeadlessFrontend frontend { pixelRatio, fileSource, *threadPool }; + Map map(frontend, MapObserver::nullObserver(), frontend.getSize(), pixelRatio, fileSource, + *threadPool, MapMode::Static); + map.getStyle().loadJSON(util::read_file("test/fixtures/api/water.json")); + map.setLatLngZoom({ 37.8, -122.5 }, 10); + + CustomGeometrySource::Options options; + options.fetchTileFunction = [&map](const mbgl::CanonicalTileID& tileID) { + double gridSpacing = 0.1; + FeatureCollection features; + const LatLngBounds bounds(tileID); + for (double y = ceil(bounds.north() / gridSpacing) * gridSpacing; y >= floor(bounds.south() / gridSpacing) * gridSpacing; y -= gridSpacing) { + + mapbox::geojson::line_string gridLine; + gridLine.emplace_back(bounds.west(), y); + gridLine.emplace_back(bounds.east(), y); + + features.emplace_back(gridLine); + } + + for (double x = floor(bounds.west() / gridSpacing) * gridSpacing; x <= ceil(bounds.east() / gridSpacing) * gridSpacing; x += gridSpacing) { + mapbox::geojson::line_string gridLine; + gridLine.emplace_back(x, bounds.south()); + gridLine.emplace_back(x, bounds.north()); + + features.emplace_back(gridLine); + } + auto source = static_cast(map.getStyle().getSource("custom")); + if (source) { + source->setTileData(tileID, features); + } + }; + + map.getStyle().addSource(std::make_unique("custom", options)); + + auto fillLayer = std::make_unique("landcover", "mapbox"); + fillLayer->setSourceLayer("landcover"); + fillLayer->setFillColor(Color{ 1.0, 1.0, 0.0, 1.0 }); + map.getStyle().addLayer(std::move(fillLayer)); + + auto layer = std::make_unique("grid", "custom"); + layer->setLineColor(Color{ 1.0, 1.0, 1.0, 1.0 }); + map.getStyle().addLayer(std::move(layer)); + + test::checkImage("test/fixtures/custom_geometry_source/grid", frontend.render(map), 0.0006, 0.1); +} diff --git a/test/fixtures/custom_geometry_source/grid/expected.png b/test/fixtures/custom_geometry_source/grid/expected.png new file mode 100644 index 00000000000..628a3b11fb9 Binary files /dev/null and b/test/fixtures/custom_geometry_source/grid/expected.png differ diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index bee1b867b83..eb419e80802 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -24,7 +25,7 @@ #include #include -#include +#include #include #include #include @@ -548,3 +549,39 @@ TEST(Source, ImageSourceImageUpdate) { test.run(); } + +TEST(Source, CustomGeometrySourceSetTileData) { + SourceTest test; + std::shared_ptr threadPool = sharedThreadPool(); + CustomGeometrySource source("source", CustomGeometrySource::Options()); + source.loadDescription(test.fileSource); + + LineLayer layer("id", "source"); + layer.setSourceLayer("water"); + std::vector> layers {{ layer.baseImpl }}; + + test.renderSourceObserver.tileChanged = [&] (RenderSource& source_, const OverscaledTileID&) { + EXPECT_EQ("source", source_.baseImpl->id); + test.end(); + }; + + test.renderSourceObserver.tileError = [&] (RenderSource&, const OverscaledTileID&, std::exception_ptr) { + FAIL() << "Should never be called"; + }; + + auto renderSource = RenderSource::create(source.baseImpl); + renderSource->setObserver(&test.renderSourceObserver); + renderSource->update(source.baseImpl, + layers, + true, + true, + test.tileParameters); + + test.loop.invoke([&] () { + // Set Tile Data + source.setTileData(CanonicalTileID(0, 0, 0), GeoJSON{ FeatureCollection{} }); + }); + + test.run(); +} + diff --git a/test/tile/custom_geometry_tile.test.cpp b/test/tile/custom_geometry_tile.test.cpp new file mode 100644 index 00000000000..21a3dd79538 --- /dev/null +++ b/test/tile/custom_geometry_tile.test.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace mbgl; +using namespace mbgl::style; + +class CustomTileTest { +public: + FakeFileSource fileSource; + TransformState transformState; + util::RunLoop loop; + ThreadPool threadPool { 1 }; + style::Style style { loop, fileSource, 1 }; + AnnotationManager annotationManager { style }; + ImageManager imageManager; + GlyphManager glyphManager { fileSource }; + + TileParameters tileParameters { + 1.0, + MapDebugOptions(), + transformState, + threadPool, + fileSource, + MapMode::Continuous, + annotationManager, + imageManager, + glyphManager, + 0 + }; +}; + +TEST(CustomGeometryTile, InvokeFetchTile) { + CustomTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::geometry::feature_collection features; + features.push_back(mapbox::geometry::feature { + mapbox::geometry::point(0, 0) + }); + CustomTileLoader loader([&](const CanonicalTileID& tileId) { + EXPECT_EQ(tileId, CanonicalTileID(0,0,0)); + test.loop.stop(); + }, [&](const CanonicalTileID&) { + + }); + auto mb =std::make_shared(*Scheduler::GetCurrent()); + ActorRef loaderActor(loader, mb); + + CustomGeometryTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, CustomGeometrySource::TileOptions(), + loaderActor); + + tile.setNecessity(TileNecessity::Required); + + test.loop.run(); +} + +TEST(CustomGeometryTile, InvokeCancelTile) { + CustomTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::geometry::feature_collection features; + features.push_back(mapbox::geometry::feature { + mapbox::geometry::point(0, 0) + }); + + CustomTileLoader loader([&](const CanonicalTileID&) { }, [&](const CanonicalTileID& tileId) { + EXPECT_EQ(tileId, CanonicalTileID(0,0,0)); + test.loop.stop(); + }); + auto mb =std::make_shared(*Scheduler::GetCurrent()); + ActorRef loaderActor(loader, mb); + + CustomGeometryTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, CustomGeometrySource::TileOptions(), + loaderActor); + + tile.setNecessity(TileNecessity::Required); + tile.setNecessity(TileNecessity::Optional); + test.loop.run(); +} + +TEST(CustomGeometryTile, InvokeTileChanged) { + CustomTileTest test; + + CircleLayer layer("circle", "source"); + + mapbox::geometry::feature_collection features; + features.push_back(mapbox::geometry::feature { + mapbox::geometry::point(0, 0) + }); + + CustomTileLoader loader(nullptr, nullptr); + auto mb =std::make_shared(*Scheduler::GetCurrent()); + ActorRef loaderActor(loader, mb); + + CustomGeometryTile tile(OverscaledTileID(0, 0, 0), "source", test.tileParameters, CustomGeometrySource::TileOptions(), + loaderActor); + + StubTileObserver observer; + observer.tileChanged = [&] (const Tile&) { + // Once present, the bucket should never "disappear", which would cause + // flickering. + ASSERT_NE(nullptr, tile.getBucket(*layer.baseImpl)); + }; + + tile.setLayers({{ layer.baseImpl }}); + tile.setObserver(&observer); + tile.setTileData(features); + + while (!tile.isComplete()) { + test.loop.runOnce(); + } +} diff --git a/test/util/unique_any.test.cpp b/test/util/unique_any.test.cpp new file mode 100644 index 00000000000..9357b9c0ec8 --- /dev/null +++ b/test/util/unique_any.test.cpp @@ -0,0 +1,186 @@ +#include + +#include + +using namespace mbgl::util; + +class TestType { +public: + TestType() : i1(0), i2(1) { + str[0] = 'a'; + } + + TestType(unique_any& p) : TestType() { + p = std::unique_ptr(this); + } + + //Detect moves + TestType(TestType&& t): i1(t.i1+1), i2(t.i2+2) { + str[0] = t.str[0]+1; + } + + int i1; + int i2; + char str[256]; +}; + +bool IsStackAllocated (const unique_any& a, const void* obj1) { + uintptr_t a_ptr = (uintptr_t)(&a); + uintptr_t obj = (uintptr_t)(obj1); + return (obj >= a_ptr && obj < a_ptr + sizeof(unique_any)); +}; + +TEST(UniqueAny, Empty) { + EXPECT_FALSE(unique_any().has_value()); + EXPECT_TRUE(unique_any().type() == typeid(void)); + EXPECT_THROW(any_cast(unique_any()), bad_any_cast); +} + +TEST(UniqueAny, BasicTypes) { + unique_any i = 3; + EXPECT_TRUE(i.has_value()); + EXPECT_TRUE(i.type() == typeid(int)); + EXPECT_TRUE(IsStackAllocated(i, any_cast(&i))); + + auto iValue = any_cast(i); + EXPECT_TRUE(iValue == 3); + + EXPECT_TRUE(unique_any(4).has_value()); + EXPECT_TRUE(unique_any(4).type() == typeid(int)); + + unique_any f = 6.2f; + EXPECT_TRUE(f.has_value()); + EXPECT_TRUE(f.type() == typeid(float)); + EXPECT_TRUE(IsStackAllocated(f, any_cast(&f))); + + const float fValue = any_cast(f); + EXPECT_TRUE(fValue == 6.2f); + + EXPECT_TRUE(unique_any(1.0f).has_value()); + EXPECT_TRUE(unique_any(1.0f).type() == typeid(float)); + + unique_any c = 'z'; + EXPECT_TRUE(c.has_value()); + EXPECT_TRUE(c.type() == typeid(char)); + EXPECT_TRUE(IsStackAllocated(c, any_cast(&c))); + + EXPECT_THROW(any_cast(c), bad_any_cast); + + EXPECT_TRUE(unique_any('4').has_value()); + EXPECT_TRUE(unique_any('4').type() == typeid(char)); +} + +TEST(UniqueAny, BasicTypes_Move) { + unique_any i = 3; + EXPECT_TRUE(i.has_value()); + EXPECT_TRUE(i.type() == typeid(int)); + + unique_any f = 6.2f; + EXPECT_TRUE(f.has_value()); + EXPECT_TRUE(f.type() == typeid(float)); + + f = std::move(i); + EXPECT_FALSE(i.has_value()); + EXPECT_TRUE(i.type() == typeid(void)); + + EXPECT_TRUE(f.has_value()); + EXPECT_TRUE(f.type() == typeid(int)); + +} + +TEST(UniqueAny, LargeType) { + TestType t1; + unique_any u1 = unique_any(std::move(t1)); + EXPECT_TRUE(u1.has_value()); + EXPECT_TRUE(u1.type() == typeid(TestType)); + EXPECT_FALSE(IsStackAllocated(u1, any_cast(&u1))); + + //TestType should be moved into owning unique_any + EXPECT_EQ(any_cast(&u1)->i1, 1); + + auto u2(std::move(u1)); + EXPECT_TRUE(u2.type() == typeid(TestType)); + EXPECT_TRUE(u1.type() == typeid(void)); + + //TestType should not be moved when owning unique_any is moved; + EXPECT_EQ(any_cast(&u2)->i1, 1); + + //TestType should be moved out of owning unique_any + // Note: two moves are involved in returning the moved value + // First out of the unique_any, and then in the return statement + auto t2 = any_cast(std::move(u2)); + EXPECT_EQ(t2.i1, 3); + EXPECT_TRUE(u2.type() == typeid(void)); +} + +TEST(UniqueAny, Pointer) { + auto t1 = new TestType(); + + auto u1 = unique_any(std::move(t1)); + EXPECT_TRUE(u1.has_value()); + EXPECT_TRUE(u1.type() == typeid(TestType *)); + EXPECT_TRUE(IsStackAllocated(u1, any_cast(&u1))); + + //Only the pointer should be moved + TestType * t2 = *any_cast(&u1); + EXPECT_EQ(t2->i1, 0); + + unique_any u2(4); + std::swap(u2, u1); + + EXPECT_TRUE(u1.has_value()); + EXPECT_TRUE(u1.type() == typeid(int)); + + EXPECT_TRUE(u2.has_value()); + EXPECT_TRUE(u2.type() == typeid(TestType *)); + + t2 = *any_cast(&u2); + EXPECT_EQ(t2->i1, 0); + delete t2; +} + + +TEST(UniqueAny, UniquePtr) { + auto t1 = std::make_unique(); + auto u1 = unique_any(std::move(t1)); + + EXPECT_EQ(t1.get(), nullptr); + EXPECT_TRUE(u1.has_value()); + EXPECT_TRUE(u1.type() == typeid(std::unique_ptr)); + + EXPECT_TRUE(IsStackAllocated(u1, any_cast>(&u1))); + + auto t2 = any_cast >(std::move(u1)); + EXPECT_FALSE(u1.has_value()); + + unique_any u2; + TestType * t3 = new TestType(); + u2 = std::unique_ptr(t3); + EXPECT_TRUE(u2.has_value()); + EXPECT_TRUE(any_cast>(&u2)->get() == t3); +} + +TEST(UniqueAny, SharedPtr) { + + std::shared_ptr shared(new int(3)); + std::weak_ptr weak = shared; + unique_any u1 = 0; + + EXPECT_THROW(any_cast(u1), bad_any_cast); + + EXPECT_EQ(weak.use_count(), 1); + unique_any u2 = shared; + EXPECT_EQ(weak.use_count(), 2); + + EXPECT_EQ(any_cast>(&u1), nullptr); + EXPECT_FALSE(IsStackAllocated(u1, any_cast>(&u1))); + + u1 = std::move(u2); + EXPECT_EQ(weak.use_count(), 2); + u2.swap(u1); + EXPECT_EQ(weak.use_count(), 2); + u2 = 0; + EXPECT_EQ(weak.use_count(), 1); + shared = nullptr; + EXPECT_EQ(weak.use_count(), 0); +}