apb_extra_osgeo_utils

Package apb_extra_osgeo_utils

Modules to add common functionality to OSGEO GDAL. Requires GDAL library C previously installed (see how here https://gdal.org/download.html#binaries).

Requires GDAL library version 3.6<=3.10 installed.

To install:

pip install apb_extra_osgeo_utils

Documentation here apb_extra_osgeo_utils

   1#  coding=utf-8
   2#
   3#  Author: Ernesto Arredondo Martinez (ernestone@gmail.com)
   4#  Created: 7/6/19 18:23
   5#  Last modified: 7/6/19 14:27
   6#  Copyright (c) 2019
   7"""
   8.. include:: ../README.md
   9"""
  10
  11import logging
  12import os
  13import re
  14from collections import OrderedDict, namedtuple
  15from logging import Logger
  16
  17import math
  18from osgeo import gdal, ogr, osr
  19from osgeo.ogr import ODsCCreateLayer, OLCAlterFieldDefn, OLCCreateField, ODsCTransactions, \
  20    ODsCDeleteLayer, OLCTransactions, Geometry, ODrCCreateDataSource, GeomFieldDefn
  21
  22from apb_extra_utils import misc as utils, utils_logging
  23
  24DRVR_PG = 'PostgreSQL'
  25DRVR_CSV = "CSV"
  26DRVR_GPKG = "GPKG"
  27DRVR_GEOJSON = "GeoJSON"
  28DRVR_KML = "KML"
  29
  30SIMPLIFY_TOLERANCE = 1e-6
  31DRIVERS_GDAL_NOT_DELETE_LAYERS = (DRVR_PG,)
  32DRIVERS_OLD_SRS_AXIS_MAPPING_4326 = (DRVR_GEOJSON, DRVR_GPKG, DRVR_PG)
  33DRIVERS_NO_SPATIAL_IDX = (DRVR_GEOJSON, DRVR_CSV, DRVR_PG)
  34PREFFIX_GEOMS_LAYERS_GDAL = 'geom_'
  35PREFFIX_GEOMS_LAYERS_GDAL_CSV = '_WKT'
  36
  37# Cache global: {ds_name: {'count': int, 'layers': set[str]}}
  38_CACHE_DS_POSTGIS: dict[str, ogr.DataSource] = {}
  39_CACHE_NOM_LAYERS_DS_GDAL = {}
  40
  41print_debug = logging.debug
  42print_warning = logging.warning
  43
  44
  45def get_geom_field_defn(layer_feature_defn, id_geom: int = 0):
  46    """
  47    Retorna el GeomFieldDefn indicado solo cuando existe al menos un campo geométrico.
  48
  49    Args:
  50        layer_feature_defn (osgeo.ogr.FeatureDefn): una instancia de FeatureDefn (vease https://gdal.org/en/stable/api/python/vector_api.html#featuredefn)
  51        id_geom (int=0): índice del campo geométrico a recuperar.
  52
  53    Returns:
  54        osgeo.ogr.GeomFieldDefn: vease https://gdal.org/en/stable/api/python/vector_api.html#geomfielddefn
  55    """
  56    if not layer_feature_defn:
  57        return None
  58
  59    if id_geom < 0:
  60        raise ValueError(f"id_geom debe ser >= 0. Valor recibido: {id_geom}")
  61
  62    count_geoms = layer_feature_defn.GetGeomFieldCount()
  63    if count_geoms == 0:
  64        return None
  65
  66    if id_geom >= count_geoms:
  67        raise ValueError(
  68            f"id_geom fuera de rango ({id_geom}). Geometrías disponibles: {count_geoms} (índices 0..{count_geoms - 1})"
  69        )
  70
  71    return layer_feature_defn.GetGeomFieldDefn(id_geom)
  72
  73
  74def srs_ref_from_epsg_code(code_epsg: int, old_axis_mapping=False) -> osr.SpatialReference:
  75    """
  76
  77    Args:
  78        code_epsg (int):
  79        old_axis_mapping (bool=False): Por cambio entre GDAL2.0 y GDAL3.0 se (vease
  80    https://github.com/OSGeo/gdal/blob/master/gdal/MIGRATION_GUIDE.TXT) por defecto se utiliza el mapeo de ejes
  81    clasico LONG,LAT
  82
  83    Returns:
  84        srs (osr.SpatialReference)
  85    """
  86    srs = osr.SpatialReference()
  87    if old_axis_mapping:
  88        try:
  89            from osgeo.osr import OAMS_TRADITIONAL_GIS_ORDER
  90            print_debug("OAMS_TRADITIONAL_GIS_ORDER")
  91            srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
  92        except ImportError:
  93            print_warning("OAMS_TRADITIONAL_GIS_ORDER not available")
  94
  95    ret = srs.ImportFromEPSG(code_epsg)
  96    if ret != 0:
  97        raise Warning("No se puede retornar un osgeo.osr.SpatialReference EPSG para el codigo '{}'!!".format(code_epsg))
  98
  99    return srs
 100
 101
 102def layer_gdal_from_file(path_file: str,
 103                         nom_driver: str = DRVR_GEOJSON,
 104                         nom_geom: str = None,
 105                         default_srs_epsg_code: int = 4326,
 106                         default_order_long_lat: bool = True):
 107    """
 108
 109    Args:
 110        path_file (str):
 111        nom_driver (str=DRVR_GEOJSON):
 112        nom_geom (str=None): si se informa devolverá la layer solo con la geometria especificada
 113        default_srs_epsg_code (int=4326): codigo del sistema de coordenadas que asignará por defecto si la layer
 114                                          NO tiene sistema definido
 115        default_order_long_lat (bool=True): si no viene informado el SRS y se usa el default EPSG4326 entonces se supone
 116                                          por defecto que las geometria vienen en orden antiguo LONGITUD, LATITUD
 117
 118    Returns:
 119        layer_gdal (osgeo.ogr.Layer), nom_layer (str), datasource_gdal (osgeo.ogr.DataSource)
 120    """
 121    drvr = ogr.GetDriverByName(nom_driver)
 122
 123    nom_layer, ext = utils.split_ext_file(os.path.basename(path_file))
 124    if ext.lower().endswith("zip"):
 125        path_file = "/vsizip/{}".format(path_file)
 126
 127    ds_vector_file = drvr.Open(path_file, 0)
 128
 129    a_layer = None
 130    if ds_vector_file:
 131        a_layer = ds_vector_file.GetLayer(0)
 132
 133        if nom_geom:
 134            lay_def = a_layer.GetLayerDefn()
 135            nom_geom = nom_geom.strip().upper()
 136
 137            if lay_def.GetGeomFieldCount() > 1:
 138                idx_geom = lay_def.GetGeomFieldIndex(nom_geom)
 139                if idx_geom < 0:
 140                    raise Exception("El fichero vectorial '{}' no contiene una geometria con el nombre '{}'".format(
 141                        ds_vector_file.GetName(), nom_geom))
 142                elif idx_geom > 0:
 143                    # Convertimos a layer en MEMORY para poder cambiar estructura
 144                    ds_mem = ds_gdal_memory()
 145                    a_layer = create_layer_from_layer_gdal_on_ds_gdal(ds_mem, a_layer, nom_layer, nom_geom,
 146                                                                      exclude_cols_geoms=False, null_geoms=True)
 147                    if a_layer:
 148                        ds_vector_file = ds_mem
 149
 150            elif not a_layer.GetGeometryColumn() and (geom_def := get_geom_field_defn(lay_def)):
 151                geom_def.SetName(nom_geom)
 152
 153        if a_layer:
 154            # Por si no carga el SRS se asigna el :default_srs_epsg_code (defecto epsg:4326)
 155            for gf in geom_fields_layer_gdal(a_layer):
 156                if not gf.GetSpatialRef():
 157                    gf.SetSpatialRef(srs_ref_from_epsg_code(default_srs_epsg_code, default_order_long_lat))
 158
 159    return a_layer, nom_layer, ds_vector_file
 160
 161
 162def datasource_gdal_vector_file(nom_driver_gdal, nom_ds, a_dir, create=None, from_zip=False, **create_options):
 163    """
 164    Crea datasource gdal para driver de tipo fichero
 165    Args:
 166        nom_driver_gdal (str):
 167        nom_ds (str):
 168        a_dir (str):
 169        create (bool=None): Por defecto crea el datasource si este no existe y se abre sin problemas previamente.
 170                        Si False entonces NO lo crea en ningún caso, y si True lo creará sin intentar abrirlo
 171        from_zip (bool=False):
 172        **create_options: lista claves valores con opciones de creacion para el datasource de GDAL
 173                        p.e.: NameField="SEQENTITAT", DescriptionField="SW_URN"
 174
 175    Returns:
 176        datasource_gdal (osgeo.ogr.DataSource), overwrited (bool)
 177    """
 178    driver, exts_driver = driver_gdal(nom_driver_gdal)
 179    if not exts_driver:
 180        raise Exception("ERROR! - El driver GDAL {} no es de tipo fichero vectorial".format(driver))
 181
 182    base_path_file = os.path.normpath(os.path.join(a_dir, nom_ds.strip().lower()))
 183    _, ext_base = os.path.splitext(base_path_file)
 184
 185    vsi_prefix = ""
 186    if from_zip:
 187        ext_file = "zip"
 188        vsi_prefix = "/vsizip/"
 189    else:
 190        if "topojson" in (ext.lower() for ext in exts_driver):
 191            ext_file = "topojson"
 192        else:
 193            ext_file = exts_driver[0]
 194
 195    if (os.path.exists(base_path_file) and os.path.isfile(
 196            base_path_file)) or ext_base.lower() == f'.{ext_file.lower()}':
 197        path_file = base_path_file
 198    else:
 199        path_file = "{}.{}".format(base_path_file, ext_file)
 200
 201    overwrited = False
 202    datasource_gdal = None
 203    if not create and os.path.exists(path_file):
 204        open_path_file = "{}{}".format(vsi_prefix, path_file)
 205        datasource_gdal = driver.Open(open_path_file, 1)
 206        if datasource_gdal is None:
 207            datasource_gdal = driver.Open(open_path_file)
 208
 209    if datasource_gdal:
 210        overwrited = True
 211
 212    if create or (create is not False and datasource_gdal is None and driver.TestCapability(ODrCCreateDataSource)):
 213        datasource_gdal = driver.CreateDataSource(path_file,
 214                                                  list("{}={}".format(opt, val) for opt, val in create_options.items()))
 215
 216    return datasource_gdal, overwrited
 217
 218
 219def driver_gdal(nom_driver):
 220    """
 221    Devuelve el driver GDAL solicitado y si es de tipo fichero vectorial tambien devuelve la extension
 222    Si no coincide exactamente devuelve el que tiene nombre más parecido.
 223    Verificar con drivers_ogr_gdal_disponibles() los disponibles
 224
 225    Args:
 226        nom_driver:
 227    Returns:
 228        driver_gdal (osgeo.ogr.Driver), exts_driver (list)
 229    """
 230    driver_gdal = ogr.GetDriverByName(nom_driver)
 231    if driver_gdal is None:
 232        raise ValueError(f"Driver '{nom_driver}' is not available")
 233
 234    metadata = driver_gdal.GetMetadata()
 235    exts_drvr = metadata.get('DMD_EXTENSIONS', "").split(" ") if metadata else []
 236
 237    return driver_gdal, exts_drvr
 238
 239
 240def ds_gdal_memory():
 241    """
 242    Devuelve un osgeo.ogr.DataSource de tipo "memory"
 243
 244    Returns:
 245        datasource_gdal (osgeo.ogr.DataSource)
 246    """
 247    return ogr.GetDriverByName("memory").CreateDataSource("")
 248
 249
 250def set_create_option_list_for_layer_gdal(layer_gdal, drvr_name=DRVR_GPKG, has_geom=True, **extra_opt_list):
 251    """
 252    Devuelve la lista de create options GDAL a partir de una layer, el driver para el que se quiere crear y options
 253    pasadas por defecto
 254
 255    Args:
 256        layer_gdal (ogr.Layer):
 257        drvr_name (str=DRVR_GPKG): nombre de driver GDAL
 258        has_geom (bool=True): Indica si la layer tiene geometria. Si es False no añadirá options de geom como GEOMETRY_NAME, SPATIAL_INDEX, ...
 259        extra_opt_list: lista pares claves-valores que se corresponden con option create list del driver GDAL
 260
 261    Returns:
 262        opt_list (dict)
 263    """
 264    opt_list = set_create_option_list_for_driver_gdal(drvr_name, has_geom=has_geom, **extra_opt_list)
 265
 266    opt_geom_name = "GEOMETRY_NAME"
 267    if has_geom and opt_geom_name not in opt_list:
 268        nom_geom_src = layer_gdal.GetGeometryColumn()
 269        if nom_geom_src:
 270            opt_list[opt_geom_name] = "{}={}".format(opt_geom_name, nom_geom_src)
 271
 272    return opt_list
 273
 274
 275def set_create_option_list_for_driver_gdal(drvr_name=DRVR_GPKG, has_geom=True, **extra_opt_list):
 276    """
 277    Devuelve la lista de create options GDAL a partir de una layer, el driver para el que se quiere crear y options
 278    pasadas por defecto
 279
 280    Args:
 281        drvr_name (str=DRVR_GPKG): nombre de driver GDAL
 282        has_geom (bool=True): Indica si la layer tiene geometria. Si es False no añadirá options de geom como SPATIAL_INDEX, ...
 283        extra_opt_list: lista pares claves-valores que se corresponden con option create list del driver GDAL
 284
 285    Returns:
 286        opt_list (dict)
 287    """
 288    # Se quitan las options que no son de creacion para el driver especificado
 289    drvr, exts_drvr = driver_gdal(drvr_name)
 290    if not drvr:
 291        Exception("!ERROR! - El nombre de driver '{}' no es un driver GDAL disponible".format(drvr_name))
 292
 293    opt_list = {k.upper(): v.upper() for k, v in extra_opt_list.items()}
 294
 295    if not "FID" in opt_list and drvr.GetName() == DRVR_GPKG:
 296        opt_list["FID"] = 'FID=FID_GPKG'
 297
 298    if drvr.GetName() == DRVR_GEOJSON:
 299        if "WRITE_BBOX" not in opt_list:
 300            opt_list["WRITE_BBOX"] = 'WRITE_BBOX=YES'
 301
 302    if drvr.GetName() == DRVR_CSV:
 303        if "CREATE_CSVT" not in opt_list:
 304            opt_list["CREATE_CSVT"] = 'CREATE_CSVT=YES'
 305        if "GEOMETRY" not in opt_list and has_geom:
 306            opt_list["GEOMETRY"] = 'GEOMETRY=AS_WKT'
 307
 308    if drvr.GetMetadataItem('DS_LAYER_CREATIONOPTIONLIST'):
 309        list_opts_drvr = drvr.GetMetadataItem('DS_LAYER_CREATIONOPTIONLIST')
 310        keys_opt_list = list(opt_list.keys())
 311        for n_opt in keys_opt_list:
 312            if list_opts_drvr.find(n_opt) < 0:
 313                opt_list.pop(n_opt)
 314
 315        if has_geom and "SPATIAL_INDEX" not in opt_list and \
 316                list_opts_drvr.find("SPATIAL_INDEX") >= 0 and drvr.GetName() != DRVR_PG:
 317            opt_list["SPATIAL_INDEX"] = 'SPATIAL_INDEX=YES'
 318
 319    return opt_list
 320
 321
 322def copy_layer_gdal_to_ds_gdal(layer_src, ds_gdal_dest, nom_layer=None, nom_geom=None,
 323                               overwrite=True, **extra_opt_list):
 324    """
 325    Copia una layer_gdal a otro datasource gdal
 326
 327    Args:
 328        layer_src:
 329        ds_gdal_dest:
 330        nom_layer (str=None): OPC - Si no viene informado cogerá el nombre de la layer
 331        nom_geom (str=None): OPC - Si se informa se cogerá como el nombre de la geometria de la nueva layer
 332        overwrite (bool=True):
 333        **extra_opt_list (str): Lista claves-valores de opciones para copylayer o createLayer
 334                                del driver de ds_gdal indicado
 335
 336    Returns:
 337        layer_dest (ogr.layer)
 338    """
 339    if not nom_layer:
 340        nom_layer = layer_src.GetName()
 341    nom_layer = nom_layer.strip().lower()
 342
 343    layer_dest = ds_gdal_dest.GetLayerByName(nom_layer)
 344    if layer_dest:
 345        if not overwrite:
 346            return layer_dest
 347        else:
 348            ds_gdal_dest.DeleteLayer(nom_layer)
 349
 350    drvr_name = ds_gdal_dest.GetDriver().GetName()
 351
 352    extra_opt_list["IDENTIFIER"] = "IDENTIFIER={}".format(nom_layer)
 353
 354    lyr_src_has_geom = bool(layer_src.GetGeometryColumn() or nom_geom)
 355
 356    opt_list = set_create_option_list_for_layer_gdal(
 357        layer_src, drvr_name=drvr_name, has_geom=lyr_src_has_geom,
 358        **{str(k).upper(): str(v).upper() for k, v in extra_opt_list.items()})
 359
 360    layer_dest = ds_gdal_dest.CopyLayer(layer_src, nom_layer, list(opt_list.values()))
 361
 362    if layer_dest:
 363        layer_dest_def = layer_dest.GetLayerDefn()
 364        if lyr_src_has_geom and not layer_dest.GetGeometryColumn() and get_geom_field_defn(layer_dest_def):
 365            if not nom_geom:
 366                nom_geom = layer_src.GetGeometryColumn()
 367            else:
 368                nom_geom = nom_geom.upper()
 369
 370            get_geom_field_defn(layer_dest_def).SetName(nom_geom)
 371
 372        if drvr_name == DRVR_GPKG and lyr_src_has_geom:
 373            create_spatial_index_layer_gpkg(ds_gdal_dest, nom_layer)
 374
 375    return layer_dest
 376
 377
 378def layer_gtype_from_geoms(layer_gdal, nom_geom=None):
 379    """
 380    A partir de la 1º geometria informada de una layer_gdal devuelve el tipo de geometria que es. Si no encuentra
 381    devuelve el geom_type de la layer (layer_gdal.GetGeomType())
 382
 383    Args:
 384        layer_gdal (osgeo.ogr.Layer):
 385        nom_geom (str=None): Nombre geometria
 386
 387    Returns:
 388        geom_type (int=layer_gdal.GetGeomType()): Si no encuentra devuelve 0 por defecto (GEOMETRY)
 389    """
 390    idx_geom = 0
 391    if nom_geom:
 392        idx_geom = layer_gdal.GetLayerDefn().GetGeomFieldIndex(nom_geom)
 393
 394    if idx_geom >= 0:
 395        layer_gdal.ResetReading()
 396        return next((f.GetGeomFieldRef(idx_geom).GetGeometryType()
 397                     for f in layer_gdal if f.GetGeomFieldRef(idx_geom)),
 398                    layer_gdal.GetGeomType())
 399
 400
 401def srs_for_layer(layer_src, nom_geom_src=None):
 402    """
 403    Devuelve el SpatialReference de la layer_gdal
 404    Args:
 405        layer_src (ogr.Layer)
 406        nom_geom_src (str=None): Nombre geometria
 407
 408    Returns:
 409        srs_lyr_src (osgeo.osr.SpatialReference)
 410    """
 411    srs_lyr_src = layer_src.GetSpatialRef()
 412    layer_src_def = layer_src.GetLayerDefn()
 413
 414    if act_geom_field := get_geom_field_defn(layer_src_def):
 415        if nom_geom_src:
 416            idx_act_geom_field = layer_src_def.GetGeomFieldIndex(nom_geom_src)
 417            if idx_act_geom_field >= 0:
 418                act_geom_field = layer_src_def.GetGeomFieldDefn(idx_act_geom_field)
 419
 420        if act_geom_field.GetSpatialRef():
 421            srs_lyr_src = act_geom_field.GetSpatialRef()
 422
 423    return srs_lyr_src
 424
 425
 426def create_layer_from_layer_gdal_on_ds_gdal(ds_gdal_dest, layer_src, nom_layer=None, nom_geom_src=None, sel_camps=None,
 427                                            exclude_cols_geoms=True, tolerance_simplify=None, null_geoms=False,
 428                                            gtype_layer_from_geoms=True, epsg_code_dest=None,
 429                                            epsg_code_src_default=4326, old_axis_mapping_srs=None, **extra_opt_list):
 430    """
 431    Crea nuevo layer a partir de layer_gdal.
 432
 433    Args:
 434        ds_gdal_dest (ogr.DataSource):
 435        layer_src (ogr.Layer):
 436        nom_layer (str=None):
 437        nom_geom_src (str=None): Si el nombre no se corresponde con ninguna de las geometrias de la layer_src se utilizará
 438                            como ALIAS de la geometria 0 (la defecto) de la nueva layer resultante
 439        sel_camps (list=None): OPC - Lista de campos a escoger de la layer original
 440        exclude_cols_geoms (bool=True): Por defecto de la lista de columnas alfanuméricas (no geometrias) excluirá las
 441                                       columnas que hagan referencia a alguna de las geometrias
 442        tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer
 443             Mirar método Simplify() sobre osgeo.ogr.Geometry
 444        null_geoms (bool=False): Por defecto no grabará las filas que la geometria principal (nom_geom) es nula
 445        gtype_layer_from_geoms (bool=True): Por defecto, si gtype de layer origen == 0 (GEOMETRY) deducirá el tipo de
 446                                geometria (POINT, LINE o POLYGON) a partir de la primera informada
 447                                encontrada en la layer_origen
 448        epsg_code_dest (int=None): Codigo EPSG para le que se transformarán las geometrias desde el SRS original
 449        epsg_code_src_default (int=4326): Codigo EPSG que se usará para las layer_src que NO tengan SRS asignado
 450        old_axis_mapping_srs (bool=None): setea si quieres usar old mapping axes en (LONG, LAT) (GDAL >3.2)  
 451        **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
 452
 453    Returns:
 454        layer_out (ogr.Layer)
 455    """
 456    desc_ds_gdal = ds_gdal_dest.GetDescription()
 457    drvr_name = ds_gdal_dest.GetDriver().GetName()
 458    print_debug("Inici crear layer '{}' en ds_gdal '{}'".format(
 459        (nom_layer if nom_layer else layer_src.GetName()).upper(),
 460        desc_ds_gdal if desc_ds_gdal else drvr_name))
 461
 462    geoms_src = geoms_layer_gdal(layer_src)
 463    camps_src = cols_layer_gdal(layer_src)
 464
 465    if nom_geom_src:
 466        nom_geom_src = nom_geom_src.upper()
 467        if len(geoms_src) > 1:
 468            if nom_geom_src not in map(lambda ng: ng.upper(), geoms_src):
 469                raise Exception("Argumento :NOM_GEOM = '{}' erróneo ya que "
 470                                "LAYER_GDAL original no contiene dicha geometria".format(nom_geom_src))
 471        elif len(geoms_src) == 0:
 472            raise Exception("Argumento :NOM_GEOM = '{}' erróneo ya que "
 473                            "LAYER_GDAL original no contiene geometrias".format(nom_geom_src))
 474
 475    if sel_camps:
 476        sel_camps = {ng.upper() for ng in sel_camps}
 477        if not sel_camps.issubset(camps_src):
 478            raise Exception("Argumento :SEL_CAMPS = '[{}]' erróneo ya que "
 479                            "LAYER_GDAL no contiene alguno de los campos indicados".format(",".join(sel_camps)))
 480    else:
 481        sel_camps = set()
 482
 483    if not nom_layer:
 484        nom_layer = layer_src.GetName()
 485    else:
 486        nom_layer = nom_layer.strip()
 487
 488    nom_layer = nom_layer.lower()
 489
 490    gtype = layer_src.GetGeomType()
 491    srs_lyr_src = layer_src.GetSpatialRef()
 492
 493    layer_src_def = layer_src.GetLayerDefn()
 494    act_geom_field = get_geom_field_defn(layer_src_def)
 495    nom_geom_out = None
 496
 497    if act_geom_field:
 498        if nom_geom_src:
 499            idx_act_geom_field = layer_src_def.GetGeomFieldIndex(nom_geom_src)
 500            if idx_act_geom_field >= 0:
 501                act_geom_field = layer_src_def.GetGeomFieldDefn(idx_act_geom_field)
 502                nom_geom_out = fix_affix_geom_name_layer_gdal(nom_geom_src, layer_src)
 503
 504        gtype = act_geom_field.GetType()
 505        if gtype_layer_from_geoms and not gtype:
 506            gtype = layer_gtype_from_geoms(layer_src, nom_geom_src)
 507
 508        if act_geom_field.GetSpatialRef():
 509            srs_lyr_src = act_geom_field.GetSpatialRef()
 510
 511    if sel_camps:
 512        sel_camps = {c.strip().upper() for c in sel_camps}
 513
 514    geoms_src_minus_affix = geoms_layer_gdal(layer_src, PREFFIX_GEOMS_LAYERS_GDAL)
 515    if exclude_cols_geoms:
 516        if sel_camps:
 517            sel_camps.difference_update(geoms_src_minus_affix)
 518        else:
 519            sel_camps = cols_layer_gdal(layer_src).difference(geoms_src_minus_affix)
 520
 521    srs_lyr_dest = None
 522    if not srs_lyr_src and epsg_code_src_default:
 523        srs_lyr_src = srs_ref_from_epsg_code(epsg_code_src_default, old_axis_mapping=old_axis_mapping_srs)
 524    if srs_lyr_src:
 525        if old_axis_mapping_srs is None and epsg_code_dest == 4326:
 526            old_axis_mapping_srs = drvr_name in DRIVERS_OLD_SRS_AXIS_MAPPING_4326
 527
 528        if epsg_code_dest:
 529            srs_lyr_dest = srs_ref_from_epsg_code(epsg_code_dest, old_axis_mapping=old_axis_mapping_srs)
 530        else:
 531            srs_lyr_dest = srs_lyr_src
 532
 533    if ds_gdal_dest.TestCapability(ODsCCreateLayer):
 534        # To avoid message "Warning"
 535        if ds_gdal_dest.TestCapability(ODsCDeleteLayer) and ds_gdal_dest.GetLayerByName(nom_layer):
 536            ds_gdal_dest.DeleteLayer(nom_layer)
 537
 538        layer_out, nom_layer = create_layer_on_ds_gdal(ds_gdal_dest, nom_layer, nom_geom_out, gtype, srs_lyr_dest,
 539                                                       **extra_opt_list)
 540    else:
 541        layer_out = ds_gdal_dest.GetLayer(0)
 542
 543    geom_field_out = None
 544    if layer_out.TestCapability(OLCAlterFieldDefn):
 545        layer_out_def = layer_out.GetLayerDefn()
 546        geom_field_out = get_geom_field_defn(layer_out_def)
 547        if geom_field_out:
 548            nom_geom_field_ref = act_geom_field.GetNameRef() if act_geom_field else None
 549            if not nom_geom_out:
 550                nom_geom_out = nom_geom_field_ref
 551                if not nom_geom_out:
 552                    nom_geom_out = "GEOMETRY"
 553            if not nom_geom_field_ref or nom_geom_field_ref.lower() != nom_geom_out.lower():
 554                geom_field_out.SetName(nom_geom_out)
 555
 556    if layer_out.TestCapability(OLCCreateField):
 557        for fd in fields_layer_gdal(layer_src):
 558            nom_fd = fd.GetNameRef().upper()
 559            if layer_out.FindFieldIndex(nom_fd, True) < 0 and (not sel_camps or nom_fd in sel_camps) and \
 560                    nom_fd not in (gn.upper() for gn in geoms_src_minus_affix):
 561                layer_out.CreateField(fd)
 562
 563        for gfd in geom_fields_layer_gdal(layer_src):
 564            nom_gfd = fix_affix_geom_name_layer_gdal(gfd.GetNameRef(), layer_src).upper()
 565            if (not sel_camps or nom_gfd in sel_camps) and \
 566                    (nom_gfd not in geoms_src or not exclude_cols_geoms) and \
 567                    nom_gfd != nom_geom_out:
 568                gfd_def_src = layer_src_def.GetGeomFieldDefn(layer_src_def.GetGeomFieldIndex(gfd.GetNameRef()))
 569                gfd_def_dest = GeomFieldDefn(nom_gfd, gfd_def_src.GetType())
 570                if srs_lyr_dest:
 571                    gfd_def_dest.SetSpatialRef(srs_lyr_dest)
 572                layer_out.CreateGeomField(gfd_def_dest)
 573
 574    # Si capa ALFA se fuerza a crear la capa aunque no tenga geometría
 575    if not nom_geom_out and not nom_geom_src:
 576        null_geoms = True  # Si no hay geom siempre se añaden
 577
 578    add_layer_features_to_layer(layer_src, ds_gdal_dest, layer_out, nom_geom_src, nom_geom_out,
 579                                srs_lyr_dest=srs_lyr_dest,
 580                                null_geoms=null_geoms,
 581                                tolerance_simplify=tolerance_simplify,
 582                                epsg_code_src_default=epsg_code_src_default,
 583                                old_axis_mapping_srs=old_axis_mapping_srs)
 584
 585    if drvr_name == DRVR_GPKG:
 586        create_spatial_index_layer_gpkg(ds_gdal_dest, nom_layer)
 587
 588    if drvr_name == DRVR_CSV:
 589        path_csv = ds_gdal_dest.GetName()
 590        if os.path.exists(path_csv):
 591            rename_wkt_geoms_csv(path_csv)
 592
 593    return ds_gdal_dest.GetLayerByName(nom_layer)
 594
 595
 596def rename_wkt_geoms_csv(path_csv):
 597    """
 598    Remove "_WKT" affix from WKT geometry fields in CSV file
 599
 600    Args:
 601        path_csv (str): Path to CSV file
 602
 603    Returns:
 604
 605    """
 606    with open(path_csv, 'r', encoding='utf-8') as r_file:
 607        content_csv = r_file.readlines()
 608        first_line = next(iter(content_csv), None)
 609    if first_line:
 610        new_first_line = first_line.replace(PREFFIX_GEOMS_LAYERS_GDAL_CSV, '')
 611        if new_first_line != first_line:
 612            content_csv[0] = new_first_line
 613            with open(path_csv, 'w', encoding='utf-8') as w_file:
 614                w_file.writelines(content_csv)
 615
 616
 617def add_layer_features_to_layer(layer_src, ds_gdal_dest, layer_dest, nom_geom_src=None, nom_geom_dest=None,
 618                                srs_lyr_dest=None, null_geoms=False, tolerance_simplify=None,
 619                                remove_prev_features=False, epsg_code_src_default=4326, old_axis_mapping_srs=None):
 620    """
 621    From a layer of a dataset, add the features to another layer of another dataset.
 622
 623    Args:
 624        layer_src (ogr.Layer): Layer origen
 625        ds_gdal_dest (gdal.Dataset): Dataset destino
 626        layer_dest (ogr.Layer): Layer destino
 627        nom_geom_src (str=None): Si el nombre no se corresponde con ninguna de las geometrias de la layer_src se utilizará
 628                            como ALIAS de la geometria 0 (la defecto) de la nueva layer resultante
 629        nom_geom_dest (str):
 630        srs_lyr_dest (ogr.SpatialReference=None): Spatial Reference System de la layer_dest
 631        null_geoms (bool=False): Por defecto no grabará las filas que la geometria principal (nom_geom) es nula
 632        tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer
 633             Mirar método Simplify() sobre osgeo.ogr.Geometry
 634        remove_prev_features (bool=False): Si es True, se eliminarán las features previas de la layer_dest
 635        epsg_code_src_default (int=4326): EPSG code del srs de la layer_src si no se encuentra
 636        old_axis_mapping_srs (bool=None): setea si quieres usar old mapping axes en (LONG, LAT) (GDAL >3.2)
 637
 638    Returns:
 639
 640    """
 641    geoms_out = geoms_layer_gdal(layer_dest)
 642    cols_out = cols_layer_gdal(layer_dest)
 643
 644    geom_transform = None
 645    srs_lyr_src = srs_for_layer(layer_src, nom_geom_src)
 646    if not srs_lyr_src:
 647        srs_lyr_src = srs_ref_from_epsg_code(epsg_code_src_default, old_axis_mapping_srs)
 648
 649    if srs_lyr_dest is None:
 650        srs_lyr_dest = srs_for_layer(layer_dest, nom_geom_dest)
 651
 652    if srs_lyr_dest and not srs_lyr_src.IsSame(srs_lyr_dest):
 653        geom_transform = osr.CoordinateTransformation(srs_lyr_src, srs_lyr_dest)
 654
 655    ds_trans = ds_gdal_dest.TestCapability(ODsCTransactions)
 656
 657    if remove_prev_features:
 658        if ds_trans:
 659            layer_dest.StartTransaction()
 660        for feat in layer_dest:
 661            layer_dest.DeleteFeature(feat.GetFID())
 662        if ds_trans:
 663            layer_dest.CommitTransaction()
 664
 665    if ds_trans:
 666        layer_dest.StartTransaction()
 667
 668    i = 0
 669    for feat_src, geom_src, nt_src in feats_layer_gdal(layer_src, nom_geom_src):
 670        cols_out_chk = [col.upper() for col in cols_out.union(geoms_out)]
 671        vals_camps = {nc: val for nc, val in nt_src._asdict().items()
 672                      if nc.upper() in cols_out_chk}
 673        if null_geoms or geom_src:
 674            if nom_geom_dest:
 675                vals_camps[nom_geom_dest.upper()] = geom_src
 676
 677            add_feature_to_layer_gdal(layer_dest,
 678                                      tolerance_simplify=tolerance_simplify,
 679                                      geom_trans=geom_transform,
 680                                      **vals_camps)
 681
 682            if i > 0 and (i % 1000) == 0:
 683                print_debug("{} registres tractats...".format(str(i)))
 684            i += 1
 685    if ds_trans:
 686        layer_dest.CommitTransaction()
 687
 688
 689def create_layer_on_ds_gdal(ds_gdal_dest, nom_layer, nom_geom=None, gtype=None, srs_lyr_out=None, **extra_opt_list):
 690    """
 691
 692    Args:
 693        ds_gdal_dest (ogr.DataSource):
 694        nom_layer (str):
 695        nom_geom (str=None):
 696        gtype (OGRwkbGeometryType=None): Indicar integer representativo del tipo de geometria de la layer (ogr.wkbPoint, ogr.wkbPolygon, ogr.LineString, ...)
 697        srs_lyr_out (osr.SpatialReference=None): codigo epsg del sistema de coordenadas de la geometria
 698        **extra_opt_list: Calves valores option list creacion layer gdal
 699
 700    Returns:
 701        layer_gdal (ogr.Layer), nom_layer (str)
 702    """
 703    drvr_name = ds_gdal_dest.GetDriver().GetName().upper()
 704
 705    opt_list = {k.upper(): v.upper() for k, v in extra_opt_list.items()}
 706
 707    opt_geom_name = "GEOMETRY_NAME"
 708    if nom_geom:
 709        opt_list[opt_geom_name] = "{}={}".format(opt_geom_name, nom_geom.upper())
 710    else:
 711        if opt_geom_name in opt_list:
 712            opt_list.pop(opt_geom_name)
 713        gtype = ogr.wkbNone
 714
 715    opt_list["IDENTIFIER"] = opt_list.get("IDENTIFIER", "IDENTIFIER={}".format(nom_layer))
 716
 717    opt_list = set_create_option_list_for_driver_gdal(drvr_name, **{k.upper(): v.upper() for k, v in opt_list.items()})
 718
 719    if ds_gdal_dest.TestCapability(ODsCDeleteLayer) and ds_gdal_dest.GetLayerByName(nom_layer):
 720        print_warning("!ATENCION! - Se sobreescribirá la layer '{}' sobre el datasource GDAL '{}'".format(
 721            nom_layer, ds_gdal_dest.GetDescription()))
 722        ds_gdal_dest.DeleteLayer(nom_layer)
 723
 724    if drvr_name.upper() == DRVR_KML:
 725        nom_layer = nom_layer.replace("-", "__")
 726
 727    layer_out = ds_gdal_dest.CreateLayer(nom_layer, srs_lyr_out, geom_type=gtype, options=list(opt_list.values()))
 728
 729    return layer_out, layer_out.GetName() if layer_out else None
 730
 731
 732def create_spatial_index_layer_gpkg(ds_gpkg, nom_layer):
 733    """
 734    Crea spatial index sobre una layer (layer_gpkg) de un datosource gpkg (ds_gpkg)
 735
 736    Args:
 737        ds_gpkg (osgeo.ogr.DataSource):
 738        layer_gpkg (ogr.Layer):
 739
 740    Returns:
 741        bool
 742    """
 743    layer_gpkg = ds_gpkg.GetLayerByName(nom_layer)
 744    if layer_gpkg and (geom_name := layer_gpkg.GetGeometryColumn()):
 745        tab_name = layer_gpkg.GetName()
 746        res = ds_gpkg.ExecuteSQL(f"SELECT HasSpatialIndex('{tab_name}', '{geom_name}');")
 747        if not bool(res and res.GetNextFeature().GetField(0) == 1):
 748            # Se crea spatial_index ya que GDAL NO lo hace
 749            ds_gpkg.StartTransaction()
 750            ds_gpkg.ExecuteSQL(f"SELECT CreateSpatialIndex('{tab_name}', '{geom_name}');")
 751            ds_gpkg.CommitTransaction()
 752
 753        return True
 754    else:
 755        return False
 756
 757
 758def add_feature_to_layer_gdal(layer_gdal, tolerance_simplify=None, geom_trans=None, commit=False, **valors_camps):
 759    """
 760
 761    Args:
 762        layer_gdal (ogr.Layer):
 763        tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer.
 764                                        Mirar método Simplify() sobre osgeo.ogr.Geometry
 765        geom_trans (osr.CoordinateTransformation=None): transformacion para convertir las geometrias a otro SRS
 766        commit (bool=False): Per defecte no farà commit de la transaccio
 767        **valors_camps: pares nombre_campo=valor de la feature a crear
 768
 769    Returns:
 770        feat (ogr.Feature)
 771    """
 772    dd_feat = ogr.Feature(layer_gdal.GetLayerDefn())
 773
 774    for camp, val in valors_camps.items():
 775        idx_geom = dd_feat.GetGeomFieldIndex(camp)
 776        es_geom = idx_geom >= 0
 777        if es_geom:
 778            if val:
 779                new_val = None
 780                if tolerance_simplify:
 781                    new_val = val.Simplify(tolerance_simplify)
 782                if val and geom_trans:
 783                    new_val = val.Transform(geom_trans)
 784                val = new_val or val
 785
 786            dd_feat.SetGeomField(idx_geom, val)
 787
 788        idx_fld = dd_feat.GetFieldIndex(camp)
 789        if idx_fld >= 0:
 790            if val:
 791                if es_geom or hasattr(val, "ExportToIsoWkt"):
 792                    val = val.ExportToIsoWkt()
 793                dd_feat.SetField(camp, val)
 794            else:
 795                dd_feat.SetFieldNull(camp)
 796
 797        if idx_fld < 0 and idx_geom < 0:
 798            if isinstance(val, Geometry):
 799                if geom_trans:
 800                    val.Transform(geom_trans)
 801                dd_feat.SetGeometry(val)
 802            elif val:
 803                print_warning("!ATENCION! - La :layer_gdal no contiene el campo '{}'".format(camp))
 804
 805    commit_trans = commit and layer_gdal.TestCapability(OLCTransactions)
 806    if commit_trans:
 807        layer_gdal.StartTransaction()
 808
 809    new_feat = layer_gdal.CreateFeature(dd_feat)
 810
 811    if commit_trans:
 812        layer_gdal.CommitTransaction()
 813
 814    return new_feat
 815
 816
 817def drivers_ogr_gdal_disponibles():
 818    """
 819    Retorna lista de drivers disponibles a través de la librería osgeo-gdal-ogr
 820
 821    Returns:
 822        dict
 823    """
 824    cnt = ogr.GetDriverCount()
 825    driver_list = []
 826    drivers = OrderedDict()
 827
 828    for i in range(cnt):
 829        driver = ogr.GetDriver(i)
 830        driver_name = driver.GetName()
 831        driver_list.append(driver_name)
 832
 833    for driver_name in driver_list:
 834        # Is File GeoDatabase available?
 835        drv = ogr.GetDriverByName(driver_name)
 836        if drv is None:
 837            print_warning("{} !!ATENTION - driver NOT available!!".format(driver_name))
 838        else:
 839            drivers[driver_name] = drv
 840            print_debug(driver_name)
 841
 842    return drivers
 843
 844
 845def drivers_ogr_gdal_vector_file():
 846    """
 847    Devuelve diccionario con los driver gdal para fichero vectorial
 848
 849    Returns:
 850        dict
 851    """
 852    result = {}
 853    for nd, d in drivers_ogr_gdal_disponibles().items():
 854        try:
 855            # Try GetMetadata_Dict first (older GDAL versions)
 856            if hasattr(d, "GetMetadata_Dict"):
 857                metadata = d.GetMetadata_Dict()
 858            else:
 859                # Fall back to GetMetadata for GDAL 3.10+
 860                metadata = d.GetMetadata()
 861
 862            if metadata and metadata.get('DMD_EXTENSIONS'):
 863                result[nd] = d
 864        except Exception:
 865            # Skip drivers that have issues
 866            continue
 867
 868    return result
 869
 870
 871def format_nom_column(nom_col, case_format=None):
 872    """
 873    Retorna el nombre de columna formateado para usar como campo de un namedtuple (quita espacios y los reemplaza por "_").
 874
 875    Args:
 876        nom_col (str):
 877        case_format (str|None): formato de la columna. Si 'upper' or 'lower'. Por defecto tal como viene indicado
 878
 879    Returns:
 880        str
 881    """
 882    nom_col_res = nom_col.replace(" ", "_")
 883    if case_format:
 884        if (case_format := case_format.lower()) == 'upper':
 885            nom_col_res = nom_col_res.upper()
 886        elif case_format == 'lower':
 887            nom_col_res = nom_col_res.lower()
 888
 889    return nom_col_res
 890
 891
 892def namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld=None, case_fields_nt=None):
 893    """
 894    Devuelve namedTuple con los campos del layer pasado por parametro
 895
 896    Args:
 897        layer_gdal:
 898        extract_affix_geom_fld (str=None):
 899        case_fields_nt (str|None): formato de las columnas de NamedTuple retornado. Si 'upper' or 'lower'. Por defecto tal como vienen de la FeatureLayer
 900
 901    Returns:
 902        namedtuple: con nombre "gdalFeatDef_{NOM_LAYER}" y con los campos de la layer
 903    """
 904    camps_layer = set()
 905    for fld in fields_layer_gdal(layer_gdal):
 906        camps_layer.add(format_nom_column(fld.GetNameRef(), case_format=case_fields_nt))
 907
 908    for nom_geom in geoms_layer_gdal(layer_gdal, extract_affix=extract_affix_geom_fld):
 909        camps_layer.add(format_nom_column(nom_geom, case_format=case_fields_nt))
 910
 911    nom_layer = layer_gdal.GetName().upper().split(".")[0].replace("-", "_")
 912    return namedtuple(f"gdalFeatDef_{nom_layer}", camps_layer)
 913
 914
 915def feats_layer_ds_gdal(ds_gdal, nom_layer=None, filter_sql=None, case_fields_nt=None):
 916    """
 917    Itera las features (registros de una layer de gdal) y los devuelve como un namdetuple
 918
 919    Args:
 920        ds_gdal: datasource gdal
 921        nom_layer (str=None): Si no viene informado cogerá la primera layer que encuentre en el datasource
 922        filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada.
 923                            Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
 924        case_fields_nt (str|None): formato de las columnas de NamedTuple retornado. Si 'upper' or 'lower'. Por defecto tal como vienen de la FeatureLayer
 925
 926    Returns:
 927        ogr.Feature, ogr.Geometry, namedtuple_layer_gdal
 928    """
 929    if not nom_layer:
 930        layer_gdal = ds_gdal.GetLayer()
 931    else:
 932        layer_gdal = ds_gdal.GetLayerByName(nom_layer)
 933
 934    if layer_gdal:
 935        for feat, geom, vals in feats_layer_gdal(layer_gdal, filter_sql=filter_sql, case_fields_nt=case_fields_nt):
 936            yield feat, geom, vals
 937    else:
 938        raise ValueError(f"'{nom_layer}' is not a valid layer over DataSource GDAL '{ds_gdal.GetName()}'")
 939
 940
 941def feats_layer_gdal(layer_gdal, nom_geom=None, filter_sql=None, extract_affix_geom_fld=PREFFIX_GEOMS_LAYERS_GDAL,
 942                     case_fields_nt=None):
 943    """
 944    Itera las features (registros de una layer de gdal) y los devuelve como un namedtuple
 945
 946    Args:
 947        layer_gdal (ogr.Layer):
 948        nom_geom (str=None): Por defecto la geometria activa o principal
 949        filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada.
 950                            Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
 951        extract_affix_geom_fld (str=SUFFIX_GEOMS_LAYERS_GDAL): Por defecto quita el sufijo 'geom_' a los campos geom
 952        case_fields_nt (str=None): si 'upper' los campos del NamedTuple retornado estarán en UPPERCASE y si 'lower' al contrario.
 953                        Por defecto tal como vienen de la FeatureLayer
 954
 955    Returns:
 956        ogr.Feature, ogr.Geometry, namedtuple_layer_gdal
 957    """
 958    layer_gdal.ResetReading()
 959    ntup_layer = namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld, case_fields_nt=case_fields_nt)
 960    n_geoms_layer = {nom_geom_feat: fix_affix_geom_name_layer_gdal(nom_geom_feat, layer_gdal, extract_affix_geom_fld)
 961                     for nom_geom_feat in geoms_layer_gdal(layer_gdal)}
 962
 963    if filter_sql:
 964        layer_gdal.SetAttributeFilter(filter_sql)
 965
 966    def vals_feature_gdal(feat_gdal):
 967        vals = {}
 968        feat_items = feat_gdal.items()
 969        # In GDAL 3.x, items() returns a dict
 970        if isinstance(feat_items, dict):
 971            for camp, val in feat_items.items():
 972                vals[format_nom_column(camp, case_format=case_fields_nt)] = val
 973        else:
 974            # Older GDAL versions may return different structure
 975            for camp, val in feat_items:
 976                vals[format_nom_column(camp, case_format=case_fields_nt)] = val
 977
 978        for camp_geom, camp_geom_val in n_geoms_layer.items():
 979            idx_geom = feat_gdal.GetGeomFieldIndex(camp_geom)
 980            if idx_geom >= 0:
 981                vals[format_nom_column(camp_geom_val, case_format=case_fields_nt)] = feat_gdal.GetGeomFieldRef(idx_geom)
 982
 983        return vals
 984
 985    if layer_gdal:
 986        for f_tab in layer_gdal:
 987            idx_geom = f_tab.GetGeomFieldIndex(nom_geom) if nom_geom else -1
 988            yield f_tab, \
 989                f_tab.GetGeomFieldRef(idx_geom) if idx_geom >= 0 else f_tab.geometry(), \
 990                ntup_layer(**vals_feature_gdal(f_tab))
 991
 992        layer_gdal.ResetReading()
 993
 994
 995def distinct_vals_camp_layer_gdal(layer_gdal, nom_camp, filter_sql=None):
 996    """
 997    Devuelve set con distintos valores para el campo indicado del layer GDAL indicada
 998
 999    Args:
1000        layer_gdal (ogr.Layer):
1001        nom_camp (str):
1002        filter_sql (str=None):
1003
1004    Returns:
1005        set
1006    """
1007    if nom_camp.upper() not in cols_layer_gdal(layer_gdal):
1008        raise Exception("Argumento :NOM_CAMP = '{}' erróneo ya que "
1009                        "LAYER_GDAL no contiene el campo indicado".format(nom_camp))
1010
1011    return {getattr(nt_feat, nom_camp.upper())
1012            for feat, geom, nt_feat in feats_layer_gdal(layer_gdal, filter_sql=filter_sql)}
1013
1014
1015def fields_layer_gdal(layer_gdal):
1016    """
1017    Itera sobre los FieldDefn de una layer gdal
1018
1019    Args:
1020        layer_gdal:
1021
1022    Yields:
1023        osgeo.ogr.FieldDefn
1024    """
1025    layer_def = layer_gdal.GetLayerDefn()
1026    for i in range(0, layer_def.GetFieldCount()):
1027        yield layer_def.GetFieldDefn(i)
1028
1029    layer_def = None
1030
1031
1032def geom_fields_layer_gdal(layer_gdal):
1033    """
1034    Itera sobre los GeomFieldDefn de una layer gdal
1035
1036    Args:
1037        layer_gdal:
1038
1039    Yields:
1040        osgeo.ogr.GeomFieldDefn
1041    """
1042    layer_def = layer_gdal.GetLayerDefn()
1043    for i in range(0, layer_def.GetGeomFieldCount()):
1044        yield layer_def.GetGeomFieldDefn(i)
1045
1046    layer_def = None
1047
1048
1049def nom_layers_datasource_gdal(ds_gdal):
1050    """
1051    Retorna lista con los nombres de las layers de un datasource gdal.
1052    CACHEA esos nombres por DS Name para optimizar llamadas repetidas.
1053
1054    Args:
1055        ds_gdal (ogr.Datasource):
1056
1057    Returns:
1058        set
1059    """
1060    if not ds_gdal:
1061        return set()
1062
1063    ds_name = ds_gdal.GetName() if hasattr(ds_gdal, "GetName") else None
1064    layer_count = ds_gdal.GetLayerCount() if hasattr(ds_gdal, "GetLayerCount") else None
1065
1066    if ds_name and ds_name in _CACHE_NOM_LAYERS_DS_GDAL:
1067        cached = _CACHE_NOM_LAYERS_DS_GDAL[ds_name]
1068        if layer_count is None or cached.get("count") == layer_count:
1069            # Devolvemos copia para evitar mutaciones externas de la caché
1070            return set(cached.get("layers", set()))
1071
1072    layers = {l.GetName() for l in ds_gdal}
1073
1074    # Solo cacheamos cuando existe identificador estable de datasource
1075    if ds_name:
1076        _CACHE_NOM_LAYERS_DS_GDAL[ds_name] = {
1077            "count": layer_count,
1078            "layers": set(layers)
1079        }
1080
1081    return layers
1082
1083
1084def cols_layer_gdal(layer_gdal):
1085    """
1086    Retorna lista con las columnas de una layer gdal
1087
1088    Args:
1089        layer_gdal:
1090
1091    Returns:
1092        set
1093    """
1094    camps = set()
1095    for fd in fields_layer_gdal(layer_gdal):
1096        # camps.add(fd.GetName().upper())
1097        camps.add(fd.GetNameRef())
1098
1099    return camps
1100
1101
1102def geoms_layer_gdal(layer_gdal, extract_affix=None):
1103    """
1104    Retorna lista con las columnas geométricas de una layer gdal
1105
1106    Args:
1107        layer_gdal:
1108        extract_affix (str=None=): if informed extract the affix passed
1109
1110    Returns:
1111        set
1112    """
1113    camps_geom = set()
1114    for gdf in geom_fields_layer_gdal(layer_gdal):
1115        # camps_geom.add(gdf.GetName().upper())
1116        name_ref = gdf.GetNameRef()
1117        if extract_affix:
1118            name_ref = fix_affix_geom_name_layer_gdal(name_ref, layer_gdal, extract_affix)
1119        camps_geom.add(name_ref)
1120
1121    return camps_geom
1122
1123
1124def fix_affix_geom_name_layer_gdal(geom_name, layer_gdal, affix=PREFFIX_GEOMS_LAYERS_GDAL):
1125    """
1126    Extract affix (default=PREFFIX_GEOMS_LAYERS_GDAL) from name geoms if correspond with other column
1127    Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'
1128
1129    Args:
1130          geom_name (str):
1131          layer_gdal:
1132          affix (str=PREFFIX_GEOMS_LAYERS_GDAL):
1133
1134    Returns:
1135        str
1136    """
1137    cols_layer = cols_layer_gdal(layer_gdal)
1138    geom_name_layer = geom_name
1139    if geom_name.lower().startswith(affix):
1140        geom_name_aux = re.sub(affix, '', geom_name, flags=re.IGNORECASE)
1141        if geom_name_aux.lower() in (col.lower() for col in cols_layer):
1142            geom_name_layer = geom_name_aux
1143
1144    return geom_name_layer
1145
1146
1147def add_layer_gdal_to_ds_gdal(ds_gdal, layer_gdal, nom_layer=None, lite=False, srs_epsg_code=None, multi_geom=False,
1148                              nom_geom=None, null_geoms=False, **extra_opt_list):
1149    """
1150    Añade una layer_gdal a un datasource_gdal. Si es una layer con multigeometrias las separa en una layer por geometria
1151
1152    Args:
1153        ds_gdal (osgeo.ogr.Datasource):
1154        layer_gdal (osgeo.ogr.Layer):
1155        nom_layer (str=None):
1156        lite (bool=False):
1157        srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir
1158                                  las geometrias
1159        nom_geom (str=None): nombre de geometria de layer origen (layer_gdal) que se copiará, si no todas
1160        multi_geom (bool=False): Si el DS_GDAL destino permite multigeometria
1161        null_geoms (bool=False): Indica si se admitirán registros con NULL geoms. Por defecto NO
1162        **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
1163
1164    Returns:
1165        new_layers_ds_gdal (list)
1166    """
1167    new_layers_ds_gdal = []
1168
1169    if not nom_layer:
1170        nom_layer = layer_gdal.GetName()
1171
1172    geoms_layer = geoms_layer_gdal(layer_gdal)
1173    if nom_geom:
1174        if nom_geom.upper() not in (g.upper() for g in geoms_layer):
1175            Exception("!ERROR! - Nombre de geometria '{}' no existe en la layer GDAL origen")
1176        else:
1177            geoms_layer = (nom_geom,)
1178
1179    if geoms_layer:
1180        tol = None
1181        if lite:
1182            tol = SIMPLIFY_TOLERANCE
1183
1184        nom_layer_base = nom_layer.split("-")[0]
1185        if not multi_geom:
1186            for geom_name in geoms_layer:
1187                # Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'
1188                geom_name_layer = fix_affix_geom_name_layer_gdal(geom_name, layer_gdal)
1189
1190                nom_layer = "{}-{}".format(nom_layer_base, geom_name_layer).lower()
1191                extra_opt_list["GEOMETRY_NAME"] = "GEOMETRY_NAME={}".format(geom_name_layer)
1192                lyr = create_layer_from_layer_gdal_on_ds_gdal(ds_gdal, layer_gdal, nom_layer, geom_name,
1193                                                              tolerance_simplify=tol, null_geoms=null_geoms,
1194                                                              epsg_code_dest=srs_epsg_code, **extra_opt_list)
1195                new_layers_ds_gdal.append(lyr)
1196        else:
1197            lyr = create_layer_from_layer_gdal_on_ds_gdal(ds_gdal, layer_gdal, nom_layer, exclude_cols_geoms=False,
1198                                                          tolerance_simplify=tol, null_geoms=True,
1199                                                          epsg_code_dest=srs_epsg_code, **extra_opt_list)
1200            new_layers_ds_gdal.append(lyr)
1201    else:
1202        if ds_gdal.GetDriver().GetName() == DRVR_PG:
1203            extra_opt_list.update(dict(
1204                precision=extra_opt_list.get('precision', 'PRECISION=NO')
1205            ))
1206
1207        lyr = copy_layer_gdal_to_ds_gdal(layer_gdal, ds_gdal, nom_layer.lower(), **extra_opt_list)
1208        new_layers_ds_gdal.append(lyr)
1209
1210    return new_layers_ds_gdal
1211
1212
1213def copy_layers_gpkg(ds_gpkg, driver, dir_base, lite=False, srs_epsg_code=None, zipped=True):
1214    """
1215
1216    Args:
1217        ds_gpkg (osgeo.ogr.Datasource):
1218        driver (str):
1219        dir_base (str=None):
1220        lite (bool=False):
1221        srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir
1222                                  las geometrias
1223        zipped (bool=False):
1224
1225    Returns:
1226        num_layers (int)
1227    """
1228    num_layers = 0
1229    subdir_drvr = os.path.normpath(os.path.join(dir_base, driver.upper()))
1230    utils.create_dir(subdir_drvr)
1231
1232    for layer_gpkg in (ds_gpkg.GetLayer(id_lyr) for id_lyr in range(ds_gpkg.GetLayerCount() - 1)):
1233        if driver == DRVR_GPKG:
1234            nom_ds, ext = utils.split_ext_file(os.path.basename(ds_gpkg.GetName()))
1235        else:
1236            nom_ds = f"{layer_gpkg.GetName()}".lower()
1237
1238        ds_gdal, existia = datasource_gdal_vector_file(driver, nom_ds, subdir_drvr)
1239
1240        add_layer_gdal_to_ds_gdal(ds_gdal, layer_gpkg, lite=lite, srs_epsg_code=srs_epsg_code)
1241
1242        num_layers += 1
1243
1244    return num_layers
1245
1246
1247def set_csvt_for_layer_csv(path_csv, **tipus_camps):
1248    """
1249    Crea/Modifica el CSVT asociado con los tipos indicados para cada columna
1250
1251    Args:
1252        path_csv (str):
1253        **tipus_camps: clave=valor con el nombre del campo y el tipo de campo asociado (p.e. String(25), WKT, Integer,...)
1254
1255    Returns:
1256        path_csvt (str)
1257    """
1258    lyr_csv, nom_layer, ds_lyr = layer_gdal_from_file(path_csv, DRVR_CSV)
1259    if not lyr_csv:
1260        print_warning("!ATENCIO! - No s'ha pogut obrir la layer CSV '{}'".format(path_csv))
1261        return
1262
1263    path_lyr_csvt = os.path.join(os.path.dirname(path_csv), "{}.csvt".format(nom_layer))
1264    tips_lyr = {}
1265    for fld in fields_layer_gdal(lyr_csv):
1266        tip_fld = fld.GetFieldTypeName(fld.GetType())
1267        sufix = ""
1268        w = fld.GetWidth()
1269        if w:
1270            sufix = "{}".format(w)
1271        p = fld.GetPrecision()
1272        if p:
1273            sufix += ".{}".format(p)
1274        if sufix:
1275            tip_fld += "({})".format(sufix)
1276
1277        tips_lyr[fld.name.upper()] = tip_fld
1278
1279    for nom_camp, tip_camp in tipus_camps.items():
1280        nom_camp = nom_camp.upper()
1281        if nom_camp not in tips_lyr:
1282            print_warning("!ATENCIO! - Camp '{}' no existeix sobre la layer CSV '{}'".format(nom_camp, path_csv))
1283            continue
1284
1285        tips_lyr[nom_camp] = tip_camp
1286
1287    with open(path_lyr_csvt, mode="w", encoding="utf8") as f_csvt:
1288        f_csvt.write(",".join(tips_lyr.values()))
1289
1290
1291def zip_and_clean_ds_csv(path_csv):
1292    """
1293    # Zipea datasource osgeo CSV y como GDAL no crea los tipos WKT para las geometrias en el CSVT se fuerza
1294
1295    Args:
1296        path_csv (str): path Datasource osgeo CSV
1297
1298    Returns:
1299        zip_path (str)
1300    """
1301    ds_gdal_csv, dummy = datasource_gdal_vector_file(DRVR_CSV, os.path.basename(path_csv).split(".")[0],
1302                                                     os.path.dirname(path_csv))
1303    layer_csv = ds_gdal_csv.GetLayer(0)
1304    tips_geoms = {gn: "WKT" for gn in
1305                  (*geoms_layer_gdal(layer_csv, extract_affix=PREFFIX_GEOMS_LAYERS_GDAL),
1306                   *geoms_layer_gdal(layer_csv, extract_affix=PREFFIX_GEOMS_LAYERS_GDAL_CSV))}
1307    path_csv = ds_gdal_csv.GetDescription()
1308    ds_gdal_csv = None  # Cerramos el datasource para que se guarden los cambios
1309    if path_csv and os.path.exists(path_csv):
1310        if tips_geoms:
1311            set_csvt_for_layer_csv(path_csv, **tips_geoms)
1312        rename_wkt_geoms_csv(path_csv)
1313    dir_base_csv = os.path.dirname(path_csv)
1314    nom_layer = os.path.basename(path_csv).split(".")[0]
1315    l_files_csv = [os.path.join(dir_base_csv, nf) for nf in os.listdir(dir_base_csv)
1316                   if nf.lower().startswith(nom_layer.lower()) and
1317                   any(nf.lower().endswith(ext) for ext in ('csv', 'csvt'))]
1318    zip_path = utils.zip_files(os.path.join(dir_base_csv, "{}.zip".format(nom_layer)), l_files_csv)
1319    if zip_path:
1320        for fl_csv in l_files_csv:
1321            os.remove(fl_csv)
1322
1323    return zip_path
1324
1325
1326def convert_angle(pt_xy, deg_ang, orig_srs, dest_srs):
1327    """
1328
1329    Args:
1330        pt_xy (tuple):
1331        deg_ang (float):
1332        orig_srs (osr.SpatialReference):
1333        dest_srs (osr.SpatialReference):
1334
1335    Returns:
1336
1337    """
1338    trans = osr.CoordinateTransformation(orig_srs, dest_srs)
1339    x, y = pt_xy
1340    dx = math.sin(math.radians(deg_ang)) * 0.00000001
1341    dy = math.cos(math.radians(deg_ang)) * 0.00000001
1342    pt1 = ogr.CreateGeometryFromWkt("POINT ({} {})".format(x, y))
1343    pt2 = ogr.CreateGeometryFromWkt("POINT ({} {})".format(x + dx, y + dy))
1344    pt1.Transform(trans)
1345    pt2.Transform(trans)
1346    x1, y1, z1 = pt1.GetPoint()
1347    x2, y2, z2 = pt2.GetPoint()
1348
1349    return math.degrees(math.atan2(y2 - y1, x2 - x1))
1350
1351
1352def transform_ogr_geom(a_ogr_geom, from_espg_code, to_epsg_code):
1353    """
1354    Transforma una geometria OGR según los EPSG indicados
1355
1356    Args:
1357        a_ogr_geom (ogr.geometry): una geometria del tipo OGR
1358        from_espg_code (int): codigo numérico del EPSG actual para la geometria
1359        to_epsg_code (int): codigo numérico del EPSG al que se quiere transformar
1360
1361    Returns:
1362        ogr.geometry
1363    """
1364    source = osr.SpatialReference()
1365    source.ImportFromEPSG(from_espg_code)
1366
1367    target = osr.SpatialReference()
1368    target.ImportFromEPSG(to_epsg_code)
1369
1370    a_transform = osr.CoordinateTransformation(source, target)
1371    a_ogr_geom.Transform(a_transform)
1372
1373    return a_ogr_geom
1374
1375
1376def ds_postgis(pg_conn_str, schemas=None, **open_options):
1377    """
1378    Retorna datasource GDAL para ddbb postgis
1379
1380    Args:
1381        pg_conn_str: str "PG:user=... password=..."
1382        schemas:
1383        **open_options: Open options del driver PostgreSQL de GDAL
1384                        (vease https://gdal.org/en/stable/drivers/vector/pg.html#dataset-open-options)
1385                        p.e.: LIST_ALL_TABLES='YES', SKIP_VIEWS='YES', SCHEMAS='public,myschema',
1386                              TABLES='tabla1,tabla2', OGR_PG_RETRIEVE_FID='YES'
1387
1388    Returns:
1389        osgeo.ogr.DataSource
1390    """
1391    if schemas:
1392        if isinstance(schemas, str):
1393            schemas = [*schemas.split(",")]
1394
1395        active_schema = schemas[0]
1396        str_schemas = ",".join(schemas)
1397        pg_conn_str = f"{pg_conn_str} active_schema='{active_schema}' schemas='{str_schemas}'"
1398
1399    if open_options:
1400        oo_list = [f"{k.upper()}={v}" for k, v in open_options.items()]
1401        return gdal.OpenEx(pg_conn_str, gdal.OF_VECTOR | gdal.OF_UPDATE, open_options=oo_list)
1402
1403    drvr, exts = driver_gdal(DRVR_PG)
1404
1405    return drvr.Open(pg_conn_str, 1)
1406
1407
1408def pg_string_connection(dbname=None, host=None, port=None, service=None, user=None, password=None, schemas=None):
1409    """
1410    Retorna una cadena de conexion a una base de datos postgis
1411
1412    Args:
1413        dbname:
1414        host:
1415        port:
1416        service:
1417        user:
1418        password:
1419        schemas (str | list[str]=None): Esquema de la base de datos
1420
1421    Returns:
1422        str
1423    """
1424    pg_conn = "PG:"
1425    if dbname:
1426        pg_conn += f"dbname='{dbname}'"
1427    if host:
1428        pg_conn += f"host='{host}'"
1429    if port:
1430        pg_conn += f"port='{port}'"
1431    if service:
1432        pg_conn += f"service='{service}'"
1433    if user:
1434        pg_conn += f"user='{user}'"
1435    if password:
1436        pg_conn += f"password='{password}'"
1437
1438    if schemas:
1439        if isinstance(schemas, str):
1440            schemas = [*schemas.split(",")]
1441
1442        active_schema = schemas[0]
1443        str_schemas = ",".join(schemas)
1444        pg_conn_str = f"{pg_conn} active_schema='{active_schema}' schemas='{str_schemas}'"
1445
1446    return pg_conn
1447
1448
1449def get_ds_postgis(dbname=None, host=None, port=None, service=None, user=None, password=None, schemas=None,
1450                   pg_conn_str=None, logger: Logger = None, **open_options) -> ogr.DataSource:
1451    """
1452    Retorna cached datasource postgis GDAL
1453
1454    Args:
1455        dbname (str=None): Nombre de la base de datos
1456        host (str=None): Host de la base de datos
1457        port (str=None): Puerto de la base de datos
1458        service (str=None): nombre service definido en pg_service.conf (env var PGSERVICE)
1459        user (str=None): Usuario de la base de datos
1460        password (str=None): Contraseña de la base de datos
1461        schemas (str | list[str]=None): Esquema de la base de datos
1462        pg_conn_str (str=None): cadena conexion tipo "PG:user=... password=... service=...". Si viene informada no se hace caso a ningún otro argumento
1463        logger (logging.Logger=None): (opcional) logger para LOG.
1464        **open_options (str): Opciones para abrir el Datasource de Postgis. Vease https://gdal.org/en/stable/drivers/vector/pg.html#dataset-open-options
1465    Returns:
1466        datasource_gdal (osgeo.gdal.DataSource)
1467    """
1468    ds_gdal: ogr.DataSource | None = None
1469
1470    if not pg_conn_str:
1471        pg_conn_str = pg_string_connection(dbname=dbname, host=host, port=port, service=service, user=user,
1472                                           password=password)
1473    elif not pg_conn_str.startswith('PG:'):
1474        pg_conn_str = f"PG:{pg_conn_str}"
1475
1476    # Retorna la pg_conn_str con el valor despues de 'password=' substituido por '****'
1477    pg_id_ds = re.sub(r"(?i)(password\s*=\s*')([^']*)(')", r"\1****\3", pg_conn_str)
1478    
1479    if ds_gdal := _CACHE_DS_POSTGIS.get(pg_id_ds):
1480        # Antes de retornar verificar que la conexión sigue siendo correcta
1481        try:
1482            _ = ds_gdal.GetLayerCount()
1483            lyr_test = ds_gdal.ExecuteSQL("SELECT 1")
1484            if lyr_test is not None:
1485                ds_gdal.ReleaseResultSet(lyr_test)
1486            return ds_gdal
1487        except Exception:
1488            _CACHE_DS_POSTGIS.pop(pg_id_ds, None)
1489
1490    if not logger:
1491        logger = utils_logging.get_base_logger()
1492
1493    error_exc = None
1494    try:
1495        ds_gdal: ogr.DataSource = ds_postgis(
1496            pg_conn_str=pg_conn_str,
1497            schemas=schemas,
1498            **open_options)
1499    except Exception as exc:
1500        error_exc = f"Error: {exc}"
1501    finally:
1502        if ds_gdal:
1503            logger.info(f"Conectado a la base de datos POSTGIS del repositorio GIS: {pg_id_ds}")
1504        else:
1505            msg_error = f"No se ha podido conectar a la base de datos POSTGIS [{pg_id_ds}] del repositorio GIS. " \
1506                        f"{error_exc if error_exc else ''} "
1507            logger.error(msg_error)
1508            raise ConnectionError(msg_error)
1509
1510    _CACHE_DS_POSTGIS[pg_id_ds] = ds_gdal
1511
1512    return ds_gdal
1513
1514
1515def reset_sequence_layer_postgis(ds_postgis, nom_layer):
1516    """
1517    Resetea la secuencia de la layer PostGIS indicada
1518
1519    Args:
1520        ds_postgis (osgeo.ogr.DataSource): datasource de la ddbb PostGIS:
1521        nom_layer (str): nombre de la layer PostGIS
1522
1523    Returns:
1524        bool: True si se ha reseteado correctamente
1525    """
1526    ok = False
1527    layer_dest = ds_postgis.GetLayerByName(nom_layer)
1528    fid_column = layer_dest.GetFIDColumn()
1529    get_seq_sql = f"SELECT pg_get_serial_sequence('{nom_layer}', '{fid_column}') FROM information_schema.columns " \
1530                  f"WHERE table_name = '{nom_layer}' AND column_default LIKE 'nextval%'"
1531
1532    if res_get_seq := ds_postgis.ExecuteSQL(get_seq_sql):
1533        seq_name = res_get_seq.GetNextFeature().GetField(0)
1534        ds_postgis.StartTransaction()
1535        reset_seq = f"SELECT setval('{seq_name}', 1, false);"
1536        ds_postgis.ExecuteSQL(reset_seq)
1537        ds_postgis.CommitTransaction()
1538        ok = True
1539
1540    return ok
1541
1542
1543if __name__ == '__main__':
1544    import fire
1545
1546    fire.Fire()
DRVR_PG = 'PostgreSQL'
DRVR_CSV = 'CSV'
DRVR_GPKG = 'GPKG'
DRVR_GEOJSON = 'GeoJSON'
DRVR_KML = 'KML'
SIMPLIFY_TOLERANCE = 1e-06
DRIVERS_GDAL_NOT_DELETE_LAYERS = ('PostgreSQL',)
DRIVERS_OLD_SRS_AXIS_MAPPING_4326 = ('GeoJSON', 'GPKG', 'PostgreSQL')
DRIVERS_NO_SPATIAL_IDX = ('GeoJSON', 'CSV', 'PostgreSQL')
PREFFIX_GEOMS_LAYERS_GDAL = 'geom_'
PREFFIX_GEOMS_LAYERS_GDAL_CSV = '_WKT'
def get_geom_field_defn(layer_feature_defn, id_geom: int = 0):
46def get_geom_field_defn(layer_feature_defn, id_geom: int = 0):
47    """
48    Retorna el GeomFieldDefn indicado solo cuando existe al menos un campo geométrico.
49
50    Args:
51        layer_feature_defn (osgeo.ogr.FeatureDefn): una instancia de FeatureDefn (vease https://gdal.org/en/stable/api/python/vector_api.html#featuredefn)
52        id_geom (int=0): índice del campo geométrico a recuperar.
53
54    Returns:
55        osgeo.ogr.GeomFieldDefn: vease https://gdal.org/en/stable/api/python/vector_api.html#geomfielddefn
56    """
57    if not layer_feature_defn:
58        return None
59
60    if id_geom < 0:
61        raise ValueError(f"id_geom debe ser >= 0. Valor recibido: {id_geom}")
62
63    count_geoms = layer_feature_defn.GetGeomFieldCount()
64    if count_geoms == 0:
65        return None
66
67    if id_geom >= count_geoms:
68        raise ValueError(
69            f"id_geom fuera de rango ({id_geom}). Geometrías disponibles: {count_geoms} (índices 0..{count_geoms - 1})"
70        )
71
72    return layer_feature_defn.GetGeomFieldDefn(id_geom)

