Skip to content

Commit

Permalink
Clean up CRS and do less work for poly(gon|line)s.
Browse files Browse the repository at this point in the history
  • Loading branch information
ignatz committed Nov 2, 2023
1 parent 1964910 commit c89e959
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 67 deletions.
53 changes: 23 additions & 30 deletions lib/src/geo/crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,8 @@ abstract class Crs {
/// Converts a point on the sphere surface (with a certain zoom) in a
/// map point.
Point<double> latLngToPoint(LatLng latlng, double zoom) {
try {
final projectedPoint = projection.project(latlng);
final scale = this.scale(zoom);
return transformation.transform(projectedPoint, scale);
} catch (_) {
return const Point(0, 0);
}
final projectedPoint = projection.project(latlng);
return transformation.transform(projectedPoint, scale(zoom));
}

/// Converts a map point to the sphere coordinate (at a certain zoom).
Expand Down Expand Up @@ -114,7 +109,7 @@ class Epsg3857 extends Earth {
@override
final Transformation transformation;

static const num _scale = 0.5 / (math.pi * SphericalMercator.r);
static const double _scale = 0.5 / (math.pi * SphericalMercator.r);

const Epsg3857()
: projection = const SphericalMercator(),
Expand Down Expand Up @@ -183,7 +178,7 @@ class Proj4Crs extends Crs {
required String code,
required proj4.Projection proj4Projection,
Transformation? transformation,
List<Point>? origins,
List<Point<double>>? origins,
Bounds<double>? bounds,
List<double>? scales,
List<double>? resolutions,
Expand Down Expand Up @@ -230,15 +225,11 @@ class Proj4Crs extends Crs {
/// map point.
@override
Point<double> latLngToPoint(LatLng latlng, double zoom) {
try {
final projectedPoint = projection.project(latlng);
final scale = this.scale(zoom);
final transformation = _getTransformationByZoom(zoom);

return transformation.transform(projectedPoint, scale);
} catch (_) {
return const Point(0, 0);
}
final projectedPoint = projection.project(latlng);
final scale = this.scale(zoom);
final transformation = _getTransformationByZoom(zoom);

return transformation.transform(projectedPoint, scale);
}

/// Converts a map point to the sphere coordinate (at a certain zoom).
Expand Down Expand Up @@ -313,14 +304,15 @@ class Proj4Crs extends Crs {

/// returns Transformation object based on zoom
Transformation _getTransformationByZoom(double zoom) {
if (null == _transformations) {
final transformations = _transformations;
if (transformations == null || transformations.isEmpty) {
return transformation;
}

final iZoom = zoom.round();
final lastIdx = _transformations!.length - 1;
final lastIdx = transformations.length - 1;

return _transformations![iZoom > lastIdx ? lastIdx : iZoom];
return transformations[iZoom > lastIdx ? lastIdx : iZoom];
}
}

Expand Down Expand Up @@ -369,7 +361,7 @@ class _LonLat extends Projection {
@override
LatLng unproject(Point point) {
return LatLng(
inclusiveLat(point.y as double), inclusiveLng(point.x as double));
inclusiveLat(point.y.toDouble()), inclusiveLng(point.x.toDouble()));
}
}

Expand All @@ -391,12 +383,13 @@ class SphericalMercator extends Projection {
@override
Point<double> project(LatLng latlng) {
const d = math.pi / 180;
const max = maxLatitude;
final lat = math.max(math.min(max, latlng.latitude), -max);
final lat = latlng.latitude.clamp(-maxLatitude, maxLatitude);
final sin = math.sin(lat * d);

return Point(
r * latlng.longitude * d, r * math.log((1 + sin) / (1 - sin)) / 2);
r * d * latlng.longitude,
r / 2 * math.log((1 + sin) / (1 - sin)),
);
}

@override
Expand Down Expand Up @@ -434,18 +427,18 @@ class _Proj4Projection extends Projection {
@override
LatLng unproject(Point point) {
final point2 = proj4Projection.transform(
epsg4326, proj4.Point(x: point.x as double, y: point.y as double));
epsg4326, proj4.Point(x: point.x.toDouble(), y: point.y.toDouble()));

return LatLng(inclusiveLat(point2.y), inclusiveLng(point2.x));
}
}

@immutable
class Transformation {
final num a;
final num b;
final num c;
final num d;
final double a;
final double b;
final double c;
final double d;

const Transformation(this.a, this.b, this.c, this.d);

Expand Down
46 changes: 32 additions & 14 deletions lib/src/layer/polygon_layer/label.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,60 @@ 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 PolygonBounds bounds,
required String labelText,
required double rotationRad,
required bool rotate,
required TextStyle labelStyle,
required double padding,
}) {
final textSpan = TextSpan(text: labelText, style: labelStyle);
final textPainter = TextPainter(
text: textSpan,
text: TextSpan(text: labelText, style: labelStyle),
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
)..layout();

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

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 wouldn't 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.
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: 28 additions & 11 deletions lib/src/layer/polygon_layer/polygon_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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/bounds.dart';
import 'package:flutter_map/src/misc/point_extensions.dart';
import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI

enum PolygonLabelPlacement {
Expand Down Expand Up @@ -148,16 +150,27 @@ class PolygonPainter extends CustomPainter {

int? _hash;

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

List<Offset> getOffsets(Offset origin, List<LatLng> points) {
return List.generate(
points.length,
(index) {
return map.getOffsetFromOrigin(points[index]);
},
(index) => getOffset(origin, points[index]),
growable: false,
);
}

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

@override
void paint(Canvas canvas, Size size) {
var filledPath = ui.Path();
Expand Down Expand Up @@ -193,12 +206,14 @@ class PolygonPainter extends CustomPainter {
lastHash = null;
}

final origin = (map.project(map.center) - map.size / 2).toOffset();

// Main loop constructing batched fill and border paths from given polygons.
for (final polygon in polygons) {
if (polygon.points.isEmpty) {
continue;
}
final offsets = getOffsets(polygon.points);
final offsets = getOffsets(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 @@ -228,7 +243,7 @@ class PolygonPainter extends CustomPainter {

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

Expand All @@ -252,8 +267,9 @@ class PolygonPainter extends CustomPainter {
// The painter will be null if the layouting algorithm determined that
// there isn't enough space.
final painter = buildLabelTextPainter(
placementPoint: map.getOffsetFromOrigin(polygon.labelPosition),
points: offsets,
mapSize: map.size,
placementPoint: getOffset(origin, polygon.labelPosition),
bounds: getBounds(origin, polygon),
labelText: polygon.label!,
labelStyle: polygon.labelStyle,
rotationRad: map.rotationRad,
Expand All @@ -277,12 +293,13 @@ class PolygonPainter extends CustomPainter {
if (polygon.points.isEmpty) {
continue;
}
final offsets = getOffsets(polygon.points);

if (polygon.label != null) {
final painter = buildLabelTextPainter(
placementPoint: map.getOffsetFromOrigin(polygon.labelPosition),
points: offsets,
mapSize: map.size,
placementPoint:
map.project(polygon.labelPosition).toOffset() - origin,
bounds: getBounds(origin, polygon),
labelText: polygon.label!,
labelStyle: polygon.labelStyle,
rotationRad: map.rotationRad,
Expand Down
23 changes: 16 additions & 7 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/point_extensions.dart';
import 'package:latlong2/latlong.dart';

class Polyline {
Expand Down Expand Up @@ -97,13 +98,19 @@ class PolylinePainter extends CustomPainter {

int? _hash;

List<Offset> getOffsets(List<LatLng> points) {
return List.generate(points.length, (index) {
return getOffset(points[index]);
}, growable: false);
Offset getOffset(Offset origin, LatLng point) {
// Critically create as little garbage as possible. This is called on every frame.
final projected = map.project(point);
return Offset(projected.x - origin.dx, projected.y - origin.dy);
}

Offset getOffset(LatLng point) => map.getOffsetFromOrigin(point);
List<Offset> getOffsets(Offset origin, List<LatLng> points) {
return List.generate(
points.length,
(index) => getOffset(origin, points[index]),
growable: false,
);
}

@override
void paint(Canvas canvas, Size size) {
Expand Down Expand Up @@ -144,8 +151,10 @@ class PolylinePainter extends CustomPainter {
paint = Paint();
}

final origin = map.project(map.center).toOffset() - map.size.toOffset() / 2;

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

strokeWidth = delta.distance;
} else {
Expand Down
10 changes: 5 additions & 5 deletions lib/src/map/camera/camera.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class MapCamera {
LatLngBounds? _bounds;

/// Lazily calculated field
Point<int>? _pixelOrigin;
Point<double>? _pixelOrigin;

/// Lazily calculated field
double? _rotationRad;
Expand Down Expand Up @@ -89,8 +89,8 @@ class MapCamera {
/// The offset of the top-left corner of the bounding rectangle of this
/// camera. This will not equal the offset of the top-left visible pixel when
/// the map is rotated.
Point<int> get pixelOrigin =>
_pixelOrigin ??= (project(center, zoom) - size / 2.0).round();
Point<double> get pixelOrigin =>
_pixelOrigin ??= project(center, zoom) - size / 2.0;

/// The camera of the closest [FlutterMap] ancestor. If this is called from a
/// context with no [FlutterMap] ancestor null, is returned.
Expand Down Expand Up @@ -118,7 +118,7 @@ class MapCamera {
Point<double>? size,
Bounds<double>? pixelBounds,
LatLngBounds? bounds,
Point<int>? pixelOrigin,
Point<double>? pixelOrigin,
double? rotationRad,
}) : _cameraSize = size,
_pixelBounds = pixelBounds,
Expand Down Expand Up @@ -254,7 +254,7 @@ class MapCamera {

/// Calculates the [Offset] from the [pos] to this camera's [pixelOrigin].
Offset getOffsetFromOrigin(LatLng pos) =>
project(pos).subtract(pixelOrigin).toOffset();
(project(pos) - pixelOrigin).toOffset();

/// Calculates the pixel origin of this [MapCamera] at the given
/// [center]/[zoom].
Expand Down

0 comments on commit c89e959

Please sign in to comment.