diff --git a/lib/gdl90/Id63NexradProduct.dart b/lib/gdl90/Id63NexradProduct.dart deleted file mode 100644 index 1a624e0..0000000 --- a/lib/gdl90/Id63NexradProduct.dart +++ /dev/null @@ -1,7 +0,0 @@ - -import 'nexrad_product.dart'; - -class Id63NexradProduct extends NexradProduct { - - Id63NexradProduct(super.time, super.data); -} \ No newline at end of file diff --git a/lib/gdl90/Id64NexradConusProduct.dart b/lib/gdl90/Id64NexradConusProduct.dart deleted file mode 100644 index 7df57dc..0000000 --- a/lib/gdl90/Id64NexradConusProduct.dart +++ /dev/null @@ -1,9 +0,0 @@ - -import 'nexrad_product.dart'; - -class Id64NexradConusProduct extends NexradProduct { - - // this is conus - Id64NexradConusProduct(super.time, super.data); - -} \ No newline at end of file diff --git a/lib/gdl90/airmet_product.dart b/lib/gdl90/airmet_product.dart new file mode 100644 index 0000000..e420556 --- /dev/null +++ b/lib/gdl90/airmet_product.dart @@ -0,0 +1,10 @@ +import 'package:avaremp/gdl90/product.dart'; + +class AirmetProduct extends Product { + AirmetProduct(super.time, super.line, super.coordinate); + + @override + void parse() { + } + +} \ No newline at end of file diff --git a/lib/gdl90/dlac.dart b/lib/gdl90/dlac.dart new file mode 100644 index 0000000..a6f7f59 --- /dev/null +++ b/lib/gdl90/dlac.dart @@ -0,0 +1,35 @@ +class Dlac { + + static final List _dlacCode = [ + 0x03, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, + 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0X52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x00, 0x09, 0x1e, 0x0a, 0x00, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, + 0x3C, 0x3D, 0x3E, 0x3F + ]; + + static String decode(int b1, int b2, int b3) { + int holder = ((b1 & 0xFF) << 24) + ((b2 & 0xFF) << 16) + ((b3 & 0xFF) << 8); + + /* + * 4 chars in 3 bytes + */ + int firstChar = _dlacCode[((holder & 0xFC000000) >> 26) & 0x3F]; + int secondChar = _dlacCode[((holder & 0x03F00000) >> 20) & 0x3F]; + int thirdChar = _dlacCode[((holder & 0x000FC000) >> 14) & 0x3F]; + int fourthChar = _dlacCode[((holder & 0x00003F00) >> 8) & 0x3F]; + + String output = String.fromCharCodes([firstChar, secondChar, thirdChar, fourthChar]); + return output; + } + + static String format(String input) { + if (input.isNotEmpty) { + input = input.split("\u001E")[0]; + input = input.replaceAll("\n\t[A-Z]{1}", "\n"); /* remove invalid chars after newline */ + } + return input; + } + +} \ No newline at end of file diff --git a/lib/gdl90/fis_buffer.dart b/lib/gdl90/fis_buffer.dart index 10ce99c..a524a3d 100644 --- a/lib/gdl90/fis_buffer.dart +++ b/lib/gdl90/fis_buffer.dart @@ -3,12 +3,14 @@ import 'dart:typed_data'; import 'package:avaremp/gdl90/product.dart'; import 'package:avaremp/gdl90/product_factory.dart'; import 'package:flutter/foundation.dart'; +import 'package:latlong2/latlong.dart'; class FisBuffer { Uint8List buffer; List products = []; - FisBuffer(this.buffer); + FisBuffer(this.buffer, this.coordinate); + LatLng? coordinate; //Parse products out of the Fis void makeProducts() { @@ -33,7 +35,7 @@ class FisBuffer { Uint8List fis = buffer.sublist(count + 2, count + 2 + iFrameLength); try { - Product? p = ProductFactory.buildProduct(fis); + Product? p = ProductFactory.buildProduct(fis, coordinate); if(p != null) { products.add(p); } diff --git a/lib/gdl90/nexrad_cache.dart b/lib/gdl90/nexrad_cache.dart index a9a977a..2acad48 100644 --- a/lib/gdl90/nexrad_cache.dart +++ b/lib/gdl90/nexrad_cache.dart @@ -1,9 +1,8 @@ import 'package:avaremp/constants.dart'; -import 'package:avaremp/gdl90/Id63NexradProduct.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:image/image.dart'; -import 'Id64NexradConusProduct.dart'; +import 'nexrad_medium_product.dart'; import 'nexrad_product.dart'; import 'dart:typed_data'; import 'package:latlong2/latlong.dart'; @@ -49,7 +48,7 @@ class NexradCache { void putImg(NexradProduct product) { - Map map = (product is Id63NexradProduct ? _cacheNexrad : _cacheNexradConus); + Map map = (product is NexradMediumProduct ? _cacheNexrad : _cacheNexradConus); if (product.empty.isNotEmpty) { // Empty, make dummy bitmaps of all. @@ -66,7 +65,7 @@ class NexradCache { img?.discard(); } // put - map[product.block] = NexradImage(product.full, product.block, product is Id64NexradConusProduct); + map[product.block] = NexradImage(product.full, product.block, product is NexradMediumProduct); } // remove expired diff --git a/lib/gdl90/nexrad_high_product.dart b/lib/gdl90/nexrad_high_product.dart new file mode 100644 index 0000000..f7184ce --- /dev/null +++ b/lib/gdl90/nexrad_high_product.dart @@ -0,0 +1,7 @@ + +import 'nexrad_product.dart'; + +class NexradHighProduct extends NexradProduct { + + NexradHighProduct(super.time, super.data, super.coordinate); +} \ No newline at end of file diff --git a/lib/gdl90/nexrad_medium_product.dart b/lib/gdl90/nexrad_medium_product.dart new file mode 100644 index 0000000..e870d4d --- /dev/null +++ b/lib/gdl90/nexrad_medium_product.dart @@ -0,0 +1,8 @@ + +import 'nexrad_product.dart'; + +class NexradMediumProduct extends NexradProduct { + + NexradMediumProduct(super.time, super.data, super.coordinate); + +} \ No newline at end of file diff --git a/lib/gdl90/nexrad_product.dart b/lib/gdl90/nexrad_product.dart index cd3fc8b..7693cb2 100644 --- a/lib/gdl90/nexrad_product.dart +++ b/lib/gdl90/nexrad_product.dart @@ -1,7 +1,7 @@ import 'package:avaremp/gdl90/product.dart'; class NexradProduct extends Product { - NexradProduct(super.time, super.data); + NexradProduct(super.time, super.line, super.coordinate); static const int numRows = 4; static const int numCols = 32; @@ -25,12 +25,12 @@ class NexradProduct extends Product { void parse() { // Get blocks, skip first 3. - bool elementIdentifier = ((data[0]).toInt() & 0x80) != 0; // RLE or Empty? + bool elementIdentifier = ((line[0]).toInt() & 0x80) != 0; // RLE or Empty? - int len = data.lengthInBytes; - block = (data[0].toInt() & 0x0F) << 16; - block += (data[1].toInt() & 0xFF) << 8; - block += data[2].toInt() & 0xFF; + int len = line.lengthInBytes; + block = (line[0].toInt() & 0x0F) << 16; + block += (line[1].toInt() & 0xFF) << 8; + block += line[2].toInt() & 0xFF; int index = 3; @@ -44,13 +44,13 @@ class NexradProduct extends Product { int j = 0; int i; while (index < len) { - int numberOfBins = ((data[index].toInt() & 0xF8) >> 3) + 1; + int numberOfBins = ((line[index].toInt() & 0xF8) >> 3) + 1; for (i = 0; i < numberOfBins; i++) { if (j >= full.length) { full = []; return; } - full[j] = _intensity[(data[index].toInt() & 0x07)]; + full[j] = _intensity[(line[index].toInt() & 0x07)]; j++; } index++; @@ -61,54 +61,54 @@ class NexradProduct extends Product { full = []; empty = []; empty.add(block); - int bitmapLen = data[index].toInt() & 0x0F; + int bitmapLen = line[index].toInt() & 0x0F; - if ((data[index].toInt() & 0x10) != 0) { + if ((line[index].toInt() & 0x10) != 0) { empty.add(block + 1); } - if ((data[index].toInt() & 0x20) != 0) { + if ((line[index].toInt() & 0x20) != 0) { empty.add(block + 2); } - if ((data[index].toInt() & 0x30) != 0) { + if ((line[index].toInt() & 0x30) != 0) { empty.add(block + 3); } - if ((data[index].toInt() & 0x40) != 0) { + if ((line[index].toInt() & 0x40) != 0) { empty.add(block + 4); } for (int i = 1; i < bitmapLen; i++) { - if ((data[index + i].toInt() & 0x01) != 0) { + if ((line[index + i].toInt() & 0x01) != 0) { empty.add(block + i * 8 - 3); } - if ((data[index + i].toInt() & 0x02) != 0) { + if ((line[index + i].toInt() & 0x02) != 0) { empty.add(block + i * 8 - 2); } - if ((data[index + i].toInt() & 0x04) != 0) { + if ((line[index + i].toInt() & 0x04) != 0) { empty.add(block + i * 8 - 1); } - if ((data[index + i].toInt() & 0x08) != 0) { + if ((line[index + i].toInt() & 0x08) != 0) { empty.add(block + i * 8 - 0); } - if ((data[index + i].toInt() & 0x10) != 0) { + if ((line[index + i].toInt() & 0x10) != 0) { empty.add(block + i * 8 + 1); } - if ((data[index + i].toInt() & 0x20) != 0) { + if ((line[index + i].toInt() & 0x20) != 0) { empty.add(block + i * 8 + 2); } - if ((data[index + i].toInt() & 0x40) != 0) { + if ((line[index + i].toInt() & 0x40) != 0) { empty.add(block + i * 8 + 3); } - if ((data[index + i].toInt() & 0x80) != 0) { + if ((line[index + i].toInt() & 0x80) != 0) { empty.add(block + i * 8 + 4); } } diff --git a/lib/gdl90/notam_product.dart b/lib/gdl90/notam_product.dart new file mode 100644 index 0000000..178d60b --- /dev/null +++ b/lib/gdl90/notam_product.dart @@ -0,0 +1,10 @@ +import 'package:avaremp/gdl90/product.dart'; + +class NotamProduct extends Product { + NotamProduct(super.time, super.line, super.coordinate); + + @override + void parse() { + } + +} \ No newline at end of file diff --git a/lib/gdl90/product.dart b/lib/gdl90/product.dart index 63cf2f0..878c6f4 100644 --- a/lib/gdl90/product.dart +++ b/lib/gdl90/product.dart @@ -1,11 +1,14 @@ import 'dart:typed_data'; +import 'package:latlong2/latlong.dart'; + class Product { final DateTime time; - final Uint8List data; + final Uint8List line; + final LatLng? coordinate; - Product(this.time, this.data); + Product(this.time, this.line, this.coordinate); void parse() { diff --git a/lib/gdl90/product_factory.dart b/lib/gdl90/product_factory.dart index a29927b..ccdfb28 100644 --- a/lib/gdl90/product_factory.dart +++ b/lib/gdl90/product_factory.dart @@ -1,13 +1,18 @@ import 'dart:typed_data'; import 'package:avaremp/gdl90/product.dart'; +import 'package:avaremp/gdl90/sigmet_product.dart'; +import 'package:avaremp/gdl90/sua_product.dart'; import 'package:flutter/foundation.dart'; - -import 'Id63NexradProduct.dart'; -import 'Id64NexradConusProduct.dart'; +import 'package:latlong2/latlong.dart'; +import 'airmet_product.dart'; +import 'textual_weather_product.dart'; +import 'nexrad_high_product.dart'; +import 'nexrad_medium_product.dart'; +import 'notam_product.dart'; class ProductFactory { - static Product? buildProduct(Uint8List fis) { + static Product? buildProduct(Uint8List fis, LatLng? coordinate) { BitInputStream s = BitInputStream(fis); bool flagAppMethod = s.getBits(1) != 0; @@ -60,24 +65,29 @@ class ProductFactory { switch (productID) { case 8: + p = NotamProduct(time, data, coordinate); // NOTAM graphics break; case 9: break; case 10: break; case 11: + p = AirmetProduct(time, data, coordinate); // AIRMET graphics break; case 12: + p = SigmetProduct(time, data, coordinate); // SIGMET graphics break; case 13: + p = SuaProduct(time, data, coordinate); // SUA graphics break; case 63: - p = Id63NexradProduct(time, data); + p = NexradHighProduct(time, data, coordinate); break; case 64: - p = Id64NexradConusProduct(time, data); + p = NexradMediumProduct(time, data, coordinate); break; case 413: + p = TextualWeatherProduct(time, data, coordinate); // MEATR, TAF, SPECI, WINDS, PIREP break; default: break; diff --git a/lib/gdl90/sigmet_product.dart b/lib/gdl90/sigmet_product.dart new file mode 100644 index 0000000..3c128b1 --- /dev/null +++ b/lib/gdl90/sigmet_product.dart @@ -0,0 +1,10 @@ +import 'package:avaremp/gdl90/product.dart'; + +class SigmetProduct extends Product { + SigmetProduct(super.time, super.line, super.coordinate); + + @override + void parse() { + } + +} \ No newline at end of file diff --git a/lib/gdl90/sua_product.dart b/lib/gdl90/sua_product.dart new file mode 100644 index 0000000..f69a578 --- /dev/null +++ b/lib/gdl90/sua_product.dart @@ -0,0 +1,10 @@ +import 'package:avaremp/gdl90/product.dart'; + +class SuaProduct extends Product { + SuaProduct(super.time, super.line, super.coordinate); + + @override + void parse() { + } + +} \ No newline at end of file diff --git a/lib/gdl90/textual_weather_product.dart b/lib/gdl90/textual_weather_product.dart new file mode 100644 index 0000000..3d958e4 --- /dev/null +++ b/lib/gdl90/textual_weather_product.dart @@ -0,0 +1,153 @@ +import 'package:avaremp/gdl90/product.dart'; +import 'package:avaremp/weather/metar.dart'; +import 'package:avaremp/weather/weather.dart'; +import 'package:avaremp/weather/winds_aloft.dart'; + +import '../weather/airep.dart'; +import '../weather/taf.dart'; +import 'dlac.dart'; + +class TextualWeatherProduct extends Product { + TextualWeatherProduct(super.time, super.line, super.coordinate); + + String text = ""; + Weather? weather; + + @override + void parse() { + + int len = line.length; + + // Decode text: begins with @METAR, @TAF, @SPECI, @PIREP, @WINDS + for (int i = 0; i < (len - 3); i += 3) { + text += Dlac.decode(line[i + 0], line[i + 1], line[i + 2]); + } + + text = Dlac.format(text); + List parts = text.split(" "); + if(parts.length < 3) { + return; + } + String type = parts[0]; + String place = parts[1]; + String report = parts.sublist(1).join(" "); + + switch(type) { + case "TAF": + case "TAF.AMD": + _parseTaf(place, report); + break; + case "METAR": + case "SPECI": + _parseMetarSpeci(place, report); + break; + case "PIREP": + _parsePirep(place, report); + break; + case "WINDS": + _parseWinds(place, report); + break; + } + } + + void _parseTaf(String place, String report) { + Taf taf = Taf(place, DateTime.now(), report); + weather = taf; + } + + void _parseMetarSpeci(String place, String report) { + if(null != coordinate) { + Metar metar = Metar(place, DateTime.now(), text, "", coordinate!); + weather = metar; + } + } + void _parsePirep(String place, String report) { + if(null != coordinate) { + Airep airep = Airep(place, DateTime.now(), report, coordinate!); + weather = airep; + } + } + void _parseWinds(String place, String report) { + List tokens = report.split("\n"); + if (tokens.length < 2) { + /* + * Must have line like + * MSY 230000Z FT 3000 6000 F9000 C12000 G18000 C24000 C30000 D34000 39000 Y + * and second line like + * 1410 2508+10 2521+07 2620+01 3037-12 3041-26 304843 295251 29765 + */ + return; + } + + try { + // this is lots of parsing with chances of exceptions + tokens[0] = tokens[0].replaceAll("\\s+", " "); + tokens[1] = tokens[1].replaceAll("\\s+", " "); + List winds = tokens[1].split(" "); + List alts = tokens[0].split(" "); + String w3k = ""; + String w6k = ""; + String w9k = ""; + String w12k = ""; + String w18k = ""; + String w24k = ""; + String w30k = ""; + String w34k = ""; + String w39k = ""; + + /* + * Start from 3rd entry - alts + */ + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("3000") && !alts[i].contains("30000")) { + w3k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("6000")) { + w6k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("9000") && !alts[i].contains("39000")) { + w9k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("12000")) { + w12k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("18000")) { + w18k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("24000")) { + w24k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("30000")) { + w30k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("34000")) { + w34k = winds[i - 2]; + } + } + for (int i = 2; i < alts.length; i++) { + if (alts[i].contains("39000")) { + w39k = winds[i - 2]; + } + } + WindsAloft wa = WindsAloft(place, DateTime.now(), w3k, w6k, w9k, w12k, w18k, w24k, w30k, w34k, w39k); + weather = wa; + } + catch (e) {} + } + +} + diff --git a/lib/gdl90/uplink_message.dart b/lib/gdl90/uplink_message.dart index ed514dc..4561385 100644 --- a/lib/gdl90/uplink_message.dart +++ b/lib/gdl90/uplink_message.dart @@ -1,5 +1,7 @@ import 'dart:typed_data'; +import 'package:latlong2/latlong.dart'; + import 'fis_buffer.dart'; import 'message.dart'; @@ -49,24 +51,16 @@ class UplinkMessage extends Message { degLon = -1 * (180 - degLon); } - bool positionValid = (message[skip + 5].toInt() & 0x01) != 0; - bool applicationDataValid = (message[skip + 6].toInt() & 0x20) != 0; if (false == applicationDataValid) { return; } - // byte 6, bits 4-8: slot ID - int slotID = message[skip + 6].toInt() & 0x1f; - - // byte 7, bit 1-4: TIS-B site ID. If zero, the broadcasting station is not broadcasting TIS-B data - int tisbSiteID = (message[skip + 7].toInt() & 0xF0) >> 4; - // byte 9-432: application data (multiple iFrames). skip = 3 + 8; Uint8List data = message.sublist(skip); - FisBuffer fisBuffer = FisBuffer(data); + FisBuffer fisBuffer = FisBuffer(data, LatLng(degLat, degLon)); // this product does not happen at a location? //Now decode all. fisBuffer.makeProducts(); diff --git a/lib/map_screen.dart b/lib/map_screen.dart index da3f24a..19c1118 100644 --- a/lib/map_screen.dart +++ b/lib/map_screen.dart @@ -105,14 +105,18 @@ class MapScreenState extends State { LatLng cur = Gps.toLatLng(Storage().position); _previousPosition ??= cur; if(null != _controller) { - LatLng diff = LatLng(cur.latitude - _previousPosition!.latitude, - cur.longitude - _previousPosition!.longitude); - LatLng now = _controller!.camera.center; - LatLng next = LatLng( - now.latitude + diff.latitude, now.longitude + diff.longitude); - if (!_interacting) { // do not move when user is moving map - _controller!.moveAndRotate(next, _controller!.camera.zoom, _northUp ? 0 : -Storage().position.heading); + try { + LatLng diff = LatLng(cur.latitude - _previousPosition!.latitude, + cur.longitude - _previousPosition!.longitude); + LatLng now = _controller!.camera.center; + LatLng next = LatLng( + now.latitude + diff.latitude, now.longitude + diff.longitude); + if (!_interacting) { // do not move when user is moving map + _controller!.moveAndRotate(next, _controller!.camera.zoom, + _northUp ? 0 : -Storage().position.heading); + } } + catch (e) {} // addign to lat lon is dangerous } _previousPosition = Gps.toLatLng(Storage().position);