Retorna el GeomFieldDefn indicado solo cuando existe al menos un campo geométrico.

Arguments:
Returns:

osgeo.ogr.GeomFieldDefn: vease https://gdal.org/en/stable/api/python/vector_api.html#geomfielddefn

def srs_ref_from_epsg_code(code_epsg: int, old_axis_mapping=False) -> osgeo.osr.SpatialReference:
 75def srs_ref_from_epsg_code(code_epsg: int, old_axis_mapping=False) -> osr.SpatialReference:
 76    """
 77
 78    Args:
 79        code_epsg (int):
 80        old_axis_mapping (bool=False): Por cambio entre GDAL2.0 y GDAL3.0 se (vease
 81    https://github.com/OSGeo/gdal/blob/master/gdal/MIGRATION_GUIDE.TXT) por defecto se utiliza el mapeo de ejes
 82    clasico LONG,LAT
 83
 84    Returns:
 85        srs (osr.SpatialReference)
 86    """
 87    srs = osr.SpatialReference()
 88    if old_axis_mapping:
 89        try:
 90            from osgeo.osr import OAMS_TRADITIONAL_GIS_ORDER
 91            print_debug("OAMS_TRADITIONAL_GIS_ORDER")
 92            srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER)
 93        except ImportError:
 94            print_warning("OAMS_TRADITIONAL_GIS_ORDER not available")
 95
 96    ret = srs.ImportFromEPSG(code_epsg)
 97    if ret != 0:
 98        raise Warning("No se puede retornar un osgeo.osr.SpatialReference EPSG para el codigo '{}'!!".format(code_epsg))
 99
