info: alternate map styles

This commit is contained in:
Thibault Deckers 2020-08-09 14:53:14 +09:00
parent f5da9e3ab9
commit fe40408c07
12 changed files with 867 additions and 116 deletions

View file

@ -1,4 +1,6 @@
// run `scripts/update_flutter_version.sh` to update with the content of `flutter --version --machine`
// note on static analysis: the output of the script above yields double quotes, just like the example below
// ignore_for_file: prefer_single_quotes
const Map<String, String> version = {
"channel": "unknown",
"dartSdkVersion": "unknown",

View file

@ -1,4 +1,5 @@
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/widgets/fullscreen/info/location_section.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -18,6 +19,7 @@ class Settings {
static const collectionGroupFactorKey = 'collection_group_factor';
static const collectionSortFactorKey = 'collection_sort_factor';
static const collectionTileExtentKey = 'collection_tile_extent';
static const infoMapStyleKey = 'info_map_style';
static const infoMapZoomKey = 'info_map_zoom';
static const catalogTimeZoneKey = 'catalog_time_zone';
static const hasAcceptedTermsKey = 'has_accepted_terms';
@ -50,6 +52,10 @@ class Settings {
}
}
EntryMapStyle get infoMapStyle => getEnumOrDefault(infoMapStyleKey, EntryMapStyle.stamenWatercolor, EntryMapStyle.values);
set infoMapStyle(EntryMapStyle newValue) => setAndNotify(infoMapStyleKey, newValue.toString());
double get infoMapZoom => _prefs.getDouble(infoMapZoomKey) ?? 12;
set infoMapZoom(double newValue) => setAndNotify(infoMapZoomKey, newValue);

View file

@ -21,6 +21,7 @@ import 'package:aves/widgets/common/aves_logo.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/debug_page.dart';
import 'package:aves/widgets/filter_grid_page.dart';
import 'package:aves/widgets/settings_page.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -93,6 +94,15 @@ class _AppDrawerState extends State<AppDrawer> {
title: 'Favourites',
filter: FavouriteFilter(),
);
final settingsEntry = SafeArea(
top: false,
bottom: false,
child: ListTile(
leading: Icon(AIcons.settings),
title: Text('Preferences'),
onTap: () => _goToSettings(context),
),
);
final aboutEntry = SafeArea(
top: false,
bottom: false,
@ -114,6 +124,7 @@ class _AppDrawerState extends State<AppDrawer> {
_buildCountrySection(),
_buildTagSection(),
Divider(),
settingsEntry,
aboutEntry,
if (kDebugMode) ...[
Divider(),
@ -310,6 +321,16 @@ class _AppDrawerState extends State<AppDrawer> {
);
}
void _goToSettings(BuildContext context) {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SettingsPage(),
),
);
}
void _goToAbout(BuildContext context) {
Navigator.pop(context);
Navigator.push(

View file

@ -19,6 +19,7 @@ class AIcons {
static const IconData location = OMIcons.place;
static const IconData shooting = OMIcons.camera;
static const IconData removableStorage = OMIcons.sdStorage;
static const IconData settings = OMIcons.settings;
static const IconData text = OMIcons.formatQuote;
static const IconData tag = OMIcons.localOffer;

View file

@ -2,13 +2,13 @@ import 'package:aves/model/filters/location.dart';
import 'package:aves/model/image_entry.dart';
import 'package:aves/model/settings.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/utils/geo_utils.dart';
import 'package:aves/widgets/common/aves_filter_chip.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/info/info_page.dart';
import 'package:aves/widgets/fullscreen/info/maps/google_map.dart';
import 'package:aves/widgets/fullscreen/info/maps/leaflet_map.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
class LocationSection extends StatefulWidget {
final CollectionLens collection;
@ -94,15 +94,20 @@ class _LocationSectionState extends State<LocationSection> {
padding: EdgeInsets.only(bottom: 8),
child: SectionRow(AIcons.location),
),
ImageMap(
markerId: entry.uri ?? entry.path,
latLng: LatLng(
entry.latLng.item1,
entry.latLng.item2,
if (settings.infoMapStyle == EntryMapStyle.google)
EntryGoogleMap(
markerId: entry.uri ?? entry.path,
latLng: entry.latLng,
geoUri: entry.geoUri,
initialZoom: settings.infoMapZoom,
)
else
EntryLeafletMap(
latLng: entry.latLng,
geoUri: entry.geoUri,
initialZoom: settings.infoMapZoom,
style: settings.infoMapStyle,
),
geoUri: entry.geoUri,
initialZoom: settings.infoMapZoom,
),
if (location.isNotEmpty)
Padding(
padding: EdgeInsets.only(top: 8),
@ -133,113 +138,21 @@ class _LocationSectionState extends State<LocationSection> {
void _handleChange() => setState(() {});
}
class ImageMap extends StatefulWidget {
final String markerId;
final LatLng latLng;
final String geoUri;
final double initialZoom;
enum EntryMapStyle { google, osmHot, stamenToner, stamenWatercolor }
const ImageMap({
Key key,
this.markerId,
this.latLng,
this.geoUri,
this.initialZoom,
}) : super(key: key);
@override
State<StatefulWidget> createState() => ImageMapState();
}
class ImageMapState extends State<ImageMap> with AutomaticKeepAliveClientMixin {
GoogleMapController _controller;
@override
void didUpdateWidget(ImageMap oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.latLng != oldWidget.latLng && _controller != null) {
_controller.moveCamera(CameraUpdate.newLatLng(widget.latLng));
extension ExtraEntryMapStyle on EntryMapStyle {
String get name {
switch (this) {
case EntryMapStyle.google:
return 'Google Maps';
case EntryMapStyle.osmHot:
return 'Humanitarian OpenStreetMap';
case EntryMapStyle.stamenToner:
return 'Stamen Toner';
case EntryMapStyle.stamenWatercolor:
return 'Stamen Watercolor';
default:
return toString();
}
}
@override
Widget build(BuildContext context) {
super.build(context);
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
return Row(
children: [
Expanded(
child: GestureDetector(
onScaleStart: (details) {
// absorb scale gesture here to prevent scrolling
// and triggering by mistake a move to the image page above
},
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(16),
),
child: Container(
color: Colors.white70,
height: 200,
child: GoogleMap(
// GoogleMap init perf issue: https://github.com/flutter/flutter/issues/28493
initialCameraPosition: CameraPosition(
target: widget.latLng,
zoom: widget.initialZoom,
),
onMapCreated: (controller) => setState(() => _controller = controller),
rotateGesturesEnabled: false,
scrollGesturesEnabled: false,
zoomControlsEnabled: false,
zoomGesturesEnabled: false,
liteModeEnabled: false,
tiltGesturesEnabled: false,
myLocationEnabled: false,
myLocationButtonEnabled: false,
markers: {
Marker(
markerId: MarkerId(widget.markerId),
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
position: widget.latLng,
)
},
),
),
),
),
),
SizedBox(width: 8),
TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Column(children: [
IconButton(
icon: Icon(AIcons.zoomIn),
onPressed: _controller == null ? null : () => _zoomBy(1),
tooltip: 'Zoom in',
),
IconButton(
icon: Icon(AIcons.zoomOut),
onPressed: _controller == null ? null : () => _zoomBy(-1),
tooltip: 'Zoom out',
),
IconButton(
icon: Icon(AIcons.openInNew),
onPressed: () => AndroidAppService.openMap(widget.geoUri),
tooltip: 'Show on map...',
),
]),
)
],
);
}
void _zoomBy(double amount) {
settings.infoMapZoom += amount;
_controller.animateCamera(CameraUpdate.zoomBy(amount));
}
@override
bool get wantKeepAlive => true;
}

View file

@ -0,0 +1,118 @@
import 'package:aves/model/settings.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:tuple/tuple.dart';
class EntryGoogleMap extends StatefulWidget {
final String markerId;
final LatLng latLng;
final String geoUri;
final double initialZoom;
EntryGoogleMap({
Key key,
this.markerId,
Tuple2<double, double> latLng,
this.geoUri,
this.initialZoom,
}) : latLng = LatLng(latLng.item1, latLng.item2),
super(key: key);
@override
State<StatefulWidget> createState() => EntryGoogleMapState();
}
class EntryGoogleMapState extends State<EntryGoogleMap> with AutomaticKeepAliveClientMixin {
GoogleMapController _controller;
@override
void didUpdateWidget(EntryGoogleMap oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.latLng != oldWidget.latLng && _controller != null) {
_controller.moveCamera(CameraUpdate.newLatLng(widget.latLng));
}
}
@override
Widget build(BuildContext context) {
super.build(context);
final accentHue = HSVColor.fromColor(Theme.of(context).accentColor).hue;
return Row(
children: [
Expanded(
child: GestureDetector(
onScaleStart: (details) {
// absorb scale gesture here to prevent scrolling
// and triggering by mistake a move to the image page above
},
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(16),
),
child: Container(
color: Colors.white70,
height: 200,
child: GoogleMap(
// GoogleMap init perf issue: https://github.com/flutter/flutter/issues/28493
initialCameraPosition: CameraPosition(
target: widget.latLng,
zoom: widget.initialZoom,
),
onMapCreated: (controller) => setState(() => _controller = controller),
rotateGesturesEnabled: false,
scrollGesturesEnabled: false,
zoomControlsEnabled: false,
zoomGesturesEnabled: false,
liteModeEnabled: false,
tiltGesturesEnabled: false,
myLocationEnabled: false,
myLocationButtonEnabled: false,
markers: {
Marker(
markerId: MarkerId(widget.markerId),
icon: BitmapDescriptor.defaultMarkerWithHue(accentHue),
position: widget.latLng,
)
},
),
),
),
),
),
SizedBox(width: 8),
TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Column(children: [
IconButton(
icon: Icon(AIcons.zoomIn),
onPressed: _controller == null ? null : () => _zoomBy(1),
tooltip: 'Zoom in',
),
IconButton(
icon: Icon(AIcons.zoomOut),
onPressed: _controller == null ? null : () => _zoomBy(-1),
tooltip: 'Zoom out',
),
IconButton(
icon: Icon(AIcons.openInNew),
onPressed: () => AndroidAppService.openMap(widget.geoUri),
tooltip: 'Show on map...',
),
]),
)
],
);
}
void _zoomBy(double amount) {
settings.infoMapZoom += amount;
_controller.animateCamera(CameraUpdate.zoomBy(amount));
}
@override
bool get wantKeepAlive => true;
}

View file

@ -0,0 +1,216 @@
import 'package:aves/model/settings.dart';
import 'package:aves/services/android_app_service.dart';
import 'package:aves/widgets/common/icons.dart';
import 'package:aves/widgets/fullscreen/info/maps/scale_layer.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong/latlong.dart';
import 'package:tuple/tuple.dart';
import '../location_section.dart';
class EntryLeafletMap extends StatefulWidget {
final LatLng latLng;
final String geoUri;
final double initialZoom;
final EntryMapStyle style;
EntryLeafletMap({
Key key,
Tuple2<double, double> latLng,
this.geoUri,
this.initialZoom,
this.style,
}) : latLng = LatLng(latLng.item1, latLng.item2),
super(key: key);
@override
State<StatefulWidget> createState() => EntryLeafletMapState();
}
class EntryLeafletMapState extends State<EntryLeafletMap> with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
final MapController _mapController = MapController();
static const markerSize = 40.0;
@override
void didUpdateWidget(EntryLeafletMap oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.latLng != oldWidget.latLng && _mapController != null) {
_mapController.move(widget.latLng, settings.infoMapZoom);
}
}
@override
Widget build(BuildContext context) {
super.build(context);
final accentColor = Theme.of(context).accentColor;
return Row(
children: [
Expanded(
child: GestureDetector(
onScaleStart: (details) {
// absorb scale gesture here to prevent scrolling
// and triggering by mistake a move to the image page above
},
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(16),
),
child: Container(
color: Colors.white70,
height: 200,
child: FlutterMap(
options: MapOptions(
center: widget.latLng,
zoom: widget.initialZoom,
interactive: false,
),
children: [
_buildMapLayer(),
ScaleLayerWidget(
options: ScaleLayerOptions(
lineColor: accentColor,
lineWidth: 2,
textStyle: TextStyle(
color: accentColor,
fontSize: 12,
),
padding: EdgeInsets.all(8),
),
),
MarkerLayerWidget(
options: MarkerLayerOptions(
markers: [
Marker(
width: markerSize,
height: markerSize,
point: widget.latLng,
builder: (ctx) {
return Icon(
Icons.place,
size: markerSize,
color: accentColor,
);
},
anchorPos: AnchorPos.align(AnchorAlign.top),
),
],
),
),
],
mapController: _mapController,
),
),
),
),
),
SizedBox(width: 8),
TooltipTheme(
data: TooltipTheme.of(context).copyWith(
preferBelow: false,
),
child: Column(children: [
IconButton(
icon: Icon(AIcons.zoomIn),
onPressed: _mapController == null ? null : () => _zoomBy(1),
tooltip: 'Zoom in',
),
IconButton(
icon: Icon(AIcons.zoomOut),
onPressed: _mapController == null ? null : () => _zoomBy(-1),
tooltip: 'Zoom out',
),
IconButton(
icon: Icon(AIcons.openInNew),
onPressed: () => AndroidAppService.openMap(widget.geoUri),
tooltip: 'Show on map...',
),
]),
)
],
);
}
Widget _buildMapLayer() {
switch (widget.style) {
case EntryMapStyle.osmHot:
return OSMHotLayer();
case EntryMapStyle.stamenToner:
return StamenTonerLayer();
case EntryMapStyle.stamenWatercolor:
return StamenWatercolorLayer();
default:
return SizedBox.shrink();
}
}
void _zoomBy(double amount) {
final endZoom = (settings.infoMapZoom + amount).clamp(1.0, 16.0);
settings.infoMapZoom = endZoom;
final zoomTween = Tween<double>(begin: _mapController.zoom, end: endZoom);
final controller = AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
final animation = CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);
controller.addListener(() => _mapController.move(widget.latLng, zoomTween.evaluate(animation)));
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.dispose();
} else if (status == AnimationStatus.dismissed) {
controller.dispose();
}
});
controller.forward();
}
@override
bool get wantKeepAlive => true;
}
class OSMHotLayer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TileLayerWidget(
options: TileLayerOptions(
// attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Tiles style by <a href="https://www.hotosm.org/" target="_blank">Humanitarian OpenStreetMap Team</a> hosted by <a href="https://openstreetmap.fr/" target="_blank">OpenStreetMap France</a>'
minZoom: 1,
maxZoom: 19,
urlTemplate: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
subdomains: ['a', 'b', 'c'],
retinaMode: MediaQuery.of(context).devicePixelRatio > 1,
),
);
}
}
class StamenTonerLayer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TileLayerWidget(
options: TileLayerOptions(
// attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
minZoom: 1,
maxZoom: 20,
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}{r}.png',
subdomains: ['a', 'b', 'c', 'd'],
retinaMode: MediaQuery.of(context).devicePixelRatio > 1,
),
);
}
}
class StamenWatercolorLayer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return TileLayerWidget(
options: TileLayerOptions(
// attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
minZoom: 1,
maxZoom: 16,
urlTemplate: 'https://stamen-tiles-{s}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg',
subdomains: ['a', 'b', 'c', 'd'],
retinaMode: MediaQuery.of(context).devicePixelRatio > 1,
),
);
}
}

