I am developing a geovisualization data map that would plot the map of different colors depending on different number values. And, would want to have the number values be visualized on top of the circles too (it is a data of air pollution in a road circling around a route). The problem is when the two layers SymbolLayer and CircleLayer are applied at the same time they crash the mobile app, which does not work. I tried testing out only one of the layers they work, only when both are applied they crash. Is there a possibility for both layer tow work where the colored circle is present and the number value label is too? Also, I am using data stored in Tilesets. Below is the image link of the app. Cannot post images yet since this is my first time using StackOverflow.
//Map View
mapView = findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(@NonNull final MapboxMap mapboxMap) {
mapboxMap.setStyle(Style.LIGHT, new Style.OnStyleLoaded() {
@Override
public void onStyleLoaded(@NonNull Style style) {
style.addSource(new VectorSource(
VECTOR_SOURCE_ID,
"http://api.mapbox.com/v4/"+TILESET_KEY+".json?access_token=" + Mapbox.getAccessToken()
));
SymbolLayer label =new SymbolLayer(TILESET_LAYER_ID, VECTOR_SOURCE_ID);
label.setSourceLayer(VECTOR_SOURCE_ID);
label.withProperties(
textField(get("gaslvl_PM")), //This contains the number values
textSize(17f),
textColor(Color.RED),
textVariableAnchor(
new String[]{TEXT_ANCHOR_TOP, TEXT_ANCHOR_BOTTOM, TEXT_ANCHOR_LEFT, TEXT_ANCHOR_RIGHT}),
textJustify(TEXT_JUSTIFY_AUTO),
textRadialOffset(0.5f)
);
label.setFilter(all(
//This is to filter out the multiple rounds in the route of one full roundtrip.
//To prevent the same data from the same area to overlap.
eq(literal("roundtrip_number"), literal(roundT_val))));
style.addLayer(label);
CircleLayer circleLayer = new CircleLayer(TILESET_LAYER_ID, VECTOR_SOURCE_ID);
circleLayer.setSourceLayer(VECTOR_SOURCE_ID);
circleLayer.withProperties(
circleRadius(
interpolate(
exponential(1.75f),
zoom(),
stop(12, 2f),
stop(22, 180f)
)),
circleColor(
match(
get(TILESET_LAYER_ID),rgb(0, 0, 0),
stop("0", COLOR_GREEN),
stop("1", COLOR_GREEN),
stop("2", COLOR_GREEN),
stop("3", COLOR_GREEN),
stop("4", COLOR_GREEN)
//Didn't add the rest of the "stop colors" since it's 500 lines.
));
circleLayer.setFilter(all(
eq(literal("roundtrip_number"), literal(roundT_val))
//eq(literal("date"), literal(Date.valueOf(date_val)))
));
style.addLayer(circleLayer);
}
});
}
});
The attached code has several issues and I do not have 100% confidence but I think the crash is happened because you assign same Layer ID for both symbol layer and circle layer.
Did you see a crash dump similar to below? It says "Layer layer-id already exists".
2020-11-06 22:54:49.324 7267-7267/com.example.sof95859 E/Mbgl-MapChangeReceiver: Exception in onDidFinishLoadingStyle com.mapbox.mapboxsdk.style.layers.CannotAddLayerException: Layer layer-id already exists at com.mapbox.mapboxsdk.maps.NativeMapView.nativeAddLayer(Native Method) at com.mapbox.mapboxsdk.maps.NativeMapView.addLayer(NativeMapView.java:858) at com.mapbox.mapboxsdk.maps.Style.addLayer(Style.java:191) at com.example.sof95859.MainActivity$1$1.onStyleLoaded(MainActivity.java:121) at com.mapbox.mapboxsdk.maps.MapboxMap.notifyStyleLoaded(MapboxMap.java:963) at com.mapbox.mapboxsdk.maps.MapboxMap.onFinishLoadingStyle(MapboxMap.java:225) at com.mapbox.mapboxsdk.maps.MapView$MapCallback.onDidFinishLoadingStyle(MapView.java:1373) at com.mapbox.mapboxsdk.maps.MapChangeReceiver.onDidFinishLoadingStyle(MapChangeReceiver.java:198) at com.mapbox.mapboxsdk.maps.NativeMapView.onDidFinishLoadingStyle(NativeMapView.java:1166) at android.os.MessageQueue.nativePollOnce(Native Method) at android.os.MessageQueue.next(MessageQueue.java:336) at android.os.Looper.loop(Looper.java:174) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
I also attach working sample that accesses my sample layer.
public class MainActivity extends AppCompatActivity {
private static final String TILESET_KEY = "yochi.6v4pssqn";
private static final String VECTOR_SOURCE_ID = "vector-source";
private static final String VECTOR_SOURCE_LAYER_ID = "sof95859-0wzvvn";
private static final String TILESET_SYMBOL_LAYER_ID = "symbol-layer-id";
private static final String TILESET_CIRCLE_LAYER_ID = "circle-layer-id";
private static final int roundT_val = 2;
private static final Expression COLOR_GREEN = rgb(0, 255, 0);
private MapView mapView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Mapbox.getInstance(this, getString(R.string.mapbox_access_token));
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(new OnMapReadyCallback() {
@Override
public void onMapReady(@NonNull MapboxMap mapboxMap) {
CameraUpdate update = CameraUpdateFactory.newLatLngBounds(
LatLngBounds.from(1, 1, -1, -1), 0, 0, 0 );
mapboxMap.easeCamera(update);
mapboxMap.setStyle(Style.MAPBOX_STREETS, new Style.OnStyleLoaded() {
@Override
public void onStyleLoaded(@NonNull Style style) {
// tileset.json API call did not work for me
style.addSource(new VectorSource(VECTOR_SOURCE_ID, "mapbox://" + TILESET_KEY));
// Asssign unique layer ID
SymbolLayer label =new SymbolLayer(TILESET_SYMBOL_LAYER_ID, VECTOR_SOURCE_ID);
// Set layer name by checking Tileset in Studio
label.setSourceLayer(VECTOR_SOURCE_LAYER_ID);
label.withProperties(
textField(get("gaslvl_PM")), //This contains the number values
textSize(17f),
textColor(Color.RED),
textVariableAnchor(
new String[]{TEXT_ANCHOR_TOP, TEXT_ANCHOR_BOTTOM, TEXT_ANCHOR_LEFT, TEXT_ANCHOR_RIGHT}),
textJustify(TEXT_JUSTIFY_AUTO),
textRadialOffset(0.5f)
);
label.setFilter(all(
//This is to filter out the multiple rounds in the route of one full roundtrip.
//To prevent the same data from the same area to overlap.
eq(literal("roundtrip_number"), literal(roundT_val))));
style.addLayer(label);
CircleLayer circleLayer = new CircleLayer(TILESET_CIRCLE_LAYER_ID, VECTOR_SOURCE_ID);
// Set layer name by checking Tileset in Studio
circleLayer.setSourceLayer(VECTOR_SOURCE_LAYER_ID);
circleLayer.withProperties(
circleRadius(
interpolate(
exponential(1.75f),
zoom(),
stop(12, 2f),
stop(22, 180f)
)),
circleColor(
match(
// I think this is wrong
get(TILESET_CIRCLE_LAYER_ID),rgb(0, 0, 0),
stop("0", COLOR_GREEN),
stop("1", COLOR_GREEN),
stop("2", COLOR_GREEN),
stop("3", COLOR_GREEN),
stop("4", COLOR_GREEN)
// the number of parentheses is wrong
)));
circleLayer.setFilter(all(
eq(literal("roundtrip_number"), literal(roundT_val))
//eq(literal("date"), literal(Date.valueOf(date_val)))
));
style.addLayer(circleLayer);
}
});
}
});
}
comments added
The important thing is "layerId" parameter for SymbolLayer
, CircleLayer
constructor is not the layer name in the tileset. You define any unique "layerId" in each constructor of SymbolLayer
, CircleLayer
etc...
You can use same data source for each layer by putting same Source ID in the second parameter of SymbolLayer
, CircleLayer
constructor. Please note that Source ID is also you define any Source ID
in style.addSource.
Then to select which Layer should be used in each layer, you call label.setSourceLayer(VECTOR_SOURCE_LAYER_ID);
Here's the summary of IDs