100    return srs
Arguments:
  • code_epsg (int):
  • old_axis_mapping (bool=False): Por cambio entre GDAL2.0 y GDAL3.0 se (vease

https://github.com/OSGeo/gdal/blob/master/gdal/MIGRATION_GUIDE.TXT) por defecto se utiliza el mapeo de ejes clasico LONG,LAT

Returns:

srs (osr.SpatialReference)

def layer_gdal_from_file( path_file: str, nom_driver: str = 'GeoJSON', nom_geom: str = None, default_srs_epsg_code: int = 4326, default_order_long_lat: bool = True):
103def layer_gdal_from_file(path_file: str,
104                         nom_driver: str = DRVR_GEOJSON,
105                         nom_geom: str = None,
106                         default_srs_epsg_code: int = 4326,
107                         default_order_long_lat: bool = True):
108    """
109
110    Args:
111        path_file (str):
112        nom_driver (str=DRVR_GEOJSON):
113        nom_geom (str=None): si se informa devolverá la layer solo con la geometria especificada
114        default_srs_epsg_code (int=4326): codigo del sistema de coordenadas que asignará por defecto si la layer
115                                          NO tiene sistema definido
116        default_order_long_lat (bool=True): si no viene informado el SRS y se usa el default EPSG4326 entonces se supone
117                                          por defecto que las geometria vienen en orden antiguo LONGITUD, LATITUD
118
119    Returns:
120        layer_gdal (osgeo.ogr.Layer), nom_layer (str), datasource_gdal (osgeo.ogr.DataSource)
121    """
122    drvr = ogr.GetDriverByName(nom_driver)
123
124    nom_layer, ext = utils.split_ext_file(os.path.basename(path_file))
125    if ext.lower().endswith("zip"):
126        path_file = "/vsizip/{}".format(path_file)
127
128    ds_vector_file = drvr.Open(path_file, 0)
129
130    a_layer = None
131    if ds_vector_file:
132        a_layer = ds_vector_file.GetLayer(0)
133
134        if nom_geom:
135            lay_def = a_layer.GetLayerDefn()
136            nom_geom = nom_geom.strip().upper()
137
138            if lay_def.GetGeomFieldCount() > 1:
139                idx_geom = lay_def.GetGeomFieldIndex(nom_geom)
140                if idx_geom < 0:
141                    raise Exception("El fichero vectorial '{}' no contiene una geometria con el nombre '{}'".format(
142                        ds_vector_file.GetName(), nom_geom))
143                elif idx_geom > 0:
144                    # Convertimos a layer en MEMORY para poder cambiar estructura
145                    ds_mem = ds_gdal_memory()
146                    a_layer = create_layer_from_layer_gdal_on_ds_gdal(ds_mem, a_layer, nom_layer, nom_geom,
147                                                                      exclude_cols_geoms=False, null_geoms=True)
148                    if a_layer:
149                        ds_vector_file = ds_mem
150
151            elif not a_layer.GetGeometryColumn() and (geom_def := get_geom_field_defn(lay_def)):
152                geom_def.SetName(nom_geom)
153
154        if a_layer:
155            # Por si no carga el SRS se asigna el :default_srs_epsg_code (defecto epsg:4326)
156            for gf in geom_fields_layer_gdal(a_layer):
157                if not gf.GetSpatialRef():
158                    gf.SetSpatialRef(srs_ref_from_epsg_code(default_srs_epsg_code, default_order_long_lat))
159
160    return a_layer, nom_layer, ds_vector_file
Arguments:
  • path_file (str):
  • nom_driver (str=DRVR_GEOJSON):
  • nom_geom (str=None): si se informa devolverá la layer solo con la geometria especificada
  • default_srs_epsg_code (int=4326): codigo del sistema de coordenadas que asignará por defecto si la layer NO tiene sistema definido
  • default_order_long_lat (bool=True): si no viene informado el SRS y se usa el default EPSG4326 entonces se supone por defecto que las geometria vienen en orden antiguo LONGITUD, LATITUD
Returns:

layer_gdal (osgeo.ogr.Layer), nom_layer (str), datasource_gdal (osgeo.ogr.DataSource)

def datasource_gdal_vector_file( nom_driver_gdal, nom_ds, a_dir, create=None, from_zip=False, **create_options):
163def datasource_gdal_vector_file(nom_driver_gdal, nom_ds, a_dir, create=None, from_zip=False, **create_options):
164    """
165    Crea datasource gdal para driver de tipo fichero
166    Args:
167        nom_driver_gdal (str):
168        nom_ds (str):
169        a_dir (str):
170        create (bool=None): Por defecto crea el datasource si este no existe y se abre sin problemas previamente.
171                        Si False entonces NO lo crea en ningún caso, y si True lo creará sin intentar abrirlo
172        from_zip (bool=False):
173        **create_options: lista claves valores con opciones de creacion para el datasource de GDAL
174                        p.e.: NameField="SEQENTITAT", DescriptionField="SW_URN"
175
176    Returns:
177        datasource_gdal (osgeo.ogr.DataSource), overwrited (bool)
178    """
179    driver, exts_driver = driver_gdal(nom_driver_gdal)
180    if not exts_driver:
181        raise Exception("ERROR! - El driver GDAL {} no es de tipo fichero vectorial".format(driver))
182
183    base_path_file = os.path.normpath(os.path.join(a_dir, nom_ds.strip().lower()))
184    _, ext_base = os.path.splitext(base_path_file)
185
186    vsi_prefix = ""
187    if from_zip:
188        ext_file = "zip"
189        vsi_prefix = "/vsizip/"
190    else:
191        if "topojson" in (ext.lower() for ext in exts_driver):
192            ext_file = "topojson"
193        else:
194            ext_file = exts_driver[0]
195
196    if (os.path.exists(base_path_file) and os.path.isfile(
197            base_path_file)) or ext_base.lower() == f'.{ext_file.lower()}':
198        path_file = base_path_file
199    else:
200        path_file = "{}.{}".format(base_path_file, ext_file)
201
202    overwrited = False
203    datasource_gdal = None
204    if not create and os.path.exists(path_file):
205        open_path_file = "{}{}".format(vsi_prefix, path_file)
206        datasource_gdal = driver.Open(open_path_file, 1)
207        if datasource_gdal is None:
208            datasource_gdal = driver.Open(open_path_file)
209
210    if datasource_gdal:
211        overwrited = True
212
213    if create or (create is not False and datasource_gdal is None and driver.TestCapability(ODrCCreateDataSource)):
214        datasource_gdal = driver.CreateDataSource(path_file,
215                                                  list("{}={}".format(opt, val) for opt, val in create_options.items()))
216
217    return datasource_gdal, overwrited

Crea datasource gdal para driver de tipo fichero

Arguments:
  • nom_driver_gdal (str):
  • nom_ds (str):
  • a_dir (str):
  • create (bool=None): Por defecto crea el datasource si este no existe y se abre sin problemas previamente. Si False entonces NO lo crea en ningún caso, y si True lo creará sin intentar abrirlo
  • from_zip (bool=False):
  • **create_options: lista claves valores con opciones de creacion para el datasource de GDAL p.e.: NameField="SEQENTITAT", DescriptionField="SW_URN"
Returns:

datasource_gdal (osgeo.ogr.DataSource), overwrited (bool)

def driver_gdal(nom_driver):
220def driver_gdal(nom_driver):
221    """
222    Devuelve el driver GDAL solicitado y si es de tipo fichero vectorial tambien devuelve la extension
223    Si no coincide exactamente devuelve el que tiene nombre más parecido.
224    Verificar con drivers_ogr_gdal_disponibles() los disponibles
225
226    Args:
227        nom_driver:
228    Returns:
229        driver_gdal (osgeo.ogr.Driver), exts_driver (list)
230    """
231    driver_gdal = ogr.GetDriverByName(nom_driver)
232    if driver_gdal is None:
233        raise ValueError(f"Driver '{nom_driver}' is not available")
234
235    metadata = driver_gdal.GetMetadata()
236    exts_drvr = metadata.get('DMD_EXTENSIONS', "").split(" ") if metadata else []
237
238    return driver_gdal, exts_drvr

Devuelve el driver GDAL solicitado y si es de tipo fichero vectorial tambien devuelve la extension Si no coincide exactamente devuelve el que tiene nombre más parecido. Verificar con drivers_ogr_gdal_disponibles() los disponibles

Arguments:
  • nom_driver:
Returns:

driver_gdal (osgeo.ogr.Driver), exts_driver (list)

def ds_gdal_memory():
241def ds_gdal_memory():
242    """
243    Devuelve un osgeo.ogr.DataSource de tipo "memory"
244
245    Returns:
246        datasource_gdal (osgeo.ogr.DataSource)
247    """
248    return ogr.GetDriverByName("memory").CreateDataSource("")

Devuelve un osgeo.ogr.DataSource de tipo "memory"

Returns:

datasource_gdal (osgeo.ogr.DataSource)

def set_create_option_list_for_layer_gdal(layer_gdal, drvr_name='GPKG', has_geom=True, **extra_opt_list):
251def set_create_option_list_for_layer_gdal(layer_gdal, drvr_name=DRVR_GPKG, has_geom=True, **extra_opt_list):
252    """
253    Devuelve la lista de create options GDAL a partir de una layer, el driver para el que se quiere crear y options
254    pasadas por defecto
255
256    Args:
257        layer_gdal (ogr.Layer):
258        drvr_name (str=DRVR_GPKG): nombre de driver GDAL
259        has_geom (bool=True): Indica si la layer tiene geometria. Si es False no añadirá options de geom como GEOMETRY_NAME, SPATIAL_INDEX, ...
260        extra_opt_list: lista pares claves-valores que se corresponden con option create list del driver GDAL
261
262    Returns:
263        opt_list (dict)
264    """
265    opt_list = set_create_option_list_for_driver_gdal(drvr_name, has_geom=has_geom, **extra_opt_list)
266
267    opt_geom_name = "GEOMETRY_NAME"
268    if has_geom and opt_geom_name not in opt_list:
269        nom_geom_src = layer_gdal.GetGeometryColumn()
270        if nom_geom_src:
271            opt_list[opt_geom_name] = "{}={}".format(opt_geom_name, nom_geom_src)
272
273    return opt_list

Devuelve la lista de create options GDAL a partir de una layer, el driver para el que se quiere crear y options pasadas por defecto

Arguments:
  • layer_gdal (ogr.Layer):
  • drvr_name (str=DRVR_GPKG): nombre de driver GDAL
  • has_geom (bool=True): Indica si la layer tiene geometria. Si es False no añadirá options de geom como GEOMETRY_NAME, SPATIAL_INDEX, ...
  • extra_opt_list: lista pares claves-valores que se corresponden con option create list del driver GDAL
Returns:

opt_list (dict)

def set_create_option_list_for_driver_gdal(drvr_name='GPKG', has_geom=True, **extra_opt_list):
276def set_create_option_list_for_driver_gdal(drvr_name=DRVR_GPKG, has_geom=True, **extra_opt_list):
277    """
278    Devuelve la lista de create options GDAL a partir de una layer, el driver para el que se quiere crear y options
279    pasadas por defecto
280
281    Args:
282        drvr_name (str=DRVR_GPKG): nombre de driver GDAL
283        has_geom (bool=True): Indica si la layer tiene geometria. Si es False no añadirá options de geom como SPATIAL_INDEX, ...
284        extra_opt_list: lista pares claves-valores que se corresponden con option create list del driver GDAL
285
286    Returns:
287        opt_list (dict)
288    """
289    # Se quitan las options que no son de creacion para el driver especificado
290    drvr, exts_drvr = driver_gdal(drvr_name)
291    if not drvr:
292        Exception("!ERROR! - El nombre de driver '{}' no es un driver GDAL disponible".format(drvr_name))
293
294    opt_list = {k.upper(): v.upper() for k, v in extra_opt_list.items()}
295
296    if not "FID" in opt_list and drvr.GetName() == DRVR_GPKG:
297        opt_list["FID"] = 'FID=FID_GPKG'
298
299    if drvr.GetName() == DRVR_GEOJSON:
300        if "WRITE_BBOX" not in opt_list:
301            opt_list["WRITE_BBOX"] = 'WRITE_BBOX=YES'
302
303    if drvr.GetName() == DRVR_CSV:
304        if "CREATE_CSVT" not in opt_list:
305            opt_list["CREATE_CSVT"] = 'CREATE_CSVT=YES'
306        if "GEOMETRY" not in opt_list and has_geom:
307            opt_list["GEOMETRY"] = 'GEOMETRY=AS_WKT'
308
309    if drvr.GetMetadataItem('DS_LAYER_CREATIONOPTIONLIST'):
310        list_opts_drvr = drvr.GetMetadataItem('DS_LAYER_CREATIONOPTIONLIST')
311        keys_opt_list = list(opt_list.keys())
312        for n_opt in keys_opt_list:
313            if list_opts_drvr.find(n_opt) < 0:
314                opt_list.pop(n_opt)
315
316        if has_geom and "SPATIAL_INDEX" not in opt_list and \
317                list_opts_drvr.find("SPATIAL_INDEX") >= 0 and drvr.GetName() != DRVR_PG:
318            opt_list["SPATIAL_INDEX"] = 'SPATIAL_INDEX=YES'
319
320    return opt_list

Devuelve la lista de create options GDAL a partir de una layer, el driver para el que se quiere crear y options pasadas por defecto

Arguments:
  • drvr_name (str=DRVR_GPKG): nombre de driver GDAL
  • has_geom (bool=True): Indica si la layer tiene geometria. Si es False no añadirá options de geom como SPATIAL_INDEX, ...
  • extra_opt_list: lista pares claves-valores que se corresponden con option create list del driver GDAL
Returns:

opt_list (dict)

def copy_layer_gdal_to_ds_gdal( layer_src, ds_gdal_dest, nom_layer=None, nom_geom=None, overwrite=True, **extra_opt_list):
323def copy_layer_gdal_to_ds_gdal(layer_src, ds_gdal_dest, nom_layer=None, nom_geom=None,
324                               overwrite=True, **extra_opt_list):
325    """
326    Copia una layer_gdal a otro datasource gdal
327
328    Args:
329        layer_src:
330        ds_gdal_dest:
331        nom_layer (str=None): OPC - Si no viene informado cogerá el nombre de la layer
332        nom_geom (str=None): OPC - Si se informa se cogerá como el nombre de la geometria de la nueva layer
333        overwrite (bool=True):
334        **extra_opt_list (str): Lista claves-valores de opciones para copylayer o createLayer
335                                del driver de ds_gdal indicado
336
337    Returns:
338        layer_dest (ogr.layer)
339    """
340    if not nom_layer:
341        nom_layer = layer_src.GetName()
342    nom_layer = nom_layer.strip().lower()
343
344    layer_dest = ds_gdal_dest.GetLayerByName(nom_layer)
345    if layer_dest:
346        if not overwrite:
347            return layer_dest
348        else:
349            ds_gdal_dest.DeleteLayer(nom_layer)
350
351    drvr_name = ds_gdal_dest.GetDriver().GetName()
352
353    extra_opt_list["IDENTIFIER"] = "IDENTIFIER={}".format(nom_layer)
354
355    lyr_src_has_geom = bool(layer_src.GetGeometryColumn() or nom_geom)
356
357    opt_list = set_create_option_list_for_layer_gdal(
358        layer_src, drvr_name=drvr_name, has_geom=lyr_src_has_geom,
359        **{str(k).upper(): str(v).upper() for k, v in extra_opt_list.items()})
360
361    layer_dest = ds_gdal_dest.CopyLayer(layer_src, nom_layer, list(opt_list.values()))
362
363    if layer_dest:
364        layer_dest_def = layer_dest.GetLayerDefn()
365        if lyr_src_has_geom and not layer_dest.GetGeometryColumn() and get_geom_field_defn(layer_dest_def):
366            if not nom_geom:
367                nom_geom = layer_src.GetGeometryColumn()
368            else:
369                nom_geom = nom_geom.upper()
370
371            get_geom_field_defn(layer_dest_def).SetName(nom_geom)
372
373        if drvr_name == DRVR_GPKG and lyr_src_has_geom:
374            create_spatial_index_layer_gpkg(ds_gdal_dest, nom_layer)
375
376    return layer_dest

Copia una layer_gdal a otro datasource gdal

Arguments:
  • layer_src:
  • ds_gdal_dest:
  • nom_layer (str=None): OPC - Si no viene informado cogerá el nombre de la layer
  • nom_geom (str=None): OPC - Si se informa se cogerá como el nombre de la geometria de la nueva layer
  • overwrite (bool=True):
  • **extra_opt_list (str): Lista claves-valores de opciones para copylayer o createLayer del driver de ds_gdal indicado
Returns:

layer_dest (ogr.layer)

def layer_gtype_from_geoms(layer_gdal, nom_geom=None):
379def layer_gtype_from_geoms(layer_gdal, nom_geom=None):
380    """
381    A partir de la 1º geometria informada de una layer_gdal devuelve el tipo de geometria que es. Si no encuentra
382    devuelve el geom_type de la layer (layer_gdal.GetGeomType())
383
384    Args:
385        layer_gdal (osgeo.ogr.Layer):
386        nom_geom (str=None): Nombre geometria
387
388    Returns:
389        geom_type (int=layer_gdal.GetGeomType()): Si no encuentra devuelve 0 por defecto (GEOMETRY)
390    """
391    idx_geom = 0
392    if nom_geom:
393        idx_geom = layer_gdal.GetLayerDefn().GetGeomFieldIndex(nom_geom)
394
395    if idx_geom >= 0:
396        layer_gdal.ResetReading()
397        return next((f.GetGeomFieldRef(idx_geom).GetGeometryType()
398                     for f in layer_gdal if f.GetGeomFieldRef(idx_geom)),
399                    layer_gdal.GetGeomType())

A partir de la 1º geometria informada de una layer_gdal devuelve el tipo de geometria que es. Si no encuentra devuelve el geom_type de la layer (layer_gdal.GetGeomType())

Arguments:
  • layer_gdal (osgeo.ogr.Layer):
  • nom_geom (str=None): Nombre geometria
Returns:

geom_type (int=layer_gdal.GetGeomType()): Si no encuentra devuelve 0 por defecto (GEOMETRY)

def srs_for_layer(layer_src, nom_geom_src=None):
402def srs_for_layer(layer_src, nom_geom_src=None):
403    """
404    Devuelve el SpatialReference de la layer_gdal
405    Args:
406        layer_src (ogr.Layer)
407        nom_geom_src (str=None): Nombre geometria
408
409    Returns:
410        srs_lyr_src (osgeo.osr.SpatialReference)
411    """
412    srs_lyr_src = layer_src.GetSpatialRef()
413    layer_src_def = layer_src.GetLayerDefn()
414
415    if act_geom_field := get_geom_field_defn(layer_src_def):
416        if nom_geom_src:
417            idx_act_geom_field = layer_src_def.GetGeomFieldIndex(nom_geom_src)
418            if idx_act_geom_field >= 0:
419                act_geom_field = layer_src_def.GetGeomFieldDefn(idx_act_geom_field)
420
421        if act_geom_field.GetSpatialRef():
422            srs_lyr_src = act_geom_field.GetSpatialRef()
423
424    return srs_lyr_src

Devuelve el SpatialReference de la layer_gdal

Arguments:
  • layer_src (ogr.Layer)
  • nom_geom_src (str=None): Nombre geometria
Returns:

srs_lyr_src (osgeo.osr.SpatialReference)

def create_layer_from_layer_gdal_on_ds_gdal( ds_gdal_dest, layer_src, nom_layer=None, nom_geom_src=None, sel_camps=None, exclude_cols_geoms=True, tolerance_simplify=None, null_geoms=False, gtype_layer_from_geoms=True, epsg_code_dest=None, epsg_code_src_default=4326, old_axis_mapping_srs=None, **extra_opt_list):
427def create_layer_from_layer_gdal_on_ds_gdal(ds_gdal_dest, layer_src, nom_layer=None, nom_geom_src=None, sel_camps=None,
428                                            exclude_cols_geoms=True, tolerance_simplify=None, null_geoms=False,
429                                            gtype_layer_from_geoms=True, epsg_code_dest=None,
430                                            epsg_code_src_default=4326, old_axis_mapping_srs=None, **extra_opt_list):
431    """
432    Crea nuevo layer a partir de layer_gdal.
433
434    Args:
435        ds_gdal_dest (ogr.DataSource):
436        layer_src (ogr.Layer):
437        nom_layer (str=None):
438        nom_geom_src (str=None): Si el nombre no se corresponde con ninguna de las geometrias de la layer_src se utilizará
439                            como ALIAS de la geometria 0 (la defecto) de la nueva layer resultante
440        sel_camps (list=None): OPC - Lista de campos a escoger de la layer original
441        exclude_cols_geoms (bool=True): Por defecto de la lista de columnas alfanuméricas (no geometrias) excluirá las
442                                       columnas que hagan referencia a alguna de las geometrias
443        tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer
444             Mirar método Simplify() sobre osgeo.ogr.Geometry
445        null_geoms (bool=False): Por defecto no grabará las filas que la geometria principal (nom_geom) es nula
446        gtype_layer_from_geoms (bool=True): Por defecto, si gtype de layer origen == 0 (GEOMETRY) deducirá el tipo de
447                                geometria (POINT, LINE o POLYGON) a partir de la primera informada
448                                encontrada en la layer_origen
449        epsg_code_dest (int=None): Codigo EPSG para le que se transformarán las geometrias desde el SRS original
450        epsg_code_src_default (int=4326): Codigo EPSG que se usará para las layer_src que NO tengan SRS asignado
451        old_axis_mapping_srs (bool=None): setea si quieres usar old mapping axes en (LONG, LAT) (GDAL >3.2)  
452        **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
453
454    Returns:
455        layer_out (ogr.Layer)
456    """
457    desc_ds_gdal = ds_gdal_dest.GetDescription()
458    drvr_name = ds_gdal_dest.GetDriver().GetName()
459    print_debug("Inici crear layer '{}' en ds_gdal '{}'".format(
460        (nom_layer if nom_layer else layer_src.GetName()).upper(),
461        desc_ds_gdal if desc_ds_gdal else drvr_name))
462
463    geoms_src = geoms_layer_gdal(layer_src)
464    camps_src = cols_layer_gdal(layer_src)
465
466    if nom_geom_src:
467        nom_geom_src = nom_geom_src.upper()
468        if len(geoms_src) > 1:
469            if nom_geom_src not in map(lambda ng: ng.upper(), geoms_src):
470                raise Exception("Argumento :NOM_GEOM = '{}' erróneo ya que "
471                                "LAYER_GDAL original no contiene dicha geometria".format(nom_geom_src))
472        elif len(geoms_src) == 0:
473            raise Exception("Argumento :NOM_GEOM = '{}' erróneo ya que "
474                            "LAYER_GDAL original no contiene geometrias".format(nom_geom_src))
475
476    if sel_camps:
477        sel_camps = {ng.upper() for ng in sel_camps}
478        if not sel_camps.issubset(camps_src):
479            raise Exception("Argumento :SEL_CAMPS = '[{}]' erróneo ya que "
480                            "LAYER_GDAL no contiene alguno de los campos indicados".format(",".join(sel_camps)))
481    else:
482        sel_camps = set()
483
484    if not nom_layer:
485        nom_layer = layer_src.GetName()
486    else:
487        nom_layer = nom_layer.strip()
488
489    nom_layer = nom_layer.lower()
490
491    gtype = layer_src.GetGeomType()
492    srs_lyr_src = layer_src.GetSpatialRef()
493
494    layer_src_def = layer_src.GetLayerDefn()
495    act_geom_field = get_geom_field_defn(layer_src_def)
496    nom_geom_out = None
497
498    if act_geom_field:
499        if nom_geom_src:
500            idx_act_geom_field = layer_src_def.GetGeomFieldIndex(nom_geom_src)
501            if idx_act_geom_field >= 0:
502                act_geom_field = layer_src_def.GetGeomFieldDefn(idx_act_geom_field)
503                nom_geom_out = fix_affix_geom_name_layer_gdal(nom_geom_src, layer_src)
504
505        gtype = act_geom_field.GetType()
506        if gtype_layer_from_geoms and not gtype:
507            gtype = layer_gtype_from_geoms(layer_src, nom_geom_src)
508
509        if act_geom_field.GetSpatialRef():
510            srs_lyr_src = act_geom_field.GetSpatialRef()
511
512    if sel_camps:
513        sel_camps = {c.strip().upper() for c in sel_camps}
514
515    geoms_src_minus_affix = geoms_layer_gdal(layer_src, PREFFIX_GEOMS_LAYERS_GDAL)
516    if exclude_cols_geoms:
517        if sel_camps:
518            sel_camps.difference_update(geoms_src_minus_affix)
519        else:
520            sel_camps = cols_layer_gdal(layer_src).difference(geoms_src_minus_affix)
521
522    srs_lyr_dest = None
523    if not srs_lyr_src and epsg_code_src_default:
524        srs_lyr_src = srs_ref_from_epsg_code(epsg_code_src_default, old_axis_mapping=old_axis_mapping_srs)
525    if srs_lyr_src:
526        if old_axis_mapping_srs is None and epsg_code_dest == 4326:
527            old_axis_mapping_srs = drvr_name in DRIVERS_OLD_SRS_AXIS_MAPPING_4326
528
529        if epsg_code_dest:
530            srs_lyr_dest = srs_ref_from_epsg_code(epsg_code_dest, old_axis_mapping=old_axis_mapping_srs)
531        else:
532            srs_lyr_dest = srs_lyr_src
533
534    if ds_gdal_dest.TestCapability(ODsCCreateLayer):
535        # To avoid message "Warning"
536        if ds_gdal_dest.TestCapability(ODsCDeleteLayer) and ds_gdal_dest.GetLayerByName(nom_layer):
537            ds_gdal_dest.DeleteLayer(nom_layer)
538
539        layer_out, nom_layer = create_layer_on_ds_gdal(ds_gdal_dest, nom_layer, nom_geom_out, gtype, srs_lyr_dest,
540                                                       **extra_opt_list)
541    else:
542        layer_out = ds_gdal_dest.GetLayer(0)
543
544    geom_field_out = None
545    if layer_out.TestCapability(OLCAlterFieldDefn):
546        layer_out_def = layer_out.GetLayerDefn()
547        geom_field_out = get_geom_field_defn(layer_out_def)
548        if geom_field_out:
549            nom_geom_field_ref = act_geom_field.GetNameRef() if act_geom_field else None
550            if not nom_geom_out:
551                nom_geom_out = nom_geom_field_ref
552                if not nom_geom_out:
553                    nom_geom_out = "GEOMETRY"
554            if not nom_geom_field_ref or nom_geom_field_ref.lower() != nom_geom_out.lower():
555                geom_field_out.SetName(nom_geom_out)
556
557    if layer_out.TestCapability(OLCCreateField):
558        for fd in fields_layer_gdal(layer_src):
559            nom_fd = fd.GetNameRef().upper()
560            if layer_out.FindFieldIndex(nom_fd, True) < 0 and (not sel_camps or nom_fd in sel_camps) and \
561                    nom_fd not in (gn.upper() for gn in geoms_src_minus_affix):
562                layer_out.CreateField(fd)
563
564        for gfd in geom_fields_layer_gdal(layer_src):
565            nom_gfd = fix_affix_geom_name_layer_gdal(gfd.GetNameRef(), layer_src).upper()
566            if (not sel_camps or nom_gfd in sel_camps) and \
567                    (nom_gfd not in geoms_src or not exclude_cols_geoms) and \
568                    nom_gfd != nom_geom_out:
569                gfd_def_src = layer_src_def.GetGeomFieldDefn(layer_src_def.GetGeomFieldIndex(gfd.GetNameRef()))
570                gfd_def_dest = GeomFieldDefn(nom_gfd, gfd_def_src.GetType())
571                if srs_lyr_dest:
572                    gfd_def_dest.SetSpatialRef(srs_lyr_dest)
573                layer_out.CreateGeomField(gfd_def_dest)
574
575    # Si capa ALFA se fuerza a crear la capa aunque no tenga geometría
576    if not nom_geom_out and not nom_geom_src:
577        null_geoms = True  # Si no hay geom siempre se añaden
578
579    add_layer_features_to_layer(layer_src, ds_gdal_dest, layer_out, nom_geom_src, nom_geom_out,
580                                srs_lyr_dest=srs_lyr_dest,
581                                null_geoms=null_geoms,
582                                tolerance_simplify=tolerance_simplify,
583                                epsg_code_src_default=epsg_code_src_default,
584                                old_axis_mapping_srs=old_axis_mapping_srs)
585
586    if drvr_name == DRVR_GPKG:
587        create_spatial_index_layer_gpkg(ds_gdal_dest, nom_layer)
588
589    if drvr_name == DRVR_CSV:
590        path_csv = ds_gdal_dest.GetName()
591        if os.path.exists(path_csv):
592            rename_wkt_geoms_csv(path_csv)
593
594    return ds_gdal_dest.GetLayerByName(nom_layer)

Crea nuevo layer a partir de layer_gdal.

Arguments:
  • ds_gdal_dest (ogr.DataSource):
  • layer_src (ogr.Layer):
  • nom_layer (str=None):
  • nom_geom_src (str=None): Si el nombre no se corresponde con ninguna de las geometrias de la layer_src se utilizará como ALIAS de la geometria 0 (la defecto) de la nueva layer resultante
  • sel_camps (list=None): OPC - Lista de campos a escoger de la layer original
  • exclude_cols_geoms (bool=True): Por defecto de la lista de columnas alfanuméricas (no geometrias) excluirá las columnas que hagan referencia a alguna de las geometrias
  • tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer Mirar método Simplify() sobre osgeo.ogr.Geometry
  • null_geoms (bool=False): Por defecto no grabará las filas que la geometria principal (nom_geom) es nula
  • gtype_layer_from_geoms (bool=True): Por defecto, si gtype de layer origen == 0 (GEOMETRY) deducirá el tipo de geometria (POINT, LINE o POLYGON) a partir de la primera informada encontrada en la layer_origen
  • epsg_code_dest (int=None): Codigo EPSG para le que se transformarán las geometrias desde el SRS original
  • epsg_code_src_default (int=4326): Codigo EPSG que se usará para las layer_src que NO tengan SRS asignado
  • old_axis_mapping_srs (bool=None): setea si quieres usar old mapping axes en (LONG, LAT) (GDAL >3.2)
  • **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
Returns:

layer_out (ogr.Layer)

def rename_wkt_geoms_csv(path_csv):
597def rename_wkt_geoms_csv(path_csv):
598    """
599    Remove "_WKT" affix from WKT geometry fields in CSV file
600
601    Args:
602        path_csv (str): Path to CSV file
603
604    Returns:
605
606    """
607    with open(path_csv, 'r', encoding='utf-8') as r_file:
608        content_csv = r_file.readlines()
609        first_line = next(iter(content_csv), None)
610    if first_line:
611        new_first_line = first_line.replace(PREFFIX_GEOMS_LAYERS_GDAL_CSV, '')
612        if new_first_line != first_line:
613            content_csv[0] = new_first_line
614            with open(path_csv, 'w', encoding='utf-8') as w_file:
615                w_file.writelines(content_csv)

Remove "_WKT" affix from WKT geometry fields in CSV file

Arguments:
  • path_csv (str): Path to CSV file

Returns:

def add_layer_features_to_layer( layer_src, ds_gdal_dest, layer_dest, nom_geom_src=None, nom_geom_dest=None, srs_lyr_dest=None, null_geoms=False, tolerance_simplify=None, remove_prev_features=False, epsg_code_src_default=4326, old_axis_mapping_srs=None):
618def add_layer_features_to_layer(layer_src, ds_gdal_dest, layer_dest, nom_geom_src=None, nom_geom_dest=None,
619                                srs_lyr_dest=None, null_geoms=False, tolerance_simplify=None,
620                                remove_prev_features=False, epsg_code_src_default=4326, old_axis_mapping_srs=None):
621    """
622    From a layer of a dataset, add the features to another layer of another dataset.
623
624    Args:
625        layer_src (ogr.Layer): Layer origen
626        ds_gdal_dest (gdal.Dataset): Dataset destino
627        layer_dest (ogr.Layer): Layer destino
628        nom_geom_src (str=None): Si el nombre no se corresponde con ninguna de las geometrias de la layer_src se utilizará
629                            como ALIAS de la geometria 0 (la defecto) de la nueva layer resultante
630        nom_geom_dest (str):
631        srs_lyr_dest (ogr.SpatialReference=None): Spatial Reference System de la layer_dest
632        null_geoms (bool=False): Por defecto no grabará las filas que la geometria principal (nom_geom) es nula
633        tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer
634             Mirar método Simplify() sobre osgeo.ogr.Geometry
635        remove_prev_features (bool=False): Si es True, se eliminarán las features previas de la layer_dest
636        epsg_code_src_default (int=4326): EPSG code del srs de la layer_src si no se encuentra
637        old_axis_mapping_srs (bool=None): setea si quieres usar old mapping axes en (LONG, LAT) (GDAL >3.2)
638
639    Returns:
640
641    """
642    geoms_out = geoms_layer_gdal(layer_dest)
643    cols_out = cols_layer_gdal(layer_dest)
644
645    geom_transform = None
646    srs_lyr_src = srs_for_layer(layer_src, nom_geom_src)
647    if not srs_lyr_src:
648        srs_lyr_src = srs_ref_from_epsg_code(epsg_code_src_default, old_axis_mapping_srs)
649
650    if srs_lyr_dest is None:
651        srs_lyr_dest = srs_for_layer(layer_dest, nom_geom_dest)
652
653    if srs_lyr_dest and not srs_lyr_src.IsSame(srs_lyr_dest):
654        geom_transform = osr.CoordinateTransformation(srs_lyr_src, srs_lyr_dest)
655
656    ds_trans = ds_gdal_dest.TestCapability(ODsCTransactions)
657
658    if remove_prev_features:
659        if ds_trans:
660            layer_dest.StartTransaction()
661        for feat in layer_dest:
662            layer_dest.DeleteFeature(feat.GetFID())
663        if ds_trans:
664            layer_dest.CommitTransaction()
665
666    if ds_trans:
667        layer_dest.StartTransaction()
668
669    i = 0
670    for feat_src, geom_src, nt_src in feats_layer_gdal(layer_src, nom_geom_src):
671        cols_out_chk = [col.upper() for col in cols_out.union(geoms_out)]
672        vals_camps = {nc: val for nc, val in nt_src._asdict().items()
673                      if nc.upper() in cols_out_chk}
674        if null_geoms or geom_src:
675            if nom_geom_dest:
676                vals_camps[nom_geom_dest.upper()] = geom_src
677
678            add_feature_to_layer_gdal(layer_dest,
679                                      tolerance_simplify=tolerance_simplify,
680                                      geom_trans=geom_transform,
681                                      **vals_camps)
682
683            if i > 0 and (i % 1000) == 0:
684                print_debug("{} registres tractats...".format(str(i)))
685            i += 1
686    if ds_trans:
687        layer_dest.CommitTransaction()

From a layer of a dataset, add the features to another layer of another dataset.

Arguments:
  • layer_src (ogr.Layer): Layer origen
  • ds_gdal_dest (gdal.Dataset): Dataset destino
  • layer_dest (ogr.Layer): Layer destino
  • nom_geom_src (str=None): Si el nombre no se corresponde con ninguna de las geometrias de la layer_src se utilizará como ALIAS de la geometria 0 (la defecto) de la nueva layer resultante
  • nom_geom_dest (str):
  • srs_lyr_dest (ogr.SpatialReference=None): Spatial Reference System de la layer_dest
  • null_geoms (bool=False): Por defecto no grabará las filas que la geometria principal (nom_geom) es nula
  • tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer Mirar método Simplify() sobre osgeo.ogr.Geometry
  • remove_prev_features (bool=False): Si es True, se eliminarán las features previas de la layer_dest
  • epsg_code_src_default (int=4326): EPSG code del srs de la layer_src si no se encuentra
  • old_axis_mapping_srs (bool=None): setea si quieres usar old mapping axes en (LONG, LAT) (GDAL >3.2)

Returns:

def create_layer_on_ds_gdal( ds_gdal_dest, nom_layer, nom_geom=None, gtype=None, srs_lyr_out=None, **extra_opt_list):
690def create_layer_on_ds_gdal(ds_gdal_dest, nom_layer, nom_geom=None, gtype=None, srs_lyr_out=None, **extra_opt_list):
691    """
692
693    Args:
694        ds_gdal_dest (ogr.DataSource):
695        nom_layer (str):
696        nom_geom (str=None):
697        gtype (OGRwkbGeometryType=None): Indicar integer representativo del tipo de geometria de la layer (ogr.wkbPoint, ogr.wkbPolygon, ogr.LineString, ...)
698        srs_lyr_out (osr.SpatialReference=None): codigo epsg del sistema de coordenadas de la geometria
699        **extra_opt_list: Calves valores option list creacion layer gdal
700
701    Returns:
702        layer_gdal (ogr.Layer), nom_layer (str)
703    """
704    drvr_name = ds_gdal_dest.GetDriver().GetName().upper()
705
706    opt_list = {k.upper(): v.upper() for k, v in extra_opt_list.items()}
707
708    opt_geom_name = "GEOMETRY_NAME"
709    if nom_geom:
710        opt_list[opt_geom_name] = "{}={}".format(opt_geom_name, nom_geom.upper())
711    else:
712        if opt_geom_name in opt_list:
713            opt_list.pop(opt_geom_name)
714        gtype = ogr.wkbNone
715
716    opt_list["IDENTIFIER"] = opt_list.get("IDENTIFIER", "IDENTIFIER={}".format(nom_layer))
717
718    opt_list = set_create_option_list_for_driver_gdal(drvr_name, **{k.upper(): v.upper() for k, v in opt_list.items()})
719
720    if ds_gdal_dest.TestCapability(ODsCDeleteLayer) and ds_gdal_dest.GetLayerByName(nom_layer):
721        print_warning("!ATENCION! - Se sobreescribirá la layer '{}' sobre el datasource GDAL '{}'".format(
722            nom_layer, ds_gdal_dest.GetDescription()))
723        ds_gdal_dest.DeleteLayer(nom_layer)
724
725    if drvr_name.upper() == DRVR_KML:
726        nom_layer = nom_layer.replace("-", "__")
727
728    layer_out = ds_gdal_dest.CreateLayer(nom_layer, srs_lyr_out, geom_type=gtype, options=list(opt_list.values()))
729
730    return layer_out, layer_out.GetName() if layer_out else None
Arguments:
  • ds_gdal_dest (ogr.DataSource):
  • nom_layer (str):
  • nom_geom (str=None):
  • gtype (OGRwkbGeometryType=None): Indicar integer representativo del tipo de geometria de la layer (ogr.wkbPoint, ogr.wkbPolygon, ogr.LineString, ...)
  • srs_lyr_out (osr.SpatialReference=None): codigo epsg del sistema de coordenadas de la geometria
  • **extra_opt_list: Calves valores option list creacion layer gdal
Returns:

layer_gdal (ogr.Layer), nom_layer (str)

def create_spatial_index_layer_gpkg(ds_gpkg, nom_layer):
733def create_spatial_index_layer_gpkg(ds_gpkg, nom_layer):
734    """
735    Crea spatial index sobre una layer (layer_gpkg) de un datosource gpkg (ds_gpkg)
736
737    Args:
738        ds_gpkg (osgeo.ogr.DataSource):
739        layer_gpkg (ogr.Layer):
740
741    Returns:
742        bool
743    """
744    layer_gpkg = ds_gpkg.GetLayerByName(nom_layer)
745    if layer_gpkg and (geom_name := layer_gpkg.GetGeometryColumn()):
746        tab_name = layer_gpkg.GetName()
747        res = ds_gpkg.ExecuteSQL(f"SELECT HasSpatialIndex('{tab_name}', '{geom_name}');")
748        if not bool(res and res.GetNextFeature().GetField(0) == 1):
749            # Se crea spatial_index ya que GDAL NO lo hace
750            ds_gpkg.StartTransaction()
751            ds_gpkg.ExecuteSQL(f"SELECT CreateSpatialIndex('{tab_name}', '{geom_name}');")
752            ds_gpkg.CommitTransaction()
753
754        return True
755    else:
756        return False

Crea spatial index sobre una layer (layer_gpkg) de un datosource gpkg (ds_gpkg)

Arguments:
  • ds_gpkg (osgeo.ogr.DataSource):
  • layer_gpkg (ogr.Layer):
Returns:

bool

def add_feature_to_layer_gdal( layer_gdal, tolerance_simplify=None, geom_trans=None, commit=False, **valors_camps):
759def add_feature_to_layer_gdal(layer_gdal, tolerance_simplify=None, geom_trans=None, commit=False, **valors_camps):
760    """
761
762    Args:
763        layer_gdal (ogr.Layer):
764        tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer.
765                                        Mirar método Simplify() sobre osgeo.ogr.Geometry
766        geom_trans (osr.CoordinateTransformation=None): transformacion para convertir las geometrias a otro SRS
767        commit (bool=False): Per defecte no farà commit de la transaccio
768        **valors_camps: pares nombre_campo=valor de la feature a crear
769
770    Returns:
771        feat (ogr.Feature)
772    """
773    dd_feat = ogr.Feature(layer_gdal.GetLayerDefn())
774
775    for camp, val in valors_camps.items():
776        idx_geom = dd_feat.GetGeomFieldIndex(camp)
777        es_geom = idx_geom >= 0
778        if es_geom:
779            if val:
780                new_val = None
781                if tolerance_simplify:
782                    new_val = val.Simplify(tolerance_simplify)
783                if val and geom_trans:
784                    new_val = val.Transform(geom_trans)
785                val = new_val or val
786
787            dd_feat.SetGeomField(idx_geom, val)
788
789        idx_fld = dd_feat.GetFieldIndex(camp)
790        if idx_fld >= 0:
791            if val:
792                if es_geom or hasattr(val, "ExportToIsoWkt"):
793                    val = val.ExportToIsoWkt()
794                dd_feat.SetField(camp, val)
795            else:
796                dd_feat.SetFieldNull(camp)
797
798        if idx_fld < 0 and idx_geom < 0:
799            if isinstance(val, Geometry):
800                if geom_trans:
801                    val.Transform(geom_trans)
802                dd_feat.SetGeometry(val)
803            elif val:
804                print_warning("!ATENCION! - La :layer_gdal no contiene el campo '{}'".format(camp))
805
806    commit_trans = commit and layer_gdal.TestCapability(OLCTransactions)
807    if commit_trans:
808        layer_gdal.StartTransaction()
809
810    new_feat = layer_gdal.CreateFeature(dd_feat)
811
812    if commit_trans:
813        layer_gdal.CommitTransaction()
814
815    return new_feat
Arguments:
  • layer_gdal (ogr.Layer):
  • tolerance_simplify (float=None): Tolerancia (distancia minima) en unidades del srs de la layer. Mirar método Simplify() sobre osgeo.ogr.Geometry
  • geom_trans (osr.CoordinateTransformation=None): transformacion para convertir las geometrias a otro SRS
  • commit (bool=False): Per defecte no farà commit de la transaccio
  • **valors_camps: pares nombre_campo=valor de la feature a crear
Returns:

feat (ogr.Feature)

def drivers_ogr_gdal_disponibles():
818def drivers_ogr_gdal_disponibles():
819    """
820    Retorna lista de drivers disponibles a través de la librería osgeo-gdal-ogr
821
822    Returns:
823        dict
824    """
825    cnt = ogr.GetDriverCount()
826    driver_list = []
827    drivers = OrderedDict()
828
829    for i in range(cnt):
830        driver = ogr.GetDriver(i)
831        driver_name = driver.GetName()
832        driver_list.append(driver_name)
833
834    for driver_name in driver_list:
835        # Is File GeoDatabase available?
836        drv = ogr.GetDriverByName(driver_name)
837        if drv is None:
838            print_warning("{} !!ATENTION - driver NOT available!!".format(driver_name))
839        else:
840            drivers[driver_name] = drv
841            print_debug(driver_name)
842
843    return drivers

Retorna lista de drivers disponibles a través de la librería osgeo-gdal-ogr

Returns:

dict

def drivers_ogr_gdal_vector_file():
846def drivers_ogr_gdal_vector_file():
847    """
848    Devuelve diccionario con los driver gdal para fichero vectorial
849
850    Returns:
851        dict
852    """
853    result = {}
854    for nd, d in drivers_ogr_gdal_disponibles().items():
855        try:
856            # Try GetMetadata_Dict first (older GDAL versions)
857            if hasattr(d, "GetMetadata_Dict"):
858                metadata = d.GetMetadata_Dict()
859            else:
860                # Fall back to GetMetadata for GDAL 3.10+
861                metadata = d.GetMetadata()
862
863            if metadata and metadata.get('DMD_EXTENSIONS'):
864                result[nd] = d
865        except Exception:
866            # Skip drivers that have issues
867            continue
868
869    return result

Devuelve diccionario con los driver gdal para fichero vectorial

Returns:

dict

def format_nom_column(nom_col, case_format=None):
872def format_nom_column(nom_col, case_format=None):
873    """
874    Retorna el nombre de columna formateado para usar como campo de un namedtuple (quita espacios y los reemplaza por "_").
875
876    Args:
877        nom_col (str):
878        case_format (str|None): formato de la columna. Si 'upper' or 'lower'. Por defecto tal como viene indicado
879
880    Returns:
881        str
882    """
883    nom_col_res = nom_col.replace(" ", "_")
884    if case_format:
885        if (case_format := case_format.lower()) == 'upper':
886            nom_col_res = nom_col_res.upper()
887        elif case_format == 'lower':
888            nom_col_res = nom_col_res.lower()
889
890    return nom_col_res

Retorna el nombre de columna formateado para usar como campo de un namedtuple (quita espacios y los reemplaza por "_").

Arguments:
  • nom_col (str):
  • case_format (str|None): formato de la columna. Si 'upper' or 'lower'. Por defecto tal como viene indicado
Returns:

str

def namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld=None, case_fields_nt=None):
893def namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld=None, case_fields_nt=None):
894    """
895    Devuelve namedTuple con los campos del layer pasado por parametro
896
897    Args:
898        layer_gdal:
899        extract_affix_geom_fld (str=None):
900        case_fields_nt (str|None): formato de las columnas de NamedTuple retornado. Si 'upper' or 'lower'. Por defecto tal como vienen de la FeatureLayer
901
902    Returns:
903        namedtuple: con nombre "gdalFeatDef_{NOM_LAYER}" y con los campos de la layer
904    """
905    camps_layer = set()
906    for fld in fields_layer_gdal(layer_gdal):
907        camps_layer.add(format_nom_column(fld.GetNameRef(), case_format=case_fields_nt))
908
909    for nom_geom in geoms_layer_gdal(layer_gdal, extract_affix=extract_affix_geom_fld):
910        camps_layer.add(format_nom_column(nom_geom, case_format=case_fields_nt))
911
912    nom_layer = layer_gdal.GetName().upper().split(".")[0].replace("-", "_")
913    return namedtuple(f"gdalFeatDef_{nom_layer}", camps_layer)

