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

Devuelve diccionario con los driver gdal para fichero vectorial

Returns:

dict

def format_nom_column(nom_col):
820def format_nom_column(nom_col):
821    """
822
823    Args:
824        nom_col:
825
826    Returns:
827        str
828    """
829    return nom_col.replace(" ", "_")
Arguments:
  • nom_col:
Returns:

str

def namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld=None):
832def namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld=None):
833    """
834    Devuelve namedTuple con los campos del layer pasado por parametro
835
836    Args:
837        layer_gdal:
838        extract_affix_geom_fld (str=None):
839
840    Returns:
841        namedtuple: con nombre "gdalFeatDef_{NOM_LAYER}" y con los campos de la layer
842    """
843    camps_layer = set()
844    for fld in fields_layer_gdal(layer_gdal):
845        camps_layer.add(format_nom_column(fld.GetNameRef()))
846
847    for nom_geom in geoms_layer_gdal(layer_gdal, extract_affix=extract_affix_geom_fld):
848        camps_layer.add(format_nom_column(nom_geom))
849
850    nom_layer = layer_gdal.GetName().upper().split(".")[0].replace("-", "_")
851    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):
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):
854def feats_layer_ds_gdal(ds_gdal, nom_layer=None, filter_sql=None):
855    """
856    Itera las features (registros de una layer de gdal) y los devuelve como un namdetuple
857
858    Args:
859        ds_gdal: datasource gdal
860        nom_layer (str=None): Si no viene informado cogerá la primera layer que encuentre en el datasource
861        filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada.
862                            Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
863
864    Returns:
865        ogr.Feature, ogr.Geometry, namedtuple_layer_gdal
866    """
867    if not nom_layer:
868        layer_gdal = ds_gdal.GetLayer()
869    else:
870        layer_gdal = ds_gdal.GetLayerByName(nom_layer)
871
872    if layer_gdal:
873        for feat, geom, vals in feats_layer_gdal(layer_gdal, filter_sql=filter_sql):
874            yield feat, geom, vals

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)
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_'):
877def feats_layer_gdal(layer_gdal, nom_geom=None, filter_sql=None, extract_affix_geom_fld=PREFFIX_GEOMS_LAYERS_GDAL):
878    """
879    Itera las features (registros de una layer de gdal) y los devuelve como un namdtuple
880
881    Args:
882        layer_gdal (ogr.Layer):
883        nom_geom (str=None): Por defecto la geometria activa o principal
884        filter_sql (str=None): Si viene informado se aplicará como filtro sql a la layer seleccionada.
885                            Utiliza OGR SQL (vease https://www.gdal.org/ogr_sql.html)
886        extract_affix_geom_fld (str=SUFFIX_GEOMS_LAYERS_GDAL): Por defecto quita el sufijo 'geom_' a los campos geom
887
888    Returns:
889        ogr.Feature, ogr.Geometry, namedtuple_layer_gdal
890    """
891    layer_gdal.ResetReading()
892    ntup_layer = namedtuple_layer_gdal(layer_gdal, extract_affix_geom_fld)
893    n_geoms_layer = {nom_geom_feat: fix_affix_geom_name_layer_gdal(nom_geom_feat, layer_gdal, extract_affix_geom_fld)
894                     for nom_geom_feat in geoms_layer_gdal(layer_gdal)}
895
896    if filter_sql:
897        layer_gdal.SetAttributeFilter(filter_sql)
898
899    def vals_feature_gdal(feat_gdal):
900        vals = {}
901        feat_items = feat_gdal.items()
902        # In GDAL 3.x, items() returns a dict
903        if isinstance(feat_items, dict):
904            for camp, val in feat_items.items():
905                vals[format_nom_column(camp)] = val
906        else:
907            # Older GDAL versions may return different structure
908            for camp, val in feat_items:
909                vals[format_nom_column(camp)] = val
910
911        for camp_geom, camp_geom_val in n_geoms_layer.items():
912            idx_geom = feat_gdal.GetGeomFieldIndex(camp_geom)
913            if idx_geom >= 0:
914                vals[camp_geom_val] = feat_gdal.GetGeomFieldRef(idx_geom)
915
916        return vals
917
918    if layer_gdal:
919        for f_tab in layer_gdal:
920            idx_geom = f_tab.GetGeomFieldIndex(nom_geom) if nom_geom else -1
921            yield f_tab, \
922                f_tab.GetGeomFieldRef(idx_geom) if idx_geom >= 0 else f_tab.geometry(), \
923                ntup_layer(**vals_feature_gdal(f_tab))
924
925        layer_gdal.ResetReading()

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

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
Returns:

