Code source de tinamit.geog.región

import csv
import os
from collections import OrderedDict

from tinamit.config import _
from tinamit.cositas import detectar_codif


[docs]class Nivel(object): """ Un nivel geográfico (p. ej, ``municipio`` o ``departamento``. """ def __init__(símismo, nombre, subniveles=None): """ Parameters ---------- nombre: str El nombre del nivel. subniveles: list of Nivel Lista de subniveles. """ símismo.nombre = nombre símismo.subniveles = subniveles def __eq__(símismo, otro): return str(símismo) == str(otro) def __str__(símismo): return símismo.nombre def __getitem__(símismo, itema): return next(n for n in símismo.subniveles if n == itema) def __hash__(símismo): return hash(str(símismo))
[docs]class Lugar(object): """ Un lugar dado en una geografía. """ def __init__(símismo, nombre, nivel, cód=None, sub_lugares=None): """ Parameters ---------- nombre: str El nombre del lugar. nivel: Nivel El nivel geográfico correspondiente. cód: str El identificador único de este lugar. Si es ``None``, se tomará su nombre como identificador. sub_lugares: Lugares que se encuentre adentro de este. """ símismo.cód = cód or nombre símismo.nivel = nivel símismo.nombre = nombre símismo.sub_lugares = set(sub_lugares or []) símismo.ord_niveles = _OrdNiveles(símismo)
[docs] def lugares(símismo, en=None, nivel=None): """ Devolver los sublugares presentes en este lugar. Parameters ---------- en: str or Lugar Sublugar al cual limitir la búsqueda. nivel: Nivel or str or list Opción para limitir los resultados a uno o más niveles. Returns ------- set[Lugar] """ if isinstance(nivel, (str, Nivel)): nivel = [nivel] if en is None: buscar_en = símismo else: buscar_en = símismo[en] return {lg for lg in buscar_en if (nivel is None or lg.nivel in nivel)}
[docs] def buscar_nombre(símismo, nombre, nivel=None): """ Devuelve el sublugar con el nombre dado. Parameters ---------- nombre: str El nombre del lugar deseado. nivel: Nivel or str Desambiguación en el caso que hayan múltiples lugares con el mismo nombre en distintos niveles. Returns ------- Lugar """ for lg in símismo: if lg.nombre == nombre and (nivel is None or lg.nivel == nivel): return lg raise ValueError(_('Lugar "{nmb}" no encontrado en "{lg}"').format(nmb=nombre, lg=símismo))
[docs] def pariente(símismo, lugar, ord_niveles=None, todos=False): """ Obtener el pariente de un sublugar dado. Parameters ---------- lugar: str or Lugar Un sublugar cuyo pariente queremos. ord_niveles: list Desambiguación para lugares con niveles paralelos. todos: bool Si queremos todos los parientes del lugar, o solamente el más cercaco. Returns ------- Lugar """ lugar = símismo[lugar] ord_niveles = símismo.ord_niveles.resolver(ord_niveles) potenciales = [lg for lg in símismo if lugar in lg.sub_lugares and lg.nivel in ord_niveles] if potenciales: if todos: return sorted(potenciales, key=lambda x: ord_niveles.index(str(x.nivel))) return sorted(potenciales, key=lambda x: ord_niveles.index(str(x.nivel)))[0]
[docs] def hijos_inmediatos(símismo, ord_niveles=None): """ Devuelve los hijos inmediatos de este ``Lugar``. Parameters ---------- ord_niveles: list Desambiguación para lugares con niveles paralelos. Returns ------- list[Lugar] """ return [lg for lg in símismo if símismo.pariente(lg, ord_niveles=ord_niveles) == símismo]
def __iter__(símismo): yield símismo for lg in símismo.sub_lugares: for s_lg in lg: yield s_lg def __getitem__(símismo, itema): for lg in símismo: if isinstance(itema, Lugar) and lg is itema: return lg elif itema == lg.cód: return lg raise KeyError(itema) def __contains__(símismo, itema): try: return símismo[itema] is not None except KeyError: return False def __str__(símismo): return símismo.nombre
class _OrdNiveles(object): def __init__(símismo, lugar): sub_ords = list(set(sub.ord_niveles.ords for sub in lugar.sub_lugares)) ords = [] for sb in sub_ords: for i, nv in enumerate(sb): if i > (len(ords) - 1): ords.append(nv) else: ant = ords[i] if isinstance(ant, Nivel) and ant != nv: ords[i] = (ant, nv) elif isinstance(ant, tuple) and nv not in ant: ords[i] = (*ant, nv) símismo.ords = (*ords, lugar.nivel) def resolver(símismo, orden=None): if orden is None: return [x if isinstance(x, Nivel) else x[0] for x in símismo.ords] if isinstance(orden, (str, Nivel)): orden = [orden] return orden def __contains__(símismo, itema): for x in símismo.ords: if (isinstance(x, Nivel) and x == itema) or (itema in x): return True return False
[docs]def gen_lugares(archivo, nivel_base, nombre=None, col_cód='Código'): """ Genera un lugar con todos los niveles y sublugares asociados desde un archivo ``.csv``. Cada columna en el ``.csv`` debe empezar con el nombre de un nivel, con la excepción de la columna ``col_cód``, la cual tendrá el código identificador único de cada lugar. Cada fila representa un lugar, con su **nombre** en la columna correspondiendo al nivel de este lugar y el **código** del lugar pariente en las otras columnas. Si un nivel no se aplica a un lugar (por ejemplo, un departamento no tendrá municipio pariente), se deja vacía la célula. Parameters ---------- archivo: str El archivo ``.csv``. nivel_base: str El el nivel más alto. Por ejemplo, si tu csv entero representa un país, sería ``país``. nombre: str El nombre del lugar correspondiendo al nivel más alto. Por ejemplo, "Guatemala". col_cód: str El nombre de la columna con los códigos de cada sublugar. Returns ------- Lugar """ codif_csv = detectar_codif(archivo) nombre = nombre or os.path.splitext(os.path.split(nombre)[1])[0] with open(archivo, newline='', encoding=codif_csv) as d: lc = csv.DictReader(d) # El lector de csv # Guardar la primera fila como nombres de columnas cols = [x.strip() for x in lc.fieldnames] if col_cód not in cols: raise ValueError(_( 'La columna de código de región especificada ("{c}") no concuerda con los nombres de ' 'columnas del csv ({n}).' ).format(c=col_cód, n=', '.join(cols))) doc = [OrderedDict((ll.strip(), v.strip()) for ll, v in f.items()) for f in lc] # Inferir el orden de la jerarquía órden = [] escalas = [x for x in cols if x != col_cód] coescalas = [] for f in doc: coescalas_f = {ll for ll, v in f.items() if len(v) and ll in escalas} if not any(x == coescalas_f for x in coescalas): coescalas.append(coescalas_f) coescalas.sort(key=lambda x: len(x)) while len(coescalas): siguientes = {x.pop() for x in coescalas if len(x) == 1} if not len(siguientes): raise ValueError(_('Parece que hay un error con el fuente de información regional.')) órden.append(sorted(list(siguientes)) if len(siguientes) > 1 else siguientes.pop()) for cn in coescalas.copy(): cn.difference_update(siguientes) if not len(cn): coescalas.remove(cn) órden.insert(0, nivel_base) niveles = {} anterior = None for nvls in órden[::-1]: if isinstance(nvls, str): niveles[nvls] = Nivel(nombre=nvls, subniveles=anterior) anterior = niveles[nvls] else: for nvl in nvls: niveles[nvl] = Nivel(nombre=nvl, subniveles=anterior) anterior = [niveles[nv] for nv in nvls] dic_doc = {f[col_cód]: f for f in doc} quedan = list(dic_doc) lugares = {} for nvl, obj_nvl in niveles.items(): for cód in list(quedan): lg = dic_doc[cód] if lg[nvl]: nmb = lg[nvl] subs = {s for c, s in lugares.items() if dic_doc[c][nvl] == cód} lugares[cód] = Lugar(nmb, nivel=obj_nvl, cód=cód, sub_lugares=subs) quedan.remove(cód) return Lugar(nombre=nombre, nivel=niveles[nivel_base], sub_lugares=lugares.values())