Devuelve namedTuple con los campos del layer pasado por parametro

Arguments:
  • layer_gdal:
  • extract_affix_geom_fld (str=None):
  • case_fields_nt (str|None): formato de las columnas de NamedTuple retornado. Si 'upper' or 'lower'. Por defecto tal como vienen de la FeatureLayer
Returns:

namedtuple: con nombre "gdalFeatDef_{NOM_LAYER}" y con los campos de la layer

def feats_layer_ds_gdal(ds_gdal, nom_layer=None, filter_sql=None, case_fields_nt=None):
916def feats_layer_ds_gdal(ds_gdal, nom_layer=None, filter_sql=None, case_fields_nt=None):
917    """
918    Itera las features (registros de una layer de gdal) y los devuelve como un namdetuple
919
920    Args:
921        ds_gdal: datasource gdal
922        nom_layer (str=None): Si no viene informado cogerá la primera layer que encuentre en el datasource
923        filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada.
924                            Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
925        case_fields_nt (str|None): formato de las columnas de NamedTuple retornado. Si 'upper' or 'lower'. Por defecto tal como vienen de la FeatureLayer
926
927    Returns:
928        ogr.Feature, ogr.Geometry, namedtuple_layer_gdal
929    """
930    if not nom_layer:
931        layer_gdal = ds_gdal.GetLayer()
932    else:
933        layer_gdal = ds_gdal.GetLayerByName(nom_layer)
934
935    if layer_gdal:
936        for feat, geom, vals in feats_layer_gdal(layer_gdal, filter_sql=filter_sql, case_fields_nt=case_fields_nt):
937            yield feat, geom, vals
938    else:
939        raise ValueError(f"'{nom_layer}' is not a valid layer over DataSource GDAL '{ds_gdal.GetName()}'")