View file

@ -0,0 +1,141 @@
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart';
import 'scalebar_utils.dart' as util;
class ScaleLayerOptions extends LayerOptions {
TextStyle textStyle;
Color lineColor;
double lineWidth;
final EdgeInsets padding;
ScaleLayerOptions({
Key key,
this.textStyle,
this.lineColor = Colors.white,
this.lineWidth = 2,
this.padding,
rebuild,
}) : super(key: key, rebuild: rebuild);
}
class ScaleLayerWidget extends StatelessWidget {
final ScaleLayerOptions options;
ScaleLayerWidget({@required this.options}) : super(key: options.key);
@override
Widget build(BuildContext context) {
final mapState = MapState.of(context);
return ScaleLayer(options, mapState, mapState.onMoved);
}
}
class ScaleLayer extends StatelessWidget {
final ScaleLayerOptions scaleLayerOpts;
final MapState map;
final Stream<Null> stream;
final scale = [
25000000,
15000000,
8000000,
4000000,
2000000,
1000000,
500000,
250000,
100000,
50000,
25000,
15000,
8000,
4000,
2000,
1000,
500,
250,
100,
50,
25,
10,
5,
];
ScaleLayer(this.scaleLayerOpts, this.map, this.stream) : super(key: scaleLayerOpts.key);
@override
Widget build(BuildContext context) {
return StreamBuilder<Null>(
stream: stream,
builder: (context, snapshot) {
var zoom = map.zoom;
var distance = scale[max(0, min(20, zoom.round() + 2))].toDouble();
var center = map.center;
var start = map.project(center);
var targetPoint = util.calculateEndingGlobalCoordinates(center, 90, distance);
var end = map.project(targetPoint);
var displayDistance = distance > 999 ? '${(distance / 1000).toStringAsFixed(0)} km' : '${distance.toStringAsFixed(0)} m';
double width = (end.x - start.x);
return CustomPaint(
painter: ScalePainter(
width,
displayDistance,
lineColor: scaleLayerOpts.lineColor,
lineWidth: scaleLayerOpts.lineWidth,
padding: scaleLayerOpts.padding,
textStyle: scaleLayerOpts.textStyle,
),
);
},
);
}
}
class ScalePainter extends CustomPainter {
ScalePainter(this.width, this.text, {this.padding, this.textStyle, this.lineWidth, this.lineColor});
final double width;
final EdgeInsets padding;
final String text;
TextStyle textStyle;
double lineWidth;
Color lineColor;
@override
void paint(ui.Canvas canvas, ui.Size size) {
final paint = Paint()
..color = lineColor
..strokeCap = StrokeCap.square
..strokeWidth = lineWidth;
var sizeForStartEnd = 4;
var paddingLeft = padding == null ? 0 : padding.left + sizeForStartEnd / 2;
var paddingTop = padding == null ? 0 : padding.top;
var textSpan = TextSpan(style: textStyle, text: text);
var textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr)..layout();
textPainter.paint(canvas, Offset(width / 2 - textPainter.width / 2 + paddingLeft, paddingTop));
paddingTop += textPainter.height;
var p1 = Offset(paddingLeft, sizeForStartEnd + paddingTop);
var p2 = Offset(paddingLeft + width, sizeForStartEnd + paddingTop);
// draw start line
canvas.drawLine(Offset(paddingLeft, paddingTop), Offset(paddingLeft, sizeForStartEnd + paddingTop), paint);
// draw middle line
var middleX = width / 2 + paddingLeft - lineWidth / 2;
canvas.drawLine(Offset(middleX, paddingTop + sizeForStartEnd / 2), Offset(middleX, sizeForStartEnd + paddingTop), paint);
// draw end line
canvas.drawLine(Offset(width + paddingLeft, paddingTop), Offset(width + paddingLeft, sizeForStartEnd + paddingTop), paint);
// draw bottom line
canvas.drawLine(p1, p2, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}

View file

@ -0,0 +1,126 @@
import 'dart:math';
import 'package:latlong/latlong.dart';
const double piOver180 = PI / 180.0;
double toDegrees(double radians) {
return radians / piOver180;
}
double toRadians(double degrees) {
return degrees * piOver180;
}
LatLng calculateEndingGlobalCoordinates(LatLng start, double startBearing, double distance) {
var mSemiMajorAxis = 6378137.0; //WGS84 major axis
var mSemiMinorAxis = (1.0 - 1.0 / 298.257223563) * 6378137.0;
var mFlattening = 1.0 / 298.257223563;
// double mInverseFlattening = 298.257223563;
var a = mSemiMajorAxis;
var b = mSemiMinorAxis;
var aSquared = a * a;
var bSquared = b * b;
var f = mFlattening;
var phi1 = toRadians(start.latitude);
var alpha1 = toRadians(startBearing);
var cosAlpha1 = cos(alpha1);
var sinAlpha1 = sin(alpha1);
var s = distance;
var tanU1 = (1.0 - f) * tan(phi1);
var cosU1 = 1.0 / sqrt(1.0 + tanU1 * tanU1);
var sinU1 = tanU1 * cosU1;
// eq. 1
var sigma1 = atan2(tanU1, cosAlpha1);
// eq. 2
var sinAlpha = cosU1 * sinAlpha1;
var sin2Alpha = sinAlpha * sinAlpha;
var cos2Alpha = 1 - sin2Alpha;
var uSquared = cos2Alpha * (aSquared - bSquared) / bSquared;
// eq. 3
var A = 1 + (uSquared / 16384) * (4096 + uSquared * (-768 + uSquared * (320 - 175 * uSquared)));
// eq. 4
var B = (uSquared / 1024) * (256 + uSquared * (-128 + uSquared * (74 - 47 * uSquared)));
// iterate until there is a negligible change in sigma
double deltaSigma;
var sOverbA = s / (b * A);
var sigma = sOverbA;
double sinSigma;
var prevSigma = sOverbA;
double sigmaM2;
double cosSigmaM2;
double cos2SigmaM2;
for (;;) {
// eq. 5
sigmaM2 = 2.0 * sigma1 + sigma;
cosSigmaM2 = cos(sigmaM2);
cos2SigmaM2 = cosSigmaM2 * cosSigmaM2;
sinSigma = sin(sigma);
var cosSignma = cos(sigma);
// eq. 6
deltaSigma = B * sinSigma * (cosSigmaM2 + (B / 4.0) * (cosSignma * (-1 + 2 * cos2SigmaM2) - (B / 6.0) * cosSigmaM2 * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM2)));
// eq. 7
sigma = sOverbA + deltaSigma;
// break after converging to tolerance
if ((sigma - prevSigma).abs() < 0.0000000000001) break;
prevSigma = sigma;
}
sigmaM2 = 2.0 * sigma1 + sigma;
cosSigmaM2 = cos(sigmaM2);
cos2SigmaM2 = cosSigmaM2 * cosSigmaM2;
var cosSigma = cos(sigma);
sinSigma = sin(sigma);
// eq. 8
var phi2 = atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1.0 - f) * sqrt(sin2Alpha + pow(sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1, 2.0)));
// eq. 9
// This fixes the pole crossing defect spotted by Matt Feemster. When a
// path passes a pole and essentially crosses a line of latitude twice -
// once in each direction - the longitude calculation got messed up.
// Using
// atan2 instead of atan fixes the defect. The change is in the next 3
// lines.
// double tanLambda = sinSigma * sinAlpha1 / (cosU1 * cosSigma - sinU1 *
// sinSigma * cosAlpha1);
// double lambda = Math.atan(tanLambda);
var lambda = atan2(sinSigma * sinAlpha1, (cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1));
// eq. 10
var C = (f / 16) * cos2Alpha * (4 + f * (4 - 3 * cos2Alpha));
// eq. 11
var L = lambda - (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cosSigmaM2 + C * cosSigma * (-1 + 2 * cos2SigmaM2)));
// eq. 12
// double alpha2 = Math.atan2(sinAlpha, -sinU1 * sinSigma + cosU1 *
// cosSigma * cosAlpha1);
// build result
var latitude = toDegrees(phi2);
var longitude = start.longitude + toDegrees(L);
// if ((endBearing != null) && (endBearing.length > 0)) {
// endBearing[0] = toDegrees(alpha2);
// }
latitude = latitude < -90 ? -90 : latitude;
latitude = latitude > 90 ? 90 : latitude;
longitude = longitude < -180 ? -180 : longitude;
longitude = longitude > 180 ? 180 : longitude;
return LatLng(latitude, longitude);
}

