def subcatch_stream(ldd, stream, threshold): """ Derive catchments based upon strahler threshold Input: ldd -- pcraster object direction, local drain directions stream -- pcraster object direction, streamorder threshold -- integer, strahler threshold, subcatchments ge threshold are derived output: stream_ge -- pcraster object, streams of strahler order ge threshold subcatch -- pcraster object, subcatchments of strahler order ge threshold """ # derive stream order # stream = pcr.streamorder(ldd) stream_ge = pcr.ifthen(stream >= threshold, stream) stream_up_sum = pcr.ordinal( pcr.upstream(ldd, pcr.cover(pcr.scalar(stream_ge), 0))) # detect any transfer of strahler order, to a higher strahler order. transition_strahler = pcr.ifthenelse( pcr.downstream(ldd, stream_ge) != stream_ge, pcr.boolean(1), pcr.ifthenelse( pcr.nominal(ldd) == 5, pcr.boolean(1), pcr.ifthenelse( pcr.downstream(ldd, pcr.scalar(stream_up_sum)) > pcr.scalar(stream_ge), pcr.boolean(1), pcr.boolean(0)))) # make unique ids (write to file) transition_unique = pcr.ordinal(pcr.uniqueid(transition_strahler)) # derive upstream catchment areas (write to file) subcatch = pcr.nominal(pcr.subcatchment(ldd, transition_unique)) return stream_ge, subcatch
def estimate_bottom_of_bank_storage(self): # influence zone depth (m) # TODO: Define this one as part of influence_zone_depth = 5.0 # bottom_elevation > flood_plain elevation - influence zone bottom_of_bank_storage = self.dem_floodplain - influence_zone_depth # reducing noise (so we will not introduce unrealistic sinks) # TODO: Define the window size as part of the configuration/ini file bottom_of_bank_storage = pcr.max(bottom_of_bank_storage,\ pcr.windowaverage(bottom_of_bank_storage, 3.0 * pcr.clone().cellSize())) # bottom_elevation > river bed bottom_of_bank_storage = pcr.max(self.dem_riverbed, bottom_of_bank_storage) # reducing noise by comparing to its downstream value (so we will not introduce unrealistic sinks) bottom_of_bank_storage = pcr.max(bottom_of_bank_storage, \ (bottom_of_bank_storage + pcr.cover(pcr.downstream(self.lddMap, bottom_of_bank_storage), bottom_of_bank_storage))/2.) # bottom_elevation >= 0.0 (must be higher than sea level) bottom_of_bank_storage = pcr.max(0.0, bottom_of_bank_storage) # bottom_elevation < dem_average (this is to drain overland flow) bottom_of_bank_storage = pcr.min(bottom_of_bank_storage, self.dem_average) bottom_of_bank_storage = pcr.cover(bottom_of_bank_storage, self.dem_average) # TODO: Check again this concept. # TODO: We may want to improve this concept - by incorporating the following # - smooth bottom_elevation # - upstream areas in the mountainous regions and above perrenial stream starting points may also be drained (otherwise water will accumulate) # - bottom_elevation > minimum elevation that is estimated from the maximum of S3 from the PCR-GLOBWB simulation return bottom_of_bank_storage
def subcatch_order_a(ldd, oorder): """ Determines subcatchments using the catchment order This version uses the last cell BELOW order to derive the catchments. In general you want the _b version Input: - ldd - order - order to use Output: - map with catchment for the given streamorder """ outl = find_outlet(ldd) large = pcr.subcatchment(ldd, pcr.boolean(outl)) stt = pcr.streamorder(ldd) sttd = pcr.downstream(ldd, stt) pts = pcr.ifthen((pcr.scalar(sttd) - pcr.scalar(stt)) > 0.0, sttd) dif = pcr.upstream( ldd, pcr.cover( pcr.ifthen( large, pcr.uniqueid(pcr.boolean(pcr.ifthen(stt == pcr.ordinal(oorder), pts))), ), 0, ), ) dif = pcr.cover(pcr.scalar(outl), dif) # Add catchment outlet dif = pcr.ordinal(pcr.uniqueid(pcr.boolean(dif))) sc = pcr.subcatchment(ldd, dif) return sc, dif, stt
def subcatch_stream(ldd, stream, threshold): """ Derive catchments based upon strahler threshold Input: ldd -- pcraster object direction, local drain directions stream -- pcraster object direction, streamorder threshold -- integer, strahler threshold, subcatchments ge threshold are derived output: stream_ge -- pcraster object, streams of strahler order ge threshold subcatch -- pcraster object, subcatchments of strahler order ge threshold """ # derive stream order # stream = pcr.streamorder(ldd) stream_ge = pcr.ifthen(stream >= threshold, stream) stream_up_sum = pcr.ordinal(pcr.upstream(ldd, pcr.cover(pcr.scalar(stream_ge), 0))) # detect any transfer of strahler order, to a higher strahler order. transition_strahler = pcr.ifthenelse(pcr.downstream(ldd, stream_ge) != stream_ge, pcr.boolean(1), pcr.ifthenelse(pcr.nominal(ldd) == 5, pcr.boolean(1), pcr.ifthenelse(pcr.downstream(ldd, pcr.scalar(stream_up_sum)) > pcr.scalar(stream_ge), pcr.boolean(1), pcr.boolean(0)))) # make unique ids (write to file) transition_unique = pcr.ordinal(pcr.uniqueid(transition_strahler)) # derive upstream catchment areas (write to file) subcatch = pcr.nominal(pcr.subcatchment(ldd, transition_unique)) return stream_ge, subcatch
def estimate_bottom_of_bank_storage(self): # influence zone depth (m) influence_zone_depth = 0.50 # bottom_elevation > flood_plain elevation - influence zone bottom_of_bank_storage = self.dem_floodplain - influence_zone_depth #~ # bottom_elevation > river bed #~ bottom_of_bank_storage = pcr.max(self.dem_riverbed, bottom_of_bank_storage) # bottom_elevation > its downstream value bottom_of_bank_storage = pcr.max(bottom_of_bank_storage, \ pcr.cover(pcr.downstream(self.lddMap, bottom_of_bank_storage), bottom_of_bank_storage)) # bottom_elevation >= 0.0 (must be higher than sea level) bottom_of_bank_storage = pcr.max(0.0, bottom_of_bank_storage) # reducing noise bottom_of_bank_storage = pcr.max(bottom_of_bank_storage,\ pcr.windowaverage(bottom_of_bank_storage, 3.0 * pcr.clone().cellSize())) # bottom_elevation < dem_average bottom_of_bank_storage = pcr.min(bottom_of_bank_storage, self.dem_average) bottom_of_bank_storage = pcr.cover(bottom_of_bank_storage, self.dem_average) # TODO: Check again this concept. # TODO: We may want to improve this concept - by incorporating the following # - smooth bottom_elevation # - upstream areas in the mountainous regions and above perrenial stream starting points may also be drained (otherwise water will accumulate) # - bottom_elevation > minimum elevation that is estimated from the maximum of S3 from the PCR-GLOBWB simulation return bottom_of_bank_storage
def initial(self): """ initial part of the evapo water module """ # ************************************************************ # ***** EVAPORATION # ************************************************************ self.var.EvaCumM3 = MaskInfo.instance().in_zero() # water use cumulated amount # water use substep amount settings = LisSettings.instance() option = settings.options binding = settings.binding maskinfo = MaskInfo.instance() if option['openwaterevapo']: LakeMask = loadmap('LakeMask', pcr=True) lmask = ifthenelse(LakeMask != 0, self.var.LddStructuresKinematic, 5) LddEva = lddrepair(lmask) lddC = compressArray(LddEva) inAr = decompress(np.arange(maskinfo.info.mapC[0], dtype="int32")) self.var.downEva = (compressArray(downstream( LddEva, inAr))).astype("int32") # each upstream pixel gets the id of the downstream pixel self.var.downEva[lddC == 5] = maskinfo.info.mapC[0] self.var.maxNoEva = int(loadmap('maxNoEva')) # all pits gets a high number # still to test if this works # ldd only inside lakes for calculating evaporation if option['varfractionwater']: self.var.diffmaxwater = loadmap( 'FracMaxWater') - self.var.WaterFraction # Fraction of maximum extend of water - fraction of water in lakes and rivers varWNo = [ 1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 370 ] self.var.varW = [] # variable fraction of water self.var.varW1 = [] self.var.varW1.append(12) j = 0 for i in range(1, 367): if i >= varWNo[j + 1]: j += 1 self.var.varW1.append(j) for i in range(12): varWName = generateName(binding['WFractionMaps'], varWNo[i]) self.var.varW.append( loadLAI(binding['WFractionMaps'], varWName, i))
def pt_flow_in_river(ldd, river): """ Returns all points (True) that flow into the mak river (boolean map with river set to True) :param ldd: Drainage network :param river: Map of river (True River, False non-river) :return ifmap: map with infrlo points into the river (True) :return ctach: catchment of each of the inflow points """ dspts = pcr.downstream(ldd, pcr.cover(river, 0)) dspts = pcr.ifthenelse(pcr.cover(river, 0) == 1, 0, dspts) catch = pcr.subcatchment(ldd, pcr.nominal(pcr.uniqueid(dspts))) return dspts, catch
def initial(self): """ initial part of the structures module """ self.var.LddStructuresKinematic = self.var.LddKinematic settings = LisSettings.instance() option = settings.options if not option['InitLisflood']: # not done in Init Lisflood IsUpsOfStructureKinematic = downstream( self.var.LddKinematic, cover(boolean(decompress(self.var.IsStructureKinematic)), boolean(0)) ) # Get all pixels just upstream of kinematic structure locations self.var.IsUpsOfStructureKinematicC = compressArray(IsUpsOfStructureKinematic) # Unmodified version of LddKinematic is needed to connect inflow and outflow points # of each structure (called LddStructuresKinematic now) self.var.LddKinematic = lddrepair(ifthenelse(IsUpsOfStructureKinematic, 5, self.var.LddKinematic))
catchments = pcr.nominal(pcr.ifthen(pcr.mapmaximum(pcr.areatotal(pcr.scalar(catchments)*0+1,pcr.nominal(catchments))) == pcr.areatotal(pcr.scalar(catchments)*0+1,pcr.nominal(catchments)),catchments)) pcr.report(ldd,ldd_map) pcr.report(streamorder,streamorder_map) pcr.report(river,river_map) pcr.report(catchments,catchments_map) if not EPSG == None: call(('gdal_translate','-of','GTiff','-stats','-a_srs',EPSG,'-ot','Float32',catchments_map,catchments_tif)) else: call(('gdal_translate','-of','GTiff','-stats','-ot','Float32',catchments_map,catchments_tif)) wt.Raster2Pol(catchments_tif,catchshp,srs) riversid_map = workdir + 'riverid.map' drain_map = workdir + 'drain.map' ldd_mask = pcr.ifthen(river, ldd) upstream = pcr.upstream(ldd_mask, pcr.scalar(river)) downstream = pcr.downstream(ldd_mask, upstream) #pcr.report(downstream,'downstream.map') confluences = pcr.boolean(pcr.ifthen(downstream >= 2, pcr.boolean(1))) #pcr.report(confluences,'confluences.map') boundaries = pcr.boolean(pcr.ifthen(pcr.scalar(ldd_mask) == 5, pcr.boolean(1))) catch_points = pcr.nominal(pcr.uniqueid(pcr.cover(confluences,boundaries))) catchmentsid = pcr.nominal(pcr.subcatchment(ldd, catch_points)) drain = pcr.accuflux(ldd_mask, 1) riversid = pcr.ifthen(river, catchmentsid) if not keepall: riversid = pcr.nominal(pcr.ifthen(pcr.mapmaximum(pcr.areatotal(pcr.scalar(catchments)*0+1,pcr.nominal(catchments))) == pcr.areatotal(pcr.scalar(catchments)*0+1,pcr.nominal(catchments)),riversid)) pcr.report(riversid,riversid_map) pcr.report(drain,drain_map)
def getReservoirOutflow(self,\ avgChannelDischarge,length_of_time_step,downstreamDemand): # avgOutflow (m3/s) avgOutflow = self.avgOutflow # The following is needed when new lakes/reservoirs introduced (its avgOutflow is still zero). #~ # - alternative 1 #~ avgOutflow = pcr.ifthenelse(\ #~ avgOutflow > 0.,\ #~ avgOutflow, #~ pcr.max(avgChannelDischarge, self.avgInflow, 0.001)) # - alternative 2 avgOutflow = pcr.ifthenelse(\ avgOutflow > 0.,\ avgOutflow, pcr.max(avgChannelDischarge, self.avgInflow)) avgOutflow = pcr.ifthenelse(\ avgOutflow > 0.,\ avgOutflow, pcr.downstream(self.lddMap, avgOutflow)) avgOutflow = pcr.areamaximum(avgOutflow, self.waterBodyIds) # calculate resvOutflow (m2/s) (based on reservoir storage and avgDischarge): # - using reductionFactor in such a way that: # - if relativeCapacity < minResvrFrac : release is terminated # - if relativeCapacity > maxResvrFrac : longterm average reductionFactor = \ pcr.cover(\ pcr.min(1., pcr.max(0., \ self.waterBodyStorage - self.minResvrFrac*self.waterBodyCap)/\ (self.maxResvrFrac - self.minResvrFrac)*self.waterBodyCap),0.0) # resvOutflow = reductionFactor * avgOutflow * length_of_time_step # unit: m3 # maximum release <= average inflow (especially during dry condition) resvOutflow = pcr.max(0, pcr.min(resvOutflow, self.avgInflow * length_of_time_step)) # unit: m3 # downstream demand (m3/s) # reduce demand if storage < lower limit reductionFactor = vos.getValDivZero( downstreamDemand, self.minResvrFrac * self.waterBodyCap, vos.smallNumber) reductionFactor = pcr.cover(reductionFactor, 0.0) downstreamDemand = pcr.min(downstreamDemand, downstreamDemand * reductionFactor) # resvOutflow > downstreamDemand resvOutflow = pcr.max(resvOutflow, downstreamDemand * length_of_time_step) # unit: m3 # floodOutflow: additional release if storage > upper limit ratioQBankfull = 2.3 estmStorage = pcr.max(0., self.waterBodyStorage - resvOutflow) floodOutflow = \ pcr.max(0.0, estmStorage - self.waterBodyCap) +\ pcr.cover(\ pcr.max(0.0, estmStorage - self.maxResvrFrac*\ self.waterBodyCap)/\ ((1.-self.maxResvrFrac)*self.waterBodyCap),0.0)*\ pcr.max(0.0,ratioQBankfull*avgOutflow* vos.secondsPerDay()-\ resvOutflow) floodOutflow = pcr.max(0.0, pcr.min(floodOutflow,\ estmStorage - self.maxResvrFrac*\ self.waterBodyCap*0.75)) # maximum limit of floodOutflow: bring the reservoir storages only to 3/4 of upper limit capacities # update resvOutflow after floodOutflow resvOutflow = pcr.cover(resvOutflow , 0.0) +\ pcr.cover(floodOutflow, 0.0) # maximum release if storage > upper limit : bring the reservoir storages only to 3/4 of upper limit capacities resvOutflow = pcr.ifthenelse(self.waterBodyStorage > self.maxResvrFrac*self.waterBodyCap,\ pcr.min(resvOutflow,\ pcr.max(0,self.waterBodyStorage - \ self.maxResvrFrac*self.waterBodyCap*0.75)), resvOutflow) # if storage > upper limit : resvOutflow > avgInflow resvOutflow = pcr.ifthenelse(self.waterBodyStorage > self.maxResvrFrac*self.waterBodyCap,\ pcr.max(0.0, resvOutflow, self.avgInflow), resvOutflow) # resvOutflow < waterBodyStorage resvOutflow = pcr.min(self.waterBodyStorage, resvOutflow) resvOutflow = pcr.ifthen( pcr.scalar(self.waterBodyIds) > 0., resvOutflow) resvOutflow = pcr.ifthen( pcr.scalar(self.waterBodyTyp) == 2, resvOutflow) return (resvOutflow) # unit: m3
def initial(self): """ initial part of the water abstraction module """ # self.testmap=windowaverage(self.var.Elevation,5) # self.report(self.testmap,"test.map") # ************************************************************ # ***** WATER USE # ************************************************************ settings = LisSettings.instance() option = settings.options binding = settings.binding maskinfo = MaskInfo.instance() if option['wateruse']: self.var.WUsePercRemain = loadmap('WUsePercRemain') self.var.NoWaterUseSteps = int(loadmap('maxNoWateruse')) self.var.GroundwaterBodies = loadmap('GroundwaterBodies') self.var.FractionGroundwaterUsed = np.minimum( np.maximum(loadmap('FractionGroundwaterUsed'), maskinfo.in_zero()), 1.0) self.var.FractionNonConventionalWaterUsed = loadmap( 'FractionNonConventionalWaterUsed') self.var.FractionLakeReservoirWaterUsed = loadmap( 'FractionLakeReservoirWaterUsed') self.var.EFlowThreshold = loadmap('EFlowThreshold') # EFlowThreshold is map with m3/s discharge, e.g. the 10th percentile discharge of the baseline run self.var.WUseRegionC = loadmap('WUseRegion').astype(int) self.var.IrrigationMult = loadmap('IrrigationMult') # ************************************************************ # ***** water use constant maps ****************************** # ************************************************************ self.var.IndustryConsumptiveUseFraction = loadmap( 'IndustryConsumptiveUseFraction') # fraction (0-1) self.var.WaterReUseFraction = loadmap('WaterReUseFraction') # fraction of water re-used (0-1) self.var.EnergyConsumptiveUseFraction = loadmap( 'EnergyConsumptiveUseFraction') # fraction (0-1), value depends on cooling technology of power plants self.var.LivestockConsumptiveUseFraction = loadmap( 'LivestockConsumptiveUseFraction') # fraction (0-1) self.var.LeakageFraction = np.minimum( np.maximum( loadmap('LeakageFraction') * (1 - loadmap('LeakageReductionFraction')), maskinfo.in_zero()), 1.0) self.var.DomesticLeakageConstant = np.minimum( np.maximum(1 / (1 - self.var.LeakageFraction), maskinfo.in_zero()), 1.0) # Domestic Water Abstraction becomes larger in case of leakage # LeakageFraction is LeakageFraction (0-1) multiplied by reduction scenario (10% reduction is 0.1 in map) # 0.65 leakage and 0.1 reduction leads to 0.585 effective leakage, resulting in 2.41 times more water abstraction self.var.DomesticWaterSavingConstant = np.minimum( np.maximum(1 - loadmap('WaterSavingFraction'), maskinfo.in_zero()), 1.0) # Domestic water saving if in place, changes this value from 1 to a value between 0 and 1, and will reduce demand and abstraction # so value = 0.9 if WaterSavingFraction equals 0.1 (10%) self.var.DomesticConsumptiveUseFraction = loadmap( 'DomesticConsumptiveUseFraction') # fraction (0-1), typically rather low ~ 0.10 self.var.LeakageWaterLossFraction = loadmap('LeakageWaterLoss') # fraction (0-1), 0=no leakage # Initialize water demand. Read from a static map either value or pcraster map or netcdf (single or stack). # If reading from NetCDF stack, get time step corresponding to model step. # Added management for sub-daily modelling time steps # Added possibility to use one single average year to be repeated during the simulation if option['useWaterDemandAveYear']: # CM: using one water demand average year throughout the model simulation self.var.DomesticDemandMM = loadmap( 'DomesticDemandMaps', timestampflag='closest', averageyearflag=True) * self.var.DtDay self.var.IndustrialDemandMM = loadmap( 'IndustrialDemandMaps', timestampflag='closest', averageyearflag=True) * self.var.DtDay self.var.LivestockDemandMM = loadmap( 'LivestockDemandMaps', timestampflag='closest', averageyearflag=True) * self.var.DtDay self.var.EnergyDemandMM = loadmap( 'EnergyDemandMaps', timestampflag='closest', averageyearflag=True) * self.var.DtDay else: # CM: using information on water demand from NetCDF files self.var.DomesticDemandMM = loadmap( 'DomesticDemandMaps', timestampflag='closest') * self.var.DtDay self.var.IndustrialDemandMM = loadmap( 'IndustrialDemandMaps', timestampflag='closest') * self.var.DtDay self.var.LivestockDemandMM = loadmap( 'LivestockDemandMaps', timestampflag='closest') * self.var.DtDay self.var.EnergyDemandMM = loadmap( 'EnergyDemandMaps', timestampflag='closest') * self.var.DtDay # Check consistency with the reference calendar that is read from the precipitation forcing file (global_modules.zusatz.optionBinding) if option['TransientWaterDemandChange'] and option[ 'readNetcdfStack']: for k in ('DomesticDemandMaps', 'IndustrialDemandMaps', 'LivestockDemandMaps', 'EnergyDemandMaps'): with Dataset(binding[k] + '.nc') as nc: cal_type = get_calendar_type(nc) if cal_type != binding['calendar_type']: warnings.warn( calendar_inconsistency_warning( binding[k], cal_type, binding['calendar_type'])) if option['groundwaterSmooth']: self.var.GroundwaterBodiesPcr = decompress( self.var.GroundwaterBodies) self.var.groundwaterCatch = boolean( decompress((self.var.GroundwaterBodies * self.var.Catchments).astype(int))) # nominal(scalar(GroundwaterBodies)*scalar(self.var.Catchments)); # smoothing for groundwater to correct error by using windowtotal, based on groundwater bodies and catchments self.var.LZSmoothRange = loadmap('LZSmoothRange') if option['wateruseRegion']: WUseRegion = nominal(loadmap('WUseRegion', pcr=True)) pitWuse1 = ifthen(self.var.AtLastPoint != 0, boolean(1)) pitWuse1b = ifthen(defined(pitWuse1), WUseRegion) # use every existing pit in the Ldd and number them by the water regions # coastal water regions can have more than one pit per water region pitWuseMax = areamaximum(self.var.UpArea, WUseRegion) pitWuse2 = ifthen(pitWuseMax == self.var.UpArea, WUseRegion) # search outlets in the inland water regions by using the maximum upstream area as criterium pitWuse3 = downstream(self.var.LddStructuresKinematic, WUseRegion) pitWuse3b = ifthen(pitWuse3 != WUseRegion, WUseRegion) # search point where ldd leaves a water region pitWuse = cover(pitWuse1b, pitWuse2, pitWuse3b, nominal(0)) # join all sources of pits LddWaterRegion = lddrepair( ifthenelse(pitWuse == 0, self.var.LddStructuresKinematic, 5)) # create a Ldd with pits at every water region outlet # this results in a interrupted ldd, so water cannot be transfered to the next water region lddC = compressArray(LddWaterRegion) inAr = decompress( np.arange(maskinfo.info.mapC[0], dtype="int32")) # giving a number to each non missing pixel as id self.var.downWRegion = (compressArray( downstream(LddWaterRegion, inAr))).astype(np.int32) # each upstream pixel gets the id of the downstream pixel self.var.downWRegion[lddC == 5] = maskinfo.info.mapC[0] # all pits gets a high number # ************************************************************ # ***** OUTFLOW AND INFLOW POINTS FOR WATER REGIONS ********** # ************************************************************ self.var.WaterRegionOutflowPoints = ifthen( pitWuse != 0, boolean(1)) # outflowpoints to calculate upstream inflow for balances and Water Exploitation Index # both inland outflowpoints to downstream subbasin, and coastal outlets WaterRegionInflow1 = boolean( upstream( self.var.LddStructuresKinematic, cover(scalar(self.var.WaterRegionOutflowPoints), 0))) self.var.WaterRegionInflowPoints = ifthen( WaterRegionInflow1, boolean(1)) # inflowpoints to calculate upstream inflow for balances and Water Exploitation Index else: self.var.downWRegion = self.var.downstruct.copy() self.var.downWRegion = self.var.downWRegion.astype(np.int32) # ************************************************************ # ***** Initialising cumulative output variables ************* # ************************************************************ # These are all needed to compute the cumulative mass balance error self.var.wateruseCum = maskinfo.in_zero() # water use cumulated amount self.var.WUseAddM3Dt = maskinfo.in_zero() self.var.WUseAddM3 = maskinfo.in_zero() self.var.IrriLossCUM = maskinfo.in_zero() # Cumulative irrigation loss [mm] # Cumulative abstraction from surface water [mm] self.var.TotalAbstractionFromSurfaceWaterM3 = maskinfo.in_zero() self.var.TotalAbstractionFromGroundwaterM3 = maskinfo.in_zero() self.var.TotalIrrigationAbstractionM3 = maskinfo.in_zero() self.var.TotalPaddyRiceIrrigationAbstractionM3 = maskinfo.in_zero() self.var.TotalLivestockAbstractionM3 = maskinfo.in_zero() self.var.IrrigationType = loadmap('IrrigationType') self.var.IrrigationEfficiency = loadmap('IrrigationEfficiency') self.var.ConveyanceEfficiency = loadmap('ConveyanceEfficiency') self.var.GroundwaterRegionPixels = np.take( np.bincount(self.var.WUseRegionC, weights=self.var.GroundwaterBodies), self.var.WUseRegionC) self.var.AllRegionPixels = np.take( np.bincount(self.var.WUseRegionC, weights=self.var.GroundwaterBodies * 0.0 + 1.0), self.var.WUseRegionC) self.var.RatioGroundWaterUse = self.var.AllRegionPixels / ( self.var.GroundwaterRegionPixels + 0.01) self.var.FractionGroundwaterUsed = np.minimum( self.var.FractionGroundwaterUsed * self.var.RatioGroundWaterUse, 1 - self.var.FractionNonConventionalWaterUsed) # FractionGroundwaterUsed is a percentage given at national scale # since the water needs to come from the GroundwaterBodies pixels, # the fraction needs correction for the non-Groundwaterbodies; this is done here self.var.EFlowIndicator = maskinfo.in_zero() self.var.ReservoirAbstractionM3 = maskinfo.in_zero() self.var.PotentialSurfaceWaterAvailabilityForIrrigationM3 = maskinfo.in_zero( ) self.var.LakeAbstractionM3 = maskinfo.in_zero() self.var.FractionAbstractedFromChannels = maskinfo.in_zero() self.var.AreatotalIrrigationUseM3 = maskinfo.in_zero() self.var.totalAddM3 = maskinfo.in_zero() self.var.TotalDemandM3 = maskinfo.in_zero()
def initial(self): """ initial part of the routing module """ maskinfo = MaskInfo.instance() self.var.avgdis = maskinfo.in_zero() self.var.Beta = loadmap('beta') self.var.InvBeta = 1 / self.var.Beta # Inverse of beta for kinematic wave self.var.ChanLength = loadmap('ChanLength').astype(float) self.var.InvChanLength = 1 / self.var.ChanLength # Inverse of channel length [1/m] self.var.NoRoutSteps = int( np.maximum(1, round(self.var.DtSec / self.var.DtSecChannel, 0))) # Number of sub-steps based on value of DtSecChannel, # or 1 if DtSec is smaller than DtSecChannel settings = LisSettings.instance() option = settings.options if option['InitLisflood']: self.var.NoRoutSteps = 1 # InitLisflood is used! # so channel routing step is the same as the general time step self.var.DtRouting = self.var.DtSec / self.var.NoRoutSteps # Corresponding sub-timestep (seconds) self.var.InvDtRouting = 1 / self.var.DtRouting self.var.InvNoRoutSteps = 1 / float(self.var.NoRoutSteps) # inverse for faster calculation inside the dynamic section # -------------------------- LDD self.var.Ldd = lddmask(loadmap('Ldd', pcr=True, lddflag=True), self.var.MaskMap) # Cut ldd to size of MaskMap (NEW, 29/9/2004) # Prevents 'unsound' ldd if MaskMap covers sub-area of ldd # Count (inverse of) upstream area for each pixel # Needed if we want to calculate average values of variables # upstream of gauge locations self.var.UpArea = accuflux(self.var.Ldd, self.var.PixelAreaPcr) # Upstream contributing area for each pixel # Note that you might expext that values of UpArea would be identical to # those of variable CatchArea (see below) at the outflow points. # This is NOT actually the case, because outflow points are shifted 1 # cell in upstream direction in the calculation of CatchArea! self.var.InvUpArea = 1 / self.var.UpArea # Calculate inverse, so we can multiply in dynamic (faster than divide) self.var.IsChannelPcr = boolean(loadmap('Channels', pcr=True)) self.var.IsChannel = np.bool8(compressArray(self.var.IsChannelPcr)) # Identify channel pixels self.var.IsChannelKinematic = self.var.IsChannel.copy() # Identify kinematic wave channel pixels # (identical to IsChannel, unless dynamic wave is used, see below) #self.var.IsStructureKinematic = pcraster.boolean(0) self.var.IsStructureKinematic = np.bool8(maskinfo.in_zero()) # Map that identifies special inflow/outflow structures (reservoirs, lakes) within the # kinematic wave channel routing. Set to (dummy) value of zero modified in reservoir and lake # routines (if those are used) LddChan = lddmask(self.var.Ldd, self.var.IsChannelPcr) # ldd for Channel network self.var.MaskMap = boolean(self.var.Ldd) # Use boolean version of Ldd as calculation mask # (important for correct mass balance check # any water generated outside of Ldd won't reach # channel anyway) self.var.LddToChan = lddrepair( ifthenelse(self.var.IsChannelPcr, 5, self.var.Ldd)) # Routing of runoff (incl. ground water)en AtOutflow = boolean(pit(self.var.Ldd)) # find outlet points... if option['dynamicWave']: IsChannelDynamic = boolean(loadmap('ChannelsDynamic', pcr=True)) # Identify channel pixels where dynamic wave is used self.var.IsChannelKinematic = (self.var.IsChannelPcr == 1) & (IsChannelDynamic == 0) # Identify (update) channel pixels where kinematic wave is used self.var.LddKinematic = lddmask(self.var.Ldd, self.var.IsChannelKinematic) # Ldd for kinematic wave: ends (pit) just before dynamic stretch LddDynamic = lddmask(self.var.Ldd, IsChannelDynamic) # Ldd for dynamic wave # Following statements produce an ldd network that connects the pits in # LddKinematic to the nearest downstream dynamic wave pixel LddToDyn = lddrepair(ifthenelse(IsChannelDynamic, 5, self.var.Ldd)) # Temporary ldd: flow paths end in dynamic pixels PitsKinematic = cover(boolean(pit(self.var.LddKinematic)), 0) # Define start of each flow path at pit on LddKinematic PathKinToDyn = path(LddToDyn, PitsKinematic) # Identify paths that connect pits in LddKinematic to dynamic wave # pixels LddKinToDyn = lddmask(LddToDyn, PathKinToDyn) # Create ldd DynWaveBoundaryCondition = boolean(pit(LddDynamic)) # NEW 12-7-2005 (experimental) # Location of boundary condition dynamic wave self.var.AtLastPoint = (downstream( self.var.Ldd, AtOutflow) == 1) & (AtOutflow != 1) & self.var.IsChannelPcr # NEW 23-6-2005 # Dynamic wave routine gives no outflow out of pits, so we calculate this # one cell upstream (WvD) # (implies that most downstream cell is not taken into account in mass balance # calculations, even if dyn wave is not used) # Only include points that are on a channel (otherwise some small 'micro-catchments' # are included, for which the mass balance cannot be calculated # properly) else: self.var.LddKinematic = LddChan # No dynamic wave, so kinematic ldd equals channel ldd self.var.AtLastPoint = AtOutflow self.var.AtLastPointC = np.bool8( compressArray(self.var.AtLastPoint)) # assign unique identifier to each of them maskinfo = MaskInfo.instance() lddC = compressArray(self.var.LddKinematic) inAr = decompress(np.arange(maskinfo.info.mapC[0], dtype="int32")) # giving a number to each non missing pixel as id self.var.downstruct = (compressArray( downstream(self.var.LddKinematic, inAr))).astype("int32") # each upstream pixel gets the id of the downstream pixel self.var.downstruct[lddC == 5] = maskinfo.info.mapC[0] # all pits gets a high number #d3=np.bincount(self.var.down, weights=loadmap('AvgDis'))[:-1] # upstream function in numpy OutflowPoints = nominal(uniqueid(self.var.AtLastPoint)) # and assign unique identifier to each of them self.var.Catchments = (compressArray( catchment(self.var.Ldd, OutflowPoints))).astype(np.int32) CatchArea = np.bincount( self.var.Catchments, weights=self.var.PixelArea)[self.var.Catchments] #CatchArea = CatchArea[self.var.Catchments] # define catchment for each outflow point #CatchArea = areatotal(self.var.PixelArea, self.var.Catchments) # Compute area of each catchment [m2] # Note: in earlier versions this was calculated using the "areaarea" function, # changed to "areatotal" in order to enable handling of grids with spatially # variable cell areas (e.g. lat/lon grids) self.var.InvCatchArea = 1 / CatchArea # inverse of catchment area [1/m2] # ************************************************************ # ***** CHANNEL GEOMETRY ************************************ # ************************************************************ self.var.ChanGrad = np.maximum(loadmap('ChanGrad'), loadmap('ChanGradMin')) # avoid calculation of Alpha using ChanGrad=0: this creates MV! self.var.CalChanMan = loadmap('CalChanMan') self.var.ChanMan = self.var.CalChanMan * loadmap('ChanMan') # Manning's n is multiplied by ChanManCal # enables calibration for peak timing self.var.ChanBottomWidth = loadmap('ChanBottomWidth') ChanDepthThreshold = loadmap('ChanDepthThreshold') ChanSdXdY = loadmap('ChanSdXdY') self.var.ChanUpperWidth = self.var.ChanBottomWidth + 2 * ChanSdXdY * ChanDepthThreshold # Channel upper width [m] self.var.TotalCrossSectionAreaBankFull = 0.5 * \ ChanDepthThreshold * (self.var.ChanUpperWidth + self.var.ChanBottomWidth) # Area (sq m) of bank full discharge cross section [m2] # (trapezoid area equation) TotalCrossSectionAreaHalfBankFull = 0.5 * self.var.TotalCrossSectionAreaBankFull # Cross-sectional area at half bankfull [m2] # This can be used to initialise channel flow (see below) TotalCrossSectionAreaInitValue = loadmap( 'TotalCrossSectionAreaInitValue') self.var.TotalCrossSectionArea = np.where( TotalCrossSectionAreaInitValue == -9999, TotalCrossSectionAreaHalfBankFull, TotalCrossSectionAreaInitValue) # Total cross-sectional area [m2]: if initial value in binding equals -9999 the value at half bankfull is used, # otherwise TotalCrossSectionAreaInitValue (typically end map from previous simulation) if option['SplitRouting']: # in_zero = maskinfo.in_zero() CrossSection2AreaInitValue = loadmap('CrossSection2AreaInitValue') self.var.CrossSection2Area = np.where( CrossSection2AreaInitValue == -9999, maskinfo.in_zero(), CrossSection2AreaInitValue) # cross-sectional area [m2] for 2nd line of routing: if initial value in binding equals -9999 the value is set to 0 # otherwise CrossSection2AreaInitValue (typically end map from previous simulation) PrevSideflowInitValue = loadmap('PrevSideflowInitValue') self.var.Sideflow1Chan = np.where(PrevSideflowInitValue == -9999, maskinfo.in_zero(), PrevSideflowInitValue) # sideflow from previous run for 1st line of routing: if initial value in binding equals -9999 the value is set to 0 # otherwise PrevSideflowInitValue (typically end map from previous simulation) # ************************************************************ # ***** CHANNEL ALPHA (KIN. WAVE)***************************** # ************************************************************ # Following calculations are needed to calculate Alpha parameter in kinematic # wave. Alpha currently fixed at half of bankful depth (this may change in # future versions!) ChanWaterDepthAlpha = np.where(self.var.IsChannel, 0.5 * ChanDepthThreshold, 0.0) # Reference water depth for calculation of Alpha: half of bankfull self.var.ChanWettedPerimeterAlpha = self.var.ChanBottomWidth + 2 * \ np.sqrt(np.square(ChanWaterDepthAlpha) + np.square(ChanWaterDepthAlpha * ChanSdXdY)) # Channel wetted perimeter [m](Pythagoras) AlpTermChan = (self.var.ChanMan / (np.sqrt(self.var.ChanGrad)))**self.var.Beta self.var.AlpPow = 2.0 / 3.0 * self.var.Beta self.var.ChannelAlpha = ( AlpTermChan * (self.var.ChanWettedPerimeterAlpha**self.var.AlpPow)).astype(float) self.var.InvChannelAlpha = 1 / self.var.ChannelAlpha # ChannelAlpha for kinematic wave # ************************************************************ # ***** CHANNEL INITIAL DISCHARGE **************************** # ************************************************************ self.var.ChanM3 = self.var.TotalCrossSectionArea * self.var.ChanLength # channel water volume [m3] self.var.ChanIniM3 = self.var.ChanM3.copy() self.var.ChanM3Kin = self.var.ChanIniM3.copy().astype(float) # Initialise water volume in kinematic wave channels [m3] self.var.ChanQKin = np.where(self.var.ChannelAlpha > 0, (self.var.TotalCrossSectionArea / self.var.ChannelAlpha)**self.var.InvBeta, 0).astype(float) # Initialise discharge at kinematic wave pixels (note that InvBeta is # simply 1/beta, computational efficiency!) self.var.CumQ = maskinfo.in_zero() # ininialise sum of discharge to calculate average # ************************************************************ # ***** CHANNEL INITIAL DYNAMIC WAVE ************************* # ************************************************************ if option['dynamicWave']: pass # TODO !!!!!!!!!!!!!!!!!!!! # lookchan = lookupstate(TabCrossSections, ChanCrossSections, ChanBottomLevel, self.var.ChanLength, # DynWaveConstantHeadBoundary + ChanBottomLevel) # ChanIniM3 = ifthenelse(AtOutflow, lookchan, ChanIniM3) # Correct ChanIniM3 for constant head boundary in pit (only if # dynamic wave is used) # ChanM3Dyn = ChanIniM3 # Set volume of water in dynamic wave channel to initial value # (note that initial condition is expressed as a state in [m3] for the dynamic wave, # and as a rate [m3/s] for the kinematic wave (a bit confusing) # Estimate number of iterations needed in first time step (based on Courant criterium) # TO DO !!!!!!!!!!!!!!!!!!!! # Potential = lookuppotential( # TabCrossSections, ChanCrossSections, ChanBottomLevel, self.var.ChanLength, ChanM3Dyn) # Potential # WaterLevelDyn = Potential - ChanBottomLevel # Water level [m above bottom level) # WaveCelerityDyn = pcraster.sqrt(9.81 * WaterLevelDyn) # Dynamic wave celerity [m/s] # CourantDynamic = self.var.DtSec * \ # (WaveCelerityDyn + 2) / self.var.ChanLength # Courant number for dynamic wave # We don't know the water velocity at this time so # we just guess it's 2 m/s (Odra tests show that flow velocity # is typically much lower than wave celerity, and 2 m/s is quite # high already so this gives a pretty conservative/safe estimate # for DynWaveIterations) # DynWaveIterationsTemp = max( # 1, roundup(CourantDynamic / CourantDynamicCrit)) # DynWaveIterations = ordinal(mapmaximum(DynWaveIterationsTemp)) # Number of sub-steps needed for required numerical # accuracy. Always greater than or equal to 1 # (otherwise division by zero!) # TEST # If polder option is used, we need an estimate of the initial channel discharge, but we don't know this # for the dynamic wave pixels (since only initial state is known)! Try if this works (dyn wave flux based on zero inflow 1 iteration) # Note that resulting ChanQ is ONLY used in the polder routine!!! # Since we need instantaneous estimate at start of time step, a # ChanQM3Dyn is calculated for one single one-second time step!!! # ChanQDyn = dynwaveflux(TabCrossSections, # ChanCrossSections, # LddDynamic, # ChanIniM3, # 0.0, # ChanBottomLevel, # self.var.ChanMan, # self.var.ChanLength, # 1, # 1, # DynWaveBoundaryCondition) # Compute volume and discharge in channel after dynamic wave # ChanM3Dyn in [cu m] # ChanQDyn in [cu m / s] # self.var.ChanQ = ifthenelse( # IsChannelDynamic, ChanQDyn, self.var.ChanQKin) # Channel discharge: combine results of kinematic and dynamic wave else: # ***** NO DYNAMIC WAVE ************************* # Dummy code if dynamic wave is not used, in which case ChanQ equals ChanQKin # (needed only for polder routine) PrevDischarge = loadmap('PrevDischarge') self.var.ChanQ = np.where(PrevDischarge == -9999, self.var.ChanQKin, PrevDischarge) # initialise channel discharge: cold start: equal to ChanQKin # [m3/s] # Initialising cumulative output variables # These are all needed to compute the cumulative mass balance error self.var.DischargeM3Out = maskinfo.in_zero() # cumulative discharge at outlet [m3] self.var.TotalQInM3 = maskinfo.in_zero() # cumulative inflow from inflow hydrographs [m3] #self.var.sumDis = maskinfo.in_zero() self.var.sumDis = maskinfo.in_zero() self.var.sumIn = maskinfo.in_zero()
def getReservoirOutflow( self, avgChannelDischarge, length_of_time_step, downstreamDemand ): # avgOutflow (m3/s) avgOutflow = self.avgOutflow # The following is needed when new lakes/reservoirs introduced (its avgOutflow is still zero). # ~ # - alternative 1 # ~ avgOutflow = pcr.ifthenelse(\ # ~ avgOutflow > 0.,\ # ~ avgOutflow, # ~ pcr.max(avgChannelDischarge, self.avgInflow, 0.001)) # - alternative 2 avgOutflow = pcr.ifthenelse( avgOutflow > 0.0, avgOutflow, pcr.max(avgChannelDischarge, self.avgInflow) ) avgOutflow = pcr.ifthenelse( avgOutflow > 0.0, avgOutflow, pcr.downstream(self.lddMap, avgOutflow) ) avgOutflow = pcr.areamaximum(avgOutflow, self.waterBodyIds) # calculate resvOutflow (m2/s) (based on reservoir storage and avgDischarge): # - using reductionFactor in such a way that: # - if relativeCapacity < minResvrFrac : release is terminated # - if relativeCapacity > maxResvrFrac : longterm average reductionFactor = pcr.cover( pcr.min( 1.0, pcr.max( 0.0, self.waterBodyStorage - self.minResvrFrac * self.waterBodyCap ) / (self.maxResvrFrac - self.minResvrFrac) * self.waterBodyCap, ), 0.0, ) # resvOutflow = reductionFactor * avgOutflow * length_of_time_step # unit: m3 # maximum release <= average inflow (especially during dry condition) resvOutflow = pcr.max( 0, pcr.min(resvOutflow, self.avgInflow * length_of_time_step) ) # unit: m3 # downstream demand (m3/s) # reduce demand if storage < lower limit reductionFactor = vos.getValDivZero( downstreamDemand, self.minResvrFrac * self.waterBodyCap, vos.smallNumber ) reductionFactor = pcr.cover(reductionFactor, 0.0) downstreamDemand = pcr.min(downstreamDemand, downstreamDemand * reductionFactor) # resvOutflow > downstreamDemand resvOutflow = pcr.max( resvOutflow, downstreamDemand * length_of_time_step ) # unit: m3 # floodOutflow: additional release if storage > upper limit ratioQBankfull = 2.3 estmStorage = pcr.max(0.0, self.waterBodyStorage - resvOutflow) floodOutflow = pcr.max(0.0, estmStorage - self.waterBodyCap) + pcr.cover( pcr.max(0.0, estmStorage - self.maxResvrFrac * self.waterBodyCap) / ((1.0 - self.maxResvrFrac) * self.waterBodyCap), 0.0, ) * pcr.max( 0.0, ratioQBankfull * avgOutflow * vos.secondsPerDay() - resvOutflow ) floodOutflow = pcr.max( 0.0, pcr.min( floodOutflow, estmStorage - self.maxResvrFrac * self.waterBodyCap * 0.75 ), ) # maximum limit of floodOutflow: bring the reservoir storages only to 3/4 of upper limit capacities # update resvOutflow after floodOutflow resvOutflow = pcr.cover(resvOutflow, 0.0) + pcr.cover(floodOutflow, 0.0) # maximum release if storage > upper limit : bring the reservoir storages only to 3/4 of upper limit capacities resvOutflow = pcr.ifthenelse( self.waterBodyStorage > self.maxResvrFrac * self.waterBodyCap, pcr.min( resvOutflow, pcr.max( 0, self.waterBodyStorage - self.maxResvrFrac * self.waterBodyCap * 0.75, ), ), resvOutflow, ) # if storage > upper limit : resvOutflow > avgInflow resvOutflow = pcr.ifthenelse( self.waterBodyStorage > self.maxResvrFrac * self.waterBodyCap, pcr.max(0.0, resvOutflow, self.avgInflow), resvOutflow, ) # resvOutflow < waterBodyStorage resvOutflow = pcr.min(self.waterBodyStorage, resvOutflow) resvOutflow = pcr.ifthen(pcr.scalar(self.waterBodyIds) > 0.0, resvOutflow) resvOutflow = pcr.ifthen(pcr.scalar(self.waterBodyTyp) == 2, resvOutflow) return resvOutflow # unit: m3
def subcatch_order_b( ldd, oorder, sizelimit=0, fill=False, fillcomplete=False, stoporder=0 ): """ Determines subcatchments using the catchment order This version tries to keep the number op upstream/downstream catchment the small by first dederivingatchment connected to the major river(the order) given, and fill up from there. Input: - ldd - oorder - order to use - sizelimit - smallest catchments to include, default is all (sizelimit=0) in number of cells - if fill is set to True the higer order catchment are filled also - if fillcomplete is set to True the whole ldd is filled with catchments. :returns sc, dif, nldd; Subcatchment, Points, subcatchldd """ # outl = find_outlet(ldd) # large = pcr.subcatchment(ldd,pcr.boolean(outl)) if stoporder == 0: stoporder = oorder stt = pcr.streamorder(ldd) sttd = pcr.downstream(ldd, stt) pts = pcr.ifthen((pcr.scalar(sttd) - pcr.scalar(stt)) > 0.0, sttd) maxorder = pcraster.framework.getCellValue(pcr.mapmaximum(stt), 1, 1) dif = pcr.uniqueid(pcr.boolean(pcr.ifthen(stt == pcr.ordinal(oorder), pts))) if fill: for order in range(oorder, maxorder): m_pts = pcr.ifthen((pcr.scalar(sttd) - pcr.scalar(order)) > 0.0, sttd) m_dif = pcr.uniqueid( pcr.boolean(pcr.ifthen(stt == pcr.ordinal(order), m_pts)) ) dif = pcr.uniqueid(pcr.boolean(pcr.cover(m_dif, dif))) for myorder in range(oorder - 1, stoporder, -1): sc = pcr.subcatchment(ldd, pcr.nominal(dif)) m_pts = pcr.ifthen((pcr.scalar(sttd) - pcr.scalar(stt)) > 0.0, sttd) m_dif = pcr.uniqueid( pcr.boolean(pcr.ifthen(stt == pcr.ordinal(myorder - 1), m_pts)) ) dif = pcr.uniqueid( pcr.boolean(pcr.cover(pcr.ifthen(pcr.scalar(sc) == 0, m_dif), dif)) ) if fillcomplete: sc = pcr.subcatchment(ldd, pcr.nominal(dif)) cs, m_dif, stt = subcatch_order_a(ldd, stoporder) dif = pcr.uniqueid( pcr.boolean( pcr.cover( pcr.ifthen(pcr.scalar(sc) == 0, pcr.ordinal(m_dif)), pcr.ordinal(dif), ) ) ) scsize = pcr.catchmenttotal(1, ldd) dif = pcr.ordinal(pcr.uniqueid(pcr.boolean(pcr.ifthen(scsize >= sizelimit, dif)))) sc = pcr.subcatchment(ldd, dif) # Make pit ldd nldd = pcr.lddrepair(pcr.ifthenelse(pcr.cover(dif, 0) > 0, 5, ldd)) return sc, dif, nldd
def subcatch_stream( ldd, threshold, min_strahler=-999, max_strahler=999, assign_edge=False, assign_existing=False, up_area=None, ): """ (From Deltares Hydrotools) Derive catchments based upon strahler threshold Input: ldd -- pcraster object direction, local drain directions threshold -- integer, strahler threshold, subcatchments ge threshold are derived min_strahler -- integer, minimum strahler threshold of river catchments to return max_strahler -- integer, maximum strahler threshold of river catchments to return assign_unique=False -- if set to True, unassigned connected areas at the edges of the domain are assigned a unique id as well. If set to False, edges are not assigned assign_existing=False == if set to True, unassigned edges are assigned to existing basins with an upstream weighting. If set to False, edges are assigned to unique IDs, or not assigned output: stream_ge -- pcraster object, streams of strahler order ge threshold subcatch -- pcraster object, subcatchments of strahler order ge threshold """ # derive stream order stream = pcr.streamorder(ldd) stream_ge = pcr.ifthen(stream >= threshold, stream) stream_up_sum = pcr.ordinal(pcr.upstream(ldd, pcr.cover(pcr.scalar(stream_ge), 0))) # detect any transfer of strahler order, to a higher strahler order. transition_strahler = pcr.ifthenelse( pcr.downstream(ldd, stream_ge) != stream_ge, pcr.boolean(1), pcr.ifthenelse( pcr.nominal(ldd) == 5, pcr.boolean(1), pcr.ifthenelse( pcr.downstream(ldd, pcr.scalar(stream_up_sum)) > pcr.scalar(stream_ge), pcr.boolean(1), pcr.boolean(0), ), ), ) # make unique ids (write to file) transition_unique = pcr.ordinal(pcr.uniqueid(transition_strahler)) # derive upstream catchment areas (write to file) subcatch = pcr.nominal(pcr.subcatchment(ldd, transition_unique)) if assign_edge: # fill unclassified areas (in pcraster equal to zero) with a unique id, above the maximum id assigned so far unique_edge = pcr.clump(pcr.ifthen(subcatch == 0, pcr.ordinal(0))) subcatch = pcr.ifthenelse( subcatch == 0, pcr.nominal(pcr.mapmaximum(pcr.scalar(subcatch)) + pcr.scalar(unique_edge)), pcr.nominal(subcatch), ) elif assign_existing: # unaccounted areas are added to largest nearest draining basin if up_area is None: up_area = pcr.ifthen( pcr.boolean(pcr.cover(stream_ge, 0)), pcr.accuflux(ldd, 1) ) riverid = pcr.ifthen(pcr.boolean(pcr.cover(stream_ge, 0)), subcatch) friction = 1.0 / pcr.scalar( pcr.spreadzone(pcr.cover(pcr.ordinal(up_area), 0), 0, 0) ) # *(pcr.scalar(ldd)*0+1) delta = pcr.ifthen( pcr.scalar(ldd) >= 0, pcr.ifthen( pcr.cover(subcatch, 0) == 0, pcr.spreadzone(pcr.cover(riverid, 0), 0, friction), ), ) subcatch = pcr.ifthenelse(pcr.boolean(pcr.cover(subcatch, 0)), subcatch, delta) # finally, only keep basins with minimum and maximum river order flowing through them strahler_subcatch = pcr.areamaximum(stream, subcatch) subcatch = pcr.ifthen( pcr.ordinal(strahler_subcatch) >= min_strahler, pcr.ifthen(pcr.ordinal(strahler_subcatch) <= max_strahler, subcatch), ) return stream_ge, pcr.ordinal(subcatch)
def subcatch_stream(ldd, threshold, stream=None, min_strahler=-999, max_strahler=999, assign_edge=False, assign_existing=False, up_area=None, basin=None): """ Derive catchments based upon strahler threshold Input: ldd -- pcraster object direction, local drain directions threshold -- integer, strahler threshold, subcatchments ge threshold are derived stream=None -- pcraster object ordinal, stream order map (made with pcr.streamorder), if provided, stream order map is not generated on the fly but used from this map. Useful when a subdomain within a catchment is provided, which would cause edge effects in the stream order map min_strahler=-999 -- integer, minimum strahler threshold of river catchments to return max_strahler=999 -- integer, maximum strahler threshold of river catchments to return assign_unique=False -- if set to True, unassigned connected areas at the edges of the domain are assigned a unique id as well. If set to False, edges are not assigned assign_existing=False == if set to True, unassigned edges are assigned to existing basins with an upstream weighting. If set to False, edges are assigned to unique IDs, or not assigned output: stream_ge -- pcraster object, streams of strahler order ge threshold subcatch -- pcraster object, subcatchments of strahler order ge threshold """ # derive stream order if stream is None: stream = pcr.streamorder(ldd) stream_ge = pcr.ifthen(stream >= threshold, stream) stream_up_sum = pcr.ordinal(pcr.upstream(ldd, pcr.cover(pcr.scalar(stream_ge), 0))) # detect any transfer of strahler order, to a higher strahler order. transition_strahler = pcr.ifthenelse(pcr.downstream(ldd, stream_ge) != stream_ge, pcr.boolean(1), pcr.ifthenelse(pcr.nominal(ldd) == 5, pcr.boolean(1), pcr.ifthenelse(pcr.downstream(ldd, pcr.scalar(stream_up_sum)) > pcr.scalar(stream_ge), pcr.boolean(1), pcr.boolean(0)))) # make unique ids (write to file) transition_unique = pcr.ordinal(pcr.uniqueid(transition_strahler)) # derive upstream catchment areas (write to file) subcatch = pcr.nominal(pcr.subcatchment(ldd, transition_unique)) # mask out areas outside basin if basin is not None: subcatch = pcr.ifthen(basin, subcatch) if assign_edge: # fill unclassified areas (in pcraster equal to zero) with a unique id, above the maximum id assigned so far unique_edge = pcr.clump(pcr.ifthen(subcatch==0, pcr.ordinal(0))) subcatch = pcr.ifthenelse(subcatch==0, pcr.nominal(pcr.mapmaximum(pcr.scalar(subcatch)) + pcr.scalar(unique_edge)), pcr.nominal(subcatch)) elif assign_existing: # unaccounted areas are added to largest nearest draining basin if up_area is None: up_area = pcr.ifthen(pcr.boolean(pcr.cover(stream_ge, 0)), pcr.accuflux(ldd, 1)) riverid = pcr.ifthen(pcr.boolean(pcr.cover(stream_ge, 0)), subcatch) friction = 1./pcr.scalar(pcr.spreadzone(pcr.cover(pcr.ordinal(up_area), 0), 0, 0)) # *(pcr.scalar(ldd)*0+1) delta = pcr.ifthen(pcr.scalar(ldd)>=0, pcr.ifthen(pcr.cover(subcatch, 0)==0, pcr.spreadzone(pcr.cover(riverid, 0), 0, friction))) subcatch = pcr.ifthenelse(pcr.boolean(pcr.cover(subcatch, 0)), subcatch, delta) # finally, only keep basins with minimum and maximum river order flowing through them strahler_subcatch = pcr.areamaximum(stream, subcatch) subcatch = pcr.ifthen(pcr.ordinal(strahler_subcatch) >= min_strahler, pcr.ifthen(pcr.ordinal(strahler_subcatch) <= max_strahler, subcatch)) return stream_ge, pcr.ordinal(subcatch)
def initial(self): """ initial part of the lakes module """ # ************************************************************ # ***** LAKES # ************************************************************ settings = LisSettings.instance() option = settings.options binding = settings.binding maskinfo = MaskInfo.instance() if option['simulateLakes']: LakeSitesC = loadmap('LakeSites') LakeSitesC[LakeSitesC < 1] = 0 LakeSitesC[self.var.IsChannel == 0] = 0 # Get rid of any lakes that are not part of the channel network # mask lakes sites when using sub-catchments mask self.var.LakeSitesCC = np.compress(LakeSitesC > 0, LakeSitesC) self.var.LakeIndex = np.nonzero(LakeSitesC)[0] if self.var.LakeSitesCC.size == 0: print(LisfloodWarning('There are no lakes. Lakes simulation stops here')) option['simulateLakes'] = False option['repsimulateLakes'] = False return # break if no lakes self.var.IsStructureKinematic = np.where(LakeSitesC > 0, np.bool8(1), self.var.IsStructureKinematic) # Add lake locations to structures map (used to modify LddKinematic # and to calculate LddStructuresKinematic) # PCRaster part # ----------------------- LakeSitePcr = loadmap('LakeSites', pcr=True) LakeSitePcr = pcraster.ifthen((pcraster.defined(LakeSitePcr) & pcraster.boolean(decompress(self.var.IsChannel))), LakeSitePcr) IsStructureLake = pcraster.boolean(LakeSitePcr) # additional structure map only for lakes to calculate water balance self.var.IsUpsOfStructureLake = pcraster.downstream(self.var.LddKinematic, pcraster.cover(IsStructureLake, 0)) # Get all pixels just upstream of lakes # ----------------------- self.var.LakeInflowOldCC = np.bincount(self.var.downstruct, weights=self.var.ChanQ)[self.var.LakeIndex] # for Modified Puls Method the Q(inflow)1 has to be used. # It is assumed that this is the same as Q(inflow)2 for the first timestep # has to be checked if this works in forecasting mode! LakeArea = pcraster.lookupscalar(str(binding['TabLakeArea']), LakeSitePcr) LakeAreaC = compressArray(LakeArea) self.var.LakeAreaCC = np.compress(LakeSitesC > 0, LakeAreaC) # Surface area of each lake [m2] LakeA = pcraster.lookupscalar(str(binding['TabLakeA']), LakeSitePcr) LakeAC = compressArray(LakeA) * loadmap('LakeMultiplier') self.var.LakeACC = np.compress(LakeSitesC > 0, LakeAC) # Lake parameter A (suggested value equal to outflow width in [m]) # multiplied with the calibration parameter LakeMultiplier LakeInitialLevelValue = loadmap('LakeInitialLevelValue') if np.max(LakeInitialLevelValue) == -9999: LakeAvNetInflowEstimate = pcraster.lookupscalar(str(binding['TabLakeAvNetInflowEstimate']), LakeSitePcr) LakeAvNetC = compressArray(LakeAvNetInflowEstimate) self.var.LakeAvNetCC = np.compress(LakeSitesC > 0, LakeAvNetC) LakeStorageIniM3CC = self.var.LakeAreaCC * np.sqrt(self.var.LakeAvNetCC / self.var.LakeACC) # Initial lake storage [m3] based on: S = LakeArea * H = LakeArea # * sqrt(Q/a) self.var.LakeLevelCC = LakeStorageIniM3CC / self.var.LakeAreaCC else: self.var.LakeLevelCC = np.compress(LakeSitesC > 0, LakeInitialLevelValue) LakeStorageIniM3CC = self.var.LakeAreaCC * self.var.LakeLevelCC # Initial lake storage [m3] based on: S = LakeArea * H self.var.LakeAvNetCC = np.compress(LakeSitesC > 0, loadmap('PrevDischarge')) # Repeatedly used expressions in lake routine # NEW Lake Routine using Modified Puls Method (see Maniak, p.331ff) # (Qin1 + Qin2)/2 - (Qout1 + Qout2)/2 = (S2 - S1)/dtime # changed into: # (S2/dtime + Qout2/2) = (S1/dtime + Qout1/2) - Qout1 + (Qin1 + Qin2)/2 # outgoing discharge (Qout) are linked to storage (S) by elevation. # Now some assumption to make life easier: # 1.) storage volume is increase proportional to elevation: S = A * H # H: elevation, A: area of lake # 2.) outgoing discharge = c * b * H **2.0 (c: weir constant, b: width) # 2.0 because it fits to a parabolic cross section see Aigner 2008 # (and it is much easier to calculate (that's the main reason) # c for a perfect weir with mu=0.577 and Poleni: 2/3 mu * sqrt(2*g) = 1.7 # c for a parabolic weir: around 1.8 # because it is a imperfect weir: C = c* 0.85 = 1.5 # results in a formular : Q = 1.5 * b * H ** 2 = a*H**2 -> H = # sqrt(Q/a) self.var.LakeFactor = self.var.LakeAreaCC / (self.var.DtRouting * np.sqrt(self.var.LakeACC)) # solving the equation (S2/dtime + Qout2/2) = (S1/dtime + Qout1/2) - Qout1 + (Qin1 + Qin2)/2 # SI = (S2/dtime + Qout2/2) = (A*H)/DtRouting + Q/2 = A/(DtRouting*sqrt(a) * sqrt(Q) + Q/2 # -> replacement: A/(DtRouting*sqrt(a)) = Lakefactor, Y = sqrt(Q) # Y**2 + 2*Lakefactor*Y-2*SI=0 # solution of this quadratic equation: # Q=sqr(-LakeFactor+sqrt(sqr(LakeFactor)+2*SI)) self.var.LakeFactorSqr = np.square(self.var.LakeFactor) # for faster calculation inside dynamic section LakeStorageIndicator = LakeStorageIniM3CC / self.var.DtRouting + self.var.LakeAvNetCC / 2 # SI = S/dt + Q/2 self.var.LakeOutflow = np.square(-self.var.LakeFactor + np.sqrt(self.var.LakeFactorSqr + 2 * LakeStorageIndicator)) # solution of quadratic equation # it is as easy as this because: # 1. storage volume is increase proportional to elevation # 2. Q= a *H **2.0 (if you choose Q= a *H **1.5 you have to solve # the formula of Cardano) self.var.LakeStorageM3CC = LakeStorageIniM3CC.copy() self.var.LakeStorageM3BalanceCC = LakeStorageIniM3CC.copy() self.var.LakeStorageIniM3 = maskinfo.in_zero() self.var.LakeLevel = maskinfo.in_zero() np.put(self.var.LakeStorageIniM3,self.var.LakeIndex,LakeStorageIniM3CC) self.var.LakeStorageM3 = self.var.LakeStorageIniM3.copy() np.put(self.var.LakeLevel, self.var.LakeIndex, self.var.LakeLevelCC) self.var.EWLakeCUMM3 = maskinfo.in_zero()