Itera las features (registros de una layer de gdal) y los devuelve como un namdetuple

Arguments:
  • ds_gdal: datasource gdal
  • nom_layer (str=None): Si no viene informado cogerá la primera layer que encuentre en el datasource
  • filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada. Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
  • case_fields_nt (str|None): formato de las columnas de NamedTuple retornado. Si 'upper' or 'lower'. Por defecto tal como vienen de la FeatureLayer
Returns:

ogr.Feature, ogr.Geometry, namedtuple_layer_gdal

def feats_layer_gdal( layer_gdal, nom_geom=None, filter_sql=None, extract_affix_geom_fld='geom_', case_fields_nt=None):
942def feats_layer_gdal(layer_gdal, nom_geom=None, filter_sql=None, extract_affix_geom_fld=PREFFIX_GEOMS_LAYERS_GDAL,
943                     case_fields_nt=None):
944    """
945    Itera las features (registros de una layer de gdal) y los devuelve como un namedtuple
946
947    Args:
948        layer_gdal (ogr.Layer):
949        nom_geom (str=None): Por defecto la geometria activa o principal
950        filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada.
951                            Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
952        extract_affix_geom_fld (str=SUFFIX_GEOMS_LAYERS_GDAL): Por defecto quita el sufijo 'geom_' a los campos geom
953        case_fields_nt (str=None): si 'upper' los campos del NamedTuple retornado estarán en UPPERCASE y si 'lower' al contrario.
954                        Por defecto tal como vienen de la FeatureLayer
955
956    Returns:
957        ogr.Feature, ogr.Geometry, namedtuple_layer_gdal
958    """
959    layer_gdal.ResetReading()
960    ntup_layer = namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld, case_fields_nt=case_fields_nt)
961    n_geoms_layer = {nom_geom_feat: fix_affix_geom_name_layer_gdal(nom_geom_feat, layer_gdal, extract_affix_geom_fld)
962                     for nom_geom_feat in geoms_layer_gdal(layer_gdal)}
963
964    if filter_sql:
965        layer_gdal.SetAttributeFilter(filter_sql)
966
967    def vals_feature_gdal(feat_gdal):
968        vals = {}
969        feat_items = feat_gdal.items()
970        # In GDAL 3.x, items() returns a dict
971        if isinstance(feat_items, dict):
972            for camp, val in feat_items.items():
973                vals[format_nom_column(camp, case_format=case_fields_nt)] = val
974        else:
975            # Older GDAL versions may return different structure
976            for camp, val in feat_items:
977                vals[format_nom_column(camp, case_format=case_fields_nt)] = val
978
979        for camp_geom, camp_geom_val in n_geoms_layer.items():
980            idx_geom = feat_gdal.GetGeomFieldIndex(camp_geom)
981            if idx_geom >= 0:
982                vals[format_nom_column(camp_geom_val, case_format=case_fields_nt)] = feat_gdal.GetGeomFieldRef(idx_geom)
983
984        return vals
985
986    if layer_gdal:
987        for f_tab in layer_gdal:
988            idx_geom = f_tab.GetGeomFieldIndex(nom_geom) if nom_geom else -1
989            yield f_tab, \
990                f_tab.GetGeomFieldRef(idx_geom) if idx_geom >= 0 else f_tab.geometry(), \
991                ntup_layer(**vals_feature_gdal(f_tab))
992
993        layer_gdal.ResetReading()