View file

@ -0,0 +1,65 @@
import 'package:aves/model/settings.dart';
import 'package:aves/widgets/common/data_providers/media_query_data_provider.dart';
import 'package:aves/widgets/fullscreen/info/location_section.dart';
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MediaQueryDataProvider(
child: DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: Text('Preferences'),
),
body: SafeArea(
child: ListView(
padding: EdgeInsets.all(16),
children: [
Text('Maps'),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('Storage:'),
SizedBox(width: 8),
Flexible(child: InfoMapStyleSelector()),
],
),
],
),
),
),
),
);
}
}
class InfoMapStyleSelector extends StatefulWidget {
@override
_InfoMapStyleSelectorState createState() => _InfoMapStyleSelectorState();
}
class _InfoMapStyleSelectorState extends State<InfoMapStyleSelector> {
@override
Widget build(BuildContext context) {
return DropdownButton<EntryMapStyle>(
items: EntryMapStyle.values
.map((style) => DropdownMenuItem(
value: style,
child: Text(
style.name,
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
))
.toList(),
value: settings.infoMapStyle,
onChanged: (style) {
settings.infoMapStyle = style;
setState(() {});
},
);
}
}

View file

@ -1,6 +1,13 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
ansicolor:
dependency: transitive
description:
name: ansicolor
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
archive:
dependency: transitive
description:
@ -36,6 +43,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
cached_network_image:
dependency: transitive
description:
name: cached_network_image
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0+1"
characters:
dependency: transitive
description:
@ -78,6 +92,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.13"
console_log_handler:
dependency: transitive
description:
name: console_log_handler
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
convert:
dependency: transitive
description:
@ -150,6 +171,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.1"
flutter_ijkplayer:
dependency: "direct main"
description:
@ -159,6 +187,20 @@ packages:
url: "git://github.com/deckerst/flutter_ijkplayer.git"
source: git
version: "0.3.7"
flutter_image:
dependency: transitive
description:
name: flutter_image
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
flutter_map:
dependency: "direct main"
description:
name: flutter_map
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.1+1"
flutter_markdown:
dependency: "direct main"
description:
@ -225,6 +267,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
http:
dependency: transitive
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.2"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.4"
image:
dependency: transitive
description:
@ -246,6 +302,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.2"
latlong:
dependency: "direct main"
description:
name: latlong
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.1"
lists:
dependency: transitive
description:
name: lists
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.6"
logging:
dependency: transitive
description:
@ -274,6 +344,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
mgrs_dart:
dependency: transitive
description:
name: mgrs_dart
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
nested:
dependency: transitive
description:
@ -323,6 +400,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
path_provider:
dependency: transitive
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.11"
path_provider_linux:
dependency: transitive
description:
@ -330,6 +414,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+2"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+3"
path_provider_platform_interface:
dependency: transitive
description:
@ -409,6 +500,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
positioned_tap_detector:
dependency: transitive
description:
name: positioned_tap_detector
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
printing:
dependency: "direct main"
description:
@ -423,6 +521,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.13"
proj4dart:
dependency: transitive
description:
name: proj4dart
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
provider:
dependency: "direct main"
description:
@ -451,6 +556,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.3"
rxdart:
dependency: transitive
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.24.1"
screen:
dependency: "direct main"
description:
@ -575,6 +687,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.17"
transparent_image:
dependency: transitive
description:
name: transparent_image
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
tuple:
dependency: "direct main"
description:
@ -589,6 +708,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
unicode:
dependency: transitive
description:
name: unicode
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.4"
url_launcher:
dependency: "direct main"
description:
@ -638,6 +764,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
validate:
dependency: transitive
description:
name: validate
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
vector_math:
dependency: transitive
description:
@ -645,6 +778,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
wkt_parser:
dependency: transitive
description:
name: wkt_parser
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.7"
xdg_directories:
dependency: transitive
description:

View file

@ -49,6 +49,7 @@ dependencies:
# path: ../flutter_ijkplayer
git:
url: git://github.com/deckerst/flutter_ijkplayer.git
flutter_map:
flutter_markdown:
flutter_native_timezone:
flutter_staggered_animations:
@ -56,6 +57,7 @@ dependencies:
geocoder:
google_maps_flutter:
intl:
latlong: # for flutter_map
outline_material_icons:
package_info:
palette_generator: