class MapMaker(object): """Create intensity raster map and PGV, PGA, and spectral contour maps. """ def __init__(self, container, topofile, layerdict, cities_file, logger): """Initialize MapMaker object. Args: container (ShakeMapOutputContainer): ShakeMapOutputContainer object containing model results. topofile (str): Path to file containing global topography grid. layerdict (dict): Dictionary containing fields: - coast: Global coastline shapefile. - ocean: Global ocean shapefile. - lake: Global lakes shapefile. - country: Global country boundaries shapefile. - state: Global state (or equivalent) boundaries shapefile. - roads: Global roads directory containing directories with regional shapefiles. cities_file (str): Path to geonames cities1000.txt file. logger (Logger): Python logging instance. Raises: KeyError: When any of layerdict keys are missing. """ req_keys = set(['coast', 'ocean', 'lake', 'country', 'state']) if len(set(layerdict.keys()).intersection(req_keys)) != len(req_keys): raise KeyError( 'layerdict input must have all keys from %s' % str(req_keys)) self.container = container self.topofile = topofile self.layerdict = layerdict cities = BasemapCities.loadFromGeoNames(cities_file) self.cities = cities self.city_cols = CITY_COLS self.city_rows = CITY_ROWS self.cities_per_grid = CITIES_PER_GRID self.intensity_colormap = ColorPalette.fromPreset('mmi') self.contour_colormap = ColorPalette.fromPreset('shaketopo') station_dict = container.getStationDict() self.stations = station_dict rupture_dict = container.getRuptureDict() info_dict = json.loads( container.getString('info.json'))['input']['event_information'] event_dict = { 'eventsourcecode': info_dict['event_id'], 'lat': float(info_dict['latitude']), 'lon': float(info_dict['longitude']), 'depth': float(info_dict['depth']), 'mag': float(info_dict['magnitude']) } origin = Origin(event_dict) if rupture_dict['features'][0]['geometry']['type'] == 'Point': rupture = PointRupture(origin) else: rupture = rupture_from_dict_and_origin(rupture_dict, origin) self.fault = rupture self.fig_width = FIG_WIDTH self.fig_height = FIG_HEIGHT self.logger = logger # clip all the vector data now so that map rendering will be fast t1 = time.time() self._clipBounds() t2 = time.time() self.logger.debug('%.1f seconds to clip vectors.' % (t2 - t1)) def _selectRoads(self, roads_folder, bbox): """Select road shapes from roads directory. Args: roads_folder (str): Path to folder containing global roads data. bbox (tuple): Tuple of map bounds (xmin,ymin,xmax,ymax). Returns: list: list of Shapely geometries. """ vshapes = [] xmin, ymin, xmax, ymax = bbox bboxpoly = sPolygon([(xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin), (xmin, ymax)]) for root, dirs, files in os.walk(roads_folder): for fname in files: if fname.endswith('.shp'): filename = os.path.join(root, fname) with fiona.open(filename, 'r') as f: shapes = f.items(bbox=bbox) for shapeidx, shape in shapes: tshape = sShape(shape['geometry']) intshape = tshape.intersection(bboxpoly) vshapes.append(intshape) return vshapes def _clipBounds(self): """ Clip input vector data to bounds of map. """ # returns a list of GeoJSON-like mapping objects comp = self.container.getComponents('MMI')[0] imtdict = self.container.getIMTGrids('MMI', comp) geodict = imtdict['mean'].getGeoDict() xmin, xmax, ymin, ymax = (geodict.xmin, geodict.xmax, geodict.ymin, geodict.ymax) bbox = (xmin, ymin, xmax, ymax) bboxpoly = sPolygon([(xmin, ymax), (xmax, ymax), (xmax, ymin), (xmin, ymin), (xmin, ymax)]) self.vectors = {} for key, value in self.layerdict.items(): vshapes = [] f = fiona.open(value, 'r') shapes = f.items(bbox=bbox) for shapeidx, shape in shapes: tshape = sShape(shape['geometry']) try: intshape = tshape.intersection(bboxpoly) # except TopologicalError as te: except Exception as te: self.logger.warn('Failure to grab %s segment: "%s"' % (key, str(te))) continue vshapes.append(intshape) self.logger.debug('Filename is %s' % value) f.close() self.vectors[key] = vshapes def setCityGrid(self, nx=2, ny=2, cities_per_grid=10): """Define grid fused to limit the number of cities plotted on the map. Args: nx (int): Number of columns in grid. ny (int): Number of rows in grid. cities_per_grid (int): Maximum number of cities to plot in each grid cell. """ self.city_cols = nx self.city_rows = ny self.cities_per_grid = cities_per_grid def setFigureSize(self, figwidth, figheight): """Set the figure size in inches. Args: figwidth (float): Figure width in inches. figheight (float): Figure height in inches. """ self.fig_width = figwidth self.fig_height = figheight def setCityList(self, dataframe): """Set the city list to an input dataframe. Args: dataframe (DataFrame): Pandas DataFrame whose columns include: - name: Name of the city (required). - lat: Latitude of city (required). - lon: Longitude of city (required). - pop: Population of city (optional). - iscap: Boolean indicating capital status (optional). - placement: String indicating where city label should be placed relative to city coordinates, one of: E, W, N, S, NE, SE, SW, NW. (optional). - xoff: Longitude offset for label relative to city coordinates (optional). - yoff: Latitude offset for label relative to city coordinates (optional). """ self.cities = BasemapCities(dataframe) # may raise exception self.city_rows = None self.city_cols = None self.cities_per_grid = None def _setMap(self, gd): """Define the map extents, figure size, etc. Args: gd (GeoDict): MapIO GeoDict defining bounds/resolution of input ShakeMap. Returns: Basemap: Basemap instance, Mercator projection. """ clon = gd.xmin + (gd.xmax - gd.xmin) / 2.0 clat = gd.ymin + (gd.ymax - gd.ymin) / 2.0 f = plt.figure(figsize=(self.fig_width, self.fig_height)) ax = f.add_axes([0.1, 0.1, 0.8, 0.8]) m = Basemap(llcrnrlon=gd.xmin, llcrnrlat=gd.ymin, urcrnrlon=gd.xmax, urcrnrlat=gd.ymax, rsphere=(6378137.00, 6356752.3142), resolution=BASEMAP_RESOLUTION, projection='merc', lat_0=clat, lon_0=clon, lat_ts=clat, ax=ax, suppress_ticks=True) return m def _projectGrid(self, data, m, gd): """Project 2D array to map projection. Args: data (ndarray): 2D Numpy array to be projected. m (Basemap): Basemap instance. gd (GeoDict): MapIO GeoDict object. Returns: ndarray: Input array projected to map projection. """ # set up meshgrid to project topo and mmi data xmin = gd.xmin if gd.xmax < gd.xmin: xmin -= 360 lons = np.linspace(xmin, gd.xmax, gd.nx) # backwards so it plots right side up lats = np.linspace(gd.ymax, gd.ymin, gd.ny) llons1, llats1 = np.meshgrid(lons, lats) pdata = m.transform_scalar(np.flipud(data), lons, lats[::-1], gd.nx, gd.ny, returnxy=False, checkbounds=False, order=1, masked=False) return pdata def _getDraped(self, data, topodata): """Get array of data "draped" on topography. Args: data (ndarray): 2D Numpy array. topodata (ndarray): 2D Numpy array. Returns: ndarray: Numpy array of data draped on topography. """ maxvalue = self.intensity_colormap.vmax mmisc = data / maxvalue rgba_img = self.intensity_colormap.cmap(mmisc) rgb = np.squeeze(rgba_img[:, :, 0:3]) # use lightsource class to make our shaded topography ls = LightSource(azdeg=135, altdeg=45) # intensity = ls.hillshade(ptopo,fraction=0.25,vert_exag=1.0) ls1 = LightSource(azdeg=120, altdeg=45) ls2 = LightSource(azdeg=225, altdeg=45) intensity1 = ls1.hillshade( topodata, fraction=0.25, vert_exag=VERT_EXAG) intensity2 = ls2.hillshade( topodata, fraction=0.25, vert_exag=VERT_EXAG) intensity = intensity1 * 0.5 + intensity2 * 0.5 draped_hsv = ls.blend_hsv(rgb, np.expand_dims(intensity, 2)) return draped_hsv def _drawBoundaries(self, m): """Draw all country/state boundaries on the map. Args: m (Basemap): Basemap instance. """ allshapes = self.vectors['country'] + self.vectors['state'] for shape in allshapes: # shape is a geojson-like mapping thing try: if hasattr(shape, 'exterior'): blon, blat = zip(*shape.exterior.coords[:]) else: blon, blat = zip(*shape.coords[:]) bx, by = m(blon, blat) m.plot(bx, by, 'k', zorder=BORDER_ZORDER) except NotImplementedError: for tshape in shape: try: blon, blat = zip(*tshape.exterior.coords[:]) bx, by = m(blon, blat) m.plot(bx, by, 'k', zorder=BORDER_ZORDER) except NotImplementedError: continue def _drawRoads(self, m): """Draw all roads on the map. Args: m (Basemap): Basemap instance. """ allshapes = self.vectors['roads'] xmin = 9999999 ymin = xmin xmax = -99999999 ymax = xmax for shape in allshapes: # shape is a shapely geometry if isinstance(shape, (MultiLineString, GeometryCollection)): blon = [] blat = [] for mshape in shape: tlon, tlat = zip(*mshape.coords[:]) blon += tlon blat += tlat else: blon, blat = zip(*shape.coords[:]) if not len(blon): continue if min(blon) < xmin: xmin = min(blon) if min(blat) < ymin: ymin = min(blat) if max(blon) > xmax: xmax = max(blon) if max(blat) > ymax: ymax = max(blat) bx, by = m(blon, blat) m.plot(bx, by, '#808080', zorder=ROAD_ZORDER) def _drawLakes(self, m, gd): """Draw all lakes on the map. Args: m (Basemap): Basemap instance. """ lakes = self.vectors['lake'] for lake in lakes: ppatches = getProjectedPatches(lake, m, edgecolor='k') for ppatch in ppatches: m.ax.add_patch(ppatch) def _drawOceans(self, m, gd): """Draw all oceans on the map. Args: m (Basemap): Basemap instance. """ if len(self.vectors['ocean']): ocean = self.vectors['ocean'][0] # this is one shapely polygon ppatches = getProjectedPatches(ocean, m) for ppatch in ppatches: m.ax.add_patch(ppatch) def _drawMapScale(self, m, gd): """Draw a map scale in the lower left corner of the map. Args: m (Basemap): Basemap instance. gd (GeoDict): MapIO GeoDict instance. """ # where to set the center of the scale bar scalex = gd.xmin + (gd.xmax - gd.xmin) / 5.0 scaley = gd.ymin + (gd.ymax - gd.ymin) / 10.0 # how tall should scale bar be, in map units (km)? yoff = (0.01 * (m.ymax - m.ymin)) # where should scale apply (center of map) clon = (gd.xmin + gd.xmax) / 2.0 clat = (gd.ymin + gd.ymax) / 2.0 # figure out a scalebar length that is approximately 30% of total # width in km map_width = (m.xmax - m.xmin) / 1000 # km lengths = np.array([25, 50, 75, 100, 125, 150, 175, 200, 250]) lfracs = lengths / map_width length = lengths[np.abs(lfracs - 0.3).argmin()] m.drawmapscale(scalex, scaley, clon, clat, length, barstyle='fancy', yoffset=yoff, zorder=SCALE_ZORDER) def _drawCoastlines(self, m, gd): """Draw all coastlines on the map. Args: m (Basemap): Basemap instance. gd (GeoDict): MapIO GeoDict instance. """ coasts = self.vectors['coast'] for coast in coasts: # these are polygons? if isinstance(coast, sPolygon): clon, clat = zip(*coast.exterior.coords[:]) cx, cy = m(clon, clat) m.plot(cx, cy, 'k', zorder=BORDER_ZORDER) elif isinstance(coast, LineString): clon, clat = zip(*coast.coords[:]) cx, cy = m(clon, clat) m.plot(cx, cy, 'k', zorder=BORDER_ZORDER) else: for tshape in coast: clon, clat = zip(*tshape.coords[:]) cx, cy = m(clon, clat) m.plot(cx, cy, 'k', zorder=BORDER_ZORDER) def _drawGraticules(self, m, gd): """Draw meridian/parallels on the map. Args: m (Basemap): Basemap instance. gd (GeoDict): MapIO GeoDict instance. """ par = np.arange(np.ceil(gd.ymin), np.floor(gd.ymax) + 1, 1.0) mer = np.arange(np.ceil(gd.xmin), np.floor(gd.xmax) + 1, 1.0) merdict = m.drawmeridians(mer, labels=[0, 0, 0, 1], fontsize=10, linewidth=0.5, color='gray', zorder=GRATICULE_ZORDER) pardict = m.drawparallels(par, labels=[1, 0, 0, 0], fontsize=10, linewidth=0.5, color='gray', zorder=GRATICULE_ZORDER) # loop over meridian and parallel dicts, change/increase font, draw # ticks xticks = [] for merkey, mervalue in merdict.items(): merline, merlablist = mervalue merlabel = merlablist[0] merlabel.set_family('sans-serif') merlabel.set_fontsize(12.0) xticks.append(merline[0].get_xdata()[0]) yticks = [] for parkey, parvalue in pardict.items(): parline, parlablist = parvalue parlabel = parlablist[0] parlabel.set_family('sans-serif') parlabel.set_fontsize(12.0) yticks.append(parline[0].get_ydata()[0]) # plt.tick_params(axis='both',color='k',direction='in') plt.xticks(xticks, ()) plt.yticks(yticks, ()) m.ax.tick_params(direction='out') def _drawTitle(self, imt): """Draw the map title. Args: imt (str): IMT that is being drawn on the map ('MMI', 'PGV', 'PGA', 'SA(x.y)'). isContour (bool): If true, use input imt, otherwise use MMI. """ # Add a title origin = self.fault.getOrigin() hlon = origin.lon hlat = origin.lat edict = json.loads(self.container.getString( 'info.json'))['input']['event_information'] eloc = edict['event_description'] etime = datetime.strptime( edict['origin_time'], '%Y-%m-%d %H:%M:%S') timestr = etime.strftime('%b %d, %Y %H:%M:%S') mag = origin.mag if hlon < 0: lonstr = 'W%.2f' % np.abs(hlon) else: lonstr = 'E%.2f' % hlon if hlat < 0: latstr = 'S%.2f' % np.abs(hlat) else: latstr = 'N%.2f' % hlat dep = origin.depth eid = edict['event_id'] tpl = (timestr, mag, latstr, lonstr, dep, eid) fmt = ('USGS ShakeMap (%s): %s\n %s UTC M%.1f %s %s ' 'Depth: %.1fkm ID:%s') tstr = fmt % (imt, eloc, timestr, mag, latstr, lonstr, dep, eid) # plt.suptitle('USGS ShakeMap (%s): %s' % (layername, eloc), # fontsize=14, verticalalignment='bottom', y=0.95) # plt.title('%s UTC M%.1f %s %s Depth: %.1fkm ID:%s' % # tpl, fontsize=10, verticalalignment='bottom') # plt.rc('text', usetex=True) # matplotlib.rcParams['text.latex.preamble']=[r"\usepackage{amsmath}"] plt.title(tstr, fontsize=10, verticalalignment='bottom') # plt.title(r"\TeX\ is Number " # r"$\displaystyle\sum_{n=1}^\infty\frac{-e^{i\pi}}{2^n}$!", # fontsize=16, color='gray') def _drawStations(self, m, fill=False, imt='PGA'): """Draw station locations on the map. Args: m (Basemap): Basemap instance. fill (bool): Whether or not to fill symbols. imt (str): One of ('MMI', 'PGA', 'PGV', or 'SA(x.y') """ dimt = imt.lower() # get the locations and values of the MMI observations mmi_dict = {'lat': [], 'lon': [], 'mmi': []} inst_dict = {'lat': [], 'lon': [], dimt: []} # get the locations and values of the observed/instrumented # observations for feature in self.stations['features']: lon, lat = feature['geometry']['coordinates'] net = feature['properties']['network'].lower() if net in ['dyfi', 'mmi', 'intensity', 'ciim']: channel = feature['properties']['channels'][0] for amplitude in channel['amplitudes']: if amplitude['name'] != 'mmi': continue mmi_dict['mmi'].append(float(amplitude['value'])) mmi_dict['lat'].append(lat) mmi_dict['lon'].append(lon) else: channel = feature['properties']['channels'][0] for amplitude in channel['amplitudes']: if amplitude['name'] != dimt: continue inst_dict[dimt].append(float(amplitude['value'])) inst_dict['lat'].append(lat) inst_dict['lon'].append(lon) mmidf = pd.DataFrame(mmi_dict) instdf = pd.DataFrame(inst_dict) if not fill: # plot MMI as small circles mmilat = mmidf['lat'].as_matrix() mmilon = mmidf['lon'].as_matrix() m.plot(mmilon, mmilat, 'ko', latlon=True, fillstyle='none', markersize=4, zorder=STATIONS_ZORDER) # plot MMI as slightly larger triangles instlat = instdf['lat'].as_matrix() instlon = instdf['lon'].as_matrix() m.plot(instlon, instlat, 'k^', latlon=True, fillstyle='none', markersize=6, zorder=STATIONS_ZORDER) else: for idx, value in enumerate(mmidf['lat']): mlat = mmidf['lat'][idx] mlon = mmidf['lon'][idx] mmi = mmidf['mmi'][idx] mcolor = self.intensity_colormap.getDataColor(mmi) m.plot(mlon, mlat, 'o', latlon=True, markerfacecolor=mcolor, markeredgecolor='k', markersize=4, zorder=STATIONS_ZORDER) for idx, value in enumerate(instdf['lat']): mlat = instdf['lat'][idx] mlon = instdf['lon'][idx] # # TODO: Make the fill color correspond to the mmi # obtained from the IMT. # # dmmi = instdf[dimt][idx] # mcolor = self.intensity_colormap.getDataColor(dmmi) m.plot(mlon, mlat, '^', latlon=True, markerfacecolor='w', markeredgecolor='k', markersize=6, zorder=STATIONS_ZORDER) def _drawFault(self, m): """Draw fault rupture on the map. Args: m (Basemap): Basemap instance. """ lats = self.fault.lats lons = self.fault.lons x, y = m(lons, lats) m.plot(x, y, 'k', lw=2, zorder=FAULT_ZORDER) def drawIntensityMap(self, outfolder): """ Render the MMI data as intensity draped over topography, with oceans, coastlines, etc. Args: outfolder (str): Path to directory where output map should be saved. Returns: str: Path to output intensity map. """ t0 = time.time() # resample shakemap to topogrid # get the geodict for the topo file topodict = GMTGrid.getFileGeoDict(self.topofile)[0] # get the geodict for the ShakeMap comp = self.container.getComponents('MMI')[0] imtdict = self.container.getIMTGrids('MMI', comp) mmigrid = imtdict['mean'] smdict = mmigrid.getGeoDict() # get a geodict that is aligned with topo, but inside shakemap sampledict = topodict.getBoundsWithin(smdict) mmigrid = mmigrid.interpolateToGrid(sampledict) gd = mmigrid.getGeoDict() # establish the basemap object m = self._setMap(gd) # get topo layer and project it topogrid = GMTGrid.load( self.topofile, samplegeodict=sampledict, resample=False) topodata = topogrid.getData().copy() ptopo = self._projectGrid(topodata, m, gd) # get intensity layer and project it imtdata = mmigrid.getData().copy() pimt = self._projectGrid(imtdata, m, gd) # get the draped intensity data draped_hsv = self._getDraped(pimt, ptopo) # where will 10.0 come from # draw the draped intensity data m.imshow(draped_hsv, interpolation='none', zorder=IMG_ZORDER) # draw country/state boundaries self._drawBoundaries(m) # draw whatever road data is available # self.logger.debug('Drawing roads...') # self._drawRoads(m) # self.logger.debug('Done drawing roads...') # draw lakes self._drawLakes(m, gd) # draw oceans (pre-processed with islands taken out) t1 = time.time() self._drawOceans(m, gd) t2 = time.time() self.logger.debug('%.1f seconds to render oceans.' % (t2 - t1)) # draw coastlines self._drawCoastlines(m, gd) # draw meridians, parallels, labels, ticks self._drawGraticules(m, gd) # draw map scale self._drawMapScale(m, gd) # draw fault polygon, if present self._drawFault(m) # get the fault loaded # draw epicenter origin = self.fault.getOrigin() hlon = origin.lon hlat = origin.lat m.plot(hlon, hlat, 'k*', latlon=True, fillstyle='none', markersize=22, mew=1.2, zorder=EPICENTER_ZORDER) # draw cities # reduce the number of cities to those whose labels don't collide # set up cities if self.city_cols is not None: self.cities = self.cities.limitByBounds( (gd.xmin, gd.xmax, gd.ymin, gd.ymax)) self.cities = self.cities.limitByGrid( nx=self.city_cols, ny=self.city_rows, cities_per_grid=self.cities_per_grid) # self.logger.debug("Available fonts: ", self.cities._fontlist) if 'Times New Roman' in self.cities._fontlist: font = 'Times New Roman' else: font = 'DejaVu Sans' self.cities = self.cities.limitByMapCollision(m, fontname=font) self.cities.renderToMap(m.ax, zorder=CITIES_ZORDER) # draw title and supertitle self._drawTitle('MMI') # draw station and macroseismic locations self._drawStations(m) # need stationlist object # save plot to file plt.draw() outfile = os.path.join(outfolder, 'intensity.pdf') plt.savefig(outfile) tn = time.time() self.logger.debug('%.1f seconds to render entire map.' % (tn - t0)) return outfile def _getShaded(self, ptopo): """Get shaded topography. Args: ptopo (ndarray): Numpy array of projected topography data. Returns: ndarray: Numpy array of light-shaded topography. """ maxvalue = self.contour_colormap.vmax ls1 = LightSource(azdeg=120, altdeg=45) ls2 = LightSource(azdeg=225, altdeg=45) intensity1 = ls1.hillshade(ptopo, fraction=0.25, vert_exag=VERT_EXAG) intensity2 = ls2.hillshade(ptopo, fraction=0.25, vert_exag=VERT_EXAG) intensity = intensity1 * 0.5 + intensity2 * 0.5 ptoposc = ptopo / maxvalue rgba = self.contour_colormap.cmap(ptoposc) rgb = np.squeeze(rgba) draped_hsv = ls1.blend_hsv(rgb, np.expand_dims(intensity, 2)) return draped_hsv def round_to(self, n, precision): """Round number to nearest level desired precision. Example: round_to(22.1,10) => 20. Args: n (float): Input number to round. precision (int): Desired precision. Returns: int: Rounded value. """ correction = 0.5 if n >= 0 else -0.5 return int(n / precision + correction) * precision def getContourLevels(self, dmin, dmax, imt): """Get contour levels given min/max values and desired IMT. Args: dmin (float): Minimum value of data to contour. dmax (float): Maximum value of data to contour. imt (str): String IMT (one of PGV,PGA, etc.) Returns: ndarray: Numpy array of contour levels. """ # groupings taken from table on # https://en.wikipedia.org/wiki/Peak_ground_acceleration if imt == 'PGV': # table of minimum dmax and dinc levels dmax_dinc = OrderedDict([(1.1, 0.1), (3.4, 0.25), (8.1, 0.5), (16.0, 2.0), (31.0, 5.0), (60.0, 10.0), (116.0, 10.0), (200.0, 25.0)]) keys = np.array(list(dmax_dinc.keys())) didx = np.where(keys < dmax)[0].max() dinc = dmax_dinc[keys[didx]] newdmin = self.round_to(dmin, dinc) newdmax = self.round_to(dmax, dinc) else: dmax_dinc = OrderedDict([(0.0017 * 100, 0.1), (0.014 * 100, 0.1), (0.039 * 100, 0.5), (0.092 * 100, 1.0), (0.18 * 100, 2.5), (0.34 * 100, 5.0), (0.65 * 100, 10.0), (1.24 * 100, 15.0), (3.0 * 100, 37.5)]) keys = np.array(list(dmax_dinc.keys())) didx = np.where(keys < dmax)[0].max() dinc = dmax_dinc[keys[didx]] newdmin = self.round_to(dmin, dinc) newdmax = self.round_to(dmax, dinc) levels = np.arange(newdmin, newdmax + dinc, dinc) return levels def drawContourMap(self, imt, outfolder, cmin=None, cmax=None): """ Render IMT data as contours over topography, with oceans, coastlines, etc. Args: outfolder (str): Path to directory where output map should be saved. Returns: str: Path to output IMT map. """ if self.contour_colormap is None: raise Exception('MapMaker.setGMTColormap() has not been called.') t0 = time.time() # resample shakemap to topogrid # get the geodict for the topo file topodict = GMTGrid.getFileGeoDict(self.topofile)[0] # get the geodict for the ShakeMap comp = self.container.getComponents(imt)[0] imtdict = self.container.getIMTGrids(imt, comp) imtgrid = imtdict['mean'] smdict = imtgrid.getGeoDict() # get a geodict that is aligned with topo, but inside shakemap sampledict = topodict.getBoundsWithin(smdict) imtgrid = imtgrid.interpolateToGrid(sampledict) gd = imtgrid.getGeoDict() # establish the basemap object m = self._setMap(gd) # get topo layer and project it topogrid = GMTGrid.load( self.topofile, samplegeodict=sampledict, resample=False) topodata = topogrid.getData().copy() ptopo = self._projectGrid(topodata, m, gd) # get contour layer and project it1 imtdata = imtgrid.getData().copy() # convert units if necessary if imt == 'MMI': pass elif imt == 'PGV': imtdata = np.exp(imtdata) else: imtdata = np.exp(imtdata) * 100 pimt = self._projectGrid(imtdata, m, gd) # get the draped intensity data hillshade = self._getShaded(ptopo) # draw the draped intensity data m.imshow(hillshade, interpolation='none', zorder=IMG_ZORDER) # draw the contours of imt data xmin = gd.xmin if gd.xmax < gd.xmin: xmin -= 360 lons = np.linspace(xmin, gd.xmax, gd.nx) # backwards so it plots right side up lats = np.linspace(gd.ymax, gd.ymin, gd.ny) x, y = m(*np.meshgrid(lons, lats)) pimt = gaussian_filter(pimt, 5.0) dmin = pimt.min() dmax = pimt.max() levels = self.getContourLevels(dmin, dmax, imt) cs = m.contour(x, y, np.flipud(pimt), colors='w', cmap=None, levels=levels, zorder=CONTOUR_ZORDER) clabels = plt.clabel(cs, colors='k', fmt='%.1f', fontsize=8.0, zorder=CONTOUR_ZORDER) for cl in clabels: bbox = dict(boxstyle="round", facecolor='white', edgecolor='w') cl.set_bbox(bbox) cl.set_zorder(CONTOUR_ZORDER) # draw country/state boundaries self._drawBoundaries(m) # draw lakes self._drawLakes(m, gd) # draw oceans (pre-processed with islands taken out) t1 = time.time() self._drawOceans(m, gd) t2 = time.time() self.logger.debug('%.1f seconds to render oceans.' % (t2 - t1)) # draw coastlines self._drawCoastlines(m, gd) # draw meridians, parallels, labels, ticks self._drawGraticules(m, gd) # draw filled symbols for MMI and instrumented measures self._drawStations(m, fill=True, imt=imt) # draw map scale self._drawMapScale(m, gd) # draw fault polygon, if present self._drawFault(m) # get the fault loaded # draw epicenter origin = self.fault.getOrigin() hlon = origin.lon hlat = origin.lat m.plot(hlon, hlat, 'k*', latlon=True, fillstyle='none', markersize=22, mew=1.2, zorder=EPICENTER_ZORDER) # draw cities # reduce the number of cities to those whose labels don't collide # set up cities if self.city_cols is not None: self.cities = self.cities.limitByBounds( (gd.xmin, gd.xmax, gd.ymin, gd.ymax)) self.cities = self.cities.limitByGrid( nx=self.city_cols, ny=self.city_rows, cities_per_grid=self.cities_per_grid) if 'Times New Roman' in self.cities._fontlist: font = 'Times New Roman' else: font = 'DejaVu Sans' self.cities = self.cities.limitByMapCollision(m, fontname=font) self.cities.renderToMap(m.ax, zorder=CITIES_ZORDER) # draw title and supertitle self._drawTitle(imt) # save plot to file fileimt = oq_to_file(imt) plt.draw() outfile = os.path.join(outfolder, 'contour_%s.pdf' % (fileimt)) plt.savefig(outfile) tn = time.time() self.logger.debug('%.1f seconds to render entire map.' % (tn - t0)) return outfile