From aa4b38bdc509e33a5994e2bb4c8ea1f87eb64e7e Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Wed, 14 Sep 2016 16:23:53 +0200 Subject: [PATCH 01/16] ADD: use netcdf file as data model, proof of concept --- wradvis/gui.py | 9 +- wradvis/properties.py | 28 +++- wradvis/utils.py | 355 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 378 insertions(+), 14 deletions(-) diff --git a/wradvis/gui.py b/wradvis/gui.py index a5df65e..c914a2b 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -62,7 +62,7 @@ def __init__(self, parent=None): self.connect_signals() # finish init - self.props.update_props() + #self.props.update_props() def connect_signals(self): self.mediabox.signal_playpause_changed.connect(self.start_stop) @@ -145,9 +145,10 @@ def slider_changed(self, pos): self.props.filelist[pos]) else: - self.data, _ = utils.read_radolan(self.props.filelist[pos]) - if self.props.product == 'RX': - self.data = (self.data / 2) - 32.5 + self.data = self.props.mem.variables['data'][pos][:]#utils.read_radolan(self.props.filelist[pos]) + #print(self.data.max()) + #if self.props.product == 'RX': + #self.data = (self.data / 2) - 32.5 except IndexError: print("Could not read any data.") else: diff --git a/wradvis/properties.py b/wradvis/properties.py index 09bf87a..5b9d08c 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -423,7 +423,6 @@ def speed_changed(self, position): self.signal_speed_changed.emit() def time_slider_moved(self, position): - self.props.actualFrame = position self.current_time.setCurrentIndex(position) self.signal_time_slider_changed.emit(position) @@ -481,7 +480,6 @@ def current_time_changed(self, value): self.time_slider.blockSignals(True) self.time_slider.setValue(value) self.time_slider.blockSignals(False) - self.props.actualFrame = value self.signal_time_slider_changed.emit(value) @@ -496,7 +494,8 @@ def __init__(self, parent=None): super(Properties, self).__init__(parent) self.parent = parent - self.update_props() + self.mem = None + #self.update_props() def set_datadir(self): f = QtGui.QFileDialog.getExistingDirectory(self.parent, @@ -533,8 +532,9 @@ def update_props(self): self.loc = conf.get("source", "loc") self.filelist = glob.glob(os.path.join(self.dir, "raa0*{0}*".format(self.loc))) self.frames = len(self.filelist) - 1 - self.actualFrame = 0 - self.cube = self.create_data_cube() + if self.mem is not None: + self.mem.close() + self.cube, self.mem = self.create_data_cube() self.signal_props_changed.emit(0) def create_data_cube(self): @@ -543,11 +543,23 @@ def create_data_cube(self): Here we just add the metadata dictionaries ''' + import tempfile cube = [] - for name in self.filelist: + mem = None + + for i, name in enumerate(self.filelist): if self.product == 'DX': _, meta = utils.read_dx(name) else: - _, meta = utils.read_radolan(name) + data, meta = utils.read_radolan(name) + if i == 0: + mem = utils.create_ncdf('tmpfile.nc', meta, units='dBZ') + if mem is not None: + utils.add_ncdf(mem, data, i, meta) cube.append(meta) - return cube + if mem is not None: + mem.variables['data'].set_auto_maskandscale(True) + import netCDF4 as nc + #mem = nc.Dataset('tmpfile.nc', 'r', format='NETCDF4') + + return cube, mem diff --git a/wradvis/utils.py b/wradvis/utils.py index 4baca26..18e629d 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -10,6 +10,8 @@ import wradlib as wrl import numpy as np +import netCDF4 as nc +import datetime as dt from wradvis.config import conf @@ -65,8 +67,8 @@ def get_radolan_origin(): return wrl.georef.get_radolan_grid()[0, 0] -def read_radolan(f, missing=0, loaddata=True): - return wrl.io.read_RADOLAN_composite(f, missing=missing, loaddata=loaddata) +def read_radolan(f, missing=-9999, loaddata=True): + return read_RADOLAN_composite(f, missing=missing, loaddata=loaddata) def read_dx(f, missing=0, loaddata=True): return wrl.io.readDX(f) @@ -86,4 +88,353 @@ def get_cities_coords(): return cities +# just for testing purposes, this can be used from wradlib when it is finalized +# and adapted +def read_RADOLAN_composite(fname, missing=-9999, loaddata=True): + """Read quantitative radar composite format of the German Weather Service + + The quantitative composite format of the DWD (German Weather Service) was + established in the course of the `RADOLAN project ` + and includes several file types, e.g. RX, RO, RK, RZ, RP, RT, RC, RI, RG, PC, + PG and many, many more. + (see format description on the RADOLAN project homepage :cite:`DWD2009`). + + At the moment, the national RADOLAN composite is a 900 x 900 grid with 1 km + resolution and in polar-stereographic projection. There are other grid resolutions + for different composites (eg. PC, PG) + + **Beware**: This function already evaluates and applies the so-called PR factor which is + specified in the header section of the RADOLAN files. The raw values in an RY file + are in the unit 0.01 mm/5min, while read_RADOLAN_composite returns values + in mm/5min (i. e. factor 100 higher). The factor is also returned as part of + attrs dictionary under keyword "precision". + + Parameters + ---------- + fname : path to the composite file + + missing : value assigned to no-data cells + + Returns + ------- + output : tuple of two items (data, attrs) + - data : numpy array of shape (number of rows, number of columns) + - attrs : dictionary of metadata information from the file header + + """ + + NODATA = missing + mask = 0xFFF # max value integer + + f = wrl.io.get_radolan_filehandle(fname) + + header = wrl.io.read_radolan_header(f) + + attrs = wrl.io.parse_DWD_quant_composite_header(header) + + if not loaddata: + f.close() + return None, attrs + + attrs["nodataflag"] = NODATA + + #if not attrs["radarid"] == "10000": + # warnings.warn("WARNING: You are using function e" + + # "wradlib.io.read_RADOLAN_composit for a non " + + # "composite file.\n " + + # "This might work...but please check the validity " + + # "of the results") + + # read the actual data + indat = wrl.io.read_radolan_binary_array(f, attrs['datasize']) + + # data handling taking different product types into account + # RX, EX, WX 'qualitative', temporal resolution 5min, RVP6-units [dBZ] + if attrs["producttype"] in ["RX", "EX", "WX"]: + #convert to 8bit unsigned integer + arr = np.frombuffer(indat, np.uint8).astype(np.uint8) + # clutter & nodata + cluttermask = np.where(arr == 249)[0] + nodatamask = np.where(arr == 250)[0] + #attrs['cluttermask'] = np.where(arr == 249)[0] + + #arr = np.where(arr >= 249, np.int32(255), arr) + + elif attrs['producttype'] in ["PG", "PC"]: + arr = wrl.io.decode_radolan_runlength_array(indat, attrs) + else: + # convert to 16-bit integers + arr = np.frombuffer(indat, np.uint16).astype(np.uint16) + # evaluate bits 13, 14, 15 and 16 + secondary = np.where(arr & 0x1000)[0] + attrs['secondary'] = np.where(arr & 0x1000)[0] + #attrs['nodata'] = np.where(arr & 0x2000)[0] + nodatamask = np.where(arr & 0x2000)[0] + negative = np.where(arr & 0x4000)[0] + cluttermask = np.where(arr & 0x8000)[0] + #attrs['cluttermask'] = np.where(arr & 0x8000)[0] + + # mask out the last 4 bits + arr = arr & mask + + # consider negative flag if product is RD (differences from adjustment) + if attrs["producttype"] == "RD": + # NOT TESTED, YET + arr[negative] = -arr[negative] + # apply precision factor + # this promotes arr to float if precision is float + #arr = arr * attrs["precision"] + # set nodata value# + #arr[attrs['secondary']] = np.int32(4096) + #arr[nodata] = np.int32(4096)#NODATA + + if nodatamask is not None: + attrs['nodatamask'] = nodatamask + if cluttermask is not None: + attrs['cluttermask'] = cluttermask + #arr[np.where(arr == 2500)[0]] = np.int32(4096) + #arr[np.where(arr == 2490)[0]] = np.int32(4096) + #arr[nodata] = np.int32(0) + #arr[clutter] = np.int32(65535) + # anyway, bring it into right shape + arr = arr.reshape((attrs["nrow"], attrs["ncol"])) + #arr = arr.reshape((attrs["nrow"], attrs["ncol"])) + + return arr, attrs + +def create_ncdf(filename, attrs, units='original'): + + nx = attrs['ncol'] + ny = attrs['nrow'] + version = attrs['radolanversion'] + precision = attrs['precision'] + prodtype = attrs['producttype'] + int = attrs['intervalseconds'] + nodata = attrs['nodataflag'] + missing_value = None + + # create NETCDF4 file in memory + id = nc.Dataset(filename, 'w', format='NETCDF4', diskless=True, persist=True) + #id.close() + #id = nc.Dataset(filename, 'a', format='NETCDF4') + + # create dimensions + yid = id.createDimension('y', ny) + xid = id.createDimension('x', nx) + tbid = id.createDimension('nv', 2) + tid = id.createDimension('time', None) + + # create and set the grid x variable that serves as x coordinate + xiid = id.createVariable('x', 'f4', ('x')) + xiid.axis = 'X' + xiid.units = 'km' + xiid.long_name = 'x coordinate of projection' + xiid.standard_name = 'projection_x_coordinate' + + # create and set the grid y variable that serves as y coordinate + yiid = id.createVariable('y', 'f4', ('y')) + yiid.axis = 'Y' + yiid.units = 'km' + yiid.long_name = 'y coordinate of projection' + yiid.standard_name = 'projection_y_coordinate' + + # create time variable + tiid = id.createVariable('time', 'f8', ('time',)) + tiid.axis = 'T' + tiid.units = 'seconds since 1970-01-01 00:00:00' + tiid.standard_name = 'time' + tiid.bounds = 'time_bnds' + + # create time bounds variable + tbiid = id.createVariable('time_bnds', 'f8', ('time', 'nv',)) + + # create grid variable that serves as lon coordinate + lonid = id.createVariable('lon', 'f4', ('y', 'x',), zlib=True, complevel=4) + lonid.units = 'degrees_east' + lonid.standard_name = 'longitude' + lonid.long_name = 'longitude coordinate' + + # create grid variable that serves as lat coordinate + latid = id.createVariable('lat', 'f4', ('y', 'x',), zlib=True, complevel=4) + latid.units = 'degrees_north' + latid.standard_name = 'latitude' + latid.long_name = 'latitude coordinate' + + # create projection variable that defines the projection according to CF-Metadata standards + coordid = id.createVariable('polar_stereographic', 'i4', zlib=True, + complevel=2) + coordid.grid_mapping_name = 'polar_stereographic' + coordid.straight_vertical_longitude_from_pole = np.float32(10.) + coordid.latitude_of_projection_origin = np.float32(90.) + coordid.standard_parallel = np.float32(60.) + coordid.false_easting = np.float32(0.) + coordid.false_northing = np.float32(0.) + coordid.earth_model_of_projection = 'spherical' + coordid.earth_radius_of_projection = np.float32(6370.04) + coordid.units = 'km' + coordid.ancillary_data = 'grid_latitude grid_longitude' + coordid.long_name = 'polar_stereographic' + + if prodtype in ['RX', 'EX']: + if units == 'original': + scale_factor = None + add_offset = None + unit = 'RVP6' + else: + scale_factor = np.float32(0.5) + add_offset = np.float32(-32.5) + unit = 'dBZ' + + valid_min = np.int32(0) + valid_max = np.int32(255) + missing_value = np.int32(255) + fillvalue = np.int32(255) + vtype = 'u1' + standard_name = 'equivalent_reflectivity_factor' + long_name = 'equivalent_reflectivity_factor' + + elif prodtype in ['RY', 'RZ', 'EY', 'EZ']: + if units == 'original': + scale_factor = None + add_offset = None + unit = '0.01mm 5min-1' + elif units == 'normal': + scale_factor = np.float32(precision * 3600 / int) + add_offset = np.float(0) + unit = 'mm h-1' + else: + scale_factor = np.float32(precision / (int * 1000)) + add_offset = np.float(0) + unit = 'm s-1' + + valid_min = np.int32(0) + valid_max = np.int32(4095) + missing_value = np.int32(4096) + fillvalue = np.int32(65535) + vtype = 'u2' + standard_name = 'rainfall_amount' + long_name = 'rainfall_amount' + + elif prodtype in ['RH', 'RB', 'RW', 'RL', 'RU', 'EH', 'EB', 'EW']: + if units == 'original': + scale_factor = None + add_offset = None + unit = '0.1mm h-1' + elif units == 'normal': + scale_factor = np.float32(precision) + add_offset = np.float(0.) + unit = 'mm h-1' + else: + scale_factor = np.float32(precision / (int * 1000)) + add_offset = np.float(0) + unit = 'm s-1' + + valid_min = np.int32(0) + valid_max = np.int32(4095) + missing_value = np.int32(4096) + fillvalue = np.int32(65535) + vtype = 'u2' + standard_name = 'rainfall_amount' + long_name = 'rainfall_amount' + + elif prodtype in ['SQ', 'SH', 'SF']: + scale_factor = np.float32(precision) + add_offset = np.float(0.) + valid_min = np.int32(0) + valid_max = np.int32(4095) + missing_value = np.int32(4096) + fillvalue = np.int32(65535) + vtype = 'u2' + standard_name = 'rainfall_amount' + long_name = 'rainfall_amount' + if int == (360 * 60): + unit = 'mm 6h-1' + elif int == (720 * 60): + unit = 'mm 12h-1' + elif int == (1440 * 60): + unit = 'mm d-1' + + prod = id.createVariable('data', vtype, ('time', 'y', 'x',), + fill_value=fillvalue, zlib=True, complevel=4) + # accept data as unsigned byte without scaling, crucial for writing already packed data + prod.set_auto_maskandscale(False) + prod.units = unit + prod.standard_name = standard_name + prod.long_name = long_name + prod.grid_mapping = 'polar_stereographic' + prod.coordinates = 'lat lon' + if scale_factor: + prod.scale_factor = scale_factor + if add_offset: + prod.add_offset = add_offset + if valid_min: + prod.valid_min = valid_min + if valid_max: + prod.valid_max = valid_max + if missing_value: + prod.missing_value = missing_value + prod.version = 'RADOLAN {0}'.format(version) + prod.comment = 'NO COMMENT' + + id_str1 = id.createVariable('radars', 'S128', ('time',), zlib=True, + complevel=4) + + # create GLOBAL attributes + id.Title = 'RADOLAN {0} Composite'.format(prodtype) + id.Institution = 'Data owned by Deutscher Wetterdienst' + id.Source = 'DWD C-Band Weather Radar Network, Original RADOLAN Data by Deutscher Wetterdienst' + id.History = 'Data transferred from RADOLAN composite format to netcdf using wradvis version 0.1 by wradlib developers' + id.Conventions = 'CF-1.6 where applicable' + utcnow = dt.datetime.utcnow() + id.Processing_date = utcnow.strftime("%Y-%m-%dT%H:%M:%S") + id.Author = '{0}, {1}'.format('Author', 'wradlib@wradlib.org') + id.Comments = 'blank' + id.License = 'DWD Licenses' + + + + # fill general variables + ny, nx = attrs['ncol'], attrs['nrow'] + radolan_grid_xy = wrl.georef.get_radolan_grid(nx, ny) + xarr = radolan_grid_xy[0, :, 0] + yarr = radolan_grid_xy[:, 0, 1] + radolan_grid_ll = wrl.georef.get_radolan_grid(nx, ny, wgs84=True) + lons = radolan_grid_ll[..., 0] + lats = radolan_grid_ll[..., 1] + + + id.variables['x'][:] = xarr + id.variables['x'].valid_min = xarr[0] + id.variables['x'].valid_max = xarr[-1] + id.variables['y'][:] = yarr + id.variables['y'].valid_min = yarr[0] + id.variables['y'].valid_max = yarr[-1] + id.variables['lat'][:] = lats + id.variables['lon'][:] = lons + + return id + + +def add_ncdf(id, data, time_index, attrs): + # remove clutter, nodata and secondary data from raw files + # wrap with if/else if necessary + if attrs['cluttermask'] is not None: + data.flat[attrs['cluttermask']] = id.variables[ + 'data'].missing_value + if attrs['nodatamask'] is not None: + data.flat[attrs['nodatamask']] = id.variables[ + 'data'].missing_value + #if attrs['secondary'] is not None: + # data.flat[attrs['secondary']] = id.variables[ + # attrs['producttype'].lower()].missing_value + + id.variables['data'][time_index, :, :] = data + print(attrs['datetime']) + delta = attrs['datetime'] - dt.datetime.utcfromtimestamp(0) + id.variables['time'][time_index] = delta.total_seconds() + id.variables['time_bnds'][time_index, :] = delta.total_seconds() + # id.variables['time_bnds'][time_index,1] = delta.total_seconds() + attrs['intervalseconds'] + id.variables['radars'][time_index] = ','.join(attrs['radarlocations']) + + From c041fc57d76287b29696e5e4583a0680a1f811d5 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Wed, 14 Sep 2016 19:59:15 +0200 Subject: [PATCH 02/16] WIP: more signal/slot-refactoring --- wradvis/gui.py | 33 +-------------------------------- wradvis/properties.py | 30 +++++++++++++++++++++++------- wradvis/utils.py | 2 +- 3 files changed, 25 insertions(+), 40 deletions(-) diff --git a/wradvis/gui.py b/wradvis/gui.py index c914a2b..84c5cf6 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -28,7 +28,6 @@ def __init__(self, parent=None): self._need_canvas_refresh = False self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.reload) # initialize RadolanCanvas self.rwidget = RadolanWidget(self) @@ -61,14 +60,10 @@ def __init__(self, parent=None): self.connect_signals() - # finish init - #self.props.update_props() - def connect_signals(self): self.mediabox.signal_playpause_changed.connect(self.start_stop) - self.mediabox.signal_time_slider_changed.connect(self.slider_changed) self.mediabox.signal_speed_changed.connect(self.speed) - self.props.signal_props_changed.connect(self.slider_changed) + #self.props.signal_props_changed.connect(self.slider_changed) def createActions(self): # Set directory @@ -119,12 +114,6 @@ def createDockWindows(self): self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.toolsMenu.addAction(dock.toggleViewAction()) - def reload(self): - if self.mediabox.time_slider.value() >= self.mediabox.range.high(): - self.mediabox.time_slider.setValue(self.mediabox.range.low()) - else: - self.mediabox.time_slider.setValue(self.mediabox.time_slider.value() + 1) - def start_stop(self): if self.timer.isActive(): self.timer.stop() @@ -134,26 +123,6 @@ def start_stop(self): def speed(self): self.timer.setInterval(self.mediabox.speed.value()) - def slider_changed(self, pos): - try: - # Todo: switching happens here, - # but we should just use a common reading function, where the - # underlying wradlib function is exchanged on switching - # format - if self.props.product == 'DX': - self.data, _ = utils.read_dx( - self.props.filelist[pos]) - - else: - self.data = self.props.mem.variables['data'][pos][:]#utils.read_radolan(self.props.filelist[pos]) - #print(self.data.max()) - #if self.props.product == 'RX': - #self.data = (self.data / 2) - 32.5 - except IndexError: - print("Could not read any data.") - else: - self.iwidget.set_data(self.data) - def keyPressEvent(self, event): if isinstance(event, QtGui.QKeyEvent): text = event.text() diff --git a/wradvis/properties.py b/wradvis/properties.py index 5b9d08c..2820605 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -328,7 +328,7 @@ def update_label(self): class MediaBox(DockBox): signal_playpause_changed = QtCore.pyqtSignal(name='startstop') - signal_time_slider_changed = QtCore.pyqtSignal(int, name='dataslidervalueChanged') + #signal_time_slider_changed = QtCore.pyqtSignal(int, name='dataslidervalueChanged') signal_speed_changed = QtCore.pyqtSignal(name='speedChanged') def __init__(self, parent=None): @@ -393,6 +393,7 @@ def __init__(self, parent=None): self.layout.addWidget(self.hline1, 8, 0, 1, 5) self.props.props_changed.connect(self.update_props) + self.props.parent.timer.timeout.connect(self.seekforward) def createMediaButtons(self): iconSize = QtCore.QSize(18, 18) @@ -424,7 +425,25 @@ def speed_changed(self, position): def time_slider_moved(self, position): self.current_time.setCurrentIndex(position) - self.signal_time_slider_changed.emit(position) + try: + # Todo: switching happens here, + # but we should just use a common reading function, where the + # underlying wradlib function is exchanged on switching + # format + if self.props.product == 'DX': + data, _ = utils.read_dx( + self.props.filelist[position]) + + else: + data = self.props.mem.variables['data'][position][:] + # print(self.data.max()) + # if self.props.product == 'RX': + # self.data = (self.data / 2) - 32.5 + except IndexError: + print("Could not read any data.") + else: + self.props.parent.iwidget.set_data(data) + def seekforward(self): if self.time_slider.value() >= self.range.high(): @@ -477,10 +496,7 @@ def range_changed(self): self.range.setHigh(self.range_end.currentIndex()) def current_time_changed(self, value): - self.time_slider.blockSignals(True) self.time_slider.setValue(value) - self.time_slider.blockSignals(False) - self.signal_time_slider_changed.emit(value) # Properties @@ -495,7 +511,7 @@ def __init__(self, parent=None): self.parent = parent self.mem = None - #self.update_props() + self.update_props() def set_datadir(self): f = QtGui.QFileDialog.getExistingDirectory(self.parent, @@ -553,7 +569,7 @@ def create_data_cube(self): else: data, meta = utils.read_radolan(name) if i == 0: - mem = utils.create_ncdf('tmpfile.nc', meta, units='dBZ') + mem = utils.create_ncdf('tmpfile.nc', meta, units='normal') if mem is not None: utils.add_ncdf(mem, data, i, meta) cube.append(meta) diff --git a/wradvis/utils.py b/wradvis/utils.py index 18e629d..6f5f8f5 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -214,7 +214,7 @@ def create_ncdf(filename, attrs, units='original'): missing_value = None # create NETCDF4 file in memory - id = nc.Dataset(filename, 'w', format='NETCDF4', diskless=True, persist=True) + id = nc.Dataset(filename, 'w', format='NETCDF4', diskless=True, persist=True) #id.close() #id = nc.Dataset(filename, 'a', format='NETCDF4') From 18d3d8d521e05a98ef314c849013b627b060e45e Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Thu, 15 Sep 2016 09:37:55 +0200 Subject: [PATCH 03/16] WIP: netcdf file as data model, add data while stepping trough the source data --- wradvis/glcanvas.py | 12 +++++ wradvis/gui.py | 9 ++-- wradvis/properties.py | 105 ++++++++++++++++++++++-------------------- wradvis/utils.py | 9 +++- 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 4217b60..337c116 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -371,6 +371,9 @@ def __init__(self, parent=None): self.hbl.addWidget(self.splitter) self.setLayout(self.hbl) + def connect_signals(self): + self.parent.mediabox.signal_time_slider_changed.connect(self.set_time) + def set_canvas(self, type): if type == 'DX': self.canvas = self.pcanvas @@ -381,6 +384,15 @@ def set_canvas(self, type): self.swapper['R'].show() self.swapper['P'].hide() + def set_time(self, pos): + # now this sets same data to all images + # we would need to do the data loading + # via objects (maybe radar-object from above) + # and use + for im in self.canvas.images: + im.set_data(self.parent.props.mem.variables['data'][pos][:]) + self.canvas.update() + def set_data(self, data): # now this sets same data to all images # we would need to do the data loading diff --git a/wradvis/gui.py b/wradvis/gui.py index 84c5cf6..a6c1b46 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -60,10 +60,13 @@ def __init__(self, parent=None): self.connect_signals() + self.props.update_props() + def connect_signals(self): self.mediabox.signal_playpause_changed.connect(self.start_stop) self.mediabox.signal_speed_changed.connect(self.speed) - #self.props.signal_props_changed.connect(self.slider_changed) + self.mediabox.connect_signals() + self.rwidget.connect_signals() def createActions(self): # Set directory @@ -120,8 +123,8 @@ def start_stop(self): else: self.timer.start() - def speed(self): - self.timer.setInterval(self.mediabox.speed.value()) + def speed(self, value): + self.timer.setInterval(value) def keyPressEvent(self, event): if isinstance(event, QtGui.QKeyEvent): diff --git a/wradvis/properties.py b/wradvis/properties.py index 2820605..d7068a3 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -328,8 +328,8 @@ def update_label(self): class MediaBox(DockBox): signal_playpause_changed = QtCore.pyqtSignal(name='startstop') - #signal_time_slider_changed = QtCore.pyqtSignal(int, name='dataslidervalueChanged') - signal_speed_changed = QtCore.pyqtSignal(name='speedChanged') + signal_time_slider_changed = QtCore.pyqtSignal(int, name='timeChanged') + signal_speed_changed = QtCore.pyqtSignal(int, name='speedChanged') def __init__(self, parent=None): super(MediaBox, self).__init__(parent) @@ -392,6 +392,8 @@ def __init__(self, parent=None): self.layout.addWidget(self.speed, 7, 1, 1, 4) self.layout.addWidget(self.hline1, 8, 0, 1, 5) + + def connect_signals(self): self.props.props_changed.connect(self.update_props) self.props.parent.timer.timeout.connect(self.seekforward) @@ -421,29 +423,20 @@ def createButton(self, style, size, tip, cfunc): return button def speed_changed(self, position): - self.signal_speed_changed.emit() - - def time_slider_moved(self, position): - self.current_time.setCurrentIndex(position) - try: - # Todo: switching happens here, - # but we should just use a common reading function, where the - # underlying wradlib function is exchanged on switching - # format - if self.props.product == 'DX': - data, _ = utils.read_dx( - self.props.filelist[position]) + self.signal_speed_changed.emit(position) - else: - data = self.props.mem.variables['data'][position][:] - # print(self.data.max()) - # if self.props.product == 'RX': - # self.data = (self.data / 2) - 32.5 - except IndexError: - print("Could not read any data.") - else: - self.props.parent.iwidget.set_data(data) + def time_slider_moved(self, pos): + self.current_time.setCurrentIndex(pos) + + # check if data already read + if self.current_time.currentText() == '--': + self.props.add_data(pos) + time = utils.get_dt(self.props.mem.variables['time'][pos]) + self.range_start.setItemText(pos, time.strftime("%H:%M")) + self.current_time.setItemText(pos, time.strftime("%H:%M")) + self.range_end.setItemText(pos, time.strftime("%H:%M")) + self.signal_time_slider_changed.emit(pos) def seekforward(self): if self.time_slider.value() >= self.range.high(): @@ -469,18 +462,26 @@ def playpause(self): self.signal_playpause_changed.emit() def update_props(self): + + stime = utils.get_dt(self.props.mem.variables['time'][0]) + etime = utils.get_dt(self.props.mem.variables['time'][-1]) + rtime = [str(item) for item in self.props.mem.variables['time'][1:-1]] + self.range_start.clear() - self.range_start.addItems( - [item['datetime'].strftime("%H:%M") for item in self.props.cube]) + self.range_start.addItem(stime.strftime("%H:%M")) + self.range_start.addItems(rtime) + self.range_start.addItem(etime.strftime("%H:%M")) self.range_end.clear() - self.range_end.addItems( - [item['datetime'].strftime("%H:%M") for item in self.props.cube]) + self.range_end.addItem(stime.strftime("%H:%M")) + self.range_end.addItems(rtime) + self.range_end.addItem(etime.strftime("%H:%M")) self.current_time.clear() - self.current_time.addItems( - [item['datetime'].strftime("%H:%M") for item in self.props.cube]) + self.current_time.addItem(stime.strftime("%H:%M")) + self.current_time.addItems(rtime) + self.current_time.addItem(etime.strftime("%H:%M")) self.time_slider.setMaximum(self.props.frames) self.time_slider.setValue(0) - self.current_date.setText(self.props.cube[0]['datetime'].strftime("%Y-%M-%d")) + self.current_date.setText(stime.strftime("%Y-%M-%d")) self.range.setMinimum(0) self.range.setMaximum(self.props.frames) self.range.setLow(0) @@ -511,7 +512,7 @@ def __init__(self, parent=None): self.parent = parent self.mem = None - self.update_props() + #self.update_props() def set_datadir(self): f = QtGui.QFileDialog.getExistingDirectory(self.parent, @@ -542,7 +543,16 @@ def open_conf(self): def update_props(self): self.dir = conf["dirs"]["data"] self.product = conf["source"]["product"] + + # setting reader function according to data type + if self.product == 'DX': + self.rfunc = utils.read_dx + else: + self.rfunc = utils.read_radolan + + # activate the correct canvas (grid or polar) self.parent.iwidget.set_canvas(self.product) + self.clim = (conf.get("vis", "cmin"), conf.get("vis", "cmax")) self.parent.iwidget.set_clim(self.clim) self.loc = conf.get("source", "loc") @@ -550,32 +560,25 @@ def update_props(self): self.frames = len(self.filelist) - 1 if self.mem is not None: self.mem.close() - self.cube, self.mem = self.create_data_cube() + self.mem = self.create_nc_dataset() self.signal_props_changed.emit(0) - def create_data_cube(self): + def create_nc_dataset(self): ''' First attempt to create some time_slider layer Here we just add the metadata dictionaries ''' - import tempfile - cube = [] mem = None + data, meta = self.rfunc(self.filelist[0]) + mem = utils.create_ncdf('tmpfile.nc', meta, units='normal') + utils.add_ncdf(mem, data, 0, meta) + data, meta = self.rfunc(self.filelist[-1]) + utils.add_ncdf(mem, data, self.frames, meta) + + return mem + + def add_data(self, pos): + data, meta = self.rfunc(self.filelist[pos]) + utils.add_ncdf(self.mem, data, pos, meta) - for i, name in enumerate(self.filelist): - if self.product == 'DX': - _, meta = utils.read_dx(name) - else: - data, meta = utils.read_radolan(name) - if i == 0: - mem = utils.create_ncdf('tmpfile.nc', meta, units='normal') - if mem is not None: - utils.add_ncdf(mem, data, i, meta) - cube.append(meta) - if mem is not None: - mem.variables['data'].set_auto_maskandscale(True) - import netCDF4 as nc - #mem = nc.Dataset('tmpfile.nc', 'r', format='NETCDF4') - - return cube, mem diff --git a/wradvis/utils.py b/wradvis/utils.py index 6f5f8f5..53109e2 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -63,6 +63,7 @@ def dx_to_wgs84(coords): def get_radolan_grid(): return wrl.georef.get_radolan_grid() + def get_radolan_origin(): return wrl.georef.get_radolan_grid()[0, 0] @@ -70,6 +71,7 @@ def get_radolan_origin(): def read_radolan(f, missing=-9999, loaddata=True): return read_RADOLAN_composite(f, missing=missing, loaddata=loaddata) + def read_dx(f, missing=0, loaddata=True): return wrl.io.readDX(f) @@ -357,7 +359,7 @@ def create_ncdf(filename, attrs, units='original'): prod = id.createVariable('data', vtype, ('time', 'y', 'x',), fill_value=fillvalue, zlib=True, complevel=4) # accept data as unsigned byte without scaling, crucial for writing already packed data - prod.set_auto_maskandscale(False) + #prod.set_auto_maskandscale(False) prod.units = unit prod.standard_name = standard_name prod.long_name = long_name @@ -428,7 +430,9 @@ def add_ncdf(id, data, time_index, attrs): # data.flat[attrs['secondary']] = id.variables[ # attrs['producttype'].lower()].missing_value + id.variables['data'].set_auto_maskandscale(False) id.variables['data'][time_index, :, :] = data + id.variables['data'].set_auto_maskandscale(True) print(attrs['datetime']) delta = attrs['datetime'] - dt.datetime.utcfromtimestamp(0) id.variables['time'][time_index] = delta.total_seconds() @@ -437,4 +441,5 @@ def add_ncdf(id, data, time_index, attrs): id.variables['radars'][time_index] = ','.join(attrs['radarlocations']) - +def get_dt(unix): + return dt.datetime.utcfromtimestamp(unix) From c7577c181d9b6535f4b203ea7d77d749da5a15e4 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Thu, 15 Sep 2016 16:14:53 +0200 Subject: [PATCH 04/16] WIP: lineplot, which shows evolution of signal over time for one pixel --- wradvis/glcanvas.py | 86 ++++++++++++++++++++++++++++++++++++++++++- wradvis/gui.py | 10 ++++- wradvis/properties.py | 29 +++++++++++++-- wradvis/utils.py | 3 +- 4 files changed, 122 insertions(+), 6 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 337c116..3cea97c 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -13,13 +13,62 @@ from vispy.util.event import EventEmitter from vispy.visuals.transforms import STTransform, MatrixTransform, PolarTransform from vispy.scene.cameras import PanZoomCamera -from vispy.scene.visuals import Image, ColorBar, Markers, Text +from vispy.scene.visuals import Image, ColorBar, Markers, Text, Line +from vispy.scene.widgets import Label, AxisWidget from vispy.geometry import Rect from wradvis import utils from wradvis.config import conf +class AxisCanvas(SceneCanvas): + def __init__(self, **kwargs): + super(AxisCanvas, self).__init__(keys='interactive', **kwargs) + + self.size = 450, 200 + self.unfreeze() + self.grid = self.central_widget.add_grid(margin=10) + self.grid.spacing = 0 + + self.pl_title = Label("Plot Title", color='white') + self.pl_title.height_max = 40 + self.grid.add_widget(self.pl_title, row=0, col=0, col_span=3) + + self.yaxis = AxisWidget(orientation='left') + self.yaxis.width_max = 40 + self.grid.add_widget(self.yaxis, row=1, col=1) + + self.ylabel = Label('Y Axis', rotation=-90, color='white') + self.ylabel.width_max = 40 + self.grid.add_widget(self.ylabel, row=1, col=0) + + self.xaxis = AxisWidget(orientation='bottom') + self.xaxis.height_max = 40 + self.grid.add_widget(self.xaxis, row=2, col=2) + + self.xlabel = Label('X Axis', color='white') + self.xlabel.height_max = 40 + self.grid.add_widget(self.xlabel, row=3, col=0, col_span=3) + + self.right_padding = self.grid.add_widget(row=0, col=3, row_span=3) + self.right_padding.width_max = 50 + + self.view = self.grid.add_view(row=1, col=2, border_color='white') + #data = np.random.normal(size=(24, 2)) + #data[0] = -10, -10 + #data[1] = 10, -10 + #data[2] = 10, 10 + #data[3] = -10, 10 + #data[4] = -10, -10 + #self.plot = Line(data, parent=view.scene) + self.view.camera = 'panzoom' + + self.xaxis.link_view(self.view) + self.yaxis.link_view(self.view) + + self.freeze() + + class ColorbarCanvas(SceneCanvas): def __init__(self, **kwargs): @@ -85,6 +134,7 @@ def __init__(self, **kwargs): # add signal emitters self.mouse_moved = EventEmitter(source=self, type="mouse_moved") + self.mouse_pressed = EventEmitter(source=self, type="mouse_pressed") self.key_pressed = EventEmitter(source=self, type="key_pressed") # block double clicks @@ -128,6 +178,7 @@ def __init__(self, **kwargs): self.view.camera = self.cam self._mouse_position = None + self._mouse_press_position = (0, 0) self.freeze() # print FPS to console, vispy SceneCanvas internal function self.measure_fps() @@ -207,6 +258,10 @@ def on_mouse_press(self, event): self.view.interactive = True + point = self.scene.node_transform(self.image).map(event.pos)[:2] + self._mouse_press_position = point + self.mouse_pressed(event) + def on_key_press(self, event): self.key_pressed(event) @@ -405,3 +460,32 @@ def set_data(self, data): def set_clim(self, clim): self.canvas.image.clim = clim self.cbar.cbar.clim = clim + + +class RadolanLineWidget(QtGui.QWidget): + def __init__(self, parent=None): + super(RadolanLineWidget, self).__init__(parent) + self.parent = parent + self.canvas = AxisCanvas() + self.canvas.create_native() + self.canvas.native.setParent(self) + self.hbl = QtGui.QHBoxLayout() + self.hbl.addWidget(self.canvas.native) + self.setLayout(self.hbl) + + def sizeHint(self): + return QtCore.QSize(650, 200) + + def connect_signals(self): + print(self.parent.parent) + self.parent.parent.iwidget.canvas.mouse_pressed.connect(self.set_line) + + def set_line(self, event): + pos = event.pos + y = self.parent.props.mem.variables['data'][:, pos[0], pos[1]] + x = np.arange(len(y)) + try: + self.plot.parent = None + except: + pass + self.plot = Line(np.squeeze(np.dstack((x, y))), parent=self.canvas.view.scene) diff --git a/wradvis/gui.py b/wradvis/gui.py index a6c1b46..9da30a1 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -11,7 +11,7 @@ # other wradvis imports from wradvis.glcanvas import RadolanWidget from wradvis.mplcanvas import MplWidget -from wradvis.properties import Properties, MediaBox, SourceBox, MouseBox +from wradvis.properties import Properties, MediaBox, SourceBox, MouseBox, GraphBox from wradvis import utils from wradvis.config import conf @@ -67,6 +67,7 @@ def connect_signals(self): self.mediabox.signal_speed_changed.connect(self.speed) self.mediabox.connect_signals() self.rwidget.connect_signals() + self.graphbox.connect_signals() def createActions(self): # Set directory @@ -117,6 +118,13 @@ def createDockWindows(self): self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.toolsMenu.addAction(dock.toggleViewAction()) + dock = QtGui.QDockWidget("Time Graphs", self) + dock.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) + self.graphbox = GraphBox(self, size_pol=(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)) + dock.setWidget(self.graphbox) + self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dock) + self.toolsMenu.addAction(dock.toggleViewAction()) + def start_stop(self): if self.timer.isActive(): self.timer.stop() diff --git a/wradvis/properties.py b/wradvis/properties.py index d7068a3..8655aa1 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -17,6 +17,7 @@ from wradvis import utils from wradvis.config import conf +from wradvis.glcanvas import RadolanLineWidget class TimeSlider(QtGui.QSlider): @@ -243,16 +244,29 @@ def paintEvent( self, event ): class DockBox(QtGui.QWidget): - def __init__(self, parent=None): + def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)): super(DockBox, self).__init__(parent) self.layout = QtGui.QGridLayout() self.setLayout(self.layout) - self.setSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) + self.setSizePolicy(size_pol[0], size_pol[1]) self.props = parent.props +class GraphBox(DockBox): + def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)): + super(GraphBox, self).__init__(parent) + + self.parent = parent + self.graph = RadolanLineWidget(self) + self.layout.addWidget(self.graph, 0, 0) + self.setSizePolicy(size_pol[0], size_pol[1]) + + def connect_signals(self): + self.graph.connect_signals() + + class MouseBox(DockBox): def __init__(self, parent=None): super(MouseBox, self).__init__(parent) @@ -264,6 +278,7 @@ def __init__(self, parent=None): self.mousePointLLLabel = QtGui.QLabel("LL", self) self.mousePointXY = QtGui.QLabel("", self) self.mousePointLL = QtGui.QLabel("", self) + self.mousePointS = QtGui.QLabel("", self) self.hline2 = QtGui.QFrame() self.hline2.setFrameShape(QtGui.QFrame.HLine) self.hline2.setFrameShadow(QtGui.QFrame.Sunken) @@ -272,19 +287,26 @@ def __init__(self, parent=None): self.layout.addWidget(self.mousePointXY, 0, 2) self.layout.addWidget(self.mousePointLLLabel, 1, 1) self.layout.addWidget(self.mousePointLL, 1, 2) - self.layout.addWidget(self.hline2, 2, 0, 1, 3) + self.layout.addWidget(QtGui.QLabel("XYsel", self), 2, 1) + self.layout.addWidget(self.mousePointS, 2, 2) + self.layout.addWidget(self.hline2, 3, 0, 1, 3) # connect to signal self.parent.rwidget.rcanvas.mouse_moved.connect(self.mouse_moved) + self.parent.rwidget.rcanvas.mouse_pressed.connect(self.mouse_moved) self.parent.rwidget.pcanvas.mouse_moved.connect(self.mouse_moved) self.parent.mwidget.rcanvas.mouse_moved.connect(self.mouse_moved) def mouse_moved(self, event): # todo: check if originating from mpl and adapt self.r0 correctly point = self.parent.iwidget.canvas._mouse_position + point1 = self.parent.iwidget.canvas._mouse_press_position self.mousePointXY.setText( "({0:d}, {1:d})".format(int(point[0]), int(point[1]))) + self.mousePointS.setText( + "({0:d}, {1:d})".format(int(point1[0]), int(point1[1]))) + # Todo: move this all to utils and use a generalized # ll-retrieving function if self.parent.props.product != 'DX': @@ -561,6 +583,7 @@ def update_props(self): if self.mem is not None: self.mem.close() self.mem = self.create_nc_dataset() + print(self.mem.variables['data'].chunking()) self.signal_props_changed.emit(0) def create_nc_dataset(self): diff --git a/wradvis/utils.py b/wradvis/utils.py index 53109e2..9fec35e 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -357,7 +357,8 @@ def create_ncdf(filename, attrs, units='original'): unit = 'mm d-1' prod = id.createVariable('data', vtype, ('time', 'y', 'x',), - fill_value=fillvalue, zlib=True, complevel=4) + fill_value=fillvalue, zlib=True, complevel=4, + chunksizes=(1, 32, 32)) # accept data as unsigned byte without scaling, crucial for writing already packed data #prod.set_auto_maskandscale(False) prod.units = unit From e812ca2f10e69d7911049e0c1864759d587da5c1 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Thu, 15 Sep 2016 18:15:10 +0200 Subject: [PATCH 05/16] WIP: fix position (x/y- changed), due to image being upside down --- wradvis/glcanvas.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 3cea97c..7eb7937 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -481,8 +481,9 @@ def connect_signals(self): self.parent.parent.iwidget.canvas.mouse_pressed.connect(self.set_line) def set_line(self, event): - pos = event.pos - y = self.parent.props.mem.variables['data'][:, pos[0], pos[1]] + pos = self.parent.parent.iwidget.canvas._mouse_press_position + print("POS:", pos[0], pos[1]) + y = self.parent.props.mem.variables['data'][:, int(pos[1]), int(pos[0])] x = np.arange(len(y)) try: self.plot.parent = None From 562a9b401fec762ca919eb7d7b4566c0756a6038 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Thu, 15 Sep 2016 20:06:10 +0200 Subject: [PATCH 06/16] WIP: changed plot axis label width, set time_limits --- wradvis/glcanvas.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 7eb7937..b930d57 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -30,28 +30,28 @@ def __init__(self, **kwargs): self.grid = self.central_widget.add_grid(margin=10) self.grid.spacing = 0 - self.pl_title = Label("Plot Title", color='white') - self.pl_title.height_max = 40 + self.pl_title = Label("Time Graph", color='white') + self.pl_title.height_max = 25 self.grid.add_widget(self.pl_title, row=0, col=0, col_span=3) self.yaxis = AxisWidget(orientation='left') - self.yaxis.width_max = 40 + self.yaxis.width_max = 25 self.grid.add_widget(self.yaxis, row=1, col=1) - self.ylabel = Label('Y Axis', rotation=-90, color='white') - self.ylabel.width_max = 40 + self.ylabel = Label('Units', rotation=-90, color='white') + self.ylabel.width_max = 25 self.grid.add_widget(self.ylabel, row=1, col=0) self.xaxis = AxisWidget(orientation='bottom') - self.xaxis.height_max = 40 + self.xaxis.height_max = 25 self.grid.add_widget(self.xaxis, row=2, col=2) - self.xlabel = Label('X Axis', color='white') - self.xlabel.height_max = 40 + self.xlabel = Label('Time', color='white') + self.xlabel.height_max = 25 self.grid.add_widget(self.xlabel, row=3, col=0, col_span=3) self.right_padding = self.grid.add_widget(row=0, col=3, row_span=3) - self.right_padding.width_max = 50 + self.right_padding.width_max = 30 self.view = self.grid.add_view(row=1, col=2, border_color='white') #data = np.random.normal(size=(24, 2)) @@ -61,7 +61,12 @@ def __init__(self, **kwargs): #data[3] = -10, 10 #data[4] = -10, -10 #self.plot = Line(data, parent=view.scene) - self.view.camera = 'panzoom' + self.cam = PanZoomCamera(name="PanZoom", + #rect=Rect(0, 0, 900, 900), + #aspect=1, + parent=self.view.scene) + self.view.camera = self.cam + self.xaxis.link_view(self.view) self.yaxis.link_view(self.view) @@ -482,7 +487,7 @@ def connect_signals(self): def set_line(self, event): pos = self.parent.parent.iwidget.canvas._mouse_press_position - print("POS:", pos[0], pos[1]) + print("POS:", int(pos[0]), int(pos[1])) y = self.parent.props.mem.variables['data'][:, int(pos[1]), int(pos[0])] x = np.arange(len(y)) try: @@ -490,3 +495,7 @@ def set_line(self, event): except: pass self.plot = Line(np.squeeze(np.dstack((x, y))), parent=self.canvas.view.scene) + self.set_time_limits() + + def set_time_limits(self): + self.canvas.cam.set_range(x=(0,23)) From 20b35c648d71aa8bf68658a5f849898fa2561378 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 09:19:07 +0200 Subject: [PATCH 07/16] WIP: add cursors to gl_canvas, need reworking --- wradvis/glcanvas.py | 48 ++++++++++++++++++++++++++++++++++++++++++- wradvis/properties.py | 10 ++++++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index b930d57..5407a85 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -174,6 +174,26 @@ def __init__(self, **kwargs): # create cities (Markers and Text Visuals self.create_cities() + # cursor lines + self.vline = Line(parent=self.view.scene, color="darkgrey") + self.vline.transform = STTransform( + translate=(0, 0, -2.5)) + self.hline = Line(parent=self.view.scene, color="darkgrey") + self.hline.transform = STTransform( + translate=(0, 0, -2.5)) + self.vline.visible = False + self.hline.visible = False + + # pick lines + self.vpline = Line(parent=self.view.scene, color="red") + self.vpline.transform = STTransform( + translate=(0, 0, -5)) + self.hpline = Line(parent=self.view.scene, color="red") + self.hpline.transform = STTransform( + translate=(0, 0, -5)) + self.vpline.visible = False + self.hpline.visible = False + # create PanZoomCamera self.cam = PanZoomCamera(name="PanZoom", rect=Rect(0, 0, 900, 900), @@ -242,6 +262,7 @@ def create_cities(self): def on_mouse_move(self, event): point = self.scene.node_transform(self.image).map(event.pos)[:2] self._mouse_position = point + self.update_cursor() # emit signal self.mouse_moved(event) @@ -265,11 +286,27 @@ def on_mouse_press(self, event): point = self.scene.node_transform(self.image).map(event.pos)[:2] self._mouse_press_position = point + self.update_select_cursor() self.mouse_pressed(event) def on_key_press(self, event): self.key_pressed(event) + def update_cursor(self): + pos = self._mouse_position + # if self.hline.visible and self.vline.visible: + # ll = utils.radolan_to_wgs84(pos + self.r0) + # self.cursor_text.text = '({0:3.3f}, {1:3.3f})'.format(ll[0], ll[1]) + # self.cursor_text.pos = pos + (0, 0) + self.vline.set_data(np.array([[pos[0], 0], [pos[0], 899]])) + self.hline.set_data(np.array([[0, pos[1]], [899, pos[1]]])) + + def update_select_cursor(self): + pos = self._mouse_press_position + self.vpline.set_data(np.array([[pos[0], 0], [pos[0], 899]])) + self.hpline.set_data(np.array([[0, pos[1]], [899, pos[1]]])) + + class PTransform(PolarTransform): glsl_imap = """ @@ -433,6 +470,7 @@ def __init__(self, parent=None): def connect_signals(self): self.parent.mediabox.signal_time_slider_changed.connect(self.set_time) + self.parent.mousebox.signal_toggle_Cursor.connect(self.toggle_cursor) def set_canvas(self, type): if type == 'DX': @@ -466,6 +504,14 @@ def set_clim(self, clim): self.canvas.image.clim = clim self.cbar.cbar.clim = clim + def toggle_cursor(self, state): + self.canvas.hline.visible = state + self.canvas.vline.visible = state + self.canvas.hpline.visible = state + self.canvas.vpline.visible = state + self.canvas.update() + + class RadolanLineWidget(QtGui.QWidget): def __init__(self, parent=None): @@ -498,4 +544,4 @@ def set_line(self, event): self.set_time_limits() def set_time_limits(self): - self.canvas.cam.set_range(x=(0,23)) + self.canvas.cam.set_range(margin=0.)# x=(0,23)) diff --git a/wradvis/properties.py b/wradvis/properties.py index 8655aa1..b9cf16e 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -268,6 +268,9 @@ def connect_signals(self): class MouseBox(DockBox): + + signal_toggle_Cursor = QtCore.pyqtSignal(int, name='toggleCursor') + def __init__(self, parent=None): super(MouseBox, self).__init__(parent) @@ -279,6 +282,9 @@ def __init__(self, parent=None): self.mousePointXY = QtGui.QLabel("", self) self.mousePointLL = QtGui.QLabel("", self) self.mousePointS = QtGui.QLabel("", self) + self.curCheckBox = QtGui.QCheckBox() + self.curCheckBox.stateChanged.connect(self.signal_toggle_Cursor) + self.hline2 = QtGui.QFrame() self.hline2.setFrameShape(QtGui.QFrame.HLine) self.hline2.setFrameShadow(QtGui.QFrame.Sunken) @@ -289,7 +295,9 @@ def __init__(self, parent=None): self.layout.addWidget(self.mousePointLL, 1, 2) self.layout.addWidget(QtGui.QLabel("XYsel", self), 2, 1) self.layout.addWidget(self.mousePointS, 2, 2) - self.layout.addWidget(self.hline2, 3, 0, 1, 3) + self.layout.addWidget(QtGui.QLabel("Activate Cursor", self), 3, 0) + self.layout.addWidget(self.curCheckBox, 3, 1) + self.layout.addWidget(self.hline2, 4, 0, 1, 3) # connect to signal self.parent.rwidget.rcanvas.mouse_moved.connect(self.mouse_moved) From 19927efe07f14c751d6e2d59afcde3c0a70a8904 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 10:22:37 +0200 Subject: [PATCH 08/16] WIP: add cursors to line_canvas, also need some reworking --- wradvis/glcanvas.py | 30 +++++++++++++++++++++++++----- wradvis/properties.py | 3 +++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 5407a85..818cb60 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -13,9 +13,10 @@ from vispy.util.event import EventEmitter from vispy.visuals.transforms import STTransform, MatrixTransform, PolarTransform from vispy.scene.cameras import PanZoomCamera -from vispy.scene.visuals import Image, ColorBar, Markers, Text, Line +from vispy.scene.visuals import Image, ColorBar, Markers, Text, Line, InfiniteLine from vispy.scene.widgets import Label, AxisWidget from vispy.geometry import Rect +from vispy.color import Color from wradvis import utils from wradvis.config import conf @@ -65,6 +66,20 @@ def __init__(self, **kwargs): #rect=Rect(0, 0, 900, 900), #aspect=1, parent=self.view.scene) + + # cursors + self.low_line = InfiniteLine(parent=self.view.scene, color=Color("blue").RGBA) + self.low_line.transform = STTransform( + translate=(0, 0, -2.5)) + self.high_line = InfiniteLine(parent=self.view.scene, color=Color("blue").RGBA) + self.high_line.transform = STTransform( + translate=(0, 0, -2.5)) + self.cur_line = InfiniteLine(parent=self.view.scene, + color=Color("red").RGBA) + self.cur_line.transform = STTransform( + translate=(0, 0, -2.5)) + + self.view.camera = self.cam @@ -512,7 +527,6 @@ def toggle_cursor(self, state): self.canvas.update() - class RadolanLineWidget(QtGui.QWidget): def __init__(self, parent=None): super(RadolanLineWidget, self).__init__(parent) @@ -528,12 +542,11 @@ def sizeHint(self): return QtCore.QSize(650, 200) def connect_signals(self): - print(self.parent.parent) self.parent.parent.iwidget.canvas.mouse_pressed.connect(self.set_line) + self.parent.parent.mediabox.signal_time_properties_changed.connect(self.set_time_limits) def set_line(self, event): pos = self.parent.parent.iwidget.canvas._mouse_press_position - print("POS:", int(pos[0]), int(pos[1])) y = self.parent.props.mem.variables['data'][:, int(pos[1]), int(pos[0])] x = np.arange(len(y)) try: @@ -544,4 +557,11 @@ def set_line(self, event): self.set_time_limits() def set_time_limits(self): - self.canvas.cam.set_range(margin=0.)# x=(0,23)) + low = self.parent.parent.mediabox.range.low() + high = self.parent.parent.mediabox.range.high() + cur = self.parent.parent.mediabox.time_slider.value() + + self.canvas.low_line.set_data(low) + self.canvas.high_line.set_data(high) + self.canvas.cur_line.set_data(cur) + self.canvas.cam.set_range(margin=0.) diff --git a/wradvis/properties.py b/wradvis/properties.py index b9cf16e..efe30b2 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -360,6 +360,7 @@ class MediaBox(DockBox): signal_playpause_changed = QtCore.pyqtSignal(name='startstop') signal_time_slider_changed = QtCore.pyqtSignal(int, name='timeChanged') signal_speed_changed = QtCore.pyqtSignal(int, name='speedChanged') + signal_time_properties_changed = QtCore.pyqtSignal(name='timepropsChanged') def __init__(self, parent=None): super(MediaBox, self).__init__(parent) @@ -525,9 +526,11 @@ def range_update(self, low, high): def range_changed(self): self.range.setLow(self.range_start.currentIndex()) self.range.setHigh(self.range_end.currentIndex()) + self.signal_time_properties_changed.emit() def current_time_changed(self, value): self.time_slider.setValue(value) + self.signal_time_properties_changed.emit() # Properties From a77b07dd5bb94e51a23460b531780320930f9e15 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 11:48:37 +0200 Subject: [PATCH 09/16] WIP: double click into linecanvas sets current time --- wradvis/glcanvas.py | 23 +++++++++++++++++++++++ wradvis/mplcanvas.py | 2 +- wradvis/properties.py | 13 ++++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 818cb60..e78b25b 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -66,6 +66,10 @@ def __init__(self, **kwargs): #rect=Rect(0, 0, 900, 900), #aspect=1, parent=self.view.scene) + # data line + self.plot = Line(parent=self.view.scene) + self.plot.transform = STTransform( + translate=(0, 0, -2.5)) # cursors self.low_line = InfiniteLine(parent=self.view.scene, color=Color("blue").RGBA) @@ -86,8 +90,17 @@ def __init__(self, **kwargs): self.xaxis.link_view(self.view) self.yaxis.link_view(self.view) + self._mouse_position = None + self.mouse_double_clicked = EventEmitter(source=self, type="mouse_double_clicked") + self.freeze() + def on_mouse_double_click(self, event): + point = self.scene.node_transform(self.plot).map(event.pos)[:2] + self._mouse_position = point + # emit signal + self.mouse_double_clicked(event) + class ColorbarCanvas(SceneCanvas): @@ -528,6 +541,9 @@ def toggle_cursor(self, state): class RadolanLineWidget(QtGui.QWidget): + + #signal_mouse_double_clicked = QtCore.pyqtSignal(int, name='mouseDblClicked') + def __init__(self, parent=None): super(RadolanLineWidget, self).__init__(parent) self.parent = parent @@ -544,6 +560,7 @@ def sizeHint(self): def connect_signals(self): self.parent.parent.iwidget.canvas.mouse_pressed.connect(self.set_line) self.parent.parent.mediabox.signal_time_properties_changed.connect(self.set_time_limits) + #self.canvas.mouse_double_clicked(self.mouse_double_clicked) def set_line(self, event): pos = self.parent.parent.iwidget.canvas._mouse_press_position @@ -554,6 +571,8 @@ def set_line(self, event): except: pass self.plot = Line(np.squeeze(np.dstack((x, y))), parent=self.canvas.view.scene) + self.plot.transform = STTransform( + translate=(0, 0, -2.5)) self.set_time_limits() def set_time_limits(self): @@ -565,3 +584,7 @@ def set_time_limits(self): self.canvas.high_line.set_data(high) self.canvas.cur_line.set_data(cur) self.canvas.cam.set_range(margin=0.) + + #def mouse_double_clicked(self): + # self.signal_mouse_double_clicked.emit(self.canvas._mouse_position) + diff --git a/wradvis/mplcanvas.py b/wradvis/mplcanvas.py index fcbcb70..5e9f927 100644 --- a/wradvis/mplcanvas.py +++ b/wradvis/mplcanvas.py @@ -101,7 +101,7 @@ def on_key_press(self, event): def on_move(self, event): if event.inaxes: ax = event.inaxes # the axes instance - print('data coords %f %f' % (event.xdata, event.ydata)) + #print('data coords %f %f' % (event.xdata, event.ydata)) self._mouse_position = np.array([event.xdata, event.ydata]) self.mouse_moved.emit(event) diff --git a/wradvis/properties.py b/wradvis/properties.py index efe30b2..902f17f 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -107,7 +107,6 @@ def paintEvent(self, event): else: opt.subControls = QtGui.QStyle.SC_SliderHandle - print(self.tickPosition(), self.NoTicks) if self.tickPosition() != self.NoTicks: opt.subControls |= QtGui.QStyle.SC_SliderTickmarks @@ -365,6 +364,8 @@ class MediaBox(DockBox): def __init__(self, parent=None): super(MediaBox, self).__init__(parent) + self.parent = parent + # Time Slider self.time_slider = QtGui.QSlider(QtCore.Qt.Horizontal) self.time_slider.setMinimum(0) @@ -425,8 +426,11 @@ def __init__(self, parent=None): def connect_signals(self): + # Todo: this seems not the correct way of doing this, needs fixing + # there must be a nicer method, than traversing over objects self.props.props_changed.connect(self.update_props) self.props.parent.timer.timeout.connect(self.seekforward) + self.parent.graphbox.graph.canvas.mouse_double_clicked.connect(self.current_time_changed) def createMediaButtons(self): iconSize = QtCore.QSize(18, 18) @@ -529,10 +533,14 @@ def range_changed(self): self.signal_time_properties_changed.emit() def current_time_changed(self, value): - self.time_slider.setValue(value) + try: + self.time_slider.setValue(value) + except TypeError: + self.time_slider.setValue(round(self.parent.graphbox.graph.canvas._mouse_position[0])) self.signal_time_properties_changed.emit() + # Properties class Properties(QtCore.QObject): """ @@ -594,7 +602,6 @@ def update_props(self): if self.mem is not None: self.mem.close() self.mem = self.create_nc_dataset() - print(self.mem.variables['data'].chunking()) self.signal_props_changed.emit(0) def create_nc_dataset(self): From 10b1918ca3d32ad669daf61dbe1aad1faee63447 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 13:35:38 +0200 Subject: [PATCH 10/16] WIP: save and load netcdf, need reworking to get in line with conf --- wradvis/gui.py | 14 ++++++++++++++ wradvis/properties.py | 18 +++++++++++++++++- wradvis/utils.py | 5 +++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/wradvis/gui.py b/wradvis/gui.py index 9da30a1..3350a7c 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -86,11 +86,25 @@ def createActions(self): statusTip='Save project', triggered=self.props.save_conf) + # Load netcdf (data file) + self.loadNC = QtGui.QAction("&Load NetCDF", self, + shortcut="Ctrl+L", + statusTip='Load netCDF data file', + triggered=self.props.load_data) + + # Save netcdf (data file) + self.saveNC = QtGui.QAction("Save &NetCDF", self, + shortcut="Ctrl+N", + statusTip='Save data file as netCDF', + triggered=self.props.save_data) + def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.setDataDir) self.fileMenu.addAction(self.openConf) self.fileMenu.addAction(self.saveConf) + self.fileMenu.addAction(self.loadNC) + self.fileMenu.addAction(self.saveNC) self.toolsMenu = self.menuBar().addMenu('&Tools') diff --git a/wradvis/properties.py b/wradvis/properties.py index 902f17f..fa089af 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -540,7 +540,6 @@ def current_time_changed(self, value): self.signal_time_properties_changed.emit() - # Properties class Properties(QtCore.QObject): """ @@ -581,6 +580,21 @@ def open_conf(self): conf.read_file(f) self.update_props() + def load_data(self): + newfile = QtGui.QFileDialog.getSaveFileName(self.parent, + 'Save NetCDF File', '', + 'netCDF (*.nc)') + self.mem = utils.open_ncdf(newfile) + #conf["source"]["product"] = self.mem.[] + + def save_data(self): + newfile = QtGui.QFileDialog.getSaveFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') + oldfile = os.path.abspath(self.mem.filepath()) + self.mem.close() + os.rename(oldfile, newfile) + self.mem = utils.open_ncdf(newfile) + + def update_props(self): self.dir = conf["dirs"]["data"] self.product = conf["source"]["product"] @@ -602,6 +616,8 @@ def update_props(self): if self.mem is not None: self.mem.close() self.mem = self.create_nc_dataset() + #self.mem.filepath() + #print(dir(self.mem))#.disk_format) self.signal_props_changed.emit(0) def create_nc_dataset(self): diff --git a/wradvis/utils.py b/wradvis/utils.py index 9fec35e..b5d3cbe 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -204,6 +204,11 @@ def read_RADOLAN_composite(fname, missing=-9999, loaddata=True): return arr, attrs + +def open_ncdf(filename): + return nc.Dataset(filename, 'r', format='NETCDF4') + + def create_ncdf(filename, attrs, units='original'): nx = attrs['ncol'] From 2564166af5ffb284c04ef246565183fbd7a1ee4f Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 14:03:43 +0200 Subject: [PATCH 11/16] WIP: fix loading netcdf, add product type as `source` attribute --- wradvis/properties.py | 12 ++++++++---- wradvis/utils.py | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/wradvis/properties.py b/wradvis/properties.py index fa089af..f575b65 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -500,7 +500,10 @@ def update_props(self): stime = utils.get_dt(self.props.mem.variables['time'][0]) etime = utils.get_dt(self.props.mem.variables['time'][-1]) - rtime = [str(item) for item in self.props.mem.variables['time'][1:-1]] + try: + rtime = [utils.get_dt(item).strftime("%H:%M") for item in self.props.mem.variables['time'][1:-1]] + except ValueError: + rtime = [str(item) for item in self.props.mem.variables['time'][1:-1]] self.range_start.clear() self.range_start.addItem(stime.strftime("%H:%M")) @@ -581,11 +584,13 @@ def open_conf(self): self.update_props() def load_data(self): - newfile = QtGui.QFileDialog.getSaveFileName(self.parent, + newfile = QtGui.QFileDialog.getOpenFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') self.mem = utils.open_ncdf(newfile) - #conf["source"]["product"] = self.mem.[] + conf["source"]["product"] = self.mem.variables['data'].source + self.signal_props_changed.emit(0) + def save_data(self): newfile = QtGui.QFileDialog.getSaveFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') @@ -594,7 +599,6 @@ def save_data(self): os.rename(oldfile, newfile) self.mem = utils.open_ncdf(newfile) - def update_props(self): self.dir = conf["dirs"]["data"] self.product = conf["source"]["product"] diff --git a/wradvis/utils.py b/wradvis/utils.py index b5d3cbe..edc86db 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -382,6 +382,7 @@ def create_ncdf(filename, attrs, units='original'): if missing_value: prod.missing_value = missing_value prod.version = 'RADOLAN {0}'.format(version) + prod.source = prodtype prod.comment = 'NO COMMENT' id_str1 = id.createVariable('radars', 'S128', ('time',), zlib=True, From 664853720c26c0fe6ab2a8e9f8218577107cb4ed Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 14:18:55 +0200 Subject: [PATCH 12/16] WIP: fix loading times from nectdf --- wradvis/properties.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wradvis/properties.py b/wradvis/properties.py index f575b65..d5e25d8 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -517,13 +517,13 @@ def update_props(self): self.current_time.addItem(stime.strftime("%H:%M")) self.current_time.addItems(rtime) self.current_time.addItem(etime.strftime("%H:%M")) - self.time_slider.setMaximum(self.props.frames) + self.time_slider.setMaximum(len(rtime) + 1) self.time_slider.setValue(0) self.current_date.setText(stime.strftime("%Y-%M-%d")) self.range.setMinimum(0) - self.range.setMaximum(self.props.frames) + self.range.setMaximum(len(rtime) + 1) self.range.setLow(0) - self.range.setHigh(self.props.frames) + self.range.setHigh(len(rtime) + 1) self.range_update(self.range.low(), self.range.high()) def range_update(self, low, high): @@ -587,6 +587,8 @@ def load_data(self): newfile = QtGui.QFileDialog.getOpenFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') + if self.mem is not None: + self.mem.close() self.mem = utils.open_ncdf(newfile) conf["source"]["product"] = self.mem.variables['data'].source self.signal_props_changed.emit(0) From da1c7e342c39301f8350428d8efa407dcec991b5 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 16 Sep 2016 16:28:27 +0200 Subject: [PATCH 13/16] WIP: add additional class layer --- wradvis/glcanvas.py | 135 ++++++++++++++++++++++-------------------- wradvis/gui.py | 7 ++- wradvis/properties.py | 14 +++-- 3 files changed, 85 insertions(+), 71 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index e78b25b..2558119 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -22,14 +22,53 @@ from wradvis.config import conf -class AxisCanvas(SceneCanvas): - def __init__(self, **kwargs): - super(AxisCanvas, self).__init__(keys='interactive', **kwargs) +class GlCanvas(SceneCanvas): + def __init__(self, vrow=0, vcol=0, **kwargs): + super(GlCanvas, self).__init__(**kwargs) - self.size = 450, 200 self.unfreeze() self.grid = self.central_widget.add_grid(margin=10) self.grid.spacing = 0 + self.view = self.grid.add_view(row=vrow, col=vcol, + border_color='white') + + self.transitem = None + + self._mouse_position = None + self._mouse_press_position = (0, 0) + + self.mouse_double_clicked = EventEmitter(source=self, + type="mouse_double_clicked") + self.mouse_moved = EventEmitter(source=self, type="mouse_moved") + self.mouse_pressed = EventEmitter(source=self, type="mouse_pressed") + self.key_pressed = EventEmitter(source=self, type="key_pressed") + + self.freeze() + + def on_mouse_double_click(self, event): + point = self.scene.node_transform(self.transitem).map(event.pos)[:2] + self._mouse_position = point + # emit signal + self.mouse_double_clicked(event) + + def on_key_press(self, event): + self.key_pressed(event) + + def on_mouse_move(self, event): + point = self.scene.node_transform(self.transitem).map(event.pos)[:2] + self._mouse_position = point + + def on_mouse_press(self, event): + point = self.scene.node_transform(self.transitem).map(event.pos)[:2] + self._mouse_press_position = point + + +class AxisCanvas(GlCanvas): + def __init__(self, **kwargs): + super(AxisCanvas, self).__init__(**kwargs) + + self.size = 450, 200 + self.unfreeze() self.pl_title = Label("Time Graph", color='white') self.pl_title.height_max = 25 @@ -54,14 +93,6 @@ def __init__(self, **kwargs): self.right_padding = self.grid.add_widget(row=0, col=3, row_span=3) self.right_padding.width_max = 30 - self.view = self.grid.add_view(row=1, col=2, border_color='white') - #data = np.random.normal(size=(24, 2)) - #data[0] = -10, -10 - #data[1] = 10, -10 - #data[2] = 10, 10 - #data[3] = -10, 10 - #data[4] = -10, -10 - #self.plot = Line(data, parent=view.scene) self.cam = PanZoomCamera(name="PanZoom", #rect=Rect(0, 0, 900, 900), #aspect=1, @@ -71,6 +102,8 @@ def __init__(self, **kwargs): self.plot.transform = STTransform( translate=(0, 0, -2.5)) + self.transitem = self.plot + # cursors self.low_line = InfiniteLine(parent=self.view.scene, color=Color("blue").RGBA) self.low_line.transform = STTransform( @@ -90,20 +123,10 @@ def __init__(self, **kwargs): self.xaxis.link_view(self.view) self.yaxis.link_view(self.view) - self._mouse_position = None - self.mouse_double_clicked = EventEmitter(source=self, type="mouse_double_clicked") - self.freeze() - def on_mouse_double_click(self, event): - point = self.scene.node_transform(self.plot).map(event.pos)[:2] - self._mouse_position = point - # emit signal - self.mouse_double_clicked(event) - - -class ColorbarCanvas(SceneCanvas): +class ColorbarCanvas(GlCanvas): def __init__(self, **kwargs): super(ColorbarCanvas, self).__init__(keys='interactive', **kwargs) @@ -112,12 +135,8 @@ def __init__(self, **kwargs): # unfreeze needed to add more elements self.unfreeze() + self.events.mouse_move.block() - # add grid central widget - self.grid = self.central_widget.add_grid() - - # add view to grid - self.view = self.grid.add_view(row=0, col=0) self.view.border_color = (0.5, 0.5, 0.5, 1) # initialize colormap, we take cubehelix for now @@ -147,8 +166,7 @@ def __init__(self, **kwargs): self.freeze() -class RadolanCanvas(SceneCanvas): - +class RadolanCanvas(GlCanvas): def __init__(self, **kwargs): super(RadolanCanvas, self).__init__(keys='interactive', **kwargs) @@ -158,18 +176,6 @@ def __init__(self, **kwargs): # unfreeze needed to add more elements self.unfreeze() - # add grid central widget - self.grid = self.central_widget.add_grid() - - # add view to grid - self.view = self.grid.add_view(row=0, col=0) - self.view.border_color = (0.5, 0.5, 0.5, 1) - - # add signal emitters - self.mouse_moved = EventEmitter(source=self, type="mouse_moved") - self.mouse_pressed = EventEmitter(source=self, type="mouse_pressed") - self.key_pressed = EventEmitter(source=self, type="key_pressed") - # block double clicks self.events.mouse_double_click.block() @@ -190,6 +196,8 @@ def __init__(self, **kwargs): clim=(0,50), parent=self.view.scene) + self.transitem = self.image + self.images.append(self.image) # add transform to Image @@ -203,20 +211,26 @@ def __init__(self, **kwargs): self.create_cities() # cursor lines - self.vline = Line(parent=self.view.scene, color="darkgrey") + self.vline = InfiniteLine(parent=self.view.scene, + color=Color("blue").RGBA) self.vline.transform = STTransform( - translate=(0, 0, -2.5)) - self.hline = Line(parent=self.view.scene, color="darkgrey") + translate=(0, 0, -10)) + self.hline = InfiniteLine(parent=self.view.scene, + color=Color("blue").RGBA, + vertical=False) self.hline.transform = STTransform( - translate=(0, 0, -2.5)) + translate=(0, 0, -10)) self.vline.visible = False self.hline.visible = False # pick lines - self.vpline = Line(parent=self.view.scene, color="red") + self.vpline = InfiniteLine(parent=self.view.scene, + color=Color("red").RGBA) self.vpline.transform = STTransform( translate=(0, 0, -5)) - self.hpline = Line(parent=self.view.scene, color="red") + self.hpline = InfiniteLine(parent=self.view.scene, + color=Color("red").RGBA, + vertical=False) self.hpline.transform = STTransform( translate=(0, 0, -5)) self.vpline.visible = False @@ -230,8 +244,6 @@ def __init__(self, **kwargs): self.view.camera = self.cam - self._mouse_position = None - self._mouse_press_position = (0, 0) self.freeze() # print FPS to console, vispy SceneCanvas internal function self.measure_fps() @@ -288,13 +300,13 @@ def create_cities(self): i += 1 def on_mouse_move(self, event): - point = self.scene.node_transform(self.image).map(event.pos)[:2] - self._mouse_position = point + super(RadolanCanvas, self).on_mouse_move(event) self.update_cursor() # emit signal self.mouse_moved(event) def on_mouse_press(self, event): + super(RadolanCanvas, self).on_mouse_press(event) self.view.interactive = False for v in self.visuals_at(event.pos, radius=30): @@ -312,8 +324,6 @@ def on_mouse_press(self, event): self.view.interactive = True - point = self.scene.node_transform(self.image).map(event.pos)[:2] - self._mouse_press_position = point self.update_select_cursor() self.mouse_pressed(event) @@ -322,18 +332,15 @@ def on_key_press(self, event): def update_cursor(self): pos = self._mouse_position - # if self.hline.visible and self.vline.visible: - # ll = utils.radolan_to_wgs84(pos + self.r0) - # self.cursor_text.text = '({0:3.3f}, {1:3.3f})'.format(ll[0], ll[1]) - # self.cursor_text.pos = pos + (0, 0) - self.vline.set_data(np.array([[pos[0], 0], [pos[0], 899]])) - self.hline.set_data(np.array([[0, pos[1]], [899, pos[1]]])) + self.vline.set_data(pos=pos[0]) + self.hline.set_data(pos=pos[1]) + # needed to work, set_data doesn't update by itself + self.update() def update_select_cursor(self): pos = self._mouse_press_position - self.vpline.set_data(np.array([[pos[0], 0], [pos[0], 899]])) - self.hpline.set_data(np.array([[0, pos[1]], [899, pos[1]]])) - + self.vpline.set_data(pos[0]) + self.hpline.set_data(pos[1]) class PTransform(PolarTransform): @@ -547,7 +554,7 @@ class RadolanLineWidget(QtGui.QWidget): def __init__(self, parent=None): super(RadolanLineWidget, self).__init__(parent) self.parent = parent - self.canvas = AxisCanvas() + self.canvas = AxisCanvas(vrow=1, vcol=2) self.canvas.create_native() self.canvas.native.setParent(self) self.hbl = QtGui.QHBoxLayout() diff --git a/wradvis/gui.py b/wradvis/gui.py index 3350a7c..37aceec 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -11,7 +11,8 @@ # other wradvis imports from wradvis.glcanvas import RadolanWidget from wradvis.mplcanvas import MplWidget -from wradvis.properties import Properties, MediaBox, SourceBox, MouseBox, GraphBox +from wradvis.properties import Properties, MediaBox, SourceBox, \ + MouseBox, GraphBox from wradvis import utils from wradvis.config import conf @@ -134,7 +135,9 @@ def createDockWindows(self): dock = QtGui.QDockWidget("Time Graphs", self) dock.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) - self.graphbox = GraphBox(self, size_pol=(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)) + size_pol = (QtGui.QSizePolicy.MinimumExpanding, + QtGui.QSizePolicy.MinimumExpanding) + self.graphbox = GraphBox(self, size_pol=size_pol) dock.setWidget(self.graphbox) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dock) self.toolsMenu.addAction(dock.toggleViewAction()) diff --git a/wradvis/properties.py b/wradvis/properties.py index d5e25d8..638acc4 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -229,7 +229,6 @@ def __pixelPosToRangeValue(self, pos): opt.upsideDown) - class LongLabel(QLabel): def paintEvent( self, event ): painter = QPainter(self) @@ -243,7 +242,8 @@ def paintEvent( self, event ): class DockBox(QtGui.QWidget): - def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)): + def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, + QtGui.QSizePolicy.Fixed)): super(DockBox, self).__init__(parent) self.layout = QtGui.QGridLayout() @@ -254,7 +254,8 @@ def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, QtGui.QSizePo class GraphBox(DockBox): - def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)): + def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, + QtGui.QSizePolicy.Fixed)): super(GraphBox, self).__init__(parent) self.parent = parent @@ -341,7 +342,7 @@ def __init__(self, parent=None): self.dirname = "None" #conf["dirs"]["time_slider"] self.dirLabel = LongLabel(self.dirname) - self.layout.addWidget(LongLabel("Current time_slider directory"), 0, 0, 1, 7) + self.layout.addWidget(LongLabel("Current data directory"), 0, 0, 1, 7) self.layout.addWidget(self.dirLabel, 1, 0, 1, 7) self.dirLabel.setFixedWidth(200) palette.setColor(QtGui.QPalette.Foreground, QtCore.Qt.darkGreen) @@ -595,7 +596,10 @@ def load_data(self): def save_data(self): - newfile = QtGui.QFileDialog.getSaveFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') + newfile = QtGui.QFileDialog.getSaveFileName(self.parent, + 'Save NetCDF File', + '', + 'netCDF (*.nc)') oldfile = os.path.abspath(self.mem.filepath()) self.mem.close() os.rename(oldfile, newfile) From dc4af70dd90f512699491ee969cd78c3c2fdf102 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Mon, 19 Sep 2016 13:42:10 +0200 Subject: [PATCH 14/16] FIX: fix DXCanvas working with netcdf, minor class shaping in glcanvas --- wradvis/glcanvas.py | 146 +++++++++++----------- wradvis/properties.py | 4 +- wradvis/utils.py | 285 ++++++++++++++++++++++++------------------ 3 files changed, 241 insertions(+), 194 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 2558119..a5a2544 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -33,9 +33,12 @@ def __init__(self, vrow=0, vcol=0, **kwargs): border_color='white') self.transitem = None + self.cursor = None self._mouse_position = None self._mouse_press_position = (0, 0) + self._cursor_position = None + self._cursor_press_position = (0, 0) self.mouse_double_clicked = EventEmitter(source=self, type="mouse_double_clicked") @@ -56,11 +59,61 @@ def on_key_press(self, event): def on_mouse_move(self, event): point = self.scene.node_transform(self.transitem).map(event.pos)[:2] + cursor = self.scene.node_transform(self.cursor).map(event.pos)[:2] self._mouse_position = point + self._cursor_position = cursor + self.update_cursor() + self.mouse_moved(event) def on_mouse_press(self, event): point = self.scene.node_transform(self.transitem).map(event.pos)[:2] + cursor = self.scene.node_transform(self.cursor).map(event.pos)[:2] self._mouse_press_position = point + self._cursor_press_position = cursor + self.update_select_cursor() + + def add_cursor(self): + # cursor lines + self.vline = InfiniteLine(parent=self.view.scene, + color=Color("blue").RGBA) + self.vline.transform = STTransform( + translate=(0, 0, -10)) + self.hline = InfiniteLine(parent=self.view.scene, + color=Color("blue").RGBA, + vertical=False) + self.hline.transform = STTransform( + translate=(0, 0, -10)) + self.vline.visible = False + self.hline.visible = False + + self.cursor = self.hline + + # pick lines + self.vpline = InfiniteLine(parent=self.view.scene, + color=Color("red").RGBA) + self.vpline.transform = STTransform( + translate=(0, 0, -5)) + self.hpline = InfiniteLine(parent=self.view.scene, + color=Color("red").RGBA, + vertical=False) + self.hpline.transform = STTransform( + translate=(0, 0, -5)) + self.vpline.visible = False + self.hpline.visible = False + + def update_cursor(self): + pos = self._cursor_position + self.vline.set_data(pos=pos[0]) + self.hline.set_data(pos=pos[1]) + # needed to work, set_data doesn't update by itself + self.update() + + def update_select_cursor(self): + pos = self._cursor_press_position + self.vpline.set_data(pos[0]) + self.hpline.set_data(pos[1]) + self.update() + class AxisCanvas(GlCanvas): @@ -116,10 +169,8 @@ def __init__(self, **kwargs): self.cur_line.transform = STTransform( translate=(0, 0, -2.5)) - self.view.camera = self.cam - self.xaxis.link_view(self.view) self.yaxis.link_view(self.view) @@ -135,7 +186,10 @@ def __init__(self, **kwargs): # unfreeze needed to add more elements self.unfreeze() + self.events.mouse_move.block() + self.events.mouse_press.block() + self.events.mouse_double_click.block() self.view.border_color = (0.5, 0.5, 0.5, 1) @@ -211,30 +265,7 @@ def __init__(self, **kwargs): self.create_cities() # cursor lines - self.vline = InfiniteLine(parent=self.view.scene, - color=Color("blue").RGBA) - self.vline.transform = STTransform( - translate=(0, 0, -10)) - self.hline = InfiniteLine(parent=self.view.scene, - color=Color("blue").RGBA, - vertical=False) - self.hline.transform = STTransform( - translate=(0, 0, -10)) - self.vline.visible = False - self.hline.visible = False - - # pick lines - self.vpline = InfiniteLine(parent=self.view.scene, - color=Color("red").RGBA) - self.vpline.transform = STTransform( - translate=(0, 0, -5)) - self.hpline = InfiniteLine(parent=self.view.scene, - color=Color("red").RGBA, - vertical=False) - self.hpline.transform = STTransform( - translate=(0, 0, -5)) - self.vpline.visible = False - self.hpline.visible = False + self.add_cursor() # create PanZoomCamera self.cam = PanZoomCamera(name="PanZoom", @@ -299,14 +330,9 @@ def create_cities(self): self.text.append(t) i += 1 - def on_mouse_move(self, event): - super(RadolanCanvas, self).on_mouse_move(event) - self.update_cursor() - # emit signal - self.mouse_moved(event) - def on_mouse_press(self, event): super(RadolanCanvas, self).on_mouse_press(event) + self.view.interactive = False for v in self.visuals_at(event.pos, radius=30): @@ -324,24 +350,6 @@ def on_mouse_press(self, event): self.view.interactive = True - self.update_select_cursor() - self.mouse_pressed(event) - - def on_key_press(self, event): - self.key_pressed(event) - - def update_cursor(self): - pos = self._mouse_position - self.vline.set_data(pos=pos[0]) - self.hline.set_data(pos=pos[1]) - # needed to work, set_data doesn't update by itself - self.update() - - def update_select_cursor(self): - pos = self._mouse_press_position - self.vpline.set_data(pos[0]) - self.hpline.set_data(pos[1]) - class PTransform(PolarTransform): glsl_imap = """ @@ -353,6 +361,16 @@ class PTransform(PolarTransform): } """ + def imap(self, coords): + coords = np.array(coords) + ret = np.empty(coords.shape, coords.dtype) + ret[..., 0] = np.rad2deg(np.arctan2(coords[..., 0], + coords[..., 1]) + np.pi) + ret[..., 1] = (coords[..., 0] ** 2 + coords[..., 1] ** 2) ** 0.5 + for i in range(2, coords.shape[-1]): + ret[..., i] = coords[..., i] + return ret + class PolarImage(Image): def __init__(self, source=None, **kwargs): @@ -388,20 +406,13 @@ def __init__(self, source=None, **kwargs): self.freeze() -class DXCanvas(SceneCanvas): +class DXCanvas(GlCanvas): def __init__(self, **kwargs): super(DXCanvas, self).__init__(keys='interactive', **kwargs) self.size = 450, 450 self.unfreeze() - # add grid central widget - self.grid = self.central_widget.add_grid() - - # add view to grid - self.view = self.grid.add_view(row=0, col=0) - self.view.border_color = (0.5, 0.5, 0.5, 1) - # This is hardcoded now, but maybe handled as the data source changes self.img_data = np.zeros((360, 128)) @@ -420,11 +431,12 @@ def __init__(self, **kwargs): clim=(-32.5, 95), parent=self.view.scene) + self.transitem = self.image + self.images.append(self.image) - # add signal emitters - self.mouse_moved = EventEmitter(source=self, type="mouse_moved") - self.key_pressed = EventEmitter(source=self, type="key_pressed") + # cursor lines + self.add_cursor() # block double clicks self.events.mouse_double_click.block() @@ -438,21 +450,9 @@ def __init__(self, **kwargs): self.view.camera = self.cam - self._mouse_position = None - self.freeze() self.measure_fps() - def on_mouse_move(self, event): - tr = self.scene.node_transform(self.image) - point = tr.map(event.pos)[:2] - # todo: we should actually move this into PTransform in the future - point[0] += np.pi - point[0] = np.rad2deg(point[0]) - self._mouse_position = point - # emit signal - self.mouse_moved(event) - def on_key_press(self, event): self.key_pressed(event) diff --git a/wradvis/properties.py b/wradvis/properties.py index 638acc4..2e2f798 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -592,9 +592,11 @@ def load_data(self): self.mem.close() self.mem = utils.open_ncdf(newfile) conf["source"]["product"] = self.mem.variables['data'].source + # activate the correct canvas (grid or polar) + self.product = conf["source"]["product"] + self.parent.iwidget.set_canvas(self.product) self.signal_props_changed.emit(0) - def save_data(self): newfile = QtGui.QFileDialog.getSaveFileName(self.parent, 'Save NetCDF File', diff --git a/wradvis/utils.py b/wradvis/utils.py index edc86db..c39e2bd 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -12,6 +12,7 @@ import numpy as np import netCDF4 as nc import datetime as dt +import pytz from wradvis.config import conf @@ -209,80 +210,10 @@ def open_ncdf(filename): return nc.Dataset(filename, 'r', format='NETCDF4') -def create_ncdf(filename, attrs, units='original'): - - nx = attrs['ncol'] - ny = attrs['nrow'] - version = attrs['radolanversion'] - precision = attrs['precision'] - prodtype = attrs['producttype'] - int = attrs['intervalseconds'] - nodata = attrs['nodataflag'] - missing_value = None - - # create NETCDF4 file in memory - id = nc.Dataset(filename, 'w', format='NETCDF4', diskless=True, persist=True) - #id.close() - #id = nc.Dataset(filename, 'a', format='NETCDF4') - - # create dimensions - yid = id.createDimension('y', ny) - xid = id.createDimension('x', nx) - tbid = id.createDimension('nv', 2) - tid = id.createDimension('time', None) - - # create and set the grid x variable that serves as x coordinate - xiid = id.createVariable('x', 'f4', ('x')) - xiid.axis = 'X' - xiid.units = 'km' - xiid.long_name = 'x coordinate of projection' - xiid.standard_name = 'projection_x_coordinate' - - # create and set the grid y variable that serves as y coordinate - yiid = id.createVariable('y', 'f4', ('y')) - yiid.axis = 'Y' - yiid.units = 'km' - yiid.long_name = 'y coordinate of projection' - yiid.standard_name = 'projection_y_coordinate' - - # create time variable - tiid = id.createVariable('time', 'f8', ('time',)) - tiid.axis = 'T' - tiid.units = 'seconds since 1970-01-01 00:00:00' - tiid.standard_name = 'time' - tiid.bounds = 'time_bnds' - - # create time bounds variable - tbiid = id.createVariable('time_bnds', 'f8', ('time', 'nv',)) - - # create grid variable that serves as lon coordinate - lonid = id.createVariable('lon', 'f4', ('y', 'x',), zlib=True, complevel=4) - lonid.units = 'degrees_east' - lonid.standard_name = 'longitude' - lonid.long_name = 'longitude coordinate' - - # create grid variable that serves as lat coordinate - latid = id.createVariable('lat', 'f4', ('y', 'x',), zlib=True, complevel=4) - latid.units = 'degrees_north' - latid.standard_name = 'latitude' - latid.long_name = 'latitude coordinate' - - # create projection variable that defines the projection according to CF-Metadata standards - coordid = id.createVariable('polar_stereographic', 'i4', zlib=True, - complevel=2) - coordid.grid_mapping_name = 'polar_stereographic' - coordid.straight_vertical_longitude_from_pole = np.float32(10.) - coordid.latitude_of_projection_origin = np.float32(90.) - coordid.standard_parallel = np.float32(60.) - coordid.false_easting = np.float32(0.) - coordid.false_northing = np.float32(0.) - coordid.earth_model_of_projection = 'spherical' - coordid.earth_radius_of_projection = np.float32(6370.04) - coordid.units = 'km' - coordid.ancillary_data = 'grid_latitude grid_longitude' - coordid.long_name = 'polar_stereographic' - - if prodtype in ['RX', 'EX']: +def get_netcdf_varattrs(attrs, units='original'): + product = attrs['producttype'] + precision = attrs.get('precision', 1) + if product in ['DX', 'RX', 'EX']: if units == 'original': scale_factor = None add_offset = None @@ -300,7 +231,7 @@ def create_ncdf(filename, attrs, units='original'): standard_name = 'equivalent_reflectivity_factor' long_name = 'equivalent_reflectivity_factor' - elif prodtype in ['RY', 'RZ', 'EY', 'EZ']: + elif product in ['RY', 'RZ', 'EY', 'EZ']: if units == 'original': scale_factor = None add_offset = None @@ -322,7 +253,7 @@ def create_ncdf(filename, attrs, units='original'): standard_name = 'rainfall_amount' long_name = 'rainfall_amount' - elif prodtype in ['RH', 'RB', 'RW', 'RL', 'RU', 'EH', 'EB', 'EW']: + elif product in ['RH', 'RB', 'RW', 'RL', 'RU', 'EH', 'EB', 'EW']: if units == 'original': scale_factor = None add_offset = None @@ -344,7 +275,7 @@ def create_ncdf(filename, attrs, units='original'): standard_name = 'rainfall_amount' long_name = 'rainfall_amount' - elif prodtype in ['SQ', 'SH', 'SF']: + elif product in ['SQ', 'SH', 'SF']: scale_factor = np.float32(precision) add_offset = np.float(0.) valid_min = np.int32(0) @@ -361,38 +292,139 @@ def create_ncdf(filename, attrs, units='original'): elif int == (1440 * 60): unit = 'mm d-1' - prod = id.createVariable('data', vtype, ('time', 'y', 'x',), - fill_value=fillvalue, zlib=True, complevel=4, + vattr = {'scale_factor': scale_factor, 'add_offset': add_offset, + 'valid_min': valid_min, 'valid_max': valid_max, + 'missing_value': missing_value, 'fillvalue': fillvalue, + 'vtype': vtype, 'standard_name': standard_name, + 'long_name': long_name, 'unit':unit} + + return vattr + + +def create_ncdf(filename, attrs, units='original'): + + product = attrs['producttype'] + if product not in ['DX']: + nx = attrs['ncol'] + ny = attrs['nrow'] + version = attrs['radolanversion'] + else: + nx = attrs['clutter'].shape[0] + ny = attrs['clutter'].shape[1] + version = attrs['version'] + + #precision = attrs['precision'] + + #int = attrs['intervalseconds'] + #nodata = attrs['nodataflag'] + #missing_value = None + + # create NETCDF4 file in memory + id = nc.Dataset(filename, 'w', format='NETCDF4', diskless=True, persist=True) + #id.close() + #id = nc.Dataset(filename, 'a', format='NETCDF4') + + # create dimensions + yid = id.createDimension('y', ny) + xid = id.createDimension('x', nx) + tbid = id.createDimension('nv', 2) + tid = id.createDimension('time', None) + + # create and set the grid x variable that serves as x coordinate + xiid = id.createVariable('x', 'f4', ('x')) + xiid.axis = 'X' + xiid.units = 'km' + xiid.long_name = 'x coordinate of projection' + xiid.standard_name = 'projection_x_coordinate' + + # create and set the grid y variable that serves as y coordinate + yiid = id.createVariable('y', 'f4', ('y')) + yiid.axis = 'Y' + yiid.units = 'km' + yiid.long_name = 'y coordinate of projection' + yiid.standard_name = 'projection_y_coordinate' + + # create time variable + tiid = id.createVariable('time', 'f8', ('time',)) + tiid.axis = 'T' + tiid.units = 'seconds since 1970-01-01 00:00:00' + tiid.standard_name = 'time' + tiid.bounds = 'time_bnds' + + # create time bounds variable + tbiid = id.createVariable('time_bnds', 'f8', ('time', 'nv',)) + + if product not in ['DX']: + # create grid variable that serves as lon coordinate + lonid = id.createVariable('lon', 'f4', ('x', 'y',), zlib=True, complevel=4) + lonid.units = 'degrees_east' + lonid.standard_name = 'longitude' + lonid.long_name = 'longitude coordinate' + + # create grid variable that serves as lat coordinate + latid = id.createVariable('lat', 'f4', ('x', 'y',), zlib=True, complevel=4) + latid.units = 'degrees_north' + latid.standard_name = 'latitude' + latid.long_name = 'latitude coordinate' + + # create projection variable that defines the projection according to CF-Metadata standards + coordid = id.createVariable('polar_stereographic', 'i4', zlib=True, + complevel=2) + coordid.grid_mapping_name = 'polar_stereographic' + coordid.straight_vertical_longitude_from_pole = np.float32(10.) + coordid.latitude_of_projection_origin = np.float32(90.) + coordid.standard_parallel = np.float32(60.) + coordid.false_easting = np.float32(0.) + coordid.false_northing = np.float32(0.) + coordid.earth_model_of_projection = 'spherical' + coordid.earth_radius_of_projection = np.float32(6370.04) + coordid.units = 'km' + coordid.ancillary_data = 'grid_latitude grid_longitude' + coordid.long_name = 'polar_stereographic' + + vattr = get_netcdf_varattrs(attrs, units=units) + + prod = id.createVariable('data', vattr['vtype'], ('time', 'x', 'y',), + fill_value=vattr['fillvalue'], zlib=True, complevel=4, chunksizes=(1, 32, 32)) # accept data as unsigned byte without scaling, crucial for writing already packed data #prod.set_auto_maskandscale(False) - prod.units = unit - prod.standard_name = standard_name - prod.long_name = long_name - prod.grid_mapping = 'polar_stereographic' - prod.coordinates = 'lat lon' - if scale_factor: - prod.scale_factor = scale_factor - if add_offset: - prod.add_offset = add_offset - if valid_min: - prod.valid_min = valid_min - if valid_max: - prod.valid_max = valid_max - if missing_value: - prod.missing_value = missing_value + prod.units = vattr['unit'] + prod.standard_name = vattr['standard_name'] + prod.long_name = vattr['long_name'] + + if product not in ['DX']: + prod.grid_mapping = 'polar_stereographic' + prod.coordinates = 'lat lon' + + if vattr['scale_factor']: + prod.scale_factor = vattr['scale_factor'] + if vattr['add_offset']: + prod.add_offset = vattr['add_offset'] + if vattr['valid_min']: + prod.valid_min = vattr['valid_min'] + if vattr['valid_max']: + prod.valid_max = vattr['valid_max'] + if vattr['missing_value']: + prod.missing_value = vattr['missing_value'] prod.version = 'RADOLAN {0}'.format(version) - prod.source = prodtype + prod.source = product prod.comment = 'NO COMMENT' id_str1 = id.createVariable('radars', 'S128', ('time',), zlib=True, complevel=4) # create GLOBAL attributes - id.Title = 'RADOLAN {0} Composite'.format(prodtype) + if product not in ['DX']: + id.Title = 'RADOLAN {0} Composite'.format(product) + id.History = 'Data transferred from RADOLAN composite format to netcdf using wradvis version 0.1 by wradlib developers' + id.Source = 'DWD C-Band Weather Radar Network, Original RADOLAN Data by Deutscher Wetterdienst' + else: + id.Title = '{0} - radarid: {1}'.format(product, attrs['radarid']) + id.History = 'Data transferred from DX format to netcdf using wradvis version 0.1 by wradlib developers' + id.Source = 'DWD C-Band Weather Radar Network, Original DX Data by Deutscher Wetterdienst' + id.Institution = 'Data owned by Deutscher Wetterdienst' - id.Source = 'DWD C-Band Weather Radar Network, Original RADOLAN Data by Deutscher Wetterdienst' - id.History = 'Data transferred from RADOLAN composite format to netcdf using wradvis version 0.1 by wradlib developers' id.Conventions = 'CF-1.6 where applicable' utcnow = dt.datetime.utcnow() id.Processing_date = utcnow.strftime("%Y-%m-%dT%H:%M:%S") @@ -400,17 +432,20 @@ def create_ncdf(filename, attrs, units='original'): id.Comments = 'blank' id.License = 'DWD Licenses' - - # fill general variables - ny, nx = attrs['ncol'], attrs['nrow'] - radolan_grid_xy = wrl.georef.get_radolan_grid(nx, ny) - xarr = radolan_grid_xy[0, :, 0] - yarr = radolan_grid_xy[:, 0, 1] - radolan_grid_ll = wrl.georef.get_radolan_grid(nx, ny, wgs84=True) - lons = radolan_grid_ll[..., 0] - lats = radolan_grid_ll[..., 1] - + if product not in ['DX']: + #ny, nx = attrs['ncol'], attrs['nrow'] + radolan_grid_xy = wrl.georef.get_radolan_grid(nx, ny) + xarr = radolan_grid_xy[0, :, 0] + yarr = radolan_grid_xy[:, 0, 1] + radolan_grid_ll = wrl.georef.get_radolan_grid(nx, ny, wgs84=True) + lons = radolan_grid_ll[..., 0] + lats = radolan_grid_ll[..., 1] + id.variables['lat'][:] = lats + id.variables['lon'][:] = lons + else: + xarr = np.arange(nx) + yarr = np.arange(ny) id.variables['x'][:] = xarr id.variables['x'].valid_min = xarr[0] @@ -418,34 +453,44 @@ def create_ncdf(filename, attrs, units='original'): id.variables['y'][:] = yarr id.variables['y'].valid_min = yarr[0] id.variables['y'].valid_max = yarr[-1] - id.variables['lat'][:] = lats - id.variables['lon'][:] = lons + return id def add_ncdf(id, data, time_index, attrs): + + if attrs['producttype'] in ['DX']: + mas = True + else: + mas = False + # remove clutter, nodata and secondary data from raw files # wrap with if/else if necessary - if attrs['cluttermask'] is not None: - data.flat[attrs['cluttermask']] = id.variables[ + cluttermask = attrs.get('cluttermask', None) + nodatamask = attrs.get('nodatamask', None) + if cluttermask is not None: + data.flat[cluttermask] = id.variables[ 'data'].missing_value - if attrs['nodatamask'] is not None: - data.flat[attrs['nodatamask']] = id.variables[ + if nodatamask is not None: + data.flat[nodatamask] = id.variables[ 'data'].missing_value #if attrs['secondary'] is not None: # data.flat[attrs['secondary']] = id.variables[ # attrs['producttype'].lower()].missing_value - id.variables['data'].set_auto_maskandscale(False) + + id.variables['data'].set_auto_maskandscale(mas) id.variables['data'][time_index, :, :] = data - id.variables['data'].set_auto_maskandscale(True) - print(attrs['datetime']) - delta = attrs['datetime'] - dt.datetime.utcfromtimestamp(0) + id.variables['data'].set_auto_maskandscale(~mas) + try: + delta = attrs['datetime'] - dt.datetime.utcfromtimestamp(0) + except TypeError: + delta = attrs['datetime'] - dt.datetime.utcfromtimestamp(0).replace(tzinfo=pytz.UTC) id.variables['time'][time_index] = delta.total_seconds() id.variables['time_bnds'][time_index, :] = delta.total_seconds() - # id.variables['time_bnds'][time_index,1] = delta.total_seconds() + attrs['intervalseconds'] - id.variables['radars'][time_index] = ','.join(attrs['radarlocations']) + #id.variables['time_bnds'][time_index,1] = delta.total_seconds() + attrs['intervalseconds'] + #id.variables['radars'][time_index] = ','.join(attrs['radarlocations']) def get_dt(unix): From 2c2a2eb0f5121b694953203a729ded2b582b2533 Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Mon, 19 Sep 2016 14:57:27 +0200 Subject: [PATCH 15/16] FIX: fix issues with data alignment, cursors and signals --- wradvis/glcanvas.py | 15 ++++++++++----- wradvis/utils.py | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index a5a2544..12e1f1a 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -71,6 +71,7 @@ def on_mouse_press(self, event): self._mouse_press_position = point self._cursor_press_position = cursor self.update_select_cursor() + self.mouse_pressed(event) def add_cursor(self): # cursor lines @@ -146,6 +147,8 @@ def __init__(self, **kwargs): self.right_padding = self.grid.add_widget(row=0, col=3, row_span=3) self.right_padding.width_max = 30 + self.add_cursor() + self.cam = PanZoomCamera(name="PanZoom", #rect=Rect(0, 0, 900, 900), #aspect=1, @@ -453,9 +456,6 @@ def __init__(self, **kwargs): self.freeze() self.measure_fps() - def on_key_press(self, event): - self.key_pressed(event) - def add_image(self, radar): # this adds an image to the images list image = PolarImage(source=radar, @@ -565,13 +565,18 @@ def sizeHint(self): return QtCore.QSize(650, 200) def connect_signals(self): - self.parent.parent.iwidget.canvas.mouse_pressed.connect(self.set_line) + self.parent.parent.iwidget.rcanvas.mouse_pressed.connect(self.set_line) + self.parent.parent.iwidget.pcanvas.mouse_pressed.connect(self.set_line) self.parent.parent.mediabox.signal_time_properties_changed.connect(self.set_time_limits) #self.canvas.mouse_double_clicked(self.mouse_double_clicked) def set_line(self, event): pos = self.parent.parent.iwidget.canvas._mouse_press_position - y = self.parent.props.mem.variables['data'][:, int(pos[1]), int(pos[0])] + + if self.parent.props.mem.variables['data'].source in ['DX']: + y = self.parent.props.mem.variables['data'][:, int(pos[0]), int(pos[1])] + else: + y = self.parent.props.mem.variables['data'][:, int(pos[1]), int(pos[0])] x = np.arange(len(y)) try: self.plot.parent = None diff --git a/wradvis/utils.py b/wradvis/utils.py index c39e2bd..5943486 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -305,8 +305,8 @@ def create_ncdf(filename, attrs, units='original'): product = attrs['producttype'] if product not in ['DX']: - nx = attrs['ncol'] - ny = attrs['nrow'] + nx = attrs['nrow'] + ny = attrs['ncol'] version = attrs['radolanversion'] else: nx = attrs['clutter'].shape[0] From 0d31a574417a84e74fcc3d6922da97948ecd5bcf Mon Sep 17 00:00:00 2001 From: Kai Muehlbauer Date: Fri, 3 Aug 2018 14:45:30 +0200 Subject: [PATCH 16/16] WIP: use pyqt5, use latest wradlib 1.0, minor fixes --- examples/radolan_viewer.py | 6 +- setup.py | 25 ++++++ wradvis/__init__.py | 7 +- wradvis/config.py | 7 +- wradvis/glcanvas.py | 24 +++-- wradvis/gui.py | 78 ++++++++-------- wradvis/mplcanvas.py | 27 +++--- wradvis/properties.py | 177 ++++++++++++++++++++----------------- wradvis/utils.py | 36 ++++---- 9 files changed, 213 insertions(+), 174 deletions(-) create mode 100644 setup.py diff --git a/examples/radolan_viewer.py b/examples/radolan_viewer.py index 42e3889..08d4055 100644 --- a/examples/radolan_viewer.py +++ b/examples/radolan_viewer.py @@ -5,9 +5,9 @@ # ----------------------------------------------------------------------------- #!/usr/bin/env python -import sys - from wradvis import gui +import matplotlib +matplotlib.use('Qt5Agg') if __name__ == '__main__': - gui.start(sys) + gui.start() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2417ea9 --- /dev/null +++ b/setup.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. + + +def setup_package(): + + from setuptools import setup, find_packages + + metadata = dict( + name='wradvis', + version='0.1.0', + packages=find_packages(), + entry_points={ + 'gui_scripts': [ + 'wradvis = wradvis.gui:start' + ] + }, + ) + + setup(**metadata) + + +if __name__ == '__main__': + setup_package() diff --git a/wradvis/__init__.py b/wradvis/__init__.py index db6504e..291401e 100644 --- a/wradvis/__init__.py +++ b/wradvis/__init__.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. from . import gui diff --git a/wradvis/config.py b/wradvis/config.py index 06f0e61..bff84cf 100644 --- a/wradvis/config.py +++ b/wradvis/config.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. """ """ diff --git a/wradvis/glcanvas.py b/wradvis/glcanvas.py index 12e1f1a..b33f574 100644 --- a/wradvis/glcanvas.py +++ b/wradvis/glcanvas.py @@ -1,13 +1,11 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. import numpy as np -from PyQt4 import QtGui, QtCore +from PyQt5 import QtCore +from PyQt5.QtWidgets import (QSplitter, QWidget, QHBoxLayout) from vispy.scene import SceneCanvas from vispy.util.event import EventEmitter @@ -116,7 +114,6 @@ def update_select_cursor(self): self.update() - class AxisCanvas(GlCanvas): def __init__(self, **kwargs): super(AxisCanvas, self).__init__(**kwargs) @@ -403,7 +400,8 @@ def __init__(self, source=None, **kwargs): # the translation moves the image to centere the ppi rot = MatrixTransform() rot.rotate(180, (0, 0, 1)) - self.transform = (STTransform(translate=(self.range+xoff, self.range+yoff, 0)) * + self.transform = (STTransform(translate=(self.range+xoff, + self.range+yoff, 0)) * rot * PTransform()) self.freeze() @@ -468,7 +466,7 @@ def add_image(self, radar): self.images.append(image) -class RadolanWidget(QtGui.QWidget): +class RadolanWidget(QWidget): def __init__(self, parent=None): super(RadolanWidget, self).__init__(parent) self.parent = parent @@ -489,7 +487,7 @@ def __init__(self, parent=None): self.swapper['R'] = self.rcanvas.native self.swapper['P'] = self.pcanvas.native - self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal) + self.splitter = QSplitter(QtCore.Qt.Horizontal) self.splitter.addWidget(self.swapper['R']) self.splitter.addWidget(self.swapper['P']) self.swapper['P'].hide() @@ -499,7 +497,7 @@ def __init__(self, parent=None): self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(1, 1) self.splitter.setStretchFactor(2, 0) - self.hbl = QtGui.QHBoxLayout() + self.hbl = QHBoxLayout() self.hbl.addWidget(self.splitter) self.setLayout(self.hbl) @@ -547,7 +545,7 @@ def toggle_cursor(self, state): self.canvas.update() -class RadolanLineWidget(QtGui.QWidget): +class RadolanLineWidget(QWidget): #signal_mouse_double_clicked = QtCore.pyqtSignal(int, name='mouseDblClicked') @@ -557,7 +555,7 @@ def __init__(self, parent=None): self.canvas = AxisCanvas(vrow=1, vcol=2) self.canvas.create_native() self.canvas.native.setParent(self) - self.hbl = QtGui.QHBoxLayout() + self.hbl = QHBoxLayout() self.hbl.addWidget(self.canvas.native) self.setLayout(self.hbl) diff --git a/wradvis/gui.py b/wradvis/gui.py index 37aceec..e12c8c0 100644 --- a/wradvis/gui.py +++ b/wradvis/gui.py @@ -1,13 +1,16 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. + +import sys -from PyQt4 import QtGui, QtCore +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import (QMainWindow, QApplication, QSplitter, QAction, + QDockWidget, QSizePolicy) import vispy +import matplotlib +matplotlib.use('Qt5Agg') # other wradvis imports from wradvis.glcanvas import RadolanWidget from wradvis.mplcanvas import MplWidget @@ -17,7 +20,7 @@ from wradvis.config import conf -class MainWindow(QtGui.QMainWindow): +class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) @@ -49,7 +52,7 @@ def __init__(self, parent=None): self.props = Properties(self) # add Horizontal Splitter and the three widgets - self.splitter = QtGui.QSplitter(QtCore.Qt.Horizontal) + self.splitter = QSplitter(QtCore.Qt.Horizontal) self.splitter.addWidget(self.swapper[0]) self.splitter.addWidget(self.swapper[1]) self.swapper[1].hide() @@ -72,29 +75,29 @@ def connect_signals(self): def createActions(self): # Set directory - self.setDataDir = QtGui.QAction("&Set directory", self, - statusTip='Set directory', - triggered=self.props.set_datadir) + self.setDataDir = QAction("&Set directory", self, + statusTip='Set directory', + triggered=self.props.set_datadir) # Open project (configuration) - self.openConf = QtGui.QAction("&Open project", self, - shortcut="Ctrl+O", - statusTip='Open project', - triggered=self.props.open_conf) + self.openConf = QAction("&Open project", self) + self.openConf.setShortcut("Ctrl+O") + self.openConf.setStatusTip('Open project') + self.openConf.triggered.connect(self.props.open_conf) # Save project (configuration) - self.saveConf = QtGui.QAction("&Save project", self, + self.saveConf = QAction("&Save project", self, shortcut="Ctrl+S", statusTip='Save project', triggered=self.props.save_conf) # Load netcdf (data file) - self.loadNC = QtGui.QAction("&Load NetCDF", self, + self.loadNC = QAction("&Load NetCDF", self, shortcut="Ctrl+L", statusTip='Load netCDF data file', triggered=self.props.load_data) # Save netcdf (data file) - self.saveNC = QtGui.QAction("Save &NetCDF", self, + self.saveNC = QAction("Save &NetCDF", self, shortcut="Ctrl+N", statusTip='Save data file as netCDF', triggered=self.props.save_data) @@ -112,31 +115,31 @@ def createMenus(self): self.helpMenu = self.menuBar().addMenu('&Help') def createDockWindows(self): - dock = QtGui.QDockWidget("Radar Source Data", self) + dock = QDockWidget("Radar Source Data", self) dock.setAllowedAreas(QtCore.Qt.RightDockWidgetArea) self.sourcebox = SourceBox(self) dock.setWidget(self.sourcebox) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.toolsMenu.addAction(dock.toggleViewAction()) - dock = QtGui.QDockWidget("Media Handling", self) + dock = QDockWidget("Media Handling", self) dock.setAllowedAreas(QtCore.Qt.RightDockWidgetArea) self.mediabox = MediaBox(self) dock.setWidget(self.mediabox) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.toolsMenu.addAction(dock.toggleViewAction()) - dock = QtGui.QDockWidget("Mouse Interaction", self) + dock = QDockWidget("Mouse Interaction", self) dock.setAllowedAreas(QtCore.Qt.RightDockWidgetArea) self.mousebox = MouseBox(self) dock.setWidget(self.mousebox) self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.toolsMenu.addAction(dock.toggleViewAction()) - dock = QtGui.QDockWidget("Time Graphs", self) + dock = QDockWidget("Time Graphs", self) dock.setAllowedAreas(QtCore.Qt.BottomDockWidgetArea) - size_pol = (QtGui.QSizePolicy.MinimumExpanding, - QtGui.QSizePolicy.MinimumExpanding) + size_pol = (QSizePolicy.MinimumExpanding, + QSizePolicy.MinimumExpanding) self.graphbox = GraphBox(self, size_pol=size_pol) dock.setWidget(self.graphbox) self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, dock) @@ -156,19 +159,24 @@ def keyPressEvent(self, event): text = event.text() else: text = event.text - if text == 'c': - self.swapper = self.swapper[::-1] - self.iwidget = self.swapper[0] - self.swapper[0].show() - self.swapper[0].setFocus() - self.swapper[1].hide() - - -def start(arg): - appQt = QtGui.QApplication(arg.argv) + print(event) + # Todo: fully implement MPLCanvas + #if text == 'c': + # self.swapper = self.swapper[::-1] + # self.iwidget = self.swapper[0] + # self.swapper[0].show() + # self.swapper[0].setFocus() + # self.swapper[1].hide() + + +def start(args=None): + if args is None: + args = sys.argv[1:] + appQt = QApplication(args) win = MainWindow() win.show() appQt.exec_() + if __name__ == '__main__': - print('wradview: Calling module as main...') + start() diff --git a/wradvis/mplcanvas.py b/wradvis/mplcanvas.py index 5e9f927..b7ef892 100644 --- a/wradvis/mplcanvas.py +++ b/wradvis/mplcanvas.py @@ -1,18 +1,18 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. import numpy as np import matplotlib -matplotlib.use('Qt4Agg') +matplotlib.use('Qt5Agg') + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import (QMainWindow, QApplication, QSplitter, QAction, + QDockWidget, QSizePolicy, QWidget, QHBoxLayout) -from PyQt4 import QtGui, QtCore -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from matplotlib.cm import get_cmap from mpl_toolkits.axes_grid1 import make_axes_locatable @@ -34,8 +34,8 @@ def __init__(self):#, parent, props): # we define the widget as expandable FigureCanvas.setSizePolicy(self, - QtGui.QSizePolicy.Expanding, - QtGui.QSizePolicy.Expanding) + QSizePolicy.Expanding, + QSizePolicy.Expanding) # notify the system of updated policy FigureCanvas.updateGeometry(self) @@ -55,6 +55,7 @@ def __init__(self):#, parent, props): self.ax.set_xlim([grid[..., 0].min(), grid[..., 0].max()]) self.ax.set_ylim([grid[..., 1].min(), grid[..., 1].max()]) self._mouse_position = None + self._mouse_press_position = (0, 0) self.create_cities() @@ -106,9 +107,9 @@ def on_move(self, event): self.mouse_moved.emit(event) -class MplWidget(QtGui.QWidget): +class MplWidget(QWidget): def __init__(self): - QtGui.QWidget.__init__(self) + QWidget.__init__(self) self.rcanvas = MplCanvas() self.pcanvas = MplCanvas() @@ -129,7 +130,7 @@ def __init__(self): #self.splitter.setStretchFactor(0, 1) #self.splitter.setStretchFactor(1, 1) #self.splitter.setStretchFactor(2, 0) - self.hbl = QtGui.QHBoxLayout() + self.hbl = QHBoxLayout() self.hbl.addWidget(self.swapper['R']) self.hbl.addWidget(self.swapper['P']) self.setLayout(self.hbl) diff --git a/wradvis/properties.py b/wradvis/properties.py index 2e2f798..63c223b 100644 --- a/wradvis/properties.py +++ b/wradvis/properties.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. """ """ @@ -11,16 +8,24 @@ import os import glob from datetime import datetime as dt +import numpy as np +from urllib.request import urlopen + +from PyQt5 import QtCore, QtGui, QtWidgets +from PyQt5.QtWidgets import (QMainWindow, QApplication, QLabel, QWidget, + QSpinBox, QComboBox, QCheckBox, QFrame, QSlider, + QGridLayout, QVBoxLayout, QToolButton, + QPushButton, QStyle, QFileDialog, QSizePolicy, + QStyleOptionSlider) +from PyQt5.QtGui import QFontMetrics, QPainter, QPalette -from PyQt4 import QtGui, QtCore -from PyQt4.QtGui import QLabel, QFontMetrics, QPainter from wradvis import utils from wradvis.config import conf from wradvis.glcanvas import RadolanLineWidget -class TimeSlider(QtGui.QSlider): +class TimeSlider(QSlider): """ This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. @@ -67,8 +72,8 @@ def __init__(self, *args): self._low = self.minimum() self._high = self.maximum() - self.pressed_control = QtGui.QStyle.SC_None - self.hover_control = QtGui.QStyle.SC_None + self.pressed_control = QStyle.SC_None + self.hover_control = QStyle.SC_None self.click_offset = 0 # 0 for the low, 1 for the high, -1 for both @@ -93,38 +98,38 @@ def paintEvent(self, event): # based on # http://qt.gitorious.org/qt/qt/blobs/master/src/gui/widgets/qslider.cpp - painter = QtGui.QPainter(self) - style = QtGui.QApplication.style() + painter = QPainter(self) + style = QApplication.style() for i, value in enumerate([self._low, self._high]): - opt = QtGui.QStyleOptionSlider() + opt = QStyleOptionSlider() self.initStyleOption(opt) # Only draw the groove for the first slider so it doesn't get drawn # on top of the existing ones every time if i == 0: - opt.subControls = QtGui.QStyle.SC_SliderGroove | QtGui.QStyle.SC_SliderHandle + opt.subControls = QStyle.SC_SliderGroove | QStyle.SC_SliderHandle else: - opt.subControls = QtGui.QStyle.SC_SliderHandle + opt.subControls = QStyle.SC_SliderHandle if self.tickPosition() != self.NoTicks: - opt.subControls |= QtGui.QStyle.SC_SliderTickmarks + opt.subControls |= QStyle.SC_SliderTickmarks if self.pressed_control: opt.activeSubControls = self.pressed_control - opt.state |= QtGui.QStyle.State_Sunken + opt.state |= QStyle.State_Sunken else: opt.activeSubControls = self.hover_control opt.sliderPosition = value opt.sliderValue = value - style.drawComplexControl(QtGui.QStyle.CC_Slider, opt, painter, self) + style.drawComplexControl(QStyle.CC_Slider, opt, painter, self) def mousePressEvent(self, event): event.accept() - style = QtGui.QApplication.style() + style = QApplication.style() button = event.button() # In a normal slider control, when the user clicks on a point in the @@ -134,7 +139,7 @@ def mousePressEvent(self, event): # slider parts if button: - opt = QtGui.QStyleOptionSlider() + opt = QStyleOptionSlider() self.initStyleOption(opt) self.active_slider = -1 @@ -152,7 +157,7 @@ def mousePressEvent(self, event): break if self.active_slider < 0: - self.pressed_control = QtGui.QStyle.SC_SliderHandle + self.pressed_control = QStyle.SC_SliderHandle self.click_offset = self.__pixelPosToRangeValue(self.__pick(event.pos())) self.triggerAction(self.SliderMove) self.setRepeatAction(self.SliderNoAction) @@ -160,13 +165,13 @@ def mousePressEvent(self, event): event.ignore() def mouseMoveEvent(self, event): - if self.pressed_control != QtGui.QStyle.SC_SliderHandle: + if self.pressed_control != QStyle.SC_SliderHandle: event.ignore() return event.accept() new_pos = self.__pixelPosToRangeValue(self.__pick(event.pos())) - opt = QtGui.QStyleOptionSlider() + opt = QStyleOptionSlider() self.initStyleOption(opt) if self.active_slider < 0: @@ -208,9 +213,9 @@ def __pick(self, pt): def __pixelPosToRangeValue(self, pos): - opt = QtGui.QStyleOptionSlider() + opt = QStyleOptionSlider() self.initStyleOption(opt) - style = QtGui.QApplication.style() + style = QApplication.style() gr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderGroove, self) sr = style.subControlRect(style.CC_Slider, opt, style.SC_SliderHandle, self) @@ -241,12 +246,12 @@ def paintEvent( self, event ): painter.drawText(self.rect(), self.alignment(), elided) -class DockBox(QtGui.QWidget): - def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed)): +class DockBox(QWidget): + def __init__(self, parent=None, size_pol=(QSizePolicy.Fixed, + QSizePolicy.Fixed)): super(DockBox, self).__init__(parent) - self.layout = QtGui.QGridLayout() + self.layout = QGridLayout() self.setLayout(self.layout) self.setSizePolicy(size_pol[0], size_pol[1]) @@ -254,8 +259,8 @@ def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, class GraphBox(DockBox): - def __init__(self, parent=None, size_pol=(QtGui.QSizePolicy.Fixed, - QtGui.QSizePolicy.Fixed)): + def __init__(self, parent=None, size_pol=(QSizePolicy.Fixed, + QSizePolicy.Fixed)): super(GraphBox, self).__init__(parent) self.parent = parent @@ -276,26 +281,26 @@ def __init__(self, parent=None): self.parent = parent self.r0 = utils.get_radolan_origin() - self.mousePointLabel = QtGui.QLabel("Mouse Position", self) - self.mousePointXYLabel = QtGui.QLabel("XY", self) - self.mousePointLLLabel = QtGui.QLabel("LL", self) - self.mousePointXY = QtGui.QLabel("", self) - self.mousePointLL = QtGui.QLabel("", self) - self.mousePointS = QtGui.QLabel("", self) - self.curCheckBox = QtGui.QCheckBox() + self.mousePointLabel = QLabel("Mouse Position", self) + self.mousePointXYLabel = QLabel("XY", self) + self.mousePointLLLabel = QLabel("LL", self) + self.mousePointXY = QLabel("", self) + self.mousePointLL = QLabel("", self) + self.mousePointS = QLabel("", self) + self.curCheckBox = QCheckBox() self.curCheckBox.stateChanged.connect(self.signal_toggle_Cursor) - self.hline2 = QtGui.QFrame() - self.hline2.setFrameShape(QtGui.QFrame.HLine) - self.hline2.setFrameShadow(QtGui.QFrame.Sunken) + self.hline2 = QFrame() + self.hline2.setFrameShape(QFrame.HLine) + self.hline2.setFrameShadow(QFrame.Sunken) self.layout.addWidget(self.mousePointLabel, 0, 0) self.layout.addWidget(self.mousePointXYLabel, 0, 1) self.layout.addWidget(self.mousePointXY, 0, 2) self.layout.addWidget(self.mousePointLLLabel, 1, 1) self.layout.addWidget(self.mousePointLL, 1, 2) - self.layout.addWidget(QtGui.QLabel("XYsel", self), 2, 1) + self.layout.addWidget(QLabel("XYsel", self), 2, 1) self.layout.addWidget(self.mousePointS, 2, 2) - self.layout.addWidget(QtGui.QLabel("Activate Cursor", self), 3, 0) + self.layout.addWidget(QLabel("Activate Cursor", self), 3, 0) self.layout.addWidget(self.curCheckBox, 3, 1) self.layout.addWidget(self.hline2, 4, 0, 1, 3) @@ -330,22 +335,22 @@ class SourceBox(DockBox): def __init__(self, parent=None): super(SourceBox, self).__init__(parent) - palette = QtGui.QPalette() + palette = QPalette() self.setStyleSheet(""" QMenuBar { font-size:13px; }""") # Horizontal line - self.hline = QtGui.QFrame() - self.hline.setFrameShape(QtGui.QFrame.HLine) - self.hline.setFrameShadow(QtGui.QFrame.Sunken) + self.hline = QFrame() + self.hline.setFrameShape(QFrame.HLine) + self.hline.setFrameShadow(QFrame.Sunken) self.dirname = "None" #conf["dirs"]["time_slider"] self.dirLabel = LongLabel(self.dirname) self.layout.addWidget(LongLabel("Current data directory"), 0, 0, 1, 7) self.layout.addWidget(self.dirLabel, 1, 0, 1, 7) self.dirLabel.setFixedWidth(200) - palette.setColor(QtGui.QPalette.Foreground, QtCore.Qt.darkGreen) + palette.setColor(QPalette.Foreground, QtCore.Qt.darkGreen) self.dirLabel.setPalette(palette) self.props.props_changed.connect(self.update_label) @@ -368,25 +373,25 @@ def __init__(self, parent=None): self.parent = parent # Time Slider - self.time_slider = QtGui.QSlider(QtCore.Qt.Horizontal) + self.time_slider = QSlider(QtCore.Qt.Horizontal) self.time_slider.setMinimum(0) self.time_slider.setTickInterval(1) self.time_slider.setSingleStep(1) self.time_slider.valueChanged.connect(self.time_slider_moved) - self.current_date = QtGui.QLabel("1900-01-01") - self.current_time = QtGui.QComboBox() + self.current_date = QLabel("1900-01-01") + self.current_time = QComboBox() self.current_time.currentIndexChanged.connect(self.current_time_changed) # Range Slider self.range = TimeSlider(QtCore.Qt.Horizontal) - self.range_start = QtGui.QComboBox() - self.range_end = QtGui.QComboBox() + self.range_start = QComboBox() + self.range_end = QComboBox() self.range.signal_range_moved.connect(self.range_update) self.range_start.currentIndexChanged.connect(self.range_changed) self.range_end.currentIndexChanged.connect(self.range_changed) # Speed Slider - self.speed = QtGui.QSlider(QtCore.Qt.Horizontal) + self.speed = QSlider(QtCore.Qt.Horizontal) self.speed.setMinimum(0) self.speed.setMaximum(1000) self.speed.setTickInterval(10) @@ -395,33 +400,33 @@ def __init__(self, parent=None): # layout self.createMediaButtons() - self.hline0 = QtGui.QFrame() - self.hline0.setFrameShape(QtGui.QFrame.HLine) - self.hline0.setFrameShadow(QtGui.QFrame.Sunken) - self.hline1 = QtGui.QFrame() - self.hline1.setFrameShape(QtGui.QFrame.HLine) - self.hline1.setFrameShadow(QtGui.QFrame.Sunken) + self.hline0 = QFrame() + self.hline0.setFrameShape(QFrame.HLine) + self.hline0.setFrameShadow(QFrame.Sunken) + self.hline1 = QFrame() + self.hline1.setFrameShape(QFrame.HLine) + self.hline1.setFrameShadow(QFrame.Sunken) self.layout.addWidget(self.hline0, 0, 0, 1, 5) - self.layout.addWidget(QtGui.QLabel("Date"), 1, 0, 1, 1) + self.layout.addWidget(QLabel("Date"), 1, 0, 1, 1) self.layout.addWidget(self.current_date, 1, 1, 1, 2) self.layout.addWidget(self.playPauseButton, 2, 1) self.layout.addWidget(self.rewButton, 2, 2) self.layout.addWidget(self.fwdButton, 2, 3) - self.layout.addWidget(QtGui.QLabel("Start Time"), 3, 1, 1, 1) - self.layout.addWidget(QtGui.QLabel("Current Time"), 3, 2, 1, 1) - self.layout.addWidget(QtGui.QLabel("Stop Time"), 3, 3, 1, 1) + self.layout.addWidget(QLabel("Start Time"), 3, 1, 1, 1) + self.layout.addWidget(QLabel("Current Time"), 3, 2, 1, 1) + self.layout.addWidget(QLabel("Stop Time"), 3, 3, 1, 1) self.layout.addWidget(self.range_start, 4, 1, 1, 1) self.layout.addWidget(self.current_time, 4, 2, 1, 1) self.layout.addWidget(self.range_end, 4, 3, 1, 1) - self.layout.addWidget(QtGui.QLabel("Time"), 5, 0, 1, 1) + self.layout.addWidget(QLabel("Time"), 5, 0, 1, 1) self.layout.addWidget(self.time_slider, 5, 1, 1, 4) - self.layout.addWidget(QtGui.QLabel("Range"), 6, 0, 1, 1) + self.layout.addWidget(QLabel("Range"), 6, 0, 1, 1) self.layout.addWidget(self.range, 6, 1, 1, 4) - self.layout.addWidget(QtGui.QLabel("Speed"), 7, 0, 1, 1) + self.layout.addWidget(QLabel("Speed"), 7, 0, 1, 1) self.layout.addWidget(self.speed, 7, 1, 1, 4) self.layout.addWidget(self.hline1, 8, 0, 1, 5) @@ -436,22 +441,22 @@ def connect_signals(self): def createMediaButtons(self): iconSize = QtCore.QSize(18, 18) - self.playPauseButton = self.createButton(QtGui.QStyle.SP_MediaPlay, + self.playPauseButton = self.createButton(QStyle.SP_MediaPlay, iconSize, "Play", self.playpause) - self.fwdButton = self.createButton(QtGui.QStyle.SP_MediaSeekForward, + self.fwdButton = self.createButton(QStyle.SP_MediaSeekForward, iconSize, "SeekForward", self.seekforward) - self.rewButton = self.createButton(QtGui.QStyle.SP_MediaSeekBackward, + self.rewButton = self.createButton(QStyle.SP_MediaSeekBackward, iconSize, "SeekBackward", self.seekbackward) def createButton(self, style, size, tip, cfunc): - button = QtGui.QToolButton() + button = QToolButton() button.setIcon(self.style().standardIcon(style)) button.setIconSize(size) button.setToolTip(tip) @@ -490,11 +495,11 @@ def playpause(self): if self.playPauseButton.toolTip() == 'Play': self.playPauseButton.setToolTip("Pause") self.playPauseButton.setIcon( - self.style().standardIcon(QtGui.QStyle.SP_MediaPause)) + self.style().standardIcon(QStyle.SP_MediaPause)) else: self.playPauseButton.setToolTip("Play") self.playPauseButton.setIcon( - self.style().standardIcon(QtGui.QStyle.SP_MediaPlay)) + self.style().standardIcon(QStyle.SP_MediaPlay)) self.signal_playpause_changed.emit() def update_props(self): @@ -503,7 +508,7 @@ def update_props(self): etime = utils.get_dt(self.props.mem.variables['time'][-1]) try: rtime = [utils.get_dt(item).strftime("%H:%M") for item in self.props.mem.variables['time'][1:-1]] - except ValueError: + except np.ma.core.MaskError: rtime = [str(item) for item in self.props.mem.variables['time'][1:-1]] self.range_start.clear() @@ -559,13 +564,13 @@ def __init__(self, parent=None): #self.update_props() def set_datadir(self): - f = QtGui.QFileDialog.getExistingDirectory(self.parent, + f = QFileDialog.getExistingDirectory(self.parent, "Select a Folder", - "/automount/time_slider/radar/dwd", - QtGui.QFileDialog.ShowDirsOnly) + "/automount/radar/dwd_new", + QFileDialog.ShowDirsOnly) if os.path.isdir(f): - conf["dirs"]["time_slider"] = str(f) + conf["dirs"]["data"] = str(f) try: _ , meta = utils.read_dx(glob.glob(os.path.join(self.dir, "raa0*"))[0]) except ValueError: @@ -574,22 +579,25 @@ def set_datadir(self): self.update_props() def save_conf(self): - name = QtGui.QFileDialog.getSaveFileName(self.parent, 'Save File') + name = QFileDialog.getSaveFileName(self.parent, 'Save File') + name = name[0] with open(name, "w") as f: conf.write(f) def open_conf(self): - name = QtGui.QFileDialog.getOpenFileName(self.parent, 'Open project') + name = QFileDialog.getOpenFileName(self.parent, 'Open project') + name = name[0] with open(name, "r") as f: conf.read_file(f) self.update_props() def load_data(self): - newfile = QtGui.QFileDialog.getOpenFileName(self.parent, + newfile = QFileDialog.getOpenFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') if self.mem is not None: self.mem.close() + newfile = newfile[0] self.mem = utils.open_ncdf(newfile) conf["source"]["product"] = self.mem.variables['data'].source # activate the correct canvas (grid or polar) @@ -598,10 +606,11 @@ def load_data(self): self.signal_props_changed.emit(0) def save_data(self): - newfile = QtGui.QFileDialog.getSaveFileName(self.parent, + newfile = QFileDialog.getSaveFileName(self.parent, 'Save NetCDF File', '', 'netCDF (*.nc)') + newfile = newfile[0] oldfile = os.path.abspath(self.mem.filepath()) self.mem.close() os.rename(oldfile, newfile) @@ -639,7 +648,10 @@ def create_nc_dataset(self): Here we just add the metadata dictionaries ''' mem = None + + data, meta = self.rfunc(self.filelist[0]) + mem = utils.create_ncdf('tmpfile.nc', meta, units='normal') utils.add_ncdf(mem, data, 0, meta) data, meta = self.rfunc(self.filelist[-1]) @@ -650,4 +662,3 @@ def create_nc_dataset(self): def add_data(self, pos): data, meta = self.rfunc(self.filelist[pos]) utils.add_ncdf(self.mem, data, pos, meta) - diff --git a/wradvis/utils.py b/wradvis/utils.py index 5943486..609f3ef 100644 --- a/wradvis/utils.py +++ b/wradvis/utils.py @@ -1,9 +1,6 @@ -# -*- coding: utf-8 -*- -# ----------------------------------------------------------------------------- -# Copyright (c) 2016, wradlib Development Team. All Rights Reserved. -# Distributed under the MIT License. See LICENSE.txt for more info. -# ----------------------------------------------------------------------------- #!/usr/bin/env python +# Copyright (c) 2016-2018, wradlib developers. +# Distributed under the MIT License. See LICENSE.txt for more info. """ """ @@ -35,6 +32,7 @@ def radolan_to_wgs84(coords): projection_target=proj_wgs) return ll + def dx_to_wgs84(coords): # currently works only with radar feldberg @@ -51,14 +49,14 @@ def dx_to_wgs84(coords): radius = wrl.georef.get_earth_radius(radar["lat"], proj_radar) - lon, lat, height = wrl.georef.polar2lonlatalt_n(coords[1] * 1000, + lonlatalt = wrl.georef.spherical_to_proj(coords[1] * 1000, coords[0], 0.8, sitecoords, re=radius, - ke=4. / 3.) - - return np.hstack((lon, lat)) + ke=4. / 3. + ) + return lonlatalt[..., 0:2] def get_radolan_grid(): @@ -74,7 +72,7 @@ def read_radolan(f, missing=-9999, loaddata=True): def read_dx(f, missing=0, loaddata=True): - return wrl.io.readDX(f) + return wrl.io.read_dx(f) def get_cities_coords(): @@ -91,9 +89,10 @@ def get_cities_coords(): return cities + # just for testing purposes, this can be used from wradlib when it is finalized # and adapted -def read_RADOLAN_composite(fname, missing=-9999, loaddata=True): +def read_RADOLAN_composite(f, missing=-9999, loaddata=True): """Read quantitative radar composite format of the German Weather Service The quantitative composite format of the DWD (German Weather Service) was @@ -129,11 +128,14 @@ def read_RADOLAN_composite(fname, missing=-9999, loaddata=True): NODATA = missing mask = 0xFFF # max value integer - f = wrl.io.get_radolan_filehandle(fname) - - header = wrl.io.read_radolan_header(f) + # If a file name is supplied, get a file handle + try: + header = wrl.io.radolan.read_radolan_header(f) + except AttributeError: + f = wrl.io.radolan.get_radolan_filehandle(f) + header = wrl.io.radolan.read_radolan_header(f) - attrs = wrl.io.parse_DWD_quant_composite_header(header) + attrs = wrl.io.radolan.parse_dwd_composite_header(header) if not loaddata: f.close() @@ -237,11 +239,11 @@ def get_netcdf_varattrs(attrs, units='original'): add_offset = None unit = '0.01mm 5min-1' elif units == 'normal': - scale_factor = np.float32(precision * 3600 / int) + scale_factor = np.float32(precision * 3600) add_offset = np.float(0) unit = 'mm h-1' else: - scale_factor = np.float32(precision / (int * 1000)) + scale_factor = np.float32(precision / 1000) add_offset = np.float(0) unit = 'm s-1'