Distance calculation Flutter background Location - flutter

I am trying to develop a Location service-based Distance calculation app. the problem is I need this to be run when the app is in the background as well. so I used the following plugin to do so.
https://pub.dev/packages/background_location
when this runs on AVD nothing happened but worked fine. but when run on some devices I got the wrong calculations. I have noticed that this happens when the app is in the background this is. following is a partial recreation of mine. I just want to know if my code get any error before posting this as an issue
Future<void> startLocationUpdate() async {
var permission = await Permission.locationAlways.isGranted;
if (!permission) {
var t = await Permission.locationAlways.request();
}
oldPosition2 = Location(
longitude: currentPosition.longitude, latitude: currentPosition.latitude);
isWaited = false;
waitButtonText = "WAIT";
BackgroundLocation.stopLocationService();
BackgroundLocation.setAndroidConfiguration(2000);
await BackgroundLocation.setAndroidNotification(
title: "XXXXXXXXXXXXXXXXX",
message: "XXXXXXXXXXXXXXXXXX",
icon: "XXXXXXXXXXXXXXXXXX",
);
//print('Inside startLocationUpdate');
await BackgroundLocation.startLocationService(distanceFilter: 20);
print('Inside startLocationUpdate 1');
BackgroundLocation.getLocationUpdates((location) {
try {
currentPosition = location;
LatLng pos = LatLng(location.latitude, location.longitude);
var rotation = MapKitHelper.getMarkerRotation(oldPosition2.latitude,
oldPosition2.longitude, pos.latitude, pos.longitude);
Marker movingMaker = Marker(
markerId: MarkerId('moving'),
position: pos,
icon: movingMarkerIcon,
rotation: rotation,
infoWindow: InfoWindow(title: 'Current Location'),
);
setState(() {
CameraPosition cp = new CameraPosition(target: pos, zoom: 17);
rideMapController.animateCamera(CameraUpdate.newCameraPosition(cp));
_markers.removeWhere((marker) => marker.markerId.value == 'moving');
_markers.add(movingMaker);
this.accuracy = location.accuracy.toStringAsFixed(0);
totalSpeed = location.speed;
totalDistance = totalDistance +
CalDistance(oldPosition2 != null ? oldPosition2 : location,
location, DistanceType.Kilometers);
var totalDistanceAdj = totalDistance != 0 ? totalDistance : 1;
//totalFare = 50 + (kmPrice * totalDistanceAdj);
print("distance $totalDistance");
});
oldPosition2 = location;
updateTripDetails(location);
Map locationMap = {
'latitude': location.latitude.toString(),
'longitude': location.longitude.toString(),
};
rideRef = FirebaseDatabase.instance
.reference()
.child("rideRequest/${widget.tripDetails.rideID}");
rideRef.child('driver_location').set(locationMap);
} catch (e) {
FirebaseService.logtoGPSData('Error in updates ${e}');
}
});
}
and the CalDistance method as follows
double CalDistance(Location pos1, Location pos2, DistanceType type) {
print("pos1 : ${pos1.latitude} pos2: ${pos2.latitude}");
double R = (type == DistanceType.Miles) ? 3960 : 6371;
double dLat = this.toRadian(pos2.latitude - pos1.latitude);
double dLon = this.toRadian(pos2.longitude - pos1.longitude);
double a = sin(dLat / 2) * sin(dLat / 2) +
cos(this.toRadian(pos1.latitude)) *
cos(this.toRadian(pos2.latitude)) *
sin(dLon / 2) *
sin(dLon / 2);
double c = 2 * asin(min(1, sqrt(a)));
double d = R * c;
//d = (d*80)/100;
return d;
}
double toRadian(double val) {
return (pi / 180) * val;
}
Any help or hint would be much appreciated

Related

Moving Car Animation Using Mapbox in flutter

I've implementing MabBox SDK with one of my flutter app. It has car live tracking screen, which will update car marker position on map based on location received. Here we would like to show car moving animation like this .
I've gone through the MabBox documents, Couldn't find any related document for our use cases. Then I've gone through google's flutter_animarker which doesn't support Mabbox. Anyone please help me on this.
I've finished car moving animation. Here is the final code.
import 'package:flutter/animation.dart';
import 'dart:math' as math;
LatLng? oldLatLng;
bool isCarAnimating = false;
onLocationChange(String latLng) async {
currentLatLng = LatLng(
double.parse(latLng.split(',')[0]), double.parse(latLng.split(',')[1]));
if (cabMarkerSymbol != null && !isCarAnimating)
_animateCabIcon(oldLatLng!, currentLatLng!);
}
_animateCabIcon(LatLng start, LatLng end) async {
try {
AnimationController controller =
AnimationController(vsync: this, duration: Duration(seconds: 3));
Animation animation;
var tween = Tween<double>(begin: 0, end: 1);
animation = tween.animate(controller);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
oldLatLng = end;
isCarAnimating = false;
} else if (status == AnimationStatus.forward) {
isCarAnimating = true;
}
});
controller.forward();
var bearing = getBearing(start, end);
animation.addListener(() async {
var v = animation.value;
var lng = v * end.longitude + (1 - v) * start.longitude;
var lat = v * end.latitude + (1 - v) * start.latitude;
var latLng = LatLng(lat, lng);
var carSymbolOptions = SymbolOptions(
geometry: latLng,
iconRotate: bearing,
);
await _completer.future
.then((map) => map.updateSymbol(carMarkerSymbol!, carSymbolOptions));
});
} catch (e) {
print(e);
}
}
double getBearing(LatLng start, LatLng end) {
var lat1 = start.latitude * math.pi / 180;
var lng1 = start.longitude * math.pi / 180;
var lat2 = end.latitude * math.pi / 180;
var lng2 = end.longitude * math.pi / 180;
var dLon = (lng2 - lng1);
var y = math.sin(dLon) * math.cos(lat2);
var x = math.cos(lat1) * math.sin(lat2) -
math.sin(lat1) * math.cos(lat2) * math.cos(dLon);
var bearing = math.atan2(y, x);
bearing = (bearing * 180) / math.pi;
bearing = (bearing + 360) % 360;
return bearing;
}

snap markers to nearest polyline point Google Maps flutter

I am writing a simple bus tracking app for my university shuttles. I have plotted out the bus GPS as navigation arrows along with the route on the map. Now as the actual GPS coordinate is a little of from the real location of bus than the plotted route, it is slightly off the road. Is there any way or method that I can call to snap these markers to the polyline nearest point in google maps flutter plugin? I just wanted to know if there is anything already in place that I am missing. I would be willing to write the custom nearest neighbor logic if necessary. Thanks!
I have done using locationIndexOnPath, computeDistanceBetween, computeHeading.
Step 1. add this function to get nearest polyline segment index based on you current location. pass your polyline coordinates list and current location. below function return index of given polyline coordinates list.
int getEdgeIndex(List<mt.LatLng> _coordinates, mt.LatLng currentLocation) {
final int edgeIndex1 = mt.PolygonUtil.locationIndexOnPath(
currentLocation, _coordinates, true,
tolerance: 1);
final int edgeIndex2 = mt.PolygonUtil.locationIndexOnPath(
currentLocation, _coordinates, true,
tolerance: 2);
final int edgeIndex6 = mt.PolygonUtil.locationIndexOnPath(
currentLocation, _coordinates, true,
tolerance: 6);
final int edgeIndex10 = mt.PolygonUtil.locationIndexOnPath(
currentLocation, _coordinates, true,
tolerance: 10);
final int edgeIndex15 = mt.PolygonUtil.locationIndexOnPath(
currentLocation, _coordinates, true,
tolerance: 15);
int finalIndex = -1;
if (edgeIndex1 >= 0) {
finalIndex = edgeIndex1;
} else if (edgeIndex2 >= 0) {
finalIndex = edgeIndex2;
} else if (edgeIndex6 >= 0) {
finalIndex = edgeIndex6;
} else if (edgeIndex10 >= 0) {
finalIndex = edgeIndex10;
} else if (edgeIndex15 >= 0) {
finalIndex = edgeIndex15;
}
print(
"getEdgeIndex: index : $edgeIndex1, $edgeIndex2, $edgeIndex6, $edgeIndex10, $edgeIndex15, $finalIndex");
return finalIndex;
}
Step 2. Now add this function to get snap LatLag to snap you current marker in centre of polyline route.
Map<String, dynamic> getSnapLatLng(LatLng currentLocation) {
final currentLocationMT =
mt.LatLng(currentLocation.latitude, currentLocation.longitude);
if (coordinatesMT.isEmpty) {
for (LatLng latLng in coordinates) {
coordinatesMT.add(mt.LatLng(latLng.latitude, latLng.longitude));
}
}
final int finalIndex = getEdgeIndex(coordinatesMT, currentLocationMT);
if (finalIndex >= 0) {
final snappedLatLng2 = (finalIndex < coordinatesMT.length - 1)
? coordinatesMT[finalIndex + 1]
: currentLocationMT;
final snappedLatLng = coordinatesMT[finalIndex];
final distanceM2 = mt.SphericalUtil.computeDistanceBetween(
snappedLatLng, currentLocationMT);
double heading = mt.SphericalUtil.computeHeading(
snappedLatLng2,
snappedLatLng,
);
final extrapolated =
mt.SphericalUtil.computeOffset(snappedLatLng, -distanceM2, heading);
print(
"snapToPolyline:distanceM $distanceM2, $heading, $finalIndex, ${coordinatesMT.length}");
return {
"index": finalIndex,
"latLng": LatLng(extrapolated.latitude, extrapolated.longitude)
};
}
return {"index": finalIndex, "latLng": currentLocation};
}
NOTE: I have used https://pub.dev/packages/maps_toolkit this package for locationIndexOnPath, computeDistanceBetween, computeHeading.

Flutter LatLngBounds not showing accurate place

I am trying to show all of my markers into the viewport using my flutter google maps. But it seems not working in my case. I have tried so far as below:
_controller.animateCamera(CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(23.785182, 90.330702),
northeast: LatLng(24.582782, 88.821163),
),
100
));
LatLngBounds boundsFromLatLngList(List<LatLng> list) {
assert(list.isNotEmpty);
double x0, x1, y0, y1;
for (LatLng latLng in list) {
if (x0 == null) {
x0 = x1 = latLng.latitude;
y0 = y1 = latLng.longitude;
} else {
if (latLng.latitude > x1) x1 = latLng.latitude;
if (latLng.latitude < x0) x0 = latLng.latitude;
if (latLng.longitude > y1) y1 = latLng.longitude;
if (latLng.longitude < y0) y0 = latLng.longitude;
}
}
return LatLngBounds(northeast: LatLng(x1, y1), southwest: LatLng(x0, y0));
}
As i have seen, it just always show the map of North Atlantic Ocean
Is there any solution regarding this issue or it is just under development in Flutter ?. thanks in advance
I'm facing the exact same issue on Android (works fine on iOS) when I animate the map with CameraUpdate.newLatLngBounds. It repositions to North Pacific Ocean immediately after setting the bounds, not sure what's causing this but here's a workaround -
Instead of setting the map position using LatLngBounds, you can calculate the centre of the bounds you want to set
// the bounds you want to set
LatLngBounds bounds = LatLngBounds(
southwest: LatLng(23.785182, 90.330702),
northeast: LatLng(24.582782, 88.821163),
);
// calculating centre of the bounds
LatLng centerBounds = LatLng(
(bounds.northeast.latitude + bounds.southwest.latitude)/2,
(bounds.northeast.longitude + bounds.southwest.longitude)/2
);
// setting map position to centre to start with
controller.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: centerBounds,
zoom: 17,
)));
zoomToFit(controller, bounds, centerBounds);
Once you set the map position to the centre of the bounds (and zoomed in), you then need to keep zooming out till the visible map region covers the bounds you want to set. You can get the visible map region with controller.getVisibleRegion(). Here's the implementation -
Future<void> zoomToFit(GoogleMapController controller, LatLngBounds bounds, LatLng centerBounds) async {
bool keepZoomingOut = true;
while(keepZoomingOut) {
final LatLngBounds screenBounds = await controller.getVisibleRegion();
if(fits(bounds, screenBounds)){
keepZoomingOut = false;
final double zoomLevel = await controller.getZoomLevel() - 0.5;
controller.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: centerBounds,
zoom: zoomLevel,
)));
break;
}
else {
// Zooming out by 0.1 zoom level per iteration
final double zoomLevel = await controller.getZoomLevel() - 0.1;
controller.moveCamera(CameraUpdate.newCameraPosition(CameraPosition(
target: centerBounds,
zoom: zoomLevel,
)));
}
}
}
bool fits(LatLngBounds fitBounds, LatLngBounds screenBounds) {
final bool northEastLatitudeCheck = screenBounds.northeast.latitude >= fitBounds.northeast.latitude;
final bool northEastLongitudeCheck = screenBounds.northeast.longitude >= fitBounds.northeast.longitude;
final bool southWestLatitudeCheck = screenBounds.southwest.latitude <= fitBounds.southwest.latitude;
final bool southWestLongitudeCheck = screenBounds.southwest.longitude <= fitBounds.southwest.longitude;
return northEastLatitudeCheck && northEastLongitudeCheck && southWestLatitudeCheck && southWestLongitudeCheck;
}
Had the same issue the problem for me:
I didn't really gave the southwest and northeast coordinates but:
NorthWest and SouthEast. iOS handled this normally but android zoomed on the Atlantic.
After fixing that like:
final highestLat = points.map((e) => e.latitude).reduce(max);
final highestLong = points.map((e) => e.longitude).reduce(max);
final lowestLat = points.map((e) => e.latitude).reduce(min);
final lowestLong = points.map((e) => e.longitude).reduce(min);
final lowestLatLowestLong = LatLng(lowestLat, lowestLong);
final highestLatHighestLong = LatLng(highestLat, highestLong);
final getRouteBoundsCameraUpdate = CameraUpdate.newLatLngBounds(LatLngBounds(southwest: lowestLatLowestLong, northeast: highestLatHighestLong), 25.0);
I also had this issue, try swapping the southwest value with northeast value
i found this way that work for me perfectly .
Future<void> updateCameraLocation(
LatLng source,
LatLng destination,
GoogleMapController mapController,
) async {
if (mapController == null) return;
LatLngBounds bounds;
if (source.latitude > destination.latitude &&
source.longitude > destination.longitude) {
bounds = LatLngBounds(southwest: destination, northeast: source);
} else if (source.longitude > destination.longitude) {
bounds = LatLngBounds(
southwest: LatLng(source.latitude, destination.longitude),
northeast: LatLng(destination.latitude, source.longitude));
} else if (source.latitude > destination.latitude) {
bounds = LatLngBounds(
southwest: LatLng(destination.latitude, source.longitude),
northeast: LatLng(source.latitude, destination.longitude));
} else {
bounds = LatLngBounds(southwest: source, northeast: destination);
}
CameraUpdate cameraUpdate = CameraUpdate.newLatLngBounds(bounds, 70);
return checkCameraLocation(cameraUpdate, mapController);
}
Future<void> checkCameraLocation(
CameraUpdate cameraUpdate, GoogleMapController mapController) async {
mapController.animateCamera(cameraUpdate);
LatLngBounds l1 = await mapController.getVisibleRegion();
LatLngBounds l2 = await mapController.getVisibleRegion();
if (l1.southwest.latitude == -90 || l2.southwest.latitude == -90) {
return checkCameraLocation(cameraUpdate, mapController);
}
}
And use by this line :
await updateCameraLocation(source, destination, controller);

How do I integrate a game in my flutter app?

I want to use this game https://github.com/yum650350/tissuebox in my Flutter project. I tried calling different parts of the main page but it wasn't coming up properly even though the game itself is working.
I want to integrate it in my iOS app and want to call it with a
function which opens a separate page where the user can play the game.
Is there a way I can do it?
So this is what I tried:
I called the method of the game in a separate screen
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(new MaterialApp(
debugShowCheckedModeBanner: false,
home: FirstScreen(),
));
}
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
body: Center(
child: RaisedButton(
color: Colors.red,
child: Text('Go to Second Screen'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute<Null>(builder: (BuildContext context) {
**???**
}));
},
),
),
);
}
}
But I don't know what to call in ?? this part since the game isn't wrapped in any class. This is the code of the game I want to call.
import 'package:shared_preferences/shared_preferences.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/gestures.dart';
import 'package:flame/sprite.dart';
import 'package:flame/flame.dart';
import 'package:flame/util.dart';
import 'package:flame/game.dart';
import 'dart:math';
tissuemain() async {
var util = Util();
await util.fullScreen();
await util.setOrientation(DeviceOrientation.portraitUp);
//loadimages
//tissuebox : 0,1,2,3,4,5,6
//background : b
//crown : c
//tissue : t
await Flame.images.loadAll(['b', '0', '1', '2', '3', '4', '5', '6', 't', 'c']);
audioLoad(c) async => (await Flame.audio.load(c)).path;
setAudio(a, s, v) async {
await a.setUrl(await audioLoad(s), isLocal: true);
a.setVolume(v);
}
//audios
//single drag : s.mp3
//double drag : s.mp3
//triple drag : s.mp3
//tick tock : tk.mp3
//game over : a.mp3
GameTable.setAudioList(GameTable.audioList1, await audioLoad('s.mp3'));
GameTable.setAudioList(GameTable.audioList2, await audioLoad('d.mp3'));
GameTable.setAudioList(GameTable.audioList3, await audioLoad('t.mp3'));
await setAudio(GameTable.tickTock, 'tk.mp3', 1.0);
await setAudio(GameTable.gameOver, 'a.mp3', .5);
var game = GameTable((await SharedPreferences.getInstance()).getInt('hs') ?? 0);
var hDrag = HorizontalDragGestureRecognizer();
var vDrag = VerticalDragGestureRecognizer();
hDrag.onUpdate = game.onDragUpdate;
hDrag.onStart = game.onDragStart;
hDrag.onEnd = game.onDragEnd;
vDrag.onUpdate = game.onDragUpdate;
vDrag.onStart = game.onDragStart;
vDrag.onEnd = game.onDragEnd;
runApp(game.widget);
util.addGestureRecognizer(hDrag);
util.addGestureRecognizer(vDrag);
}
enum Drag { tissue, box, none }
class GameTable extends Game {
//Audio
//These are workarounds for the ios memory leak
static var
tickTock = AudioPlayer(),
gameOver = AudioPlayer(),
audioList1 = [AudioPlayer(), AudioPlayer(), AudioPlayer()],
audioList2 = [AudioPlayer(), AudioPlayer()],
audioList3 = [AudioPlayer(), AudioPlayer()],
audioIndex1 = 0,
audioIndex2 = 0,
audioIndex3 = 0;
static getPlayIndex(int audioPlayer) {
if (audioPlayer == 1)
audioIndex1 = audioIndex1 < audioList1.length - 1 ? audioIndex1 + 1 : 0;
else if (audioPlayer == 2)
audioIndex2 = audioIndex2 < audioList2.length - 1 ? audioIndex2 + 1 : 0;
else if (audioPlayer == 3) audioIndex3 = audioIndex3 < audioList3.length - 1 ? audioIndex3 + 1 : 0;
return audioPlayer == 1 ? audioIndex1 : audioPlayer == 2 ? audioIndex2 : audioIndex3;
}
static get tissue1 => audioList1[getPlayIndex(1)];
static get tissue2 => audioList2[getPlayIndex(2)];
static get tissue3 => audioList3[getPlayIndex(3)];
static setAudioList(List<AudioPlayer> al,String audioName) => al.forEach((x) {
x.setUrl(audioName, isLocal: true);
x.setVolume(.2);
});
//
var background = Sprite('b'),
crown = Sprite('c'),
initialPoint = Offset.zero,
destPoint = Offset.zero,
dragState = Drag.none,
gameing = false,
gameover = false,
timePass = .0,
heighScore = 0,
score = 0,
timePassTemp = 0;
double tileSize, point1;
double get k => screenSize.width / 5 / tileSize;
Size screenSize;
Rect rect;
TissueBox tissueBox;
saveHighScore() async => await (await SharedPreferences.getInstance()).setInt('hs', heighScore);
GameTable(this.heighScore) {
init();
}
init() async {
resize(await Flame.util.initialDimensions());
rect = Rect.fromLTWH(.0, screenSize.height - tileSize * 23, tileSize * 9, tileSize * 23);
tissueBox = TissueBox(this);
}
#override
render(Canvas c) {
paintText(txt, offset, center, fontSize) {
var painter = TextPainter(
text: TextSpan(
style: TextStyle(
color: Colors.white,
fontSize: fontSize,
fontFamily: 'NS'),
text: txt),
textScaleFactor: k,
textDirection: TextDirection.ltr);
painter.layout();
painter.paint(c, center ? Offset(offset.dx - painter.width / 2, offset.dy) : offset);
}
background.renderRect(c, rect);
tissueBox.render(c);
var horCenter = tissueBox.initialLeft + tissueBox.boxRect.width / 2;
if (gameing)
paintText(timePass.toStringAsFixed(timePass < 1 ? 1 : 0) + 's', Offset(horCenter + 8, k * 23), true, k * 10);
var heighScoreTxt = heighScore.toString();
paintText(heighScoreTxt, Offset(heighScoreTxt.length==1?44.0:heighScoreTxt.length>2?22.0:33.0, k * 30), false, k * 12);
crown.renderRect(c, Rect.fromLTWH(28.0, k * 10, 49.2, 39.0));
paintText(score.toString(), Offset(horCenter, k * 50), true, k * 25);
heighScore = score > heighScore ? score : heighScore;
}
#override
update(double t) {
tissueBox.update(t);
timePass -= gameing || gameover ? t : 0;
if (timePass < 0 && gameing) {
tissueBox.isAway = true;
gameing = false;
timePass = 2;
gameover = true;
saveHighScore();
tissueBox.newGame();
} else if (gameing && !gameover) {
var floor = timePass.floor();
if (floor < timePassTemp && floor < 6 && floor != 0)
TissueBox.delay(Duration(milliseconds: 300), () => GameTable.tickTock.resume());
timePassTemp = floor;
}
gameover = timePass <= 0 && gameover ? false : gameover;
}
resize(s) {
screenSize = s;
tileSize = screenSize.width / 9;
}
onDragStart(DragStartDetails detail) {
var point = detail.globalPosition;
dragState = tissueBox.tissue.rect.contains(point) ? Drag.tissue : tissueBox.boxRect.contains(point) ? Drag.box : Drag.none;
initialPoint = Offset(point.dx == 0 ? initialPoint.dx : point.dx, point.dy == 0 ? initialPoint.dy : point.dy);
point1 = (tissueBox.tissue.rect.left - point.dx).abs();
}
onDragUpdate(DragUpdateDetails detail) {
if (gameover || dragState == Drag.none) return;
var point = detail.globalPosition;
destPoint = Offset(point.dx == 0 ? destPoint.dx : point.dx, point.dy == 0 ? destPoint.dy : point.dy);
if (dragState == Drag.tissue) {
if (initialPoint.dy - destPoint.dy > 100) {
if (gameing != true && gameover != true) {
gameing = true;
timePass = 10;
score = 0;
}
var sub = (point1 - (tissueBox.tissue.rect.left - point.dx).abs()).abs();
var addPoint = sub < 3 ? 3 : sub < 6 ? 2 : 1;
dragState = Drag.none;
tissueBox.nextTissue(addPoint);
playTissueAudio(addPoint);
score += addPoint;
}
} else if (dragState == Drag.box) {
tissueBox.boxRect = Rect.fromLTWH(tissueBox.initialLeft + destPoint.dx - initialPoint.dx, tissueBox.boxRect.top, TissueBox.boxSize.dx, TissueBox.boxSize.dy);
tissueBox.ismoving = true;
}
}
playTissueAudio(i) => (i == 1 ? GameTable.tissue1 : i == 2 ? GameTable.tissue2 : GameTable.tissue3).resume();
onDragEnd(DragEndDetails detail) {
initialPoint = Offset.zero;
dragState = Drag.none;
tissueBox.tissue.isMoving = false;
tissueBox.ismoving = false;
destPoint = initialPoint;
}
}
class TissueBox {
Rect get initialRect => Rect.fromLTWH(boxRect.center.dx - Tissue.width / 2, boxRect.top - boxRect.height + 20.3, Tissue.width, Tissue.width);
Sprite get getBoxSprite =>Sprite( rnd.nextInt(7).toString());
var tissueAwayList = List<TissueAway>(), rnd = Random(), ismoving = false, isAway = false;
Offset get getTissueUpPosition => Offset(initialRect.left, initialRect.top - 150);
final GameTable game;
Sprite boxSprite;
Rect boxRect;
int tissueCount;
Tissue tissue;
double get initialLeft => game.screenSize.width / 2 - TissueBox.boxSize.dx / 2;
double get initialTop => game.screenSize.height - game.tileSize * 5.5;
static var boxSize = Offset(150.0, 100.0);
TissueBox(this.game) {
boxRect = Rect.fromLTWH(initialLeft, initialTop, boxSize.dx, boxSize.dy);
tissueCount = 10 - rnd.nextInt(5);
tissue = Tissue(game, this);
boxSprite = getBoxSprite;
}
render(Canvas c) {
boxSprite.renderRect(c, boxRect);
tissue.render(c);
tissueAwayList.forEach((x) => x.render(c));
}
update(double t) {
tissue.update(t);
tissueAwayList.removeWhere((x) => x.isAway);
tissueAwayList.forEach((x) => x.update(t));
var distense = boxRect.left - initialLeft;
if (ismoving && !game.gameover) {
if (distense.abs() > 50 && tissueCount == 0){
isAway = true;
}
} else if (isAway && !game.gameover) {
boxRect = boxRect.shift(Offset(distense > 0 ? boxRect.left + game.k * 11 : boxRect.left - game.k * 11, boxRect.top));
if (boxRect.right < -50 || boxRect.left > game.screenSize.width + 50) {
newBox();
}
} else if (isAway && game.gameover) {
var target = Offset(boxRect.left, game.screenSize.height + Tissue.width) - Offset(boxRect.left, boxRect.top);
boxRect = boxRect.shift(
game.k * 11 < target.distance ?
Offset.fromDirection(target.direction, game.k * 11)
: target);
} else {
var target = Offset(initialLeft, initialTop) - Offset(boxRect.left, boxRect.top);
boxRect = boxRect.shift(
game.k * 11 < target.distance ?
Offset.fromDirection(target.direction, game.k * 11)
: target);
}
}
nextTissue(int pointsAdd) {
var duration = Duration(milliseconds: 100);
tissueAwayList.add(TissueAway(game, this));
if (pointsAdd > 1)
delay(duration, () {
tissueAwayList.add(TissueAway(game, this));
if (pointsAdd > 2)
delay(duration, () {
tissueAwayList.add(TissueAway(game, this));
});
});
tissue = Tissue(game, this, --tissueCount == 0);
}
newBox() {
boxSprite = getBoxSprite;
boxRect = Rect.fromLTWH(boxRect.right < -0 ? game.screenSize.width + 50 - boxSize.dx : -50.0, initialTop, boxSize.dx, boxSize.dy);
tissueCount = 10 - rnd.nextInt(5);
tissue = Tissue(game, this);
isAway = false;
ismoving = false;
}
newGame() async {
isAway = true;
GameTable.gameOver.resume();
await delay(Duration(seconds: 2), () {});
newBox();
}
static delay(duration, func()) async => await Future.delayed(duration, func);
}
class Tissue {
var tissueSprite = Sprite('t'), isMoving = false;
static var width = 100.0;
final TissueBox tissueBox;
final GameTable game;
bool isAway;
Rect rect;
Tissue(this.game, this.tissueBox, [this.isAway = false]) {
rect = tissueBox.initialRect;
}
render(Canvas c) => tissueSprite.renderRect(c, rect);
update(double t) => rect = isAway ? rect.shift(Offset.infinite) : tissueBox.initialRect;
}
class TissueAway extends Tissue {
TissueAway(GameTable game, TissueBox tissueBox) : super(game, tissueBox);
render(Canvas c) => tissueSprite.renderRect(c, rect);
update(double t) {
var speed = 500 * t;
Offset target = tissueBox.getTissueUpPosition - Offset(rect.left, rect.top);
if (speed < target.distance)
rect = rect.shift(Offset.fromDirection(target.direction, speed));
else
isAway = true;
}
}
The code in the main method will have a line that says runApp(...). The value of the ... is a widget that will be treated as the root widget of the app. In theory, you could just take that widget and pass it to your Navigator.push method and it should treat that widget as any other widget.
That being said, real life probably won't be as clean as this. There might be some initialization code in that app's main method or root widget that won't work properly if the app has long since already been initialized. Since virtually every Flutter app's root widget creates a WidgetsApp (or one of its derived classes MaterialApp or CupertinoApp), there might be some conflict that arrives from having one of those widgets being inserted as a descendent of another one of those widgets.
The game in question may work simply, or it might take some tweaking to work properly. That's something that will depend entirely on what app you are trying to embed into your own, so the only solution is to try it out and see for yourself.

How can one make an ellipse in react-leaflet?

I am trying to draw an ellipse on a map made using react-leaflet, which has built-in support for circles and rectangles.
To achieve this, I am using code to produce an ellipse in (non-react) leaflet from here, that I have adapted and pasted below:
import * as L from 'leaflet';
L.SVG.include ({
_updateEllipse: function (layer) {
var // c = layer._point,
rx = layer._radiusX,
ry = layer._radiusY,
phi = layer._tiltDeg,
endPoint = layer._endPointParams;
var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
'A' + rx + ',' + ry + ',' + phi + ',' +
endPoint.largeArc + ',' + endPoint.sweep + ',' +
endPoint.x1 + ',' + endPoint.y1 + ' z';
this._setPath(layer, d);
}
});
L.Canvas.include ({
_updateEllipse: function (layer) {
if (layer._empty()) { return; }
var p = layer._point,
ctx = this._ctx,
r = layer._radiusX,
s = (layer._radiusY || r) / r;
this._drawnLayers[layer._leaflet_id] = layer;
ctx.save();
ctx.translate(p.x, p.y);
if (layer._tilt !== 0) {
ctx.rotate( layer._tilt );
}
if (s !== 1) {
ctx.scale(1, s);
}
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.restore();
this._fillStroke(ctx, layer);
},
});
L.Ellipse = L.Path.extend({
options: {
fill: true,
startAngle: 0,
endAngle: 359.9
},
initialize: function (latlng, radii, tilt, options) {
L.setOptions(this, options);
this._latlng = L.latLng(latlng);
if (tilt) {
this._tiltDeg = tilt;
} else {
this._tiltDeg = 0;
}
if (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
}
},
setRadius: function (radii) {
this._mRadiusX = radii[0];
this._mRadiusY = radii[1];
return this.redraw();
},
getRadius: function () {
return new L.point(this._mRadiusX, this._mRadiusY);
},
setTilt: function (tilt) {
this._tiltDeg = tilt;
return this.redraw();
},
getBounds: function () {
// TODO respect tilt (bounds are too big)
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng;
return new L.LatLngBounds(
[latlng.lat - latRadius, latlng.lng - lngRadius],
[latlng.lat + latRadius, latlng.lng + lngRadius]);
},
// #method setLatLng(latLng: LatLng): this
// Sets the position of a circle marker to a new location.
setLatLng: function (latlng) {
this._latlng = L.latLng(latlng);
this.redraw();
return this.fire('move', {latlng: this._latlng});
},
// #method getLatLng(): LatLng
// Returns the current geographical position of the circle marker
getLatLng: function () {
return this._latlng;
},
setStyle: L.Path.prototype.setStyle,
_project: function () {
var lngRadius = this._getLngRadius(),
latRadius = this._getLatRadius(),
latlng = this._latlng,
pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
this._point = this._map.latLngToLayerPoint(latlng);
this._radiusX = Math.max(this._point.x - pointLeft.x, 1);
this._radiusY = Math.max(pointBelow.y - this._point.y, 1);
this._tilt = Math.PI * this._tiltDeg / 180;
this._endPointParams = this._centerPointToEndPoint();
this._updateBounds();
},
_updateBounds: function () {
// http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var sinSquare = sin * sin;
var cosSquare = cos * cos;
var aSquare = this._radiusX * this._radiusX;
var bSquare = this._radiusY * this._radiusY;
var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
var w = this._clickTolerance();
var p = [halfWidth + w, halfHeight + w];
this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
},
_update: function () {
if (this._map) {
this._updatePath();
}
},
_updatePath: function () {
this._renderer._updateEllipse(this);
},
_getLatRadius: function () {
return (this._mRadiusY / 40075017) * 360;
},
_getLngRadius: function () {
return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
},
_centerPointToEndPoint: function () {
// Convert between center point parameterization of an ellipse
// too SVG's end-point and sweep parameters. This is an
// adaptation of the perl code found here:
// http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
var c = this._point,
rx = this._radiusX,
ry = this._radiusY,
theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
theta1 = this.options.startAngle * (Math.PI / 180),
delta = this.options.endAngle,
phi = this._tiltDeg * (Math.PI / 180);
// Determine start and end-point coordinates
var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
Math.sin(-phi) * ry * Math.sin(theta1);
var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
Math.cos(phi) * ry * Math.sin(theta1);
var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
Math.sin(-phi) * ry * Math.sin(theta2);
var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
Math.cos(phi) * ry * Math.sin(theta2);
var largeArc = (delta > 180) ? 1 : 0;
var sweep = (delta > 0) ? 1 : 0;
return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
'sweep': sweep, 'x1': x1, 'y1': y1};
},
_empty: function () {
return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
},
_containsPoint : function (p) {
// http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
var sin = Math.sin(this._tilt);
var cos = Math.cos(this._tilt);
var dx = p.x - this._point.x;
var dy = p.y - this._point.y;
var sumA = cos * dx + sin * dy;
var sumB = sin * dx - cos * dy;
return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
}
});
export const lellipse = function (latlng, radii, tilt, options) {
return new L.Ellipse(latlng, radii, tilt, options);
};
To create an ellipse to use with react-leaflet, I followed the example of Circle in react-leaflet to produce the following Ellipse component:
import PropTypes from 'prop-types'
import { lellipse as LeafletEllipse } from '../l.ellipse';
import Path from './Path'
import children from './propTypes/children'
import latlng from './propTypes/latlng'
import type { LatLng, MapLayerProps, PathOptions } from './types'
type LeafletElement = LeafletEllipse
type Props = {
center: LatLng,
mSemiMajorAxis: number,
mSemiMinorAxis: number,
degreeTiltFromWest: number,
} & MapLayerProps &
PathOptions &
Object
export default class Ellipse extends Path<LeafletElement, Props> {
static propTypes = {
center: latlng.isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired,
children: children,
}
createLeafletElement(props: Props): LeafletElement {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], this.getOptions(options))
}
updateLeafletElement(fromProps: Props, toProps: Props) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
The problem with the code is that it does not render an ellipse and it does not throw any errors. Could someone suggest how to render an ellipse with react-leaflet? Thanks.
Your createLeafletElement function is missing the tilt parameter. It should be:
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
See below for the complete file (in ES6 rather than in typescript, as I find it clearer).
import React, { PropTypes } from 'react';
import { lellipse as LeafletEllipse } from './l.ellipse';
import { Path, withLeaflet } from 'react-leaflet';
class Ellipse extends Path {
static propTypes = {
center: PropTypes.arrayOf(PropTypes.number).isRequired,
mSemiMajorAxis: PropTypes.number.isRequired,
mSemiMinorAxis: PropTypes.number.isRequired,
degreeTiltFromWest: PropTypes.number.isRequired
}
createLeafletElement(props) {
const { center, mSemiMajorAxis, mSemiMinorAxis, degreeTiltFromWest, ...options } = props
return new LeafletEllipse(center, [mSemiMajorAxis, mSemiMinorAxis], degreeTiltFromWest, this.getOptions(options))
}
updateLeafletElement(fromProps, toProps) {
if (toProps.center !== fromProps.center) {
this.leafletElement.setLatLng(toProps.center);
}
if (toProps.degreeTiltFromWest !== fromProps.degreeTiltFromWest) {
this.leafletElement.setTilt(toProps.degreeTiltFromWest);
}
if (toProps.mSemiMinorAxis !== fromProps.mSemiMinorAxis || toProps.mSemiMajorAxis !== fromProps.mSemiMajorAxis) {
this.leafletElement.setRadius([toProps.mSemiMajorAxis, toProps.mSemiMinorAxis]);
}
}
}
export default class withLeaflet(Ellipse);