ogr.Feature, ogr.Geometry, namedtuple_layer_gdal

def distinct_vals_camp_layer_gdal(layer_gdal, nom_camp, filter_sql=None):
928def distinct_vals_camp_layer_gdal(layer_gdal, nom_camp, filter_sql=None):
929    """
930    Devuelve set con distintos valores para el campo indicado del layer GDAL indicada
931
932    Args:
933        layer_gdal (ogr.Layer):
934        nom_camp (str):
935        filter_sql (str=None):
936
937    Returns:
938        set
939    """
940    if nom_camp.upper() not in cols_layer_gdal(layer_gdal):
941        raise Exception("Argumento :NOM_CAMP = '{}' erróneo ya que "
942                        "LAYER_GDAL no contiene el campo indicado".format(nom_camp))
943
944    return {getattr(nt_feat, nom_camp.upper())
945            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):
948def fields_layer_gdal(layer_gdal):
949    """
950    Itera sobre los FieldDefn de una layer gdal
951
952    Args:
953        layer_gdal:
954
955    Yields:
956        osgeo.ogr.FieldDefn
957    """
958    layer_def = layer_gdal.GetLayerDefn()
959    for i in range(0, layer_def.GetFieldCount()):
960        yield layer_def.GetFieldDefn(i)
961
962    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):
965def geom_fields_layer_gdal(layer_gdal):
966    """
967    Itera sobre los GeomFieldDefn de una layer gdal
968
969    Args:
970        layer_gdal:
971
972    Yields:
973        osgeo.ogr.GeomFieldDefn
974    """
975    layer_def = layer_gdal.GetLayerDefn()
976    for i in range(0, layer_def.GetGeomFieldCount()):
977        yield layer_def.GetGeomFieldDefn(i)
978
979    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):
982def nom_layers_datasource_gdal(ds_gdal):
983    """
984
985    Args:
986        ds_gdal (ogr.Datasource:
987
988    Returns:
989        set
990    """
991    return {l.GetName() for l in ds_gdal}
Arguments:
  • ds_gdal (ogr.Datasource:
Returns:

set

def cols_layer_gdal(layer_gdal):
 994def cols_layer_gdal(layer_gdal):
 995    """
 996    Retorna lista con las columnas de una layer gdal
 997
 998    Args:
 999        layer_gdal:
1000
1001    Returns:
1002        set
1003    """
1004    camps = set()
1005    for fd in fields_layer_gdal(layer_gdal):
1006        # camps.add(fd.GetName().upper())
1007        camps.add(fd.GetNameRef())
1008
1009    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):
1012def geoms_layer_gdal(layer_gdal, extract_affix=None):
1013    """
1014    Retorna lista con las columnas geométricas de una layer gdal
1015
1016    Args:
1017        layer_gdal:
1018        extract_affix (str=None=): if informed extract the affix passed
1019
1020    Returns:
1021        set
1022    """
1023    camps_geom = set()
1024    for gdf in geom_fields_layer_gdal(layer_gdal):
1025        # camps_geom.add(gdf.GetName().upper())
1026        name_ref = gdf.GetNameRef()
1027        if extract_affix:
1028            name_ref = fix_affix_geom_name_layer_gdal(name_ref, layer_gdal, extract_affix)
1029        camps_geom.add(name_ref)
1030
1031    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_'):
1034def fix_affix_geom_name_layer_gdal(geom_name, layer_gdal, affix=PREFFIX_GEOMS_LAYERS_GDAL):
1035    """
1036    Extract affix (default=PREFFIX_GEOMS_LAYERS_GDAL) from name geoms if correspond with other column
1037    Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'
1038
1039    Args:
1040          geom_name (str):
1041          layer_gdal:
1042          affix (str=PREFFIX_GEOMS_LAYERS_GDAL):
1043
1044    Returns:
1045        str
1046    """
1047    cols_layer = cols_layer_gdal(layer_gdal)
1048    geom_name_layer = geom_name
1049    if geom_name.lower().startswith(affix):
1050        geom_name_aux = re.sub(affix, '', geom_name, flags=re.IGNORECASE)
1051        if geom_name_aux.lower() in (col.lower() for col in cols_layer):
1052            geom_name_layer = geom_name_aux
1053
1054    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):
1057def add_layer_gdal_to_ds_gdal(ds_gdal, layer_gdal, nom_layer=None, lite=False, srs_epsg_code=None, multi_geom=False,
1058                              nom_geom=None, null_geoms=False, **extra_opt_list):
1059    """
1060    Añade una layer_gdal a un datasource_gdal. Si es una layer con multigeometrias las separa en una layer por geometria
1061
1062    Args:
1063        ds_gdal (osgeo.ogr.Datasource):
1064        layer_gdal (osgeo.ogr.Layer):
1065        nom_layer (str=None):
1066        lite (bool=False):
1067        srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir
1068                                  las geometrias
1069        nom_geom (str=None): nombre de geometria de layer origen (layer_gdal) que se copiará, si no todas
1070        multi_geom (bool=False): Si el DS_GDAL destino permite multigeometria
1071        null_geoms (bool=False): Indica si se admitirán registros con NULL geoms. Por defecto NO
1072        **extra_opt_list (str): Lista claves-valores de opciones para createLayer del driver de ds_gdal indicado
1073
1074    Returns:
1075        new_layers_ds_gdal (list)
1076    """
1077    new_layers_ds_gdal = []
1078
1079    if not nom_layer:
1080        nom_layer = layer_gdal.GetName()
1081
1082    geoms_layer = geoms_layer_gdal(layer_gdal)
1083    if nom_geom:
1084        if nom_geom.upper() not in (g.upper() for g in geoms_layer):
1085            Exception("!ERROR! - Nombre de geometria '{}' no existe en la layer GDAL origen")
1086        else:
1087            geoms_layer = (nom_geom,)
1088
1089    if geoms_layer:
1090        tol = None
1091        if lite:
1092            tol = SIMPLIFY_TOLERANCE
1093
1094        nom_layer_base = nom_layer.split("-")[0]
1095        if not multi_geom:
1096            for geom_name in geoms_layer:
1097                # Fix GDAL > 3.4 where geoms on GeoCSV arrive with preffix 'geom_'
1098                geom_name_layer = fix_affix_geom_name_layer_gdal(geom_name, layer_gdal)
1099
1100                nom_layer = "{}-{}".format(nom_layer_base, geom_name_layer).lower()
1101                extra_opt_list["GEOMETRY_NAME"] = "GEOMETRY_NAME={}".format(geom_name_layer)
1102                lyr = create_layer_from_layer_gdal_on_ds_gdal(ds_gdal, layer_gdal, nom_layer, geom_name,
1103                                                              tolerance_simplify=tol, null_geoms=null_geoms,
1104                                                              epsg_code_dest=srs_epsg_code, **extra_opt_list)
1105                new_layers_ds_gdal.append(lyr)
1106        else:
1107            lyr = create_layer_from_layer_gdal_on_ds_gdal(ds_gdal, layer_gdal, nom_layer, exclude_cols_geoms=False,
1108                                                          tolerance_simplify=tol, null_geoms=True,
1109                                                          epsg_code_dest=srs_epsg_code, **extra_opt_list)
1110            new_layers_ds_gdal.append(lyr)
1111    else:
1112        if ds_gdal.GetDriver().GetName() == 'PostgreSQL':
1113            extra_opt_list.update(dict(
1114                precision=extra_opt_list.get('precision', 'PRECISION=NO')
1115            ))
1116
1117        lyr = copy_layer_gdal_to_ds_gdal(layer_gdal, ds_gdal, nom_layer.lower(), **extra_opt_list)
1118        new_layers_ds_gdal.append(lyr)
1119
1120    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):
1123def copy_layers_gpkg(ds_gpkg, driver, dir_base, lite=False, srs_epsg_code=None, zipped=True):
1124    """
1125
1126    Args:
1127        ds_gpkg (osgeo.ogr.Datasource):
1128        driver (str):
1129        dir_base (str=None):
1130        lite (bool=False):
1131        srs_epsg_code (int=None): codigo EPSG para el sistema de coordenadas con el que se quieren convertir
1132                                  las geometrias
1133        zipped (bool=False):
1134
1135    Returns:
1136        num_layers (int)
1137    """
1138    num_layers = 0
1139    subdir_drvr = os.path.normpath(os.path.join(dir_base, driver.upper()))
1140    utils.create_dir(subdir_drvr)
1141
1142    for layer_gpkg in (ds_gpkg.GetLayer(id_lyr) for id_lyr in range(ds_gpkg.GetLayerCount() - 1)):
1143        if driver == "GPKG":
1144            nom_ds, ext = utils.split_ext_file(os.path.basename(ds_gpkg.GetName()))
1145        else:
1146            nom_ds = f"{layer_gpkg.GetName()}".lower()
1147
1148        ds_gdal, existia = datasource_gdal_vector_file(driver, nom_ds, subdir_drvr)
1149
1150        add_layer_gdal_to_ds_gdal(ds_gdal, layer_gpkg, lite=lite, srs_epsg_code=srs_epsg_code)
1151
1152        num_layers += 1
1153
1154    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):
1157def set_csvt_for_layer_csv(path_csv, **tipus_camps):
1158    """
1159    Crea/Modifica el CSVT asociado con los tipos indicados para cada columna
1160
1161    Args:
1162        path_csv (str):
1163        **tipus_camps: clave=valor con el nombre del campo y el tipo de campo asociado (p.e. String(25), WKT, Integer,...)
1164
1165    Returns:
1166        path_csvt (str)
1167    """
1168    lyr_csv, nom_layer, ds_lyr = layer_gdal_from_file(path_csv, "CSV")
1169    if not lyr_csv:
1170        print_warning("!ATENCIO! - No s'ha pogut obrir la layer CSV '{}'".format(path_csv))
1171        return
1172
1173    path_lyr_csvt = os.path.join(os.path.dirname(path_csv), "{}.csvt".format(nom_layer))
1174    tips_lyr = {}
1175    for fld in fields_layer_gdal(lyr_csv):
1176        tip_fld = fld.GetFieldTypeName(fld.GetType())
1177        sufix = ""
1178        w = fld.GetWidth()
1179        if w:
1180            sufix = "{}".format(w)
1181        p = fld.GetPrecision()
1182        if p:
1183            sufix += ".{}".format(p)
1184        if sufix:
1185            tip_fld += "({})".format(sufix)
1186
1187        tips_lyr[fld.name.upper()] = tip_fld
1188
1189    for nom_camp, tip_camp in tipus_camps.items():
1190        nom_camp = nom_camp.upper()
1191        if nom_camp not in tips_lyr:
1192            print_warning("!ATENCIO! - Camp '{}' no existeix sobre la layer CSV '{}'".format(nom_camp, path_csv))
1193            continue
1194
1195        tips_lyr[nom_camp] = tip_camp
1196
1197    with open(path_lyr_csvt, mode="w", encoding="utf8") as f_csvt:
1198        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):
1201def zip_and_clean_ds_csv(path_csv):
1202    """
1203    # Zipea datasource osgeo CSV y como GDAL no crea los tipos WKT para las geometrias en el CSVT se fuerza
1204
1205    Args:
1206        path_csv (str): path Datasource osgeo CSV
1207
1208    Returns:
1209        zip_path (str)
1210    """
1211    ds_gdal_csv, dummy = datasource_gdal_vector_file("CSV", os.path.basename(path_csv).split(".")[0],
1212                                                     os.path.dirname(path_csv))
1213    layer_csv = ds_gdal_csv.GetLayer(0)
1214    tips_geoms = {gn: "WKT" for gn in
1215                  (*geoms_layer_gdal(layer_csv, extract_affix=PREFFIX_GEOMS_LAYERS_GDAL),
1216                   *geoms_layer_gdal(layer_csv, extract_affix=PREFFIX_GEOMS_LAYERS_GDAL_CSV))}
1217    path_csv = ds_gdal_csv.GetDescription()
1218    ds_gdal_csv = None  # Cerramos el datasource para que se guarden los cambios
1219    if path_csv and os.path.exists(path_csv):
1220        if tips_geoms:
1221            set_csvt_for_layer_csv(path_csv, **tips_geoms)
1222        rename_wkt_geoms_csv(path_csv)
1223    dir_base_csv = os.path.dirname(path_csv)
1224    nom_layer = os.path.basename(path_csv).split(".")[0]
1225    l_files_csv = [os.path.join(dir_base_csv, nf) for nf in os.listdir(dir_base_csv)
1226                   if nf.lower().startswith(nom_layer.lower()) and
1227                   any(nf.lower().endswith(ext) for ext in ('csv', 'csvt'))]
1228    zip_path = utils.zip_files(os.path.join(dir_base_csv, "{}.zip".format(nom_layer)), l_files_csv)
1229    if zip_path:
1230        for fl_csv in l_files_csv:
1231            os.remove(fl_csv)
1232
1233    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):
1236def convert_angle(pt_xy, deg_ang, orig_srs, dest_srs):
1237    """
1238
1239    Args:
1240        pt_xy (tuple):
1241        deg_ang (float):
1242        orig_srs (osr.SpatialReference):
1243        dest_srs (osr.SpatialReference):
1244
1245    Returns:
1246
1247    """
1248    trans = osr.CoordinateTransformation(orig_srs, dest_srs)
1249    x, y = pt_xy
1250    dx = math.sin(math.radians(deg_ang)) * 0.00000001
1251    dy = math.cos(math.radians(deg_ang)) * 0.00000001
1252    pt1 = ogr.CreateGeometryFromWkt("POINT ({} {})".format(x, y))
1253    pt2 = ogr.CreateGeometryFromWkt("POINT ({} {})".format(x + dx, y + dy))
1254    pt1.Transform(trans)
1255    pt2.Transform(trans)
1256    x1, y1, z1 = pt1.GetPoint()
1257    x2, y2, z2 = pt2.GetPoint()
1258
1259    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):
1262def transform_ogr_geom(a_ogr_geom, from_espg_code, to_epsg_code):
1263    """
1264    Transforma una geometria OGR según los EPSG indicados
1265
1266    Args:
1267        a_ogr_geom (ogr.geometry): una geometria del tipo OGR
1268        from_espg_code (int): codigo numérico del EPSG actual para la geometria
1269        to_epsg_code (int): codigo numérico del EPSG al que se quiere transformar
1270
1271    Returns:
1272        ogr.geometry
1273    """
1274    source = osr.SpatialReference()
1275    source.ImportFromEPSG(from_espg_code)
1276
1277    target = osr.SpatialReference()
1278    target.ImportFromEPSG(to_epsg_code)
1279
1280    a_transform = osr.CoordinateTransformation(source, target)
1281    a_ogr_geom.Transform(a_transform)
1282
1283    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( dbname='POSTGRES', host='localhost', port='5432', user='postgres', password='postgres', schema='public'):
1286def ds_postgis(dbname='POSTGRES', host='localhost', port='5432', user='postgres', password='postgres', schema='public'):
1287    """
1288    Retorna datasource GDAL para ddbb postgis
1289
1290    Args:
1291        dbname:
1292        host:
1293        port:
1294        user:
1295        password:
1296        schema:
1297
1298    Returns:
1299        osgeo.ogr.DataSource
1300    """
1301    pg_conn = f"PG:dbname='{dbname}' host='{host}' port='{port}' user='{user}' password='{password}' active_schema='{schema}'"
1302    drvr, exts = driver_gdal('PostgreSQL')
1303    return drvr.Open(pg_conn, 1)

