Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Supercluster.hpp 0.3.0 update #12726

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmake/mason-dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ mason_use(unique_resource VERSION cba309e HEADER_ONLY)
mason_use(rapidjson VERSION 1.1.0 HEADER_ONLY)
mason_use(boost VERSION 1.65.1 HEADER_ONLY)
mason_use(geojsonvt VERSION 6.6.1 HEADER_ONLY)
mason_use(supercluster VERSION 0.2.2 HEADER_ONLY)
mason_use(kdbush VERSION 0.1.1-1 HEADER_ONLY)
mason_use(supercluster VERSION 0.3.1 HEADER_ONLY)
mason_use(kdbush VERSION 0.1.3 HEADER_ONLY)
mason_use(earcut VERSION 0.12.4 HEADER_ONLY)
mason_use(protozero VERSION 1.5.2 HEADER_ONLY)
mason_use(pixelmatch VERSION 0.10.0 HEADER_ONLY)
Expand Down
5 changes: 4 additions & 1 deletion include/mbgl/style/sources/geojson_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ class GeoJSONSource : public Source {

void setURL(const std::string& url);
void setGeoJSON(const GeoJSON&);

optional<std::string> getURL() const;

mapbox::geometry::feature_collection<double> getClusterChildren(const std::uint32_t) const;
mapbox::geometry::feature_collection<double> getClusterLeaves(const std::uint32_t, const std::uint32_t = 10U, const std::uint32_t = 0U) const;
std::uint8_t getClusterExpansionZoom(std::uint32_t) const;

class Impl;
const Impl& impl() const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,57 @@ public String getUrl() {
public List<Feature> querySourceFeatures(@Nullable Expression filter) {
checkThread();
Feature[] features = querySourceFeatures(filter != null ? filter.toArray() : null);
return features != null ? Arrays.asList(features) : new ArrayList<Feature>();
return features != null ? Arrays.asList(features) : new ArrayList<>();
}

/**
* Returns the children of a cluster (on the next zoom level) given its id (cluster_id value from feature properties).
* <p>
* Requires configuring this source as a cluster by calling {@link GeoJsonOptions#withCluster(boolean)}.
* </p>
*
* @param clusterId cluster_id value from feature properties
* @return a list of features for the underlying children
*/
@NonNull
public List<Feature> getClusterChildren(long clusterId) {
checkThread();
Feature[] features = nativeGetClusterChildren(clusterId);
return features != null ? Arrays.asList(features) : new ArrayList<>();
}

/**
* Returns all the points of a cluster (given its cluster_id), with pagination support: limit is the number of points
* to return (set to Infinity for all points), and offset is the amount of points to skip (for pagination).
* <p>
* Requires configuring this source as a cluster by calling {@link GeoJsonOptions#withCluster(boolean)}.
* </p>
*
* @param clusterId cluster_id value from feature properties
* @param limit limit is the number of points to return
* @param offset offset is the amount of points to skip (for pagination)
* @return a list of features for the underlying leaves
*/
@NonNull
public List<Feature> getClusterLeaves(long clusterId, long limit, long offset) {
checkThread();
Feature[] features = nativeGetClusterLeaves(clusterId, limit, offset);
return features != null ? Arrays.asList(features) : new ArrayList<>();
}

/**
* Returns the zoom on which the cluster expands into several children (useful for "click to zoom" feature)
* given the cluster's cluster_id (cluster_id value from feature properties).
* <p>
* Requires configuring this source as a cluster by calling {@link GeoJsonOptions#withCluster(boolean)}.
* </p>
*
* @param clusterId cluster_id value from feature properties
* @return the zoom on which the cluster expands into several children
*/
public double getClusterExpansionZoom(long clusterId) {
checkThread();
return nativeGetClusterExpansionZoom(clusterId);
}

@Keep
Expand Down Expand Up @@ -299,6 +349,15 @@ public List<Feature> querySourceFeatures(@Nullable Expression filter) {
@Keep
private native Feature[] querySourceFeatures(Object[] filter);

@Keep
private native Feature[] nativeGetClusterChildren(long clusterId);

@Keep
private native Feature[] nativeGetClusterLeaves(long clusterId, long limit, long offset);

@Keep
private native double nativeGetClusterExpansionZoom(long clusterId);

@Override
@Keep
protected native void finalize() throws Throwable;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.mapbox.mapboxsdk.testapp.activity.style;

import android.graphics.Color;
import android.graphics.PointF;
import android.os.Bundle;
import android.support.v4.content.res.ResourcesCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import com.mapbox.geojson.Feature;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.maps.MapView;
Expand All @@ -20,9 +22,9 @@

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import static com.mapbox.mapboxsdk.style.expressions.Expression.all;
import static com.mapbox.mapboxsdk.style.expressions.Expression.division;
import static com.mapbox.mapboxsdk.style.expressions.Expression.exponential;
import static com.mapbox.mapboxsdk.style.expressions.Expression.get;
import static com.mapbox.mapboxsdk.style.expressions.Expression.gt;
Expand All @@ -36,9 +38,10 @@
import static com.mapbox.mapboxsdk.style.expressions.Expression.toNumber;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.circleRadius;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconSize;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textColor;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.textField;
Expand All @@ -52,17 +55,16 @@ public class GeoJsonClusteringActivity extends AppCompatActivity {

private MapView mapView;
private MapboxMap mapboxMap;
private GeoJsonSource source;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_geojson_clustering);

// Initialize map as normal
mapView = (MapView) findViewById(R.id.mapView);
// noinspection ConstantConditions
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);

mapView.getMapAsync(map -> {
mapboxMap = map;
mapboxMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(37.7749, 122.4194), 0));
Expand Down Expand Up @@ -132,7 +134,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
private void addClusteredGeoJsonSource() {
// Add a clustered source
try {
mapboxMap.addSource(
mapboxMap.addSource(source =
new GeoJsonSource("earthquakes",
new URL("https://www.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson"),
new GeoJsonOptions()
Expand All @@ -155,11 +157,8 @@ private void addClusteredGeoJsonSource() {
SymbolLayer unclustered = new SymbolLayer("unclustered-points", "earthquakes");
unclustered.setProperties(
iconImage("icon-id"),
iconSize(
division(
get("mag"), literal(4.0f)
)
),
iconAllowOverlap(true),
iconIgnorePlacement(true),
iconColor(
interpolate(exponential(1), get("mag"),
stop(2.0, rgb(0, 255, 0)),
Expand All @@ -171,9 +170,13 @@ private void addClusteredGeoJsonSource() {

mapboxMap.addLayer(unclustered);


String[] layerIds = new String[layers.length];

for (int i = 0; i < layers.length; i++) {
// Add some nice circles
CircleLayer circles = new CircleLayer("cluster-" + i, "earthquakes");
layerIds[i] = "cluster-" + i;
CircleLayer circles = new CircleLayer(layerIds[i], "earthquakes");
circles.setProperties(
circleColor(layers[i][1]),
circleRadius(18f)
Expand Down Expand Up @@ -204,7 +207,43 @@ private void addClusteredGeoJsonSource() {
mapboxMap.addLayer(count);


mapboxMap.addOnMapClickListener(latLng -> {
PointF point = mapboxMap.getProjection().toScreenLocation(latLng);
List<Feature> features = mapboxMap.queryRenderedFeatures(point, layerIds);
if (!features.isEmpty()) {
moveCameraToClusterExpansion(features.get(0), latLng);
recursiveLoopClusterFeatures(features);
}
});

// Zoom out to start
mapboxMap.animateCamera(CameraUpdateFactory.zoomTo(1));
}

private void moveCameraToClusterExpansion(Feature feature, LatLng latLng) {
if (feature.hasProperty("cluster") && feature.getBooleanProperty("cluster")) {
double newZoom = source.getClusterExpansionZoom((long) feature.getNumberProperty("cluster_id"));
mapboxMap.animateCamera(
CameraUpdateFactory.newLatLngZoom(latLng, newZoom + 0.01), 750
);
}
}

private void recursiveLoopClusterFeatures(List<Feature> features) {
for (Feature feature : features) {
boolean cluster = feature.hasProperty("cluster") && feature.getBooleanProperty("cluster");
if (cluster) {
long pointCount = (long) feature.getNumberProperty("point_count");
long clusterId = (long) feature.getNumberProperty("cluster_id");
double expansionZoom = source.getClusterExpansionZoom(clusterId);
Timber.e(
"Cluster= (id=%s) with %s points, cluster will expand at zoom %s",
clusterId, pointCount, expansionZoom
);
recursiveLoopClusterFeatures(source.getClusterLeaves(clusterId, 10, 0));
} else {
Timber.e("Point data: %s", feature.toJson());
}
}
}
}
25 changes: 24 additions & 1 deletion platform/android/src/style/sources/geojson_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,26 @@ namespace android {
return Feature::convert(env, features);
}

jni::Local<jni::Array<jni::Object<geojson::Feature>>> GeoJSONSource::getClusterChildren(jni::JNIEnv& env, jni::jlong clusterId) {
using namespace mbgl::android::conversion;
using namespace mbgl::android::geojson;

std::vector<mbgl::Feature> features = source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::getClusterChildren(clusterId);
return Feature::convert(env, features);
}

jni::Local<jni::Array<jni::Object<geojson::Feature>>> GeoJSONSource::getClusterLeaves(jni::JNIEnv& env, jni::jlong clusterId, jni::jlong limit, jni::jlong offset) {
using namespace mbgl::android::conversion;
using namespace mbgl::android::geojson;

std::vector<mbgl::Feature> features = source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::getClusterLeaves(clusterId, limit, offset);
return Feature::convert(env, features);
}

jni::jdouble GeoJSONSource::getClusterExpansionZoom(jni::JNIEnv&, jni::jlong clusterId) {
return source.as<mbgl::style::GeoJSONSource>()->GeoJSONSource::getClusterExpansionZoom(clusterId);
}

jni::Local<jni::Object<Source>> GeoJSONSource::createJavaPeer(jni::JNIEnv& env) {
static auto& javaClass = jni::Class<GeoJSONSource>::Singleton(env);
static auto constructor = javaClass.GetConstructor<jni::jlong>(env);
Expand Down Expand Up @@ -176,7 +196,10 @@ namespace android {
METHOD(&GeoJSONSource::setGeometry, "nativeSetGeometry"),
METHOD(&GeoJSONSource::setURL, "nativeSetUrl"),
METHOD(&GeoJSONSource::getURL, "nativeGetUrl"),
METHOD(&GeoJSONSource::querySourceFeatures, "querySourceFeatures")
METHOD(&GeoJSONSource::querySourceFeatures, "querySourceFeatures"),
METHOD(&GeoJSONSource::getClusterLeaves, "nativeGetClusterLeaves"),
METHOD(&GeoJSONSource::getClusterChildren, "nativeGetClusterChildren"),
METHOD(&GeoJSONSource::getClusterExpansionZoom, "nativeGetClusterExpansionZoom")
);
}

Expand Down
7 changes: 7 additions & 0 deletions platform/android/src/style/sources/geojson_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,14 @@ class GeoJSONSource : public Source {

jni::Local<jni::String> getURL(jni::JNIEnv&);

jni::Local<jni::Array<jni::Object<geojson::Feature>>> getClusterChildren(jni::JNIEnv &, jni::jlong);

jni::Local<jni::Array<jni::Object<geojson::Feature>>> getClusterLeaves(jni::JNIEnv &, jni::jlong, jni::jlong, jni::jlong);

jni::jdouble getClusterExpansionZoom(jni::JNIEnv&, jni::jlong);

jni::Local<jni::Object<Source>> createJavaPeer(jni::JNIEnv&);

std::unique_ptr<Update> awaitingUpdate;
std::unique_ptr<Update> update;
std::shared_ptr<ThreadPool> threadPool;
Expand Down
12 changes: 6 additions & 6 deletions src/mbgl/renderer/sources/render_geojson_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,23 @@ void RenderGeoJSONSource::update(Immutable<style::Source::Impl> baseImpl_,

enabled = needsRendering;

GeoJSONData* data_ = impl().getData();
auto data_ = impl().getData().lock();

if (data_ != data) {
if (data.lock() != data_) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to create the shared_ptr<> here for the comparison or can you compare the internal pointers directly?

data = data_;
tilePyramid.cache.clear();

if (data) {
if (data_) {
const uint8_t maxZ = impl().getZoomRange().max;
for (const auto& pair : tilePyramid.tiles) {
if (pair.first.canonical.z <= maxZ) {
static_cast<GeoJSONTile*>(pair.second.get())->updateData(data->getTile(pair.first.canonical));
static_cast<GeoJSONTile*>(pair.second.get())->updateData(data_->getTile(pair.first.canonical));
}
}
}
}

if (!data) {
if (!data_) {
tilePyramid.tiles.clear();
tilePyramid.renderTiles.clear();
return;
Expand All @@ -63,7 +63,7 @@ void RenderGeoJSONSource::update(Immutable<style::Source::Impl> baseImpl_,
util::tileSize,
impl().getZoomRange(),
optional<LatLngBounds>{},
[&] (const OverscaledTileID& tileID) {
[&, data = data_] (const OverscaledTileID& tileID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use data_ or rename so as to not shadow the member variable.

return std::make_unique<GeoJSONTile>(tileID, impl().id, parameters, data->getTile(tileID.canonical));
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/mbgl/renderer/sources/render_geojson_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class RenderGeoJSONSource : public RenderSource {
const style::GeoJSONSource::Impl& impl() const;

TilePyramid tilePyramid;
style::GeoJSONData* data = nullptr;
std::weak_ptr<style::GeoJSONData> data;
};

template <>
Expand Down
26 changes: 26 additions & 0 deletions src/mbgl/style/sources/geojson_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ optional<std::string> GeoJSONSource::getURL() const {
return url;
}

mapbox::geometry::feature_collection<double> GeoJSONSource::getClusterChildren(const std::uint32_t cluster_id) const {
if (auto data = impl().getData().lock()) {
return data->getClusterChildren(cluster_id);
}

return {};
}

mapbox::geometry::feature_collection<double> GeoJSONSource::getClusterLeaves(const std::uint32_t cluster_id,
const std::uint32_t limit,
const std::uint32_t offset) const {
if (auto data = impl().getData().lock()) {
return data->getClusterLeaves(cluster_id, limit, offset);
}

return {};
}

std::uint8_t GeoJSONSource::getClusterExpansionZoom(std::uint32_t cluster_id) const {
if (auto data = impl().getData().lock()) {
return data->getClusterExpansionZoom(cluster_id);
}

return 0;
}

void GeoJSONSource::loadDescription(FileSource& fileSource) {
if (!url) {
loaded = true;
Expand Down
Loading