diff --git a/README.md b/README.md index 9821cd19b..45141ec7a 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,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 | :white_check_mark: | :white_check_mark: | | +| Fill | :white_check_mark: | :white_check_mark: | :white_check_mark: | ## Map Styles diff --git a/example/lib/full_map.dart b/example/lib/full_map.dart index f34449aac..b6a10c89d 100644 --- a/example/lib/full_map.dart +++ b/example/lib/full_map.dart @@ -5,8 +5,7 @@ import 'main.dart'; import 'page.dart'; class FullMapPage extends ExamplePage { - FullMapPage() - : super(const Icon(Icons.map), 'Full screen map'); + FullMapPage() : super(const Icon(Icons.map), 'Full screen map'); @override Widget build(BuildContext context) { diff --git a/example/lib/main.dart b/example/lib/main.dart index 08f2607cd..ff2076302 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -39,7 +39,6 @@ final List _allPages = [ ]; class MapsDemo extends StatelessWidget { - //FIXME: Add your Mapbox access token here static const String ACCESS_TOKEN = "YOUR_TOKEN_HERE"; diff --git a/example/lib/place_fill.dart b/example/lib/place_fill.dart index 480c7dde2..71d73149f 100644 --- a/example/lib/place_fill.dart +++ b/example/lib/place_fill.dart @@ -74,20 +74,19 @@ class PlaceFillBodyState extends State { 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(-33.719, 151.150), + LatLng(-33.858, 151.150), + LatLng(-33.866, 151.401), + LatLng(-33.747, 151.328), + LatLng(-33.719, 151.150), ], [ - LatLng(-32.86711, 152.1947171), - LatLng(-33.86711, 151.1947171), - LatLng(-32.86711, 151.1947171), - LatLng(-33.86711, 152.1947171), + LatLng(-33.762, 151.250), + LatLng(-33.827, 151.250), + LatLng(-33.833, 151.347), + LatLng(-33.762, 151.250), ] - ], - fillColor: "#FF0000", - fillOutlineColor: "#FF0000"), + ], fillColor: "#FF0000", fillOutlineColor: "#FF0000"), ); setState(() { _fillCount += 1; @@ -154,7 +153,8 @@ class PlaceFillBodyState extends State { } Future _changeFillPattern() async { - String current = _selectedFill.options.fillPattern == null ? "assetImage" : null; + String current = + _selectedFill.options.fillPattern == null ? "assetImage" : null; _updateSelectedFill( FillOptions(fillPattern: current), ); @@ -210,9 +210,8 @@ class PlaceFillBodyState extends State { ), 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'), @@ -228,15 +227,13 @@ class PlaceFillBodyState extends State { ), FlatButton( child: const Text('change position'), - onPressed: (_selectedFill == null) - ? null - : _changePosition, + onPressed: + (_selectedFill == null) ? null : _changePosition, ), FlatButton( child: const Text('toggle draggable'), - onPressed: (_selectedFill == null) - ? null - : _changeDraggable, + onPressed: + (_selectedFill == null) ? null : _changeDraggable, ), ], ), diff --git a/lib/src/controller.dart b/lib/src/controller.dart index e8be6744e..449f354a1 100644 --- a/lib/src/controller.dart +++ b/lib/src/controller.dart @@ -147,7 +147,9 @@ class MapboxMapController extends ChangeNotifier { onMapIdle(); } }); - MapboxGlPlatform.getInstance(_id).onUserLocationUpdatedPlatform.add((location) { + MapboxGlPlatform.getInstance(_id) + .onUserLocationUpdatedPlatform + .add((location) { onUserLocationUpdated?.call(location); }); } @@ -779,7 +781,8 @@ class MapboxMapController extends ChangeNotifier { } /// Adds an image source to the style currently displayed in the map, so that it can later be referred to by the provided id. - Future addImageSource(String imageSourceId, Uint8List bytes, LatLngQuad coordinates) { + Future addImageSource( + String imageSourceId, Uint8List bytes, LatLngQuad coordinates) { return MapboxGlPlatform.getInstance(_id) .addImageSource(imageSourceId, bytes, coordinates); } @@ -791,12 +794,15 @@ class MapboxMapController extends ChangeNotifier { /// Adds a Mapbox style layer to the map's style at render time. Future addLayer(String imageLayerId, String imageSourceId) { - return MapboxGlPlatform.getInstance(_id).addLayer(imageLayerId, imageSourceId); + return MapboxGlPlatform.getInstance(_id) + .addLayer(imageLayerId, imageSourceId); } /// Adds a Mapbox style layer below the layer provided with belowLayerId to the map's style at render time, - Future addLayerBelow(String imageLayerId, String imageSourceId, String belowLayerId) { - return MapboxGlPlatform.getInstance(_id).addLayerBelow(imageLayerId, imageSourceId, belowLayerId); + Future addLayerBelow( + String imageLayerId, String imageSourceId, String belowLayerId) { + return MapboxGlPlatform.getInstance(_id) + .addLayerBelow(imageLayerId, imageSourceId, belowLayerId); } /// Removes a Mapbox style layer @@ -821,8 +827,8 @@ class MapboxMapController extends ChangeNotifier { /// Returns the distance spanned by one pixel at the specified [latitude] and current zoom level. /// The distance between pixels decreases as the latitude approaches the poles. This relationship parallels the relationship between longitudinal coordinates at different latitudes. - Future getMetersPerPixelAtLatitude(double latitude) async{ - return MapboxGlPlatform.getInstance(_id).getMetersPerPixelAtLatitude(latitude); + Future getMetersPerPixelAtLatitude(double latitude) async { + return MapboxGlPlatform.getInstance(_id) + .getMetersPerPixelAtLatitude(latitude); } - -} \ No newline at end of file +} diff --git a/mapbox_gl_platform_interface/lib/src/fill.dart b/mapbox_gl_platform_interface/lib/src/fill.dart index 01cc709ce..dbeb4e25c 100644 --- a/mapbox_gl_platform_interface/lib/src/fill.dart +++ b/mapbox_gl_platform_interface/lib/src/fill.dart @@ -6,8 +6,22 @@ part of mapbox_gl_platform_interface; +FillOptions translateFillOptions(FillOptions options, LatLng delta) { + if (options.geometry != null) { + List> newGeometry = []; + for (var ring in options.geometry) { + List newRing = []; + for (var coords in ring) { + newRing.add(coords + delta); + } + newGeometry.add(newRing); + } + return FillOptions(geometry: newGeometry); + } + return options; +} + class Fill { - @visibleForTesting Fill(this._id, this.options, [this._data]); /// A unique identifier for this fill. @@ -36,14 +50,13 @@ class FillOptions { /// /// 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 - }); + const FillOptions( + {this.fillOpacity, + this.fillColor, + this.fillOutlineColor, + this.fillPattern, + this.geometry, + this.draggable}); final double fillOpacity; final String fillColor; @@ -81,9 +94,13 @@ class FillOptions { addIfPresent('fillColor', fillColor); addIfPresent('fillOutlineColor', fillOutlineColor); addIfPresent('fillPattern', fillPattern); - addIfPresent('geometry', - geometry?.map((List latLngList) => latLngList.map((LatLng latLng) => latLng.toJson())?.toList())?.toList()); + addIfPresent( + 'geometry', + geometry + ?.map((List latLngList) => + latLngList.map((LatLng latLng) => latLng.toJson())?.toList()) + ?.toList()); addIfPresent('draggable', draggable); return json; } -} \ No newline at end of file +} diff --git a/mapbox_gl_platform_interface/lib/src/location.dart b/mapbox_gl_platform_interface/lib/src/location.dart index 1bd0005a8..ba54f314f 100644 --- a/mapbox_gl_platform_interface/lib/src/location.dart +++ b/mapbox_gl_platform_interface/lib/src/location.dart @@ -26,6 +26,14 @@ class LatLng { /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive). final double longitude; + LatLng operator +(LatLng o) { + return LatLng(latitude + o.latitude, longitude + o.longitude); + } + + LatLng operator -(LatLng o) { + return LatLng(latitude - o.latitude, longitude - o.longitude); + } + dynamic toJson() { return [latitude, longitude]; } @@ -107,7 +115,11 @@ class LatLngBounds { /// A geographical area representing a non-aligned quadrilateral /// This class does not wrap values to the world bounds class LatLngQuad { - const LatLngQuad({@required this.topLeft, @required this.topRight, @required this.bottomRight, @required this.bottomLeft}) + const LatLngQuad( + {@required this.topLeft, + @required this.topRight, + @required this.bottomRight, + @required this.bottomLeft}) : assert(topLeft != null), assert(topRight != null), assert(bottomRight != null), @@ -122,7 +134,12 @@ class LatLngQuad { final LatLng bottomLeft; dynamic toList() { - return [topLeft.toJson(), topRight.toJson(), bottomRight.toJson(), bottomLeft.toJson()]; + return [ + topLeft.toJson(), + topRight.toJson(), + bottomRight.toJson(), + bottomLeft.toJson() + ]; } @visibleForTesting @@ -154,7 +171,6 @@ class LatLngQuad { @override int get hashCode => hashValues(topLeft, topRight, bottomRight, bottomLeft); - } /// User's observed location diff --git a/mapbox_gl_web/lib/mapbox_gl_web.dart b/mapbox_gl_web/lib/mapbox_gl_web.dart index 1ee7b279a..da703e15c 100644 --- a/mapbox_gl_web/lib/mapbox_gl_web.dart +++ b/mapbox_gl_web/lib/mapbox_gl_web.dart @@ -26,4 +26,5 @@ part 'src/feature_manager/feature_manager.dart'; part 'src/feature_manager/symbol_manager.dart'; part 'src/feature_manager/line_manager.dart'; part 'src/feature_manager/circle_manager.dart'; +part 'src/feature_manager/fill_manager.dart'; part 'src/mapbox_map_controller.dart'; diff --git a/mapbox_gl_web/lib/src/convert.dart b/mapbox_gl_web/lib/src/convert.dart index 24ac53441..668d2fdac 100644 --- a/mapbox_gl_web/lib/src/convert.dart +++ b/mapbox_gl_web/lib/src/convert.dart @@ -357,4 +357,58 @@ class Convert { } return feature.copyWith(properties: properties, geometry: geometry); } + + static List>> fillGeometryToFeatureGeometry( + List> geom) { + List>> convertedFill = []; + for (final ring in geom) { + List> convertedRing = []; + for (final coords in ring) { + convertedRing.add([coords.longitude, coords.latitude]); + } + convertedFill.add(convertedRing); + } + return convertedFill; + } + + static List> featureGeometryToFillGeometry( + List>> geom) { + List> convertedFill = []; + for (final ring in geom) { + List convertedRing = []; + for (final coords in ring) { + convertedRing.add(LatLng(coords[1], coords[0])); + } + convertedFill.add(convertedRing); + } + return convertedFill; + } + + static Feature intepretFillOptions(FillOptions options, Feature feature) { + var properties = feature.properties; + var geometry = feature.geometry; + if (options.draggable != null) { + properties['draggable'] = options.draggable; + } + if (options.fillColor != null) { + properties['fillColor'] = options.fillColor; + } + if (options.fillOpacity != null) { + properties['fillOpacity'] = options.fillOpacity; + } + if (options.fillOutlineColor != null) { + properties['fillOutlineColor'] = options.fillOutlineColor; + } + if (options.fillPattern != null) { + properties['fillPattern'] = options.fillPattern; + } + + if (options.geometry != null) { + geometry = Geometry( + type: geometry.type, + coordinates: fillGeometryToFeatureGeometry(options.geometry), + ); + } + return feature.copyWith(properties: properties, geometry: geometry); + } } diff --git a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart index 34ebe17fe..0e53a3d6d 100644 --- a/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart +++ b/mapbox_gl_web/lib/src/feature_manager/feature_manager.dart @@ -8,6 +8,8 @@ abstract class FeatureManager { final String layerId; final MapboxMap map; final FeatureTapCallback onTap; + @protected + LatLng dragOrigin; final Map _features = {}; num featureCounter = 1; @@ -39,27 +41,21 @@ abstract class FeatureManager { return '${feature.id}'; } - void updateFeature(Feature feature) { updateFeatures([feature]); } - void updateFeatures(Iterable features) { - features.forEach( - (feature) => _features['${feature.id}'] = feature - ); + features.forEach((feature) => _features['${feature.id}'] = feature); _updateSource(); } - + void remove(String featureId) { removeAll([featureId]); } void removeAll(Iterable featuresIds) { - featuresIds.forEach( - (featureId) => _features.remove(featureId) - ); + featuresIds.forEach((featureId) => _features.remove(featureId)); _updateSource(); } @@ -91,6 +87,8 @@ abstract class FeatureManager { e.preventDefault(); _draggableFeatureId = '${e.features[0].id}'; map.getCanvas().style.cursor = 'grabbing'; + var coords = e.lngLat; + dragOrigin = LatLng(coords.lat, coords.lng); } }); diff --git a/mapbox_gl_web/lib/src/feature_manager/fill_manager.dart b/mapbox_gl_web/lib/src/feature_manager/fill_manager.dart new file mode 100644 index 000000000..9c06fb249 --- /dev/null +++ b/mapbox_gl_web/lib/src/feature_manager/fill_manager.dart @@ -0,0 +1,52 @@ +part of mapbox_gl_web; + +/// Signature for when a tap has occurred. +typedef FillTapCallback = void Function(String id); + +class FillManager extends FeatureManager { + final MapboxMap map; + final FillTapCallback onTap; + + FillManager({ + @required this.map, + this.onTap, + }) : super( + sourceId: 'fill_source', + layerId: 'fill_layer', + map: map, + onTap: onTap, + ); + + @override + void initLayer() { + map.addLayer({ + 'id': layerId, + 'type': 'fill', + 'source': sourceId, + 'paint': { + 'fill-color': ['get', 'fillColor'], + 'fill-opacity': ['get', 'fillOpacity'], + 'fill-outline-color': ['get', 'fillOutlineColor'], + } + }); + } + + @override + void onDrag(String featureId, LatLng latLng) { + Feature oldFeature = getFeature(featureId); + final geometry = + Convert.featureGeometryToFillGeometry(oldFeature.geometry.coordinates); + update( + featureId, + translateFillOptions( + FillOptions(geometry: geometry), latLng - dragOrigin)); + dragOrigin = latLng; + } + + @override + void update(String featureId, FillOptions changes) { + Feature oldFeature = getFeature(featureId); + Feature newFeature = Convert.intepretFillOptions(changes, oldFeature); + updateFeature(newFeature); + } +} diff --git a/mapbox_gl_web/lib/src/mapbox_map_controller.dart b/mapbox_gl_web/lib/src/mapbox_map_controller.dart index f8a8f7318..53d2fe47e 100644 --- a/mapbox_gl_web/lib/src/mapbox_map_controller.dart +++ b/mapbox_gl_web/lib/src/mapbox_map_controller.dart @@ -10,6 +10,7 @@ class MapboxMapController extends MapboxGlPlatform SymbolManager symbolManager; LineManager lineManager; CircleManager circleManager; + FillManager fillManager; bool _trackCameraPosition = false; GeolocateControl _geolocateControl; @@ -139,31 +140,28 @@ class MapboxMapController extends MapboxGlPlatform } @override - Future> addSymbols(List options, [List data]) async { - Map optionsById = Map.fromIterable( - options, - key: (o) => symbolManager.add( - Feature( - geometry: Geometry( - type: 'Point', - coordinates: [o.geometry.longitude, o.geometry.latitude], - ), - ) - ), - value: (o) => o - ); + Future> addSymbols(List options, + [List data]) async { + Map optionsById = Map.fromIterable(options, + key: (o) => symbolManager.add(Feature( + geometry: Geometry( + type: 'Point', + coordinates: [o.geometry.longitude, o.geometry.latitude], + ), + )), + value: (o) => o); symbolManager.updateAll(optionsById); - - return optionsById.map( - (id, singleOptions) { + + return optionsById + .map((id, singleOptions) { int dataIndex = options.indexOf(singleOptions); - Map singleData = data != null && data.length >= dataIndex + 1 ? data[dataIndex] : null; - return MapEntry( - id, - Symbol(id, singleOptions, singleData) - ); - } - ).values.toList(); + Map singleData = data != null && data.length >= dataIndex + 1 + ? data[dataIndex] + : null; + return MapEntry(id, Symbol(id, singleOptions, singleData)); + }) + .values + .toList(); } @override @@ -228,6 +226,26 @@ class MapboxMapController extends MapboxGlPlatform circleManager.remove(circleId); } + Future addFill(FillOptions options, [Map data]) async { + String fillId = fillManager.add(Feature( + geometry: Geometry( + type: 'Polygon', + coordinates: Convert.fillGeometryToFeatureGeometry(options.geometry), + ), + )); + + fillManager.update(fillId, options); + return Fill(fillId, options, data); + } + + Future updateFill(Fill fill, FillOptions changes) async { + fillManager.update(fill.id, changes); + } + + Future removeFill(String fillId) async { + fillManager.remove(fillId); + } + @override Future queryRenderedFeatures( Point point, List layerIds, List filter) async { @@ -334,6 +352,7 @@ class MapboxMapController extends MapboxGlPlatform symbolManager = SymbolManager(map: _map, onTap: onSymbolTappedPlatform); lineManager = LineManager(map: _map, onTap: onLineTappedPlatform); circleManager = CircleManager(map: _map, onTap: onCircleTappedPlatform); + fillManager = FillManager(map: _map, onTap: onFillTappedPlatform); onMapStyleLoadedPlatform(null); _map.on('click', _onMapClick); // long click not available in web, so it is mapped to double click @@ -563,7 +582,7 @@ class MapboxMapController extends MapboxGlPlatform @override void setMyLocationTrackingMode(int myLocationTrackingMode) { - if(_geolocateControl==null){ + if (_geolocateControl == null) { //myLocationEnabled is false, ignore myLocationTrackingMode return; } @@ -639,21 +658,23 @@ class MapboxMapController extends MapboxGlPlatform @override Future toScreenLocation(LatLng latLng) async { - var screenPosition = _map.project(LngLat(latLng.longitude, latLng.latitude)); + var screenPosition = + _map.project(LngLat(latLng.longitude, latLng.latitude)); return Point(screenPosition.x.round(), screenPosition.y.round()); } @override Future toLatLng(Point screenLocation) async { - var lngLat = _map.unproject(mapbox.Point(screenLocation.x, screenLocation.y)); + var lngLat = + _map.unproject(mapbox.Point(screenLocation.x, screenLocation.y)); return LatLng(lngLat.lat, lngLat.lng); } @override - Future getMetersPerPixelAtLatitude(double latitude) async{ + Future getMetersPerPixelAtLatitude(double latitude) async { //https://wiki.openstreetmap.org/wiki/Zoom_levels var circumference = 40075017.686; var zoom = _map.getZoom(); - return circumference * cos(latitude * (pi/180)) / pow(2, zoom + 9); + return circumference * cos(latitude * (pi / 180)) / pow(2, zoom + 9); } } diff --git a/mapbox_gl_web/pubspec.yaml b/mapbox_gl_web/pubspec.yaml index 40169b757..b6d63dad7 100644 --- a/mapbox_gl_web/pubspec.yaml +++ b/mapbox_gl_web/pubspec.yaml @@ -23,6 +23,10 @@ dependencies: mapbox_gl_dart: ^0.1.5 image: ^2.1.12 +dependency_overrides: + mapbox_gl_platform_interface: + path: ../mapbox_gl_platform_interface + dev_dependencies: flutter_test: sdk: flutter diff --git a/pubspec.lock b/pubspec.lock index 7a5db66e2..eae87bfc3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0-nullsafety.5" charcode: dependency: transitive description: @@ -35,7 +35,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0-nullsafety.5" convert: dependency: transitive description: @@ -66,14 +66,14 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.14" + version: "2.1.19" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3-nullsafety.3" mapbox_gl_dart: dependency: transitive description: @@ -86,7 +86,7 @@ packages: description: path: mapbox_gl_platform_interface ref: HEAD - resolved-ref: "7861b62ee1d396439d44ebb1f84563244aa6bf82" + resolved-ref: "37703aa872a877d0dc4c88fe358647003f4949cc" url: "https://github.com/tobrun/flutter-mapbox-gl.git" source: git version: "0.9.0" @@ -95,7 +95,7 @@ packages: description: path: mapbox_gl_web ref: HEAD - resolved-ref: "7861b62ee1d396439d44ebb1f84563244aa6bf82" + resolved-ref: "37703aa872a877d0dc4c88fe358647003f4949cc" url: "https://github.com/tobrun/flutter-mapbox-gl.git" source: git version: "0.9.0" @@ -105,7 +105,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.6" path: dependency: transitive description: @@ -119,7 +119,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "3.0.4" + version: "3.1.0" sky_engine: dependency: transitive description: flutter @@ -131,14 +131,14 @@ packages: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0-nullsafety.5" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0-nullsafety.5" xml: dependency: transitive description: @@ -147,5 +147,5 @@ packages: source: hosted version: "4.5.1" sdks: - dart: ">=2.10.0-110 <2.11.0" + dart: ">=2.12.0-0 <3.0.0" flutter: ">=1.12.13+hotfix.4 <2.0.0"