libinsitu 1.7.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- libinsitu/__init__.py +6 -0
- libinsitu/_version.py +10 -0
- libinsitu/catalog.py +155 -0
- libinsitu/cdl.py +336 -0
- libinsitu/cli/__init__.py +0 -0
- libinsitu/cli/cat.py +360 -0
- libinsitu/cli/check.py +36 -0
- libinsitu/cli/diff.py +152 -0
- libinsitu/cli/enrich_csv.py +440 -0
- libinsitu/cli/index.py +234 -0
- libinsitu/cli/info.py +58 -0
- libinsitu/cli/ls.py +135 -0
- libinsitu/cli/qc.py +92 -0
- libinsitu/cli/sync.py +470 -0
- libinsitu/cli/transform.py +495 -0
- libinsitu/cli/update_meta.py +102 -0
- libinsitu/common.py +943 -0
- libinsitu/handlers/ABOM.py +38 -0
- libinsitu/handlers/BSRN.py +43 -0
- libinsitu/handlers/ESMAP.py +98 -0
- libinsitu/handlers/GenericCSVHandler.py +249 -0
- libinsitu/handlers/IEA_PVPS.py +26 -0
- libinsitu/handlers/ISE_PVLive.py +17 -0
- libinsitu/handlers/METEO_FRANCE.py +20 -0
- libinsitu/handlers/NREL_MIDC.py +130 -0
- libinsitu/handlers/SAURAN.py +41 -0
- libinsitu/handlers/SKYNET.py +70 -0
- libinsitu/handlers/SOLRAD.py +25 -0
- libinsitu/handlers/SURFRAD.py +31 -0
- libinsitu/handlers/UniOregon_readdata.py +117 -0
- libinsitu/handlers/__init__.py +34 -0
- libinsitu/handlers/base_handler.py +231 -0
- libinsitu/handlers/enerMENA.py +113 -0
- libinsitu/log.py +176 -0
- libinsitu/qc/__init__.py +0 -0
- libinsitu/qc/graphs/__init__.py +1 -0
- libinsitu/qc/graphs/base.py +1184 -0
- libinsitu/qc/graphs/main_layout.py +296 -0
- libinsitu/qc/qc_utils.py +725 -0
- libinsitu/res/__init__.py +0 -0
- libinsitu/res/base.cdl +282 -0
- libinsitu/res/index.cdl +51 -0
- libinsitu/res/networks.csv +13 -0
- libinsitu/res/qc-tests.csv +13 -0
- libinsitu/res/station-info/ABOM.csv +22 -0
- libinsitu/res/station-info/BSRN.csv +79 -0
- libinsitu/res/station-info/ESMAP.csv +17 -0
- libinsitu/res/station-info/IEA_PVPS.csv +162 -0
- libinsitu/res/station-info/NREL_MIDC.csv +34 -0
- libinsitu/res/station-info/SAURAN.csv +22 -0
- libinsitu/res/station-info/SKYNET.csv +8 -0
- libinsitu/res/station-info/SOLRAD.csv +10 -0
- libinsitu/res/station-info/SURFRAD.csv +8 -0
- libinsitu/res/station-info/__init__.py +0 -0
- libinsitu/res/station-info/enerMENA.csv +13 -0
- libinsitu-1.7.1.dist-info/METADATA +81 -0
- libinsitu-1.7.1.dist-info/RECORD +61 -0
- libinsitu-1.7.1.dist-info/WHEEL +5 -0
- libinsitu-1.7.1.dist-info/entry_points.txt +12 -0
- libinsitu-1.7.1.dist-info/licenses/LICENSE +25 -0
- libinsitu-1.7.1.dist-info/top_level.txt +1 -0
libinsitu/__init__.py
ADDED
libinsitu/_version.py
ADDED
libinsitu/catalog.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Handling fetch and parsing of Thredds server
|
|
2
|
+
|
|
3
|
+
import xml.etree.ElementTree as ET
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
from urllib.parse import urljoin, urlsplit
|
|
7
|
+
import pprint
|
|
8
|
+
|
|
9
|
+
from requests import HTTPError, Session
|
|
10
|
+
|
|
11
|
+
from libinsitu import parallel_map
|
|
12
|
+
from libinsitu.log import info
|
|
13
|
+
|
|
14
|
+
NS={
|
|
15
|
+
"thredds" : "http://www.unidata.ucar.edu/namespaces/thredds/InvCatalog/v1.0",
|
|
16
|
+
"xlink" : "http://www.w3.org/1999/xlink"}
|
|
17
|
+
|
|
18
|
+
XLINK = "{http://www.w3.org/1999/xlink}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AutoRepr :
|
|
22
|
+
def __repr__(self):
|
|
23
|
+
return pprint.pformat(self.__dict__)
|
|
24
|
+
|
|
25
|
+
class Dataset(AutoRepr) :
|
|
26
|
+
def __init__(self, id, name, size) :
|
|
27
|
+
self.id = id
|
|
28
|
+
self.name = name
|
|
29
|
+
self.size = size
|
|
30
|
+
self.services = dict() # Dict of service type => URL
|
|
31
|
+
|
|
32
|
+
class Catalog(AutoRepr) :
|
|
33
|
+
def __init__(self, id, name, description, url, link) :
|
|
34
|
+
self.id = id
|
|
35
|
+
self.name = name
|
|
36
|
+
self.description = description
|
|
37
|
+
self.url = url
|
|
38
|
+
self.link = link
|
|
39
|
+
self.authorized = None
|
|
40
|
+
self.datasets : Dict[str, Dataset]= dict()
|
|
41
|
+
self.catalogs : Dict[str, Catalog] = dict() # Dict of sub catalogs
|
|
42
|
+
self.services = dict() # Dict of service type => URL
|
|
43
|
+
|
|
44
|
+
def parse_services(catalog, base):
|
|
45
|
+
|
|
46
|
+
res = dict()
|
|
47
|
+
for service in catalog.findall('.//thredds:service', NS):
|
|
48
|
+
name = service.get("name")
|
|
49
|
+
url = service.get("base")
|
|
50
|
+
if url != "":
|
|
51
|
+
res[name] = base + url
|
|
52
|
+
return res
|
|
53
|
+
|
|
54
|
+
def parse_catalog(el, url, id=None, name=None) :
|
|
55
|
+
"""Parse catalog from 'Dataset' element of root catalog or datasetREf/metadata element """
|
|
56
|
+
|
|
57
|
+
if id is None :
|
|
58
|
+
name = el.attrib['name']
|
|
59
|
+
id = el.attrib['ID']
|
|
60
|
+
|
|
61
|
+
# find doc
|
|
62
|
+
metadata = el.find('thredds:metadata', NS) # Optionnal nested node "metadata"
|
|
63
|
+
if metadata is None:
|
|
64
|
+
metadata = el
|
|
65
|
+
docs = metadata.findall('thredds:documentation', NS)
|
|
66
|
+
description = None
|
|
67
|
+
link = None
|
|
68
|
+
for doc in docs:
|
|
69
|
+
if doc.get("type") == "summary":
|
|
70
|
+
description = doc.text
|
|
71
|
+
else:
|
|
72
|
+
link_ = doc.get('{%s}href' % NS["xlink"])
|
|
73
|
+
if link_ is not None:
|
|
74
|
+
link = link_
|
|
75
|
+
return Catalog(id, name, description, url, link)
|
|
76
|
+
|
|
77
|
+
def extract_sub(url, subCatEl) :
|
|
78
|
+
id = subCatEl.attrib[XLINK + "title"]
|
|
79
|
+
href = subCatEl.attrib[XLINK + "href"]
|
|
80
|
+
sub_url = urljoin(url, href)
|
|
81
|
+
return (id, sub_url)
|
|
82
|
+
|
|
83
|
+
def fetch_catalog(url, session=None, recursive=True, parallel=True) :
|
|
84
|
+
|
|
85
|
+
info("Fetching : %s" % url)
|
|
86
|
+
|
|
87
|
+
if session is None :
|
|
88
|
+
session = Session()
|
|
89
|
+
|
|
90
|
+
base = base_url(url)
|
|
91
|
+
xml = http_get(url, session)
|
|
92
|
+
|
|
93
|
+
catalogEl = ET.fromstring(xml)
|
|
94
|
+
datasetEl = catalogEl.find('thredds:dataset', NS)
|
|
95
|
+
|
|
96
|
+
catalog = parse_catalog(datasetEl, url)
|
|
97
|
+
catalog.services = parse_services(catalogEl, base)
|
|
98
|
+
|
|
99
|
+
# Parse or fetch sub catalogs
|
|
100
|
+
|
|
101
|
+
sub_elements = list(datasetEl.findall("thredds:catalogRef", NS))
|
|
102
|
+
|
|
103
|
+
def fetch_rec(subCatEl) :
|
|
104
|
+
id, sub_url = extract_sub(url, subCatEl)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
sub_catalog = fetch_catalog(sub_url, session, parallel=parallel)
|
|
108
|
+
return (id, sub_catalog)
|
|
109
|
+
except HTTPError as e:
|
|
110
|
+
if e.response.status_code == 401:
|
|
111
|
+
print("URL %s not authorized." % e.request.url)
|
|
112
|
+
else:
|
|
113
|
+
raise e
|
|
114
|
+
|
|
115
|
+
if recursive :
|
|
116
|
+
# Common loop for sequential of parallel fetch
|
|
117
|
+
for id, sub_catalog in parallel_map(fetch_rec, sub_elements, parallel):
|
|
118
|
+
catalog.catalogs[id] = sub_catalog
|
|
119
|
+
else :
|
|
120
|
+
for subCatEl in sub_elements :
|
|
121
|
+
id, sub_url = extract_sub(url, subCatEl)
|
|
122
|
+
catalog.catalogs[id] = parse_catalog(subCatEl, sub_url, id, id)
|
|
123
|
+
|
|
124
|
+
# Datasets
|
|
125
|
+
datasets = datasetEl.findall("thredds:dataset", NS)
|
|
126
|
+
|
|
127
|
+
for dataset_el in datasets :
|
|
128
|
+
|
|
129
|
+
uri = dataset_el.attrib["urlPath"]
|
|
130
|
+
id = dataset_el.attrib["ID"]
|
|
131
|
+
name = dataset_el.attrib["name"]
|
|
132
|
+
size = float(dataset_el.find("thredds:dataSize", NS).text)
|
|
133
|
+
|
|
134
|
+
dataset = Dataset(id, name, size)
|
|
135
|
+
dataset.services = {key: base + uri for key, base in catalog.services.items()}
|
|
136
|
+
|
|
137
|
+
catalog.datasets[dataset.name] = dataset
|
|
138
|
+
|
|
139
|
+
return catalog
|
|
140
|
+
|
|
141
|
+
def base_url(url) :
|
|
142
|
+
parts = urlsplit(url)
|
|
143
|
+
return "%s://%s" % (parts.scheme, parts.netloc)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def http_get(url, session) :
|
|
148
|
+
|
|
149
|
+
#print("fetching : %s" % url)
|
|
150
|
+
|
|
151
|
+
res = session.get(url)
|
|
152
|
+
res.raise_for_status()
|
|
153
|
+
|
|
154
|
+
res.encoding = res.apparent_encoding
|
|
155
|
+
return res.text
|
libinsitu/cdl.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from copy import deepcopy
|
|
3
|
+
from typing import Dict, List, Any
|
|
4
|
+
from libinsitu import STATION_NAME_VAR, DefaultDict, read_str, readSingleVar
|
|
5
|
+
from libinsitu.common import parse_value, DATA_VARS, read_res, CDL_PATH, LONGITUDE_VAR, LATITUDE_VAR, ELEVATION_VAR, \
|
|
6
|
+
fill_str, TIME_VAR
|
|
7
|
+
from libinsitu.log import info, warning
|
|
8
|
+
import numpy as np
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
VALUE_ATTR = "_value"
|
|
12
|
+
FILL_VALUE_ATTR = "_FillValue"
|
|
13
|
+
SYSTEM_ATTRIBUTES = [FILL_VALUE_ATTR, VALUE_ATTR]
|
|
14
|
+
|
|
15
|
+
class Variable :
|
|
16
|
+
def __init__(self, name:str, type:str, dimensions:List[str]):
|
|
17
|
+
self.type = type
|
|
18
|
+
self.dimensions = dimensions
|
|
19
|
+
self.name = name
|
|
20
|
+
self.attributes = {}
|
|
21
|
+
|
|
22
|
+
class CDL :
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.dimensions : Dict[str, int] = {}
|
|
25
|
+
self.variables : Dict[str, Variable] = {}
|
|
26
|
+
self.global_attributes = {}
|
|
27
|
+
|
|
28
|
+
class CDLType:
|
|
29
|
+
CHAR = "char"
|
|
30
|
+
STRING = "string"
|
|
31
|
+
FLOAT = "float"
|
|
32
|
+
DOUBLE = "double"
|
|
33
|
+
INT = "int"
|
|
34
|
+
SHORT = "short"
|
|
35
|
+
UINT = "uint"
|
|
36
|
+
|
|
37
|
+
class NetCDFType :
|
|
38
|
+
CHAR = "c"
|
|
39
|
+
STRING = str
|
|
40
|
+
FLOAT = "f4"
|
|
41
|
+
DOUBLE = "f8"
|
|
42
|
+
INT = "i4"
|
|
43
|
+
SHORT = "i2"
|
|
44
|
+
UINT = "u4"
|
|
45
|
+
|
|
46
|
+
# CDL to NetCDF types
|
|
47
|
+
CDL_TYPES_MAP = {
|
|
48
|
+
CDLType.CHAR : NetCDFType.CHAR,
|
|
49
|
+
CDLType.STRING: NetCDFType.STRING,
|
|
50
|
+
CDLType.FLOAT: NetCDFType.FLOAT,
|
|
51
|
+
CDLType.DOUBLE: NetCDFType.DOUBLE,
|
|
52
|
+
CDLType.INT: NetCDFType.INT,
|
|
53
|
+
CDLType.SHORT: NetCDFType.SHORT,
|
|
54
|
+
CDLType.UINT: NetCDFType.UINT}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Cache to CDL
|
|
59
|
+
_CDL:CDL = None
|
|
60
|
+
|
|
61
|
+
def init_cdl(properties=DefaultDict(lambda : "-"), custom_cdl = None) :
|
|
62
|
+
global _CDL
|
|
63
|
+
# Read CDL from resource or custom file
|
|
64
|
+
if custom_cdl is None:
|
|
65
|
+
cdl_file = read_res(CDL_PATH)
|
|
66
|
+
else:
|
|
67
|
+
info("Using custom CDL file %s" % custom_cdl)
|
|
68
|
+
cdl_file = open(custom_cdl, "r")
|
|
69
|
+
_CDL = parse_cdl(cdl_file, properties)
|
|
70
|
+
|
|
71
|
+
def get_cdl(init=False) :
|
|
72
|
+
if _CDL is None:
|
|
73
|
+
if not init :
|
|
74
|
+
raise Exception("No CDL set yet")
|
|
75
|
+
else:
|
|
76
|
+
init_cdl(DefaultDict(lambda : "-"))
|
|
77
|
+
return _CDL
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def replace_placeholders(strval, attributes) :
|
|
82
|
+
|
|
83
|
+
def repl(m) :
|
|
84
|
+
key = m.group().strip("{").strip("}")
|
|
85
|
+
|
|
86
|
+
mandatory = False
|
|
87
|
+
|
|
88
|
+
if "!" in key :
|
|
89
|
+
mandatory = True
|
|
90
|
+
key = key.replace("!", "")
|
|
91
|
+
|
|
92
|
+
if not key in attributes :
|
|
93
|
+
if mandatory:
|
|
94
|
+
raise Exception(f"Missing mandatory attribute '{key}'")
|
|
95
|
+
else:
|
|
96
|
+
warning("Key : '%s' not found in attributes, using empty string instead" % key)
|
|
97
|
+
return ""
|
|
98
|
+
|
|
99
|
+
res = attributes[key]
|
|
100
|
+
return "" if res is None else str(res)
|
|
101
|
+
|
|
102
|
+
return re.sub(r'{\!?\w+}', repl, strval)
|
|
103
|
+
|
|
104
|
+
def parse_cdl(lines, attributes=dict()) :
|
|
105
|
+
""" Parse CDL file """
|
|
106
|
+
|
|
107
|
+
res = CDL()
|
|
108
|
+
|
|
109
|
+
section = None
|
|
110
|
+
curr_var = None
|
|
111
|
+
|
|
112
|
+
for line in lines :
|
|
113
|
+
line = line.strip()
|
|
114
|
+
|
|
115
|
+
# Skip comments
|
|
116
|
+
if line.startswith("#") or len(line) == 0:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
if "#" in line :
|
|
120
|
+
line = line.split("#")[0]
|
|
121
|
+
line = line.strip()
|
|
122
|
+
|
|
123
|
+
# key = value
|
|
124
|
+
if "=" in line :
|
|
125
|
+
line = line.strip(";")
|
|
126
|
+
key, val = line.split("=", 1)
|
|
127
|
+
key = key.strip()
|
|
128
|
+
val = parse_value(val.strip(), split=True)
|
|
129
|
+
|
|
130
|
+
if isinstance(val, str):
|
|
131
|
+
val = replace_placeholders(val.strip(), attributes)
|
|
132
|
+
|
|
133
|
+
# Defining dimension
|
|
134
|
+
if section == "dimensions" :
|
|
135
|
+
|
|
136
|
+
dim = 0 if val == "UNLIMITED" else int(val)
|
|
137
|
+
res.dimensions[key] = dim
|
|
138
|
+
|
|
139
|
+
elif section == "variables" :
|
|
140
|
+
varname, attrname = key.split(":")
|
|
141
|
+
|
|
142
|
+
if varname == "*" :
|
|
143
|
+
varname = curr_var
|
|
144
|
+
|
|
145
|
+
if varname == "" :
|
|
146
|
+
res.global_attributes[attrname] = val
|
|
147
|
+
else :
|
|
148
|
+
res.variables[varname].attributes[attrname] = val
|
|
149
|
+
else :
|
|
150
|
+
raise Exception("Assignement outside any section : %s" % line)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Skip start or end
|
|
154
|
+
elif "{" in line or "}" in line :
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# Change section
|
|
158
|
+
elif ":" in line :
|
|
159
|
+
section = line.strip(":").strip()
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
elif ";" in line :
|
|
163
|
+
# New var
|
|
164
|
+
line = line.strip(";").strip()
|
|
165
|
+
type, var = line.split()
|
|
166
|
+
|
|
167
|
+
# Transform CDL type to NetCDF type
|
|
168
|
+
type = CDL_TYPES_MAP[type]
|
|
169
|
+
|
|
170
|
+
dims=[]
|
|
171
|
+
if "(" in var :
|
|
172
|
+
var, dims = var.split("(")
|
|
173
|
+
dims = dims.strip(")").strip().split(",")
|
|
174
|
+
res.variables[var] = Variable(var, type, dims)
|
|
175
|
+
curr_var = var
|
|
176
|
+
else :
|
|
177
|
+
raise Exception("Bad line : %s" %line)
|
|
178
|
+
|
|
179
|
+
return res
|
|
180
|
+
|
|
181
|
+
def update_attributes(dest_var, attrs:Dict[str, Any], dry_run=False, delete=False) :
|
|
182
|
+
|
|
183
|
+
dry_prefix = "would " if dry_run else ""
|
|
184
|
+
|
|
185
|
+
existing_attrs = set(dest_var.ncattrs())
|
|
186
|
+
new_attrs = set(attrs.keys())
|
|
187
|
+
|
|
188
|
+
extra_attrs = existing_attrs - new_attrs
|
|
189
|
+
|
|
190
|
+
if delete :
|
|
191
|
+
for attrname in extra_attrs :
|
|
192
|
+
info(dry_prefix + "delete attribute %s#%s" % (dest_var.name, attrname))
|
|
193
|
+
if not dry_run :
|
|
194
|
+
dest_var.delncattr(attrname)
|
|
195
|
+
|
|
196
|
+
for key, val in attrs.items() :
|
|
197
|
+
oldval = None if not key in existing_attrs else dest_var.getncattr(key)
|
|
198
|
+
|
|
199
|
+
if key in SYSTEM_ATTRIBUTES :
|
|
200
|
+
# Do not update system attributes
|
|
201
|
+
continue
|
|
202
|
+
|
|
203
|
+
if oldval != val :
|
|
204
|
+
|
|
205
|
+
if (val is None or val == "") and not delete :
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
info(dry_prefix + "update attribute %s#%s %s -> %s" % (dest_var.name, key, oldval, val))
|
|
209
|
+
|
|
210
|
+
if not dry_run:
|
|
211
|
+
dest_var.setncattr(key, val)
|
|
212
|
+
|
|
213
|
+
def update_value(dest_var, vardef:Variable, dry_run=False):
|
|
214
|
+
""""Update single value from _value attribute"""
|
|
215
|
+
|
|
216
|
+
# No _value attribute ?
|
|
217
|
+
if not VALUE_ATTR in vardef.attributes :
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
value = vardef.attributes[VALUE_ATTR]
|
|
221
|
+
|
|
222
|
+
if len(vardef.dimensions) > 0:
|
|
223
|
+
raise Exception(f"Variable {vardef.name} : _value attribute only supported on non zero dimention variables")
|
|
224
|
+
|
|
225
|
+
if vardef.type == NetCDFType.STRING :
|
|
226
|
+
old_val = read_str(dest_var)
|
|
227
|
+
else:
|
|
228
|
+
old_val = readSingleVar(dest_var)
|
|
229
|
+
|
|
230
|
+
prefix = "would " if dry_run else ""
|
|
231
|
+
info(f'{prefix}replace value {old_val} => {value} in {vardef.name}')
|
|
232
|
+
|
|
233
|
+
if dry_run:
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
# Skip empty value
|
|
237
|
+
if value is None or value == "":
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
dest_var[0] = value
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def cmp_var(var, vardef:Variable) :
|
|
245
|
+
return var.dtype == vardef.type and var.dimensions == tuple(vardef.dimensions)
|
|
246
|
+
|
|
247
|
+
def create_or_replace_var(ncfile, vardef:Variable, dry_run=False) :
|
|
248
|
+
|
|
249
|
+
if vardef.name in ncfile.variables:
|
|
250
|
+
|
|
251
|
+
var = ncfile.variables[vardef.name]
|
|
252
|
+
|
|
253
|
+
if cmp_var(var, vardef):
|
|
254
|
+
# Same var -> skipping
|
|
255
|
+
return
|
|
256
|
+
|
|
257
|
+
# Different vars
|
|
258
|
+
if dry_run:
|
|
259
|
+
info("Would replace var : %s" % vardef.name)
|
|
260
|
+
else:
|
|
261
|
+
info("Replacing var : %s" % vardef.name)
|
|
262
|
+
del ncfile.variables[vardef.name]
|
|
263
|
+
|
|
264
|
+
if dry_run :
|
|
265
|
+
info("Would add variable : %s" % vardef.name)
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
least_significant_digit = vardef.attributes.get("least_significant_digit", None)
|
|
269
|
+
fill_value = vardef.attributes.get(FILL_VALUE_ATTR, None)
|
|
270
|
+
|
|
271
|
+
info("Adding variable '%s'. Precision:%s" % (vardef.name, least_significant_digit))
|
|
272
|
+
|
|
273
|
+
ncfile.createVariable(
|
|
274
|
+
vardef.name, vardef.type, vardef.dimensions,
|
|
275
|
+
zlib=vardef.type is not str,
|
|
276
|
+
complevel=9,
|
|
277
|
+
least_significant_digit=least_significant_digit,
|
|
278
|
+
fill_value=fill_value)
|
|
279
|
+
|
|
280
|
+
def initVar(ncfile, vardef:Variable, dry_run=False, delete_attrs=False) :
|
|
281
|
+
|
|
282
|
+
create_or_replace_var(ncfile, vardef, dry_run)
|
|
283
|
+
|
|
284
|
+
var = ncfile.variables[vardef.name]
|
|
285
|
+
|
|
286
|
+
# Update attributes
|
|
287
|
+
update_attributes(var, vardef.attributes, dry_run, delete_attrs)
|
|
288
|
+
|
|
289
|
+
# Update single value if any
|
|
290
|
+
update_value(var, vardef, dry_run=dry_run)
|
|
291
|
+
|
|
292
|
+
def cdl2netcdf(ncfile, cdl: CDL, dry_run=False, delete_attrs=False) :
|
|
293
|
+
"""Init NetCDF file from a CDL"""
|
|
294
|
+
|
|
295
|
+
for dimname, dim in cdl.dimensions.items() :
|
|
296
|
+
|
|
297
|
+
# Already there, skipping
|
|
298
|
+
if not dimname in ncfile.dimensions and not dry_run :
|
|
299
|
+
info("Adding dimension '%s'", dimname)
|
|
300
|
+
ncfile.createDimension(dimname, dim)
|
|
301
|
+
|
|
302
|
+
for varname, vardef in cdl.variables.items() :
|
|
303
|
+
initVar(ncfile, vardef, dry_run, delete_attrs)
|
|
304
|
+
|
|
305
|
+
# Update global attributes
|
|
306
|
+
update_attributes(ncfile, cdl.global_attributes, dry_run, delete_attrs)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def init_nc(netcdf, properties, data_vars=DATA_VARS, dry_run=False, delete_attrs=False, custom_cdl=None) :
|
|
310
|
+
|
|
311
|
+
init_cdl(properties, custom_cdl)
|
|
312
|
+
|
|
313
|
+
cdl = get_cdl()
|
|
314
|
+
|
|
315
|
+
# Ensures all requested data vars are defined
|
|
316
|
+
missing_vars = set(data_var for data_var in data_vars if data_var not in cdl.variables)
|
|
317
|
+
if len(missing_vars) > 0 :
|
|
318
|
+
raise Exception("Unknown data vars : %s" % missing_vars)
|
|
319
|
+
|
|
320
|
+
filtered_cdl = deepcopy(cdl)
|
|
321
|
+
|
|
322
|
+
# Filter data vars (variables with "time" dimension)
|
|
323
|
+
# Also adds the "Time" variable
|
|
324
|
+
filtered_cdl.variables = dict()
|
|
325
|
+
for key, var in cdl.variables.items() :
|
|
326
|
+
if "time" in var.dimensions and not key in data_vars + [TIME_VAR] :
|
|
327
|
+
continue
|
|
328
|
+
filtered_cdl.variables[key] = var
|
|
329
|
+
|
|
330
|
+
cdl2netcdf(netcdf, filtered_cdl, dry_run, delete_attrs)
|
|
331
|
+
|
|
332
|
+
return cdl
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
File without changes
|