Skip to content

Commit

Permalink
Add batch mode of screen location (#554)
Browse files Browse the repository at this point in the history
* Change SDK version of example

* Add toScreenLocationBatch

* Add custom marker example
  • Loading branch information
OttyLab committed Mar 30, 2021
1 parent 74c656a commit 489bbd8
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 1 deletion.
13 changes: 13 additions & 0 deletions android/src/main/java/com/mapbox/mapboxgl/MapboxMapController.java
Expand Up @@ -460,6 +460,19 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
result.success(reply);
break;
}
case "map#toScreenLocationBatch": {
double[] param = (double[])call.argument("coordinates");
double[] reply = new double[param.length];

for (int i = 0; i < param.length; i += 2) {
PointF pointf = mapboxMap.getProjection().toScreenLocation(new LatLng(param[i], param[i + 1]));
reply[i] = pointf.x;
reply[i + 1] = pointf.y;
}

result.success(reply);
break;
}
case "map#toLatLng": {
Map<String, Object> reply = new HashMap<>();
LatLng latlng = mapboxMap.getProjection().fromScreenLocation(new PointF( ((Double) call.argument("x")).floatValue(), ((Double) call.argument("y")).floatValue()));
Expand Down
255 changes: 255 additions & 0 deletions example/lib/custom_marker.dart
@@ -0,0 +1,255 @@
import 'dart:io';
import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:mapbox_gl/mapbox_gl.dart';

import 'main.dart';
import 'page.dart';

const randomMarkerNum = 100;

class CustomMarkerPage extends ExamplePage {
CustomMarkerPage() : super(const Icon(Icons.place), 'Custom marker');

@override
Widget build(BuildContext context) {
return CustomMarker();
}
}

class CustomMarker extends StatefulWidget {
const CustomMarker();

@override
State createState() => CustomMarkerState();
}

class CustomMarkerState extends State<CustomMarker> {
final Random _rnd = new Random();

MapboxMapController _mapController;
List<Marker> _markers = [];
List<_MarkerState> _markerStates = [];

void _addMarkerStates(_MarkerState markerState) {
_markerStates.add(markerState);
}

void _onMapCreated(MapboxMapController controller) {
_mapController = controller;
controller.addListener(() {
if (controller.isCameraMoving) {
_updateMarkerPosition();
}
});
}

void _onStyleLoadedCallback() {
print('onStyleLoadedCallback');
}

void _onMapLongClickCallback(Point<double> point, LatLng coordinates) {
_addMarker(point, coordinates);
}

void _onCameraIdleCallback() {
_updateMarkerPosition();
}

void _updateMarkerPosition() {
final coordinates = <LatLng>[];

for (final markerState in _markerStates) {
coordinates.add(markerState.getCoordinate());
}

_mapController.toScreenLocationBatch(coordinates).then((points){
_markerStates.asMap().forEach((i, value){
_markerStates[i].updatePosition(points[i]);
});
});
}

void _addMarker(Point<double> point, LatLng coordinates) {
setState(() {
_markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point, _addMarkerStates));
});
}

@override
Widget build(BuildContext context) {
return new Scaffold(
body: Stack(
children: [
MapboxMap(
accessToken: MapsDemo.ACCESS_TOKEN,
trackCameraPosition: true,
onMapCreated: _onMapCreated,
onMapLongClick: _onMapLongClickCallback,
onCameraIdle: _onCameraIdleCallback,
onStyleLoadedCallback: _onStyleLoadedCallback,
initialCameraPosition: const CameraPosition(target: LatLng(35.0, 135.0), zoom: 5),
),
IgnorePointer(
ignoring: true,
child:
Stack(
children: _markers,
)
)
]
),
floatingActionButton: FloatingActionButton(
onPressed: (){
//_measurePerformance();

// Generate random markers
var param = <LatLng>[];
for (var i = 0; i < randomMarkerNum; i++) {
final lat = _rnd.nextDouble() * 20 + 30;
final lng = _rnd.nextDouble() * 20 + 125;
param.add(LatLng(lat, lng));
}

_mapController.toScreenLocationBatch(param).then((value) {
for (var i = 0; i < randomMarkerNum; i++) {
var point = Point<double>(value[i].x, value[i].y);
_addMarker(point, param[i]);
}
});
},
child: Icon(Icons.add),
),
);
}