Retorna datasource GDAL para ddbb postgis

Arguments:
  • dbname:
  • host:
  • port:
  • user:
  • password:
  • schema:
Returns:

osgeo.ogr.DataSource

def reset_sequence_layer_postgis(ds_postgis, nom_layer):
1306def reset_sequence_layer_postgis(ds_postgis, nom_layer):
1307    """
1308    Resetea la secuencia de la layer PostGIS indicada
1309
1310    Args:
1311        ds_postgis (osgeo.ogr.DataSource): datasource de la ddbb PostGIS:
1312        nom_layer (str): nombre de la layer PostGIS
1313
1314    Returns:
1315        bool: True si se ha reseteado correctamente
1316    """
1317    ok = False
1318    layer_dest = ds_postgis.GetLayerByName(nom_layer)
1319    fid_column = layer_dest.GetFIDColumn()
1320    get_seq_sql = f"SELECT pg_get_serial_sequence('{nom_layer}', '{fid_column}') FROM information_schema.columns " \
1321                  f"WHERE table_name = '{nom_layer}' AND column_default LIKE 'nextval%'"
1322
1323    if res_get_seq := ds_postgis.ExecuteSQL(get_seq_sql):
1324        seq_name = res_get_seq.GetNextFeature().GetField(0)
1325        ds_postgis.StartTransaction()
1326        reset_seq = f"SELECT setval('{seq_name}', 1, false);"
1327        ds_postgis.ExecuteSQL(reset_seq)
1328        ds_postgis.CommitTransaction()
1329        ok = True
1330
1331    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