En este mes se verá como leer binariamente un Shapefile para extraer sus geometrías. Para ello, usaremos este documento de ESRI que habla sobre la estructura de un Shapefile:
Antes de eso, se explicara unos conceptos y detalles:
Archivo Binario: Un archivo binario es un archivo informático
que contiene información de cualquier tipo codificada en binario
para el propósito de almacenamiento y procesamiento en ordenadores. Por ejemplo
los archivos informáticos que almacenan texto formateado o fotografías, así
como los archivos ejecutables que contienen programas…
…Habitualmente se piensa en los archivos binarios
como una secuencia de bytes, que es lo que implica que dígitos binarios (bits)
se agrupen de ocho en ocho comúnmente. Los archivos binarios que contienen
bytes suelen ser interpretados como alguna cosa que no sean caracteres de texto.
(Fuente: Wikipedia)
Como se ve arriba, los archivos binarios están escritos en
bits, y el Shapefile está en formato binario, por lo tanto, se leerá los bits
que conformarían el Shapefile (específicamente, el archivo con extensión *.shp).
Según el documento que habla sobre los Shapefile
vemos una seria de Tablas, que explican la ubicación de cada Byte (se utilizara
desde ahora esta unidad, de acuerdo a dicho documento) y que es lo que
representa o la información que contiene:
Tabla 1: Explica la información general del Shapefile a
analizar.
Tabla 2: Explica sobre el número de orden o índex de cada
“Entidad” o “Geometría” y su” tamaño”.
Tabla 3 al 16: Explica la descripción de cada tipo de
Geometría que pueda tener un Shapefile.
¿Cómo usar e
interpretar las Tablas?
Las tablas tienen una columna que esta la Posición (Columna Position y está en unidades en Byte) de
cada información del Shapefile, y vemos que todas las tablas comienzan en el
Byte 0. Para un mejor entendimiento podríamos decir que la suma de la Tabla 1 +
Tabla 2 + Tabla 3 o Tabla 4 o… Tabla 16 formarían el Shapefile en sí. El
grafico siguiente explicaría mejor esta idea:
Vemos que en el grafico hay una Posición Relativa (la
Posición que se ve en cada tabla en la columna Position) y una Posición Absoluta (que es la que
necesitaremos para leer el contenido del Shapefile y esta en la columna Posición Absoluta).
En estas Tablas que están en el documento de ESRI (mencionado al principio de este Post), tenemos las siguientes columnas:
Position: Muestra
la posición relativa del Byte en el Shapefile
Field: Muestra la
descripción del contenido de la posición del Byte.
Value: Muestra el
valor que debe tener el contenido en la posición del Byte.
Type: Muestra el
tipo de valor que debe tener el contenido del Byte.
Byte Order: Seria
la forma de cómo debe ser leído o interpretado el contenido en la posición del
Byte.
Sobre Byte Order,
vemos que tiene dos valores posibles: Big
y Little. Para hacerlo más sencillo,
si el Byte Order es Big, pasamos el valor de la columna Value en este algoritmo (que es una función
y para nuestro ejemplo de lectura lo llamaremos ReordenaBytes):
Function ReordenaBytes(ByVal Num As Long) As Long
Dim BO, B1,
B2, B3 As Byte
BO = Num - 256
* Int(Num / 256)
Num = Num /
256
B1 = Num - 256
* Int(Num / 256)
Num = Num /
256
B2 = Num - 256
* Int(Num / 256)
Num = Num /
256
B3 = Num - 256
* Int(Num / 256)
ReordenaBytes
= (((BO * 256 + B1) * 256 + B2) * 256 + B3)
End
Function
Que se encargara de “ordenar” el valor del Byte para ser
leído “normalmente”. Si el Byte Order es Little, usamos este valor de la columna Value directamente.
Consideraciones de
la Posición Absoluta:
Al usar en nuestro ejemplo el valor del Byte tal como
aparece en la columna Position, no dará
el resultado esperado, pero si usamos el valor de la columna Posición Absoluta (que como se ve, es
el valor de la columna Position más
1) si tendremos el valor esperado.
Consideraciones sobre
la Función ReordenaBytes:
a) Se usó las formulas propuesta en esta página:
Donde también hay mayor referencia sobre los tipos de Byte
Order (Big y Little)
b) Los valores de BO, B1, B2, B3, son el
Módulo o Residuo de Num y 256 (Num Modulo 256), pero se recomienda usar la fórmula propuesta en esta página: http://support.microsoft.com/kb/141178/es
Al parecer, al usar otros métodos o funciones para hallar el
Modulo o Residuo dan resultados no deseados.
c) El Valor de ingreso de la Función ReordenaBytes es un entero Largo (Long) y
devuelve un valor del mismo tipo.
Consideraciones
sobre la columna File Length:
En la Tabla 1, Byte 24 (que según el grafico de arriba está
en la Posición Absoluta Byte 25, que es la que necesitamos para leerlo en
nuestro ejemplo) se encuentra el tamaño del archivo (File Length), un valor de tipo entero y con Byte Order del tipo Big. Al pie de la Tabla 1 hay una nota
donde dice que el valor del File Length está
ya contenido “50 16-bit words” que pertenecen a la cabecera (presumiblemente del
archivo). Además, al pie de la Tabla 2, en el párrafo siguiente se menciona que
cada “Entidad” o “Geometría” contribuye con 4 Bytes. Según lo visto en el
documento (http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf),
no habria una manera directa de obtener el número de “Entidades” o “Geometrías”
del Shapefile, pero podemos calcularlo de la siguiente Manera:
FileLengthActual = 50
+ 4*(NumEntidades) + Sum(Content Length[0] + Content Length[1] +.. +Content
Length[NumEntidades - 1])
En donde haremos una iteración para calcular el Número de
Entidades.
Ejemplo de Uso:
Leeremos como ejemplo unShapefile de Polilineas que tienen solo dos nodos (Nodo Inicial y Nodo Final) en una Macro en VB en Excel y usaremos como
referencia la imagen mostrada arriba (que se muestran las Tablas y sus columnas). Imprimiremos las coordenadas en la Hoja actual del archivo Excel. El Shapefile lo puedes bajar desde este enlace:
Código:
Sub LeerShapefile() Dim Shapefile As Long Dim RutaShapefile As String Dim ByteActual As Integer Dim FileCode As Long Dim FileLength As Long Dim Version As Long Dim ShapeType As Long Dim X_MIN As Double Dim Y_MIN As Double Dim X_MAX As Double Dim Y_MAX As Double Dim RecordNumber As Long Dim ContentLength As Long Dim NumParts As Long Dim NumPoints As Long Dim ByteActualX As Long Dim X As Double Dim Y As Double Dim FileLengthActual As Long Dim i, j, k As Long RutaShapefile = "C:\shape\mi_shape.shp" Shapefile = FreeFile() Open RutaShapefile For Binary Access Read As Shapefile ByteActual = 1 'INICIO TABLA 1 Get Shapefile, ByteActual, FileCode 'BYTE 0 FileCode = ReordenaBytes(FileCode) ByteActual = ByteActual + 24 Get Shapefile, ByteActual, FileLength 'BYTE 24 FileLength = ReordenaBytes(FileLength) ByteActual = ByteActual + 4 Get Shapefile, ByteActual, Version 'BYTE 28 ByteActual = ByteActual + 4 Get Shapefile, ByteActual, ShapeType 'BYTE 32 ByteActual = ByteActual + 4 Get Shapefile, ByteActual, X_MIN 'BYTE 36 ByteActual = ByteActual + 8 Get Shapefile, ByteActual, Y_MIN 'BYTE 44 ByteActual = ByteActual + 8 Get Shapefile, ByteActual, X_MAX 'BYTE 52 ByteActual = ByteActual + 8 Get Shapefile, ByteActual, Y_MAX 'BYTE 60 'FIN TABLA 1 FileLengthActual = 50 j = 0 While (FileLengthActual < FileLength) If j = 0 Then ByteActual = 101 Else ByteActual = Seek(Shapefile) End If 'INICIO TABLA 2 Get Shapefile, ByteActual, RecordNumber 'BYTE 0 de la Tabla 2 RecordNumber = ReordenaBytes(RecordNumber) ByteActual = ByteActual + 4 Get Shapefile, ByteActual, ContentLength 'BYTE 4 de la Tabla 2 ContentLength = ReordenaBytes(ContentLength) 'FIN TABLA 2 'INICIO TABLA 6 ByteActual = ByteActual + 4 Get Shapefile, ByteActual, ShapeTypePolyline 'BYTE 0 de la tabla 6 ByteActual = ByteActual + 36 Get Shapefile, ByteActual, NumParts 'BYTE 36 de la tabla 6 ByteActual = ByteActual + 4 Get Shapefile, ByteActual, NumPoints 'BYTE 40 de la tabla 6 ReDim Parts(NumParts) ByteActual = ByteActual + 4 Get Shapefile, ByteActual, Parts 'BYTE 44 de la tabla 6 Dim Nuevo As Boolean Nuevo = False ByteActualX = ByteActual + 4 * NumParts Range("A" & j + 1).Value = "Polilinea " & j For i = 1 To NumPoints If i = 1 Then Get Shapefile, ByteActualX, X Get Shapefile, , Y Range("B" & j + 1).Value = "X " & X Range("C" & j + 1).Value = "Y " & Y Else Get Shapefile, , X Get Shapefile, , Y Range("D" & j + 1).Value = "X " & X Range("E" & j + 1).Value = "Y " & Y End If Next i 'FIN TABLA 6 FileLengthActual = FileLengthActual + ContentLength + 4 j = j + 1 Wend End Sub Function ReordenaBytes(ByVal Num As Long) As Long Dim BO, B1, B2, B3 As Byte BO = Num - 256 * Int(Num / 256) Num = Num / 256 B1 = Num - 256 * Int(Num / 256) Num = Num / 256 B2 = Num - 256 * Int(Num / 256) Num = Num / 256 B3 = Num - 256 * Int(Num / 256) ReordenaBytes = (((BO * 256 + B1) * 256 + B2) * 256 + B3) End Function
Explicación:
Abrimos el archivo con extensión *.shp y lo abrimos en forma
binaria. Luego empezamos a leer según la Posición Absoluta del Byte desde 1 (para
ello se usa la variable ByteActual).
INICIO TABLA 1:
Para leer / obtener los valores del Byte, se utiliza la
Funcion Get donde hay tres parámetros:
Get IdArchivo, Byte a leer, Variable
Dónde:
Get: es el nombre
de la Función.
idArchivo: es un identificador del Archivo a leer en forma
binaria.
Byte a leer: posición del Byte del Archivo que se desea leer
(se debe ser muy cuidadoso con la posición, si se pasa otro valor, se obtendrá
otros valores).
Variable: es la variable donde se guardara el valor obtenido en
Byte
a leer.
Como se mencionó, si el Byte
Order es Big, el valor se pasara
por la función ReordenaBytes.
Un dato adicional: si queremos leer el Byte siguiente,
hacemos esto:
Get
IdArchivo, , Variable
Donde se ve que no hay parámetro en Byte a leer, pero el
lenguaje de programación asume que se le él byte siguiente
Luego de la Tabla 1, y como se dijo en la parte de Consideraciones
sobre la columna File Length, se
empezara la iteración para hallar el número de entidades, para ellos, primero
inicializamos la variable FileLengthActual (que es la que controlara el tamaño
del Archivo) en 50 (ver Consideraciones sobre la columna File Length). Inicializamos la
variable “j” en cero (0) que nos servirá de contador y que al final nos dara la
cantidad de entidades que existe. También usaremos una función While para recorrer
el resto del Archivo.
INICIO TABLA 2:
Se lee el número de record de la “Entidad” o “Geometría” y
su tamaño, la cual será guardada en la parte final de la función While (FileLengthActual
< FileLength) en esta fórmula:
FileLengthActual = FileLengthActual
+ ContentLength + 4
j = j + 1
El “j” es el contador de entidades.
El While (FileLengthActual < FileLength) funcionara hasta
que el FileLengthActual sea igual a FileLength. Al principio del while, vemos
una condición If, donde si el contador” j” = 0 el valor del Byte a leer (contenido
en la variable ByteActual) sea igual a 101, por que la Tabla 2 comienza en el
Byte 101, luego si el contador” j” no es igual a cero, se leer el valor del
Byte actual, que se obtiene con la función Seek.
INICIO TABLA 6:
En este bloque se lee el resto del Archivo para obtener lo
que está contenido en la Tabla 6, y se repitiera junto con la Tabla hasta que
se cumpla la condición del While. El resultado se imprime en una hoja Excel.
Esperando que sea de utilidad. Sera hasta el otro mes.
Saludos