batman
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
|
||||||
|
dat_files/*
|
||||||
|
asc_files/*
|
||||||
|
*.tar.gz
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
from nimrod_3 import Nimrod
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import logging # TODO: Add logging
|
||||||
|
|
||||||
|
"""
|
||||||
|
import nimrod
|
||||||
|
a = nimrod.Nimrod(open(
|
||||||
|
'200802252000_nimrod_ng_radar_rainrate_composite_1km_merged_UK_zip'))
|
||||||
|
a.query()
|
||||||
|
a.extract_asc(open('full_raster.asc', 'w'))
|
||||||
|
a.apply_bbox(279906, 285444, 283130, 290440)
|
||||||
|
a.query()
|
||||||
|
a.extract_asc(open('clipped_raster.asc', 'w'))
|
||||||
|
"""
|
||||||
|
|
||||||
|
BOUNDING_BOX_INFO = {
|
||||||
|
"BRISCS": (607000, 608000, 217000, 218000),
|
||||||
|
"WINTSC": (499000, 500000, 416000, 417000),
|
||||||
|
}
|
||||||
|
in_top_folder = "./dat_files"
|
||||||
|
out_top_folder = "./asc_files"
|
||||||
|
|
||||||
|
|
||||||
|
def get_datetime(file_name: str) -> str:
|
||||||
|
# Pattern to match YYYYMMDDHHMM format
|
||||||
|
pattern = r"(\d{8})(\d{4})"
|
||||||
|
match = re.search(pattern, file_name)
|
||||||
|
if match:
|
||||||
|
date_part = match.group(1) # YYYYMMDD
|
||||||
|
time_part = match.group(2) # HHMM
|
||||||
|
return f"{date_part}{time_part}"
|
||||||
|
else:
|
||||||
|
return "date_not_found"
|
||||||
|
|
||||||
|
|
||||||
|
# read all file names in the folder
|
||||||
|
area_folders = os.listdir(in_top_folder)
|
||||||
|
|
||||||
|
for area in area_folders:
|
||||||
|
bounding_box = BOUNDING_BOX_INFO.get(area, (0, 0, 0, 0))
|
||||||
|
print(area, bounding_box)
|
||||||
|
xmin, xmax, ymin, ymax = bounding_box
|
||||||
|
os.makedirs(Path(out_top_folder, area), exist_ok=True)
|
||||||
|
for in_file in os.listdir(Path(in_top_folder, area)):
|
||||||
|
timestamp = get_datetime(in_file)
|
||||||
|
out_file_name = f"{timestamp}_{area}.asc"
|
||||||
|
out_file_path = Path(out_top_folder, area, out_file_name)
|
||||||
|
in_file_full = Path(in_top_folder, area, in_file)
|
||||||
|
#print(in_file_full)
|
||||||
|
try:
|
||||||
|
image = Nimrod(open(in_file_full, 'rb'))
|
||||||
|
image.apply_bbox(xmin, xmax, ymin, ymax)
|
||||||
|
# image.query() # prints out file_details
|
||||||
|
with open(out_file_path, 'w') as outfile:
|
||||||
|
image.extract_asc(outfile)
|
||||||
|
except Nimrod.HeaderReadError as e:
|
||||||
|
print(f'Failed to read file {in_file_full}, is it corrupt?')
|
||||||
|
print(e)
|
||||||
|
continue
|
||||||
|
except Nimrod.PayloadReadError as e:
|
||||||
|
print(f'Failed to load the raster data in {in_file_full}')
|
||||||
|
print(e)
|
||||||
|
continue
|
||||||
|
except Nimrod.BboxRangeError as e:
|
||||||
|
print(f'Bounding Box out of range. Given bounding box: {bounding_box}')
|
||||||
|
print(e)
|
||||||
|
# Skips the whole area as bounding box will be out of bounds for all files
|
||||||
|
break
|
||||||
|
|
||||||
+448
@@ -0,0 +1,448 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
"""
|
||||||
|
Extract data from UK Met Office Rain Radar NIMROD image files.
|
||||||
|
|
||||||
|
Parse NIMROD format image files, display header data and allow extraction of
|
||||||
|
raster image to an ESRI ASCII (.asc) format file. A bounding box may be
|
||||||
|
specified to clip the image to the area of interest. Can be imported as a
|
||||||
|
Python module or run directly as a command line script.
|
||||||
|
|
||||||
|
Author: Richard Thomas
|
||||||
|
Version: 1.0 (13 April 2015)
|
||||||
|
Public Repository: https://github.com/richard-thomas/MetOffice_NIMROD
|
||||||
|
|
||||||
|
Command line usage:
|
||||||
|
python nimrod.py [-h] [-q] [-x] [-bbox XMIN XMAX YMIN YMAX] [infile] [outfile]
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
infile (Uncompressed) NIMROD input filename
|
||||||
|
outfile Output raster filename (*.asc)
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-q, --query Display metadata
|
||||||
|
-x, --extract Extract raster file in ASC format
|
||||||
|
-bbox XMIN XMAX YMIN YMAX
|
||||||
|
Bounding box to clip raster data to
|
||||||
|
|
||||||
|
Note that any bounding box must be specified in the same units and projection
|
||||||
|
as the input file. The bounding box does not need to be contained by the input
|
||||||
|
raster but must intersect it.
|
||||||
|
|
||||||
|
Example command line usage:
|
||||||
|
python nimrod.py -bbox 279906 285444 283130 290440
|
||||||
|
-xq 200802252000_nimrod_ng_radar_rainrate_composite_1km_merged_UK_zip
|
||||||
|
plynlimon_catchments_rainfall.asc
|
||||||
|
|
||||||
|
Example Python module usage:
|
||||||
|
import nimrod
|
||||||
|
a = nimrod.Nimrod(open(
|
||||||
|
'200802252000_nimrod_ng_radar_rainrate_composite_1km_merged_UK_zip'))
|
||||||
|
a.query()
|
||||||
|
a.extract_asc(open('full_raster.asc', 'w'))
|
||||||
|
a.apply_bbox(279906, 285444, 283130, 290440)
|
||||||
|
a.query()
|
||||||
|
a.extract_asc(open('clipped_raster.asc', 'w'))
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
1. Valid for v1.7 and v2.6-4 of NIMROD file specification
|
||||||
|
2. Assumes image origin is top left (i.e. that header[24] = 0)
|
||||||
|
3. Tested on UK composite 1km and 5km data, under Linux and Windows XP
|
||||||
|
4. Further details of NIMROD data and software at the NERC BADC website:
|
||||||
|
http://badc.nerc.ac.uk/browse/badc/ukmo-nimrod/
|
||||||
|
|
||||||
|
Copyright (c) 2015 Richard Thomas
|
||||||
|
(Nimrod.__init__() method based on read_nimrod.py by Charles Kilburn Aug 2008)
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the Artistic License 2.0 as published by the
|
||||||
|
Open Source Initiative (http://opensource.org/licenses/Artistic-2.0)
|
||||||
|
|
||||||
|
This program 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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import array
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
class Nimrod:
|
||||||
|
"""Reading, querying and processing of NIMROD format rainfall data files."""
|
||||||
|
|
||||||
|
class RecordLenError(Exception):
|
||||||
|
"""
|
||||||
|
Exception Type: NIMROD record length read from file not as expected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, actual, expected, location):
|
||||||
|
self.message = "Incorrect record length %d bytes (expected %d) at %s." % (
|
||||||
|
actual,
|
||||||
|
expected,
|
||||||
|
location,
|
||||||
|
)
|
||||||
|
|
||||||
|
class HeaderReadError(Exception):
|
||||||
|
"""Exception Type: Read error whilst parsing NIMROD header elements."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PayloadReadError(Exception):
|
||||||
|
"""Exception Type: Read error whilst parsing NIMROD raster data."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
class BboxRangeError(Exception):
|
||||||
|
"""
|
||||||
|
Exception Type: Bounding box specified out of range of raster image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, infile):
|
||||||
|
"""
|
||||||
|
Parse all header and data info from a NIMROD data file into this object.
|
||||||
|
(This method based on read_nimrod.py by Charles Kilburn Aug 2008)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
infile: NIMROD file object opened for binary reading
|
||||||
|
Raises:
|
||||||
|
RecordLenError: NIMROD record length read from file not as expected
|
||||||
|
HeaderReadError: Read error whilst parsing NIMROD header elements
|
||||||
|
PayloadReadError: Read error whilst parsing NIMROD raster data
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check_record_len(infile, expected, location):
|
||||||
|
"""
|
||||||
|
Check record length in C struct is as expected.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
infile: file to read from
|
||||||
|
expected: expected value of record length read
|
||||||
|
location: description of position in file (for reporting)
|
||||||
|
Raises:
|
||||||
|
HeaderReadError: Read error whilst reading record length
|
||||||
|
RecordLenError: Unexpected NIMROD record length read from file
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Unpack length from C struct (Big Endian, 4-byte long)
|
||||||
|
try:
|
||||||
|
(record_length,) = struct.unpack(">l", infile.read(4))
|
||||||
|
except Exception:
|
||||||
|
raise Nimrod.HeaderReadError
|
||||||
|
if record_length != expected:
|
||||||
|
raise Nimrod.RecordLenError(record_length, expected, location)
|
||||||
|
|
||||||
|
# Header should always be a fixed length record
|
||||||
|
check_record_len(infile, 512, "header start")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Read first 31 2-byte integers (header fields 1-31)
|
||||||
|
gen_ints = array.array("h")
|
||||||
|
data = infile.read(31 * 2) # 31 integers * 2 bytes each
|
||||||
|
gen_ints.frombytes(data)
|
||||||
|
gen_ints.byteswap()
|
||||||
|
|
||||||
|
# Read next 28 4-byte floats (header fields 32-59)
|
||||||
|
gen_reals = array.array("f")
|
||||||
|
data = infile.read(28 * 4) # 28 floats * 4 bytes each
|
||||||
|
gen_reals.frombytes(data)
|
||||||
|
gen_reals.byteswap()
|
||||||
|
|
||||||
|
# Read next 45 4-byte floats (header fields 60-104)
|
||||||
|
spec_reals = array.array("f")
|
||||||
|
data = infile.read(45 * 4) # 45 floats * 4 bytes each
|
||||||
|
spec_reals.frombytes(data)
|
||||||
|
spec_reals.byteswap()
|
||||||
|
|
||||||
|
# Read next 56 characters (header fields 105-107)
|
||||||
|
characters = infile.read(56)
|
||||||
|
|
||||||
|
# Read next 51 2-byte integers (header fields 108-)
|
||||||
|
spec_ints = array.array("h")
|
||||||
|
data = infile.read(51 * 2) # 51 integers * 2 bytes each
|
||||||
|
spec_ints.frombytes(data)
|
||||||
|
spec_ints.byteswap()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
infile.close()
|
||||||
|
raise Nimrod.HeaderReadError
|
||||||
|
|
||||||
|
check_record_len(infile, 512, "header end")
|
||||||
|
|
||||||
|
# Extract strings and make duplicate entries to give meaningful names
|
||||||
|
chars = characters.decode("utf-8")
|
||||||
|
self.units = chars[0:8]
|
||||||
|
self.data_source = chars[8:32]
|
||||||
|
self.title = chars[32:55]
|
||||||
|
|
||||||
|
# Store header values in a list so they can be indexed by "element
|
||||||
|
# number" shown in NIMROD specification (starts at 1)
|
||||||
|
self.hdr_element = [None] # Dummy value at element 0
|
||||||
|
self.hdr_element.extend(gen_ints)
|
||||||
|
self.hdr_element.extend(gen_reals)
|
||||||
|
self.hdr_element.extend(spec_reals)
|
||||||
|
self.hdr_element.extend([self.units])
|
||||||
|
self.hdr_element.extend([self.data_source])
|
||||||
|
self.hdr_element.extend([self.title])
|
||||||
|
self.hdr_element.extend(spec_ints)
|
||||||
|
|
||||||
|
# Duplicate some of values to give more meaningful names
|
||||||
|
self.nrows = self.hdr_element[16]
|
||||||
|
self.ncols = self.hdr_element[17]
|
||||||
|
self.n_data_specific_reals = self.hdr_element[22]
|
||||||
|
self.n_data_specific_ints = self.hdr_element[23] + 1
|
||||||
|
# Note "+ 1" because header value is count from element 109
|
||||||
|
self.y_top = self.hdr_element[34]
|
||||||
|
self.y_pixel_size = self.hdr_element[35]
|
||||||
|
self.x_left = self.hdr_element[36]
|
||||||
|
self.x_pixel_size = self.hdr_element[37]
|
||||||
|
|
||||||
|
# Calculate other image bounds (note these are pixel centres)
|
||||||
|
self.x_right = self.x_left + self.x_pixel_size * (self.ncols - 1)
|
||||||
|
self.y_bottom = self.y_top - self.y_pixel_size * (self.nrows - 1)
|
||||||
|
|
||||||
|
# Read payload (actual raster data)
|
||||||
|
array_size = self.ncols * self.nrows
|
||||||
|
check_record_len(infile, array_size * 2, "data start")
|
||||||
|
|
||||||
|
self.data = array.array("h")
|
||||||
|
try:
|
||||||
|
data = infile.read(array_size * 2)
|
||||||
|
self.data.frombytes(data)
|
||||||
|
self.data.byteswap()
|
||||||
|
except Exception:
|
||||||
|
infile.close()
|
||||||
|
raise Nimrod.PayloadReadError
|
||||||
|
|
||||||
|
check_record_len(infile, array_size * 2, "data end")
|
||||||
|
infile.close()
|
||||||
|
|
||||||
|
def query(self):
|
||||||
|
"""Print complete NIMROD file header information."""
|
||||||
|
|
||||||
|
print("NIMROD file raw header fields listed by element number:")
|
||||||
|
print("General (Integer) header entries:")
|
||||||
|
for i in range(1, 32):
|
||||||
|
print(i, "\t", self.hdr_element[i])
|
||||||
|
print("General (Real) header entries:")
|
||||||
|
for i in range(32, 60):
|
||||||
|
print(i, "\t", self.hdr_element[i])
|
||||||
|
print("Data Specific (Real) header entries (%d):" % self.n_data_specific_reals)
|
||||||
|
for i in range(60, 60 + self.n_data_specific_reals):
|
||||||
|
print(i, "\t", self.hdr_element[i])
|
||||||
|
print(
|
||||||
|
"Data Specific (Integer) header entries (%d):" % self.n_data_specific_ints
|
||||||
|
)
|
||||||
|
for i in range(108, 108 + self.n_data_specific_ints):
|
||||||
|
print(i, "\t", self.hdr_element[i])
|
||||||
|
print("Character header entries:")
|
||||||
|
print(" 105 Units: ", self.units)
|
||||||
|
print(" 106 Data source: ", self.data_source)
|
||||||
|
print(" 107 Title of field: ", self.title)
|
||||||
|
|
||||||
|
# Print out info & header fields
|
||||||
|
# Note that ranges are given to the edge of each pixel
|
||||||
|
print(
|
||||||
|
"\nValidity Time: %2.2d:%2.2d on %2.2d/%2.2d/%4.4d"
|
||||||
|
% (
|
||||||
|
self.hdr_element[4],
|
||||||
|
self.hdr_element[5],
|
||||||
|
self.hdr_element[3],
|
||||||
|
self.hdr_element[2],
|
||||||
|
self.hdr_element[1],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Easting range: %.1f - %.1f (at pixel steps of %.1f)"
|
||||||
|
% (
|
||||||
|
self.x_left - self.x_pixel_size / 2,
|
||||||
|
self.x_right + self.x_pixel_size / 2,
|
||||||
|
self.x_pixel_size,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Northing range: %.1f - %.1f (at pixel steps of %.1f)"
|
||||||
|
% (
|
||||||
|
self.y_bottom - self.y_pixel_size / 2,
|
||||||
|
self.y_top + self.y_pixel_size / 2,
|
||||||
|
self.y_pixel_size,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print("Image size: %d rows x %d cols" % (self.nrows, self.ncols))
|
||||||
|
|
||||||
|
def apply_bbox(self, xmin, xmax, ymin, ymax):
|
||||||
|
"""
|
||||||
|
Clip raster data to all pixels that intersect specified bounding box.
|
||||||
|
|
||||||
|
Note that existing object data is modified and all header values
|
||||||
|
affected are appropriately adjusted. Because pixels are specified by
|
||||||
|
their centre points, a bounding box that comes within half a pixel
|
||||||
|
width of the raster edge will intersect with the pixel.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xmin: Most negative easting or longitude of bounding box
|
||||||
|
xmax: Most positive easting or longitude of bounding box
|
||||||
|
ymin: Most negative northing or latitude of bounding box
|
||||||
|
ymax: Most positive northing or latitude of bounding box
|
||||||
|
Raises:
|
||||||
|
BboxRangeError: Bounding box specified out of range of raster image
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Check if there is no overlap of bounding box with raster
|
||||||
|
if (
|
||||||
|
xmin > self.x_right + self.x_pixel_size / 2
|
||||||
|
or xmax < self.x_left - self.x_pixel_size / 2
|
||||||
|
or ymin > self.y_top + self.y_pixel_size / 2
|
||||||
|
or ymax < self.y_bottom - self.x_pixel_size / 2
|
||||||
|
):
|
||||||
|
raise Nimrod.BboxRangeError
|
||||||
|
|
||||||
|
# Limit bounds to within raster image
|
||||||
|
xmin = max(xmin, self.x_left)
|
||||||
|
xmax = min(xmax, self.x_right)
|
||||||
|
ymin = max(ymin, self.y_bottom)
|
||||||
|
ymax = min(ymax, self.y_top)
|
||||||
|
|
||||||
|
# Calculate min and max pixel index in each row and column to use
|
||||||
|
# Note addition of 0.5 as x_left location is centre of pixel
|
||||||
|
# ('int' truncates floats towards zero)
|
||||||
|
xMinPixelId = int((xmin - self.x_left) / self.x_pixel_size + 0.5)
|
||||||
|
xMaxPixelId = int((xmax - self.x_left) / self.x_pixel_size + 0.5)
|
||||||
|
|
||||||
|
# For y (northings), note the first data row stored is most north
|
||||||
|
yMinPixelId = int((self.y_top - ymax) / self.y_pixel_size + 0.5)
|
||||||
|
yMaxPixelId = int((self.y_top - ymin) / self.y_pixel_size + 0.5)
|
||||||
|
|
||||||
|
bbox_data = []
|
||||||
|
for i in range(yMinPixelId, yMaxPixelId + 1):
|
||||||
|
bbox_data.extend(
|
||||||
|
self.data[
|
||||||
|
i * self.ncols + xMinPixelId : i * self.ncols + xMaxPixelId + 1
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update object where necessary
|
||||||
|
self.data = bbox_data
|
||||||
|
self.x_right = self.x_left + xMaxPixelId * self.x_pixel_size
|
||||||
|
self.x_left += xMinPixelId * self.x_pixel_size
|
||||||
|
self.ncols = xMaxPixelId - xMinPixelId + 1
|
||||||
|
self.y_bottom = self.y_top - yMaxPixelId * self.y_pixel_size
|
||||||
|
self.y_top -= yMinPixelId * self.y_pixel_size
|
||||||
|
self.nrows = yMaxPixelId - yMinPixelId + 1
|
||||||
|
self.hdr_element[16] = self.nrows
|
||||||
|
self.hdr_element[17] = self.ncols
|
||||||
|
self.hdr_element[34] = self.y_top
|
||||||
|
self.hdr_element[36] = self.x_left
|
||||||
|
|
||||||
|
def extract_asc(self, outfile):
|
||||||
|
"""
|
||||||
|
Write raster data to an ESRI ASCII (.asc) format file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
outfile: file object opened for writing text
|
||||||
|
"""
|
||||||
|
|
||||||
|
# As ESRI ASCII format only supports square pixels, warn if not so
|
||||||
|
if self.x_pixel_size != self.y_pixel_size:
|
||||||
|
print(
|
||||||
|
"Warning: x_pixel_size(%d) != y_pixel_size(%d)"
|
||||||
|
% (self.x_pixel_size, self.y_pixel_size)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write header to output file. Note that data is valid at the centre
|
||||||
|
# of each pixel so "xllcenter" rather than "xllcorner" must be used
|
||||||
|
outfile.write("ncols %d\n" % self.ncols)
|
||||||
|
outfile.write("nrows %d\n" % self.nrows)
|
||||||
|
outfile.write("xllcenter %d\n" % self.x_left)
|
||||||
|
outfile.write("yllcenter %d\n" % self.y_bottom)
|
||||||
|
outfile.write("cellsize %.1f\n" % self.y_pixel_size)
|
||||||
|
outfile.write("nodata_value %.1f\n" % self.hdr_element[38])
|
||||||
|
|
||||||
|
# Write raster data to output file
|
||||||
|
for i in range(self.nrows):
|
||||||
|
for j in range(self.ncols - 1):
|
||||||
|
outfile.write("%d " % self.data[i * self.ncols + j])
|
||||||
|
outfile.write("%d\n" % self.data[i * self.ncols + self.ncols - 1])
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
# Handle if called as a command line script
|
||||||
|
# (And as an example of how to invoke class methods from an importing module)
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Extract information and data from a NIMROD format file",
|
||||||
|
epilog="""Note that any bounding box must be specified in the same
|
||||||
|
units and projection as the input file. The bounding box
|
||||||
|
does not need to be contained by the input raster but
|
||||||
|
must intersect it.""",
|
||||||
|
)
|
||||||
|
parser.add_argument("-q", "--query", action="store_true", help="Display metadata")
|
||||||
|
parser.add_argument(
|
||||||
|
"-x", "--extract", action="store_true", help="Extract raster file in ASC format"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"infile",
|
||||||
|
nargs="?",
|
||||||
|
type=argparse.FileType("rb"),
|
||||||
|
default=sys.stdin,
|
||||||
|
help="(Uncompressed) NIMROD input filename",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"outfile",
|
||||||
|
nargs="?",
|
||||||
|
type=argparse.FileType("w"),
|
||||||
|
default=sys.stdout,
|
||||||
|
help="Output raster filename (*.asc)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-bbox",
|
||||||
|
type=float,
|
||||||
|
nargs=4,
|
||||||
|
metavar=("XMIN", "XMAX", "YMIN", "YMAX"),
|
||||||
|
help="Bounding box to clip raster data to",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.query and not args.extract:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Initialise data object by reading NIMROD file
|
||||||
|
# (Only trap record length exception as others self-explanatory)
|
||||||
|
try:
|
||||||
|
rainfall_data = Nimrod(args.infile)
|
||||||
|
except Nimrod.RecordLenError as error:
|
||||||
|
sys.stderr.write("ERROR: %s\n" % error.message)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.bbox:
|
||||||
|
print("Trimming NIMROD raster to bounding box...")
|
||||||
|
try:
|
||||||
|
rainfall_data.apply_bbox(*args.bbox)
|
||||||
|
except Nimrod.BboxRangeError:
|
||||||
|
sys.stderr.write("ERROR: bounding box not within raster image.\n")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Perform query after any bounding box trimming to allow sanity checking of
|
||||||
|
# size of resulting image
|
||||||
|
if args.query:
|
||||||
|
rainfall_data.query()
|
||||||
|
|
||||||
|
if args.extract:
|
||||||
|
print("Extracting NIMROD raster to ASC file...")
|
||||||
|
print(
|
||||||
|
" Outputting data array (%d rows x %d cols = %d pixels)"
|
||||||
|
% (
|
||||||
|
rainfall_data.nrows,
|
||||||
|
rainfall_data.ncols,
|
||||||
|
rainfall_data.nrows * rainfall_data.ncols,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rainfall_data.extract_asc(args.outfile)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[project]
|
||||||
|
name = "met-office"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"ruff>=0.14.3",
|
||||||
|
]
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 3
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "met-office"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "ruff", specifier = ">=0.14.3" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/75/62/50b7727004dfe361104dfbf898c45a9a2fdfad8c72c04ae62900224d6ecf/ruff-0.14.3.tar.gz", hash = "sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153", size = 5558687, upload-time = "2025-10-31T00:26:26.878Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/8e/0c10ff1ea5d4360ab8bfca4cb2c9d979101a391f3e79d2616c9bf348cd26/ruff-0.14.3-py3-none-linux_armv6l.whl", hash = "sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371", size = 12535613, upload-time = "2025-10-31T00:25:44.302Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/c8/6724f4634c1daf52409fbf13fefda64aa9c8f81e44727a378b7b73dc590b/ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654", size = 12855812, upload-time = "2025-10-31T00:25:47.793Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/03/db1bce591d55fd5f8a08bb02517fa0b5097b2ccabd4ea1ee29aa72b67d96/ruff-0.14.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14", size = 11944026, upload-time = "2025-10-31T00:25:49.657Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/75/4f8dbd48e03272715d12c87dc4fcaaf21b913f0affa5f12a4e9c6f8a0582/ruff-0.14.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed", size = 12356818, upload-time = "2025-10-31T00:25:51.949Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/9b/506ec5b140c11d44a9a4f284ea7c14ebf6f8b01e6e8917734a3325bff787/ruff-0.14.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc", size = 12336745, upload-time = "2025-10-31T00:25:54.248Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/e1/c560d254048c147f35e7f8131d30bc1f63a008ac61595cf3078a3e93533d/ruff-0.14.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd", size = 13101684, upload-time = "2025-10-31T00:25:56.253Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/32/e310133f8af5cd11f8cc30f52522a3ebccc5ea5bff4b492f94faceaca7a8/ruff-0.14.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb", size = 14535000, upload-time = "2025-10-31T00:25:58.397Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/a1/7b0470a22158c6d8501eabc5e9b6043c99bede40fa1994cadf6b5c2a61c7/ruff-0.14.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20", size = 14156450, upload-time = "2025-10-31T00:26:00.889Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/96/24bfd9d1a7f532b560dcee1a87096332e461354d3882124219bcaff65c09/ruff-0.14.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0", size = 13568414, upload-time = "2025-10-31T00:26:03.291Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/e7/138b883f0dfe4ad5b76b58bf4ae675f4d2176ac2b24bdd81b4d966b28c61/ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e", size = 13315293, upload-time = "2025-10-31T00:26:05.708Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/f4/c09bb898be97b2eb18476b7c950df8815ef14cf956074177e9fbd40b7719/ruff-0.14.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5", size = 13539444, upload-time = "2025-10-31T00:26:08.09Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9c/aa/b30a1db25fc6128b1dd6ff0741fa4abf969ded161599d07ca7edd0739cc0/ruff-0.14.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e", size = 12252581, upload-time = "2025-10-31T00:26:10.297Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/13/21096308f384d796ffe3f2960b17054110a9c3828d223ca540c2b7cc670b/ruff-0.14.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e", size = 12307503, upload-time = "2025-10-31T00:26:12.646Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/cc/a350bac23f03b7dbcde3c81b154706e80c6f16b06ff1ce28ed07dc7b07b0/ruff-0.14.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa", size = 12675457, upload-time = "2025-10-31T00:26:15.044Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/76/46346029fa2f2078826bc88ef7167e8c198e58fe3126636e52f77488cbba/ruff-0.14.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f", size = 13403980, upload-time = "2025-10-31T00:26:17.81Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/a4/35f1ef68c4e7b236d4a5204e3669efdeefaef21f0ff6a456792b3d8be438/ruff-0.14.3-py3-none-win32.whl", hash = "sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7", size = 12500045, upload-time = "2025-10-31T00:26:20.503Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/15/51960ae340823c9859fb60c63301d977308735403e2134e17d1d2858c7fb/ruff-0.14.3-py3-none-win_amd64.whl", hash = "sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f", size = 13594005, upload-time = "2025-10-31T00:26:22.533Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/73/4de6579bac8e979fca0a77e54dec1f1e011a0d268165eb8a9bc0982a6564/ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1", size = 12590017, upload-time = "2025-10-31T00:26:24.52Z" },
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user