# Copyright (c) 2016,2017,2019 MetPy Developers.
# Distributed under the terms of the BSD 3-Clause License.
# SPDX-License-Identifier: BSD-3-Clause
"""Simplify using the weather symbol font.
See WMO manual 485 Vol 1 for more info on the symbols.
"""
try:
from importlib.resources import (files as importlib_resources_files,
as_file as importlib_resources_as_file)
except ImportError: # Can remove when we require Python > 3.8
from importlib_resources import (files as importlib_resources_files,
as_file as importlib_resources_as_file)
import matplotlib.font_manager as fm
import numpy as np
from ..package_tools import Exporter
exporter = Exporter(globals())
# Create a matplotlib font object pointing to our weather symbol font
fontfile = importlib_resources_files('metpy.plots') / 'fonts/wx_symbols.ttf'
with importlib_resources_as_file(fontfile) as fname:
# Need to pass str, not Path, for older matplotlib
wx_symbol_font = fm.FontProperties(fname=str(fname))
[docs]@exporter.export
def wx_code_to_numeric(codes):
"""Determine the numeric weather symbol value from METAR code text.
A robust method to identifies the numeric value for plotting the correct symbol from a
decoded METAR current weather group. The METAR codes should be strings with no missing
values or NaN strings (empty strings are okay).
For example, if from a Pandas Dataframe sfc_df.wxcodes.fillna('')
Parameters
----------
codes : Array like containing string values of METAR weather codes
Returns
-------
array of numeric codes of current weather symbols from the wx_code_map for use in
plotting.
"""
wx_sym_list = []
for s in codes:
wxcode = s.split()[0] if ' ' in s else s
try:
wx_sym_list.append(wx_code_map[wxcode])
except KeyError:
if wxcode[0].startswith(('-', '+')):
options = [slice(None, 7), slice(None, 5), slice(1, 5), slice(None, 3),
slice(1, 3)]
else:
options = [slice(None, 6), slice(None, 4), slice(None, 2)]
for opt in options:
try:
wx_sym_list.append(wx_code_map[wxcode[opt]])
break
except KeyError:
pass
else:
wx_sym_list.append(0)
return np.array(wx_sym_list)
class CodePointMapping:
"""Map integer values to font code points."""
def __init__(self, num, font_start, font_jumps=None, char_jumps=None):
"""Initialize the instance.
Parameters
----------
num : int
The number of values that will be mapped
font_start : int
The first code point in the font to use in the mapping
font_jumps : list[int, int], optional
Sequence of code point jumps in the font. These are places where the next
font code point does not correspond to a new input code. This is usually caused
by there being multiple symbols for a single code. Defaults to :data:`None`, which
indicates no jumps.
char_jumps : list[int, int], optional
Sequence of code jumps. These are places where the next code value does not
have a valid code point in the font. This usually comes from place in the WMO
table where codes have no symbol. Defaults to :data:`None`, which indicates no
jumps.
"""
next_font_jump = self._safe_pop(font_jumps)
next_char_jump = self._safe_pop(char_jumps)
font_point = font_start
self.chrs = []
code = 0
while code < num:
if next_char_jump and code >= next_char_jump[0]:
jump_len = next_char_jump[1]
code += jump_len
self.chrs.extend([''] * jump_len)
next_char_jump = self._safe_pop(char_jumps)
else:
self.chrs.append(chr(font_point))
if next_font_jump and code >= next_font_jump[0]:
font_point += next_font_jump[1]
next_font_jump = self._safe_pop(font_jumps)
code += 1
font_point += 1
@staticmethod
def _safe_pop(lst):
"""Safely pop from a list.
Returns None if list empty.
"""
return lst.pop(0) if lst else None
def __call__(self, code):
"""Return the Unicode code point corresponding to `code`."""
return self.chrs[code]
def __len__(self):
"""Return the number of codes supported by this mapping."""
return len(self.chrs)
def alt_char(self, code, alt):
"""Get one of the alternate code points for a given value.
In the WMO tables, some code have multiple symbols. This allows getting that
symbol rather than main one.
Parameters
----------
code : int
The code for looking up the font code point
alt : int
The number of the alternate symbol
Returns
-------
int
The appropriate code point in the font
"""
return chr(ord(self(code)) + alt)
#
# Set up mapping objects for various groups of symbols. The integer values follow from
# the WMO.
#
with exporter:
#: Current weather
current_weather = CodePointMapping(100, 0xE9A2, [(7, 2), (93, 2), (94, 2), (95, 2),
(97, 2)], [(0, 4)])
#: Current weather from an automated station
current_weather_auto = CodePointMapping(100, 0xE94B, [(92, 2), (95, 2)],
[(6, 4), (13, 5), (19, 1), (36, 4), (49, 1),
(59, 1), (69, 1), (79, 1), (88, 1), (97, 2)])
#: Low clouds
low_clouds = CodePointMapping(10, 0xE933, [(7, 1)], [(0, 1)])
#: Mid-altitude clouds
mid_clouds = CodePointMapping(10, 0xE93D, char_jumps=[(0, 1)])
#: High clouds
high_clouds = CodePointMapping(10, 0xE946, char_jumps=[(0, 1)])
#: Sky cover symbols
sky_cover = CodePointMapping(12, 0xE90A)
#: Pressure tendency
pressure_tendency = CodePointMapping(10, 0xE900)
#####################################################################
# This dictionary is for mapping METAR present weather text codes
# to WMO codes for plotting wx symbols along with the station plots.
# Pages II-4-3 and II-4-4 of this document describes the difference between
# manned and automated stations:
# https://github.com/Unidata/MetPy/files/1151142/485_Vol_I_en.pdf
# It may become necessary to add automated station wx_codes in the future,
# but that will also require knowing the status of all stations.
wx_code_map = {'': 0, 'M': 0, 'TSNO': 0, 'VA': 4, 'FU': 4,
'HZ': 5, 'DU': 6, 'BLDU': 7, 'SA': 7,
'BLSA': 7, 'VCBLSA': 7, 'VCBLDU': 7, 'BLPY': 7,
'PO': 8, 'VCPO': 8, 'VCDS': 9, 'VCSS': 9,
'BR': 10, 'BCBR': 10, 'BC': 11, 'MIFG': 12,
'VCTS': 13, 'VIRGA': 14, 'VCSH': 16, 'TS': 17,
'THDR': 17, 'VCTSHZ': 17, 'TSFZFG': 17, 'TSBR': 17,
'TSDZ': 17, 'SQ': 18, 'FC': 19, '+FC': 19,
'DS': 31, 'SS': 31, 'DRSA': 31, 'DRDU': 31,
'TSUP': 32, '+DS': 34, '+SS': 34, '-BLSN': 36,
'BLSN': 36, '+BLSN': 36, 'VCBLSN': 36, 'DRSN': 38,
'+DRSN': 38, 'VCFG': 40, 'BCFG': 41, 'PRFG': 44,
'FG': 45, 'FZFG': 49, '-VCTSDZ': 51, '-DZ': 51,
'-DZBR': 51, 'VCTSDZ': 53, 'DZ': 53, '+VCTSDZ': 55,
'+DZ': 55, '-FZDZ': 56, '-FZDZSN': 56, 'FZDZ': 57,
'+FZDZ': 57, 'FZDZSN': 57, '-DZRA': 58, 'DZRA': 59,
'+DZRA': 59, '-VCTSRA': 61, '-RA': 61, '-RABR': 61,
'VCTSRA': 63, 'RA': 63, 'RABR': 63, 'RAFG': 63,
'+VCTSRA': 65, '+RA': 65, '-FZRA': 66, '-FZRASN': 66,
'-FZRABR': 66, '-FZRAPL': 66, '-FZRASNPL': 66, 'TSFZRAPL': 67,
'-TSFZRA': 67, 'FZRA': 67, '+FZRA': 67, 'FZRASN': 67,
'TSFZRA': 67, '-DZSN': 68, '-RASN': 68, '-SNRA': 68,
'-SNDZ': 68, 'RASN': 69, '+RASN': 69, 'SNRA': 69,
'DZSN': 69, 'SNDZ': 69, '+DZSN': 69, '+SNDZ': 69,
'-VCTSSN': 71, '-SN': 71, '-SNBR': 71, 'VCTSSN': 73,
'SN': 73, '+VCTSSN': 75, '+SN': 75, 'VCTSUP': 76,
'IN': 76, '-UP': 76, 'UP': 76, '+UP': 76,
'-SNSG': 77, 'SG': 77, '-SG': 77, 'IC': 78,
'-FZDZPL': 79, '-FZDZPLSN': 79, 'FZDZPL': 79, '-FZRAPLSN': 79,
'FZRAPL': 79, '+FZRAPL': 79, '-RAPL': 79, '-RASNPL': 79,
'-RAPLSN': 79, '+RAPL': 79, 'RAPL': 79, '-SNPL': 79,
'SNPL': 79, '-PL': 79, 'PL': 79, '-PLSN': 79,
'-PLRA': 79, 'PLRA': 79, '-PLDZ': 79, '+PL': 79,
'PLSN': 79, 'PLUP': 79, '+PLSN': 79, '-SH': 80,
'-SHRA': 80, 'SH': 81, 'SHRA': 81, '+SH': 81,
'+SHRA': 81, '-SHRASN': 83, '-SHSNRA': 83, '+SHRABR': 84,
'SHRASN': 84, '+SHRASN': 84, 'SHSNRA': 84, '+SHSNRA': 84,
'-SHSN': 85, 'SHSN': 86, '+SHSN': 86, '-GS': 87,
'-SHGS': 87, 'FZRAPLGS': 88, '-SNGS': 88, 'GSPLSN': 88,
'GSPL': 88, 'PLGSSN': 88, 'GS': 88, 'SHGS': 88,
'+GS': 88, '+SHGS': 88, '-GR': 89, '-SHGR': 89,
'-SNGR': 90, 'GR': 90, 'SHGR': 90, '+GR': 90,
'+SHGR': 90, '-TSRA': 95, 'TSRA': 95, 'TSSN': 95,
'TSPL': 95, '-TSDZ': 95, '-TSSN': 95, '-TSPL': 95,
'TSPLSN': 95, 'TSSNPL': 95, '-TSSNPL': 95, 'TSRAGS': 96,
'TSGS': 96, 'TSGR': 96, '+TSRA': 97, '+TSSN': 97,
'+TSPL': 97, '+TSPLSN': 97, 'TSSA': 98, 'TSDS': 98,
'TSDU': 98, '+TSGS': 99, '+TSGR': 99}