Itera las features (registros de una layer de gdal) y los devuelve como un namedtuple

Arguments:
  • layer_gdal (ogr.Layer):
  • nom_geom (str=None): Por defecto la geometria activa o principal
  • filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada. Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
  • extract_affix_geom_fld (str=SUFFIX_GEOMS_LAYERS_GDAL): Por defecto quita el sufijo 'geom_' a los campos geom
  • case_fields_nt (str=None): si 'upper' los campos del NamedTuple retornado estarán en UPPERCASE y si 'lower' al contrario. Por defecto tal como vienen de la FeatureLayer
Returns:

ogr.Feature, ogr.Geometry, namedtuple_layer_gdal

def distinct_vals_camp_layer_gdal(layer_gdal, nom_camp, filter_sql=None):
 996def distinct_vals_camp_layer_gdal(layer_gdal, nom_camp, filter_sql=None):
 997    """
 998    Devuelve set con distintos valores para el campo indicado del layer GDAL indicada
 999
1000    Args:
1001        layer_gdal (ogr.Layer):
1002        nom_camp (str):
1003        filter_sql (str=None):
1004
1005    Returns:
1006        set
1007    """
1008    if nom_camp.upper() not in cols_layer_gdal(layer_gdal):
1009        raise Exception("Argumento :NOM_CAMP = '{}' erróneo ya que "
1010                        "LAYER_GDAL no contiene el campo indicado".format(nom_camp))
1011
1012    return {getattr(nt_feat, nom_camp.upper())
1013            for feat, geom, nt_feat in feats_layer_gdal(layer_gdal, filter_sql=filter_sql)}

