Skip to content

Commit

Permalink
perf(polygons): cull labels separately, and cache TextPainter (#1716)
Browse files Browse the repository at this point in the history
  • Loading branch information
ignatz authored Nov 3, 2023
1 parent 1964910 commit 0758979
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 31 deletions.
51 changes: 30 additions & 21 deletions lib/src/layer/polygon_layer/label.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,50 @@ import 'package:latlong2/latlong.dart';
import 'package:polylabel/polylabel.dart';

void Function(Canvas canvas)? buildLabelTextPainter({
required math.Point<double> mapSize,
required Offset placementPoint,
required List<Offset> points,
required String labelText,
required ({Offset min, Offset max}) bounds,
required TextPainter textPainter,
required double rotationRad,
required bool rotate,
required TextStyle labelStyle,
required double padding,
}) {
final textSpan = TextSpan(text: labelText, style: labelStyle);
final textPainter = TextPainter(
text: textSpan,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
)..layout();
final dx = placementPoint.dx;
final dy = placementPoint.dy;
final width = textPainter.width;
final height = textPainter.height;

final dx = placementPoint.dx - textPainter.width / 2;
final dy = placementPoint.dy - textPainter.height / 2;

double maxDx = 0;
var minDx = double.infinity;
for (final point in points) {
maxDx = math.max(maxDx, point.dx);
minDx = math.min(minDx, point.dx);
// Cull labels where the polygon is still on the map but the label would not be.
// Currently this is only enabled when the map isn't rotated, since the placementOffset
// is relative to the MobileLayerTransformer rather than in actual screen coordinates.
if (rotationRad == 0) {
if (dx + width / 2 < 0 || dx - width / 2 > mapSize.x) {
return null;
}
if (dy + height / 2 < 0 || dy - height / 2 > mapSize.y) {
return null;
}
}

if (maxDx - minDx - padding > textPainter.width) {
// Note: I'm pretty sure this doesn't work for concave shapes. It would be more
// correct to evaluate the width of the polygon at the height of the label.
if (bounds.max.dx - bounds.min.dx - padding > width) {
return (canvas) {
if (rotate) {
canvas.save();
canvas.translate(placementPoint.dx, placementPoint.dy);
canvas.translate(dx, dy);
canvas.rotate(-rotationRad);
canvas.translate(-placementPoint.dx, -placementPoint.dy);
canvas.translate(-dx, -dy);
}

textPainter.paint(canvas, Offset(dx, dy));
textPainter.paint(
canvas,
Offset(
dx - width / 2,
dy - height / 2,
),
);

if (rotate) {
canvas.restore();
}
Expand Down
39 changes: 29 additions & 10 deletions lib/src/layer/polygon_layer/polygon_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ class Polygon {
LatLngBounds get boundingBox =>
_boundingBox ??= LatLngBounds.fromPoints(points);

TextPainter? _textPainter;
TextPainter? get textPainter {
if (label != null) {
return _textPainter ??= TextPainter(
text: TextSpan(text: label, style: labelStyle),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
)..layout();
}
return null;
}

Polygon({
required this.points,
this.holePointsList,
Expand Down Expand Up @@ -148,6 +160,14 @@ class PolygonPainter extends CustomPainter {

int? _hash;

({Offset min, Offset max}) getBounds(Polygon polygon) {
final bbox = polygon.boundingBox;
return (
min: map.getOffsetFromOrigin(bbox.southWest),
max: map.getOffsetFromOrigin(bbox.northEast),
);
}

List<Offset> getOffsets(List<LatLng> points) {
return List.generate(
points.length,
Expand Down Expand Up @@ -241,7 +261,7 @@ class PolygonPainter extends CustomPainter {
}
}

if (polygonLabels && !drawLabelsLast && polygon.label != null) {
if (!drawLabelsLast && polygonLabels && polygon.textPainter != null) {
// Labels are expensive because:
// * they themselves cannot easily be pulled into our batched path
// painting with the given text APIs
Expand All @@ -252,10 +272,10 @@ class PolygonPainter extends CustomPainter {
// The painter will be null if the layouting algorithm determined that
// there isn't enough space.
final painter = buildLabelTextPainter(
mapSize: map.size,
placementPoint: map.getOffsetFromOrigin(polygon.labelPosition),
points: offsets,
labelText: polygon.label!,
labelStyle: polygon.labelStyle,
bounds: getBounds(polygon),
textPainter: polygon.textPainter!,
rotationRad: map.rotationRad,
rotate: polygon.rotateLabel,
padding: 20,
Expand All @@ -277,14 +297,13 @@ class PolygonPainter extends CustomPainter {
if (polygon.points.isEmpty) {
continue;
}
final offsets = getOffsets(polygon.points);

if (polygon.label != null) {
final textPainter = polygon.textPainter;
if (textPainter != null) {
final painter = buildLabelTextPainter(
mapSize: map.size,
placementPoint: map.getOffsetFromOrigin(polygon.labelPosition),
points: offsets,
labelText: polygon.label!,
labelStyle: polygon.labelStyle,
bounds: getBounds(polygon),
textPainter: textPainter,
rotationRad: map.rotationRad,
rotate: polygon.rotateLabel,
padding: 20,
Expand Down

0 comments on commit 0758979

Please sign in to comment.