Source code for openquake.hazardlib.geo.surface.multi
# -*- coding: utf-8 -*-# vim: tabstop=4 shiftwidth=4 softtabstop=4## Copyright (C) 2013-2023 GEM Foundation## OpenQuake is free software: you can redistribute it and/or modify it# under the terms of the GNU Affero General Public License as published# by the Free Software Foundation, either version 3 of the License, or# (at your option) any later version.## OpenQuake is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU Affero General Public License for more details.## You should have received a copy of the GNU Affero General Public License# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>."""Module :mod:`openquake.hazardlib.geo.surface.multi` defines:class:`MultiSurface`."""importnumpyasnpfromshapely.geometryimportPolygonfromopenquake.baselib.performanceimportMonitorfromopenquake.hazardlib.geo.surface.baseimportBaseSurfacefromopenquake.hazardlib.geo.meshimportMeshfromopenquake.hazardlib.geoimportutilsfromopenquake.hazardlibimportgeofromopenquake.hazardlib.geo.surfaceimportPlanarSurfaceF32=np.float32MSPARAMS=['area','dip','strike','u_max','width','zbot','ztor','tl0','tl1','tr0','tr1','west','east','north','south']MS_DT=[(p,np.float32)forpinMSPARAMS]+[('hypo',(F32,3))]# really fast
[docs]defbuild_secparams(sections):""" :returns: an array of section parameters """secparams=np.zeros(len(sections),MS_DT)forsparam,secinzip(secparams,sections):sparam['area']=sec.get_area()sparam['dip']=sec.get_dip()sparam['strike']=sec.get_strike()sparam['width']=sec.get_width()sparam['ztor']=sec.get_top_edge_depth()sparam['zbot']=sec.mesh.depths.max()sparam['tl0']=sec.tor.coo[0,0]sparam['tl1']=sec.tor.coo[0,1]sparam['tr0']=sec.tor.coo[-1,0]sparam['tr1']=sec.tor.coo[-1,1]bb=sec.get_bounding_box()sparam['west']=bb[0]sparam['east']=bb[1]sparam['north']=bb[2]sparam['south']=bb[3]mid=sec.get_middle_point()sparam['hypo']=(mid.x,mid.y,mid.z)returnsecparams
# not fast
[docs]defbuild_msparams(rupture_idxs,secparams,close_sec=None,ry0=False,mon1=Monitor(),mon2=Monitor()):""" :returns: a structured array of parameters """U=len(rupture_idxs)# number of rupturesmsparams=np.zeros(U,MS_DT)ifclose_secisNone:# NB: in the engine close_sec is computed in the preclassical phaseclose_sec=np.ones(len(secparams),bool)# building lines, very fastwithmon1:lines=[]forsecparaminsecparams:tl0,tl1,tr0,tr1=secparam[['tl0','tl1','tr0','tr1']]line=geo.Line.from_coo(np.array([[tl0,tl1],[tr0,tr1]],float))lines.append(line)# building msparams, slow due to the computation of u_maxwithmon2:formsparam,idxsinzip(msparams,rupture_idxs):# building u_maxtors=[lines[idx]foridxinidxsifclose_sec[idx]]ifnottors:# all sections are far awaycontinueifry0:msparam['u_max']=geo.MultiLine(tors).get_u_max()# building simple multisurface paramssecparam=secparams[idxs]areas=secparam['area']msparam['area']=areas.sum()ws=areas/msparam['area']# weightsmsparam['dip']=ws@secparam['dip']msparam['strike']=utils.angular_mean_weighted(secparam['strike'],ws)%360msparam['width']=ws@secparam['width']msparam['ztor']=ws@secparam['ztor']msparam['zbot']=ws@secparam['zbot']# building bounding boxlons=np.concatenate([secparam['west'],secparam['east']])lats=np.concatenate([secparam['north'],secparam['south']])bb=utils.get_spherical_bounding_box(lons,lats)msparam['west']=bb[0]msparam['east']=bb[1]msparam['north']=bb[2]msparam['south']=bb[3]returnmsparams
[docs]classMultiSurface(BaseSurface):""" Represent a surface as a collection of independent surface elements. :param surfaces: List of instances of subclasses of :class:`~openquake.hazardlib.geo.surface.base.BaseSurface` each representing a surface geometry element. """@propertydefsurface_nodes(self):""" :returns: a list of surface nodes from the underlying single node surfaces """iftype(self.surfaces[0]).__name__=='PlanarSurface':return[surf.surface_nodes[0]forsurfinself.surfaces]return[surf.surface_nodesforsurfinself.surfaces]
[docs]@classmethoddeffrom_csv(cls,fname:str):""" :param fname: path to a CSV file with header (lon, lat, dep) and 4 x P rows describing planes in terms of corner points in the order topleft, topright, bottomright, bottomleft :returns: a MultiSurface made of P planar surfaces """surfaces=[]tmp=np.genfromtxt(fname,delimiter=',',comments='#',skip_header=1)tmp=tmp.reshape(-1,4,3,order='A')foriinrange(tmp.shape[0]):arr=tmp[i,:,:]surfaces.append(PlanarSurface.from_ucerf(arr))returncls(surfaces)
# NB: called in event_based calculations@propertydefmesh(self):""" :returns: mesh corresponding to the whole multi surface """lons=[]lats=[]deps=[]formin[surface.meshforsurfaceinself.surfaces]:ok=np.isfinite(m.lons)&np.isfinite(m.lats)lons.append(m.lons[ok])lats.append(m.lats[ok])deps.append(m.depths[ok])returnMesh(np.concatenate(lons),np.concatenate(lats),np.concatenate(deps))def__init__(self,surfaces,msparam=None):""" Intialize a multi surface object from a list of surfaces :param surfaces: A list of instances of subclasses of :class:`openquake.hazardlib.geo.surface.BaseSurface` """self.surfaces=surfacesifmsparamisNone:# slow operation: happens only in hazardlib, NOT in the enginesecparams=build_secparams(self.surfaces)idxs=range(len(self.surfaces))self.msparam=build_msparams([idxs],secparams)[0]else:self.msparam=msparamself.tor=geo.MultiLine([s.torforsinself.surfaces])
[docs]defget_min_distance(self,mesh):""" For each point in ``mesh`` compute the minimum distance to each surface element and return the smallest value. See :meth:`superclass method <.base.BaseSurface.get_min_distance>` for spec of input and result values. """dists=[surf.get_min_distance(mesh)forsurfinself.surfaces]returnnp.min(dists,axis=0)
[docs]defget_joyner_boore_distance(self,mesh):""" For each point in mesh compute the Joyner-Boore distance to all the surface elements and return the smallest value. See :meth:`superclass method <.base.BaseSurface.get_joyner_boore_distance>` for spec of input and result values. """# For each point in mesh compute the Joyner-Boore distance to all the# surfaces and return the shortest one.dists=[surf.get_joyner_boore_distance(mesh)forsurfinself.surfaces]returnnp.min(dists,axis=0)
[docs]defget_top_edge_depth(self):""" Compute top edge depth of each surface element and return area-weighted average value (in km). """returnself.msparam['ztor']
[docs]defget_strike(self):""" Compute strike of each surface element and return area-weighted average value (in range ``[0, 360]``) using formula from: http://en.wikipedia.org/wiki/Mean_of_circular_quantities Note that the original formula has been adapted to compute a weighted rather than arithmetic mean. """returnself.msparam['strike']
[docs]defget_dip(self):""" Compute dip of each surface element and return area-weighted average value (in range ``(0, 90]``). Given that dip values are constrained in the range (0, 90], the simple formula for weighted mean is used. """returnself.msparam['dip']
[docs]defget_width(self):""" Compute width of each surface element, and return area-weighted average value (in km). """returnself.msparam['width']
[docs]defget_area(self):""" Return sum of surface elements areas (in squared km). """returnself.msparam['area']
[docs]defget_bounding_box(self):""" Compute bounding box for each surface element, and then return the bounding box of all surface elements' bounding boxes. :return: A tuple of four items. These items represent western, eastern, northern and southern borders of the bounding box respectively. Values are floats in decimal degrees. """returnself.msparam[['west','east','north','south']]
[docs]defget_middle_point(self):""" If :class:`MultiSurface` is defined by a single surface, simply returns surface's middle point, otherwise find surface element closest to the surface's bounding box centroid and return corresponding middle point. Note that the concept of middle point for a multi surface is ambiguous and alternative definitions may be possible. However, this method is mostly used to define the hypocenter location for ruptures described by a multi surface (see :meth:`openquake.hazardlib.source.characteristic.CharacteristicFaultSource.iter_ruptures`). This is needed because when creating fault based sources, the rupture's hypocenter locations are not explicitly defined, and therefore an automated way to define them is required. """iflen(self.surfaces)==1:returnself.surfaces[0].get_middle_point()west,east,north,south=self.get_bounding_box()midlon,midlat=utils.get_middle_point(west,north,east,south)m=Mesh(np.array([midlon]),np.array([midlat]))dists=[surf.get_min_distance(m)forsurfinself.surfaces]returnself.surfaces[np.argmin(dists)].get_middle_point()
[docs]defget_rx_distance(self,mesh):""" :param mesh: An instance of :class:`openquake.hazardlib.geo.mesh.Mesh` with the coordinates of the sites. :returns: A :class:`numpy.ndarray` instance with the Rx distance. Note that the Rx distance is directly taken from the GC2 t-coordinate. """tut,uut=self.tor.get_tu(mesh.lons,mesh.lats)rx=tut[0]iflen(tut[0].shape)>1elsetutreturnrx
[docs]defget_ry0_distance(self,mesh):""" :param mesh: An instance of :class:`openquake.hazardlib.geo.mesh.Mesh` with the coordinates of the sites. """u_max=self.tor.get_u_max()tut,uut=self.tor.get_tu(mesh.lons,mesh.lats)ry0=np.zeros_like(uut)ry0[uut<0]=np.abs(uut[uut<0])condition=uut>u_maxry0[condition]=uut[condition]-u_maxreturnry0