def reproject(layer: ogr.Layer, srs: osr.SpatialReference): '''Reprojects an existing layer given a spatial reference''' layer_name = layer.GetName() logging.info(f'reprojecting {layer_name}') projection = layer.GetSpatialRef() if projection is None: raise baseException( f'layer: {layer_name} does not have a spatial reference.', baseException.ERR_INPUT_LEVEL) # no action needed if projection.IsSame(srs): logging.info('no reprojection required') return try: # transform coordTrans = osr.CoordinateTransformation(projection, srs) # copy features layer.ResetReading() for ft in layer: geom = ft.GetGeometryRef() geom.Transform(coordTrans) ft.SetGeometry(geom) except Exception as e: raise baseException(f'layer: {layer_name} could not be reprojected.', baseException.ERR_CODE_LEVEL, e)
def reproject_shapefile(fp: str, srs: osr.SpatialReference, out_dir: str) -> str: '''Reprojects an existing layer given a spatial reference''' logging.info(f'reprojecting {fp}') driver = ogr.GetDriverByName('ESRI Shapefile') # check if reprojection is needed in_ds = driver.Open(fp) in_srs = in_ds.GetLayer().GetSpatialRef() # no spatial reference found if in_srs is None: raise baseException(f'layer: {in_ds.GetLayer().GetName()} does not have a spatial reference.', baseException.ERR_INPUT_LEVEL) # spatial references match, no action needed if in_srs.IsSame(srs): logging.info('no reprojection required') in_ds.Release() in_ds = None return fp # copy datasource out = os.path.join(out_dir, os.path.basename(fp)) out_ds = driver.CopyDataSource(in_ds, out) layer = out_ds.GetLayer() try: # transform coordTrans = osr.CoordinateTransformation(in_srs, srs) # copy features layer.ResetReading() for ft in layer: geom = ft.GetGeometryRef() geom.Transform(coordTrans) ft.SetGeometry(geom) except Exception as e: raise baseException(f'layer: {layer.GetName()} could not be reprojected.', baseException.ERR_CODE_LEVEL, e) finally: in_ds.Release() out_ds.Release() in_ds, out_ds = None, None return out
def close_shapefile(shp: ogr.DataSource) -> str: '''Closes the shapefile''' logging.info(f'closing shapefile {shp.GetName()}') try: shp.Release() shp = None except Exception as e: raise baseException(f'shapefile: {shp.GetName()} could not be closed', baseException.ERR_CODE_LEVEL, e)
def open_shapefile(fp: str, edit: bool = False) -> ogr.DataSource: '''Returns an opened ogr DataSource in r/w mode''' logging.info(f'opening shapefile {fp}') try: driver = ogr.GetDriverByName('ESRI Shapefile') return driver.Open(fp, int(edit)) except Exception as e: raise baseException(f'shapefile: {fp} could not be opened.', baseException.ERR_CODE_LEVEL, e)
def make_dir(path: str): '''Create a new dictory given path''' if not os.path.exists(path): try: os.makedirs(path) logging.info(f'created directory {path}') except OSError as e: if e.errno != errno.EEXIST: raise baseException(f'There was an issue creating the directory {path}.', baseException.ERR_UNK_LEVEL, e)
def set_label(layout: QgsPrintLayout, item_id: str, text: str): '''Set the text for existing layout items''' logging.info(f'setting text with label id={item_id}') item = layout.itemById(item_id) if item is not None: item.setText(text) else: raise baseException(f'item_id {item_id} does not exist', baseException.ERR_CODE_LEVEL)
def format_label(layout: QgsPrintLayout, item_id: str, params: List[Union[int, str]]): '''Formats the text for existing layout items''' logging.info(f'formatting text with label id={item_id}') item = layout.itemById(item_id) text = item.text().format(*params) if item is not None: item.setText(text) else: raise baseException(f'item_id {item_id} does not exist', baseException.ERR_CODE_LEVEL)
def open_template(layout: QgsPrintLayout, fp: str): '''Load items from template into layout''' logging.info(f'opening template: {fp}') doc = QDomDocument() context = QgsReadWriteContext() # read template with open(fp, 'rt', encoding='utf-8') as f: content = f.read() # set content and load items from template doc.setContent(content) _, ok = layout.loadFromTemplate(doc, context) if not ok: raise baseException(f'failed to load QGS template: {fp}', baseException.ERR_CODE_LEVEL)
def set_median_predom(const: dict, layer: ogr.Layer, region: str, *unused): '''Set the Median Predominance burn-in value''' try: burn_field = const['burn_field'] burn = ogr.FieldDefn(burn_field, ogr.OFTInteger) layer.CreateField(burn) # set burn-in values for each feature logging.info('setting median predominance') layer.ResetReading() for ft in layer: predominant = get_predom(const, ft) ft.SetField(burn_field, predominant) layer.SetFeature(ft) except Exception as e: raise baseException( f'could not set values in {layer.GetName()} for median predominance', baseException.ERR_CODE_LEVEL, e)
def set_median_ct(const: dict, layer: ogr.Layer, region: str, ice_type: str, *unused): '''Set the Median Concentration burn-in value''' try: water, land, nodata = const['water'], const['land'], const['nodata'] burn_field = const['burn_field'] burn = ogr.FieldDefn(burn_field, ogr.OFTInteger) layer.CreateField(burn) # set burn-in values for each feature logging.info('setting median ct') layer.ResetReading() for ft in layer: fa = ft.GetField('FA') fb = ft.GetField('FB') fc = ft.GetField('FC') concn = ft.GetField(ice_type) if concn is None or concn == '': concn = water elif '08' in [fa, fb, fc] and concn in ['9.7', '10.0']: concn = '11.0' elif '07' in [fa, fb, fc] and concn == '10.0': concn = '10.0' ft.SetField(burn_field, int(float(concn))) pnt_type = ft.GetField('PNT_TYPE') if pnt_type in ['101', '115', '107']: ft.SetField(burn_field, water) elif pnt_type in ['400', '900']: ft.SetField(burn_field, land) elif pnt_type in ['123', '128', '133', '143']: ft.SetField(burn_field, nodata) layer.SetFeature(ft) except Exception as e: raise baseException( f'could not set values in {layer.GetName()} for median concentration', baseException.ERR_CODE_LEVEL, e)
def set_frequency(const: dict, layer: ogr.Layer, region: str, ice_type: str, threshold: int = 1): '''Set the Frequency burn-in value''' try: water, nodata = const['water'], const['nodata'] burn_field = const['burn_field'] burn = ogr.FieldDefn(burn_field, ogr.OFTInteger) layer.CreateField(burn) # set burn-in values for each feature logging.info(f'setting frequency using threshold: {threshold}') layer.ResetReading() for ft in layer: pnt_type = ft.GetField('PNT_TYPE') concn = ft.GetField(ice_type) concn = 0.0 if not concn else float(concn) if pnt_type in ['101', '107', '115', '400', '900']: ft.SetField(burn_field, water) elif pnt_type in ['123', '128', '133', '143']: ft.SetField(burn_field, nodata) elif pnt_type in ['106', '117', '118', '120', '122', '144' ] and concn >= float(threshold): ft.SetField(burn_field, 1) elif pnt_type in ['106', '117', '118', '120', '122', '144' ] and concn < float(threshold): ft.SetField(burn_field, water) else: ft.SetField(burn_field, nodata) layer.SetFeature(ft) except Exception as e: raise baseException( f'could not set values in {layer.GetName()} for frequency', baseException.ERR_CODE_LEVEL, e)
def open_layer(project: QgsProject, fp: str) -> QgsLayer: '''Opens and returns a QgsVectorLayer/QgsRasterLayer''' if fp is None: return None logging.info(f'opening layer: {fp}') basename, ext = os.path.splitext(os.path.basename(fp)) vector = not (ext in ['.tif', '.tiff']) # create vector or raster layer if vector: layer = QgsVectorLayer(fp, basename) else: layer = QgsRasterLayer(fp, basename) if not layer.isValid(): raise baseException(f'failed to load layer: {fp}', baseException.ERR_CFG_LEVEL) # add layer to project project.addMapLayer(layer) return layer
def make_dummy(ds: ogr.DataSource, name: str, burn: Tuple) -> ogr.DataSource: '''Returns a dummy layer for CTMED populated with burn[1] values''' logging.info(f'creating dummy: {name} layer') try: # create dummy layer mem = create_shapefile('memory', 'MEMORY') dummy = mem.CopyLayer(ds.GetLayer(), name, ['OVERWRITE=YES']) # add burn field and populate fdefn = ogr.FieldDefn(burn[0], ogr.OFTInteger) dummy.CreateField(fdefn) dummy.ResetReading() for ft in dummy: ft.SetField(burn[0], burn[1]) dummy.SetFeature(ft) return mem except Exception as e: close_shapefile(ds) raise baseException(f'dummy: {name} layer could not be created.', baseException.ERR_CODE_LEVEL, e)
def main(): args = argparser_init() args.config = os.path.join(ROOT, args.config) with BaseHandler(args.inputs, args.config) as chart: chart.start_logging() chart.set_methods(set_frequency, calc_frequency, set_frequency_colours) chart.start() return 0 if __name__ == '__main__': try: status = main() except Exception as e: status = 1 # pylint: disable=no-member if isinstance(e, baseException): print(e.to_json()) else: if hasattr(e, 'message'): print( baseException(e.message, baseException.ERR_UNK_LEVEL, e).to_json()) else: print( baseException('', baseException.ERR_UNK_LEVEL, e).to_json()) finally: sys.exit(status)
def calc_freezeup(config: dict, ctmeds: str, region: str, threshold: int, out: str): ''' Calculates Freezeup Dates Parameters: config: config file as JSON ctmeds: directory containing f'{region}_{chart}{date}.tif' files region: region to make chart for threshold: minimum concentration [0-10] out: output file path ''' logging.info( f'calculating freezeup using threshold: {threshold} for {region}') const = config['constants'] pixel_size = const['pixel_size'] land, nodata = const['land'], const['nodata'] dates = config['ctfup'] hd = dates.get(region, dates['default']) # retrieve relevant rasters # reason: AddBand is not supported. Cannot add bands dynamically rasters, missing = [], [] for date in hd: found = False pattern = f'{region.lower()}_ctmed{date}' for f in os.listdir(ctmeds): fn, ext = os.path.splitext(f) if fn.startswith(pattern) and ext == '.tif': rasters.append(os.path.join(ctmeds, f)) found = True if not found: missing.append(date) # ensure all ctmeds present if missing: fn = os.path.splitext(os.path.basename(out))[0] raise baseException( f'unable to create {fn} - insufficient ctmed charts, missing {missing}', baseException.ERR_CODE_LEVEL) try: # get aoi info aoi = open_shapefile(os.path.join(*config[region]['aoi'])) spref = aoi.GetLayer().GetSpatialRef() extent = round_extent(aoi, pixel_size) close_shapefile(aoi) # stack all bands stack = [] for r in rasters: tmp_ds = gdal.Open(r) tmp_band = tmp_ds.GetRasterBand(1).ReadAsArray() stack.append(tmp_band) tmp_ds = None stack = np.array(stack) # create land grid from band = 1 band = stack[0] land = np.where(band < land, nodata, land) # create zero grid zero = np.where(np.logical_or(band > (land - 1), band == nodata), nodata, 0) # add historic date attributes tmp_grid1 = (np.logical_and(stack >= threshold * 10, stack <= 100)).astype(int) tmp_grid2 = np.full(tmp_grid1.shape, nodata, dtype=int) for i in range(stack.shape[0]): tmp_grid2[i] = np.where(tmp_grid1[i] == 1, int(hd[i]), nodata) # merge - for each column, retrieve the index of first non-null tmp_grid3 = np.vstack((land[None], tmp_grid2, zero[None])) mask = tmp_grid3 != nodata index = np.where(mask.any(axis=0), mask.argmax(axis=0), -1) idx, idy = np.ogrid[0:index.shape[0], 0:index.shape[1]] result = tmp_grid3[index[idx, idy], idx, idy] except Exception as e: raise baseException('error calculating freezeup.', baseException.ERR_CODE_LEVEL, e) try: tif = create_tif(out, extent, spref, pixel_size) set_nodata(tif, nodata) save_array(tif, result) tif = None except Exception as e: raise baseException('error saving raster results for freezeup.', baseException.ERR_CODE_LEVEL, e)