// ignore: unused_element
void _measurePerformance() {
final trial = 10;
final batches = [500, 1000, 1500, 2000, 2500, 3000];
var results = Map<int, List<double>>();
for (final batch in batches) {
results[batch] = [0.0, 0.0];
}

_mapController.toScreenLocation(LatLng(0, 0));
Stopwatch sw = Stopwatch();

for (final batch in batches) {
//
// primitive
//
for (var i = 0; i < trial; i++) {
sw.start();
var list = <Future<Point<num>>>[];
for (var j = 0; j < batch; j++) {
var p = _mapController.toScreenLocation(LatLng(j.toDouble() % 80, j.toDouble() % 300));
list.add(p);
}
Future.wait(list);
sw.stop();
results[batch][0] += sw.elapsedMilliseconds;
sw.reset();
}

//
// batch
//
for (var i = 0; i < trial; i++) {
sw.start();
var param = <LatLng>[];
for (var j = 0; j < batch; j++) {
param.add(LatLng(j.toDouble() % 80, j.toDouble() % 300));
}
Future.wait([_mapController.toScreenLocationBatch(param)]);
sw.stop();
results[batch][1] += sw.elapsedMilliseconds;
sw.reset();
}

print('batch=$batch,primitive=${results[batch][0] / trial}ms, batch=${results[batch][1] / trial}ms');
}

}
}

class Marker extends StatefulWidget {
final Point _initialPosition;
final LatLng _coordinate;
final void Function(_MarkerState) _addMarkerState;

Marker(String key, this._coordinate, this._initialPosition, this._addMarkerState) : super(key: Key(key));

@override
State<StatefulWidget> createState() {
final state = _MarkerState(_initialPosition);
_addMarkerState(state);
return state;
}
}

class _MarkerState extends State with TickerProviderStateMixin {
final _iconSize = 20.0;

Point _position;

AnimationController _controller;
Animation<double> _animation;

_MarkerState(this._position);

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.elasticOut,
);
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
var ratio = 1.0;

//web does not support Platform._operatingSystem
if (!kIsWeb) {
// iOS returns logical pixel while Android returns screen pixel
ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio;
}

return
Positioned(
left: _position.x / ratio - _iconSize / 2,
top: _position.y / ratio - _iconSize / 2,
child:
RotationTransition(
turns: _animation,
child:
Image.asset('assets/symbols/2.0x/custom-icon.png', height: _iconSize))
);
}

void updatePosition(Point<num> point) {
setState(() {
_position = point;
});
}

LatLng getCoordinate() {
return (widget as Marker)._coordinate;
}
}

2 changes: 2 additions & 0 deletions example/lib/main.dart
Expand Up @@ -5,6 +5,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:mapbox_gl_example/custom_marker.dart';
import 'package:mapbox_gl_example/full_map.dart';
import 'package:mapbox_gl_example/offline_regions.dart';

Expand Down Expand Up @@ -36,6 +37,7 @@ final List<ExamplePage> _allPages = <ExamplePage>[
ScrollingMapPage(),
OfflineRegionsPage(),
AnnotationOrderPage(),
CustomMarkerPage(),
];

