def createEconTable(self): table_lines = [ "\\begin{tabularx}{\\barwidth}{lc*{1}{>{\\raggedleft\\arraybackslash}X}}" ] table_lines.append("\\hline") table_lines.append( "\\textbf{County} & \\textbf{State} & \\textbf{Total (\\textdollar M)} \\\\" ) table_lines.append("\\hline") ntotal = len(self._dataframe) for i in range(0, self._ncounties): row = self._dataframe.iloc[i] fips = int(row["CountyFips"]) county_name, state_abbrev = self._county_dict[fips] # econ losses are in thousands of dollars lossvalue = round_to_nearest(row["EconLoss"] * 1e3, 1000000) loss_str = "{:,}".format(int(lossvalue / 1e6)) line = "\\truncate{4cm}{%s} & %s & %s \\\\" % ( county_name, state_abbrev, loss_str, ) table_lines.append(line) fmt = "\\multicolumn{2}{l}{\\textbf{Total (%i counties)}} & \\multicolumn{1}{>{\\raggedleft}X}{\\textbf{%s}} \\\\" total_dollars = self._dataframe["EconLoss"].sum() * 1e3 total_rounded = round_to_nearest(total_dollars, 1000000) total_str = "{:,}".format(int(total_rounded / 1e6)) line = fmt % (ntotal, total_str) table_lines.append(line) table_lines.append("\\hline") table_lines.append("\\end{tabularx}") table_text = "\n".join(table_lines) return table_text
def test(): print('Testing decimal to roman number conversion...') assert text.dec_to_roman(10) == 'X' print('Passed decimal to roman number conversion...') print('Testing setting number precision...') assert text.set_num_precision(7642, 2) == 7600 assert text.set_num_precision(321, 2) == 320 print('Passed setting number precision...') print('Testing rounding population value...') assert text.pop_round(7642) == '8,000' print('Passed rounding population value...') print('Testing rounding dollar value...') assert text.dollar_round(1.234e9, digits=2, mode='short') == '$1.2B' assert text.dollar_round(1.234e9, digits=2, mode='long') == '$1.2 billion' print('Passed rounding population value...') print('Testing abbreviating population value...') assert text.pop_round_short(1024125) == '1,024k' print('Passed abbreviating population value...') print('Testing rounding to nearest integer value...') assert text.round_to_nearest(998, round_value=1000) == 1000 assert text.round_to_nearest(78, round_value=100) == 100 print('Passed rounding population value...') print('Testing flooring to nearest integer value...') assert text.floor_to_nearest(1501, floor_value=1000) == 1000 assert text.floor_to_nearest(51, floor_value=100) == 0 print('Passed flooring population value...') print('Testing ceiling to nearest integer value...') assert text.ceil_to_nearest(1001, ceil_value=1000) == 2000 assert text.ceil_to_nearest(49, ceil_value=100) == 100 print('Passed ceiling population value...') print('Testing commify...') assert text.commify(1234567) == '1,234,567' print('Passed commify...') assert text.floor_to_nearest(0.56, floor_value=0.1) == 0.5 assert text.ceil_to_nearest(0.44, ceil_value=0.1) == 0.5 assert text.round_to_nearest(0.48, round_value=0.1) == 0.5 assert text.pop_round_short(125) == '125' assert text.pop_round_short(1.23e6, usemillion=True) == '1m' assert text.pop_round_short(0) == '0' assert text.dollar_round(1.23, digits=2, mode='short') == '$1' assert text.dollar_round(1.23e3, digits=2, mode='short') == '$1.2K' assert text.dollar_round(1.23e3, digits=2, mode='long') == '$1.2 thousand' assert text.dollar_round(1.23e6, digits=2, mode='short') == '$1.2M' assert text.dollar_round(1.23e6, digits=2, mode='long') == '$1.2 million' assert text.dec_to_roman(10) == 'X'
def format_exposure(exposures, format, max_border_mmi): expstr_hold = 'Estimated Population Exposure\n' if format == 'short': # get the three highest exposures with 1,000 people or greater # format them as: # I6=19,000 # I5=1,034,000 expstr = 'No population exposure' if len(exposures): expstr = '' expohold = 0 for mmi in range(10, 0, -1): pop = 0 expo = exposures[mmi - 1] if mmi == 10: expohold = expo elif mmi == 9: pop = expo + expohold else: pop = expo pop = round_to_nearest(pop, round_value=1000) if pop >= MIN_POP: popstr = pop_round(pop) expstr += 'I%i=%s\n' % (mmi, popstr) else: # get the all highest exposures with 1,000 people or greater # format them as: # MMI6 19,000 # MMI5 1,034,000 expstr = expstr_hold + '\tIntensity Population\n' if len(exposures): expohold = 0 for mmi in range(10, 0, -1): pop = 0 expo = exposures[mmi - 1] if mmi == 10: expohold = expo elif mmi == 9: pop = expo + expohold else: pop = expo pop = round_to_nearest(pop, round_value=1000) if pop >= MIN_POP: popstr = pop_round(pop) flag = '' if mmi < max_border_mmi: flag = '*' expstr += 'MMI%i\t%-8s%s\n' % (mmi, popstr, flag) if expstr == expstr_hold: expstr = 'No population exposure.' else: if format == 'long': expstr += '\n* - MMI level extends beyond map boundary, actual population exposure may be larger.\n' return expstr
def get_quake_desc(event,lat,lon,isMainEvent): ndeaths = event['TotalDeaths'] #summarize the exposure values exposures = np.array([event['MMI1'],event['MMI2'],event['MMI3'],event['MMI4'],event['MMI5'], event['MMI6'],event['MMI7'],event['MMI8'],event['MMI9+']]) exposures = np.array([round_to_nearest(exp,1000) for exp in exposures]) #get the highest two exposures greater than zero iexp = np.where(exposures > 0)[0][::-1] romans = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX or greater'] if len(iexp) >= 2: exposures = [exposures[iexp[1]],exposures[iexp[0]]] ilevels = [romans[iexp[1]],romans[iexp[0]]] expfmt = ', with estimated population exposures of %s at intensity' expfmt = expfmt + ' %s and %s at intensity %s' exptxt = expfmt % (commify(int(exposures[0])),ilevels[0],commify(int(exposures[1])),ilevels[1]) else: exptxt = '' #create string describing this most impactful event dfmt = 'A magnitude %.1f earthquake %i km %s of this event struck %s on %s (UTC)%s' mag = event['Magnitude'] etime = event['Time'].strftime('%B %d, %Y') etime = re.sub(' 0',' ',etime) country = Country() if not event['Name']: if event['CountryCode'] == 'UM' and event['Latitude'] > 40: #hack for persistent error in expocat cdict = country.getCountryCode('US') else: cdict = country.getCountryCode(event['CountryCode']) if cdict: cname = cdict['Name'] else: cname = 'in the open ocean' else: cname = event['Name'].replace('"','') cdist = round(geodetic_distance(event['Lat'],event['Lon'],lat,lon)) cdir = get_compass_dir(lat,lon,event['Lat'],event['Lon'],format='long').lower() if ndeaths and str(ndeaths) != "nan": dfmt = dfmt + ', resulting in a reported %s %s.' if ndeaths > 1: dstr = 'fatalities' else: dstr = 'fatality' ndeathstr = commify(int(ndeaths)) eqdesc = dfmt % (mag,cdist,cdir,cname,etime,exptxt,ndeathstr,dstr) else: dfmt = dfmt + ', with no reported fatalities.' eqdesc = dfmt % (mag,cdist,cdir,cname,etime,exptxt) return eqdesc
def create_onepager(pdata, version_dir, debug=False): """ :param pdata: PagerData object. :param version_dir: Path of event version directory. :param debug: bool for whether or not to add textpos boxes to onepager. """ #--------------------------------------------------------------------------- # Sort out some paths #--------------------------------------------------------------------------- # Locaiton of this module mod_dir, dummy = os.path.split(__file__) # losspager package direcotry losspager_dir = os.path.join(mod_dir, '..') # Repository root directory root_dir = os.path.join(losspager_dir, '..') # Data directory data_dir = os.path.join(losspager_dir, 'data') # Onepager latex template file template_file = os.path.join(data_dir, 'onepager2.tex') #--------------------------------------------------------------------------- # Read in pager data and latex template #--------------------------------------------------------------------------- json_dir = os.path.join(version_dir, 'json') pdict = pdata._pagerdict edict = pdata.getEventInfo() with open(template_file, 'r') as f: template = f.read() #--------------------------------------------------------------------------- # Fill in template values #--------------------------------------------------------------------------- # Sort out origin time olat = edict['lat'] olon = edict['lon'] otime_utc = edict['time'] date_utc = datetime.strptime(otime_utc, "%Y-%m-%d %H:%M:%S") date_local = pdata.local_time DoW = date_local.strftime('%a') otime_local = date_local.strftime('%H:%M:%S') otime_local = DoW + ' ' + otime_local template = template.replace("[ORIGTIME]", otime_utc) template = template.replace("[LOCALTIME]", otime_local) # Some paths template = template.replace("[VERSIONFOLDER]", version_dir) template = template.replace("[HOMEDIR]", root_dir) # Magnitude location string under USGS logo magloc = 'M %.1f, %s' % (edict['mag'], texify(edict['location'])) template = template.replace("[MAGLOC]", magloc) # Pager version ver = "Version " + str(pdict['pager']['version_number']) template = template.replace("[VERSION]", ver) template = template.replace("[VERSIONX]", "2.5") # Epicenter location lat = edict['lat'] lon = edict['lon'] dep = edict['depth'] if lat > 0: hlat = "N" else: hlat = "S" if lon > 0: hlon = "E" else: hlon = "W" template = template.replace("[LAT]", '%.4f' % abs(lat)) template = template.replace("[LON]", '%.4f' % abs(lon)) template = template.replace("[HEMILAT]", hlat) template = template.replace("[HEMILON]", hlon) template = template.replace("[DEPTH]", '%.1f' % dep) # Tsunami warning? --- need to fix to be a function of tsunamic flag if edict['tsunami']: template = template.replace( "[TSUNAMI]", "FOR TSUNAMI INFORMATION, SEE: tsunami.gov") else: template = template.replace("[TSUNAMI]", "") if pdata.isScenario(): elapse = '' else: elapse = "Created: " + pdict['pager'][ 'elapsed_time'] + " after earthquake" template = template.replace("[ELAPSED]", elapse) template = template.replace("[IMPACT1]", texify(pdict['comments']['impact1'])) template = template.replace("[IMPACT2]", texify(pdict['comments']['impact2'])) template = template.replace("[STRUCTCOMMENT]", texify(pdict['comments']['struct_comment'])) # Summary alert color template = template.replace("[SUMMARYCOLOR]", pdata.summary_alert.capitalize()) template = template.replace("[ALERTFILL]", pdata.summary_alert) # fill in exposure values max_border_mmi = pdata._pagerdict['population_exposure'][ 'maximum_border_mmi'] explist = pdata.getTotalExposure() pophold = 0 for mmi in range(1, 11): iexp = mmi - 1 if mmi == 2: pophold += explist[iexp] continue elif mmi == 3: pop = explist[iexp] + pophold macro = '[MMI2-3]' else: pop = explist[iexp] macro = '[MMI%i]' % mmi if pop < 1000: pop = round_to_nearest(pop, round_value=1000) if max_border_mmi > mmi and mmi <= 4: if pop == 0: popstr = '--*' else: if pop < 1000: pop = round_to_nearest(pop, round_value=1000) popstr = pop_round_short(pop) + '*' else: popstr = pop_round_short(pop) template = template.replace(macro, popstr) # MMI color pal pal = ColorPalette.fromPreset('mmi') # Historical table htab = pdata.getHistoricalTable() if htab[0] is None: # use pdata.getHistoricalComment() htex = pdata.getHistoricalComment() else: # build latex table htex = """ \\begin{tabularx}{7.25cm}{lrc*{1}{>{\\centering\\arraybackslash}X}*{1}{>{\\raggedleft\\arraybackslash}X}} \hline \\textbf{Date} &\\textbf{Dist.}&\\textbf{Mag.}&\\textbf{Max} &\\textbf{Shaking}\\\\ \\textbf{(UTC)}&\\textbf{(km)} & &\\textbf{MMI(\#)}&\\textbf{Deaths} \\\\ \hline [TABLEDATA] \hline \multicolumn{5}{p{7.2cm}}{\\small [COMMENT]} \end{tabularx}""" comment = pdata._pagerdict['comments']['secondary_comment'] htex = htex.replace("[COMMENT]", texify(comment)) tabledata = "" nrows = len(htab) for i in range(nrows): date = htab[i]['Time'].split()[0] dist = str(int(htab[i]['Distance'])) mag = str(htab[i]['Magnitude']) mmi = dec_to_roman(np.round(htab[i]['MaxMMI'], 0)) col = pal.getDataColor(htab[i]['MaxMMI']) texcol = "%s,%s,%s" % (col[0], col[1], col[2]) nmmi = pop_round_short(htab[i]['NumMaxMMI']) mmicell = '%s(%s)' % (mmi, nmmi) shakedeath = htab[i]['ShakingDeaths'] if np.isnan(shakedeath): death = "--" else: death = pop_round_short(shakedeath) row = '%s & %s & %s & \cellcolor[rgb]{%s} %s & %s \\\\ '\ '\n' %(date, dist, mag, texcol, mmicell, death) tabledata = tabledata + row htex = htex.replace("[TABLEDATA]", tabledata) template = template.replace("[HISTORICAL_BLOCK]", htex) # City table ctex = """ \\begin{tabularx}{7.25cm}{lXr} \hline \\textbf{MMI} & \\textbf{City} & \\textbf{Population} \\\\ \hline [TABLEDATA] \hline \end{tabularx}""" ctab = pdata.getCityTable() nrows = len(ctab.index) tabledata = "" for i in range(nrows): mmi = dec_to_roman(np.round(ctab['mmi'].iloc[i], 0)) city = ctab['name'].iloc[i] if ctab['pop'].iloc[i] == 0: pop = '$<$1k' else: if ctab['pop'].iloc[i] < 1000: popnum = round_to_nearest(ctab['pop'].iloc[i], round_value=1000) else: popnum = ctab['pop'].iloc[i] pop = pop_round_short(popnum) col = pal.getDataColor(ctab['mmi'].iloc[i]) texcol = "%s,%s,%s" % (col[0], col[1], col[2]) if ctab['on_map'].iloc[i] == 1: if ctab['pop'].iloc[i] == 0: pop = '\\boldmath$<$\\textbf{1k}' row = '\\rowcolor[rgb]{%s}\\textbf{%s} & \\textbf{%s} & '\ '%s\\\\ \n' %(texcol, mmi, city, pop) else: row = '\\rowcolor[rgb]{%s}\\textbf{%s} & \\textbf{%s} & '\ '\\textbf{%s}\\\\ \n' %(texcol, mmi, city, pop) else: row = '\\rowcolor[rgb]{%s}%s & %s & '\ '%s\\\\ \n' %(texcol, mmi, city, pop) tabledata = tabledata + row ctex = ctex.replace("[TABLEDATA]", tabledata) template = template.replace("[CITYTABLE]", ctex) eventid = edict['eventid'] # query ComCat for information about this event # fill in the url, if we can find it try: ccinfo = ComCatInfo(eventid) eventid, allids = ccinfo.getAssociatedIds() event_url = ccinfo.getURL() + '#pager' except: event_url = DEFAULT_PAGER_URL eventid = "Event ID: " + eventid template = template.replace("[EVENTID]", texify(eventid)) template = template.replace("[EVENTURL]", texify(event_url)) # Write latex file tex_output = os.path.join(version_dir, 'onepager.tex') with open(tex_output, 'w') as f: f.write(template) pdf_output = os.path.join(version_dir, 'onepager.pdf') stderr = '' try: cwd = os.getcwd() os.chdir(version_dir) cmd = '%s -interaction nonstopmode --output-directory %s %s' % ( LATEX_TO_PDF_BIN, version_dir, tex_output) print('Running %s...' % cmd) res, stdout, stderr = get_command_output(cmd) os.chdir(cwd) if not res: return (None, stderr) else: if os.path.isfile(pdf_output): return (pdf_output, stderr) else: pass except Exception as e: pass finally: os.chdir(cwd) return (None, stderr)
def draw_contour(shakegrid, popgrid, oceanfile, oceangridfile, cityfile, basename, borderfile=None, is_scenario=False): """Create a contour map showing MMI contours over greyscale population. :param shakegrid: ShakeGrid object. :param popgrid: Grid2D object containing population data. :param oceanfile: String path to file containing ocean vector data in a format compatible with fiona. :param oceangridfile: String path to file containing ocean grid data . :param cityfile: String path to file containing GeoNames cities data. :param basename: String path containing desired output PDF base name, i.e., /home/pager/exposure. ".pdf" and ".png" files will be made. :param make_png: Boolean indicating whether a PNG version of the file should also be created in the same output folder as the PDF. :returns: Tuple containing: - Name of PNG file created, or None if PNG output not specified. - Cities object containing the cities that were rendered on the contour map. """ gd = shakegrid.getGeoDict() # Retrieve the epicenter - this will get used on the map center_lat = shakegrid.getEventDict()['lat'] center_lon = shakegrid.getEventDict()['lon'] # load the ocean grid file (has 1s in ocean, 0s over land) # having this file saves us almost 30 seconds! oceangrid = read(oceangridfile, samplegeodict=gd, resample=True, doPadding=True) # load the cities data, limit to cities within shakemap bounds allcities = Cities.fromDefault() cities = allcities.limitByBounds((gd.xmin, gd.xmax, gd.ymin, gd.ymax)) # define the map # first cope with stupid 180 meridian height = (gd.ymax - gd.ymin) * DEG2KM if gd.xmin < gd.xmax: width = (gd.xmax - gd.xmin) * np.cos(np.radians(center_lat)) * DEG2KM xmin, xmax, ymin, ymax = (gd.xmin, gd.xmax, gd.ymin, gd.ymax) else: xmin, xmax, ymin, ymax = (gd.xmin, gd.xmax, gd.ymin, gd.ymax) xmax += 360 width = ((gd.xmax + 360) - gd.xmin) * \ np.cos(np.radians(center_lat)) * DEG2KM aspect = width / height # if the aspect is not 1, then trim bounds in x or y direction # as appropriate if width > height: dw = (width - height) / 2.0 # this is width in km xmin = xmin + dw / (np.cos(np.radians(center_lat)) * DEG2KM) xmax = xmax - dw / (np.cos(np.radians(center_lat)) * DEG2KM) width = (xmax - xmin) * np.cos(np.radians(center_lat)) * DEG2KM if height > width: dh = (height - width) / 2.0 # this is width in km ymin = ymin + dh / DEG2KM ymax = ymax - dh / DEG2KM height = (ymax - ymin) * DEG2KM aspect = width / height figheight = FIGWIDTH / aspect bbox = (xmin, ymin, xmax, ymax) bounds = (xmin, xmax, ymin, ymax) figsize = (FIGWIDTH, figheight) # Create the MercatorMap object, which holds a separate but identical # axes object used to determine collisions between city labels. mmap = MercatorMap(bounds, figsize, cities, padding=0.5) fig = mmap.figure ax = mmap.axes # this needs to be done here so that city label collision # detection will work fig.canvas.draw() geoproj = mmap.geoproj proj = mmap.proj # project our population grid to the map projection projstr = proj.proj4_init popgrid_proj = popgrid.project(projstr) popdata = popgrid_proj.getData() newgd = popgrid_proj.getGeoDict() # Use our GMT-inspired palette class to create population and MMI colormaps popmap = ColorPalette.fromPreset('pop') mmimap = ColorPalette.fromPreset('mmi') # set the image extent to that of the data img_extent = (newgd.xmin, newgd.xmax, newgd.ymin, newgd.ymax) plt.imshow(popdata, origin='upper', extent=img_extent, cmap=popmap.cmap, vmin=popmap.vmin, vmax=popmap.vmax, zorder=POP_ZORDER, interpolation='nearest') # draw 10m res coastlines ax.coastlines(resolution="10m", zorder=COAST_ZORDER) states_provinces = cfeature.NaturalEarthFeature( category='cultural', name='admin_1_states_provinces_lines', scale='50m', facecolor='none') ax.add_feature(states_provinces, edgecolor='black', zorder=COAST_ZORDER) # draw country borders using natural earth data set if borderfile is not None: borders = ShapelyFeature( Reader(borderfile).geometries(), ccrs.PlateCarree()) ax.add_feature(borders, zorder=COAST_ZORDER, edgecolor='black', linewidth=2, facecolor='none') # clip the ocean data to the shakemap bbox = (gd.xmin, gd.ymin, gd.xmax, gd.ymax) oceanshapes = _clip_bounds(bbox, oceanfile) ax.add_feature(ShapelyFeature(oceanshapes, crs=geoproj), facecolor=WATERCOLOR, zorder=OCEAN_ZORDER) # So here we're going to project the MMI data to # our mercator map, then smooth and contour that # projected grid. # smooth the MMI data for contouring, themn project mmi = shakegrid.getLayer('mmi').getData() smoothed_mmi = gaussian_filter(mmi, FILTER_SMOOTH) newgd = shakegrid.getGeoDict().copy() smooth_grid = Grid2D(data=smoothed_mmi, geodict=newgd) smooth_grid_merc = smooth_grid.project(projstr) newgd2 = smooth_grid_merc.getGeoDict() # project the ocean grid oceangrid_merc = oceangrid.project(projstr) # create masked arrays using the ocean grid data_xmin, data_xmax = newgd2.xmin, newgd2.xmax data_ymin, data_ymax = newgd2.ymin, newgd2.ymax smooth_data = smooth_grid_merc.getData() landmask = np.ma.masked_where(oceangrid_merc._data == 0.0, smooth_data) oceanmask = np.ma.masked_where(oceangrid_merc._data == 1.0, smooth_data) # contour the data contourx = np.linspace(data_xmin, data_xmax, newgd2.nx) contoury = np.linspace(data_ymin, data_ymax, newgd2.ny) ax.contour( contourx, contoury, np.flipud(oceanmask), linewidths=3.0, linestyles='solid', zorder=1000, cmap=mmimap.cmap, vmin=mmimap.vmin, vmax=mmimap.vmax, levels=np.arange(0.5, 10.5, 1.0), ) ax.contour( contourx, contoury, np.flipud(landmask), linewidths=2.0, linestyles='dashed', zorder=OCEANC_ZORDER, cmap=mmimap.cmap, vmin=mmimap.vmin, vmax=mmimap.vmax, levels=np.arange(0.5, 10.5, 1.0), ) # the idea here is to plot invisible MMI contours at integer levels # and then label them. clabel method won't allow text to appear, # which is this case is kind of ok, because it allows us an # easy way to draw MMI labels as roman numerals. cs_land = plt.contour( contourx, contoury, np.flipud(oceanmask), linewidths=0.0, levels=np.arange(0, 11), alpha=0.0, zorder=CLABEL_ZORDER, ) clabel_text = ax.clabel(cs_land, cs_land.cvalues, colors='k', fmt='%.0f', fontsize=40) for clabel in clabel_text: x, y = clabel.get_position() label_str = clabel.get_text() roman_label = MMI_LABELS[label_str] th = plt.text(x, y, roman_label, zorder=CLABEL_ZORDER, ha='center', va='center', color='black', weight='normal', size=16) th.set_path_effects([ path_effects.Stroke(linewidth=2.0, foreground='white'), path_effects.Normal() ]) cs_ocean = plt.contour( contourx, contoury, np.flipud(landmask), linewidths=0.0, levels=np.arange(0, 11), zorder=CLABEL_ZORDER, ) clabel_text = ax.clabel(cs_ocean, cs_ocean.cvalues, colors='k', fmt='%.0f', fontsize=40) for clabel in clabel_text: x, y = clabel.get_position() label_str = clabel.get_text() roman_label = MMI_LABELS[label_str] th = plt.text(x, y, roman_label, ha='center', va='center', color='black', weight='normal', size=16) th.set_path_effects([ path_effects.Stroke(linewidth=2.0, foreground='white'), path_effects.Normal() ]) # draw meridians and parallels using Cartopy's functions for that gl = ax.gridlines(draw_labels=True, linewidth=2, color=(0.9, 0.9, 0.9), alpha=0.5, linestyle='-', zorder=GRID_ZORDER) gl.xlabels_top = False gl.xlabels_bottom = False gl.ylabels_left = False gl.ylabels_right = False gl.xlines = True # let's floor/ceil the edges to nearest half a degree gxmin = np.floor(xmin * 2) / 2 gxmax = np.ceil(xmax * 2) / 2 gymin = np.floor(ymin * 2) / 2 gymax = np.ceil(ymax * 2) / 2 xlocs = np.linspace(gxmin, gxmax + 0.5, num=5) ylocs = np.linspace(gymin, gymax + 0.5, num=5) gl.xlocator = mticker.FixedLocator(xlocs) gl.ylocator = mticker.FixedLocator(ylocs) gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER gl.xlabel_style = {'size': 15, 'color': 'black'} gl.ylabel_style = {'size': 15, 'color': 'black'} # TODO - figure out x/y axes data coordinates # corresponding to 10% from left and 10% from top # use geoproj and proj dleft = 0.01 dtop = 0.97 proj_str = proj.proj4_init merc_to_dd = pyproj.Proj(proj_str) # use built-in transforms to get from axes units to data units display_to_data = ax.transData.inverted() axes_to_display = ax.transAxes # these are x,y coordinates in projected space yleft, t1 = display_to_data.transform( axes_to_display.transform((dleft, 0.5))) t2, xtop = display_to_data.transform(axes_to_display.transform( (0.5, dtop))) # these are coordinates in lon,lat space yleft_dd, t1_dd = merc_to_dd(yleft, t1, inverse=True) t2_dd, xtop_dd = merc_to_dd(t2, xtop, inverse=True) # drawing our own tick labels INSIDE the plot, as # Cartopy doesn't seem to support this. yrange = ymax - ymin xrange = xmax - xmin ddlabelsize = 12 for xloc in gl.xlocator.locs: outside = xloc < xmin or xloc > xmax # don't draw labels when we're too close to either edge near_edge = (xloc - xmin) < (xrange * 0.1) or (xmax - xloc) < (xrange * 0.1) if outside or near_edge: continue xtext = r'$%.1f^\circ$W' % (abs(xloc)) ax.text(xloc, xtop_dd, xtext, fontsize=ddlabelsize, zorder=GRID_ZORDER, ha='center', fontname=DEFAULT_FONT, transform=ccrs.Geodetic()) for yloc in gl.ylocator.locs: outside = yloc < gd.ymin or yloc > gd.ymax # don't draw labels when we're too close to either edge near_edge = (yloc - gd.ymin) < (yrange * 0.1) or (gd.ymax - yloc) < ( yrange * 0.1) if outside or near_edge: continue if yloc < 0: ytext = r'$%.1f^\circ$S' % (abs(yloc)) else: ytext = r'$%.1f^\circ$N' % (abs(yloc)) ax.text(yleft_dd, yloc, ytext, fontsize=ddlabelsize, zorder=GRID_ZORDER, va='center', fontname=DEFAULT_FONT, transform=ccrs.Geodetic()) # draw cities mapcities = mmap.drawCities(shadow=True, zorder=CITIES_ZORDER) # draw the figure border thickly # TODO - figure out how to draw map border # bwidth = 3 # ax.spines['top'].set_visible(True) # ax.spines['left'].set_visible(True) # ax.spines['bottom'].set_visible(True) # ax.spines['right'].set_visible(True) # ax.spines['top'].set_linewidth(bwidth) # ax.spines['right'].set_linewidth(bwidth) # ax.spines['bottom'].set_linewidth(bwidth) # ax.spines['left'].set_linewidth(bwidth) # Get the corner of the map with the lowest population corner_rect, filled_corner = _get_open_corner(popgrid, ax) clat2 = round_to_nearest(center_lat, 1.0) clon2 = round_to_nearest(center_lon, 1.0) # draw a little globe in the corner showing in small-scale # where the earthquake is located. proj = ccrs.Orthographic(central_latitude=clat2, central_longitude=clon2) ax2 = fig.add_axes(corner_rect, projection=proj) ax2.add_feature(cfeature.OCEAN, zorder=0, facecolor=WATERCOLOR, edgecolor=WATERCOLOR) ax2.add_feature(cfeature.LAND, zorder=0, edgecolor='black') ax2.plot([clon2], [clat2], 'w*', linewidth=1, markersize=16, markeredgecolor='k', markerfacecolor='r') ax2.gridlines() ax2.set_global() ax2.outline_patch.set_edgecolor('black') ax2.outline_patch.set_linewidth(2) # Draw the map scale in the unoccupied lower corner. corner = 'lr' if filled_corner == 'lr': corner = 'll' draw_scale(ax, corner, pady=0.05, padx=0.05) # Draw the epicenter as a black star plt.sca(ax) plt.plot(center_lon, center_lat, 'k*', markersize=16, zorder=EPICENTER_ZORDER, transform=geoproj) if is_scenario: plt.text(center_lon, center_lat, 'SCENARIO', fontsize=64, zorder=WATERMARK_ZORDER, transform=geoproj, alpha=0.2, color='red', horizontalalignment='center') # create pdf and png output file names pdf_file = basename + '.pdf' png_file = basename + '.png' # save to pdf plt.savefig(pdf_file) plt.savefig(png_file) return (pdf_file, png_file, mapcities)
def draw_contour(shakefile, popfile, oceanfile, cityfile, outfilename, make_png=False): """Create a contour map showing population (greyscale) underneath contoured MMI. :param shakefile: String path to ShakeMap grid.xml file. :param popfile: String path to GDALGrid-compliant file containing population data. :param oceanfile: String path to file containing ocean vector data in a format compatible with fiona. :param cityfile: String path to file containing GeoNames cities data. :param outfilename: String path containing desired output PDF filename. :param make_png: Boolean indicating whether a PNG version of the file should also be created in the same output folder as the PDF. :returns: Tuple containing: - Name of PNG file created, or None if PNG output not specified. - CartopyCities object containing the cities that were rendered on the contour map. """ #load the shakemap - for the time being, we're interpolating the #population data to the shakemap, which would be important #if we were doing math with the pop values. We're not, so I think it's ok. shakegrid = ShakeGrid.load(shakefile, adjust='res') gd = shakegrid.getGeoDict() #retrieve the epicenter - this will get used on the map clat = shakegrid.getEventDict()['lat'] clon = shakegrid.getEventDict()['lon'] #load the population data, sample to shakemap popgrid = GDALGrid.load(popfile, samplegeodict=gd, resample=True) popdata = popgrid.getData() #smooth the MMI data for contouring mmi = shakegrid.getLayer('mmi').getData() smoothed_mmi = gaussian_filter(mmi, FILTER_SMOOTH) #clip the ocean data to the shakemap bbox = (gd.xmin, gd.ymin, gd.xmax, gd.ymax) oceanshapes = _clip_bounds(bbox, oceanfile) #load the cities data, limit to cities within shakemap bounds allcities = CartopyCities.fromDefault() cities = allcities.limitByBounds((gd.xmin, gd.xmax, gd.ymin, gd.ymax)) # Define ocean/land masks to do the contours, since we want different contour line styles over land and water. oceangrid = Grid2D.rasterizeFromGeometry(oceanshapes, gd, burnValue=1.0, fillValue=0.0, mustContainCenter=False, attribute=None) oceanmask = np.ma.masked_where(oceangrid == 1.0, smoothed_mmi) landmask = np.ma.masked_where(oceangrid == 0.0, smoothed_mmi) # Use our GMT-inspired palette class to create population and MMI colormaps popmap = ColorPalette.fromPreset('pop') mmimap = ColorPalette.fromPreset('mmi') #use the ShakeMap to determine the aspect ratio of the map aspect = (gd.xmax - gd.xmin) / (gd.ymax - gd.ymin) figheight = FIGWIDTH / aspect fig = plt.figure(figsize=(FIGWIDTH, figheight)) # set up axes object with PlateCaree (non) projection. ax = plt.axes([0.02, 0.02, 0.95, 0.95], projection=ccrs.PlateCarree()) #set the image extent to that of the data img_extent = (gd.xmin, gd.xmax, gd.ymin, gd.ymax) plt.imshow(popdata, origin='upper', extent=img_extent, cmap=popmap.cmap, vmin=popmap.vmin, vmax=popmap.vmax, zorder=9, interpolation='none') #define arrays of latitude and longitude we will use to plot MMI contours lat = np.linspace(gd.ymin, gd.ymax, gd.ny) lon = np.linspace(gd.xmin, gd.xmax, gd.nx) #contour the masked land/ocean MMI data at half-integer levels plt.contour(lon, lat, landmask, linewidths=3.0, linestyles='solid', zorder=10, cmap=mmimap.cmap, vmin=mmimap.vmin, vmax=mmimap.vmax, levels=np.arange(0.5, 10.5, 1.0)) plt.contour(lon, lat, oceanmask, linewidths=2.0, linestyles='dashed', zorder=13, cmap=mmimap.cmap, vmin=mmimap.vmin, vmax=mmimap.vmax, levels=np.arange(0.5, 10.5, 1.0)) #the idea here is to plot invisible MMI contours at integer levels and then label them. #labeling part does not currently work. cs = plt.contour(lon, lat, landmask, linewidths=0.0, levels=np.arange(0, 11), zorder=10) #clabel is not actually drawing anything, but it is blotting out a portion of the contour line. ?? ax.clabel(cs, np.arange(0, 11), colors='k', zorder=25) #set the extent of the map to our data ax.set_extent([lon.min(), lon.max(), lat.min(), lat.max()]) #draw the ocean data if isinstance(oceanshapes[0], mPolygon): for shape in oceanshapes[0]: ocean_patch = PolygonPatch(shape, zorder=10, facecolor=WATERCOLOR, edgecolor=WATERCOLOR) ax.add_patch(ocean_patch) else: ocean_patch = PolygonPatch(oceanshapes[0], zorder=10, facecolor=WATERCOLOR, edgecolor=WATERCOLOR) ax.add_patch(ocean_patch) # add coastlines with desired scale of resolution ax.coastlines('10m', zorder=11) #draw meridians and parallels using Cartopy's functions for that gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=2, color=(0.9, 0.9, 0.9), alpha=0.5, linestyle='-', zorder=20) gl.xlabels_top = False gl.xlabels_bottom = False gl.ylabels_left = False gl.ylabels_right = False gl.xlines = True xlocs = np.arange(np.floor(gd.xmin - 1), np.ceil(gd.xmax + 1)) ylocs = np.arange(np.floor(gd.ymin - 1), np.ceil(gd.ymax + 1)) gl.xlocator = mticker.FixedLocator(xlocs) gl.ylocator = mticker.FixedLocator(ylocs) gl.xformatter = LONGITUDE_FORMATTER gl.yformatter = LATITUDE_FORMATTER gl.xlabel_style = {'size': 15, 'color': 'black'} gl.ylabel_style = {'size': 15, 'color': 'black'} #drawing our own tick labels INSIDE the plot, as Cartopy doesn't seem to support this. yrange = gd.ymax - gd.ymin xrange = gd.xmax - gd.xmin for xloc in gl.xlocator.locs: outside = xloc < gd.xmin or xloc > gd.xmax #don't draw labels when we're too close to either edge near_edge = (xloc - gd.xmin) < (xrange * 0.1) or (gd.xmax - xloc) < ( xrange * 0.1) if outside or near_edge: continue if xloc < 0: xtext = r'$%s^\circ$W' % str(abs(int(xloc))) else: xtext = r'$%s^\circ$E' % str(int(xloc)) ax.text(xloc, gd.ymax - (yrange / 35), xtext, fontsize=14, zorder=20, ha='center', fontname='Bitstream Vera Sans') for yloc in gl.ylocator.locs: outside = yloc < gd.ymin or yloc > gd.ymax #don't draw labels when we're too close to either edge near_edge = (yloc - gd.ymin) < (yrange * 0.1) or (gd.ymax - yloc) < ( yrange * 0.1) if outside or near_edge: continue if yloc < 0: ytext = r'$%s^\circ$S' % str(abs(int(yloc))) else: ytext = r'$%s^\circ$N' % str(int(yloc)) thing = ax.text(gd.xmin + (xrange / 100), yloc, ytext, fontsize=14, zorder=20, va='center', fontname='Bitstream Vera Sans') #Limit the number of cities we show - we may not want to use the population size #filter in the global case, but the map collision filter is a little sketchy right now. mapcities = cities.limitByPopulation(25000) mapcities = mapcities.limitByGrid() mapcities = mapcities.limitByMapCollision(ax, shadow=True) mapcities.renderToMap(ax, shadow=True, fontsize=12, zorder=11) #Get the corner of the map with the lowest population corner_rect, filled_corner = _get_open_corner(popgrid, ax) clat = round_to_nearest(clat, 1.0) clon = round_to_nearest(clon, 1.0) #draw a little globe in the corner showing in small-scale where the earthquake is located. proj = ccrs.Orthographic(central_latitude=clat, central_longitude=clon) ax2 = fig.add_axes(corner_rect, projection=proj) ax2.add_feature(cartopy.feature.OCEAN, zorder=0, facecolor=WATERCOLOR, edgecolor=WATERCOLOR) ax2.add_feature(cartopy.feature.LAND, zorder=0, edgecolor='black') ax2.plot([clon], [clat], 'w*', linewidth=1, markersize=16, markeredgecolor='k', markerfacecolor='r') gh = ax2.gridlines() ax2.set_global() ax2.outline_patch.set_edgecolor('black') ax2.outline_patch.set_linewidth(2) #Draw the map scale in the unoccupied lower corner. corner = 'lr' if filled_corner == 'lr': corner = 'll' draw_scale(ax, corner, pady=0.05, padx=0.05) plt.savefig(outfilename) pngfile = None if make_png: fpath, fname = os.path.split(outfilename) fbase, t = os.path.splitext(fname) pngfile = os.path.join(fpath, fbase + '.png') plt.savefig(pngfile) return (pngfile, mapcities)
def get_quake_desc(event, lat, lon, isMainEvent): ndeaths = event["TotalDeaths"] # summarize the exposure values exposures = np.array([ event["MMI1"], event["MMI2"], event["MMI3"], event["MMI4"], event["MMI5"], event["MMI6"], event["MMI7"], event["MMI8"], event["MMI9+"], ]) exposures = np.array([round_to_nearest(exp, 1000) for exp in exposures]) # get the highest two exposures greater than zero iexp = np.where(exposures > 0)[0][::-1] romans = [ "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX or greater" ] if len(iexp) >= 2: exposures = [exposures[iexp[1]], exposures[iexp[0]]] ilevels = [romans[iexp[1]], romans[iexp[0]]] expfmt = ", with estimated population exposures of %s at intensity" expfmt = expfmt + " %s and %s at intensity %s" exptxt = expfmt % ( commify(int(exposures[0])), ilevels[0], commify(int(exposures[1])), ilevels[1], ) else: exptxt = "" # create string describing this most impactful event dfmt = "A magnitude %.1f earthquake %i km %s of this event struck %s on %s (UTC)%s" mag = event["Magnitude"] etime = pd.Timestamp(event["Time"]) etime = etime.strftime("%B %d, %Y") etime = re.sub(" 0", " ", etime) country = Country() if pd.isnull(event["Name"]): # hack for persistent error in expocat if event["CountryCode"] == "UM" and event["Lat"] > 40: cdict = country.getCountry("US") else: cdict = country.getCountry(event["CountryCode"]) if cdict: cname = cdict["Name"] else: cname = "in the open ocean" else: cname = event["Name"].replace('"', "") cdist = round(geodetic_distance(event["Lat"], event["Lon"], lat, lon)) cdir = get_compass_dir(lat, lon, event["Lat"], event["Lon"], format="long").lower() if ndeaths and str(ndeaths) != "nan": dfmt = dfmt + ", resulting in a reported %s %s." if ndeaths > 1: dstr = "fatalities" else: dstr = "fatality" ndeathstr = commify(int(ndeaths)) eqdesc = dfmt % (mag, cdist, cdir, cname, etime, exptxt, ndeathstr, dstr) else: dfmt = dfmt + ", with no reported fatalities." eqdesc = dfmt % (mag, cdist, cdir, cname, etime, exptxt) return eqdesc