From 8f2129a26b72fbd9ce873eb4e02098cbc6a6aaf1 Mon Sep 17 00:00:00 2001 From: tobrun Date: Wed, 27 Mar 2019 23:29:33 +0100 Subject: [PATCH 01/15] [flutter] [android] - add fill support --- .../java/com/mapbox/mapboxgl/Convert.java | 56 +++- .../java/com/mapbox/mapboxgl/FillBuilder.java | 58 ++++ .../com/mapbox/mapboxgl/FillController.java | 74 +++++ .../com/mapbox/mapboxgl/FillOptionsSink.java | 27 ++ .../mapbox/mapboxgl/MapboxMapController.java | 68 +++- .../mapbox/mapboxgl/OnFillTappedListener.java | 7 + example/lib/main.dart | 2 + example/lib/place_fill.dart | 300 ++++++++++++++++++ lib/mapbox_gl.dart | 1 + lib/src/fill.dart | 90 ++++++ 10 files changed, 676 insertions(+), 7 deletions(-) create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillController.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java create mode 100644 example/lib/place_fill.dart create mode 100644 lib/src/fill.dart diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index 28d55566e..0f3315bca 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -5,17 +5,15 @@ package com.mapbox.mapboxgl; import android.graphics.Point; - -import android.util.Log; - +import com.mapbox.geojson.Polygon; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; -import com.mapbox.mapboxsdk.log.Logger; import com.mapbox.mapboxsdk.maps.MapboxMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -101,7 +99,7 @@ static CameraUpdate toCameraUpdate(Object o, MapboxMap mapboxMap, float density) case "bearingTo": return CameraUpdateFactory.bearingTo(toFloat(data.get(1))); case "tiltTo": - return CameraUpdateFactory.tiltTo(toFloat(data.get(1))); + return CameraUpdateFactory.tiltTo(toFloat(data.get(1))); default: throw new IllegalArgumentException("Cannot interpret " + o + " as CameraUpdate"); } @@ -388,4 +386,52 @@ static void interpretCircleOptions(Object o, CircleOptionsSink sink) { sink.setDraggable(toBoolean(draggable)); } } + + static void interpretFillOptions(Object o, FillOptionsSink sink) { + final Map data = toMap(o); + final Object fillOpacity = data.get("fillOpacity"); + if (fillOpacity != null) { + sink.setFillOpacity(toFloat(fillOpacity)); + } + final Object fillColor = data.get("fillColor"); + if (fillColor != null) { + sink.setFillColor(toString(fillColor)); + } + final Object fillOutlineColor = data.get("fillOutlineColor"); + if (fillOutlineColor != null) { + sink.setFillOutlineColor(toString(fillOutlineColor)); + } + final Object fillPattern = data.get("fillPattern"); + if (fillPattern != null) { + sink.setFillPattern(toString(fillPattern)); + } + final Object geometry = data.get("geometry"); + if (geometry != null) { + sink.setGeometry(toLatLngListList(geometry)); + } + final Object draggable = data.get("draggable"); + if (draggable != null) { + sink.setDraggable(toBoolean(draggable)); + } + } + + private static List> toLatLngListList(Object o) { + if (o == null) { + return null; + } + // todo add conversion + return new ArrayList<>(); + } + + static Polygon interpretListLatLng(List> geometry) { + List> points = new ArrayList<>(geometry.size()); + for (List innerGeometry : geometry) { + List innerPoints = new ArrayList<>(innerGeometry.size()); + for (LatLng latLng : innerGeometry) { + innerPoints.add(com.mapbox.geojson.Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); + } + points.add(innerPoints); + } + return Polygon.fromLngLats(points); + } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java new file mode 100644 index 000000000..188d580fe --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java @@ -0,0 +1,58 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; +import com.mapbox.mapboxsdk.plugins.annotation.FillOptions; + +import java.util.List; + +class FillBuilder implements FillOptionsSink { + private final FillManager fillManager; + private final FillOptions fillOptions; + + FillBuilder(FillManager fillManager) { + this.fillManager = fillManager; + this.fillOptions = new FillOptions(); + } + + Fill build() { + return fillManager.create(fillOptions); + } + + @Override + public void setFillOpacity(float fillOpacity) { + fillOptions.withFillOpacity(fillOpacity); + } + + @Override + public void setFillColor(String fillColor) { + fillOptions.withFillColor(fillColor); + } + + @Override + public void setFillOutlineColor(String fillOutlineColor) { + fillOptions.withFillOutlineColor(fillOutlineColor); + } + + @Override + public void setFillPattern(String fillPattern) { + fillOptions.withFillPattern(fillPattern); + } + + @Override + public void setGeometry(List> geometry) { + fillOptions.withGeometry(Convert.interpretListLatLng(geometry)); + } + + @Override + public void setDraggable(boolean draggable) { + fillOptions.setDraggable(draggable); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillController.java b/android/src/main/java/com/mapbox/mapboxgl/FillController.java new file mode 100644 index 000000000..200b13f76 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillController.java @@ -0,0 +1,74 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import android.graphics.Color; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; + +import java.util.List; + +/** + * Controller of a single Fill on the map. + */ +class FillController implements FillOptionsSink { + private final Fill fill; + private final OnFillTappedListener onTappedListener; + private boolean consumeTapEvents; + + FillController(Fill fill, boolean consumeTapEvents, OnFillTappedListener onTappedListener) { + this.fill = fill; + this.consumeTapEvents = consumeTapEvents; + this.onTappedListener = onTappedListener; + } + + boolean onTap() { + if (onTappedListener != null) { + onTappedListener.onFillTapped(fill); + } + return consumeTapEvents; + } + + void remove(FillManager fillManager) { + fillManager.delete(fill); + } + + @Override + public void setFillOpacity(float fillOpacity) { + fill.setFillOpacity(fillOpacity); + } + + @Override + public void setFillColor(String fillColor) { + fill.setFillColor(Color.parseColor(fillColor)); + } + + @Override + public void setFillOutlineColor(String fillOutlineColor) { + fill.setFillOutlineColor(Color.parseColor(fillOutlineColor)); + } + + @Override + public void setFillPattern(String fillPattern) { + fill.setFillPattern(fillPattern); + } + + @Override + public void setGeometry(List> geometry) { + fill.setGeometry(Convert.interpretListLatLng(geometry)); + } + + @Override + public void setDraggable(boolean draggable) { + fill.setDraggable(draggable); + } + + public void update(FillManager fillManager) { + fillManager.update(fill); + } +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java new file mode 100644 index 000000000..849788103 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java @@ -0,0 +1,27 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +import java.util.List; + +/** Receiver of Fill configuration options. */ +interface FillOptionsSink { + + void setFillOpacity(float fillOpacity); + + void setFillColor(String fillColor); + + void setFillOutlineColor(String fillOutlineColor); + + void setFillPattern(String fillPattern); + + void setGeometry(List> geometry); + + void setDraggable(boolean draggable); +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 1d6e42c7b..0b3d40ede 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -30,10 +30,13 @@ import com.mapbox.mapboxsdk.maps.MapView; import com.mapbox.mapboxsdk.maps.MapboxMap; import com.mapbox.mapboxsdk.maps.MapboxMapOptions; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.mapboxsdk.plugins.annotation.Annotation; import com.mapbox.mapboxsdk.plugins.annotation.Circle; import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; import com.mapbox.mapboxsdk.plugins.annotation.OnAnnotationClickListener; import com.mapbox.mapboxsdk.plugins.annotation.Symbol; import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; @@ -69,10 +72,11 @@ final class MapboxMapController MapboxMap.OnMapClickListener, MapboxMapOptionsSink, MethodChannel.MethodCallHandler, - com.mapbox.mapboxsdk.maps.OnMapReadyCallback, + OnMapReadyCallback, OnCameraTrackingChangedListener, OnSymbolTappedListener, OnCircleTappedListener, + OnFillTappedListener, PlatformView { private static final String TAG = "MapboxMapController"; private final int id; @@ -82,9 +86,11 @@ final class MapboxMapController private final MapView mapView; private final Map symbols; private final Map circles; + private final Map fills; private MapboxMap mapboxMap; private SymbolManager symbolManager; private CircleManager circleManager; + private FillManager fillManager; private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private int myLocationTrackingMode = 0; @@ -112,6 +118,7 @@ final class MapboxMapController this.mapView = new MapView(context, options); this.symbols = new HashMap<>(); this.circles = new HashMap<>(); + this.fills = new HashMap<>(); this.density = context.getResources().getDisplayMetrics().density; methodChannel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_maps_" + id); @@ -223,7 +230,22 @@ private void removeCircle(String circleId) { private CircleController circle(String circleId) { final CircleController circle = circles.get(circleId); if (circle == null) { - throw new IllegalArgumentException("Unknown symbol: " + circleId); + throw new IllegalArgumentException("Unknown circle: " + circleId); + } + return circle; + } + + private void removeFill(String fillId) { + final FillController fillController = fills.remove(fillId); + if (fillController != null) { + fillController.remove(fillManager); + } + } + + private CircleController fill(String fillId) { + final CircleController circle = circles.get(fillId); + if (circle == null) { + throw new IllegalArgumentException("Unknown fill: " + fillId); } return circle; } @@ -259,6 +281,7 @@ public void setStyleString(String styleString) { public void onStyleLoaded(@NonNull Style style) { enableSymbolManager(style); enableCircleManager(style); + enableFillManager(style); enableLocationComponent(style); // needs to be placed after SymbolManager#addClickListener, // is fixed with 0.6.0 of annotations plugin @@ -302,6 +325,13 @@ private void enableCircleManager(@NonNull Style style) { } } + private void enableFillManager(@NonNull Style style) { + if (fillManager == null) { + fillManager = new FillManager(mapView, mapboxMap, style); + fillManager.addClickListener(MapboxMapController.this::onAnnotationClick); + } + } + @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { @@ -412,6 +442,30 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { result.success(null); break; } + case "fill#add": { + final FillBuilder fillBuilder = newFillBuilder(); + Convert.interpretFillOptions(call.argument("options"), fillBuilder); + final Fill fill = fillBuilder.build(); + final String fillId = String.valueOf(fill.getId()); + fills.put(fillId, new FillController(fill, true, this)); + result.success(fillId); + break; + } + case "fill#remove": { + final String fillId = call.argument("fill"); + removeFill(fillId); + result.success(null); + break; + } + case "fill#update": { + Log.e(TAG, "update fill"); + final String fillId = call.argument("fill"); + final FillController fill = fill(fillId); + Convert.interpretFillOptions(call.argument("options"), fill); + fill.update(fillManager); + result.success(null); + break; + } default: result.notImplemented(); } @@ -480,6 +534,13 @@ public void onCircleTapped(Circle circle) { methodChannel.invokeMethod("circle#onTap", arguments); } + @Override + public void onFillTapped(Fill fill) { + final Map arguments = new HashMap<>(2); + arguments.put("fill", String.valueOf(fill.getId())); + methodChannel.invokeMethod("fill#onTap", arguments); + } + @Override public boolean onMapClick(@NonNull LatLng point) { PointF pointf = mapboxMap.getProjection().toScreenLocation(point); @@ -507,6 +568,9 @@ public void dispose() { if (circleManager != null) { circleManager.onDestroy(); } + if (fillManager != null) { + fillManager.onDestroy(); + } mapView.onDestroy(); registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java new file mode 100644 index 000000000..27eb86425 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java @@ -0,0 +1,7 @@ +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.plugins.annotation.Fill; + +public interface OnFillTappedListener { + void onFillTapped(Fill fill); +} diff --git a/example/lib/main.dart b/example/lib/main.dart index d733f2c1c..12a85bc05 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,6 +9,7 @@ import 'move_camera.dart'; import 'page.dart'; import 'place_symbol.dart'; import 'place_circle.dart'; +import 'place_fill.dart'; import 'scrolling_map.dart'; final List _allPages = [ @@ -17,6 +18,7 @@ final List _allPages = [ MoveCameraPage(), PlaceSymbolPage(), PlaceCirclePage(), + PlaceFillPage(), ScrollingMapPage(), ]; diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart new file mode 100644 index 000000000..8794d6c57 --- /dev/null +++ b/example/lib/place_fill.dart @@ -0,0 +1,300 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; + +import 'page.dart'; + +class PlaceFillPage extends Page { + PlaceFillPage() : super(const Icon(Icons.check_circle), 'Place fill'); + + @override + Widget build(BuildContext context) { + return const PlaceFillBody(); + } +} + +class PlaceFillBody extends StatefulWidget { + const PlaceFillBody(); + + @override + State createState() => PlaceFillBodyState(); +} + +class PlaceFillBodyState extends State { + PlaceFillBodyState(); + + static final LatLng center = const LatLng(-33.86711, 151.1947171); + + MapboxMapController controller; + int _fillCount = 0; + Fill _selectedFill; + + void _onMapCreated(MapboxMapController controller) { + this.controller = controller; + controller.onFillTapped.add(_onFillTapped); + } + + @override + void dispose() { + controller?.onFillTapped?.remove(_onFillTapped); + super.dispose(); + } + + void _onFillTapped(Fill fill) { + if (_selectedFill != null) { + _updateSelectedFill( + const FillOptions(fillRadius: 60), + ); + } + setState(() { + _selectedFill = fill; + }); + _updateSelectedFill( + FillOptions( + fillRadius: 30, + ), + ); + } + + void _updateSelectedFill(FillOptions changes) { + controller.updateFill(_selectedFill, changes); + } + + void _add() { + controller.addFill( + FillOptions( + geometry: LatLng( + center.latitude + sin(_fillCount * pi / 6.0) / 20.0, + center.longitude + cos(_fillCount * pi / 6.0) / 20.0, + ), + fillColor: "#FF0000"), + ); + setState(() { + _fillCount += 1; + }); + } + + void _remove() { + controller.removeFill(_selectedFill); + setState(() { + _selectedFill = null; + _fillCount -= 1; + }); + } + + void _changePosition() { + final LatLng current = _selectedFill.options.geometry; + final Offset offset = Offset( + center.latitude - current.latitude, + center.longitude - current.longitude, + ); + _updateSelectedFill( + FillOptions( + geometry: LatLng( + center.latitude + offset.dy, + center.longitude + offset.dx, + ), + ), + ); + } + + void _changeDraggable() { + bool draggable = _selectedFill.options.draggable; + if (draggable == null) { + // default value + draggable = false; + } + _updateSelectedFill( + FillOptions( + draggable: !draggable, + ), + ); + } + + void _changeFillStrokeOpacity() { + double current = _selectedFill.options.fillStrokeOpacity; + if (current == null) { + // default value + current = 1.0; + } + + _updateSelectedFill( + FillOptions(fillStrokeOpacity: current < 0.1 ? 1.0 : current * 0.75), + ); + } + + void _changeFillStrokeWidth() { + double current = _selectedFill.options.fillStrokeWidth; + if (current == null) { + // default value + current = 0; + } + _updateSelectedFill(FillOptions(fillStrokeWidth: current == 0 ? 5.0 : 0)); + } + + Future _changeFillStrokeColor() async { + String current = _selectedFill.options.fillStrokeColor; + if (current == null) { + // default value + current = "#FFFFFF"; + } + + _updateSelectedFill( + FillOptions(fillStrokeColor: current == "#FFFFFF" ? "#FF0000" : "#FFFFFF"), + ); + } + + Future _changeFillOpacity() async { + double current = _selectedFill.options.fillOpacity; + if (current == null) { + // default value + current = 1.0; + } + + _updateSelectedFill( + FillOptions(fillOpacity: current < 0.1 ? 1.0 : current * 0.75), + ); + } + + Future _changeFillRadius() async { + double current = _selectedFill.options.fillRadius; + if (current == null) { + // default value + current = 0; + } + _updateSelectedFill( + FillOptions(fillRadius: current == 120.0 ? 30.0 : current + 30.0), + ); + } + + Future _changeFillColor() async { + String current = _selectedFill.options.fillColor; + if (current == null) { + // default value + current = "#FF0000"; + } + + _updateSelectedFill( + FillOptions( + fillColor: "#FFFF00"), + ); + } + + Future _changeFillBlur() async { + double current = _selectedFill.options.fillBlur; + if (current == null) { + // default value + current = 0; + } + _updateSelectedFill( + FillOptions(fillBlur: current == 0.75 ? 0 : 0.75), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: SizedBox( + width: 300.0, + height: 200.0, + child: MapboxMap( + onMapCreated: _onMapCreated, + initialCameraPosition: const CameraPosition( + target: LatLng(-33.852, 151.211), + zoom: 11.0, + ), + ), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + Column( + children: [ + FlatButton( + child: const Text('add'), + onPressed: (_fillCount == 12) ? null : _add, + ), + FlatButton( + child: const Text('remove'), + onPressed: (_selectedFill == null) ? null : _remove, + ), + ], + ), + Column( + children: [ + FlatButton( + child: const Text('change fill-opacity'), + onPressed: + (_selectedFill == null) ? null : _changeFillOpacity, + ), + FlatButton( + child: const Text('change fill-radius'), + onPressed: (_selectedFill == null) + ? null + : _changeFillRadius, + ), + FlatButton( + child: const Text('change fill-color'), + onPressed: + (_selectedFill == null) ? null : _changeFillColor, + ), + FlatButton( + child: const Text('change fill-blur'), + onPressed: + (_selectedFill == null) ? null : _changeFillBlur, + ), + FlatButton( + child: const Text('change fill-stroke-width'), + onPressed: + (_selectedFill == null) ? null : _changeFillStrokeWidth, + ), + FlatButton( + child: const Text('change fill-stroke-color'), + onPressed: (_selectedFill == null) + ? null + : _changeFillStrokeColor, + ), + FlatButton( + child: const Text('change fill-stroke-opacity'), + onPressed: (_selectedFill == null) + ? null + : _changeFillStrokeOpacity, + ), + FlatButton( + child: const Text('change position'), + onPressed: (_selectedFill == null) + ? null + : _changePosition, + ), + FlatButton( + child: const Text('toggle draggable'), + onPressed: (_selectedFill == null) + ? null + : _changeDraggable, + ), + ], + ), + ], + ) + ], + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index 5d20ad082..c593ddcfb 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -21,4 +21,5 @@ part 'src/mapbox_map.dart'; part 'src/location.dart'; part 'src/symbol.dart'; part 'src/circle.dart'; +part 'src/fill.dart'; part 'src/ui.dart'; diff --git a/lib/src/fill.dart b/lib/src/fill.dart new file mode 100644 index 000000000..c95aea871 --- /dev/null +++ b/lib/src/fill.dart @@ -0,0 +1,90 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of mapbox_gl; + +class Fill { + @visibleForTesting + Fill(this._id, this._options); + + /// A unique identifier for this fill. + /// + /// The identifier is an arbitrary unique string. + final String _id; + + String get id => _id; + + FillOptions _options; + + /// The fill configuration options most recently applied programmatically + /// via the map controller. + /// + /// The returned value does not reflect any changes made to the fill through + /// touch events. Add listeners to the owning map controller to track those. + FillOptions get options => _options; +} + +/// Configuration options for [Fill] instances. +/// +/// When used to change configuration, null values will be interpreted as +/// "do not change this configuration option". +class FillOptions { + /// Creates a set of fill configuration options. + /// + /// By default, every non-specified field is null, meaning no desire to change + /// fill defaults or current configuration. + const FillOptions({ + this.fillOpacity, + this.fillColor, + this.fillOutlineColor, + this.fillPattern, + this.geometry, + }); + + final double fillOpacity; + final String fillColor; + final String fillOutlineColor; + final String fillPattern; + final LatLng geometry; + + static const FillOptions defaultOptions = FillOptions( + fillOpacity: 1.0, + fillColor: "rgba(0, 0, 0, 1)", + fillOutlineColor: "rgba(0, 0, 0, 1)", + fillPattern: "pedestrian-polygon", + geometry: LatLng(0.0, 0.0), + ); + + FillOptions copyWith(FillOptions changes) { + if (changes == null) { + return this; + } + return FillOptions( + fillOpacity: changes.fillOpacity ?? fillOpacity, + fillColor: changes.fillColor ?? fillColor, + fillOutlineColor: changes.fillOutlineColor ?? fillOutlineColor, + fillPattern: changes.fillPattern ?? fillPattern, + geometry: changes.geometry ?? geometry, + ); + } + + dynamic _toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) {q + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('fill-opacity', fillOpacity); + addIfPresent('fill-color', fillColor); + addIfPresent('fill-outline-color', fillOutlineColor); + addIfPresent('fill-pattern', fillPattern); + addIfPresent('geometry', geometry?._toJson()); + return json; + } +} \ No newline at end of file From a2ca6e48946fea8f29c4bb42ec1aa0037416df09 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Sun, 7 Jun 2020 20:35:05 +0200 Subject: [PATCH 02/15] Resolved merge conflict. --- .../java/com/mapbox/mapboxgl/Convert.java | 52 ++- .../java/com/mapbox/mapboxgl/FillBuilder.java | 58 ++++ .../com/mapbox/mapboxgl/FillController.java | 74 +++++ .../com/mapbox/mapboxgl/FillOptionsSink.java | 27 ++ .../mapbox/mapboxgl/MapboxMapController.java | 65 +++- .../mapbox/mapboxgl/OnFillTappedListener.java | 7 + example/ios/Podfile | 83 +++-- example/lib/main.dart | 2 + example/lib/place_fill.dart | 305 ++++++++++++++++++ example/pubspec.yaml | 7 + lib/mapbox_gl.dart | 4 +- lib/src/controller.dart | 68 +++- .../lib/mapbox_gl_platform_interface.dart | 2 + .../lib/src/fill.dart | 94 ++++++ .../lib/src/mapbox_gl_platform_interface.dart | 12 + .../lib/src/method_channel_mapbox_gl.dart | 26 ++ mapbox_gl_platform_interface/pubspec.yaml | 2 +- 17 files changed, 845 insertions(+), 43 deletions(-) create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillController.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java create mode 100644 android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java create mode 100644 example/lib/place_fill.dart create mode 100644 mapbox_gl_platform_interface/lib/src/fill.dart diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index 85cd80e00..960307403 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -5,13 +5,12 @@ package com.mapbox.mapboxgl; import android.graphics.Point; - +import com.mapbox.geojson.Polygon; import com.mapbox.mapboxsdk.camera.CameraPosition; import com.mapbox.mapboxsdk.camera.CameraUpdate; import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; -import com.mapbox.mapboxsdk.log.Logger; import com.mapbox.mapboxsdk.maps.MapboxMap; import java.util.ArrayList; @@ -100,7 +99,7 @@ static CameraUpdate toCameraUpdate(Object o, MapboxMap mapboxMap, float density) case "bearingTo": return CameraUpdateFactory.bearingTo(toFloat(data.get(1))); case "tiltTo": - return CameraUpdateFactory.tiltTo(toFloat(data.get(1))); + return CameraUpdateFactory.tiltTo(toFloat(data.get(1))); default: throw new IllegalArgumentException("Cannot interpret " + o + " as CameraUpdate"); } @@ -477,4 +476,51 @@ static void interpretLineOptions(Object o, LineOptionsSink sink) { sink.setDraggable(toBoolean(draggable)); } } + static void interpretFillOptions(Object o, FillOptionsSink sink) { + final Map data = toMap(o); + final Object fillOpacity = data.get("fillOpacity"); + if (fillOpacity != null) { + sink.setFillOpacity(toFloat(fillOpacity)); + } + final Object fillColor = data.get("fillColor"); + if (fillColor != null) { + sink.setFillColor(toString(fillColor)); + } + final Object fillOutlineColor = data.get("fillOutlineColor"); + if (fillOutlineColor != null) { + sink.setFillOutlineColor(toString(fillOutlineColor)); + } + final Object fillPattern = data.get("fillPattern"); + if (fillPattern != null) { + sink.setFillPattern(toString(fillPattern)); + } + final Object geometry = data.get("geometry"); + if (geometry != null) { + sink.setGeometry(toLatLngListList(geometry)); + } + final Object draggable = data.get("draggable"); + if (draggable != null) { + sink.setDraggable(toBoolean(draggable)); + } + } + + private static List> toLatLngListList(Object o) { + if (o == null) { + return null; + } + // todo add conversion + return new ArrayList<>(); + } + + static Polygon interpretListLatLng(List> geometry) { + List> points = new ArrayList<>(geometry.size()); + for (List innerGeometry : geometry) { + List innerPoints = new ArrayList<>(innerGeometry.size()); + for (LatLng latLng : innerGeometry) { + innerPoints.add(com.mapbox.geojson.Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); + } + points.add(innerPoints); + } + return Polygon.fromLngLats(points); + } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java new file mode 100644 index 000000000..188d580fe --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java @@ -0,0 +1,58 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; +import com.mapbox.mapboxsdk.plugins.annotation.FillOptions; + +import java.util.List; + +class FillBuilder implements FillOptionsSink { + private final FillManager fillManager; + private final FillOptions fillOptions; + + FillBuilder(FillManager fillManager) { + this.fillManager = fillManager; + this.fillOptions = new FillOptions(); + } + + Fill build() { + return fillManager.create(fillOptions); + } + + @Override + public void setFillOpacity(float fillOpacity) { + fillOptions.withFillOpacity(fillOpacity); + } + + @Override + public void setFillColor(String fillColor) { + fillOptions.withFillColor(fillColor); + } + + @Override + public void setFillOutlineColor(String fillOutlineColor) { + fillOptions.withFillOutlineColor(fillOutlineColor); + } + + @Override + public void setFillPattern(String fillPattern) { + fillOptions.withFillPattern(fillPattern); + } + + @Override + public void setGeometry(List> geometry) { + fillOptions.withGeometry(Convert.interpretListLatLng(geometry)); + } + + @Override + public void setDraggable(boolean draggable) { + fillOptions.setDraggable(draggable); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillController.java b/android/src/main/java/com/mapbox/mapboxgl/FillController.java new file mode 100644 index 000000000..200b13f76 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillController.java @@ -0,0 +1,74 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import android.graphics.Color; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; + +import java.util.List; + +/** + * Controller of a single Fill on the map. + */ +class FillController implements FillOptionsSink { + private final Fill fill; + private final OnFillTappedListener onTappedListener; + private boolean consumeTapEvents; + + FillController(Fill fill, boolean consumeTapEvents, OnFillTappedListener onTappedListener) { + this.fill = fill; + this.consumeTapEvents = consumeTapEvents; + this.onTappedListener = onTappedListener; + } + + boolean onTap() { + if (onTappedListener != null) { + onTappedListener.onFillTapped(fill); + } + return consumeTapEvents; + } + + void remove(FillManager fillManager) { + fillManager.delete(fill); + } + + @Override + public void setFillOpacity(float fillOpacity) { + fill.setFillOpacity(fillOpacity); + } + + @Override + public void setFillColor(String fillColor) { + fill.setFillColor(Color.parseColor(fillColor)); + } + + @Override + public void setFillOutlineColor(String fillOutlineColor) { + fill.setFillOutlineColor(Color.parseColor(fillOutlineColor)); + } + + @Override + public void setFillPattern(String fillPattern) { + fill.setFillPattern(fillPattern); + } + + @Override + public void setGeometry(List> geometry) { + fill.setGeometry(Convert.interpretListLatLng(geometry)); + } + + @Override + public void setDraggable(boolean draggable) { + fill.setDraggable(draggable); + } + + public void update(FillManager fillManager) { + fillManager.update(fill); + } +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java new file mode 100644 index 000000000..849788103 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/FillOptionsSink.java @@ -0,0 +1,27 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.geometry.LatLng; + +import java.util.List; + +/** Receiver of Fill configuration options. */ +interface FillOptionsSink { + + void setFillOpacity(float fillOpacity); + + void setFillColor(String fillColor); + + void setFillOutlineColor(String fillOutlineColor); + + void setFillPattern(String fillPattern); + + void setGeometry(List> geometry); + + void setDraggable(boolean draggable); +} diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index aa55f5ea9..c6d7648a8 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -50,10 +50,13 @@ import com.mapbox.mapboxsdk.maps.MapboxMapOptions; import com.mapbox.mapboxsdk.maps.Projection; import com.mapbox.mapboxsdk.offline.OfflineManager; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; import com.mapbox.mapboxsdk.maps.Style; import com.mapbox.mapboxsdk.plugins.annotation.Annotation; import com.mapbox.mapboxsdk.plugins.annotation.Circle; import com.mapbox.mapboxsdk.plugins.annotation.CircleManager; +import com.mapbox.mapboxsdk.plugins.annotation.Fill; +import com.mapbox.mapboxsdk.plugins.annotation.FillManager; import com.mapbox.mapboxsdk.plugins.annotation.OnAnnotationClickListener; import com.mapbox.mapboxsdk.plugins.annotation.Symbol; import com.mapbox.mapboxsdk.plugins.annotation.SymbolManager; @@ -93,11 +96,12 @@ final class MapboxMapController MapboxMap.OnMapLongClickListener, MapboxMapOptionsSink, MethodChannel.MethodCallHandler, - com.mapbox.mapboxsdk.maps.OnMapReadyCallback, + OnMapReadyCallback, OnCameraTrackingChangedListener, OnSymbolTappedListener, OnLineTappedListener, OnCircleTappedListener, + OnFillTappedListener, PlatformView { private static final String TAG = "MapboxMapController"; private final int id; @@ -109,9 +113,11 @@ final class MapboxMapController private final Map symbols; private final Map lines; private final Map circles; + private final Map fills; private SymbolManager symbolManager; private LineManager lineManager; private CircleManager circleManager; + private FillManager fillManager; private boolean trackCameraPosition = false; private boolean myLocationEnabled = false; private int myLocationTrackingMode = 0; @@ -144,6 +150,7 @@ final class MapboxMapController this.symbols = new HashMap<>(); this.lines = new HashMap<>(); this.circles = new HashMap<>(); + this.fills = new HashMap<>(); this.density = context.getResources().getDisplayMetrics().density; methodChannel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/mapbox_maps_" + id); @@ -278,7 +285,22 @@ private void removeCircle(String circleId) { private CircleController circle(String circleId) { final CircleController circle = circles.get(circleId); if (circle == null) { - throw new IllegalArgumentException("Unknown symbol: " + circleId); + throw new IllegalArgumentException("Unknown circle: " + circleId); + } + return circle; + } + + private void removeFill(String fillId) { + final FillController fillController = fills.remove(fillId); + if (fillController != null) { + fillController.remove(fillManager); + } + } + + private CircleController fill(String fillId) { + final CircleController circle = circles.get(fillId); + if (circle == null) { + throw new IllegalArgumentException("Unknown fill: " + fillId); } return circle; } @@ -325,6 +347,7 @@ public void onStyleLoaded(@NonNull Style style) { enableLineManager(style); enableSymbolManager(style); enableCircleManager(style); + enableFillManager(style); if (myLocationEnabled) { enableLocationComponent(style); } @@ -389,6 +412,13 @@ private void enableCircleManager(@NonNull Style style) { } } + private void enableFillManager(@NonNull Style style) { + if (fillManager == null) { + fillManager = new FillManager(mapView, mapboxMap, style); + fillManager.addClickListener(MapboxMapController.this::onAnnotationClick); + } + } + @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { @@ -683,6 +713,27 @@ public void onFailure(@NonNull Exception exception) { result.error("STYLE IS NULL", "The style is null. Has onStyleLoaded() already been invoked?", null); } style.addImage(call.argument("name"), BitmapFactory.decodeByteArray(call.argument("bytes"),0,call.argument("length")), call.argument("sdf")); + case "fill#add": { + final FillBuilder fillBuilder = newFillBuilder(); + Convert.interpretFillOptions(call.argument("options"), fillBuilder); + final Fill fill = fillBuilder.build(); + final String fillId = String.valueOf(fill.getId()); + fills.put(fillId, new FillController(fill, true, this)); + result.success(fillId); + break; + } + case "fill#remove": { + final String fillId = call.argument("fill"); + removeFill(fillId); + result.success(null); + break; + } + case "fill#update": { + Log.e(TAG, "update fill"); + final String fillId = call.argument("fill"); + final FillController fill = fill(fillId); + Convert.interpretFillOptions(call.argument("options"), fill); + fill.update(fillManager); result.success(null); break; } @@ -772,6 +823,13 @@ public void onCircleTapped(Circle circle) { methodChannel.invokeMethod("circle#onTap", arguments); } + @Override + public void onFillTapped(Fill fill) { + final Map arguments = new HashMap<>(2); + arguments.put("fill", String.valueOf(fill.getId())); + methodChannel.invokeMethod("fill#onTap", arguments); + } + @Override public boolean onMapClick(@NonNull LatLng point) { PointF pointf = mapboxMap.getProjection().toScreenLocation(point); @@ -814,6 +872,9 @@ public void dispose() { if (circleManager != null) { circleManager.onDestroy(); } + if (fillManager != null) { + fillManager.onDestroy(); + } mapView.onDestroy(); registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this); diff --git a/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java new file mode 100644 index 000000000..27eb86425 --- /dev/null +++ b/android/src/main/java/com/mapbox/mapboxgl/OnFillTappedListener.java @@ -0,0 +1,7 @@ +package com.mapbox.mapboxgl; + +import com.mapbox.mapboxsdk.plugins.annotation.Fill; + +public interface OnFillTappedListener { + void onFillTapped(Fill fill); +} diff --git a/example/ios/Podfile b/example/ios/Podfile index a563f857a..17ffc678f 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -15,58 +15,71 @@ def parse_KV_file(file, separator='=') if !File.exists? file_abs_path return []; end - pods_ary = [] + generated_key_values = {} skip_line_start_symbols = ["#", "/"] - File.foreach(file_abs_path) { |line| - next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } - plugin = line.split(pattern=separator) - if plugin.length == 2 - podname = plugin[0].strip() - path = plugin[1].strip() - podpath = File.expand_path("#{path}", file_abs_path) - pods_ary.push({:name => podname, :path => podpath}); - else - puts "Invalid plugin specification: #{line}" - end - } - return pods_ary + File.foreach(file_abs_path) do |line| + next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } + plugin = line.split(pattern=separator) + if plugin.length == 2 + podname = plugin[0].strip() + path = plugin[1].strip() + podpath = File.expand_path("#{path}", file_abs_path) + generated_key_values[podname] = podpath + else + puts "Invalid plugin specification: #{line}" + end + end + generated_key_values end target 'Runner' do + # Flutter Pod use_frameworks! - # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock - # referring to absolute paths on developers' machines. - system('rm -rf .symlinks') - system('mkdir -p .symlinks/plugins') + copied_flutter_dir = File.join(__dir__, 'Flutter') + copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') + copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') + unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) + # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. + # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. + # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. - # Flutter Pods - generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') - if generated_xcode_build_settings.empty? - puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." - end - generated_xcode_build_settings.map { |p| - if p[:name] == 'FLUTTER_FRAMEWORK_DIR' - symlink = File.join('.symlinks', 'flutter') - File.symlink(File.dirname(p[:path]), symlink) - pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) + generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') + unless File.exist?(generated_xcode_build_settings_path) + raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) + cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; + + unless File.exist?(copied_framework_path) + FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) end - } + unless File.exist?(copied_podspec_path) + FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) + end + end + + # Keep pod path relative so it can be checked into Podfile.lock. + pod 'Flutter', :path => 'Flutter' # Plugin Pods + + # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock + # referring to absolute paths on developers' machines. + system('rm -rf .symlinks') + system('mkdir -p .symlinks/plugins') plugin_pods = parse_KV_file('../.flutter-plugins') - plugin_pods.map { |p| - symlink = File.join('.symlinks', 'plugins', p[:name]) - File.symlink(p[:path], symlink) - pod p[:name], :path => File.join(symlink, 'ios') - } + plugin_pods.each do |name, path| + symlink = File.join('.symlinks', 'plugins', name) + File.symlink(path, symlink) + pod name, :path => File.join(symlink, 'ios') + end end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' - config.build_settings['SWIFT_VERSION'] = '4.2' end end end diff --git a/example/lib/main.dart b/example/lib/main.dart index d2ecf286d..ea05643b3 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -15,6 +15,7 @@ import 'move_camera.dart'; import 'page.dart'; import 'place_circle.dart'; import 'place_symbol.dart'; +import 'place_fill.dart'; import 'scrolling_map.dart'; final List _allPages = [ @@ -25,6 +26,7 @@ final List _allPages = [ PlaceSymbolPage(), LinePage(), PlaceCirclePage(), + PlaceFillPage(), ScrollingMapPage(), ]; diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart new file mode 100644 index 000000000..87c07035d --- /dev/null +++ b/example/lib/place_fill.dart @@ -0,0 +1,305 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:mapbox_gl/mapbox_gl.dart'; + +import 'page.dart'; + +class PlaceFillPage extends ExamplePage { + PlaceFillPage() : super(const Icon(Icons.check_circle), 'Place fill'); + + @override + Widget build(BuildContext context) { + return const PlaceFillBody(); + } +} + +class PlaceFillBody extends StatefulWidget { + const PlaceFillBody(); + + @override + State createState() => PlaceFillBodyState(); +} + +class PlaceFillBodyState extends State { + PlaceFillBodyState(); + + static final LatLng center = const LatLng(-33.86711, 151.1947171); + + MapboxMapController controller; + int _fillCount = 0; + Fill _selectedFill; + + void _onMapCreated(MapboxMapController controller) { + this.controller = controller; + controller.onFillTapped.add(_onFillTapped); + } + + @override + void dispose() { + controller?.onFillTapped?.remove(_onFillTapped); + super.dispose(); + } + + void _onFillTapped(Fill fill) { + // if (_selectedFill != null) { + // _updateSelectedFill( + // const FillOptions(fillRadius: 60), + // ); + // } + setState(() { + _selectedFill = fill; + }); + // _updateSelectedFill( + // FillOptions( + // fillRadius: 30, + // ), + // ); + } + + void _updateSelectedFill(FillOptions changes) { + controller.updateFill(_selectedFill, changes); + } + + void _add() { + controller.addFill( + FillOptions( + geometry: LatLng( + center.latitude + sin(_fillCount * pi / 6.0) / 20.0, + center.longitude + cos(_fillCount * pi / 6.0) / 20.0, + ), + fillColor: "#FF0000"), + ); + setState(() { + _fillCount += 1; + }); + } + + void _remove() { + controller.removeFill(_selectedFill); + setState(() { + _selectedFill = null; + _fillCount -= 1; + }); + } + + void _changePosition() { + final LatLng current = _selectedFill.options.geometry; + final Offset offset = Offset( + center.latitude - current.latitude, + center.longitude - current.longitude, + ); + _updateSelectedFill( + FillOptions( + geometry: LatLng( + center.latitude + offset.dy, + center.longitude + offset.dx, + ), + ), + ); + } + + void _changeDraggable() { + bool draggable = _selectedFill.options.draggable; + if (draggable == null) { + // default value + draggable = false; + } + _updateSelectedFill( + FillOptions( + draggable: !draggable, + ), + ); + } + + void _changeFillStrokeOpacity() { + //TODO: Implement fillStrokeOpacity. + // double current = _selectedFill.options.fillStrokeOpacity; + // if (current == null) { + // // default value + // current = 1.0; + // } + + // _updateSelectedFill( + // FillOptions(fillStrokeOpacity: current < 0.1 ? 1.0 : current * 0.75), + // ); + } + + void _changeFillStrokeWidth() { + //TODO: Implement fillStrokeWidth + // double current = _selectedFill.options.fillStrokeWidth; + // if (current == null) { + // // default value + // current = 0; + // } + // _updateSelectedFill(FillOptions(fillStrokeWidth: current == 0 ? 5.0 : 0)); + } + + Future _changeFillStrokeColor() async { + //TODO: Implement fillStrokeColor + // String current = _selectedFill.options.fillStrokeColor; + // if (current == null) { + // // default value + // current = "#FFFFFF"; + // } + + // _updateSelectedFill( + // FillOptions(fillStrokeColor: current == "#FFFFFF" ? "#FF0000" : "#FFFFFF"), + // ); + } + + Future _changeFillOpacity() async { + double current = _selectedFill.options.fillOpacity; + if (current == null) { + // default value + current = 1.0; + } + + _updateSelectedFill( + FillOptions(fillOpacity: current < 0.1 ? 1.0 : current * 0.75), + ); + } + + Future _changeFillRadius() async { + // TODO: Implement fillRadius + // double current = _selectedFill.options.fillRadius; + // if (current == null) { + // // default value + // current = 0; + // } + // _updateSelectedFill( + // FillOptions(fillRadius: current == 120.0 ? 30.0 : current + 30.0), + // ); + } + + Future _changeFillColor() async { + String current = _selectedFill.options.fillColor; + if (current == null) { + // default value + current = "#FF0000"; + } + + _updateSelectedFill( + FillOptions( + fillColor: "#FFFF00"), + ); + } + + Future _changeFillBlur() async { + // TODO: Implement fillBlur + // double current = _selectedFill.options.fillBlur; + // if (current == null) { + // // default value + // current = 0; + // } + // _updateSelectedFill( + // FillOptions(fillBlur: current == 0.75 ? 0 : 0.75), + // ); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center( + child: SizedBox( + width: 300.0, + height: 200.0, + child: MapboxMap( + onMapCreated: _onMapCreated, + initialCameraPosition: const CameraPosition( + target: LatLng(-33.852, 151.211), + zoom: 11.0, + ), + ), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + Column( + children: [ + FlatButton( + child: const Text('add'), + onPressed: (_fillCount == 12) ? null : _add, + ), + FlatButton( + child: const Text('remove'), + onPressed: (_selectedFill == null) ? null : _remove, + ), + ], + ), + Column( + children: [ + FlatButton( + child: const Text('change fill-opacity'), + onPressed: + (_selectedFill == null) ? null : _changeFillOpacity, + ), + FlatButton( + child: const Text('change fill-radius'), + onPressed: (_selectedFill == null) + ? null + : _changeFillRadius, + ), + FlatButton( + child: const Text('change fill-color'), + onPressed: + (_selectedFill == null) ? null : _changeFillColor, + ), + FlatButton( + child: const Text('change fill-blur'), + onPressed: + (_selectedFill == null) ? null : _changeFillBlur, + ), + FlatButton( + child: const Text('change fill-stroke-width'), + onPressed: + (_selectedFill == null) ? null : _changeFillStrokeWidth, + ), + FlatButton( + child: const Text('change fill-stroke-color'), + onPressed: (_selectedFill == null) + ? null + : _changeFillStrokeColor, + ), + FlatButton( + child: const Text('change fill-stroke-opacity'), + onPressed: (_selectedFill == null) + ? null + : _changeFillStrokeOpacity, + ), + FlatButton( + child: const Text('change position'), + onPressed: (_selectedFill == null) + ? null + : _changePosition, + ), + FlatButton( + child: const Text('toggle draggable'), + onPressed: (_selectedFill == null) + ? null + : _changeDraggable, + ), + ], + ), + ], + ) + ], + ), + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6eeae4cca..ff7d9c996 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,7 @@ name: mapbox_gl_example description: Demonstrates how to use the mapbox_gl plugin. publish_to: 'none' +version: 1.0.0+1 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" @@ -15,6 +16,12 @@ dependencies: location: ^2.5.3 http: +dependency_overrides: + mapbox_gl_platform_interface: + path: ../mapbox_gl_platform_interface + mapbox_gl_web: + path: ../mapbox_gl_web + dev_dependencies: flutter_test: sdk: flutter diff --git a/lib/mapbox_gl.dart b/lib/mapbox_gl.dart index b856b363d..0a95b0d35 100644 --- a/lib/mapbox_gl.dart +++ b/lib/mapbox_gl.dart @@ -31,7 +31,9 @@ export 'package:mapbox_gl_platform_interface/mapbox_gl_platform_interface.dart' Circle, CircleOptions, Line, - LineOptions; + LineOptions, + Fill, + FillOptions; part 'src/bitmap.dart'; part 'src/controller.dart'; diff --git a/lib/src/controller.dart b/lib/src/controller.dart index 3f5b8ece9..ec4c396bc 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -162,6 +162,9 @@ class MapboxMapController extends ChangeNotifier { /// Callbacks to receive tap events for symbols placed on this map. final ArgumentCallbacks onCircleTapped = ArgumentCallbacks(); + /// Callbacks to receive tap events for fills placed on this map. + final ArgumentCallbacks onFillTapped = ArgumentCallbacks(); + /// Callbacks to receive tap events for info windows on symbols final ArgumentCallbacks onInfoWindowTapped = ArgumentCallbacks(); @@ -183,10 +186,16 @@ class MapboxMapController extends ChangeNotifier { /// The current set of circles on this map. /// - /// The returned set will be a detached snapshot of the symbols collection. + /// The returned set will be a detached snapshot of the circles collection. Set get circles => Set.from(_circles.values); final Map _circles = {}; + /// The current set of fills on this map. + /// + /// The returned set will be a detached snapshot of the fills collection. + Set get fills => Set.from(_fills.values); + final Map _fills = {}; + /// True if the map camera is currently moving. bool get isCameraMoving => _isCameraMoving; bool _isCameraMoving = false; @@ -526,6 +535,63 @@ class MapboxMapController extends ChangeNotifier { _circles.remove(id); } + /// Adds a fill to the map, configured using the specified custom [options]. + /// + /// Change listeners are notified once the fill has been added on the + /// platform side. + /// + /// The returned [Future] completes with the added fill once listeners have + /// been notified. + Future addFill(FillOptions options, [Map data]) async { + final FillOptions effectiveOptions = + FillOptions.defaultOptions.copyWith(options); + final fill = await MapboxGlPlatform.instance.addFill(effectiveOptions); + _fills[fill.id] = fill; + notifyListeners(); + return fill; + } + + /// Updates the specified [fill] with the given [changes]. The fill must + /// be a current member of the [fills] set. + /// + /// Change listeners are notified once the fill has been updated on the + /// platform side. + /// + /// The returned [Future] completes once listeners have been notified. + Future updateFill(Fill fill, FillOptions changes) async { + assert(fill != null); + assert(_fills[fill.id] == fill); + assert(changes != null); + await MapboxGlPlatform.instance.updateFill(fill, changes); + fill.options = fill.options.copyWith(changes); + notifyListeners(); + } + + /// Removes the specified [fill] from the map. The fill must be a current + /// member of the [fills] set. + /// + /// Change listeners are notified once the fill has been removed on the + /// platform side. + /// + /// The returned [Future] completes once listeners have been notified. + Future removeFill(Fill fill) async { + assert(fill != null); + assert(_fills[fill.id] == fill); + await _removeFill(fill.id); + notifyListeners(); + } + + /// Helper method to remove a single fill from the map. Consumed by + /// [removeFill] and [clearFills]. + /// + /// The returned [Future] completes once the fill has been removed from + /// [_fills]. + Future _removeFill(String id) async { + await MapboxGlPlatform.instance.removeFill(id); + + _fills.remove(id); + } + Future queryRenderedFeatures( Point point, List layerIds, String filter) async { return MapboxGlPlatform.instance diff --git a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart index 0abe3163a..f5c1ed938 100644 --- a/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/mapbox_gl_platform_interface.dart @@ -15,5 +15,7 @@ part 'src/line.dart'; part 'src/location.dart'; part 'src/method_channel_mapbox_gl.dart'; part 'src/symbol.dart'; +part 'src/fill.dart'; part 'src/ui.dart'; part 'src/mapbox_gl_platform_interface.dart'; + diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart new file mode 100644 index 000000000..ae1568b60 --- /dev/null +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -0,0 +1,94 @@ +// This file is generated. + +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of mapbox_gl_platform_interface; + +class Fill { + @visibleForTesting + Fill(this._id, this.options, [this._data]); + + /// A unique identifier for this fill. + /// + /// The identifier is an arbitrary unique string. + final String _id; + String get id => _id; + + final Map _data; + Map get data => _data; + + /// The fill configuration options most recently applied programmatically + /// via the map controller. + /// + /// The returned value does not reflect any changes made to the fill through + /// touch events. Add listeners to the owning map controller to track those. + FillOptions options; +} + +/// Configuration options for [Fill] instances. +/// +/// When used to change configuration, null values will be interpreted as +/// "do not change this configuration option". +class FillOptions { + /// Creates a set of fill configuration options. + /// + /// By default, every non-specified field is null, meaning no desire to change + /// fill defaults or current configuration. + const FillOptions({ + this.fillOpacity, + this.fillColor, + this.fillOutlineColor, + this.fillPattern, + this.geometry, + this.draggable + }); + + final double fillOpacity; + final String fillColor; + final String fillOutlineColor; + final String fillPattern; + final LatLng geometry; + final bool draggable; + + static const FillOptions defaultOptions = FillOptions( + fillOpacity: 1.0, + fillColor: "rgba(0, 0, 0, 1)", + fillOutlineColor: "rgba(0, 0, 0, 1)", + fillPattern: "pedestrian-polygon", + geometry: LatLng(0.0, 0.0), + ); + + FillOptions copyWith(FillOptions changes) { + if (changes == null) { + return this; + } + return FillOptions( + fillOpacity: changes.fillOpacity ?? fillOpacity, + fillColor: changes.fillColor ?? fillColor, + fillOutlineColor: changes.fillOutlineColor ?? fillOutlineColor, + fillPattern: changes.fillPattern ?? fillPattern, + geometry: changes.geometry ?? geometry, + draggable: changes.draggable ?? draggable, + ); + } + + dynamic toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, dynamic value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('fill-opacity', fillOpacity); + addIfPresent('fill-color', fillColor); + addIfPresent('fill-outline-color', fillOutlineColor); + addIfPresent('fill-pattern', fillPattern); + addIfPresent('geometry', geometry?.toJson()); + addIfPresent('draggable', draggable); + return json; + } +} \ No newline at end of file diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index 93882a729..cfa72a889 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -146,6 +146,18 @@ abstract class MapboxGlPlatform { throw UnimplementedError('removeCircle() has not been implemented.'); } + Future addFill(FillOptions options, [Map data]) async { + throw UnimplementedError('addFill() has not been implemented.'); + } + + FutureupdateFill(Fill fill, FillOptions changes) async { + throw UnimplementedError('updateFill() has not been implemented.'); + } + + Future removeFill(String fillId) async { + throw UnimplementedError('removeFill() has not been implemented.'); + } + Future queryRenderedFeatures( Point point, List layerIds, String filter) async { throw UnimplementedError( diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index 2723a2538..54a5c6faa 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -268,6 +268,32 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { }); } + @override + Future addFill(FillOptions options, [Map data]) async { + final String fillId = await _channel.invokeMethod( + 'fill#add', + { + 'options': options.toJson(), + }, + ); + return Fill(fillId, options, data); + } + + @override + Future updateFill(Fill fill, FillOptions changes) async { + await _channel.invokeMethod('fill#update', { + 'fill': fill.id, + 'options': changes.toJson(), + }); + } + + @override + Future removeFill(String fillId) async { + await _channel.invokeMethod('fill#remove', { + 'fill': fillId, + }); + } + @override Future queryRenderedFeatures( Point point, List layerIds, String filter) async { diff --git a/mapbox_gl_platform_interface/pubspec.yaml b/mapbox_gl_platform_interface/pubspec.yaml index 62d650818..7362b683a 100644 --- a/mapbox_gl_platform_interface/pubspec.yaml +++ b/mapbox_gl_platform_interface/pubspec.yaml @@ -10,4 +10,4 @@ dependencies: environment: sdk: ">=2.1.0 <3.0.0" - flutter: ">=1.9.1+hotfix.4 <2.0.0" + flutter: ">=1.10.0 <2.0.0" From e46165b6d161ad1d2adaab4f0daa6298ae0a42a9 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Sun, 21 Jun 2020 18:16:16 +0200 Subject: [PATCH 03/15] A first working version for ios (after some extensive rebasing). --- example/lib/place_fill.dart | 39 ++++++++------- ios/Classes/Convert.swift | 19 +++++++ ios/Classes/MapboxMapController.swift | 50 +++++++++++++++++++ lib/src/controller.dart | 13 +++-- .../lib/src/fill.dart | 23 +++++---- .../lib/src/mapbox_gl_platform_interface.dart | 3 ++ .../lib/src/method_channel_mapbox_gl.dart | 6 +++ 7 files changed, 121 insertions(+), 32 deletions(-) diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 87c07035d..b016f71d0 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -51,6 +51,7 @@ class PlaceFillBodyState extends State { // const FillOptions(fillRadius: 60), // ); // } + print("Fill: ${fill.id}"); setState(() { _selectedFill = fill; }); @@ -68,10 +69,12 @@ class PlaceFillBodyState extends State { void _add() { controller.addFill( FillOptions( - geometry: LatLng( - center.latitude + sin(_fillCount * pi / 6.0) / 20.0, - center.longitude + cos(_fillCount * pi / 6.0) / 20.0, - ), + geometry: [ + LatLng(-32.86711, 152.1947171), + LatLng(-33.86711, 151.1947171), + LatLng(-32.86711, 151.1947171), + LatLng(-33.86711, 152.1947171), + ], fillColor: "#FF0000"), ); setState(() { @@ -88,19 +91,19 @@ class PlaceFillBodyState extends State { } void _changePosition() { - final LatLng current = _selectedFill.options.geometry; - final Offset offset = Offset( - center.latitude - current.latitude, - center.longitude - current.longitude, - ); - _updateSelectedFill( - FillOptions( - geometry: LatLng( - center.latitude + offset.dy, - center.longitude + offset.dx, - ), - ), - ); + // final LatLng current = _selectedFill.options.geometry; + // final Offset offset = Offset( + // center.latitude - current.latitude, + // center.longitude - current.longitude, + // ); + // _updateSelectedFill( + // FillOptions( + // geometry: LatLng( + // center.latitude + offset.dy, + // center.longitude + offset.dx, + // ), + // ), + // ); } void _changeDraggable() { @@ -215,7 +218,7 @@ class PlaceFillBodyState extends State { onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), - zoom: 11.0, + zoom: 7.0, ), ), ), diff --git a/ios/Classes/Convert.swift b/ios/Classes/Convert.swift index 84b6c4de8..1969d5af4 100644 --- a/ios/Classes/Convert.swift +++ b/ios/Classes/Convert.swift @@ -314,4 +314,23 @@ class Convert { delegate.isDraggable = draggable } } + + class func interpretFillOptions(options: Any?, delegate: MGLPolygonStyleAnnotation) { + guard let options = options as? [String: Any] else { return } + if let fillOpacity = options["fillOpacity"] as? CGFloat { + delegate.fillOpacity = fillOpacity + } + if let fillColor = options["fillColor"] as? String { + delegate.fillColor = UIColor(hexString: fillColor) ?? UIColor.black + } + if let fillOutlineColor = options["fillOutlineColor"] as? String { + delegate.fillOutlineColor = UIColor(hexString: fillOutlineColor) ?? UIColor.black + } + if let fillPattern = options["fillPattern"] as? String { + delegate.fillPattern = fillPattern + } + if let draggable = options["draggable"] as? Bool { + delegate.isDraggable = draggable + } + } } diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index c3815460f..ee510d398 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -20,6 +20,7 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma private var symbolAnnotationController: MGLSymbolAnnotationController? private var circleAnnotationController: MGLCircleAnnotationController? private var lineAnnotationController: MGLLineAnnotationController? + private var fillAnnotationController: MGLPolygonAnnotationController? func view() -> UIView { return mapView @@ -372,6 +373,49 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma } } result(reply) + case "fill#add": + guard let fillAnnotationController = fillAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + // Parse geometry + if let options = arguments["options"] as? [String: Any], + let geometry = options["geometry"] as? [[Double]] { + // Convert geometry to coordinate and create polygon. + var fillCoordinates: [CLLocationCoordinate2D] = [] + for coordinate in geometry { + fillCoordinates.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) + } + let fill = MGLPolygonStyleAnnotation(coordinates: fillCoordinates, count: UInt(fillCoordinates.count)) + Convert.interpretFillOptions(options: arguments["options"], delegate: fill) + fillAnnotationController.addStyleAnnotation(fill) + result(fill.identifier) + } else { + result(nil) + } + case "fill#update": + guard let fillAnnotationController = fillAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let fillId = arguments["fill"] as? String else { return } + + for fill in fillAnnotationController.styleAnnotations() { + if fill.identifier == fillId { + Convert.interpretFillOptions(options: arguments["options"], delegate: fill as! MGLPolygonStyleAnnotation) + fillAnnotationController.updateStyleAnnotation(fill) + break; + } + } + result(nil) + case "fill#remove": + guard let fillAnnotationController = fillAnnotationController else { return } + guard let arguments = methodCall.arguments as? [String: Any] else { return } + guard let fillId = arguments["fill"] as? String else { return } + + for fill in fillAnnotationController.styleAnnotations() { + if fill.identifier == fillId { + fillAnnotationController.removeStyleAnnotation(fill) + break; + } + } + result(nil) case "style#addImage": guard let arguments = methodCall.arguments as? [String: Any] else { return } guard let name = arguments["name"] as? String else { return } @@ -485,6 +529,8 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma channel.invokeMethod("circle#onTap", arguments: ["circle" : "\(circle.identifier)"]) } else if let line = styleAnnotation as? MGLLineStyleAnnotation { channel.invokeMethod("line#onTap", arguments: ["line" : "\(line.identifier)"]) + } else if let fill = styleAnnotation as? MGLPolygonStyleAnnotation { + channel.invokeMethod("fill#onTap", arguments: ["fill" : "\(fill.identifier)"]) } } @@ -521,6 +567,10 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma circleAnnotationController!.annotationsInteractionEnabled = true circleAnnotationController?.delegate = self + fillAnnotationController = MGLPolygonAnnotationController(mapView: self.mapView) + fillAnnotationController!.annotationsInteractionEnabled = true + fillAnnotationController?.delegate = self + mapReadyResult?(nil) if let channel = channel { channel.invokeMethod("map#onStyleLoaded", arguments: nil) diff --git a/lib/src/controller.dart b/lib/src/controller.dart index a5c878549..9b113af4d 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -73,6 +73,13 @@ class MapboxMapController extends ChangeNotifier { } }); + MapboxGlPlatform.getInstance(_id).onFillTappedPlatform.add((fillId) { + final Fill fill = _fills[fillId]; + if (fill != null) { + onFillTapped(fill); + } + }); + MapboxGlPlatform.getInstance(_id).onCameraMoveStartedPlatform.add((_) { _isCameraMoving = true; notifyListeners(); @@ -601,7 +608,7 @@ class MapboxMapController extends ChangeNotifier { Future addFill(FillOptions options, [Map data]) async { final FillOptions effectiveOptions = FillOptions.defaultOptions.copyWith(options); - final fill = await MapboxGlPlatform.instance.addFill(effectiveOptions); + final fill = await MapboxGlPlatform.getInstance(_id).addFill(effectiveOptions); _fills[fill.id] = fill; notifyListeners(); return fill; @@ -618,7 +625,7 @@ class MapboxMapController extends ChangeNotifier { assert(fill != null); assert(_fills[fill.id] == fill); assert(changes != null); - await MapboxGlPlatform.instance.updateFill(fill, changes); + await MapboxGlPlatform.getInstance(_id).updateFill(fill, changes); fill.options = fill.options.copyWith(changes); notifyListeners(); } @@ -643,7 +650,7 @@ class MapboxMapController extends ChangeNotifier { /// The returned [Future] completes once the fill has been removed from /// [_fills]. Future _removeFill(String id) async { - await MapboxGlPlatform.instance.removeFill(id); + await MapboxGlPlatform.getInstance(_id).removeFill(id); _fills.remove(id); } diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart index ae1568b60..6bce8cc88 100644 --- a/mapbox_gl_platform_interface/lib/src/fill.dart +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -49,15 +49,15 @@ class FillOptions { final String fillColor; final String fillOutlineColor; final String fillPattern; - final LatLng geometry; + final List geometry; final bool draggable; static const FillOptions defaultOptions = FillOptions( - fillOpacity: 1.0, - fillColor: "rgba(0, 0, 0, 1)", - fillOutlineColor: "rgba(0, 0, 0, 1)", - fillPattern: "pedestrian-polygon", - geometry: LatLng(0.0, 0.0), + // fillOpacity: 1.0, + // fillColor: "rgba(0, 0, 0, 1)", + // fillOutlineColor: "rgba(0, 0, 0, 1)", + // fillPattern: "pedestrian-polygon", + // geometry: LatLng(0.0, 0.0), ); FillOptions copyWith(FillOptions changes) { @@ -83,11 +83,12 @@ class FillOptions { } } - addIfPresent('fill-opacity', fillOpacity); - addIfPresent('fill-color', fillColor); - addIfPresent('fill-outline-color', fillOutlineColor); - addIfPresent('fill-pattern', fillPattern); - addIfPresent('geometry', geometry?.toJson()); + addIfPresent('fillOpacity', fillOpacity); + addIfPresent('fillColor', fillColor); + addIfPresent('fillOutlineColor', fillOutlineColor); + addIfPresent('fillPattern', fillPattern); + addIfPresent('geometry', + geometry?.map((LatLng latLng) => latLng.toJson())?.toList()); addIfPresent('draggable', draggable); return json; } diff --git a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart index ce5371ac2..fe85e6d0e 100644 --- a/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart +++ b/mapbox_gl_platform_interface/lib/src/mapbox_gl_platform_interface.dart @@ -34,6 +34,9 @@ abstract class MapboxGlPlatform { final ArgumentCallbacks onCircleTappedPlatform = ArgumentCallbacks(); + final ArgumentCallbacks onFillTappedPlatform = + ArgumentCallbacks(); + final ArgumentCallbacks onCameraMoveStartedPlatform = ArgumentCallbacks(); diff --git a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart index ee40c79f8..ce792a4b4 100644 --- a/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart +++ b/mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart @@ -29,6 +29,12 @@ class MethodChannelMapboxGl extends MapboxGlPlatform { onCircleTappedPlatform(circleId); } break; + case 'fill#onTap': + final String fillId = call.arguments['fill']; + if (fillId != null) { + onFillTappedPlatform(fillId); + } + break; case 'camera#onMoveStarted': onCameraMoveStartedPlatform(null); break; From 767fbfeb0eb2322cbe661f34b79c9aa4fd9e3597 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 11:07:34 +0200 Subject: [PATCH 04/15] Minor cleanup --- mapbox_gl_platform_interface/lib/src/fill.dart | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart index 6bce8cc88..057c41ef9 100644 --- a/mapbox_gl_platform_interface/lib/src/fill.dart +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -52,13 +52,7 @@ class FillOptions { final List geometry; final bool draggable; - static const FillOptions defaultOptions = FillOptions( - // fillOpacity: 1.0, - // fillColor: "rgba(0, 0, 0, 1)", - // fillOutlineColor: "rgba(0, 0, 0, 1)", - // fillPattern: "pedestrian-polygon", - // geometry: LatLng(0.0, 0.0), - ); + static const FillOptions defaultOptions = FillOptions(); FillOptions copyWith(FillOptions changes) { if (changes == null) { From d706d505d6ca76e70eff4119d504c75120c3a9a6 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 11:25:44 +0200 Subject: [PATCH 05/15] Minor cleanup. --- example/lib/place_fill.dart | 111 +++++++++--------------------------- 1 file changed, 27 insertions(+), 84 deletions(-) diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index b016f71d0..d81f55035 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math'; + +import 'dart:async'; import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; +import 'package:mapbox_gl_example/main.dart'; import 'page.dart'; @@ -75,7 +77,8 @@ class PlaceFillBodyState extends State { LatLng(-32.86711, 151.1947171), LatLng(-33.86711, 152.1947171), ], - fillColor: "#FF0000"), + fillColor: "#FF0000", + fillOutlineColor: "#FF0000"), ); setState(() { _fillCount += 1; @@ -119,42 +122,6 @@ class PlaceFillBodyState extends State { ); } - void _changeFillStrokeOpacity() { - //TODO: Implement fillStrokeOpacity. - // double current = _selectedFill.options.fillStrokeOpacity; - // if (current == null) { - // // default value - // current = 1.0; - // } - - // _updateSelectedFill( - // FillOptions(fillStrokeOpacity: current < 0.1 ? 1.0 : current * 0.75), - // ); - } - - void _changeFillStrokeWidth() { - //TODO: Implement fillStrokeWidth - // double current = _selectedFill.options.fillStrokeWidth; - // if (current == null) { - // // default value - // current = 0; - // } - // _updateSelectedFill(FillOptions(fillStrokeWidth: current == 0 ? 5.0 : 0)); - } - - Future _changeFillStrokeColor() async { - //TODO: Implement fillStrokeColor - // String current = _selectedFill.options.fillStrokeColor; - // if (current == null) { - // // default value - // current = "#FFFFFF"; - // } - - // _updateSelectedFill( - // FillOptions(fillStrokeColor: current == "#FFFFFF" ? "#FF0000" : "#FFFFFF"), - // ); - } - Future _changeFillOpacity() async { double current = _selectedFill.options.fillOpacity; if (current == null) { @@ -167,18 +134,6 @@ class PlaceFillBodyState extends State { ); } - Future _changeFillRadius() async { - // TODO: Implement fillRadius - // double current = _selectedFill.options.fillRadius; - // if (current == null) { - // // default value - // current = 0; - // } - // _updateSelectedFill( - // FillOptions(fillRadius: current == 120.0 ? 30.0 : current + 30.0), - // ); - } - Future _changeFillColor() async { String current = _selectedFill.options.fillColor; if (current == null) { @@ -188,20 +143,25 @@ class PlaceFillBodyState extends State { _updateSelectedFill( FillOptions( - fillColor: "#FFFF00"), + fillColor: "#FFFF00"), ); } - Future _changeFillBlur() async { - // TODO: Implement fillBlur - // double current = _selectedFill.options.fillBlur; - // if (current == null) { - // // default value - // current = 0; - // } - // _updateSelectedFill( - // FillOptions(fillBlur: current == 0.75 ? 0 : 0.75), - // ); + Future _changeFillOutlineColor() async { + String current = _selectedFill.options.fillOutlineColor; + if (current == null) { + // default value + current = "#FF0000"; + } + + _updateSelectedFill( + FillOptions( + fillOutlineColor: "#FFFF00"), + ); + } + + void _notImplemented(item) { + Scaffold.of(context).showSnackBar(SnackBar(content: Text('$item not yet implemented'))); } @override @@ -215,6 +175,7 @@ class PlaceFillBodyState extends State { width: 300.0, height: 200.0, child: MapboxMap( + accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), @@ -249,38 +210,20 @@ class PlaceFillBodyState extends State { onPressed: (_selectedFill == null) ? null : _changeFillOpacity, ), - FlatButton( - child: const Text('change fill-radius'), - onPressed: (_selectedFill == null) - ? null - : _changeFillRadius, - ), FlatButton( child: const Text('change fill-color'), onPressed: (_selectedFill == null) ? null : _changeFillColor, ), FlatButton( - child: const Text('change fill-blur'), - onPressed: - (_selectedFill == null) ? null : _changeFillBlur, - ), - FlatButton( - child: const Text('change fill-stroke-width'), + child: const Text('change fill-outline-color'), onPressed: - (_selectedFill == null) ? null : _changeFillStrokeWidth, + (_selectedFill == null) ? null : _changeFillOutlineColor, ), FlatButton( - child: const Text('change fill-stroke-color'), - onPressed: (_selectedFill == null) - ? null - : _changeFillStrokeColor, - ), - FlatButton( - child: const Text('change fill-stroke-opacity'), - onPressed: (_selectedFill == null) - ? null - : _changeFillStrokeOpacity, + child: const Text('change fill-pattern'), + onPressed: + (_selectedFill == null) ? null : () => _notImplemented('fill-pattern'), ), FlatButton( child: const Text('change position'), From e26187820490acbfa02fe0088a94fd201c2edb79 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 12:23:28 +0200 Subject: [PATCH 06/15] Fix broken build Android. --- .../java/com/mapbox/mapboxgl/Convert.java | 1 + .../java/com/mapbox/mapboxgl/FillBuilder.java | 2 +- .../mapbox/mapboxgl/MapboxMapController.java | 73 ++++++++++--------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index 71e1e641e..e915bc0fc 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -11,6 +11,7 @@ import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; +import com.mapbox.mapboxsdk.log.Logger; import com.mapbox.mapboxsdk.maps.MapboxMap; import java.util.ArrayList; diff --git a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java index 188d580fe..09adcd90a 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java +++ b/android/src/main/java/com/mapbox/mapboxgl/FillBuilder.java @@ -53,6 +53,6 @@ public void setGeometry(List> geometry) { @Override public void setDraggable(boolean draggable) { - fillOptions.setDraggable(draggable); + fillOptions.withDraggable(draggable); } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 03d3d78b2..b6350d2fc 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -284,6 +284,10 @@ private CircleController circle(String circleId) { return circle; } + private FillBuilder newFillBuilder() { + return new FillBuilder(fillManager); + } + private void removeFill(String fillId) { final FillController fillController = fills.remove(fillId); if (fillController != null) { @@ -291,12 +295,12 @@ private void removeFill(String fillId) { } } - private CircleController fill(String fillId) { - final CircleController circle = circles.get(fillId); - if (circle == null) { + private FillController fill(String fillId) { + final FillController fill = fills.get(fillId); + if (fill == null) { throw new IllegalArgumentException("Unknown fill: " + fillId); } - return circle; + return fill; } @Override @@ -557,12 +561,12 @@ public void onCancel() { result.success(reply); break; } - case "map#setTelemetryEnabled": { - final boolean enabled = call.argument("enabled"); - Mapbox.getTelemetry().setUserTelemetryRequestState(enabled); - result.success(null); - break; - } + case "map#setTelemetryEnabled": { + final boolean enabled = call.argument("enabled"); + Mapbox.getTelemetry().setUserTelemetryRequestState(enabled); + result.success(null); + break; + } case "map#getTelemetryEnabled": { final TelemetryEnabler.State telemetryState = TelemetryEnabler.retrieveTelemetryStateFromPreferences(); result.success(telemetryState == TelemetryEnabler.State.ENABLED); @@ -737,6 +741,30 @@ public void onError(@NonNull String message) { result.success(hashMapLatLng); break; } + case "fill#add": { + final FillBuilder fillBuilder = newFillBuilder(); + Convert.interpretFillOptions(call.argument("options"), fillBuilder); + final Fill fill = fillBuilder.build(); + final String fillId = String.valueOf(fill.getId()); + fills.put(fillId, new FillController(fill, true, this)); + result.success(fillId); + break; + } + case "fill#remove": { + final String fillId = call.argument("fill"); + removeFill(fillId); + result.success(null); + break; + } + case "fill#update": { + Log.e(TAG, "update fill"); + final String fillId = call.argument("fill"); + final FillController fill = fill(fillId); + Convert.interpretFillOptions(call.argument("options"), fill); + fill.update(fillManager); + result.success(null); + break; + } case "locationComponent#getLastLocation": { Log.e(TAG, "location component: getLastLocation"); if (this.myLocationEnabled && locationComponent != null && locationEngine != null) { @@ -763,32 +791,11 @@ public void onFailure(@NonNull Exception exception) { } break; } - case "style#addImage":{ - if(style==null){ + case "style#addImage": { + if(style==null) { result.error("STYLE IS NULL", "The style is null. Has onStyleLoaded() already been invoked?", null); } style.addImage(call.argument("name"), BitmapFactory.decodeByteArray(call.argument("bytes"),0,call.argument("length")), call.argument("sdf")); - case "fill#add": { - final FillBuilder fillBuilder = newFillBuilder(); - Convert.interpretFillOptions(call.argument("options"), fillBuilder); - final Fill fill = fillBuilder.build(); - final String fillId = String.valueOf(fill.getId()); - fills.put(fillId, new FillController(fill, true, this)); - result.success(fillId); - break; - } - case "fill#remove": { - final String fillId = call.argument("fill"); - removeFill(fillId); - result.success(null); - break; - } - case "fill#update": { - Log.e(TAG, "update fill"); - final String fillId = call.argument("fill"); - final FillController fill = fill(fillId); - Convert.interpretFillOptions(call.argument("options"), fill); - fill.update(fillManager); result.success(null); break; } From b1d7a73186802fbb210ab7d8b63cb11d92ad8872 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 12:48:36 +0200 Subject: [PATCH 07/15] A working version for Android. --- .../java/com/mapbox/mapboxgl/Convert.java | 45 ++++++++++--------- .../mapbox/mapboxgl/MapboxMapController.java | 7 +++ example/lib/place_fill.dart | 10 +++-- ios/Classes/MapboxMapController.swift | 6 ++- .../lib/src/fill.dart | 4 +- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/Convert.java b/android/src/main/java/com/mapbox/mapboxgl/Convert.java index e915bc0fc..0680f1bbb 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/Convert.java +++ b/android/src/main/java/com/mapbox/mapboxgl/Convert.java @@ -168,6 +168,31 @@ private static List toLatLngList(Object o) { return latLngList; } + private static List> toLatLngListList(Object o) { + if (o == null) { + return null; + } + final List data = toList(o); + List> latLngListList = new ArrayList<>(); + for (int i = 0; i < data.size(); i++) { + List latLngList = toLatLngList(data.get(i)); + latLngListList.add(latLngList); + } + return latLngListList; + } + + static Polygon interpretListLatLng(List> geometry) { + List> points = new ArrayList<>(geometry.size()); + for (List innerGeometry : geometry) { + List innerPoints = new ArrayList<>(innerGeometry.size()); + for (LatLng latLng : innerGeometry) { + innerPoints.add(com.mapbox.geojson.Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); + } + points.add(innerPoints); + } + return Polygon.fromLngLats(points); + } + private static List toList(Object o) { return (List) o; } @@ -504,24 +529,4 @@ static void interpretFillOptions(Object o, FillOptionsSink sink) { sink.setDraggable(toBoolean(draggable)); } } - - private static List> toLatLngListList(Object o) { - if (o == null) { - return null; - } - // todo add conversion - return new ArrayList<>(); - } - - static Polygon interpretListLatLng(List> geometry) { - List> points = new ArrayList<>(geometry.size()); - for (List innerGeometry : geometry) { - List innerPoints = new ArrayList<>(innerGeometry.size()); - for (LatLng latLng : innerGeometry) { - innerPoints.add(com.mapbox.geojson.Point.fromLngLat(latLng.getLongitude(), latLng.getLatitude())); - } - points.add(innerPoints); - } - return Polygon.fromLngLats(points); - } } \ No newline at end of file diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index b6350d2fc..c76fd656e 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -862,6 +862,13 @@ public void onAnnotationClick(Annotation annotation) { circleController.onTap(); } } + + if (annotation instanceof Fill) { + final FillController fillController = fills.get(String.valueOf(annotation.getId())); + if (fillController != null) { + fillController.onTap(); + } + } } @Override diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index d81f55035..7416a2474 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -72,10 +72,12 @@ class PlaceFillBodyState extends State { controller.addFill( FillOptions( geometry: [ - LatLng(-32.86711, 152.1947171), - LatLng(-33.86711, 151.1947171), - LatLng(-32.86711, 151.1947171), - LatLng(-33.86711, 152.1947171), + [ + LatLng(-32.86711, 152.1947171), + LatLng(-33.86711, 151.1947171), + LatLng(-32.86711, 151.1947171), + LatLng(-33.86711, 152.1947171), + ] ], fillColor: "#FF0000", fillOutlineColor: "#FF0000"), diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index ee510d398..a39433cc0 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -378,10 +378,12 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma guard let arguments = methodCall.arguments as? [String: Any] else { return } // Parse geometry if let options = arguments["options"] as? [String: Any], - let geometry = options["geometry"] as? [[Double]] { + let geometry = options["geometry"] as? [[[Double]]] { // Convert geometry to coordinate and create polygon. + // FIXME: The Polygon in Annotation Plugin takes a list of coordinates. This should how ever be + // a list of list of coordinates. For now we will only take the first list of coordinates (from the list of lists) var fillCoordinates: [CLLocationCoordinate2D] = [] - for coordinate in geometry { + for coordinate in geometry[0] { fillCoordinates.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) } let fill = MGLPolygonStyleAnnotation(coordinates: fillCoordinates, count: UInt(fillCoordinates.count)) diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart index 057c41ef9..01cc709ce 100644 --- a/mapbox_gl_platform_interface/lib/src/fill.dart +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -49,7 +49,7 @@ class FillOptions { final String fillColor; final String fillOutlineColor; final String fillPattern; - final List geometry; + final List> geometry; final bool draggable; static const FillOptions defaultOptions = FillOptions(); @@ -82,7 +82,7 @@ class FillOptions { addIfPresent('fillOutlineColor', fillOutlineColor); addIfPresent('fillPattern', fillPattern); addIfPresent('geometry', - geometry?.map((LatLng latLng) => latLng.toJson())?.toList()); + geometry?.map((List latLngList) => latLngList.map((LatLng latLng) => latLng.toJson())?.toList())?.toList()); addIfPresent('draggable', draggable); return json; } From 942194495a6bba420afad5e16e820eb7c732110b Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 12:55:01 +0200 Subject: [PATCH 08/15] Minor cleanup. --- .../mapbox/mapboxgl/MapboxMapController.java | 8 +++--- example/lib/place_fill.dart | 25 +------------------ 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index c76fd656e..b78681bae 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -562,10 +562,10 @@ public void onCancel() { break; } case "map#setTelemetryEnabled": { - final boolean enabled = call.argument("enabled"); - Mapbox.getTelemetry().setUserTelemetryRequestState(enabled); - result.success(null); - break; + final boolean enabled = call.argument("enabled"); + Mapbox.getTelemetry().setUserTelemetryRequestState(enabled); + result.success(null); + break; } case "map#getTelemetryEnabled": { final TelemetryEnabler.State telemetryState = TelemetryEnabler.retrieveTelemetryStateFromPreferences(); diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 7416a2474..75e833d00 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -48,20 +48,9 @@ class PlaceFillBodyState extends State { } void _onFillTapped(Fill fill) { - // if (_selectedFill != null) { - // _updateSelectedFill( - // const FillOptions(fillRadius: 60), - // ); - // } - print("Fill: ${fill.id}"); setState(() { _selectedFill = fill; }); - // _updateSelectedFill( - // FillOptions( - // fillRadius: 30, - // ), - // ); } void _updateSelectedFill(FillOptions changes) { @@ -96,19 +85,7 @@ class PlaceFillBodyState extends State { } void _changePosition() { - // final LatLng current = _selectedFill.options.geometry; - // final Offset offset = Offset( - // center.latitude - current.latitude, - // center.longitude - current.longitude, - // ); - // _updateSelectedFill( - // FillOptions( - // geometry: LatLng( - // center.latitude + offset.dy, - // center.longitude + offset.dx, - // ), - // ), - // ); + //TODO: Implement change position. } void _changeDraggable() { From 66b75718ded278338430a2889f3daa920145e7f2 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 13:13:53 +0200 Subject: [PATCH 09/15] Added fill pattern example. Works on Android not on iOS. Seems to break consecutive fills though. --- .../assets/fill/cat_silhouette_pattern.png | Bin 0 -> 972 bytes example/lib/place_fill.dart | 26 +++++++++++++++++- example/pubspec.yaml | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 example/assets/fill/cat_silhouette_pattern.png diff --git a/example/assets/fill/cat_silhouette_pattern.png b/example/assets/fill/cat_silhouette_pattern.png new file mode 100644 index 0000000000000000000000000000000000000000..6f5ece826b8e11c01e88c837810e83fe0099efd0 GIT binary patch literal 972 zcmeAS@N?(olHy`uVBq!ia0vp^4nSNn{1`6_P!I zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W> zdx@v7EBiwZ4iRC7c_+N51Ld!Hx;TbJ9DX~)H$&J_qHX@a8Bxmr61mwNXKS!|2d)xN zb`4qOy=#X9;_qS($pF4MM{yoVb*Vi5i-E!2&UHeYL?GEF%^Smn< z?rt$S`^dWALBZk$TVohYlVa3IrUy^k`1CW1n-6d@`z@ZnyhpP1VC?I~OZx;Q4F6TG z^zI6cTz#}p;Kg}?=tsvyL_S~ra%pn+`Jd(j%e9@iT&rXfI4AIVw;`uoosS`7;k8|p zzsvbJF1Wk)NVS&kpYA8E@oyv=zsx?TyC_dEV*a(?mNy@|sOcn&w%?d(7WPo4ab^q0 z#-^-4k|A!ob6>f~oXV5?Xzcv%cl5m#?Doet=)Q32TyZra`^9-io4{wQo9&%c(kl)s zg?_lMa)8w^R{3eJ`qBriwOJ3OJH3T&EM>Zvez9Wbl;0vN6)xy<{+}eN^`PA25WhkA z*+2UyHT_@a)sr_>rhfCPtN#PI>H@AV3s;=_o2C7#YP{r+id9?_LS1-+w%C7g$%s?|E$oR^x})X z!Hc)=SWEC2X=M^yi`LpHDA)!55|SyXD>f3Gy|~ z7X!Clnt0{Yws-Rt6zV@%xL+wdx883j_r)(ataonRFz31D+^TsOj>^9BwcR3d@%m1# zPYQF7EGgUXDK_iZ&a5B)xA&*q_Um`8$ZolLIOx@jjjji4%ma=@?p<&E@<34Pr?hpO z&xE{R++5xBdaKa3hbOIC(rdh|oaNH_swL0b{aUnWvio literal 0 HcmV?d00001 diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 75e833d00..62ba85674 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -4,8 +4,10 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; import 'package:mapbox_gl_example/main.dart'; @@ -31,6 +33,7 @@ class PlaceFillBodyState extends State { PlaceFillBodyState(); static final LatLng center = const LatLng(-33.86711, 151.1947171); + final String _fillPatternImage = "assets/fill/cat_silhouette_pattern.png"; MapboxMapController controller; int _fillCount = 0; @@ -41,6 +44,17 @@ class PlaceFillBodyState extends State { controller.onFillTapped.add(_onFillTapped); } + void _onStyleLoaded() { + addImageFromAsset("assetImage", _fillPatternImage); + } + + /// Adds an asset image to the currently displayed style + Future addImageFromAsset(String name, String assetName) async { + final ByteData bytes = await rootBundle.load(assetName); + final Uint8List list = bytes.buffer.asUint8List(); + return controller.addImage(name, list); + } + @override void dispose() { controller?.onFillTapped?.remove(_onFillTapped); @@ -138,6 +152,15 @@ class PlaceFillBodyState extends State { fillOutlineColor: "#FFFF00"), ); } + + Future _changeFillPattern() async { + String current = _selectedFill.options.fillPattern == null ? "assetImage" : null; + _updateSelectedFill( + FillOptions( + fillPattern: current + ), + ); + } void _notImplemented(item) { Scaffold.of(context).showSnackBar(SnackBar(content: Text('$item not yet implemented'))); @@ -156,6 +179,7 @@ class PlaceFillBodyState extends State { child: MapboxMap( accessToken: MapsDemo.ACCESS_TOKEN, onMapCreated: _onMapCreated, + onStyleLoadedCallback: _onStyleLoaded, initialCameraPosition: const CameraPosition( target: LatLng(-33.852, 151.211), zoom: 7.0, @@ -202,7 +226,7 @@ class PlaceFillBodyState extends State { FlatButton( child: const Text('change fill-pattern'), onPressed: - (_selectedFill == null) ? null : () => _notImplemented('fill-pattern'), + (_selectedFill == null) ? null : _changeFillPattern, ), FlatButton( child: const Text('change position'), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ea0a73b79..ad13abbb2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -49,6 +49,7 @@ flutter: # https://flutter.io/assets-and-images/#resolution-aware. assets: + - assets/fill/cat_silhouette_pattern.png - assets/symbols/custom-icon.png - assets/symbols/2.0x/custom-icon.png - assets/symbols/3.0x/custom-icon.png From f3aa0c69f828681f6a91d4157ef429d932897564 Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 23:00:17 +0200 Subject: [PATCH 10/15] For the first queried feature (when filter is set) create a fill. --- example/lib/map_ui.dart | 47 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/example/lib/map_ui.dart b/example/lib/map_ui.dart index 6e344495b..8957e7c38 100644 --- a/example/lib/map_ui.dart +++ b/example/lib/map_ui.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:mapbox_gl/mapbox_gl.dart'; @@ -54,8 +56,9 @@ class MapUiBodyState extends State { bool _zoomGesturesEnabled = true; bool _myLocationEnabled = true; bool _telemetryEnabled = true; - MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.Tracking; + MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.None; List _featureQueryFilter; + Fill _selectedFill; @override void initState() { @@ -238,6 +241,38 @@ class MapUiBodyState extends State { ); } + _clearFill() { + if (_selectedFill != null) { + mapController.removeFill(_selectedFill); + setState(() { + _selectedFill = null; + }); + } + } + + _drawFill(features) async { + Map feature = jsonDecode(features[0]); + if (feature['geometry']['type'] == 'Polygon') { + var coordinates = feature['geometry']['coordinates']; + List> geometry = coordinates.map( + (ll) => ll.map( + (l) => LatLng(l[1],l[0]) + ).toList().cast() + ).toList().cast>(); + Fill fill = await mapController.addFill( + FillOptions( + geometry: geometry, + fillColor: "#FF0000", + fillOutlineColor: "#FF0000", + fillOpacity: 0.6, + ) + ); + setState(() { + _selectedFill = fill; + }); + } + } + @override Widget build(BuildContext context) { final MapboxMap mapboxMap = MapboxMap( @@ -260,9 +295,13 @@ class MapUiBodyState extends State { print("Map click: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); print("Filter $_featureQueryFilter"); List features = await mapController.queryRenderedFeatures(point, [], _featureQueryFilter); - if (features.length>0) { - print(features[0]); - } + print('# features: ${features.length}'); + _clearFill(); + if (features.length == 0 && _featureQueryFilter != null) { + Scaffold.of(context).showSnackBar(SnackBar(content: Text('QueryRenderedFeatures: No features found!'))); + } else { + _drawFill(features); + } }, onMapLongClick: (point, latLng) async { print("Map long press: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}"); From a7b74e8964c38a8907f02adb5af267315b00e52a Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Mon, 22 Jun 2020 23:01:05 +0200 Subject: [PATCH 11/15] Fix lint issue (unused method). --- example/lib/place_fill.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 62ba85674..59523de4d 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -162,10 +162,6 @@ class PlaceFillBodyState extends State { ); } - void _notImplemented(item) { - Scaffold.of(context).showSnackBar(SnackBar(content: Text('$item not yet implemented'))); - } - @override Widget build(BuildContext context) { return Column( From cba5fac69ec28a55fa5dcf527be3dce212c9705e Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Wed, 8 Jul 2020 15:52:16 +0200 Subject: [PATCH 12/15] Updated code formatting. --- example/lib/place_fill.dart | 68 +++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 59523de4d..c8043bee8 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'dart:async'; import 'dart:typed_data'; @@ -73,17 +72,16 @@ class PlaceFillBodyState extends State { void _add() { controller.addFill( - FillOptions( - geometry: [ - [ - LatLng(-32.86711, 152.1947171), - LatLng(-33.86711, 151.1947171), - LatLng(-32.86711, 151.1947171), - LatLng(-33.86711, 152.1947171), - ] - ], - fillColor: "#FF0000", - fillOutlineColor: "#FF0000"), + FillOptions(geometry: [ + [ + LatLng(-32.86711, 152.1947171), + LatLng(-33.86711, 151.1947171), + LatLng(-32.86711, 151.1947171), + LatLng(-33.86711, 152.1947171), + ] + ], + fillColor: "#FF0000", + fillOutlineColor: "#FF0000"), ); setState(() { _fillCount += 1; @@ -109,9 +107,7 @@ class PlaceFillBodyState extends State { draggable = false; } _updateSelectedFill( - FillOptions( - draggable: !draggable, - ), + FillOptions(draggable: !draggable), ); } @@ -135,8 +131,7 @@ class PlaceFillBodyState extends State { } _updateSelectedFill( - FillOptions( - fillColor: "#FFFF00"), + FillOptions(fillColor: "#FFFF00"), ); } @@ -148,17 +143,14 @@ class PlaceFillBodyState extends State { } _updateSelectedFill( - FillOptions( - fillOutlineColor: "#FFFF00"), + FillOptions(fillOutlineColor: "#FFFF00"), ); } - + Future _changeFillPattern() async { String current = _selectedFill.options.fillPattern == null ? "assetImage" : null; _updateSelectedFill( - FillOptions( - fillPattern: current - ), + FillOptions(fillPattern: current), ); } @@ -206,34 +198,38 @@ class PlaceFillBodyState extends State { children: [ FlatButton( child: const Text('change fill-opacity'), - onPressed: - (_selectedFill == null) ? null : _changeFillOpacity, + onPressed: (_selectedFill == null) + ? null + : _changeFillOpacity, ), FlatButton( child: const Text('change fill-color'), - onPressed: - (_selectedFill == null) ? null : _changeFillColor, + onPressed: (_selectedFill == null) + ? null + : _changeFillColor, ), FlatButton( child: const Text('change fill-outline-color'), - onPressed: - (_selectedFill == null) ? null : _changeFillOutlineColor, + onPressed: (_selectedFill == null) + ? null + : _changeFillOutlineColor, ), FlatButton( child: const Text('change fill-pattern'), - onPressed: - (_selectedFill == null) ? null : _changeFillPattern, + onPressed: (_selectedFill == null) + ? null + : _changeFillPattern, ), FlatButton( child: const Text('change position'), - onPressed: (_selectedFill == null) - ? null + onPressed: (_selectedFill == null) + ? null : _changePosition, ), FlatButton( child: const Text('toggle draggable'), - onPressed: (_selectedFill == null) - ? null + onPressed: (_selectedFill == null) + ? null : _changeDraggable, ), ], @@ -247,4 +243,4 @@ class PlaceFillBodyState extends State { ], ); } -} \ No newline at end of file +} From e3213f98506b5aaa31c3dbd4c798aee4e5fe5fdb Mon Sep 17 00:00:00 2001 From: Timothy Sealy Date: Wed, 8 Jul 2020 19:25:35 +0200 Subject: [PATCH 13/15] Added interior polygon to iOS. --- example/lib/place_fill.dart | 6 ++++++ ios/Classes/Convert.swift | 13 +++++++++++++ ios/Classes/Extensions.swift | 7 +++++++ ios/Classes/MapboxMapController.swift | 14 +++++++------- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index c8043bee8..480c7dde2 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -73,6 +73,12 @@ class PlaceFillBodyState extends State { void _add() { controller.addFill( FillOptions(geometry: [ + [ + LatLng(-32.81711, 151.1447171), + LatLng(-32.81711, 152.2447171), + LatLng(-33.91711, 152.2447171), + LatLng(-33.91711, 151.1447171), + ], [ LatLng(-32.86711, 152.1947171), LatLng(-33.86711, 151.1947171), diff --git a/ios/Classes/Convert.swift b/ios/Classes/Convert.swift index 1969d5af4..ffd7d31b2 100644 --- a/ios/Classes/Convert.swift +++ b/ios/Classes/Convert.swift @@ -333,4 +333,17 @@ class Convert { delegate.isDraggable = draggable } } + + class func toPolygons(geometry: [[[Double]]]) -> [MGLPolygonFeature] { + var polygons:[MGLPolygonFeature] = [] + for lineString in geometry { + var linearRing: [CLLocationCoordinate2D] = [] + for coordinate in lineString { + linearRing.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) + } + let polygon = MGLPolygonFeature(coordinates: linearRing, count: UInt(linearRing.count)) + polygons.append(polygon) + } + return polygons + } } diff --git a/ios/Classes/Extensions.swift b/ios/Classes/Extensions.swift index 1b5039d99..800e32dcb 100644 --- a/ios/Classes/Extensions.swift +++ b/ios/Classes/Extensions.swift @@ -89,3 +89,10 @@ extension UIColor { return nil } } + + +extension Array { + var tail: Array { + return Array(self.dropFirst()) + } +} diff --git a/ios/Classes/MapboxMapController.swift b/ios/Classes/MapboxMapController.swift index a39433cc0..e0a16f077 100644 --- a/ios/Classes/MapboxMapController.swift +++ b/ios/Classes/MapboxMapController.swift @@ -377,22 +377,22 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma guard let fillAnnotationController = fillAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } // Parse geometry + var identifier: String? = nil if let options = arguments["options"] as? [String: Any], let geometry = options["geometry"] as? [[[Double]]] { - // Convert geometry to coordinate and create polygon. - // FIXME: The Polygon in Annotation Plugin takes a list of coordinates. This should how ever be - // a list of list of coordinates. For now we will only take the first list of coordinates (from the list of lists) + guard geometry.count > 0 else { break } + // Convert geometry to coordinate and interior polygonc. var fillCoordinates: [CLLocationCoordinate2D] = [] for coordinate in geometry[0] { fillCoordinates.append(CLLocationCoordinate2DMake(coordinate[0], coordinate[1])) } - let fill = MGLPolygonStyleAnnotation(coordinates: fillCoordinates, count: UInt(fillCoordinates.count)) + let polygons = Convert.toPolygons(geometry: geometry.tail) + let fill = MGLPolygonStyleAnnotation(coordinates: fillCoordinates, count: UInt(fillCoordinates.count), interiorPolygons: polygons) Convert.interpretFillOptions(options: arguments["options"], delegate: fill) fillAnnotationController.addStyleAnnotation(fill) - result(fill.identifier) - } else { - result(nil) + identifier = fill.identifier } + result(identifier) case "fill#update": guard let fillAnnotationController = fillAnnotationController else { return } guard let arguments = methodCall.arguments as? [String: Any] else { return } From 41ad1583f5ec51f88b8984fd953ed0c550b88e22 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Sat, 24 Oct 2020 11:49:29 +0200 Subject: [PATCH 14/15] [docs] update readme support table --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08b9ac1d0..b0a94537e 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ This project is available on [pub.dev](https://pub.dev/packages/mapbox_gl), foll | Symbol | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Circle | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Line | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Fill | | | | +| Fill | :white_check_mark: | :white_check_mark: | | ## Map Styles From 9dfc5fdc7c5c7ee9cd75f047a5673074f22be265 Mon Sep 17 00:00:00 2001 From: Tobrun Van Nuland Date: Sat, 24 Oct 2020 12:56:09 +0200 Subject: [PATCH 15/15] fixup --- .../src/main/java/com/mapbox/mapboxgl/MapboxMapController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java index 7cf4c9233..ef6e801f4 100644 --- a/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java +++ b/android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java @@ -1194,7 +1194,7 @@ private int checkSelfPermission(String permission) { * @return */ private Bitmap getScaledImage(String imageId, float density) { - AssetManager assetManager = registrar.context().getAssets();:white_check_mark: + AssetManager assetManager = registrar.context().getAssets(); AssetFileDescriptor assetFileDescriptor = null; // Split image path into parts.