Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions benchmark/crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Future<void> main() async {
const N = 100000000;

const crs = Epsg3857();
final cachedCrs = Epsg3857Cached();
results.add(await timedRun('Concrete type: ${crs.code}.latLngToXY()', () {
double x = 0;
double y = 0;
Expand All @@ -40,6 +41,20 @@ Future<void> main() async {
}
return x + y;
}));
results.add(await timedRun(
'Concrete type (cached): ${cachedCrs.code}.latLngToXY()',
() {
double x = 0;
double y = 0;
for (int i = 0; i < N; ++i) {
final latlng = LatLng((i % 90).toDouble(), (i % 180).toDouble());
final (cx, cy) = cachedCrs.latLngToXY(latlng, 1);
x += cx;
y += cy;
}
return x + y;
},
));

results.add(await timedRun('Concrete type: ${crs.code}.latLngToOffset()', () {
double x = 0;
Expand All @@ -53,13 +68,30 @@ Future<void> main() async {
return x + y;
}));

const crss = <Crs>[
Epsg3857(),
Epsg4326(),
results.add(await timedRun(
'Concrete type (cached): ${cachedCrs.code}.latLngToOffset()',
() {
double x = 0;
double y = 0;
for (int i = 0; i < N; ++i) {
final latlng = LatLng((i % 90).toDouble(), (i % 180).toDouble());
final p = cachedCrs.latLngToOffset(latlng, 1);
x += p.dx;
y += p.dy;
}
return x + y;
},
));

final crss = <(String, Crs)>[
('EPSG:3857', const Epsg3857()),
('EPSG:3857 (cached)', Epsg3857Cached()),
('EPSG:4326', const Epsg4326()),
('EPSG:4326 (cached)', Epsg4326Cached()),
];

for (final crs in crss) {
results.add(await timedRun('${crs.code}.latLngToXY()', () {
for (final (label, crs) in crss) {
results.add(await timedRun('$label.latLngToXY()', () {
double x = 0;
double y = 0;
for (int i = 0; i < N; ++i) {
Expand All @@ -71,7 +103,7 @@ Future<void> main() async {
return x + y;
}));

results.add(await timedRun('${crs.code}.latlngToPoint()', () {
results.add(await timedRun('$label.latlngToPoint()', () {
double x = 0;
double y = 0;
for (int i = 0; i < N; ++i) {
Expand All @@ -83,7 +115,7 @@ Future<void> main() async {
return x + y;
}));

results.add(await timedRun('${crs.code}.pointToLatLng()', () {
results.add(await timedRun('$label.pointToLatLng()', () {
double x = 0;
double y = 0;
for (int i = 0; i < N; ++i) {
Expand Down
81 changes: 75 additions & 6 deletions lib/src/geo/crs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,10 @@ abstract class Crs {
LatLng offsetToLatLng(Offset point, double zoom);

/// Zoom to Scale function.
double scale(double zoom) => 256.0 * math.pow(2, zoom);
double scale(double zoom) => 256.0 * math.pow(2.0, zoom);

/// Scale to Zoom function.
double zoom(double scale) => math.log(scale / 256) / math.ln2;
double zoom(double scale) => math.log(scale / 256.0) / math.ln2;

/// Rescales the bounds to a given zoom value.
Rect? getProjectedBounds(double zoom);
Expand All @@ -74,6 +74,51 @@ abstract class Crs {
bool get replicatesWorldLongitude => false;
}

/// Mixin which uses [_ScaleZoomCache] to implement [scale] & [zoom] methods
/// backed by a cache, to improve performance.
mixin _ScaleCacheMixin on Crs {
static final _caches = Expando<_ScaleZoomCache>('_scaleZoomCache');

_ScaleZoomCache get _cache {
final existing = _caches[this];
if (existing != null) return existing;
final created = _ScaleZoomCache();
_caches[this] = created;
return created;
}

@override
double scale(double zoom) {
final cache = _cache;
if (zoom == cache.lastScaleZoom) return cache.lastScaleValue;
final value = super.scale(zoom);
cache.lastScaleZoom = zoom;
cache.lastScaleValue = value;
return value;
}

@override
double zoom(double scale) {
final cache = _cache;
if (scale == cache.lastZoomScale) return cache.lastZoomValue;
final value = super.zoom(scale);
cache.lastZoomScale = scale;
cache.lastZoomValue = value;
return value;
}
}

/// Object used by a caching Crs (which mixes in [_ScaleCacheMixin]) to store
/// cache details
class _ScaleZoomCache {
_ScaleZoomCache();

double lastScaleZoom = double.nan;
double lastScaleValue = double.nan;
double lastZoomScale = double.nan;
double lastZoomValue = double.nan;
}

/// Internal base class for CRS with a single zoom-level independent transformation.
@immutable
@internal
Expand Down Expand Up @@ -133,7 +178,9 @@ abstract class CrsWithStaticTransformation extends Crs {
}
}

/// Custom CRS for non geographical maps
/// Custom CRS for non geographical maps.
///
/// See also [CrsSimpleCached].
@immutable
class CrsSimple extends CrsWithStaticTransformation {
/// Create a new [CrsSimple].
Expand All @@ -148,7 +195,15 @@ class CrsSimple extends CrsWithStaticTransformation {
);
}

/// EPSG:3857, The most common CRS used for rendering maps.
/// [CrsSimple] with cached scale/zoom.
class CrsSimpleCached extends CrsSimple with _ScaleCacheMixin {
/// [CrsSimple] with cached scale/zoom.
CrsSimpleCached() : super();
}

/// EPSG:3857, the most common CRS used for rendering maps.
///
/// See also [Epsg3857Cached].
@immutable
class Epsg3857 extends CrsWithStaticTransformation {
static const double _scale = 0.5 / (math.pi * SphericalMercator.r);
Expand Down Expand Up @@ -185,8 +240,16 @@ class Epsg3857 extends CrsWithStaticTransformation {
bool get replicatesWorldLongitude => true;
}

/// EPSG:4326, A common CRS among GIS enthusiasts.
/// Uses simple Equirectangular projection.
/// [Epsg3857] with cached scale/zoom.
class Epsg3857Cached extends Epsg3857 with _ScaleCacheMixin {
/// [Epsg3857] with cached scale/zoom.
Epsg3857Cached() : super();
}

/// EPSG:4326, a common CRS among GIS enthusiasts using simple equirectangular
/// projection.
///
/// See also [Epsg4326Cached].
@immutable
class Epsg4326 extends CrsWithStaticTransformation {
/// Create a new [Epsg4326] CRS instance.
Expand All @@ -200,6 +263,12 @@ class Epsg4326 extends CrsWithStaticTransformation {
);
}

/// [Epsg4326] with cached scale/zoom.
class Epsg4326Cached extends Epsg4326 with _ScaleCacheMixin {
/// [Epsg4326] with cached scale/zoom.
Epsg4326Cached() : super();
}

/// Custom CRS
@immutable
class Proj4Crs extends Crs {
Expand Down