Devuelve set con distintos valores para el campo indicado del layer GDAL indicada

Arguments:
  • layer_gdal (ogr.Layer):
  • nom_camp (str):
  • filter_sql (str=None):
Returns:

set

def fields_layer_gdal(layer_gdal):
1016def fields_layer_gdal(layer_gdal):
1017    """
1018    Itera sobre los FieldDefn de una layer gdal
1019
1020    Args:
1021        layer_gdal:
1022
1023    Yields:
1024        osgeo.ogr.FieldDefn
1025    """
1026    layer_def = layer_gdal.GetLayerDefn()
1027    for i in range(0, layer_def.GetFieldCount()):
1028        yield layer_def.GetFieldDefn(i)
1029
1030    layer_def = None

Itera sobre los FieldDefn de una layer gdal

Arguments:
  • layer_gdal:
Yields:

osgeo.ogr.FieldDefn

def geom_fields_layer_gdal(layer_gdal):
1033def geom_fields_layer_gdal(layer_gdal):
1034    """
1035    Itera sobre los GeomFieldDefn de una layer gdal
1036
1037    Args:
1038        layer_gdal:
1039
1040    Yields:
1041        osgeo.ogr.GeomFieldDefn
1042    """
1043    layer_def = layer_gdal.GetLayerDefn()
1044    for i in range(0, layer_def.GetGeomFieldCount()):
1045        yield layer_def.GetGeomFieldDefn(i)
1046
1047    layer_def = None

Itera sobre los GeomFieldDefn de una layer gdal

Arguments:
  • layer_gdal:
Yields:

osgeo.ogr.GeomFieldDefn

def nom_layers_datasource_gdal(ds_gdal):
1050def nom_layers_datasource_gdal(ds_gdal):
1051    """
1052    Retorna lista con los nombres de las layers de un datasource gdal.
1053    CACHEA esos nombres por DS Name para optimizar llamadas repetidas.
1054
1055    Args:
1056        ds_gdal (ogr.Datasource):
1057
1058    Returns:
1059        set
1060    """
1061    if not ds_gdal:
1062        return set()
1063
1064    ds_name = ds_gdal.GetName() if hasattr(ds_gdal, "GetName") else None
1065    layer_count = ds_gdal.GetLayerCount() if hasattr(ds_gdal, "GetLayerCount") else None
1066
1067    if ds_name and ds_name in _CACHE_NOM_LAYERS_DS_GDAL:
1068        cached = _CACHE_NOM_LAYERS_DS_GDAL[ds_name]
1069        if layer_count is None or cached.get("count") == layer_count:
1070            # Devolvemos copia para evitar mutaciones externas de la caché
1071            return set(cached.get("layers", set()))
1072
1073    layers = {l.GetName() for l in ds_gdal}
1074
1075    # Solo cacheamos cuando existe identificador estable de datasource
1076    if ds_name:
1077        _CACHE_NOM_LAYERS_DS_GDAL[ds_name] = {
1078            "count": layer_count,
1079            "layers": set(layers)
1080        }
1081
1082    return layers

Retorna lista con los nombres de las layers de un datasource gdal. CACHEA esos nombres por DS Name para optimizar llamadas repetidas.

Arguments:
  • ds_gdal (ogr.Datasource):
Returns:

set

def cols_layer_gdal(layer_gdal):
1085def cols_layer_gdal(layer_gdal):
1086    """
1087    Retorna lista con las columnas de una layer gdal
1088
1089    Args:
1090        layer_gdal:
1091
1092    Returns:
1093        set
1094    """
1095    camps = set()
1096    for fd in fields_layer_gdal(layer_gdal):
1097        # camps.add(fd.GetName().upper())
1098        camps.add(fd.GetNameRef())
1099
1100    return camps

Retorna lista con las columnas de una layer gdal

Arguments:
  • layer_gdal:
Returns:

set

def geoms_layer_gdal(layer_gdal, extract_affix=None):
1103def geoms_layer_gdal(layer_gdal, extract_affix=None):
1104    """
1105    Retorna lista con las columnas geométricas de una layer gdal
1106
1107    Args:
1108        layer_gdal:
1109        extract_affix (str=None=): if informed extract the affix passed
1110
1111    Returns:
1112        set
1113    """
1114    camps_geom = set()
1115    for gdf in geom_fields_layer_gdal(layer_gdal):
1116        # camps_geom.add(gdf.GetName().upper())
1117        name_ref = gdf.GetNameRef()
1118        if extract_affix:
1119            name_ref = fix_affix_geom_name_layer_gdal(name_ref, layer_gdal, extract_affix)
1120        camps_geom.add(name_ref)
1121
1122    return camps_geom

Retorna lista con las columnas geométricas de una layer gdal

Arguments:
  • layer_gdal:
  • extract_affix (str=None=): if informed extract the affix passed
Returns:

set

def fix_affix_geom_name_layer_gdal(geom_name, layer_gdal, affix='geom_'):
1125def fix_affix_geom_name_layer_gdal(geom_name, layer_gdal, affix=PREFFIX_GEOMS_LAYERS_GDAL):
1126    """
1127    Extract affix (default=PREFFIX_GEOMS_LAYERS_GDAL) from name geoms if correspond with other column
1128    Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'
1129
1130    Args:
1131          geom_name (str):
1132          layer_gdal:
1133          affix (str=PREFFIX_GEOMS_LAYERS_GDAL):
1134
1135    Returns:
1136        str
1137    """
1138    cols_layer = cols_layer_gdal(layer_gdal)
1139    geom_name_layer = geom_name
1140    if geom_name.lower().startswith(affix):
1141        geom_name_aux = re.sub(affix, '', geom_name, flags=re.IGNORECASE)
1142        if geom_name_aux.lower() in (col.lower() for col in cols_layer):
1143            geom_name_layer = geom_name_aux
1144
1145    return geom_name_layer

Extract affix (default=PREFFIX_GEOMS_LAYERS_GDAL) from name geoms if correspond with other column Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'

Arguments:
  • geom_name (str):
  • layer_gdal:
  • affix (str=PREFFIX_GEOMS_LAYERS_GDAL):
Returns:

str

def add_layer_gdal_to_ds_gdal( ds_gdal, layer_gdal, nom_layer=None, lite=False, srs_epsg_code=None, multi_geom=False, nom_geom=None, null_geoms=False, **extra_opt_list):
1148def add_layer_gdal_to_ds_gdal(ds_gdal, layer_gdal, nom_layer=None, lite=False, srs_epsg_code=None, multi_geom=False,
1149                              nom_geom=None, null_geoms=False, **extra_opt_list):
1150    """
1151    Añade una layer_gdal a un datasource_gdal. Si es una layer con multigeometrias las separa en una layer por geometria
1152
1153    Args:
1154        ds_gdal (osgeo.ogr.Datasource):
1155        layer_gdal (osgeo.ogr.Layer):
1156        nom_layer (str=None):
1157        lite (bool=False):
1158        srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir
1159                                  las geometrias
1160        nom_geom (str=None): nombre de geometria de layer origen (layer_gdal) que se copiará, si no todas
1161        multi_geom (bool=False): Si el DS_GDAL destino permite multigeometria
1162        null_geoms (bool=False): Indica si se admitirán registros con NULL geoms. Por defecto NO
1163        **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
1164
1165    Returns:
1166        new_layers_ds_gdal (list)
1167    """
1168    new_layers_ds_gdal = []
1169
1170    if not nom_layer:
1171        nom_layer = layer_gdal.GetName()
1172
1173    geoms_layer = geoms_layer_gdal(layer_gdal)
1174    if nom_geom:
1175        if nom_geom.upper() not in (g.upper() for g in geoms_layer):
1176            Exception("!ERROR! - Nombre de geometria '{}' no existe en la layer GDAL origen")
1177        else:
1178            geoms_layer = (nom_geom,)
1179
1180    if geoms_layer:
1181        tol = None
1182        if lite:
1183            tol = SIMPLIFY_TOLERANCE
1184
1185        nom_layer_base = nom_layer.split("-")[0]
1186        if not multi_geom:
1187            for geom_name in geoms_layer:
1188                # Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'
1189                geom_name_layer = fix_affix_geom_name_layer_gdal(geom_name, layer_gdal)
1190
1191                nom_layer = "{}-{}".format(nom_layer_base, geom_name_layer).lower()
1192                extra_opt_list["GEOMETRY_NAME"] = "GEOMETRY_NAME={}".format(geom_name_layer)
1193                lyr = create_layer_from_layer_gdal_on_ds_gdal(ds_gdal, layer_gdal, nom_layer, geom_name,
1194                                                              tolerance_simplify=tol, null_geoms=null_geoms,
1195                                                              epsg_code_dest=srs_epsg_code, **extra_opt_list)
1196                new_layers_ds_gdal.append(lyr)
1197        else:
1198            lyr = create_layer_from_layer_gdal_on_ds_gdal(ds_gdal, layer_gdal, nom_layer, exclude_cols_geoms=False,
1199                                                          tolerance_simplify=tol, null_geoms=True,
1200                                                          epsg_code_dest=srs_epsg_code, **extra_opt_list)
1201            new_layers_ds_gdal.append(lyr)
1202    else:
1203        if ds_gdal.GetDriver().GetName() == DRVR_PG:
1204            extra_opt_list.update(dict(
1205                precision=extra_opt_list.get('precision', 'PRECISION=NO')
1206            ))
1207
1208        lyr = copy_layer_gdal_to_ds_gdal(layer_gdal, ds_gdal, nom_layer.lower(), **extra_opt_list)
1209        new_layers_ds_gdal.append(lyr)
1210
1211    return new_layers_ds_gdal

