Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/area.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class Area {
// get surface wind from nearest airport
String wind = WindsCache.getWind0kFromMetar(Gps.toLatLng(position));
// get aloft wind from nearest station
String? station = WindsCache.locateNearestStation(Gps.toLatLng(position));
var (station, dist, bearing) = WindsCache.locateNearestStation(Gps.toLatLng(position));
WindsAloft? wa = Storage().winds.get("${station}06H") as WindsAloft?;
if(null != wa) {
// combine surface and aloft wind
Expand All @@ -55,4 +55,4 @@ class Area {
}
return (direction, speed);
}
}
}
16 changes: 8 additions & 8 deletions lib/geo_calculations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,27 @@ class GeoCalculations {
String dir;
double bearing = getMagneticHeading(bearingIn, declination);
if ((bearing > dirDegrees) && (bearing <= (90 - dirDegrees))) {
dir = "SW of";
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an issue as the direction is from the point touched. Of is required you are off of this heading

dir = "SW";
} else {
if ((bearing > (90 - dirDegrees)) && (bearing <= (90 + dirDegrees))) {
dir = "W of";
dir = "W";
} else {
if ((bearing > (90 + dirDegrees)) && (bearing <= (180 - dirDegrees))) {
dir = "NW of";
dir = "NW";
} else {
if ((bearing > (180 - dirDegrees)) && (bearing <= (180 + dirDegrees))) {
dir = "N of";
dir = "N";
} else {
if ((bearing > (180 + dirDegrees)) && (bearing <= (270 - dirDegrees))) {
dir = "NE of";
dir = "NE";
} else {
if ((bearing > (270 - dirDegrees)) && (bearing <= (270 + dirDegrees))) {
dir = "E of";
dir = "E";
} else {
if ((bearing > (270 + dirDegrees)) && (bearing <= (360 - dirDegrees))) {
dir = "SE of";
dir = "SE";
} else {
dir = "S of";
dir = "S";
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions lib/longpress_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class LongPressScreenState extends State<LongPressScreen> {
LatLng ll = LatLng(Storage().position.latitude, Storage().position.longitude);
double distance = geo.calculateDistance(ll, widget.destinations[0].coordinate);
double bearing = geo.calculateBearing(ll, widget.destinations[0].coordinate);
String direction = ("${distance.round()} ${GeoCalculations.getGeneralDirectionFrom(bearing, Storage().area.variation)}");
String direction = ("${distance.round()} ${Storage().units.distanceName} ${GeoCalculations.getGeneralDirectionFrom(bearing, Storage().area.variation)}");
String facility = showDestination.facilityName.length > 16 ? showDestination.facilityName.substring(0, 16) : showDestination.facilityName;
List<Widget?> pages = List.generate(labels.length, (index) => null);
String label = "$facility (${showDestination.locationID}) $direction${showDestination.elevation != null ? "; EL ${showDestination.elevation!.round()}" : ""}";
Expand All @@ -123,7 +123,7 @@ class LongPressScreenState extends State<LongPressScreen> {
]);
}
pages[labels.indexOf("NOTAM")] = FutureBuilder(future: Storage().notam.getSync(showDestination.locationID),
builder: (context, snapshot) { // notmas are downloaded when not in cache and can be slow to download so do async
builder: (context, snapshot) { // notams are downloaded when not in cache and can be slow to download so do async
if (snapshot.hasData) {
return snapshot.data != null ?
SingleChildScrollView(child: Padding(padding: const EdgeInsets.all(10), child:Text((snapshot.data as Notam).text)))
Expand Down Expand Up @@ -159,13 +159,13 @@ class LongPressScreenState extends State<LongPressScreen> {
}

Weather? winds;
String? station = WindsCache.locateNearestStation(showDestination.coordinate);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work with weather received from ADSB?

var (station, dist, stationBearing) = WindsCache.locateNearestStation(showDestination.coordinate);
if(station != null) {
winds = Storage().winds.get("${station}06H"); // 6HR wind
if(winds != null) {
WindsAloft wa = winds as WindsAloft;
pages[labels.indexOf("Wind")] = ListView(children: [
ListTile(title: Text(winds.toString())),
ListTile(title: Text('${dist.round()} ${Storage().units.distanceName} ${GeoCalculations.getGeneralDirectionFrom(stationBearing, 0)} @ ${winds.toString()}')),
for((String, String) wl in wa.toList())
ListTile(leading: Text(wl.$1), title: Text(wl.$2)),
]);
Expand Down
25 changes: 25 additions & 0 deletions lib/time_zone.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

class TimeZone {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a package for it 'tz' in pubspec.yaml.

//calculates the timestamp from the passed Zulu time in METAR format (e.g. DDHHMMZ)
//any trailing values after the Z are disregarded
//if there isn't 7 characters to create a Zulu time, or letters in first six locations, returns null
static DateTime? parseZuluTime(String s)
{
if(s.length < 7 || int.tryParse(s.substring(0,5)) == null)
{
return null;
}
DateTime now = DateTime.now().toUtc();
DateTime expires = DateTime.utc(
now.year,
now.month,
now.day, //day
0,
0);
int from = int.parse(s[2]!);
int to = int.parse(s[3]!);
// if from > to then its next day
expires = expires.add(Duration(days: to < from ? 1 : 0, hours: int.parse(s[3]!.substring(0, 2)), minutes: int.parse(s[5]!.substring(0, 2))));
return expires;
}
}
6 changes: 5 additions & 1 deletion lib/unit_conversion.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class UnitConversion {
mpsTo = 2.23694;
toMps = 0.44704;
knotsTo = 1.15078;
distanceName = 'mi';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have all the places that use this been added this info? For example measuring tool?

}
}

Expand All @@ -28,4 +29,7 @@ class UnitConversion {
// knots for wind.
double knotsTo = 1;

}
//name of distance unit
String distanceName = 'nm';

}
42 changes: 32 additions & 10 deletions lib/weather/sounding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,27 @@ import '../geo_calculations.dart';

class Sounding {

static String? _locateNearestStation(LatLng location) {
static (String?, double, double) _locateNearestStation(LatLng location) {

// find distance
GeoCalculations geo = GeoCalculations();
double distanceMin = double.maxFinite;
String? station;
LatLng? stationLocation;
for(MapEntry<String, LatLng> map in _stationMap.entries) {
double distance = geo.calculateDistance(map.value, location);
if(distance < distanceMin) {
distanceMin = distance;
station = map.key;
stationLocation = map.value;
}
}
return station;
double? bearing;
if(stationLocation != null)
{
bearing = geo.calculateBearing(stationLocation, location);
}
return (station, distanceMin, bearing ?? 0);
}

static Widget? getSoundingImage(LatLng coordinate, BuildContext context) {
Expand All @@ -29,20 +36,35 @@ class Sounding {
return const Center(child: Text('Error downloading the Sounding Analysis for this area.'));
}

String? station = _locateNearestStation(coordinate);
var (station, dist, bearing) = _locateNearestStation(coordinate);
if(null == station) {
return null;
}
DateTime now = DateTime.now().toUtc();
DateTime now = DateTime.timestamp();
now = now.subtract(const Duration(hours: 1)); // 1 hour delayed on website
String hour = ((now.hour / 12).floor() * 12).toString().padLeft(2, '0');
String year = now.year.toString().substring(2);
String day = now.day.toString().padLeft(2, '0');
String month = now.month.toString().padLeft(2, '0');
DateTime obsTime = DateTime.utc(now.year, now.month, now.day, (now.hour/ 12).floor() * 12);
String hour = obsTime.hour.toString().padLeft(2, '0');
String year = obsTime.year.toString().substring(2);
String day = obsTime.day.toString().padLeft(2, '0');
String month = obsTime.month.toString().padLeft(2, '0');
String url = "https://www.spc.noaa.gov/exper/soundings/$year$month$day${hour}_OBS/$station.gif";
Duration timeSinceObs = DateTime.timestamp().difference(obsTime);
CachedNetworkImage image = CachedNetworkImage(imageUrl: url, cacheManager: FileCacheManager().networkCacheManager, errorWidget: errorImage,);
return Container(padding: const EdgeInsets.all(10), child:
InteractiveViewer(child: Container(color: Colors.white , alignment: Alignment.center, child: image)));
return ListView(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stack is a better widget here

children: <Widget>[
ListTile(title: Text("${dist.round()} ${Storage().units.distanceName} ${GeoCalculations.getGeneralDirectionFrom(bearing, 0)} @ ${station} (${timeSinceObs.inHours}:${(timeSinceObs.inMinutes % 60).toString().padLeft(2, '0')} ago)")),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: InteractiveViewer(
child: Container(
color: Colors.white,
alignment: Alignment.center,
child: image
)
)
)
]
);
}

// List of station codes from the HTML area elements
Expand Down
12 changes: 8 additions & 4 deletions lib/weather/winds_aloft.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class WindsAloft extends Weather {
return(dir, (speed.toDouble() * Storage().units.knotsTo).round());
}

static String invertTemperature(String wind) {
return wind.replaceRange(4,4,'-');
}

(double?, double?) getWindAtAltitude(double altitude) { // dir, speed
String wHigher;
String wLower;
Expand Down Expand Up @@ -176,13 +180,13 @@ class WindsAloft extends Weather {
return w24k;
}
else if (altitude == 30000) {
return w30k;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing as pilots are used to seeing negative temps.

return invertTemperature(w30k);
}
else if (altitude == 34000) {
return w34k;
return invertTemperature(w34k);
}
else if (altitude == 39000) {
return w39k;
return invertTemperature(w39k);
}
return "N/A";
}
Expand Down Expand Up @@ -242,7 +246,7 @@ class WindsAloft extends Weather {
toString() {
DateTime zulu = expires.toUtc(); // winds in Zulu time
// boilerplate
String wind = "$station (Temps negative above 24000)\nValid till ${zulu.day .toString().padLeft(2, "0")}${zulu.hour.toString().padLeft(2, "0")}00Z";
String wind = "$station\nValid till ${expires.hour.toString().padLeft(2, '0')}:${expires.minute.toString().padLeft(2, '0')} (${zulu.day.toString().padLeft(2, '0')}${zulu.hour.toString().padLeft(2, '0')}00Z)";
return wind;
}
}
Expand Down
30 changes: 26 additions & 4 deletions lib/weather/winds_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:typed_data';

import 'package:avaremp/data/weather_database_helper.dart';
import 'package:avaremp/geo_calculations.dart';
import 'package:avaremp/time_zone.dart';
import 'package:avaremp/storage.dart';
import 'package:avaremp/weather/weather_cache.dart';
import 'package:avaremp/weather/winds_aloft.dart';
Expand Down Expand Up @@ -34,7 +35,7 @@ class WindsCache extends WeatherCache {
double? ws;
double? wd;
String foreString = fore.toString().padLeft(2, "0"); // stations has fore in the name
String? station = WindsCache.locateNearestStation(coordinate);
var (station, dist, bearing) = WindsCache.locateNearestStation(coordinate);
var ww = Storage().winds.get("$station${foreString}H");
(wd, ws) = WindsCache.getWindAtAltitude(altitude, ww == null ? null : ww as WindsAloft);
return (wd, ws);
Expand All @@ -50,12 +51,22 @@ class WindsCache extends WeatherCache {

for(Uint8List datum in data)
{
RegExp exp1 = RegExp("VALID\\s*([0-9]*)Z\\s*FOR USE\\s*([0-9]*)-([0-9]*)Z");
String dataString = utf8.decode(datum);

// parse winds, set expire time 6 hrs in future
DateTime expires = DateTime.now().add(const Duration(hours: 6));
DateTime? expires;
List<String> lines = dataString.split('\n');

for (String line in lines) {
line = line.trim();
RegExpMatch? match = exp1.firstMatch(line);
if (match != null) {
expires = TimeZone.parseZuluTime(match.toString());
break;
}
}

bool start = false;
// parse winds, first check if a new download is needed
_WindsAloftProduct? p = _getWindsAloftProductType(lines);
Expand All @@ -70,6 +81,9 @@ class WindsCache extends WeatherCache {
if(!start) {
continue;
}
if(expires == null) {
continue;
}

// find fore in winds. Stations are suffixes with 06H 12H or 24H for 6 12 and 24 hour forecasts
String fore = p.toString().substring(p.toString().length - 3);
Expand Down Expand Up @@ -184,19 +198,27 @@ class WindsCache extends WeatherCache {
return null;
}

static String? locateNearestStation(LatLng location) {
//returns station name, distance, bearing
static (String?, double, double) locateNearestStation(LatLng location) {
// find distance
GeoCalculations geo = GeoCalculations();
double distanceMin = double.maxFinite;
String? station;
LatLng? stationLocation;
for(MapEntry<String, LatLng> map in _stationMap.entries) {
double distance = geo.calculateDistance(map.value, location);
if(distance < distanceMin) {
distanceMin = distance;
station = map.key;
stationLocation = map.value;
}
}
return station;
double? bearing;
if(stationLocation != null)
{
bearing = geo.calculateBearing(stationLocation, location);
}
return (station, distanceMin, bearing ?? 0);
}

// dir, speed
Expand Down