Skip to content

Instantly share code, notes, and snippets.

@jtornero
Last active March 17, 2022 19:08
Show Gist options
  • Save jtornero/4ae64933e99e21d27f5044b4f42e78ec to your computer and use it in GitHub Desktop.
Save jtornero/4ae64933e99e21d27f5044b4f42e78ec to your computer and use it in GitHub Desktop.
Vector layer batch creation with PyQGis
# Ejemplo de cómo crear capas en QGIS y dotarlas de simbología basada en reglas
# Las fuentes en las que me he basado para crearlo son principalmente el QGIS Cookbook,
# algún artículo de Anita Grase y otras fuentes como
# https://gis.stackexchange.com/questions/86770/how-to-programmatically-set-a-rule-based-renderer-in-qgis
# Cuando pueda subir un artículo más detallado a la wiki de qgis
# se acompañará de estas fuentes en detalle con enlaces,etc, para referencia y consulta
# En mi caso creo una capa por cada una de estas especies de peces,
que meto en un diccionario junto al color que tendrá la capa
especies={'ANE':'#00ff00',
'PIL':'#0000ff',
'MAS':'#00ffff',
'MAC':'#ff0000',
'HOM':'#ffff00',
'HMM':'#ffffc0',
'JAA':'#ff7f00',
'BOG':'#c000c0',
'MAV':'#b5fe5f',
'SNS':'#c8c8c8',
'POA':'#c8c8c8',
'BOC':'#c8c8c8'}
# Esto es una función para crear el renderizador basado en reglas
# para representar NASC (Nautical Area Scattering Coefficient)
# Le pasamos el color del símbolo y el máximo de NASC para crear
# la simbología.
# Al principio lo intenté haciendo una subclase de QgsRuleBasedRenderer,
# pero según comento Víctor Olaya es necesario reimplementar unos cuantos métodos
# lo que queda fuera de mi alcance, por lo que se crea un QgsRuleBasedRenderer
# y luego se va modificando.
def creaRendererNASC(color,maxNASC):
# Se crea un objeto QgsRuleBasedRenderer, con un símbolo por defecto
# que es al que luego iremos añadiendo capas de simbología, etc. según corresponda
renderer=QgsRuleBasedRenderer(QgsSymbol.defaultSymbol(0))
# Tenemos que convertir el marker color a un QColor y añadirle transparencia
markerColor=QColor(color)
markerColor.setAlpha(127)
# La simbología consta de varias capas dependiendo ddel rango de valores.
# Pero hay un caso especial, el valor 0 que lo representamos como una cruz
# Y el punto central de cada símbolo, que es un punto pequeño.
# Esto lo creamos como capas de símbolo e iremos poniendo cada una donde toque
# Regla 0, shape=11 significa que el simbolo basico es una cruz
markerZero=QgsSimpleMarkerSymbolLayer(shape=11,size=0.7)
# Punto central, el marcador es un punto muy pequeño
markerDot=QgsSimpleMarkerSymbolLayer(size=0.2)
# En esta simbología cada regla tiene un símbolo de un tamaño dependiendo del
# rango de valores. En esta lista se almacenan los tamaños del símbolo principal de la capa
markerSizes=[0.5,1.83,3.96,6.55,9.55,12.90]
# Mediante el logaritmo del valor maximo hallamos el rango
# máximo del NASC, que luego servirá como índice para buscar el tamaño
# del símbolo y también para hallar el intervalo de la regla
rangeNASC=math.log(maxNASC,10)
if rangeNASC < 0:
rangeNASC = 0
elif rangeNASC >= 0:
rangeNASC = math.trunc(rangeNASC)+1
if rangeNASC>5:
rangeNASC = 5
#Dejamos el numero de decimales en funcion del valor de maxNASC
if rangeNASC==0:
maxNASC=round(maxNASC,2)
elif rangeNASC==1:
maxNASC=round(maxNASC,1)
else:
maxNASC=math.trunc(maxNASC)
# Tomamos todos los valores de tamaño de marcador hasta el rangeNASC
# y después apartamos el último ya que tiene un tratamiento diferente
sizes=markerSizes[0:rangeNASC+1]
lastMarker=sizes.pop()
# Esto lo explica bastante bien el cookbook y otro artículos.
# Basicamente lo que hacemos es tomar la regla base del nuevo renderizador
# Para clonarla después, algo así como usarla de plantilla para el resto de reglas
root_rule=renderer.rootRule()
# En primer lugar añadimos la regla correspomndiente al valor 0
rule=root_rule.children()[0].clone()
rule.symbol().appendSymbolLayer(markerZero)
rule.symbol().deleteSymbolLayer(0)
rule.setFilterExpression('nasc_especie=0')
rule.setLabel('0')
root_rule.appendChild(rule)
# Ahora creamos cada una de las demás reglas,
# siguiendo el mismo esquema de antes
for indice,markerSize in enumerate(sizes):
rule=root_rule.children()[0].clone()
print (markerSize,indice)
rule.symbol().appendSymbolLayer(QgsSimpleMarkerSymbolLayer(size=markerSize,color=markerColor))
rule.symbol().deleteSymbolLayer(0)
if indice==0: #La regla de filtrado es diferente y la etiqueta también
rule.setFilterExpression('nasc_especie<1')
rule.setLabel('<1')
root_rule.appendChild(rule)
else:
rangeMin=10**(indice-1)
rangeMax=10**(indice)
rule.setLabel('%i - %i' %(rangeMin,rangeMax))
rule.setFilterExpression('nasc_especie>=%i and nasc_especie<%i' %(rangeMin,rangeMax))
root_rule.appendChild(rule)
# La última regla es un poco diferente, por el el tope de la regla
@ es el valor máximo y, como el caso O, se trata aparte
rule=root_rule.children()[0].clone()
rule.symbol().appendSymbolLayer(markerDot)
rule.symbol().appendSymbolLayer(QgsSimpleMarkerSymbolLayer(size=lastMarker,color=markerColor))
rule.symbol().deleteSymbolLayer(0)
rangeMin=10**(rangeNASC-1)
rangeMax=10**(rangeNASC)
rule.setLabel('%i - %s (%s)' %(rangeMin,rangeMax,maxNASC))
rule.setFilterExpression('nasc_especie>=%i and nasc_especie<%i' %(rangeMin,rangeMax))
root_rule.appendChild(rule)
root_rule.removeChildAt(0)
return renderer
# Con esta función creamos el sql para luego establecer el origen de datos al crear la capa,
# Se podría haber creado dentro de la función de creación de capas pero preferí hacer así
def creaSql(camp,especie):
"""Crea el sql para generar las capas que servirán como base para los mapas de NASC"""
sql="""(with rclasses as( select interv as intervalo, array_to_string(array_agg(DISTINCT region_class),',') as region_classes from regiones where regiones.camp='{0}' group by interv ) SELECT generalmillas.id,generalmillas.intervalo, generalmillas.profundidad, coalesce(round(generalmillas.{1},4),0) as nasc_especie, case when coalesce(nasc_total,0)>0 then round({1}/nasc_total,4) else coalesce(round(nasc_total,4),0) end as porc_nasc, region_classes, generalmillas.millapun FROM generalmillas left join rclasses using (intervalo) WHERE generalmillas.camp='{0}' and generalmillas.valida=true ORDER BY generalmillas.intervalo )""".format(camp,especie)
return sql
# Esta es la función que crea la capa vectorial, el renderizador, se lo asigna y carga la capa
# generada en QGIS
def creaCapaNASC(camp,especie,colorCapa):
nombreCapa='%s_NASC_%s' %(camp,especie)
# Creamos la consulta que nos servirá como origen de datos
sql=creaSql(camp,especie)
# Primero creamos el URI y luego la capa
uriCapa=QgsDataSourceUri()
uriCapa.setConnection('localhost','5432','biofuturo','antares','2$trabuco')
uriCapa.setDataSource("",sql,"millapun","","id")
layer=QgsVectorLayer(uriCapa.uri(),nombreCapa,"postgres")
# Obtenemos el valor máximo de NASC para la capa, se lo tendremos que pasar
# a la funciń que crea el rederizador
nascMax=layer.maximumValue(layer.fields().indexFromName('nasc_especie'))
# Añadimos la capa e intentamos convertir a float. Esta es la versión
# chapucera para evitar que la cosa casque si en algún momento devolviese un null
# Cosa que no debería hacer ya que introduje en la consulta un coalesce para evitar
# precistamente esto, pero bueno
try:
nascMax=float(nascMax)
except:
nascMax=0
# Con el dato de nascMax creamos el rederizador, se lo acoplamos a la capa
# y cargamos la capa en QGIS
renderer=creaRendererNASC(colorCapa,nascMax)
layer.setRenderer(renderer)
QgsProject.instance().addMapLayer(layer)
# Para hacer el lote de capas, procedemos así
spes=especies.keys()
for spe in spes:
creaCapaNASC('ECOCADIZ-R-2019',spe,especies[spe])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment