sport_utils.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. import 'dart:io';
  2. import 'dart:math';
  3. import 'package:amap_flutter_base/amap_flutter_base.dart';
  4. import 'package:amap_flutter_map/amap_flutter_map.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:sport/pages/run/location.dart';
  7. import 'package:sport/pages/run/run_page.dart';
  8. import 'package:sport/widgets/circular_percent_indicator.dart';
  9. const Color COLOR_RUN = Color(0xffFF5B1D);
  10. const Color COLOR_RUN_SLOW = Color(0xff00DC42);
  11. const Color COLOR_RUN_MIDDLE = Color(0xffFFDD00);
  12. const Color COLOR_RUN_FAST = Color(0xffFF5B1D);
  13. const SPORT_TYPE = [
  14. {"name": "跑步", "met": 11.5, "unit": "公里", "v": 12.1, "i":"pop_icon_run"},
  15. {"name": "骑行", "met": 6.8, "unit": "公里", "v": 17.7, "i":"pop_icon_ride"},
  16. {"name": "跳绳", "met": 19.5, "unit": "次", "v": 200.0 * 60.0, "fix": 0, "i":"pop_icon_skip"},
  17. {"name": "步行", "met": 4.3, "unit": "公里", "v": 5.6, "i":"pop_icon_walk"},
  18. ];
  19. extension Iterables<E> on Iterable<E> {
  20. Map<K, List<E>> groupBy<K>(K Function(E) keyFunction) => fold(<K, List<E>>{}, (Map<K, List<E>> map, E element) => map..putIfAbsent(keyFunction(element), () => <E>[]).add(element));
  21. }
  22. class SportUtils {
  23. static String numToStr(num? v,{int asFixed = -1}) {
  24. if (v == null) return "0";
  25. if (v > 10000) {
  26. return "${(v / 10000).toStringAsFixed(1)}W";
  27. }
  28. return asFixed == -1 ? v.toString() : v.toStringAsFixed(asFixed);
  29. }
  30. static String calSportStr(int sportType, double data) {
  31. var map = SPORT_TYPE[sportType];
  32. int fixed = map['fix'] == null ? 2 : map['fix'] as int;
  33. return "${map['name']} ${formatNum(data, fixed)}${map['unit']}";
  34. }
  35. static String calSportType(int sportType, int consume, double weight) {
  36. var map = SPORT_TYPE[sportType];
  37. double result = (consume * 1.0 / (map['met']! as double) / weight) * (map['v']! as double);
  38. return calSportStr(sportType, result);
  39. }
  40. static String calSportTypeDataStr(int sportType, double data) {
  41. var map = SPORT_TYPE[sportType];
  42. int fixed = map['fix'] == null ? 2 : map['fix'] as int;
  43. return "${formatNum(data, fixed)}";
  44. }
  45. static double calSportTypeData(int sportType, int consume, double weight) {
  46. var map = SPORT_TYPE[sportType];
  47. double result = (consume * 1.0 / (map['met']! as double) / weight) * (map['v']! as double);
  48. return result;
  49. }
  50. static LatLng toLatLng(Location location) {
  51. return LatLng(location.latitude, location.longitude);
  52. }
  53. static String toDateTime(DateTime? time, {String join = "-"}) {
  54. if (time == null) return "";
  55. // return "${time.year}$join${time.month.toString().padLeft(2, "0")}$join${time.day.toString().padLeft(2, "0")}";
  56. return "${time.month.toString().padLeft(2, "0")}月${time.day.toString().padLeft(2, "0")}日";
  57. }
  58. static String toDateTimeFull(DateTime? time) {
  59. if (time == null) return "";
  60. return "${time.year}-${time.month.toString().padLeft(2, "0")}-${time.day.toString().padLeft(2, "0")} ${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}";
  61. }
  62. static String toDateTimeFullCh(DateTime? time) {
  63. if (time == null) return "";
  64. return "${time.year}年${time.month.toString().padLeft(2, "0")}月${time.day.toString().padLeft(2, "0")}日 ${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}";
  65. }
  66. static String toDateTimeFullDay(DateTime? time, {String join = "."}) {
  67. if (time == null) return "";
  68. return "${time.year}$join${time.month.toString().padLeft(2, "0")}$join${time.day.toString().padLeft(2, "0")}";
  69. }
  70. static String toDateTimeDay(DateTime? time, {String join = "."}) {
  71. if (time == null) return "";
  72. DateTime now = DateTime.now();
  73. if (now.day - time.day == 0) {
  74. return "今日";
  75. } else if (now.day - time.day == 1) {
  76. return "昨日";
  77. }
  78. return "${time.year}$join${time.month.toString().padLeft(2, "0")}$join${time.day.toString().padLeft(2, "0")}";
  79. }
  80. static String toEndTime(DateTime? time) {
  81. if (time == null) return "";
  82. return "${time.hour.toString().padLeft(2, "0")}:${time.minute.toString().padLeft(2, "0")}";
  83. }
  84. static int calorie4run(double weight, int second, int step) {
  85. final double k = 671.1;
  86. double stepRate = 1.0;
  87. if (step > 0) {
  88. stepRate = step / second * 60.0;
  89. }
  90. // return 用户体重kg/95 * 运动时间h * K(运动类型系数;
  91. return (weight / 95.0 * (second / 3600) * (k * (stepRate / 150.0))).toInt();
  92. }
  93. static double speedToMet(double speed) {
  94. if (speed == 0) return 0;
  95. final List<double> mets = [6.0, 8.3, 9.0, 9.8, 10.5, 11.0, 11.5, 11.8, 12.3, 12.8, 14.5, 16.0, 19.0, 19.8, 23.0];
  96. final List<double> speeds = [6.4, 8.0, 8.4, 9.7, 10.8, 11.3, 12.1, 12.9, 13.8, 14.5, 16.1, 17.7, 19.3, 20.9, 22.5];
  97. double met = mets.first;
  98. try {
  99. for (var i = 0; i < speeds.length; i++) {
  100. double s = speeds[i];
  101. if (speed < s) {
  102. if (i == 0) {
  103. met = mets.first;
  104. } else {
  105. double percent = (speed - speeds[i - 1]) / (speeds[i] - speeds[i - 1]);
  106. met = (mets[i] - mets[i - 1]) * percent + mets[i - 1];
  107. }
  108. break;
  109. }
  110. }
  111. } catch (e) {
  112. print(e);
  113. }
  114. return met;
  115. }
  116. static int calorie4runByMet(double weight, double distance, int second) {
  117. final double speed = (distance / 1000.0) / (second / 3600);
  118. double met = speedToMet(speed);
  119. // 卡路里消耗(大卡)=MET值× 0.0167 ×时间h(min)×体重(kg)
  120. return consume(met, (second / 60.0), weight);
  121. }
  122. static int consume(double met, double minute, double weight){
  123. return (met * 0.0167 * minute * weight).round();
  124. }
  125. static int consumeToMinute(int consume, double met, double weight){
  126. return (consume / (met * 0.0167 * weight)).round();
  127. }
  128. static int calorie(double weight, int difficulty, int costTime) {
  129. return weight * 1.0 * difficulty * costTime ~/ 3600000;
  130. }
  131. static double calPace(int second, double kilometre) {
  132. double pace = 0;
  133. if (kilometre != 0) {
  134. pace = second / kilometre;
  135. }
  136. return pace;
  137. }
  138. static String pace(double pace, {String join = ""}) {
  139. return "${(pace ~/ 60).toString().padLeft(2, "0")}′$join${(pace.toInt() % 60).toString().padLeft(2, "0")}″";
  140. }
  141. static String pace11(double pace, {String join = ""}) {
  142. return "${(pace ~/ 60).toString().padLeft(2, "0")}";
  143. }
  144. static String pace12(double pace, {String join = ""}) {
  145. return "${(pace.toInt() % 60).toString().padLeft(2, "0")}";
  146. }
  147. static String pace4(int second, int kilometre) {
  148. return pace(calPace(second, kilometre / 1000));
  149. }
  150. static String paceToMinute(int second, int kilometre) {
  151. String _p = pace(calPace(second, kilometre / 1000));
  152. return _p.substring(0, _p.indexOf("′"));
  153. }
  154. static String paceToSecond(int second, int kilometre) {
  155. String _p = pace(calPace(second, kilometre / 1000));
  156. return _p.substring(_p.indexOf("′"), _p.length);
  157. }
  158. static String sound(int number) {
  159. return number == 2 ? "run/two" : "number/$number";
  160. }
  161. static List<String> timeToAudio(int totalSecond) {
  162. final List<String> audios = [];
  163. int second = totalSecond;
  164. int hour = 0, min = 0, sec = 0;
  165. sec = second % 60;
  166. second = second ~/ 60;
  167. min = second % 60;
  168. hour = second ~/ 60;
  169. if (hour > 0) {
  170. audios.add(sound(hour));
  171. audios.add("run/hour");
  172. }
  173. if (min > 0) {
  174. audios.add(sound(min));
  175. audios.add("run/m");
  176. }
  177. if (sec > 0) {
  178. audios.add(sound(sec));
  179. audios.add("run/s");
  180. }
  181. return audios;
  182. }
  183. static List<List<Location>> split(List<Location> items) {
  184. List<List<Location>> result = [];
  185. List<Location> part = [];
  186. int state = 0;
  187. for (var e in items) {
  188. if (e.state != state) {
  189. if (part.length > 1) result.add(part);
  190. part = [];
  191. state = e.state!;
  192. }
  193. part.add(e);
  194. }
  195. if (part.length > 1) result.add(part);
  196. return result;
  197. }
  198. static Set<Polyline> splitRunLine(List<Location> items, {double speed = 0.0}) {
  199. List<Polyline> result = [];
  200. if (items.length > 1) {
  201. List<LatLng> part = [];
  202. int state = 0;
  203. for (var i = 0; i < items.length; i++) {
  204. var e = items[i];
  205. var latLng = LatLng(e.latitude, e.longitude);
  206. part.add(latLng);
  207. if (e.state != state) {
  208. result.add(createRunLine(part, state));
  209. part = [];
  210. state = e.state ?? 0;
  211. }
  212. }
  213. if (part.isNotEmpty == true) {
  214. result.add(createRunLine(part, state));
  215. }
  216. }
  217. return Set.of(result);
  218. }
  219. static double lineWidth = 5;
  220. static Polyline createRunLine(List<LatLng> part, int state) {
  221. final double _width = lineWidth;
  222. if (Platform.isAndroid) {
  223. return Polyline(points: part, color: COLOR_RUN, width: _width, capType: CapType.round, dashLineType: state == 0 ? DashLineType.none : DashLineType.circle);
  224. } else {
  225. return Polyline(points: part, color: COLOR_RUN, colorList: [COLOR_RUN.value, COLOR_RUN.value], capType: CapType.round, width: _width, dashLineType: state == 0 ? DashLineType.none : DashLineType.circle);
  226. }
  227. }
  228. static Set<Polyline> splitLine(List<Location> items, {double speed = 0.0}) {
  229. List<Polyline> result = [];
  230. final double _speedWeight = 0.92;
  231. final double _speedMax = speed / _speedWeight;
  232. final double _speedMin = speed * _speedWeight;
  233. final double _width = lineWidth;
  234. List<List<Location>> splitList = split(items);
  235. for (var list in splitList) {
  236. var first = list.first;
  237. int state = first.state ?? 0;
  238. if (state == 1) {
  239. result.add(createRunLine(list.map((e) => LatLng(e.latitude, e.longitude)).toList(), 1));
  240. } else {
  241. Color color = COLOR_RUN_SLOW;
  242. int size = 100;
  243. for (var i = 0; i < list.length; i += size) {
  244. var start = list[i];
  245. List<LatLng> part = [];
  246. List<int> colors = [];
  247. colors.add(color.value);
  248. int _j = i;
  249. double speed = 0;
  250. var end = min(list.length, i + size + 2);
  251. for (var j = i; j < end; j++) {
  252. var e = list[j];
  253. part.add(LatLng(e.latitude, e.longitude));
  254. speed += e.speed ?? 0;
  255. }
  256. speed /= part.length;
  257. // print("1111111111111111 $speed $_speedMax $_speedMin");
  258. color = speed > _speedMax
  259. ? COLOR_RUN_FAST
  260. : speed < _speedMin
  261. ? COLOR_RUN_SLOW
  262. : COLOR_RUN_MIDDLE;
  263. colors.add(color.value);
  264. result.add(Polyline(points: part, width: _width, dashLineType: state == 0 ? DashLineType.none : DashLineType.circle, gradient: true, colorList: colors, color: COLOR_RUN));
  265. }
  266. }
  267. }
  268. return Set.of(result);
  269. }
  270. }
  271. final double radius = 6378137;
  272. LatLng navigationTrack(LatLng last, double bearing, int count) {
  273. double lat = last.latitude;
  274. double lon = last.longitude;
  275. final double step = 1.02;
  276. double _bearing = bearing % 360;
  277. if (_bearing > 180) _bearing = _bearing - 360;
  278. double azimut = radians(_bearing);
  279. for (var i = 0; i < count; i++) {
  280. lat += step * cos(azimut) * 180 / radius / pi;
  281. lon += step * sin(azimut) * 180 / radius / cos(pi * lat / 180) / pi;
  282. }
  283. return LatLng(lat, lon);
  284. }
  285. /**
  286. * 根据两点算斜率
  287. */
  288. double getSlope(LatLng fromPoint, LatLng toPoint) {
  289. if (fromPoint == null || toPoint == null) {
  290. return 0;
  291. }
  292. if (toPoint.longitude == fromPoint.longitude) {
  293. return double.maxFinite;
  294. }
  295. double slope = ((toPoint.latitude - fromPoint.latitude) / (toPoint.longitude - fromPoint.longitude));
  296. return slope;
  297. }
  298. /**
  299. * 根据两点算取图标转的角度
  300. */
  301. double getAngle(LatLng fromPoint, LatLng toPoint) {
  302. if (fromPoint == null || toPoint == null) {
  303. return 0;
  304. }
  305. double slope = getSlope(fromPoint, toPoint);
  306. if (slope == double.maxFinite) {
  307. if (toPoint.latitude > fromPoint.latitude) {
  308. return 0;
  309. } else {
  310. return 180;
  311. }
  312. }
  313. double deltAngle = 0;
  314. if ((toPoint.latitude - fromPoint.latitude) * slope < 0) {
  315. deltAngle = 180;
  316. }
  317. double radio = atan(slope);
  318. double angle = 180 * (radio / pi) + deltAngle - 90;
  319. return angle;
  320. }
  321. class LatLngBoundsBuilder {
  322. double mSouth = 1.0 / 0.0;
  323. double mNorth = -1.0 / 0.0;
  324. double mWest = 0.0 / 0.0;
  325. double mEast = 0.0 / 0.0;
  326. include(LatLng var1) {
  327. if (var1 == null) {
  328. return this;
  329. } else {
  330. this.mSouth = min(this.mSouth, var1.latitude);
  331. this.mNorth = max(this.mNorth, var1.latitude);
  332. double var2 = var1.longitude;
  333. if (this.mWest.isNaN) {
  334. this.mWest = var2;
  335. this.mEast = var2;
  336. } else if (!this.a(var2)) {
  337. if (c(this.mWest, var2) < d(this.mEast, var2)) {
  338. this.mWest = var2;
  339. } else {
  340. this.mEast = var2;
  341. }
  342. }
  343. return this;
  344. }
  345. }
  346. static double c(double var0, double var2) {
  347. return (var0 - var2 + 360.0) % 360.0;
  348. }
  349. static double d(double var0, double var2) {
  350. return (var2 - var0 + 360.0) % 360.0;
  351. }
  352. bool a(double var1) {
  353. if (this.mWest <= this.mEast) {
  354. return this.mWest <= var1 && var1 <= this.mEast;
  355. } else {
  356. return this.mWest <= var1 || var1 <= this.mEast;
  357. }
  358. }
  359. static LatLngBounds? fromList(List<LatLng> list) {
  360. LatLngBoundsBuilder builder = LatLngBoundsBuilder();
  361. for (var e in list) {
  362. builder.include(e);
  363. }
  364. return builder.build();
  365. }
  366. LatLngBounds? build() {
  367. if (this.mWest.isNaN) {
  368. return null;
  369. } else {
  370. if (this.mWest > this.mEast) {
  371. double var1 = this.mWest;
  372. this.mWest = this.mEast;
  373. this.mEast = var1;
  374. }
  375. if (this.mSouth > this.mNorth) {
  376. double var3 = this.mSouth;
  377. this.mSouth = this.mNorth;
  378. this.mNorth = var3;
  379. }
  380. LatLngBounds bounds = LatLngBounds(southwest: LatLng(this.mSouth, this.mWest), northeast: LatLng(this.mNorth, this.mEast));
  381. return bounds;
  382. }
  383. }
  384. }