diff --git a/src/frontend/lib/utils/network_utils.dart b/src/frontend/lib/utils/network_utils.dart index fc76d14..b32742f 100644 --- a/src/frontend/lib/utils/network_utils.dart +++ b/src/frontend/lib/utils/network_utils.dart @@ -7,10 +7,13 @@ import 'package:flutter/material.dart'; Future loadNetworkImage(String path) async { final completer = Completer(); var img = NetworkImage(path); - img - .resolve(const ImageConfiguration()) - .addListener(ImageStreamListener((info, _) => completer.complete(info))); + var resolved = img.resolve(const ImageConfiguration()); + var listener = ImageStreamListener((info, _) => completer.complete(info)); + resolved.addListener(listener); + final imageInfo = await completer.future; + resolved.removeListener(listener); final byteData = await imageInfo.image.toByteData(format: ui.ImageByteFormat.png); + return byteData?.buffer.asUint8List(); } diff --git a/src/frontend/lib/utils/polygon_utils.dart b/src/frontend/lib/utils/polygon_utils.dart index 189d5d8..3e7954a 100644 --- a/src/frontend/lib/utils/polygon_utils.dart +++ b/src/frontend/lib/utils/polygon_utils.dart @@ -114,3 +114,33 @@ CameraPosition getGoodCameraPositionForPolygon(List geoPoints) { zoom: zoom, ); } + +// Returns an LatLngBounds object that encloses the polygon +// This is used to set the image overlay boundaries. +LatLngBounds getLatLngBoundsForPolygon(List geoPoints) { + if (geoPoints.isEmpty) { + return LatLngBounds( + southwest: const LatLng(0, 0), + northeast: const LatLng(0, 0), + ); + } + + // TODO: It should account for the case where the polygon crosses the 180th meridian + // and the longitude values are negative. + double minLat = geoPoints[0].latitude; + double maxLat = geoPoints[0].latitude; + double minLong = geoPoints[0].longitude; + double maxLong = geoPoints[0].longitude; + + for (var geoPoint in geoPoints) { + minLat = min(minLat, geoPoint.latitude); + maxLat = max(maxLat, geoPoint.latitude); + minLong = min(minLong, geoPoint.longitude); + maxLong = max(maxLong, geoPoint.longitude); + } + + return LatLngBounds( + southwest: LatLng(minLat, minLong), + northeast: LatLng(maxLat, maxLong), + ); +} diff --git a/src/frontend/lib/views/insights/widgets/visualize_insights_map.dart b/src/frontend/lib/views/insights/widgets/visualize_insights_map.dart index 335c7ed..00a2d4e 100644 --- a/src/frontend/lib/views/insights/widgets/visualize_insights_map.dart +++ b/src/frontend/lib/views/insights/widgets/visualize_insights_map.dart @@ -1,9 +1,11 @@ import 'dart:async'; import 'dart:math'; +import 'package:f_logs/model/flog/flog.dart'; import 'package:flutter/material.dart'; import 'package:frontend/providers/field_scan_provider.dart'; import 'package:frontend/providers/insight_types_provider.dart'; -import 'package:frontend/views/insights/widgets/color_legend.dart'; +import 'package:frontend/utils/network_utils.dart'; +import 'package:frontend/utils/polygon_utils.dart'; import 'package:frontend/views/insights/widgets/insight_details_sheet.dart'; import 'package:frontend/views/insights/widgets/menu_drawer_button.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; @@ -14,6 +16,7 @@ import 'package:frontend/utils/network_utils.dart' as nutils; import '../../../models/insight_model.dart'; import '../../../providers/insight_choices_provider.dart'; import '../../loading.dart'; +import 'color_legend.dart'; import 'insights_selection.dart'; import 'maps_dropdown.dart'; @@ -29,9 +32,9 @@ class VisualizeInsightsMap extends StatefulWidget { class _VisualizeInsightsMapState extends State { final Completer _controller = Completer(); - - // Workaround lagging screen due to Google Maps initialization - final Future _mapFuture = Future.delayed(const Duration(milliseconds: 250), () => true); + BitmapDescriptor? _overlayImage; + double _bearing = 0; + double _transparency = 0; // For keeping track of polygons to draw Set _polygons = Set(); @@ -39,6 +42,8 @@ class _VisualizeInsightsMapState extends State { // For keeping track of insights to show Set _insightMarkers = Set(); + Set _groundOverlays = Set(); + /// Takes a list of field models and created polygons to draw on the map void _drawInsightMap(InsightMapType mapType) { // Convert from List to List @@ -96,6 +101,7 @@ class _VisualizeInsightsMapState extends State { InsightMapType mapType = Provider.of(context, listen: true).currInsightMapType; _drawInsightMap(mapType); + FLog.info(text: 'Updating map'); } Future updateMarkers() async { @@ -108,9 +114,97 @@ class _VisualizeInsightsMapState extends State { await _drawMarkersForInsights(insights, excluded); } + Future _removeGroundOverlay() async { + var bytes = await loadNetworkImage( + 'https://holistichormonalhealth.com/wp-content/uploads/2015/08/transparent1.png'); + + setState(() { + FLog.info(text: 'Removing ground overlay'); + var bitmap = BitmapDescriptor.fromBytes( + bytes!, + ); + _overlayImage = bitmap; + _groundOverlays = {}; + }); + } + + @override + void initState() { + super.initState(); + _addGroundOverlay(); + Provider.of(context, listen: false).addListener(_addGroundOverlay); + Provider.of(context, listen: false).addListener(_addGroundOverlay); + updateMap(); + } + + @override + void dispose() { + Provider.of(context, listen: false).removeListener(_addGroundOverlay); + Provider.of(context, listen: false).removeListener(_addGroundOverlay); + super.dispose(); + } + + Future _addGroundOverlay() async { + try { + var indexType = + Provider.of(context, listen: false).currInsightMapType; + var scanData = Provider.of(context, listen: false).selectedFieldScan; + + FLog.info(text: 'Adding ground overlay for ${indexType.name}'); + + if (scanData == null) { + FLog.warning(text: 'No scan data found'); + _removeGroundOverlay(); + return; + } + + if (scanData.indices[indexType.name.toLowerCase()] == null) { + FLog.warning(text: 'No ${indexType.name} data found'); + _removeGroundOverlay(); + return; + } + + try { + FLog.info( + text: + 'Adding ground overlay for ${indexType.name} with url: ${scanData.indices[indexType.name.toLowerCase()]['url']}'); + var bytes = await loadNetworkImage(scanData.indices[indexType.name.toLowerCase()]['url']); + FLog.info(text: 'Made bytes for ${indexType.name}'); + var bitmap = BitmapDescriptor.fromBytes( + bytes!, + ); + FLog.info(text: 'Made bitmap for ${indexType.name}'); + setState(() { + FLog.info(text: 'Added ground overlay for ${indexType.name}'); + _overlayImage = bitmap; + + _groundOverlays = { + GroundOverlay( + groundOverlayId: GroundOverlayId(Random().nextInt(100000).toString()), //random id + image: _overlayImage!, + positionFromBounds: getLatLngBoundsForPolygon(widget.currField.boundaries), + bearing: 0, + transparency: 0, + zIndex: 1, + ), + }; + }); + } catch (e) { + FLog.error(text: 'Error adding ground overlay for ${indexType.name} with error'); + _removeGroundOverlay(); + } finally { + FLog.info(text: 'Added ground overlay for ${indexType.name}'); + } + } catch (e) { + FLog.error(text: 'Error adding ground overlay with error $e'); + _removeGroundOverlay(); + } + } + @override Widget build(BuildContext context) { updateMap(); + return Scaffold( body: FutureBuilder( future: updateMarkers(), @@ -124,18 +218,19 @@ class _VisualizeInsightsMapState extends State { child: Stack( children: [ GoogleMap( - myLocationEnabled: true, - myLocationButtonEnabled: false, - rotateGesturesEnabled: true, - scrollGesturesEnabled: true, - mapToolbarEnabled: false, - compassEnabled: false, - zoomControlsEnabled: false, + mapToolbarEnabled: false, + zoomControlsEnabled: false, + zoomGesturesEnabled: true, + scrollGesturesEnabled: true, + rotateGesturesEnabled: true, + tiltGesturesEnabled: true, + myLocationEnabled: false, + groundOverlays: _groundOverlays, initialCameraPosition: utils.getGoodCameraPositionForPolygon(widget.currField.boundaries), mapType: MapType.satellite, markers: _insightMarkers, - polygons: _polygons, + // polygons: _polygons, onMapCreated: (GoogleMapController controller) { _controller.complete(controller); }, @@ -157,10 +252,11 @@ class _VisualizeInsightsMapState extends State { alignment: Alignment.topRight, child: const MapsDropdown()), Container( - padding: const EdgeInsets.only(bottom: 70, right: 10, left: 10), - alignment: Alignment.bottomCenter, - child: ColorLegend(mapType: Provider.of(context, listen: true).currInsightMapType) - ) + padding: const EdgeInsets.only(bottom: 70, right: 10, left: 10), + alignment: Alignment.bottomCenter, + child: ColorLegend( + mapType: Provider.of(context, listen: true) + .currInsightMapType)) ], ), ) diff --git a/src/frontend/pubspec.lock b/src/frontend/pubspec.lock index 920139f..ff6f2a6 100644 --- a/src/frontend/pubspec.lock +++ b/src/frontend/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "64fcb0dbca4386356386c085142fa6e79c00a3326ceaa778a2d25f5d9ba61441" + sha256: "330d7fcbb72624f5b6d374af8b059b0ef4ba96ba5b8987f874964a1287eb617d" url: "https://pub.dev" source: hosted - version: "1.0.16" + version: "1.0.18" analyzer: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" build_resolvers: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.4.4" cached_network_image: dependency: "direct main" description: @@ -181,50 +181,50 @@ packages: dependency: "direct main" description: name: cloud_firestore - sha256: "65f148d9f5b4f389320abb45847120cf5e46094c1a8cbc64934ffc1e29688596" + sha256: "0ad93f01816ab7e59560bffeece2d19cec872fedfea04a7ad5be418cad25d574" url: "https://pub.dev" source: hosted - version: "4.4.3" + version: "4.4.5" cloud_firestore_platform_interface: dependency: transitive description: name: cloud_firestore_platform_interface - sha256: "43ccae09f7e0c82752e69c251c6dc5efcdff4ddcfc09564175a28657bbd74188" + sha256: "6255cf3ad845d44c6c69a7db51139c8d90691e435dd175578f6365f10c023fcc" url: "https://pub.dev" source: hosted - version: "5.11.3" + version: "5.11.5" cloud_firestore_web: dependency: transitive description: name: cloud_firestore_web - sha256: e054c007217e28e07179bbae0564c2a4f6338a60bddb0c139e4834e953f4b95c + sha256: "3bc08abdeb21a59e80491aee827895aaa3c6da602bad9ab68d83e1ab13c951b1" url: "https://pub.dev" source: hosted - version: "3.3.3" + version: "3.3.5" cloud_functions: dependency: "direct main" description: name: cloud_functions - sha256: "33e41559db531cb8ddbadc614fd2552ad235e57637996d929e2f8048af8abed4" + sha256: "6b7a47399f4d726f89e1f756e6a5ffbe7585a663794e3faba1bc86aa1894d6d6" url: "https://pub.dev" source: hosted - version: "4.0.12" + version: "4.0.13" cloud_functions_platform_interface: dependency: transitive description: name: cloud_functions_platform_interface - sha256: "01a8de02ac0813084ec7e3b0e6d8596467c7e46c03978e68ee8445c11d6fbf4c" + sha256: a6fa0352cacddb16927726b055763ac0d237a18bb964c069fb27b20346719d2b url: "https://pub.dev" source: hosted - version: "5.1.31" + version: "5.1.32" cloud_functions_web: dependency: transitive description: name: cloud_functions_web - sha256: d165a0aee0a13c51ab4d11f50d5b7cd62d5a25bdca86e834df9bb78a5ca05dc5 + sha256: b72dcee58ff119045bd80ca9b80d8210f2948689175391f061025bb0872f3365 url: "https://pub.dev" source: hosted - version: "4.3.20" + version: "4.3.21" code_builder: dependency: transitive description: @@ -333,34 +333,34 @@ packages: dependency: "direct main" description: name: firebase_auth - sha256: "9907d80446466e638dad31c195150b305dffd145dc57610fcd12c72289432143" + sha256: "94c229e296a5b9ee5c8cda918e0b320e3a0cc4f6a349cd410c427da347f2a244" url: "https://pub.dev" source: hosted - version: "4.2.9" + version: "4.3.0" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: c645fec50b0391aa878288f58fa4fe9762c271380c457aedf5c7c9b718604f68 + sha256: "1217d8aa313b49d58b489aa8879544563abc8793d9612ff20d8df193f202aedc" url: "https://pub.dev" source: hosted - version: "6.11.11" + version: "6.12.0" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: "2dcf2a36852b9091741b4a4047a02e1f2c43a62c6cacec7df573a793a6543e6d" + sha256: bf7f1a87995a58b0f07dc617806dabd7ff25c64be7fa47b41ab1bb9a485b0062 url: "https://pub.dev" source: hosted - version: "5.2.8" + version: "5.2.10" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "1c121a478af23755b0b93fd4aa70d3bd32a587dd51ef0a3979091ac0d2317d32" + sha256: "75f747cafd7cbd6c00b908e3a7aa59fc31593d46ba8165d9ee8a79e69464a394" url: "https://pub.dev" source: hosted - version: "2.7.1" + version: "2.8.0" firebase_core_platform_interface: dependency: transitive description: @@ -381,26 +381,26 @@ packages: dependency: "direct main" description: name: firebase_storage - sha256: fac4ebf20da0ff7c7f23596d741130a85e8bd5fac4cabf8f568f38ba55fba19d + sha256: "0a49413725587aa3ceaaa6233fdb2e32687e4972f13cd7277526219cd5acb398" url: "https://pub.dev" source: hosted - version: "11.0.14" + version: "11.0.16" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface - sha256: c58dedec6d12416306e08dde463f3c65443aa2d5ccc100856ae7da75bab63e29 + sha256: "951871b91ffdc49ead69daa03e58606f29b9b541056258ef805bbb7128f54afc" url: "https://pub.dev" source: hosted - version: "4.1.30" + version: "4.1.32" firebase_storage_web: dependency: transitive description: name: firebase_storage_web - sha256: "28e2c6317214f63af56b1b3f2907d168534a7629ec24269b036556f399063d5a" + sha256: d77573712c9fa5e05c7f1e23c77db0757b0b0718136a550d4d9823c92eeca8c8 url: "https://pub.dev" source: hosted - version: "3.3.23" + version: "3.3.25" fixnum: dependency: transitive description: @@ -458,10 +458,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b" + sha256: c224ac897bed083dabf11f238dd11a239809b446740be0c2044608c50029ffdf url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.9" flutter_spinkit: dependency: "direct main" description: @@ -484,10 +484,10 @@ packages: dependency: "direct main" description: name: flutter_zoom_drawer - sha256: "93c4f2cfe1edfe2f9e48b94a7f9538520d2f2593e202b8e89c394b7d27137a8e" + sha256: b439c87d63680465582ac301f2502dea6f95ddcba3a9787365dcadb52777c1e1 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4+1" frontend_server_client: dependency: transitive description: @@ -523,35 +523,21 @@ packages: google_maps_flutter: dependency: "direct main" description: - name: google_maps_flutter - sha256: "0c6b72b4b1e0f6204973e2b40868a75fe6380725d498f215cd7e35ed920d1c57" - url: "https://pub.dev" - source: hosted - version: "2.2.3" - google_maps_flutter_android: - dependency: transitive - description: - name: google_maps_flutter_android - sha256: "4152d1ddf6393cb7de73c406e1a4feba343f15f7b85494354ebee69730daee53" - url: "https://pub.dev" - source: hosted - version: "2.4.5" - google_maps_flutter_ios: - dependency: transitive - description: - name: google_maps_flutter_ios - sha256: "33bbca8d4148ed373251ea2ec2344fdc63009926b6d6be71a0854fd42409b1ba" - url: "https://pub.dev" - source: hosted - version: "2.1.13" + path: "packages/google_maps_flutter/google_maps_flutter" + ref: HEAD + resolved-ref: "9f444ad31c720b4f8b41a32c88a07f99d42f8eb5" + url: "https://github.com/RD211/plugins.git" + source: git + version: "2.1.0" google_maps_flutter_platform_interface: dependency: transitive description: - name: google_maps_flutter_platform_interface - sha256: a07811d2b82055815ede75e1fe4b7b76f71a0b4820b26f71bdaddd157d6a3e20 - url: "https://pub.dev" - source: hosted - version: "2.2.6" + path: "packages/google_maps_flutter/google_maps_flutter_platform_interface" + ref: HEAD + resolved-ref: "9f444ad31c720b4f8b41a32c88a07f99d42f8eb5" + url: "https://github.com/RD211/plugins.git" + source: git + version: "2.1.3" graphs: dependency: transitive description: @@ -724,26 +710,26 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e" + sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" url: "https://pub.dev" source: hosted - version: "2.0.23" + version: "2.0.24" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972 + sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a" + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" url: "https://pub.dev" source: hosted - version: "2.1.9" + version: "2.1.10" path_provider_platform_interface: dependency: transitive description: @@ -756,10 +742,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d" + sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pedantic: dependency: transitive description: @@ -828,10 +814,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pointycastle: dependency: transitive description: @@ -884,10 +870,10 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" rxdart: dependency: transitive description: @@ -945,18 +931,18 @@ packages: dependency: transitive description: name: sqflite - sha256: "851d5040552cf911f4cabda08d003eca76b27da3ed0002978272e27c8fbf8ecc" + sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" url: "https://pub.dev" source: hosted - version: "2.2.5" + version: "2.2.6" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: bfd6973aaeeb93475bc0d875ac9aefddf7965ef22ce09790eb963992ffc5183f + sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" url: "https://pub.dev" source: hosted - version: "2.4.2+2" + version: "2.4.3" stack_trace: dependency: transitive description: diff --git a/src/frontend/pubspec.yaml b/src/frontend/pubspec.yaml index e6f6e07..ca47bd0 100644 --- a/src/frontend/pubspec.yaml +++ b/src/frontend/pubspec.yaml @@ -35,7 +35,10 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - google_maps_flutter: ^2.2.3 + google_maps_flutter: + git: + url: https://github.com/RD211/plugins.git + path: packages/google_maps_flutter/google_maps_flutter/ http: ^0.13.5 # A better way to handle environment variables using `.env` file. flutter_dotenv: ^5.0.2