class MapsDemo extends StatelessWidget {
Expand Down
2 changes: 1 addition & 1 deletion example/pubspec.yaml
Expand Up @@ -4,7 +4,7 @@ publish_to: 'none'
version: 1.0.0+1

environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
sdk: ">=2.1.0 <3.0.0"

dependencies:
flutter:
Expand Down
19 changes: 19 additions & 0 deletions ios/Classes/MapboxMapController.swift
Expand Up @@ -181,6 +181,25 @@ class MapboxMapController: NSObject, FlutterPlatformView, MGLMapViewDelegate, Ma
reply["x"] = returnVal.x as NSObject
reply["y"] = returnVal.y as NSObject
result(reply)
case "map#toScreenLocationBatch":
guard let arguments = methodCall.arguments as? [String: Any] else { return }
guard let data = arguments["coordinates"] as? FlutterStandardTypedData else { return }
let latLngs = data.data.withUnsafeBytes {
Array(
UnsafeBufferPointer(
start: $0.baseAddress!.assumingMemoryBound(to: Double.self),
count:Int(data.elementCount))
)
}
var reply: [Double] = Array(repeating: 0.0, count: latLngs.count)
for i in stride(from: 0, to: latLngs.count, by: 2) {
let coordinate = CLLocationCoordinate2DMake(latLngs[i], latLngs[i + 1])
let returnVal = mapView.convert(coordinate, toPointTo: mapView)
reply[i] = Double(returnVal.x)
reply[i + 1] = Double(returnVal.y)
}
result(FlutterStandardTypedData(
float64: Data(bytes: &reply, count: reply.count * 8) ))
case "map#getMetersPerPixelAtLatitude":
guard let arguments = methodCall.arguments as? [String: Any] else { return }
var reply = [String: NSObject]()
Expand Down
4 changes: 4 additions & 0 deletions lib/src/controller.dart
Expand Up @@ -820,6 +820,10 @@ class MapboxMapController extends ChangeNotifier {
return MapboxGlPlatform.getInstance(_id).toScreenLocation(latLng);
}

Future<List<Point>> toScreenLocationBatch(Iterable<LatLng> latLngs) async {
return MapboxGlPlatform.getInstance(_id).toScreenLocationBatch(latLngs);
}

/// Returns the geographic location (as [LatLng]) that corresponds to a point on the screen. The screen location is specified in screen pixels (not display pixels) relative to the top left of the map (not the top left of the whole screen).
Future<LatLng> toLatLng(Point screenLocation) async {
return MapboxGlPlatform.getInstance(_id).toLatLng(screenLocation);
Expand Down
Expand Up @@ -254,6 +254,11 @@ abstract class MapboxGlPlatform {
'toScreenLocation() has not been implemented.');
}

Future<List<Point>> toScreenLocationBatch(Iterable<LatLng> latLngs) async{
throw UnimplementedError(
'toScreenLocationList() has not been implemented.');
}

Future<LatLng> toLatLng(Point screenLocation) async{
throw UnimplementedError(
'toLatLng() has not been implemented.');
Expand Down
19 changes: 19 additions & 0 deletions mapbox_gl_platform_interface/lib/src/method_channel_mapbox_gl.dart
Expand Up @@ -543,6 +543,25 @@ class MethodChannelMapboxGl extends MapboxGlPlatform {
}
}

@override
Future<List<Point>> toScreenLocationBatch(Iterable<LatLng> latLngs) async {
try {
var coordinates = Float64List.fromList(
latLngs.map((e) => [e.latitude, e.longitude]).expand((e) => e).toList());
Float64List result = await _channel
.invokeMethod('map#toScreenLocationBatch', {"coordinates": coordinates});

var points = <Point>[];
for (int i = 0; i < result.length; i += 2) {
points.add(Point(result[i], result[i + 1]));
}

return points;
} on PlatformException catch (e) {
return new Future.error(e);
}
}

@override
Future<void> removeImageSource(String imageSourceId) async {
try {
Expand Down
8 changes: 8 additions & 0 deletions mapbox_gl_web/lib/src/mapbox_map_controller.dart
Expand Up @@ -665,6 +665,14 @@ class MapboxMapController extends MapboxGlPlatform
return Point(screenPosition.x.round(), screenPosition.y.round());
}

@override
Future<List<Point>> toScreenLocationBatch(Iterable<LatLng> latLngs) async {
return latLngs.map((latLng) {
var screenPosition = _map.project(LngLat(latLng.longitude, latLng.latitude));
return Point(screenPosition.x.round(), screenPosition.y.round());
}).toList(growable: false);
}

@override
Future<LatLng> toLatLng(Point screenLocation) async {
var lngLat =
Expand Down

0 comments on commit 489bbd8

Please sign in to comment.