fiona

Note

Fecha Autores
25 Junio 2014

©2014 Fernando González Cortés

Excepto donde quede reflejado de otra manera, la presente documentación se halla bajo licencia : Creative Commons (Creative Commons - Attribution - Share Alike: http://creativecommons.org/licenses/by-sa/3.0/deed.es)

Lectura de una juego de datos

Ejercicio: Hacer script que muestra información de un fichero que se le pasa como parámetro:

#! /usr/bin/env python

import sys
import fiona

file = sys.argv[1]

d = fiona.open(file)

print "crs:", d.crs
print "bounds:", d.bounds
print "driver:", d.driver
print "encoding:", d.encoding
print "schema", d.schema

d.close()

Lectura de objetos

Es posible iterar secuencialmente por la fuente de datos con un bucle:

for i in d:
        print i

Fiona ofrece los contenidos del shapefile como objetos python. Invocando el método next podemos obtener el primer registro de una fuente de datos:

{
    'geometry': {
        'type': 'Point',
        'coordinates': (914347.8748615, 249079.07840056275, 0.0)
    },
    'type': 'Feature',
    'id': '159',
    'properties': OrderedDict([
        (u'cat', 160.0),
        (u'OBJECTID', 160.0),
        (u'AREA', 0.0),
        (u'PERIMETER', 0.0),
        (u'HLS_', 160.0),
        (u'HLS_ID', 160.0),
        (u'NAME', u'TheOuterBanksHospital(licensepending)'),
        (u'ADDRESS', u'4800SCroatanHwy'),
        (u'CITY', u'NagsHead'),
        (u'ZIP', u'27959'),
        (u'COUNTY', u'Dare'),
        (u'PHONE', None),
        (u'CANCER', u'?'),
        (u'POLYGONID', 0.0),
        (u'SCALE', 1.0),
        (u'ANGLE', 0.0)
    ])
}

Ejercicio: hacer script que muestre todos los valores de un campo que se pasa como parámetro (fiona_show_field.py):

#! /usr/bin/env python

import sys
import fiona

file = sys.argv[1]
fieldName = sys.argv[2]

d = fiona.open(file)

for feature in d:
        print feature["properties"][fieldName]


d.close()

Ejemplo:

./fiona_show_field.py ~/data/north_carolina/shape/hospitals.shp NAME

Operaciones con campos

Llevando el ejemplo anterior un paso más allá, podemos hacer un programita que en lugar de mostrar los campos por la consola lo que haga sea modificar las features eliminando todos los campos menos el seleccionado (fiona_projection.py):

#! /usr/bin/env python

import sys
import fiona

file = sys.argv[1]
fieldName = sys.argv[2]

d = fiona.open(file)

for feature in d:
        for property in feature["properties"]:
                if property != fieldName:
                        del feature["properties"][property]

        print feature

d.close()

Ejemplo:

./fiona_projection.py ~/data/north_carolina/shape/hospitals.shp NAME

Y generalizando todavía más, podemos obtener una serie de expresiones como parámetros que serán los nuevos campos (fiona_projection_ops.py):

#! /usr/bin/env python

import sys
import fiona
import re

file = sys.argv[1]

d = fiona.open(file)

for feature in d:
        newFeature = {
                "geometry" : feature["geometry"],
                "properties" : {}
        }
        for i in range(2, len(sys.argv)):
                fieldExpression = sys.argv[i]

                # field parsing
                asIndex = fieldExpression.find(" as ")
                fieldName = fieldExpression[:asIndex].strip()
                evalExpression = fieldExpression[asIndex+4:]

                # field evaluation
                value = None
                if re.match("^[A-Za-z0-9_-]*$", evalExpression):
                        # Just field reference
                        value = feature["properties"][evalExpression]
                else:
                        # Expression
                        value = eval(evalExpression)

                # create field in new feature
                newFeature["properties"][fieldName] = value

        print newFeature

d.close()

Ejemplo:

./fiona_projection_ops.py ~/data/north_carolina/shape/hospitals.shp 'ingol as feature["properties"]["CITY"]=="Goldsboro"' 'city as CITY' 'name as NAME'

Filtrado

Ejercicio: Hacer un script que muestre sólo los objetos hospital que están en la ciudad de “Goldsboro” (fiona_goldsboro_hospitals.py):

#! /usr/bin/env python

import sys
import fiona

d = fiona.open("/home/user/data/north_carolina/shape/hospitals.shp")

for feature in d:
        if feature["properties"]["CITY"]=="Goldsboro":
                print feature["properties"]["NAME"]

d.close()

Incluso se podría extender el último ejemplo del punto anterior y pasar la expresión como parámetro también (fiona_projection_selection.py):

#! /usr/bin/env python

import sys
import fiona
import re

file = sys.argv[1]
expression = sys.argv[2]

d = fiona.open(file)

for feature in d:
        if eval(expression):
                newFeature = {
                        "geometry" : feature["geometry"],
                        "properties" : {}
                }

                # If there are no field ops include all
                if len(sys.argv) == 3:
                        newFeature["properties"] = feature["properties"]
                else:
                        for i in range(3, len(sys.argv)):
                                fieldExpression = sys.argv[i]

                                # field parsing
                                asIndex = fieldExpression.find(" as ")
                                fieldName = fieldExpression[:asIndex].strip()
                                evalExpression = fieldExpression[asIndex+4:]

                                # field evaluation
                                value = None
                                if re.match("^[A-Za-z0-9_-]*$", evalExpression):
                                        # Just field reference
                                        value = feature["properties"][evalExpression]
                                else:
                                        # Expression
                                        value = eval(evalExpression)

                                # create field in new feature
                                newFeature["properties"][fieldName] = value

                print newFeature

d.close()

Ejemplo:

./fiona_projection_selection.py ~/data/north_carolina/shape/hospitals.shp 'feature["properties"]["CITY"]=="Goldsboro"'

./fiona_projection_selection.py ~/data/north_carolina/shape/hospitals.shp 'feature["properties"]["CITY"]=="Goldsboro"' 'name as NAME' 'city as CITY'

Es obvio que sería interesante escribir el resultado como otro shapefile, ¿no? Vemos primero cómo crear un shapefile desde cero.

Creación de un shapefile desde cero

El siguiente código crea un fichero con objetos de tipo punto cuyas coordenadas se leen como parámetro (fiona_create_points.py):

#! /usr/bin/env python

import sys
import fiona
from fiona.crs import from_epsg

target = sys.argv[1]
epsg = sys.argv[2]

outputSchema = {
        "geometry": "Point",
        "properties": {
                ("gid", "str")
        }
}

output = fiona.open(target, "w", driver="ESRI Shapefile", crs=from_epsg(epsg), schema=outputSchema)

id = 0
for i in range(3, len(sys.argv), 2):
        x = float(sys.argv[i])
        y = float(sys.argv[i+1])
        feature = {
                "geometry" : {
                        "coordinates" : (x, y),
                        "type" : "Point"
                },
                "properties" : {
                        "gid" : id
                }
        }
        id = id + 1
        output.write(feature)

output.close()

Ejercicio: Crear un programa que tome un shapefile de entrada y un tamaño y cree una malla que cubra el shapefile original y cuya celda tiene el tamaño especificado. Se puede usar la plantilla siguiente:

#! /usr/bin/env python

import sys
import fiona

file = sys.argv[1]
size = int(sys.argv[2])
target = sys.argv[3]

d = fiona.open(file)
bounds = d.bounds
crs = d.crs
d.close();

outputSchema = {...}

output = fiona.open(target, "w", driver="ESRI Shapefile", crs=crs, schema=outputSchema)

id = 0
x = bounds[0]
while x < bounds[2]:
        y = bounds[1]
        while y < bounds[3]:
                feature = {...}
                id = id + 1
                output.write(feature)

                y = y + size

        x = x + size

output.close()

Solución (fiona_grid.py):

#! /usr/bin/env python

import sys
import fiona

file = sys.argv[1]
size = int(sys.argv[2])
target = sys.argv[3]

d = fiona.open(file)
bounds = d.bounds
crs = d.crs
d.close();

outputSchema = {
        "geometry": "Polygon",
        "properties": {
                ("gid", "str")
        }
}

output = fiona.open(target, "w", driver="ESRI Shapefile", crs=crs, schema=outputSchema)

id = 0
x = bounds[0]
while x < bounds[2]:
        y = bounds[1]
        while y < bounds[3]:
                feature = {
                        "geometry" : {
                                "coordinates" : [[
                                        (x, y),
                                        (x + size, y),
                                        (x + size, y + size),
                                        (x, y + size),
                                        (x, y)
                                ]],
                                "type" : "Polygon"
                        },
                        "properties" : {
                                "gid" : id
                        }
                }
                id = id + 1
                output.write(feature)

                y = y + size

        x = x + size

output.close()

Ejemplo:

./fiona_grid.py ~/data/north_carolina/shape/hospitals.shp 50000 /tmp/grid.shp

Modificación y escritura de un shapefile

Ejercicio: tomar el ejemplo “fiona_goldsboro_hospitals” y escribir el resultado en otro fichero (fiona_goldsboro_hospitals_write.py):

#! /usr/bin/env python

import sys
import fiona

d = fiona.open("/home/user/data/north_carolina/shape/hospitals.shp")

outputSchema = {
        "geometry": d.schema["geometry"],
        "properties": {
                ("NAME", d.schema["properties"]["NAME"])
        }
}
output = fiona.open("/tmp/hospitals_in_goldsboro.shp", "w", driver="ESRI Shapefile", crs=d.crs, schema=outputSchema)

for feature in d:
        if feature["properties"]["CITY"]=="Goldsboro":
                newFeature = {
                        "geometry" : feature["geometry"],
                        "properties" : {
                                "NAME" : feature["properties"]["NAME"]
                        }
                }

                output.write(feature)

output.close()

d.close()

Por último, vamos a generalizar el último ejemplo del punto de filtrado para pasarle como segundo parámetro el fichero de salida donde se quiere escribir (fiona_projection_selection_write.py):

#! /usr/bin/env python

import sys
import fiona

file = sys.argv[1]
target = sys.argv[2]
expression = sys.argv[3]

d = fiona.open(file)

outputSchema = {
        "geometry": d.schema["geometry"],
        "properties": {
        }
}

fields = []

for i in range(4, len(sys.argv)):
        fieldExpression = sys.argv[i]

        # field parsing
        asIndex = fieldExpression.find(" as ")
        fieldNameAndType = fieldExpression[:asIndex].strip()
        fieldEvalExpression = fieldExpression[asIndex+4:]
        colonIndex = fieldNameAndType.find(":")
        if colonIndex != -1:
                fieldName = fieldNameAndType[:colonIndex]
                fieldType = fieldNameAndType[colonIndex+1:]
                computed = True
        else:
                fieldName = fieldNameAndType
                fieldType = d.schema["properties"][fieldEvalExpression]
                computed = False
        field = {
                "name" : fieldName,
                "type" : fieldType,
                "expression" : fieldEvalExpression,
                "computed" : computed
        }
        fields.append(field)

        # create field in new feature
        outputSchema["properties"][field["name"]] = field["type"]

output = fiona.open(target, "w", driver="ESRI Shapefile", crs=d.crs, schema=outputSchema)

for feature in d:
        if eval(expression):
                newFeature = {
                        "geometry" : feature["geometry"],
                        "properties" : {}
                }

                # If there are no field ops include all
                if len(fields) == 0:
                        newFeature["properties"] = feature["properties"]
                else:
                        for field in fields:
                                # field evaluation
                                value = None
                                if field["computed"]:
                                        # Expression
                                        value = eval(field["expression"])
                                else:
                                        # Just field reference
                                        value = feature["properties"][field["expression"]]

                                # create field in new feature
                                newFeature["properties"][field["name"]] = value

                output.write(newFeature)

d.close()
output.close()

Ejemplo:

./fiona_projection_selection_write.py ~/data/north_carolina/shape/hospitals.shp /tmp/hospital_projection_and_filter.shp 'feature["properties"]["CITY"]=="Goldsboro"' 'name as NAME' 'city as CITY' 'shorname:str as feature["properties"]["NAME"][:3]'