diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/earth/ios/Flutter/flutter_export_environment.sh b/example/earth/ios/Flutter/flutter_export_environment.sh index 2fcb847..9cd9e0c 100755 --- a/example/earth/ios/Flutter/flutter_export_environment.sh +++ b/example/earth/ios/Flutter/flutter_export_environment.sh @@ -1,10 +1,13 @@ #!/bin/sh # This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=/Users/mark/development/flutter" -export "FLUTTER_APPLICATION_PATH=/Users/mark/workspace/flutter/flutter_earth/example/earth" -export "FLUTTER_TARGET=/Users/mark/workspace/flutter/flutter_earth/example/earth/lib/main.dart" +export "FLUTTER_ROOT=/Users/kerimamansaryyev/development/flutter" +export "FLUTTER_APPLICATION_PATH=/Users/kerimamansaryyev/Desktop/flutter_projects/flutter_earth/example/earth" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=lib/main.dart" export "FLUTTER_BUILD_DIR=build" -export "SYMROOT=${SOURCE_ROOT}/../build/ios" -export "FLUTTER_FRAMEWORK_DIR=/Users/mark/development/flutter/bin/cache/artifacts/engine/ios-release" export "FLUTTER_BUILD_NAME=1.0.0" export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=false" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.packages" diff --git a/example/earth/pubspec.lock b/example/earth/pubspec.lock index 3be1a9e..6eeb8d5 100644 --- a/example/earth/pubspec.lock +++ b/example/earth/pubspec.lock @@ -7,28 +7,42 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.8.2" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.15.0" cupertino_icons: dependency: "direct main" description: @@ -36,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.2" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" flutter: dependency: "direct main" description: flutter @@ -47,47 +68,54 @@ packages: path: "../.." relative: true source: path - version: "0.0.3" + version: "0.0.4" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - matcher: + lints: dependency: transitive description: - name: matcher + name: lints url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" - meta: + version: "1.0.1" + matcher: dependency: transitive description: - name: meta + name: matcher url: "https://pub.dartlang.org" source: hosted - version: "1.1.7" - path: + version: "0.12.11" + material_color_utilities: dependency: transitive description: - name: path + name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" - pedantic: + version: "0.1.3" + meta: dependency: transitive description: - name: pedantic + name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" - quiver: + version: "1.7.0" + path: dependency: transitive description: - name: quiver + name: path url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "1.8.0" sky_engine: dependency: transitive description: flutter @@ -99,55 +127,55 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.4.8" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.1" sdks: - dart: ">=2.2.2 <3.0.0" + dart: ">=2.16.0 <3.0.0" diff --git a/example/earth/pubspec.yaml b/example/earth/pubspec.yaml index c574913..f09b22d 100644 --- a/example/earth/pubspec.yaml +++ b/example/earth/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^1.0.0 # For information on the generic Dart part of this file, see the diff --git a/lib/flutter_earth.dart b/lib/flutter_earth.dart index ee18a77..668d86c 100644 --- a/lib/flutter_earth.dart +++ b/lib/flutter_earth.dart @@ -41,7 +41,8 @@ Offset latLonToPoint(double latitude, double longitude) { LatLon pointToLatLon(double x, double y) { final longitude = (x - 0.5) * (2.0 * math.pi); - final latitude = 2.0 * math.atan(math.exp(math.pi - 2.0 * math.pi * y)) - math.pi / 2.0; + final latitude = + 2.0 * math.atan(math.exp(math.pi - 2.0 * math.pi * y)) - math.pi / 2.0; return LatLon(latitude, longitude); } @@ -91,7 +92,8 @@ Vector3 quaternionAxis(Quaternion q) { if (den == 0) return new Vector3(1.0, 0.0, 0.0); final double scale = 1.0 / math.sqrt(den); - return new Vector3(_qStorage[0] * scale, _qStorage[1] * scale, _qStorage[2] * scale); + return new Vector3( + _qStorage[0] * scale, _qStorage[1] * scale, _qStorage[2] * scale); } /// Euler Angles @@ -133,10 +135,13 @@ class EulerAngles { roll *= arg; } - EulerAngles inRadians() => EulerAngles(radians(yaw), radians(pitch), radians(roll)); - EulerAngles inDegrees() => EulerAngles(degrees(yaw), degrees(pitch), degrees(roll)); + EulerAngles inRadians() => + EulerAngles(radians(yaw), radians(pitch), radians(roll)); + EulerAngles inDegrees() => + EulerAngles(degrees(yaw), degrees(pitch), degrees(roll)); @override - String toString() => 'pitch:${pitch.toStringAsFixed(4)}, yaw:${yaw.toStringAsFixed(4)}, roll:${roll.toStringAsFixed(4)}'; + String toString() => + 'pitch:${pitch.toStringAsFixed(4)}, yaw:${yaw.toStringAsFixed(4)}, roll:${roll.toStringAsFixed(4)}'; } class LatLon { @@ -146,7 +151,8 @@ class LatLon { LatLon inRadians() => LatLon(radians(latitude), radians(longitude)); LatLon inDegrees() => LatLon(degrees(latitude), degrees(longitude)); @override - String toString() => 'LatLon(${degrees(latitude ?? 0).toStringAsFixed(2)}, ${degrees(longitude ?? 0).toStringAsFixed(2)})'; + String toString() => + 'LatLon(${degrees(latitude).toStringAsFixed(2)}, ${degrees(longitude).toStringAsFixed(2)})'; } class Polygon { @@ -167,17 +173,17 @@ class Mesh { this.vertexCount = 0; this.indexCount = 0; } - Float32List positions; - Float32List positionsZ; - Float32List texcoords; - Int32List colors; - Uint16List indices; - int vertexCount; - int indexCount; - Image texture; - double x; - double y; - double z; + late Float32List positions; + late Float32List positionsZ; + late Float32List texcoords; + late Int32List colors; + late Uint16List indices; + late int vertexCount; + late int indexCount; + Image? texture; + double x = 0; + double y = 0; + double z = 0; } enum TileStatus { @@ -189,15 +195,46 @@ enum TileStatus { } class Tile { - Tile(this.x, this.y, this.z, {this.image, this.future}); + Tile(this.x, this.y, this.z, + {this.image, this.future, required this.imageProvider}); int x; int y; /// zoom level int z; TileStatus status = TileStatus.clear; - Image image; - Future future; + Image? image; + Future? future; + ImageProvider imageProvider; + ImageStream? _imageStream; + ImageStreamListener? _listener; + + void _tileOnLoad( + ImageInfo imageInfo, bool synchronousCall, Completer completer) { + completer.complete(imageInfo.image); + } + + Future loadImage() async { + status = TileStatus.fetching; + final c = Completer(); + final oldImageStream = _imageStream; + _imageStream = imageProvider.resolve(const ImageConfiguration()); + if (_imageStream!.key != oldImageStream?.key) { + if (_listener != null) oldImageStream?.removeListener(_listener!); + + _listener = ImageStreamListener((info, s) => _tileOnLoad(info, s, c), + onError: (exception, stackTrace) { + if (!c.isCompleted) c.completeError(exception, stackTrace); + }); + _imageStream!.addListener(_listener!); + try { + image = await c.future; + status = TileStatus.ready; + } catch (e) { + status = TileStatus.error; + } + } + } } typedef TileCallback = void Function(Tile tile); @@ -205,41 +242,44 @@ typedef void MapCreatedCallback(FlutterEarthController controller); typedef void CameraPositionCallback(LatLon latLon, double zoom); class FlutterEarth extends StatefulWidget { - FlutterEarth({ - Key key, - this.url, - this.radius, - this.maxVertexCount = 5000, - this.showPole = true, - this.onMapCreated, - this.onCameraMove, - this.onTileStart, - this.onTileEnd, - }) : super(key: key); + FlutterEarth( + {Key? key, + required this.url, + this.radius, + this.maxVertexCount = 5000, + this.showPole = true, + this.onMapCreated, + this.onCameraMove, + this.onTileStart, + this.onTileEnd, + this.imageProvider}) + : super(key: key); final String url; - final double radius; + final double? radius; final int maxVertexCount; - final bool showPole; - final TileCallback onTileStart; - final TileCallback onTileEnd; - final MapCreatedCallback onMapCreated; - final CameraPositionCallback onCameraMove; + final bool? showPole; + final TileCallback? onTileStart; + final TileCallback? onTileEnd; + final MapCreatedCallback? onMapCreated; + final CameraPositionCallback? onCameraMove; + final ImageProvider Function(String url)? imageProvider; @override _FlutterEarthState createState() => _FlutterEarthState(); } -class _FlutterEarthState extends State with TickerProviderStateMixin { - FlutterEarthController _controller; - double width; - double height; - double zoom; - double _lastZoom; - Offset _lastFocalPoint; - Quaternion _lastQuaternion; - Vector3 _lastRotationAxis; - double _lastGestureScale; - double _lastGestureRatation; +class _FlutterEarthState extends State + with TickerProviderStateMixin { + late final FlutterEarthController _controller; + double width = 0; + double height = 0; + double zoom = 0; + double? _lastZoom = 0; + Offset _lastFocalPoint = Offset(0, 0); + Quaternion? _lastQuaternion; + Vector3 _lastRotationAxis = Vector3(0, 0, 0); + double _lastGestureScale = 1; + double _lastGestureRatation = 0; int _lastGestureTime = 0; final double _radius = 256 / (2 * math.pi); @@ -249,19 +289,19 @@ class _FlutterEarthState extends State with TickerProviderStateMix EulerAngles get eulerAngles => quaternionToEulerAngles(quaternion); Quaternion quaternion = Quaternion.identity(); - AnimationController animController; - Animation panAnimation; - Animation riseAnimation; - Animation zoomAnimation; + late AnimationController animController; + Animation? panAnimation; + Animation? riseAnimation; + Animation? zoomAnimation; double _panCurveEnd = 0; final double tileWidth = 256; final double tileHeight = 256; final int minZoom = 2; final int maxZoom = 21; - List> tiles; - Image northPoleImage; - Image southPoleImage; + List> tiles = []; + Image? northPoleImage; + Image? southPoleImage; Vector3 canvasPointToVector3(Offset point) { final x = point.dx - width / 2; @@ -294,36 +334,34 @@ class _FlutterEarthState extends State with TickerProviderStateMix } Future loadTileImage(Tile tile) async { + if (tile.status == TileStatus.error) { + await Future.delayed(const Duration(milliseconds: 200)); + } tile.status = TileStatus.pending; - if (widget.onTileStart != null) widget.onTileStart(tile); + if (widget.onTileStart != null) widget.onTileStart!(tile); if (tile.status == TileStatus.ready) return tile; - - tile.status = TileStatus.fetching; - final c = Completer(); - final url = widget.url.replaceAll('{z}', '${tile.z}').replaceAll('{x}', '${tile.x}').replaceAll('{y}', '${tile.y}'); - final networkImage = NetworkImage(url); - final imageStream = networkImage.resolve(ImageConfiguration()); - imageStream.addListener( - ImageStreamListener((ImageInfo imageInfo, bool synchronousCall) { - c.complete(imageInfo.image); - }), - ); - tile.image = await c.future; - tile.status = TileStatus.ready; - if (widget.onTileEnd != null) widget.onTileEnd(tile); - if(mounted) setState(() {}); + await tile.loadImage(); + if (widget.onTileEnd != null) widget.onTileEnd!(tile); + if (mounted) setState(() {}); return tile; } - Tile getTile(int x, int y, int z) { + Tile? getTile(int x, int y, int z) { final key = (x << 32) + y; var tile = tiles[z][key]; if (tile == null) { - tile = Tile(x, y, z); + final url = widget.url + .replaceAll('{z}', '$z') + .replaceAll('{x}', '$x') + .replaceAll('{y}', '$y'); + tile = Tile(x, y, z, + imageProvider: widget.imageProvider != null + ? widget.imageProvider!(url) + : NetworkImage(url)); tiles[z][key] = tile; } - if (tile.status == TileStatus.clear) { + if (tile.status == TileStatus.clear || tile.status == TileStatus.error) { loadTileImage(tile); } @@ -340,10 +378,10 @@ class _FlutterEarthState extends State with TickerProviderStateMix } List clipTiles(Rect clipRect, double radius) { - final list = List(); - final scale = math.pow(2.0, zoomLevel); + final list = []; + final scale = math.pow(2.0, zoomLevel).toDouble(); final observed = HashMap(); - final lastKeys = List(clipRect.width ~/ 10 + 1); + final lastKeys = List.filled(clipRect.width ~/ 10 + 1, 0); for (var y = clipRect.top; y < clipRect.bottom; y += 10.0) { var i = 0; for (var x = clipRect.left; x < clipRect.right; x += 10.0) { @@ -352,9 +390,12 @@ class _FlutterEarthState extends State with TickerProviderStateMix final point = latLonToPoint(latLon.latitude, latLon.longitude) * scale; if (point.dx >= scale || point.dy >= scale) continue; final key = (point.dx.toInt() << 32) + point.dy.toInt(); - if ((i == 0 || lastKeys[i - 1] != key) && (lastKeys[i] != key) && !observed.containsKey(key)) { + if ((i == 0 || lastKeys[i - 1] != key) && + (lastKeys[i] != key) && + !observed.containsKey(key)) { observed[key] = 0; - list.add(Offset(point.dx.truncateToDouble(), point.dy.truncateToDouble())); + list.add( + Offset(point.dx.truncateToDouble(), point.dy.truncateToDouble())); } lastKeys[i] = key; i++; @@ -365,24 +406,25 @@ class _FlutterEarthState extends State with TickerProviderStateMix void initMeshTexture(Mesh mesh) { final tile = getTile(mesh.x ~/ tileWidth, mesh.y ~/ tileHeight, zoomLevel); - if (tile.status == TileStatus.ready) { + if (tile?.status == TileStatus.ready) { //Is zoomed tile? - if (tile.z != zoomLevel) { + if (tile?.z != zoomLevel && tile != null) { final Float32List texcoords = mesh.texcoords; final int texcoordCount = texcoords.length; - final double scale = math.pow(2, tile.z - zoomLevel); + final double scale = math.pow(2, tile.z - zoomLevel).toDouble(); for (int i = 0; i < texcoordCount; i += 2) { texcoords[i] = (mesh.x + texcoords[i]) * scale - tile.x * tileWidth; - texcoords[i + 1] = (mesh.y + texcoords[i + 1]) * scale - tile.y * tileHeight; + texcoords[i + 1] = + (mesh.y + texcoords[i + 1]) * scale - tile.y * tileHeight; } } - mesh.texture = tile.image; + mesh.texture = tile?.image; } } Mesh initMeshFaces(Mesh mesh, int subdivisionsX, int subdivisionsY) { final int faceCount = subdivisionsX * subdivisionsY * 2; - final List faces = List(faceCount); + final List _faces = []..length = (faceCount); final Float32List positionsZ = mesh.positionsZ; int indexOffset = mesh.indexCount; double z = 0.0; @@ -393,10 +435,10 @@ class _FlutterEarthState extends State with TickerProviderStateMix int k3 = k1 + 1; int k4 = k2 + 1; double sumOfZ = positionsZ[k1] + positionsZ[k2] + positionsZ[k3]; - faces[indexOffset] = Polygon(k1, k2, k3, sumOfZ); + _faces[indexOffset] = Polygon(k1, k2, k3, sumOfZ); z += sumOfZ; sumOfZ = positionsZ[k3] + positionsZ[k2] + positionsZ[k4]; - faces[indexOffset + 1] = Polygon(k3, k2, k4, sumOfZ); + _faces[indexOffset + 1] = Polygon(k3, k2, k4, sumOfZ); z += sumOfZ; indexOffset += 2; k1++; @@ -405,6 +447,8 @@ class _FlutterEarthState extends State with TickerProviderStateMix } mesh.indexCount += faceCount; + var faces = _faces.whereType().toList(); + faces.sort((Polygon a, Polygon b) { // return b.sumOfZ.compareTo(a.sumOfZ); final double az = a.sumOfZ; @@ -431,7 +475,8 @@ class _FlutterEarthState extends State with TickerProviderStateMix return mesh; } - Mesh buildPoleMesh(double startLatitude, double endLatitude, int subdivisions, Image image) { + Mesh buildPoleMesh(double startLatitude, double endLatitude, int subdivisions, + Image? image) { //Rotate the tile from initial LatLon(-90, -90) to LatLon(0, 0) first. final q = Quaternion(-0.5, -0.5, 0.5, 0.5) * quaternion; //Use matrix rotation is more efficient. @@ -478,7 +523,15 @@ class _FlutterEarthState extends State with TickerProviderStateMix return initMeshFaces(mesh, subdivisionsX, subdivisions); } - Mesh buildTileMesh(double offsetX, double offsetY, double tileWidth, double tileHeight, int subdivisions, double mapWidth, double mapHeight, double radius) { + Mesh buildTileMesh( + double offsetX, + double offsetY, + double tileWidth, + double tileHeight, + int subdivisions, + double mapWidth, + double mapHeight, + double radius) { //Rotate the tile from initial LatLon(-90, -90) to LatLon(0, 0) first. final q = Quaternion(-0.5, -0.5, 0.5, 0.5) * quaternion; //Use matrix rotation is more efficient. @@ -522,12 +575,13 @@ class _FlutterEarthState extends State with TickerProviderStateMix void drawTiles(Canvas canvas, Size size) { final tiles = clipTiles(Rect.fromLTWH(0, 0, width, height), radius); - final meshList = List(); + final meshList = []; final maxWidth = tileWidth * (1 << zoomLevel); final maxHeight = tileHeight * (1 << zoomLevel); final tileCount = math.pow(math.pow(2, zoomLevel), 2); - final int subdivisions = math.max(2, math.sqrt(widget.maxVertexCount / tileCount).toInt()); + final int subdivisions = + math.max(2, math.sqrt(widget.maxVertexCount / tileCount).toInt()); for (var t in tiles) { final mesh = buildTileMesh( t.dx * tileWidth, @@ -542,9 +596,10 @@ class _FlutterEarthState extends State with TickerProviderStateMix initMeshTexture(mesh); meshList.add(mesh); } - if (widget.showPole) { + if (widget.showPole ?? false) { meshList..add(buildPoleMesh(math.pi / 2, radians(84), 5, northPoleImage)); - meshList.add(buildPoleMesh(-radians(84), -math.pi / 2, 5, southPoleImage)); + meshList + .add(buildPoleMesh(-radians(84), -math.pi / 2, 5, southPoleImage)); } meshList.sort((Mesh a, Mesh b) { @@ -562,7 +617,8 @@ class _FlutterEarthState extends State with TickerProviderStateMix final paint = Paint(); if (mesh.texture != null) { Float64List matrix4 = new Matrix4.identity().storage; - final shader = ImageShader(mesh.texture, TileMode.mirror, TileMode.mirror, matrix4); + final shader = ImageShader( + mesh.texture!, TileMode.mirror, TileMode.mirror, matrix4); paint.shader = shader; } canvas.drawVertices(vertices, BlendMode.src, paint); @@ -587,7 +643,7 @@ class _FlutterEarthState extends State with TickerProviderStateMix // fixed scaling error caused by ScaleUpdate delay _lastZoom = zoom - math.log(details.scale) / math.ln2; } else { - zoom = _lastZoom + math.log(details.scale) / math.ln2; + zoom = _lastZoom! + math.log(details.scale) / math.ln2; } final Vector3 oldCoord = canvasPointToVector3(_lastFocalPoint); @@ -599,27 +655,33 @@ class _FlutterEarthState extends State with TickerProviderStateMix if (axis.x != 0 && axis.y != 0 && axis.z != 0) _lastRotationAxis = axis; q *= Quaternion.axisAngle(Vector3(0, 0, 1.0), -details.rotation); - quaternion = _lastQuaternion * q; //quaternion A * B is not equal to B * A + if (_lastQuaternion != null) + quaternion = + _lastQuaternion! * q; //quaternion A * B is not equal to B * A if (widget.onCameraMove != null) { - widget.onCameraMove(position, zoom); + widget.onCameraMove!(position, zoom); } - if(mounted) setState(() {}); + if (mounted) setState(() {}); } void _handleScaleEnd(ScaleEndDetails details) { _lastQuaternion = quaternion; const double duration = 1000; const double maxDistance = 4000; - final double distance = math.min(maxDistance, details.velocity.pixelsPerSecond.distance) / maxDistance; + final double distance = + math.min(maxDistance, details.velocity.pixelsPerSecond.distance) / + maxDistance; if (distance == 0) return; if (DateTime.now().millisecondsSinceEpoch - _lastGestureTime < 300) { - if (_lastGestureScale != 1.0 && (_lastGestureScale - 1.0).abs() > _lastGestureRatation.abs()) { + if (_lastGestureScale != 1.0 && + (_lastGestureScale - 1.0).abs() > _lastGestureRatation.abs()) { double radians = 3.0 * distance; if (_lastGestureScale < 1.0) radians = -radians; animController.duration = Duration(milliseconds: duration.toInt()); - zoomAnimation = Tween(begin: zoom, end: zoom + radians).animate(CurveTween(curve: Curves.decelerate).animate(animController)); + zoomAnimation = Tween(begin: zoom, end: zoom + radians).animate( + CurveTween(curve: Curves.decelerate).animate(animController)); panAnimation = null; riseAnimation = null; animController.reset(); @@ -630,7 +692,8 @@ class _FlutterEarthState extends State with TickerProviderStateMix if (_lastGestureRatation > 0) radians = -radians; _lastRotationAxis = Vector3(0, 0, 1.0); animController.duration = Duration(milliseconds: duration.toInt()); - panAnimation = Tween(begin: 0, end: radians).animate(CurveTween(curve: Curves.decelerate).animate(animController)); + panAnimation = Tween(begin: 0, end: radians).animate( + CurveTween(curve: Curves.decelerate).animate(animController)); riseAnimation = null; zoomAnimation = null; animController.reset(); @@ -642,13 +705,15 @@ class _FlutterEarthState extends State with TickerProviderStateMix double radians = 1000 * distance / radius; final Offset center = Offset(width / 2, height / 2); final Vector3 oldCoord = canvasPointToVector3(center); - final Vector3 newCoord = canvasPointToVector3(center + details.velocity.pixelsPerSecond / distance); + final Vector3 newCoord = canvasPointToVector3( + center + details.velocity.pixelsPerSecond / distance); Quaternion q = quaternionFromTwoVectors(newCoord, oldCoord); final Vector3 axis = quaternionAxis(q); if (axis.x != 0 && axis.y != 0 && axis.z != 0) _lastRotationAxis = axis; animController.duration = Duration(milliseconds: duration.toInt()); - panAnimation = Tween(begin: 0, end: radians).animate(CurveTween(curve: Curves.decelerate).animate(animController)); + panAnimation = Tween(begin: 0, end: radians) + .animate(CurveTween(curve: Curves.decelerate).animate(animController)); riseAnimation = null; zoomAnimation = null; animController.reset(); @@ -658,23 +723,35 @@ class _FlutterEarthState extends State with TickerProviderStateMix void _handleDoubleTap() { _lastZoom = zoom; animController.duration = Duration(milliseconds: 600); - zoomAnimation = Tween(begin: zoom, end: zoom + 1.0).animate(CurveTween(curve: Curves.decelerate).animate(animController)); + zoomAnimation = Tween(begin: zoom, end: zoom + 1.0) + .animate(CurveTween(curve: Curves.decelerate).animate(animController)); panAnimation = null; riseAnimation = null; animController.reset(); animController.forward(); } - void animateCamera({LatLon newLatLon, double riseZoom, double fallZoom, double panSpeed = 1000.0, double riseSpeed = 1.0, double fallSpeed = 1.0}) { + void animateCamera( + {LatLon? newLatLon, + double? riseZoom, + double? fallZoom, + double panSpeed = 1000.0, + double riseSpeed = 1.0, + double fallSpeed = 1.0}) { double panTime = 0; double riseTime = 0; double fallTime = 0; - if (riseZoom != null) riseTime = Duration.millisecondsPerSecond * (riseZoom - zoom).abs() / riseSpeed; + if (riseZoom != null) + riseTime = + Duration.millisecondsPerSecond * (riseZoom - zoom).abs() / riseSpeed; riseZoom ??= zoom; - if (fallZoom != null) fallTime = Duration.millisecondsPerSecond * (fallZoom - riseZoom).abs() / fallSpeed; + if (fallZoom != null) + fallTime = Duration.millisecondsPerSecond * + (fallZoom - riseZoom).abs() / + fallSpeed; fallZoom ??= riseZoom; - double panRadians; + double panRadians = 0; if (newLatLon != null) { final oldEuler = quaternionToEulerAngles(quaternion); final newEuler = latLonToEulerAngles(newLatLon); @@ -689,23 +766,28 @@ class _FlutterEarthState extends State with TickerProviderStateMix _lastRotationAxis = quaternionAxis(q1); //q1.axis; _lastQuaternion = q0; panRadians = q1.radians; - panTime = Duration.millisecondsPerSecond * (panRadians * _radius * math.pow(2, riseZoom)).abs() / panSpeed; + panTime = Duration.millisecondsPerSecond * + (panRadians * _radius * math.pow(2, riseZoom)).abs() / + panSpeed; } int duration = (riseTime + panTime + fallTime).ceil(); animController.duration = Duration(milliseconds: duration); final double riseCurveEnd = riseTime / duration; riseAnimation = Tween(begin: zoom, end: riseZoom).animate( - CurveTween(curve: Interval(0, riseCurveEnd, curve: Curves.ease)).animate(animController), + CurveTween(curve: Interval(0, riseCurveEnd, curve: Curves.ease)) + .animate(animController), ); final double panCurveEnd = riseCurveEnd + panTime / duration; _panCurveEnd = panCurveEnd; panAnimation = Tween(begin: 0, end: panRadians).animate( - CurveTween(curve: Interval(riseCurveEnd, panCurveEnd, curve: Curves.ease)).animate(animController), + CurveTween(curve: Interval(riseCurveEnd, panCurveEnd, curve: Curves.ease)) + .animate(animController), ); - final double fallCurveEnd = 1.0; + const double fallCurveEnd = 1.0; zoomAnimation = Tween(begin: riseZoom, end: fallZoom).animate( - CurveTween(curve: Interval(panCurveEnd, fallCurveEnd, curve: Curves.ease)).animate(animController), + CurveTween(curve: Interval(panCurveEnd, fallCurveEnd, curve: Curves.ease)) + .animate(animController), ); animController.reset(); animController.forward(); @@ -714,40 +796,55 @@ class _FlutterEarthState extends State with TickerProviderStateMix @override void initState() { super.initState(); - tiles = List(maxZoom + 1); - for (var i = 0; i <= maxZoom; i++) tiles[i] = HashMap(); - - zoom = math.log(widget.radius / _radius) / math.ln2; + PaintingBinding.instance!.imageCache!.maximumSizeBytes = + 1024 * 1024 * 1024 * 1024; + var _tiles = ?>[]..length = (maxZoom + 1); + for (var i = 0; i <= maxZoom; i++) { + _tiles[i] = HashMap(); + } + tiles = _tiles.whereType>().toList(); + if (widget.radius != null) { + zoom = math.log(widget.radius! / _radius) / math.ln2; + } _lastRotationAxis = Vector3(0, 0, 1.0); animController = AnimationController(vsync: this) ..addListener(() { - if(mounted) setState(() { - if (!animController.isCompleted) { - if (panAnimation != null) { - final q = Quaternion.axisAngle(_lastRotationAxis, panAnimation.value); - quaternion = _lastQuaternion * q; - } - if (riseAnimation != null) { - if (animController.value < _panCurveEnd) zoom = riseAnimation.value; - } - if (zoomAnimation != null) { - if (animController.value >= _panCurveEnd) zoom = zoomAnimation.value; + if (mounted) + setState(() { + if (!animController.isCompleted) { + if (panAnimation != null && _lastQuaternion != null) { + final q = Quaternion.axisAngle( + _lastRotationAxis, panAnimation!.value); + quaternion = _lastQuaternion! * q; + } + if (riseAnimation != null) { + if (animController.value < _panCurveEnd) + zoom = riseAnimation!.value; + } + if (zoomAnimation != null) { + if (animController.value >= _panCurveEnd) + zoom = zoomAnimation!.value; + } + if (widget.onCameraMove != null) + widget.onCameraMove!(position, zoom); + } else { + _panCurveEnd = 0; } - if (widget.onCameraMove != null) widget.onCameraMove(position, zoom); - } else { - _panCurveEnd = 0; - } - }); + }); }); _controller = FlutterEarthController(this); if (widget.onMapCreated != null) { - widget.onMapCreated(_controller); + widget.onMapCreated!(_controller); } - loadImageFromAsset('packages/flutter_earth/assets/google_map_north_pole.png').then((Image value) => northPoleImage = value); - loadImageFromAsset('packages/flutter_earth/assets/google_map_south_pole.png').then((Image value) => southPoleImage = value); + loadImageFromAsset( + 'packages/flutter_earth/assets/google_map_north_pole.png') + .then((Image value) => northPoleImage = value); + loadImageFromAsset( + 'packages/flutter_earth/assets/google_map_south_pole.png') + .then((Image value) => southPoleImage = value); } @override @@ -808,7 +905,19 @@ class FlutterEarthController { void clearCache() => _state.clearCache(); - void animateCamera({LatLon newLatLon, double riseZoom, double fallZoom, double panSpeed = 10.0, double riseSpeed = 1.0, double fallSpeed = 1.0}) { - _state.animateCamera(newLatLon: newLatLon, riseZoom: riseZoom, fallZoom: fallZoom, panSpeed: panSpeed, riseSpeed: riseSpeed, fallSpeed: fallSpeed); + void animateCamera( + {LatLon? newLatLon, + double? riseZoom, + double? fallZoom, + double panSpeed = 10.0, + double riseSpeed = 1.0, + double fallSpeed = 1.0}) { + _state.animateCamera( + newLatLon: newLatLon, + riseZoom: riseZoom, + fallZoom: fallZoom, + panSpeed: panSpeed, + riseSpeed: riseSpeed, + fallSpeed: fallSpeed); } } diff --git a/pubspec.lock b/pubspec.lock index 73dd6db..10efb83 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,73 +7,262 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.8.2" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.1.0" + cached_network_image: + dependency: "direct dev" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.3.1" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + lints: + dependency: transitive + description: + name: lints + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.7" + version: "1.7.0" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.12" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" - quiver: + version: "1.11.1" + platform: dependency: transitive description: - name: quiver + name: platform url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.27.3" sky_engine: dependency: transitive description: flutter @@ -85,55 +274,98 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.8.1" + sqflite: + dependency: transitive + description: + name: sqflite + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" + synchronized: + dependency: transitive + description: + name: synchronized + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0+2" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.4.8" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" + uuid: + dependency: transitive + description: + name: uuid + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.6" vector_math: dependency: "direct main" description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.1" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+1" sdks: - dart: ">=2.2.2 <3.0.0" + dart: ">=2.16.0 <3.0.0" + flutter: ">=2.8.0" diff --git a/pubspec.yaml b/pubspec.yaml index d9a2c6a..c3c39e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,20 +1,20 @@ name: flutter_earth description: A Flutter earth widget. version: 0.0.4 -author: Zebiao Hu homepage: https://github.com/zesage/flutter_earth environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.16.0 <3.0.0" dependencies: flutter: sdk: flutter - vector_math: any + vector_math: ^2.1.1 dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^1.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -22,6 +22,7 @@ dev_dependencies: # The following section is specific to Flutter. flutter: + generate: true # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg