Skip to content

Commit

Permalink
Reduce the cost of polygons by culling the labels on partially visibl…
Browse files Browse the repository at this point in the history
…e polygons, deriving pixel bounds from polygon's latlng bounds, and most importantly cache the TextPainter.
  • Loading branch information
ignatz committed Nov 3, 2023
1 parent 1964910 commit 564f65a
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 31 deletions.
54 changes: 33 additions & 21 deletions lib/src/layer/polygon_layer/label.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,54 @@ import 'package:flutter_map/src/layer/polygon_layer/polygon_layer.dart';
import 'package:latlong2/latlong.dart';
import 'package:polylabel/polylabel.dart';

class PolygonBounds {
final Offset min;
final Offset max;

const PolygonBounds(this.min, this.max);
}

void Function(Canvas canvas)? buildLabelTextPainter({
required math.Point<double> mapSize,
required Offset placementPoint,
required List<Offset> points,
required String labelText,
required PolygonBounds 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.
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
38 changes: 28 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,13 @@ class PolygonPainter extends CustomPainter {

int? _hash;

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

List<Offset> getOffsets(List<LatLng> points) {
return List.generate(
points.length,
Expand Down Expand Up @@ -241,7 +260,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 +271,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 +296,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 564f65a

Please sign in to comment.