Skip to content

Commit

Permalink
supporting fill-extrusion (#211)
Browse files Browse the repository at this point in the history
# Pull Request

## Description

Hey! I needed to use extrusions in my project and I thought maybe you
want to add support to them.

This is the result in example Layer page, adding
controller.addFillExtrusionLayer:
<img
src="https://user-images.githubusercontent.com/29118355/225405519-f6ae3d3d-8715-4b0c-a28a-1ebe70b5fe29.jpeg"
height="400">

---------


Co-authored-by: m0nac0 <58807793+m0nac0@users.noreply.github.com>
  • Loading branch information
krupupakku and m0nac0 authored Dec 9, 2023
1 parent 58c7c0a commit dc21739
Show file tree
Hide file tree
Showing 17 changed files with 566 additions and 27 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Include the following JavaScript and CSS files in the `<head>` of the `web/index
| Circle | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Line | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Fill | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Fill Extrusion | :white_check_mark: | :white_check_mark: | :white_check_mark: |


## Map Styles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;


import static com.mapbox.mapboxgl.Convert.toMap;

class LayerPropertyConverter {
Expand Down Expand Up @@ -106,9 +107,9 @@ static PropertyValue[] interpretSymbolLayerProperties(Object o) {
properties.add(PropertyFactory.iconTextFitPadding(expression));
break;
case "icon-image":
if (jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isString()) {
if(jsonElement.isJsonPrimitive() && jsonElement.getAsJsonPrimitive().isString()){
properties.add(PropertyFactory.iconImage(jsonElement.getAsString()));
} else {
}else{
properties.add(PropertyFactory.iconImage(expression));
}
break;
Expand Down Expand Up @@ -375,6 +376,50 @@ static PropertyValue[] interpretFillLayerProperties(Object o) {
return properties.toArray(new PropertyValue[properties.size()]);
}

static PropertyValue[] interpretFillExtrusionLayerProperties(Object o) {
final Map<String, String> data = (Map<String, String>) toMap(o);
final List<PropertyValue> properties = new LinkedList();
final JsonParser parser = new JsonParser();

for (Map.Entry<String, String> entry : data.entrySet()) {
final JsonElement jsonElement = parser.parse(entry.getValue());
Expression expression = Expression.Converter.convert(jsonElement);
switch (entry.getKey()) {
case "fill-extrusion-opacity":
properties.add(PropertyFactory.fillExtrusionOpacity(expression));
break;
case "fill-extrusion-color":
properties.add(PropertyFactory.fillExtrusionColor(expression));
break;
case "fill-extrusion-translate":
properties.add(PropertyFactory.fillExtrusionTranslate(expression));
break;
case "fill-extrusion-translate-anchor":
properties.add(PropertyFactory.fillExtrusionTranslateAnchor(expression));
break;
case "fill-extrusion-pattern":
properties.add(PropertyFactory.fillExtrusionPattern(expression));
break;
case "fill-extrusion-height":
properties.add(PropertyFactory.fillExtrusionHeight(expression));
break;
case "fill-extrusion-base":
properties.add(PropertyFactory.fillExtrusionBase(expression));
break;
case "fill-extrusion-vertical-gradient":
properties.add(PropertyFactory.fillExtrusionVerticalGradient(expression));
break;
case "visibility":
properties.add(PropertyFactory.visibility(entry.getValue().substring(1, entry.getValue().length() - 1)));
break;
default:
break;
}
}

return properties.toArray(new PropertyValue[properties.size()]);
}

static PropertyValue[] interpretRasterLayerProperties(Object o) {
final Map<String, String> data = (Map<String, String>) toMap(o);
final List<PropertyValue> properties = new LinkedList();
Expand Down
65 changes: 65 additions & 0 deletions android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,40 @@ private void addFillLayer(
}
}

private void addFillExtrusionLayer(
String layerName,
String sourceName,
String belowLayerId,
String sourceLayer,
Float minZoom,
Float maxZoom,
PropertyValue[] properties,
boolean enableInteraction,
Expression filter) {
FillExtrusionLayer fillLayer = new FillExtrusionLayer(layerName, sourceName);
fillLayer.setProperties(properties);
if (sourceLayer != null) {
fillLayer.setSourceLayer(sourceLayer);
}
if (minZoom != null) {
fillLayer.setMinZoom(minZoom);
}
if (maxZoom != null) {
fillLayer.setMaxZoom(maxZoom);
}
if (filter != null) {
fillLayer.setFilter(filter);
}
if (belowLayerId != null) {
style.addLayerBelow(fillLayer, belowLayerId);
} else {
style.addLayer(fillLayer);
}
if (enableInteraction) {
interactiveFeatureLayerIds.add(layerName);
}
}

private void addCircleLayer(
String layerName,
String sourceName,
Expand Down Expand Up @@ -1037,6 +1071,37 @@ public void onError(@NonNull String message) {
result.success(null);
break;
}
case "fillExtrusionLayer#add":
{
final String sourceId = call.argument("sourceId");
final String layerId = call.argument("layerId");
final String belowLayerId = call.argument("belowLayerId");
final String sourceLayer = call.argument("sourceLayer");
final Double minzoom = call.argument("minzoom");
final Double maxzoom = call.argument("maxzoom");
final String filter = call.argument("filter");
final boolean enableInteraction = call.argument("enableInteraction");
final PropertyValue[] properties =
LayerPropertyConverter.interpretFillExtrusionLayerProperties(
call.argument("properties"));

Expression filterExpression = parseFilter(filter);

addFillExtrusionLayer(
layerId,
sourceId,
belowLayerId,
sourceLayer,
minzoom != null ? minzoom.floatValue() : null,
maxzoom != null ? maxzoom.floatValue() : null,
properties,
enableInteraction,
filterExpression);
updateLocationComponentLayer();

result.success(null);
break;
}
case "circleLayer#add":
{
final String sourceId = call.argument("sourceId");
Expand Down
36 changes: 36 additions & 0 deletions example/lib/layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,25 @@ class LayerState extends State {
filter: ['==', 'id', filteredId],
);

await controller.addFillExtrusionLayer(
"fills",
"fills-extrusion",
const FillExtrusionLayerProperties(
fillExtrusionHeight: 300,
fillExtrusionColor: [
Expressions.interpolate,
['exponential', 0.5],
[Expressions.zoom],
11,
'red',
18,
'blue'
],
),
belowLayerId: "water",
filter: ['==', 'id', 2],
);

await controller.addLineLayer(
"fills",
"lines",
Expand Down Expand Up @@ -314,6 +333,23 @@ final _fills = {
]
}
},
{
"type": "Feature",
"id": 2,
"properties": <String, dynamic>{'id': 2},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[151.121824791363, -33.885947459842846],
[151.121824791363, -33.89768020458625],
[151.13561641336742, -33.89768020458625],
[151.13561641336742, -33.885947459842846],
[151.121824791363, -33.885947459842846]
]
],
}
},
{
"type": "Feature",
"id": 1,
Expand Down
30 changes: 30 additions & 0 deletions ios/Classes/LayerPropertyConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,36 @@ class LayerPropertyConverter {
}
}

class func addFillExtrusionProperties(fillExtrusionLayer: MGLFillExtrusionStyleLayer, properties: [String: String]) {
for (propertyName, propertyValue) in properties {
let expression = interpretExpression(propertyName: propertyName, expression: propertyValue)
switch propertyName {
case "fill-extrusion-opacity":
fillExtrusionLayer.fillExtrusionOpacity = expression
case "fill-extrusion-color":
fillExtrusionLayer.fillExtrusionColor = expression
case "fill-extrusion-translate":
fillExtrusionLayer.fillExtrusionTranslation = expression
case "fill-extrusion-translate-anchor":
fillExtrusionLayer.fillExtrusionTranslationAnchor = expression
case "fill-extrusion-pattern":
fillExtrusionLayer.fillExtrusionPattern = expression
case "fill-extrusion-height":
fillExtrusionLayer.fillExtrusionHeight = expression
case "fill-extrusion-base":
fillExtrusionLayer.fillExtrusionBase = expression
case "fill-extrusion-vertical-gradient":
fillExtrusionLayer.fillExtrusionHasVerticalGradient = expression
case "visibility":
let trimmedPropertyValue = propertyValue.trimmingCharacters(in: .init(charactersIn: "\""))
fillExtrusionLayer.isVisible = trimmedPropertyValue == "visible"

default:
break
}
}
}

class func addRasterProperties(rasterLayer: MGLRasterStyleLayer, properties: [String: String]) {
for (propertyName, propertyValue) in properties {
let expression = interpretExpression(propertyName: propertyName, expression: propertyValue)
Expand Down
73 changes: 73 additions & 0 deletions ios/Classes/MapboxMapController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,34 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma
case let .failure(error): result(error.flutterError)
}

case "fillExtrusionLayer#add":
guard let arguments = methodCall.arguments as? [String: Any] else { return }
guard let sourceId = arguments["sourceId"] as? String else { return }
guard let layerId = arguments["layerId"] as? String else { return }
guard let properties = arguments["properties"] as? [String: String] else { return }
guard let enableInteraction = arguments["enableInteraction"] as? Bool else { return }
let belowLayerId = arguments["belowLayerId"] as? String
let sourceLayer = arguments["sourceLayer"] as? String
let minzoom = arguments["minzoom"] as? Double
let maxzoom = arguments["maxzoom"] as? Double
let filter = arguments["filter"] as? String

let addResult = addFillExtrusionLayer(
sourceId: sourceId,
layerId: layerId,
belowLayerId: belowLayerId,
sourceLayerIdentifier: sourceLayer,
minimumZoomLevel: minzoom,
maximumZoomLevel: maxzoom,
filter: filter,
enableInteraction: enableInteraction,
properties: properties
)
switch addResult {
case .success: result(nil)
case let .failure(error): result(error.flutterError)
}

case "circleLayer#add":
guard let arguments = methodCall.arguments as? [String: Any] else { return }
guard let sourceId = arguments["sourceId"] as? String else { return }
Expand Down Expand Up @@ -1273,6 +1301,51 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma
return .success(())
}

func addFillExtrusionLayer(
sourceId: String,
layerId: String,
belowLayerId: String?,
sourceLayerIdentifier: String?,
minimumZoomLevel: Double?,
maximumZoomLevel: Double?,
filter: String?,
enableInteraction: Bool,
properties: [String: String]
) -> Result<Void, MethodCallError> {
if let style = mapView.style {
if let source = style.source(withIdentifier: sourceId) {
let layer = MGLFillExtrusionStyleLayer(identifier: layerId, source: source)
LayerPropertyConverter.addFillExtrusionProperties(
fillExtrusionLayer: layer,
properties: properties
)
if let sourceLayerIdentifier = sourceLayerIdentifier {
layer.sourceLayerIdentifier = sourceLayerIdentifier
}
if let minimumZoomLevel = minimumZoomLevel {
layer.minimumZoomLevel = Float(minimumZoomLevel)
}
if let maximumZoomLevel = maximumZoomLevel {
layer.maximumZoomLevel = Float(maximumZoomLevel)
}
if let filter = filter {
if case let .failure(error) = setFilter(layer, filter) {
return .failure(error)
}
}
if let id = belowLayerId, let belowLayer = style.layer(withIdentifier: id) {
style.insertLayer(layer, below: belowLayer)
} else {
style.addLayer(layer)
}
if enableInteraction {
interactiveFeatureLayerIds.insert(layerId)
}
}
}
return .success(())
}

func addCircleLayer(
sourceId: String,
layerId: String,
Expand Down
46 changes: 46 additions & 0 deletions lib/src/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,46 @@ class MaplibreMapController extends ChangeNotifier {
);
}

/// Add a fill extrusion layer to the map with the given properties
///
/// Consider using [addLayer] for an unified layer api.
///
/// The returned [Future] completes after the change has been made on the
/// platform side.
///
/// Setting [belowLayerId] adds the new layer below the given id.
/// If [enableInteraction] is set the layer is considered for touch or drag
/// events. [sourceLayer] is used to selected a specific source layer from
/// Vector source.
/// [minzoom] is the minimum (inclusive) zoom level at which the layer is
/// visible.
/// [maxzoom] is the maximum (exclusive) zoom level at which the layer is
/// visible.
/// [filter] determines which features should be rendered in the layer.
/// Filters are written as [expressions].
///
/// [expressions]: https://maplibre.org/maplibre-style-spec/expressions/
Future<void> addFillExtrusionLayer(
String sourceId, String layerId, FillExtrusionLayerProperties properties,
{String? belowLayerId,
String? sourceLayer,
double? minzoom,
double? maxzoom,
dynamic filter,
bool enableInteraction = true}) async {
await _maplibreGlPlatform.addFillExtrusionLayer(
sourceId,
layerId,
properties.toJson(),
belowLayerId: belowLayerId,
sourceLayer: sourceLayer,
minzoom: minzoom,
maxzoom: maxzoom,
filter: filter,
enableInteraction: enableInteraction,
);
}

/// Add a circle layer to the map with the given properties
///
/// Consider using [addLayer] for an unified layer api.
Expand Down Expand Up @@ -1270,6 +1310,12 @@ class MaplibreMapController extends ChangeNotifier {
minzoom: minzoom,
maxzoom: maxzoom,
filter: filter);
} else if (properties is FillExtrusionLayerProperties) {
addFillExtrusionLayer(sourceId, layerId, properties,
belowLayerId: belowLayerId,
sourceLayer: sourceLayer,
minzoom: minzoom,
maxzoom: maxzoom);
} else if (properties is LineLayerProperties) {
addLineLayer(sourceId, layerId, properties,
belowLayerId: belowLayerId,
Expand Down
Loading

0 comments on commit dc21739

Please sign in to comment.