sábado, 21 de junio de 2014

Leer las geometrías de un Shapefile binariamente

 

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

 

 
 
 

 
ª