Code source de tinamit.geog.mapa
import os
import matplotlib.pyplot as dib
import numpy as np
import shapefile as sf
from matplotlib import colors, cm
from matplotlib.axes import Axes
from matplotlib.backends.backend_agg import FigureCanvasAgg as TelaFigura
from matplotlib.figure import Figure as Figura
from tinamit.config import _
from ..mod import ResultadosSimul, ResultadosGrupo
[docs]def dibujar_mapa(formas, archivo=None, título=None, fig=None, args_color=None):
"""
Dibuja un mapa.
Parameters
----------
formas: list of Forma
Las formas para incluir.
archivo: str
Dónde hay que guardar el gráfico. Si es ``None``, no se guardará el gráfico.
título: str
El título del mapa.
fig: matplotlib.Figure
Figura para dibujar el mapa.
Returns
-------
tuple[Figure, Axes]
La figura y sus ejes.
"""
formas = [formas] if isinstance(formas, Forma) else formas
if not fig:
fig = Figura()
TelaFigura(fig)
ejes = fig.add_subplot(111)
ejes.set_aspect('equal')
for frm in formas:
frm.dibujar(ejes, fig, args_color=args_color)
if título is not None:
ejes.set_title(título)
if archivo:
fig.savefig(archivo, dpi=500)
return fig, ejes
[docs]def dibujar_mapa_de_res(
forma_dinámica, res, var, t, escala=None, título='', directorio=None, otras_formas=None
):
"""
Dibujar los resultados de una simulación en un mapa.
Parameters
----------
forma_dinámica: FormaDinámica
La forma cuyos colores variarán según los resultados.
res: ResultadosSimul or ResultadosGrupo
Los resultados para dibujar.
var: str
El variable de interés.
t: int or tuple or range or list
Los tiempos a los cuales queremos graficar los resultados.
escala: tuple
El rango para aplicar colores. Si es ``None``, se aplicará según los datos de los resultados.
título: str
El título del gráfico.
directorio: str
Dónnde hay que guardar el gráfico.
otras_formas: list of FormaEstática or FormaEstática
Las otras formas (estáticas) para incluir en el gráfico.
"""
título = título or res.nombre
otras_formas = otras_formas or []
if isinstance(otras_formas, FormaEstática):
otras_formas = [otras_formas]
def _extr_i(matr, i):
return matr[i] if isinstance(i, int) else matr.sel(**{_('fecha'): i}).values
if isinstance(res, ResultadosSimul):
res_var = res[var].vals
unids = res[var].var.unid
escala = escala or (np.min(res_var.values), np.max(res_var.values))
def _extr_res_i(i):
return _extr_i(res_var, i)
else:
unids = list(res.values())[0][var].var.unid
todos_vals = np.array([res_lg[var].vals for res_lg in res.values()])
escala = escala or (np.min(todos_vals), np.max(todos_vals))
def _extr_res_i(i):
return {lg: _extr_i(res_lg[var].vals, i) for lg, res_lg in res.items()}
if isinstance(t, int):
pasos = range(t, t + 1)
else:
pasos = t
for i_p in pasos:
título_i = '{}_{}'.format(título, i_p)
res_i = _extr_res_i(i_p)
forma_dinámica.estab_valores(res_i, escala_valores=escala, unidades=unids)
arch_i = os.path.join(directorio, título_i) if directorio is not None else None
fig_i = None if directorio else dib.figure()
dibujar_mapa([forma_dinámica, *otras_formas], archivo=arch_i, título=título_i, fig=fig_i)
[docs]class Forma(object):
"""
Clase pariente para todas las formas que se pueden dibujar.
"""
def __init__(símismo, archivo, llenar, alpha, **argsll):
# codif = detectar_codif(os.path.splitext(archivo)[0] + '.dbf')
símismo.forma = sf.Reader(archivo, **argsll) # , encoding=codif)
símismo.llenar = llenar
símismo.alpha = alpha
[docs] def dibujar(símismo, ejes, fig, args_color=None):
"""
Agrega la forma a la figura.
Parameters
----------
ejes:
Los ejes de la figura.
fig:
La figura.
"""
raise NotImplementedError
def _dibujar_frm(símismo, ejes, color):
for i, frm in enumerate(símismo.forma.shapes()):
puntos = frm.points
partes = frm.parts
for ip, i0 in enumerate(partes): # Para cada parte del imagen
if ip < len(partes) - 1:
i1 = partes[ip + 1] - 1
else:
i1 = len(puntos)
seg = puntos[i0:i1 + 1]
x_lon = np.zeros((len(seg), 1))
y_lat = np.zeros((len(seg), 1))
for j in range(len(seg)):
x_lon[j] = seg[j][0]
y_lat[j] = seg[j][1]
clr = color[i] if isinstance(color, np.ndarray) else color
if símismo.llenar:
ejes.fill(x_lon, y_lat, color=clr, alpha=símismo.alpha)
else:
ejes.plot(x_lon, y_lat, color=clr, alpha=símismo.alpha)
[docs]class FormaEstática(Forma):
"""
Clase de base para formas estáticas en el mapa, cuyos colores no cambian.
"""
def __init__(símismo, archivo, color, llenar, alpha):
símismo.color = color
super().__init__(archivo, llenar=llenar, alpha=alpha)
[docs] def dibujar(símismo, ejes, fig, args_color=None):
símismo._dibujar_frm(ejes, color=símismo.color)
[docs]class FormaDinámica(Forma):
"""
Forma cuyos colores se asignan según valores numéricos.
"""
def __init__(símismo, archivo, escala_colores=None, llenar=True, alpha=1, **argsll):
"""
Parameters
----------
archivo: str
El archivo ``.shp``.
escala_colores: list or tuple or str or int or None
Lista de dos colores para establecer una escala de colores. Si es un solo color, se agregará el color
blanco. Si es ``-1``, se inverserán los colores automáticos.
llenar: bool
Si hay que llenar la forma o simplement delinear su contorno.
alpha: float or int
La opacidad del interior de la forma. Solamente aplica si ``llenar`` est ``False``.
"""
super().__init__(archivo, llenar=llenar, alpha=alpha, **argsll)
símismo.escala_colores = símismo._resolver_colores(escala_colores)
símismo.valores = np.full(len(símismo.forma.shapes()), np.nan)
símismo.unidades = None
símismo.escala = None
[docs] def estab_valores(símismo, valores, escala_valores=None, unidades=None):
"""
Establece los valores para colorar.
Parameters
----------
valores: np.ndarray or dict
Los valores para dibujar. Debe ser del mismo tamaño que el archivo ``.shp`` en ``archivo``.
escala_valores: tuple or list or None
La escala para el rango de colores. Si es ``None``, se ajustará el rango según de los valores dados.
unidades: str, optional
Las unidades.
"""
símismo.unidades = unidades
símismo._llenar_valores(valores)
if escala_valores is None:
if np.all(np.isnan(símismo.valores)):
escala_valores = (0, 1)
else:
escala_valores = (np.nanmin(símismo.valores), np.nanmax(símismo.valores))
if escala_valores[0] == escala_valores[1]:
escala_valores = (escala_valores[0] - 0.5, escala_valores[0] + 0.5)
símismo.escala = escala_valores
[docs] def dibujar(símismo, ejes, fig, args_color=None):
args_color = args_color or {}
vals_norm = (símismo.valores - símismo.escala[0]) / (símismo.escala[1] - símismo.escala[0])
d_clrs = _gen_d_mapacolores(colores=símismo.escala_colores)
mapa_color = colors.LinearSegmentedColormap('mapa_color', d_clrs)
norm = colors.Normalize(vmin=símismo.escala[0], vmax=símismo.escala[1])
cpick = cm.ScalarMappable(norm=norm, cmap=mapa_color)
cpick.set_array(np.array([]))
v_cols = mapa_color(vals_norm)
v_cols[np.isnan(vals_norm)] = 1
símismo._dibujar_frm(ejes=ejes, color=v_cols)
if símismo.unidades is not None:
fig.colorbar(cpick, label=símismo.unidades, ax=ejes, **args_color)
else:
fig.colorbar(cpick, ax=ejes, **args_color)
@staticmethod
def _resolver_colores(colores):
if colores is None:
return ['#FF6666', '#FFCC66', '#00CC66']
elif colores == -1:
return ['#00CC66', '#FFCC66', '#FF6666']
elif isinstance(colores, str):
return ['#FFFFFF', colores]
return colores
def _extraer_col(símismo, col):
nombres_attr = [field[0] for field in símismo.forma.fields[1:]]
try:
return [x.record[nombres_attr.index(col)] for x in símismo.forma.shapeRecords()]
except ValueError:
raise ValueError(_('La columna "{}" no existe en la base de datos.').format(col))
def _llenar_valores(símismo, valores):
raise NotImplementedError
[docs]class FormaDinámicaNumérica(FormaDinámica):
"""
Forma dinámica cuyos valores se asignan a los polígonos de la forma ``.shp`` por su orden en la matriz de valores.
"""
def __init__(símismo, archivo, col_id=None, escala_colores=None, llenar=True, alpha=1):
"""
Parameters
----------
archivo: str
El archivo ``.shp``.
col_id: str, optional
La columna con el número de cada polígono en la forma ``.shp``. Si es ``None``, se asiñará número según
su orden en la forma ``.shp``.
escala_colores: list or tuple or str or int or None
Lista de dos colores para establecer una escala de colores. Si es un solo color, se agregará el color
blanco. Si es ``-1``, se inverserán los colores automáticos.
llenar: bool
Si hay que llenar la forma o simplement delinear su contorno.
alpha: float or int
La opacidad del interior de la forma. Solamente aplica si ``llenar`` est ``False``.
"""
super().__init__(archivo, escala_colores, llenar, alpha)
if col_id is not None:
ids = np.array(símismo._extraer_col(col_id))
símismo.ids = ids - np.min(ids)
else:
símismo.ids = None
def _llenar_valores(símismo, valores):
if símismo.ids is None:
símismo.valores[:] = valores
else:
símismo.valores[:] = valores[símismo.ids]
[docs]class FormaDinámicaNombrada(FormaDinámica):
"""
Forma dinámica cuyos valores se asignan a los polígonos de la forma ``.shp`` por su llave en el diccionario de
valores.
"""
def __init__(símismo, archivo, col_id, escala_colores=None, llenar=True, alpha=1, **argsll):
"""
Parameters
----------
archivo: str
La archivo ``.shp``.
col_id: str
La columna en el archivo ``.shp`` con el nomrbe de cada polígono.
escala_colores: list or tuple or str or int or None
Lista de dos colores para establecer una escala de colores. Si es un solo color, se agregará el color
blanco. Si es ``-1``, se inverserán los colores automáticos.
llenar: bool
Si hay que llenar la forma o simplement delinear su contorno.
alpha: float or int
La opacidad del interior de la forma. Solamente aplica si ``llenar`` est ``False``.
"""
super().__init__(archivo, escala_colores, llenar, alpha, **argsll)
símismo.ids = [str(x) for x in símismo._extraer_col(col_id)]
def _llenar_valores(símismo, valores):
símismo.valores[:] = np.nan
for id_, val in valores.items():
i = símismo.ids.index(id_)
símismo.valores[i] = val
[docs]class Agua(FormaEstática):
"""
Representa áreas de agua.
"""
def __init__(símismo, archivo, llenar=True):
"""
Parameters
----------
archivo: str
El archivo ``.shp``.
llenar: bool
Si hay que llenar el cuerpo de agua o no.
"""
super().__init__(archivo=archivo, color='#13A6DD', llenar=llenar, alpha=0.5)
[docs]class Bosque(FormaEstática):
"""
Representa áreas con bosque.
"""
def __init__(símismo, archivo):
super().__init__(archivo=archivo, color='#33A02C', llenar=True, alpha=0.7)
[docs]class Calle(FormaEstática):
"""
Representa calles.
"""
def __init__(símismo, archivo):
super().__init__(archivo=archivo, color='#585763', llenar=False, alpha=1)
[docs]class Ciudad(FormaEstática):
"""
Representa áreas urbanas.
"""
def __init__(símismo, archivo):
super().__init__(archivo=archivo, color='#FB9A99', llenar=True, alpha=1)
[docs]class OtraForma(FormaEstática):
"""
Representa otras áreas no representadas por las otras formas disonibles.
"""
def __init__(símismo, archivo):
super().__init__(archivo=archivo, color='#FFECB3', llenar=True, alpha=1)
def _hex_a_rva(hx):
"""
Convierte colores RVA a Hex.
Parameters
----------
hx: str
El valor hex.
Returns
-------
tuple
El valor rva.
"""
return tuple(int(hx.lstrip('#')[i:i + 2], 16) for i in (0, 2, 4))
def _gen_d_mapacolores(colores):
"""
Genera un diccionario de mapa de color para MatPlotLib.
Parameters
----------
colores: list
Una lista de colores
Returns
-------
dict
Un diccionario para MatPlotLib
"""
clrs_rva = [_hex_a_rva(x) for x in colores]
# noinspection PyTypeChecker
n_colores = len(colores)
dic_c = {'red': tuple((round(i / (n_colores - 1), 2), clrs_rva[i][0] / 255, clrs_rva[i][0] / 255) for i in
range(0, n_colores)),
'green': tuple(
(round(i / (n_colores - 1), 2), clrs_rva[i][1] / 255, clrs_rva[i][1] / 255) for i in
range(0, n_colores)),
'blue': tuple(
(round(i / (n_colores - 1), 2), clrs_rva[i][2] / 255, clrs_rva[i][2] / 255) for i in
range(0, n_colores))}
return dic_c