From 0dc3fa9ba851e5e5fd7b29012ef559f488abcaf5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 5 Nov 2023 12:31:34 +0100 Subject: [PATCH] perf!: remove unnecessary rounding and surrounding features, and produce less garbage (#1714) --- lib/src/geo/crs.dart | 53 ++++++++----------- lib/src/layer/marker_layer.dart | 3 +- lib/src/layer/overlay_image_layer.dart | 14 +++-- .../layer/polygon_layer/polygon_layer.dart | 32 ++++++----- lib/src/layer/polyline_layer.dart | 23 +++++--- lib/src/layer/tile_layer/tile_layer.dart | 5 +- .../tile_layer/wms_tile_layer_options.dart | 5 +- lib/src/map/camera/camera.dart | 15 +++--- lib/src/misc/point_extensions.dart | 44 +-------------- 9 files changed, 78 insertions(+), 116 deletions(-) diff --git a/lib/src/geo/crs.dart b/lib/src/geo/crs.dart index 1abdd3f3e..5ec3d7277 100644 --- a/lib/src/geo/crs.dart +++ b/lib/src/geo/crs.dart @@ -24,13 +24,8 @@ abstract class Crs { /// Converts a point on the sphere surface (with a certain zoom) in a /// map point. Point 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). @@ -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(), @@ -183,7 +178,7 @@ class Proj4Crs extends Crs { required String code, required proj4.Projection proj4Projection, Transformation? transformation, - List? origins, + List>? origins, Bounds? bounds, List? scales, List? resolutions, @@ -230,15 +225,11 @@ class Proj4Crs extends Crs { /// map point. @override Point 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). @@ -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]; } } @@ -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())); } } @@ -391,12 +383,13 @@ class SphericalMercator extends Projection { @override Point 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 @@ -434,7 +427,7 @@ 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)); } @@ -442,10 +435,10 @@ class _Proj4Projection extends Projection { @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); diff --git a/lib/src/layer/marker_layer.dart b/lib/src/layer/marker_layer.dart index 490ad7d73..6b8e231a3 100644 --- a/lib/src/layer/marker_layer.dart +++ b/lib/src/layer/marker_layer.dart @@ -4,7 +4,6 @@ import 'package:flutter/widgets.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/bounds.dart'; -import 'package:flutter_map/src/misc/point_extensions.dart'; import 'package:latlong2/latlong.dart'; /// A container for a [child] widget located at a geographic coordinate [point] @@ -126,7 +125,7 @@ class MarkerLayer extends StatelessWidget { )) continue; // Apply map camera to marker position - final pos = pxPoint.subtract(map.pixelOrigin); + final pos = pxPoint - map.pixelOrigin; yield Positioned( key: m.key, diff --git a/lib/src/layer/overlay_image_layer.dart b/lib/src/layer/overlay_image_layer.dart index 531b51061..a83a9f906 100644 --- a/lib/src/layer/overlay_image_layer.dart +++ b/lib/src/layer/overlay_image_layer.dart @@ -66,8 +66,8 @@ class OverlayImage extends BaseOverlayImage { }) { // northWest is not necessarily upperLeft depending on projection final bounds = Bounds( - camera.project(this.bounds.northWest).subtract(camera.pixelOrigin), - camera.project(this.bounds.southEast).subtract(camera.pixelOrigin), + camera.project(this.bounds.northWest) - camera.pixelOrigin, + camera.project(this.bounds.southEast) - camera.pixelOrigin, ); return Positioned( @@ -111,12 +111,10 @@ class RotatedOverlayImage extends BaseOverlayImage { required Image child, required MapCamera camera, }) { - final pxTopLeft = - camera.project(topLeftCorner).subtract(camera.pixelOrigin); + final pxTopLeft = camera.project(topLeftCorner) - camera.pixelOrigin; final pxBottomRight = - camera.project(bottomRightCorner).subtract(camera.pixelOrigin); - final pxBottomLeft = - camera.project(bottomLeftCorner).subtract(camera.pixelOrigin); + camera.project(bottomRightCorner) - camera.pixelOrigin; + final pxBottomLeft = camera.project(bottomLeftCorner) - camera.pixelOrigin; /// calculate pixel coordinate of top-right corner by calculating the /// vector from bottom-left to top-left and adding it to bottom-right @@ -129,7 +127,7 @@ class RotatedOverlayImage extends BaseOverlayImage { final vectorX = (pxTopRight - pxTopLeft) / bounds.size.x; final vectorY = (pxBottomLeft - pxTopLeft) / bounds.size.y; - final offset = pxTopLeft.subtract(bounds.topLeft); + final offset = pxTopLeft - bounds.topLeft; final a = vectorX.x; final b = vectorX.y; diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart index 0d61879fe..ca5e7f578 100644 --- a/lib/src/layer/polygon_layer/polygon_layer.dart +++ b/lib/src/layer/polygon_layer/polygon_layer.dart @@ -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/point_extensions.dart'; import 'package:latlong2/latlong.dart' hide Path; // conflict with Path from UI enum PolygonLabelPlacement { @@ -160,20 +161,24 @@ class PolygonPainter extends CustomPainter { int? _hash; - ({Offset min, Offset max}) getBounds(Polygon polygon) { + ({Offset min, Offset max}) getBounds(Offset origin, Polygon polygon) { final bbox = polygon.boundingBox; return ( - min: map.getOffsetFromOrigin(bbox.southWest), - max: map.getOffsetFromOrigin(bbox.northEast), + min: getOffset(origin, bbox.southWest), + max: getOffset(origin, bbox.northEast), ); } - List getOffsets(List 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 getOffsets(Offset origin, List points) { return List.generate( points.length, - (index) { - return map.getOffsetFromOrigin(points[index]); - }, + (index) => getOffset(origin, points[index]), growable: false, ); } @@ -213,12 +218,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 @@ -248,7 +255,7 @@ class PolygonPainter extends CustomPainter { final holeOffsetsList = List>.generate( holePointsList.length, - (i) => getOffsets(holePointsList[i]), + (i) => getOffsets(origin, holePointsList[i]), growable: false, ); @@ -274,7 +281,7 @@ class PolygonPainter extends CustomPainter { final painter = buildLabelTextPainter( mapSize: map.size, placementPoint: map.getOffsetFromOrigin(polygon.labelPosition), - bounds: getBounds(polygon), + bounds: getBounds(origin, polygon), textPainter: polygon.textPainter!, rotationRad: map.rotationRad, rotate: polygon.rotateLabel, @@ -301,8 +308,9 @@ class PolygonPainter extends CustomPainter { if (textPainter != null) { final painter = buildLabelTextPainter( mapSize: map.size, - placementPoint: map.getOffsetFromOrigin(polygon.labelPosition), - bounds: getBounds(polygon), + placementPoint: + map.project(polygon.labelPosition).toOffset() - origin, + bounds: getBounds(origin, polygon), textPainter: textPainter, rotationRad: map.rotationRad, rotate: polygon.rotateLabel, diff --git a/lib/src/layer/polyline_layer.dart b/lib/src/layer/polyline_layer.dart index cc245a647..b20de99f1 100644 --- a/lib/src/layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer.dart @@ -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 { @@ -97,13 +98,19 @@ class PolylinePainter extends CustomPainter { int? _hash; - List getOffsets(List 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 getOffsets(Offset origin, List points) { + return List.generate( + points.length, + (index) => getOffset(origin, points[index]), + growable: false, + ); + } @override void paint(Canvas canvas, Size size) { @@ -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; } @@ -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 { diff --git a/lib/src/layer/tile_layer/tile_layer.dart b/lib/src/layer/tile_layer/tile_layer.dart index 3fbe08627..db4d6cf2b 100644 --- a/lib/src/layer/tile_layer/tile_layer.dart +++ b/lib/src/layer/tile_layer/tile_layer.dart @@ -25,7 +25,6 @@ import 'package:flutter_map/src/layer/tile_layer/tile_update_transformer.dart'; import 'package:flutter_map/src/map/camera/camera.dart'; import 'package:flutter_map/src/map/controller/map_controller.dart'; import 'package:flutter_map/src/misc/bounds.dart'; -import 'package:flutter_map/src/misc/point_extensions.dart'; import 'package:http/retry.dart'; import 'package:logger/logger.dart'; @@ -533,8 +532,8 @@ class _TileLayerState extends State with TickerProviderStateMixin { ); final currentPixelOrigin = Point( - map.pixelOrigin.x.toDouble(), - map.pixelOrigin.y.toDouble(), + map.pixelOrigin.x, + map.pixelOrigin.y, ); _tileScaleCalculator.clearCacheUnlessZoomMatches(map.zoom); diff --git a/lib/src/layer/tile_layer/wms_tile_layer_options.dart b/lib/src/layer/tile_layer/wms_tile_layer_options.dart index 2fb33ddf7..82bdd1091 100644 --- a/lib/src/layer/tile_layer/wms_tile_layer_options.dart +++ b/lib/src/layer/tile_layer/wms_tile_layer_options.dart @@ -69,9 +69,8 @@ class WMSTileLayerOptions { } String getUrl(TileCoordinates coords, int tileSize, bool retinaMode) { - final tileSizePoint = Point(tileSize, tileSize); - final nwPoint = coords.scaleBy(tileSizePoint); - final sePoint = nwPoint + tileSizePoint; + final nwPoint = coords * tileSize; + final sePoint = nwPoint + Point(tileSize, tileSize); final nwCoords = crs.pointToLatLng(nwPoint, coords.z.toDouble()); final seCoords = crs.pointToLatLng(sePoint, coords.z.toDouble()); final nw = crs.projection.project(nwCoords); diff --git a/lib/src/map/camera/camera.dart b/lib/src/map/camera/camera.dart index 2875c2db1..c02392a69 100644 --- a/lib/src/map/camera/camera.dart +++ b/lib/src/map/camera/camera.dart @@ -58,7 +58,7 @@ class MapCamera { LatLngBounds? _bounds; /// Lazily calculated field - Point? _pixelOrigin; + Point? _pixelOrigin; /// Lazily calculated field double? _rotationRad; @@ -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 get pixelOrigin => - _pixelOrigin ??= (project(center, zoom) - size / 2.0).round(); + Point 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. @@ -118,7 +118,7 @@ class MapCamera { Point? size, Bounds? pixelBounds, LatLngBounds? bounds, - Point? pixelOrigin, + Point? pixelOrigin, double? rotationRad, }) : _cameraSize = size, _pixelBounds = pixelBounds, @@ -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]. @@ -281,8 +281,7 @@ class MapCamera { /// This will convert a latLng to a position that we could use with a widget /// outside of FlutterMap layer space. Eg using a Positioned Widget. Point latLngToScreenPoint(LatLng latLng) { - final nonRotatedPixelOrigin = - (project(center, zoom) - nonRotatedSize / 2.0).round(); + final nonRotatedPixelOrigin = project(center, zoom) - nonRotatedSize / 2.0; var point = crs.latLngToPoint(latLng, zoom); @@ -292,7 +291,7 @@ class MapCamera { point = rotatePoint(mapCenter, point, counterRotation: false); } - return point.subtract(nonRotatedPixelOrigin); + return point - nonRotatedPixelOrigin; } LatLng pointToLatLng(Point localPoint) { diff --git a/lib/src/misc/point_extensions.dart b/lib/src/misc/point_extensions.dart index dd1568100..ac4ab23f3 100644 --- a/lib/src/misc/point_extensions.dart +++ b/lib/src/misc/point_extensions.dart @@ -67,54 +67,12 @@ extension PointExtension on Point { /// Point(1, 2).subtract(1.5) would cause a runtime error because the /// resulting x/y values are doubles and the return value is a Point since /// the method returns Point. -/// -/// Note that division methods (unscaleBy and the / operator) are defined on -/// Point with a Point return argument because division -/// always returns a double. extension DoublePointExtension on Point { /// Subtract [other] from this Point. + @Deprecated('camera.pixelOrigin is now a Point. Prefer operator-.') Point subtract(Point other) { return Point(x - other.x, y - other.y); } - - /// Add [other] to this Point. - Point add(Point other) { - return Point(x + other.x, y + other.y); - } - - /// Create a new [Point] where [x] and [y] values are scaled by the respective - /// values in [other]. - Point scaleBy(Point other) { - return Point(x * other.x, y * other.y); - } -} - -/// This extension contains methods which, if defined on Point, -/// could cause a runtime error when called on a Point with a non-int -/// argument. An example: -/// -/// Point(1, 2).subtract(1.5) would cause a runtime error because the -/// resulting x/y values are doubles and the return value is a Point since -/// the method returns Point. -/// -/// The methods in this extension only take Point arguments to prevent -/// this. -extension IntegerPointExtension on Point { - /// Subtract [other] from this Point. - Point subtract(Point other) { - return Point(x - other.x, y - other.y); - } - - /// Add [other] to this Point. - Point add(Point other) { - return Point(x + other.x, y + other.y); - } - - /// Create a new [Point] where [x] and [y] values are scaled by the respective - /// values in [other]. - Point scaleBy(Point other) { - return Point(x * other.x, y * other.y); - } } extension OffsetToPointExtension on Offset {