stratux/dump978/extract_nexrad.c

316 wiersze
12 KiB
C

//
// Copyright 2015, Oliver Jowett <oliver@mutability.co.uk>
//
// This file is free software: you may copy, redistribute and/or modify it
// under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 2 of the License, or (at your
// option) any later version.
//
// This file is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <stdio.h>
#include <math.h>
#include "uat.h"
#include "uat_decode.h"
#include "reader.h"
#define BLOCK_WIDTH (48.0/60.0)
#define WIDE_BLOCK_WIDTH (96.0/60.0)
#define BLOCK_HEIGHT (4.0/60.0)
#define BLOCK_THRESHOLD 405000
#define BLOCKS_PER_RING 450
//
// This reads demodulated uplink messages and extracts NEXRAD global block representation formats - type 63 and 64
//
// The output format is a series of lines, one line per decoded block.
// Each line is space-separated and is formatted as:
//
// NEXRAD <type> <hour>:<minute> <scale> <north> <west> <height> <width> <data>
//
// where:
// <type> is Regional (for type 63) or CONUS (for type 64)
// <hour>:<minute> is the time from the PDU header - all blocks from one composite radar image will have the same time
// <scale> is the scale value of this block - 0 (high res), 1 (med res), or 2 (low res)
// <north> is the north edge of the block, in _integer arcminutes_. Divide by 60 to get degrees.
// <west> is the west edge of the block, in _positive integer arcminutes_. Divide by 60 to get degrees; subtract 360 if you want the conventional -180..+180 range.
// <height> is the height of the block, in integer arcminutes of latitude
// <width> is the width of the block, in integer arcminutes of longitude
//
// Each block contains 128 evenly spaced bins, in a grid of 32 (longitude) x 4 (latitude), working west-to-east then north-to-south.
// i.e. each bin represents a pixel that covers <width>/32 arcminutes of longitude by <height>/4 arcminutes of latitude.
//
// <data> is a string of 128 digits (no spaces); each character represents the intensity of one bin, in the order above.
//
// Given:
// bn - block number
// ns - north/south flag
// sf - scale factor
//
// compute the northwest corner of the referenced block and place it in (*latN, *lonW)
// and place the total height and width of the block in (*latSize, *lonSize)
void block_location(int bn, int ns, int sf, double *latN, double *lonW, double *latSize, double *lonSize)
{
// With SF=0:
// blocks are (48 arcminutes longitude) x (4 arcminute latitude) between 0 and 60 degrees latitude
// (450 blocks for each ring of latitude)
// blocks are (96 arcminutes longitude) x (4 arcminute latitude) between 60 and 90 degrees latitude
// (225 blocks for each ring of latitude) - but the block numbering continues to use
// a 48-arcminute spacing, so only even numbered blocks are meaningful.
// block zero is immediately northeast of (0,0), then blocks are numbered east-to-west, south-to-north.
//
// Southern hemisphere numbering is mirrored around the equator, and indicated by the "ns" flag.
// ^N
// | 405446 | 405448 | 405000 | 405002 |
// --------------------------------------------------------- 60 00' 00" N
// |404996|404997|404998|404999|404550|404551|404552|404553|
// --------------------------------------------------------- 59 56' 00" N
// ...
// | 896 | 897 | 898 | 899 | 450 | 451 | 452 | 453 |
// --------------------------------------------------------- 00 04' 00" N
// | 446 | 447 | 447 | 449 | 0 | 1 | 2 | 3 |
//W<------------------------------------------------------->E equator
// | 446* | 447* | 447* | 449* | 0* | 1* | 2* | 3* |
// --------------------------------------------------------- 00 04' 00" S
// | 896* | 897* | 898* | 899* | 450* | 451* | 452* | 453* |
// 2d24'W 1d36'W 0d48'W V 0d48'E 1d36'E 2d24'E
// (* = ns_flag set)
// Each block is subdivided into 32 (longitude) x 4 (latitude) bins.
// The bins are numbered starting at the northwest corner of the block,
// west-to-east then north-to-south.
// block 0:
//
// ------------------------------------ <- 0d04m00s N
// | 0 1 2 3 ... 28 29 30 31| <- each bin is 1 arcminute tall
// | 32 33 34 35 ... 60 61 62 63|
// | 64 65 66 67 ... 92 93 94 95|
// | 96 97 98 99 ... 124 125 126 127|
// ------------------------------------ <- 0N - equator
// ^ ^ each bin is ^
// 0E 1.5 arcminute wide 0d48m00s E
// With SF=1, an identical block numbering is used to locate the northwest corner of the block,
// but then each bin is 5x larger in both axes i.e. 240 x 20 or 480 x 20 arcminutes.
// this means that the block data will actually overlap 24 other block positions.
// With SF=2, it works like SF=1 but with a scale factor of 9x.
double raw_lat, raw_lon;
double scale;
if (sf == 1)
scale = 5.0;
else if (sf == 2)
scale = 9.0;
else
scale = 1.0;
if (bn >= BLOCK_THRESHOLD) {
// 60-90 degrees - even-numbered blocks only
bn = bn & ~1;
}
raw_lat = BLOCK_HEIGHT * trunc(bn / BLOCKS_PER_RING);
raw_lon = (bn % BLOCKS_PER_RING) * BLOCK_WIDTH;
*lonSize = (bn >= BLOCK_THRESHOLD ? WIDE_BLOCK_WIDTH : BLOCK_WIDTH) * scale;
*latSize = BLOCK_HEIGHT * scale;
// raw_lat/raw_lon points to the southwest corner in the northern hemisphere version
*lonW = raw_lon;
if (ns) {
// southern hemisphere, mirror along the equator
*latN = 0 - raw_lat;
} else {
// adjust to the northwest corner
*latN = raw_lat + BLOCK_HEIGHT;
}
}
void decode_nexrad(struct fisb_apdu *fisb)
{
// Header:
//
// byte/bit 7 6 5 4 3 2 1 0
// 0 |RLE|NS | Scale | MSB Block # |
// 1 | Block # |
// 2 | Block # LSB |
int rle_flag = (fisb->data[0] & 0x80) != 0;
int ns_flag = (fisb->data[0] & 0x40) != 0;
int block_num = ((fisb->data[0] & 0x0f) << 16) | (fisb->data[1] << 8) | (fisb->data[2]);
int scale_factor = (fisb->data[0] & 0x30) >> 4;
// now decode the bins
if (rle_flag) {
// One bin, 128 values, RLE-encoded
int i;
double latN = 0, lonW = 0, latSize = 0, lonSize = 0;
block_location(block_num, ns_flag, scale_factor, &latN, &lonW, &latSize, &lonSize);
fprintf(stdout, "NEXRAD %s %02d:%02d %d %.0f %.0f %.0f %.0f ",
fisb->product_id == 63 ? "Regional" : "CONUS",
fisb->hours,
fisb->minutes,
scale_factor,
latN * 60,
lonW * 60,
latSize * 60,
lonSize * 60);
// each byte following the header is:
// 7 6 5 4 3 2 1 0
// | runlength - 1 | intensity |
for (i = 3; i < fisb->length; ++i) {
int intensity = fisb->data[i] & 7;
int runlength = (fisb->data[i] >> 3) + 1;
while (runlength-- > 0)
fprintf(stdout, "%d", intensity);
}
fprintf(stdout, "\n");
} else {
int L = fisb->data[3] & 15;
int i;
int row_start, row_offset, row_size;
//
// Empty block representation, representing one
// or more blocks that are completely empty of
// data.
//
// 7 6 5 4 3 2 1 0
// 3 |b+4 |b+3 |b+2 |b+1 | length (L) |
// 4 |b+12|b+11|b+10|b+9 |b+8 |b+7 |b+6 |b+5 |
// ...
// 3+L |b+8L-3 ... b+8L+4|
// The block number from the header is always
// empty.
//
// If the bit for b+x is empty, then the block
// X to the right of the block from the header is
// empty. Note that the block is _always on the
// same row_ even if the offset would make the
// block cross the 0E meridian, so it is not simply
// a case of adding to the block number.
// find the lowest-numbered block of this row
if (block_num >= 405000) {
row_start = block_num - ((block_num - 405000) % 225);
row_size = 225;
} else {
row_start = block_num - (block_num % 450);
row_size = 450;
}
// find the offset of the first block in this row handled
// by this message
row_offset = block_num - row_start;
for (i = 0; i < L; ++i) {
int bb;
int j;
if (i == 0)
bb = (fisb->data[3] & 0xF0) | 0x08; // synthesize a first byte in the same format as all the other bytes
else
bb = (fisb->data[i+3]);
for (j = 0; j < 8; ++j) {
if (bb & (1 << j)) {
// find the relevant block for this bit, limited
// to the same row as the original block.
int row_x = (row_offset + 8*i + j - 3) % row_size;
int bn = row_start + row_x;
double latN = 0, lonW = 0, latSize = 0, lonSize = 0;
int k;
block_location(bn, ns_flag, scale_factor, &latN, &lonW, &latSize, &lonSize);
fprintf(stdout, "NEXRAD %s %02d:%02d %d %.0f %.0f %.0f %.0f ",
fisb->product_id == 63 ? "Regional" : "CONUS",
fisb->hours,
fisb->minutes,
scale_factor,
latN * 60,
lonW * 60,
latSize * 60,
lonSize * 60);
// seems to work best if we assume that
// CONUS empty blocks = intensity 1 (valid data, but no precipitation)
// regional empty blocks = intensity 0 (valid data <5dBz)
for (k = 0; k < 128; ++k)
fprintf(stdout, "%d", (fisb->product_id == 63 ? 0 : 1));
fprintf(stdout, "\n");
}
}
}
}
}
void handle_frame(frame_type_t type, uint8_t *frame, int len, void *extra)
{
if (type == UAT_UPLINK) {
struct uat_uplink_mdb mdb;
int i;
uat_decode_uplink_mdb(frame, &mdb);
if (!mdb.app_data_valid)
return;
for (i = 0; i < mdb.num_info_frames; ++i) {
struct fisb_apdu *fisb;
if (!mdb.info_frames[i].is_fisb)
continue;
fisb = &mdb.info_frames[i].fisb;
if (fisb->product_id != 63 && fisb->product_id != 64)
continue;
decode_nexrad(fisb);
}
}
fflush(stdout);
}
int main(int argc, char **argv)
{
struct dump978_reader *reader;
int framecount;
reader = dump978_reader_new(0,0);
if (!reader) {
perror("dump978_reader_new");
return 1;
}
while ((framecount = dump978_read_frames(reader, handle_frame, NULL)) > 0)
;
if (framecount < 0) {
perror("dump978_read_frames");
return 1;
}
return 0;
}