Skip to content

Commit

Permalink
Monomorphize projection call and avoid Num.clamp. Saves another 22% C…
Browse files Browse the repository at this point in the history
…PU cycles on the UI thread for my test-case.
  • Loading branch information
ignatz committed Nov 23, 2023
1 parent f196242 commit 3ea11fe
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 55 deletions.
11 changes: 8 additions & 3 deletions lib/src/geo/crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -322,13 +322,15 @@ abstract class Projection {

const Projection(this.bounds);

@nonVirtual
Point<double> project(LatLng latlng) {
final (x, y) = projectXY(latlng);
return Point<double>(x, y);
}

(double, double) projectXY(LatLng latlng);

@nonVirtual
LatLng unproject(Point point) =>
unprojectXY(point.x.toDouble(), point.y.toDouble());
LatLng unprojectXY(double x, double y);
Expand Down Expand Up @@ -365,7 +367,7 @@ class SphericalMercator extends Projection {
const SphericalMercator() : super(_bounds);

static double projectLat(double latitude) {
final lat = latitude.clamp(-maxLatitude, maxLatitude);
final lat = _clampSym(latitude, maxLatitude);
final sin = math.sin(lat * math.pi / 180);

return r / 2 * math.log((1 + sin) / (1 - sin));
Expand Down Expand Up @@ -445,5 +447,8 @@ class _Transformation {
);
}

double _inclusiveLat(double value) => value.clamp(-90, 90);
double _inclusiveLng(double value) => value.clamp(-180, 180);
// Num.clamp is slow due to virtual function overhead.
double _clampSym(double value, double limit) =>
value < -limit ? -limit : (value > limit ? limit : value);
double _inclusiveLat(double value) => _clampSym(value, 90);
double _inclusiveLng(double value) => _clampSym(value, 180);
32 changes: 5 additions & 27 deletions lib/src/layer/polygon_layer/polygon_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter_map/src/geo/latlng_bounds.dart';
import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart';
import 'package:flutter_map/src/layer/polygon_layer/label.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:flutter_map/src/misc/offsets.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI

Expand Down Expand Up @@ -164,31 +165,8 @@ class PolygonPainter extends CustomPainter {
({Offset min, Offset max}) getBounds(Offset origin, Polygon polygon) {
final bbox = polygon.boundingBox;
return (
min: getOffset(origin, bbox.southWest),
max: getOffset(origin, bbox.northEast),
);
}

Offset getOffset(Offset origin, LatLng point) {
// Critically create as little garbage as possible. This is called on every frame.
final projected = map.project(point, map.zoom);
return Offset(projected.x - origin.dx, projected.y - origin.dy);
}

List<Offset> getOffsets(Offset origin, List<LatLng> points) {
final crs = map.crs;
final zoomScale = crs.scale(map.zoom);

final ox = -origin.dx;
final oy = -origin.dy;

return List<Offset>.generate(
points.length,
(index) {
final (x, y) = crs.latLngToXY(points[index], zoomScale);
return Offset(x + ox, y + oy);
},
growable: false,
min: getOffset(map, origin, bbox.southWest),
max: getOffset(map, origin, bbox.northEast),
);
}

Expand Down Expand Up @@ -234,7 +212,7 @@ class PolygonPainter extends CustomPainter {
if (polygon.points.isEmpty) {
continue;
}
final offsets = getOffsets(origin, polygon.points);
final offsets = getOffsets(map, origin, polygon.points);

// The hash is based on the polygons visual properties. If the hash from
// the current and the previous polygon no longer match, we need to flush
Expand Down Expand Up @@ -264,7 +242,7 @@ class PolygonPainter extends CustomPainter {

final holeOffsetsList = List<List<Offset>>.generate(
holePointsList.length,
(i) => getOffsets(origin, holePointsList[i]),
(i) => getOffsets(map, origin, holePointsList[i]),
growable: false,
);

Expand Down
28 changes: 3 additions & 25 deletions lib/src/layer/polyline_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_map/src/geo/latlng_bounds.dart';
import 'package:flutter_map/src/layer/general/mobile_layer_transformer.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:flutter_map/src/misc/offsets.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:latlong2/latlong.dart';

Expand Down Expand Up @@ -98,29 +99,6 @@ class PolylinePainter extends CustomPainter {

int? _hash;

Offset getOffset(Offset origin, LatLng point) {
// Critically create as little garbage as possible. This is called on every frame.
final projected = map.project(point, map.zoom);
return Offset(projected.x - origin.dx, projected.y - origin.dy);
}

List<Offset> getOffsets(Offset origin, List<LatLng> points) {
final crs = map.crs;
final zoomScale = crs.scale(map.zoom);

final ox = -origin.dx;
final oy = -origin.dy;

return List<Offset>.generate(
points.length,
(index) {
final (x, y) = crs.latLngToXY(points[index], zoomScale);
return Offset(x + ox, y + oy);
},
growable: false,
);
}

@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
Expand Down Expand Up @@ -163,7 +141,7 @@ class PolylinePainter extends CustomPainter {
final origin = map.project(map.center).toOffset() - map.size.toOffset() / 2;

for (final polyline in polylines) {
final offsets = getOffsets(origin, polyline.points);
final offsets = getOffsets(map, origin, polyline.points);
if (offsets.isEmpty) {
continue;
}
Expand All @@ -185,7 +163,7 @@ class PolylinePainter extends CustomPainter {
polyline.strokeWidth,
180,
);
final delta = firstOffset - getOffset(origin, r);
final delta = firstOffset - getOffset(map, origin, r);

strokeWidth = delta.distance;
} else {
Expand Down
40 changes: 40 additions & 0 deletions lib/src/misc/offsets.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'dart:ui';

import 'package:flutter_map/src/geo/crs.dart';
import 'package:flutter_map/src/map/camera/camera.dart';
import 'package:latlong2/latlong.dart';

Offset getOffset(MapCamera camera, Offset origin, LatLng point) {
final crs = camera.crs;
final zoomScale = crs.scale(camera.zoom);
final (x, y) = crs.latLngToXY(point, zoomScale);
return Offset(x - origin.dx, y - origin.dy);
}

List<Offset> getOffsets(MapCamera camera, Offset origin, List<LatLng> points) {
// Critically create as little garbage as possible. This is called on every frame.
final crs = camera.crs;
final zoomScale = crs.scale(camera.zoom);

final ox = -origin.dx;
final oy = -origin.dy;
final len = points.length;

// Optimization: monomorphize the Epsg3857-case to save the virtual function overhead.
if (crs is Epsg3857) {
final Epsg3857 epsg3857 = crs;
final v = List<Offset>.filled(len, Offset.zero);
for (int i = 0; i < len; ++i) {
final (x, y) = epsg3857.latLngToXY(points[i], zoomScale);
v[i] = Offset(x + ox, y + oy);
}
return v;
}

final v = List<Offset>.filled(len, Offset.zero);
for (int i = 0; i < len; ++i) {
final (x, y) = crs.latLngToXY(points[i], zoomScale);
v[i] = Offset(x + ox, y + oy);
}
return v;
}

0 comments on commit 3ea11fe

Please sign in to comment.