// // Copyright 2015, Oliver Jowett // // 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 . #include #include #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 : // // where: // is Regional (for type 63) or CONUS (for type 64) // : is the time from the PDU header - all blocks from one composite radar image will have the same time // is the scale value of this block - 0 (high res), 1 (med res), or 2 (low res) // is the north edge of the block, in _integer arcminutes_. Divide by 60 to get degrees. // 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. // is the height of the block, in integer arcminutes of latitude // 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 /32 arcminutes of longitude by /4 arcminutes of latitude. // // 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; }