Añade una layer_gdal a un datasource_gdal. Si es una layer con multigeometrias las separa en una layer por geometria

Arguments:
  • ds_gdal (osgeo.ogr.Datasource):
  • layer_gdal (osgeo.ogr.Layer):
  • nom_layer (str=None):
  • lite (bool=False):
  • srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir las geometrias
  • nom_geom (str=None): nombre de geometria de layer origen (layer_gdal) que se copiará, si no todas
  • multi_geom (bool=False): Si el DS_GDAL destino permite multigeometria
  • null_geoms (bool=False): Indica si se admitirán registros con NULL geoms. Por defecto NO
  • **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
Returns:

new_layers_ds_gdal (list)

def copy_layers_gpkg( ds_gpkg, driver, dir_base, lite=False, srs_epsg_code=None, zipped=True):
1214def copy_layers_gpkg(ds_gpkg, driver, dir_base, lite=False, srs_epsg_code=None, zipped=True):
1215    """
1216
1217    Args:
1218        ds_gpkg (osgeo.ogr.Datasource):
1219        driver (str):
1220        dir_base (str=None):
1221        lite (bool=False):
1222        srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir
1223                                  las geometrias
1224        zipped (bool=False):
1225
1226    Returns:
1227        num_layers (int)
1228    """
1229    num_layers = 0
1230    subdir_drvr = os.path.normpath(os.path.join(dir_base, driver.upper()))
1231    utils.create_dir(subdir_drvr)
1232
1233    for layer_gpkg in (ds_gpkg.GetLayer(id_lyr) for id_lyr in range(ds_gpkg.GetLayerCount() - 1)):
1234        if driver == DRVR_GPKG:
1235            nom_ds, ext = utils.split_ext_file(os.path.basename(ds_gpkg.GetName()))
1236        else:
1237            nom_ds = f"{layer_gpkg.GetName()}".lower()
1238
1239        ds_gdal, existia = datasource_gdal_vector_file(driver, nom_ds, subdir_drvr)
1240
1241        add_layer_gdal_to_ds_gdal(ds_gdal, layer_gpkg, lite=lite, srs_epsg_code=srs_epsg_code)
1242
1243        num_layers += 1
1244
1245    return num_layers
Arguments:
  • ds_gpkg (osgeo.ogr.Datasource):
  • driver (str):
  • dir_base (str=None):
  • lite (bool=False):
  • srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir las geometrias
  • zipped (bool=False):
Returns:

num_layers (int)

def set_csvt_for_layer_csv(path_csv, **tipus_camps):
1248def set_csvt_for_layer_csv(path_csv, **tipus_camps):
1249    """
1250    Crea/Modifica el CSVT asociado con los tipos indicados para cada columna
1251
1252    Args:
1253        path_csv (str):
1254        **tipus_camps: clave=valor con el nombre del campo y el tipo de campo asociado (p.e. String(25), WKT, Integer,...)
1255
1256    Returns:
1257        path_csvt (str)
1258    """
1259    lyr_csv, nom_layer, ds_lyr = layer_gdal_from_file(path_csv, DRVR_CSV)
1260    if not lyr_csv:
1261        print_warning("!ATENCIO! - No s'ha pogut obrir la layer CSV '{}'".format(path_csv))
1262        return
1263
1264    path_lyr_csvt = os.path.join(os.path.dirname(path_csv), "{}.csvt".format(nom_layer))
1265    tips_lyr = {}
1266    for fld in fields_layer_gdal(lyr_csv):
1267        tip_fld = fld.GetFieldTypeName(fld.GetType())
1268        sufix = ""
1269        w = fld.GetWidth()
1270        if w:
1271            sufix = "{}".format(w)
1272        p = fld.GetPrecision()
1273        if p:
1274            sufix += ".{}".format(p)
1275        if sufix:
1276            tip_fld += "({})".format(sufix)
1277
1278        tips_lyr[fld.name.upper()] = tip_fld
1279
1280    for nom_camp, tip_camp in tipus_camps.items():
1281        nom_camp = nom_camp.upper()
1282        if nom_camp not in tips_lyr:
1283            print_warning("!ATENCIO! - Camp '{}' no existeix sobre la layer CSV '{}'".format(nom_camp, path_csv))
1284            continue
1285
1286        tips_lyr[nom_camp] = tip_camp
1287
1288    with open(path_lyr_csvt, mode="w", encoding="utf8") as f_csvt:
1289        f_csvt.write(",".join(tips_lyr.values()))

Crea/Modifica el CSVT asociado con los tipos indicados para cada columna

Arguments:
  • path_csv (str):
  • **tipus_camps: clave=valor con el nombre del campo y el tipo de campo asociado (p.e. String(25), WKT, Integer,...)
Returns:

path_csvt (str)

def zip_and_clean_ds_csv(path_csv):
1292def zip_and_clean_ds_csv(path_csv):
1293    """
1294    # Zipea datasource osgeo CSV y como GDAL no crea los tipos WKT para las geometrias en el CSVT se fuerza
1295
1296    Args:
1297        path_csv (str): path Datasource osgeo CSV
1298
1299    Returns:
1300        zip_path (str)
1301    """
1302    ds_gdal_csv, dummy = datasource_gdal_vector_file(DRVR_CSV, os.path.basename(path_csv).split(".")[0],
1303                                                     os.path.dirname(path_csv))
1304    layer_csv = ds_gdal_csv.GetLayer(0)
1305    tips_geoms = {gn: "WKT" for gn in
1306                  (*geoms_layer_gdal(layer_csv, extract_affix=PREFFIX_GEOMS_LAYERS_GDAL),
1307                   *geoms_layer_gdal(layer_csv, extract_affix=PREFFIX_GEOMS_LAYERS_GDAL_CSV))}
1308    path_csv = ds_gdal_csv.GetDescription()
1309    ds_gdal_csv = None  # Cerramos el datasource para que se guarden los cambios
1310    if path_csv and os.path.exists(path_csv):
1311        if tips_geoms:
1312            set_csvt_for_layer_csv(path_csv, **tips_geoms)
1313        rename_wkt_geoms_csv(path_csv)
1314    dir_base_csv = os.path.dirname(path_csv)
1315    nom_layer = os.path.basename(path_csv).split(".")[0]
1316    l_files_csv = [os.path.join(dir_base_csv, nf) for nf in os.listdir(dir_base_csv)
1317                   if nf.lower().startswith(nom_layer.lower()) and
1318                   any(nf.lower().endswith(ext) for ext in ('csv', 'csvt'))]
1319    zip_path = utils.zip_files(os.path.join(dir_base_csv, "{}.zip".format(nom_layer)), l_files_csv)
1320    if zip_path:
1321        for fl_csv in l_files_csv:
1322            os.remove(fl_csv)
1323
1324    return zip_path

Zipea datasource osgeo CSV y como GDAL no crea los tipos WKT para las geometrias en el CSVT se fuerza

Arguments:
  • path_csv (str): path Datasource osgeo CSV
Returns:

zip_path (str)

def convert_angle(pt_xy, deg_ang, orig_srs, dest_srs):
1327def convert_angle(pt_xy, deg_ang, orig_srs, dest_srs):
1328    """
1329
1330    Args:
1331        pt_xy (tuple):
1332        deg_ang (float):
1333        orig_srs (osr.SpatialReference):
1334        dest_srs (osr.SpatialReference):
1335
1336    Returns:
1337
1338    """
1339    trans = osr.CoordinateTransformation(orig_srs, dest_srs)
1340    x, y = pt_xy
1341    dx = math.sin(math.radians(deg_ang)) * 0.00000001
1342    dy = math.cos(math.radians(deg_ang)) * 0.00000001
1343    pt1 = ogr.CreateGeometryFromWkt("POINT ({} {})".format(x, y))
1344    pt2 = ogr.CreateGeometryFromWkt("POINT ({} {})".format(x + dx, y + dy))
1345    pt1.Transform(trans)
1346    pt2.Transform(trans)
1347    x1, y1, z1 = pt1.GetPoint()
1348    x2, y2, z2 = pt2.GetPoint()
1349
1350    return math.degrees(math.atan2(y2 - y1, x2 - x1))
Arguments:
  • pt_xy (tuple):
  • deg_ang (float):
  • orig_srs (osr.SpatialReference):
  • dest_srs (osr.SpatialReference):

Returns:

def transform_ogr_geom(a_ogr_geom, from_espg_code, to_epsg_code):
1353def transform_ogr_geom(a_ogr_geom, from_espg_code, to_epsg_code):
1354    """
1355    Transforma una geometria OGR según los EPSG indicados
1356
1357    Args:
1358        a_ogr_geom (ogr.geometry): una geometria del tipo OGR
1359        from_espg_code (int): codigo numérico del EPSG actual para la geometria
1360        to_epsg_code (int): codigo numérico del EPSG al que se quiere transformar
1361
1362    Returns:
1363        ogr.geometry
1364    """
1365    source = osr.SpatialReference()
1366    source.ImportFromEPSG(from_espg_code)
1367
1368    target = osr.SpatialReference()
1369    target.ImportFromEPSG(to_epsg_code)
1370
1371    a_transform = osr.CoordinateTransformation(source, target)
1372    a_ogr_geom.Transform(a_transform)
1373
1374    return a_ogr_geom

Transforma una geometria OGR según los EPSG indicados

Arguments:
  • a_ogr_geom (ogr.geometry): una geometria del tipo OGR
  • from_espg_code (int): codigo numérico del EPSG actual para la geometria
  • to_epsg_code (int): codigo numérico del EPSG al que se quiere transformar
Returns:

ogr.geometry

def ds_postgis(pg_conn_str, schemas=None, **open_options):
1377def ds_postgis(pg_conn_str, schemas=None, **open_options):
1378    """
1379    Retorna datasource GDAL para ddbb postgis
1380
1381    Args:
1382        pg_conn_str: str "PG:user=... password=..."
1383        schemas:
1384        **open_options: Open options del driver PostgreSQL de GDAL
1385                        (vease https://gdal.org/en/stable/drivers/vector/pg.html#dataset-open-options)
1386                        p.e.: LIST_ALL_TABLES='YES', SKIP_VIEWS='YES', SCHEMAS='public,myschema',
1387                              TABLES='tabla1,tabla2', OGR_PG_RETRIEVE_FID='YES'
1388
1389    Returns:
1390        osgeo.ogr.DataSource
1391    """
1392    if schemas:
1393        if isinstance(schemas, str):
1394            schemas = [*schemas.split(",")]
1395
1396        active_schema = schemas[0]
1397        str_schemas = ",".join(schemas)
1398        pg_conn_str = f"{pg_conn_str} active_schema='{active_schema}' schemas='{str_schemas}'"
1399
1400    if open_options:
1401        oo_list = [f"{k.upper()}={v}" for k, v in open_options.items()]
1402        return gdal.OpenEx(pg_conn_str, gdal.OF_VECTOR | gdal.OF_UPDATE, open_options=oo_list)
1403
1404    drvr, exts = driver_gdal(DRVR_PG)
1405
1406    return drvr.Open(pg_conn_str, 1)

Retorna datasource GDAL para ddbb postgis

Arguments:
Returns:

osgeo.ogr.DataSource

def pg_string_connection( dbname=None, host=None, port=None, service=None, user=None, password=None, schemas=None):
1409def pg_string_connection(dbname=None, host=None, port=None, service=None, user=None, password=None, schemas=None):
1410    """
1411    Retorna una cadena de conexion a una base de datos postgis
1412
1413    Args:
1414        dbname:
1415        host:
1416        port:
1417        service:
1418        user:
1419        password:
1420        schemas (str | list[str]=None): Esquema de la base de datos
1421
1422    Returns:
1423        str
1424    """
1425    pg_conn = "PG:"
1426    if dbname:
1427        pg_conn += f"dbname='{dbname}'"
1428    if host:
1429        pg_conn += f"host='{host}'"
1430    if port:
1431        pg_conn += f"port='{port}'"
1432    if service:
1433        pg_conn += f"service='{service}'"
1434    if user:
1435        pg_conn += f"user='{user}'"
1436    if password:
1437        pg_conn += f"password='{password}'"
1438
1439    if schemas:
1440        if isinstance(schemas, str):
1441            schemas = [*schemas.split(",")]
1442
1443        active_schema = schemas[0]
1444        str_schemas = ",".join(schemas)
1445        pg_conn_str = f"{pg_conn} active_schema='{active_schema}' schemas='{str_schemas}'"
1446
1447    return pg_conn

Retorna una cadena de conexion a una base de datos postgis

Arguments:
  • dbname:
  • host:
  • port:
  • service:
  • user:
  • password:
  • schemas (str | list[str]=None): Esquema de la base de datos
Returns:

str

def get_ds_postgis( dbname=None, host=None, port=None, service=None, user=None, password=None, schemas=None, pg_conn_str=None, logger: logging.Logger = None, **open_options) -> osgeo.gdal.Dataset:
1450def get_ds_postgis(dbname=None, host=None, port=None, service=None, user=None, password=None, schemas=None,
1451                   pg_conn_str=None, logger: Logger = None, **open_options) -> ogr.DataSource:
1452    """
1453    Retorna cached datasource postgis GDAL
1454
1455    Args:
1456        dbname (str=None): Nombre de la base de datos
1457        host (str=None): Host de la base de datos
1458        port (str=None): Puerto de la base de datos
1459        service (str=None): nombre service definido en pg_service.conf (env var PGSERVICE)
1460        user (str=None): Usuario de la base de datos
1461        password (str=None): Contraseña de la base de datos
1462        schemas (str | list[str]=None): Esquema de la base de datos
1463        pg_conn_str (str=None): cadena conexion tipo "PG:user=... password=... service=...". Si viene informada no se hace caso a ningún otro argumento
1464        logger (logging.Logger=None): (opcional) logger para LOG.
1465        **open_options (str): Opciones para abrir el Datasource de Postgis. Vease https://gdal.org/en/stable/drivers/vector/pg.html#dataset-open-options
1466    Returns:
1467        datasource_gdal (osgeo.gdal.DataSource)
1468    """
1469    ds_gdal: ogr.DataSource | None = None
1470
1471    if not pg_conn_str:
1472        pg_conn_str = pg_string_connection(dbname=dbname, host=host, port=port, service=service, user=user,
1473                                           password=password)
1474    elif not pg_conn_str.startswith('PG:'):
1475        pg_conn_str = f"PG:{pg_conn_str}"
1476
1477    # Retorna la pg_conn_str con el valor despues de 'password=' substituido por '****'
1478    pg_id_ds = re.sub(r"(?i)(password\s*=\s*')([^']*)(')", r"\1****\3", pg_conn_str)
1479    
1480    if ds_gdal := _CACHE_DS_POSTGIS.get(pg_id_ds):
1481        # Antes de retornar verificar que la conexión sigue siendo correcta
1482        try:
1483            _ = ds_gdal.GetLayerCount()
1484            lyr_test = ds_gdal.ExecuteSQL("SELECT 1")
1485            if lyr_test is not None:
1486                ds_gdal.ReleaseResultSet(lyr_test)
1487            return ds_gdal
1488        except Exception:
1489            _CACHE_DS_POSTGIS.pop(pg_id_ds, None)
1490
1491    if not logger:
1492        logger = utils_logging.get_base_logger()
1493
1494    error_exc = None
1495    try:
1496        ds_gdal: ogr.DataSource = ds_postgis(
1497            pg_conn_str=pg_conn_str,
1498            schemas=schemas,
1499            **open_options)
1500    except Exception as exc:
1501        error_exc = f"Error: {exc}"
1502    finally:
1503        if ds_gdal:
1504            logger.info(f"Conectado a la base de datos POSTGIS del repositorio GIS: {pg_id_ds}")
1505        else:
1506            msg_error = f"No se ha podido conectar a la base de datos POSTGIS [{pg_id_ds}] del repositorio GIS. " \
1507                        f"{error_exc if error_exc else ''} "
1508            logger.error(msg_error)
1509            raise ConnectionError(msg_error)
1510
1511    _CACHE_DS_POSTGIS[pg_id_ds] = ds_gdal
1512
1513    return ds_gdal

Retorna cached datasource postgis GDAL

Arguments:
  • dbname (str=None): Nombre de la base de datos
  • host (str=None): Host de la base de datos
  • port (str=None): Puerto de la base de datos
  • service (str=None): nombre service definido en pg_service.conf (env var PGSERVICE)
  • user (str=None): Usuario de la base de datos
  • password (str=None): Contraseña de la base de datos
  • schemas (str | list[str]=None): Esquema de la base de datos
  • pg_conn_str (str=None): cadena conexion tipo "PG:user=... password=... service=...". Si viene informada no se hace caso a ningún otro argumento
  • logger (logging.Logger=None): (opcional) logger para LOG.
  • **open_options (str): Opciones para abrir el Datasource de Postgis. Vease https://gdal.org/en/stable/drivers/vector/pg.html#dataset-open-options
Returns:

datasource_gdal (osgeo.gdal.DataSource)

def reset_sequence_layer_postgis(ds_postgis, nom_layer):
1516def reset_sequence_layer_postgis(ds_postgis, nom_layer):
1517    """
1518    Resetea la secuencia de la layer PostGIS indicada
1519
1520    Args:
1521        ds_postgis (osgeo.ogr.DataSource): datasource de la ddbb PostGIS:
1522        nom_layer (str): nombre de la layer PostGIS
1523
1524    Returns:
1525        bool: True si se ha reseteado correctamente
1526    """
1527    ok = False
1528    layer_dest = ds_postgis.GetLayerByName(nom_layer)
1529    fid_column = layer_dest.GetFIDColumn()
1530    get_seq_sql = f"SELECT pg_get_serial_sequence('{nom_layer}', '{fid_column}') FROM information_schema.columns " \
1531                  f"WHERE table_name = '{nom_layer}' AND column_default LIKE 'nextval%'"
1532
1533    if res_get_seq := ds_postgis.ExecuteSQL(get_seq_sql):
1534        seq_name = res_get_seq.GetNextFeature().GetField(0)
1535        ds_postgis.StartTransaction()
1536        reset_seq = f"SELECT setval('{seq_name}', 1, false);"
1537        ds_postgis.ExecuteSQL(reset_seq)
1538        ds_postgis.CommitTransaction()
1539        ok = True
1540
1541    return ok

Resetea la secuencia de la layer PostGIS indicada

Arguments:
  • ds_postgis (osgeo.ogr.DataSource): datasource de la ddbb PostGIS:
  • nom_layer (str): nombre de la layer PostGIS
Returns:

bool: True si se ha reseteado correctamente