-3

I need to read elevation data from a binary .hgt file in Swift. I have found this result for c, but I can not migrate it to Swift.

#include <stdio.h>

#define SIZE 1201
signed short int matrix[SIZE][SIZE] = {0};

int main(int argc, const char * argv[])
{
FILE *fp = fopen("N49E013.hgt", "rb");    

unsigned char buffer[2];
for (int i = 0; i < SIZE; ++i)
{
    for (int j = 0; j < SIZE; ++j) 
    {
        if (fread(buffer, sizeof(buffer), 1, fp) != 1)
        {
            printf("Error reading file!\n");
            system("PAUSE");
            return -1;
        }
        matrix[i][j] = (buffer[0] << 8) | buffer[1];       
    }
}

fclose(fp);
}
Zorro02
  • 31
  • 6

2 Answers2

3

#define SIZE 1201

This defines a constant named 'SIZE', so do that:

let size = 1201

next:

FILE *fp = fopen("N49E013.hgt", "rb");

This opens a file for reading. We can do that. Close the file in a 'defer' block, so that no matter what, the file gets closed when we're done.

// change the path below to the correct path
let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: "/path/to/N49E013.hgt"))
defer { handle.closeFile() }

Now, to construct the matrix. We want to create size number of arrays, each of which has size elements, read from the file. The original used two nested for loops, but Swift supports functional programming constructs, which we can use to do this a bit more elegantly:

let matrix = try (0..<size).map { _ in
    try (0..<size).map { _ -> Int in
        // Unfortunately, FileHandle doesn't have any decent error-reporting mechanism
        // other than Objective-C exceptions.
        // If you need to catch errors, you can use fread as in the original,
        // or use an Objective-C wrapper to catch the exceptions.

        let data = handle.readData(ofLength: 2)

        if data.count < 2 { throw CocoaError(.fileReadCorruptFile) }

        return (Int(data[0]) << 8) | Int(data[1])
    }
}

Think that ought to do it.

Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
0

I was implementing the same problem recently but found out solution provided by Charles Srstka is bit slow. It takes about 10 seconds to load one file on Late 2016 15" MBP.

I tweaked it a bit and made it about 50x faster using direct access to memory and reading it by rows instead of 2 bytes.

static let size = 1201

static func read(from path: String) throws -> [[UInt16]] {

    let handle = try FileHandle(forReadingFrom: URL(fileURLWithPath: path))

    defer { handle.closeFile() }

    // Calculate all the necessary values
    let unitSize = MemoryLayout<UInt16>.size
    let rowSize = size * unitSize
    let expectedFileSize = size * rowSize

    // Get fileSize
    let fileSize = handle.seekToEndOfFile()

    // Check file size
    guard fileSize == expectedFileSize else {
        throw CocoaError(.fileReadCorruptFile)
    }

    // Go back to the start
    handle.seek(toFileOffset: 0)

    // Iterate
    let matrix: [[UInt16]] = (0..<size).map { _ in
        // Read a row
        let data = handle.readData(ofLength: rowSize)
        // With bytes...
        let row: [UInt16] = data.withUnsafeBytes { (bytes: UnsafePointer<UInt16>) -> [UInt16] in
            // Get the buffer. Count isn't using rowSize because it calculates number of bytes based on data type
            let buffer = UnsafeBufferPointer<UInt16>(start: bytes, count: size)
            // Create an array
            return Array<UInt16>(buffer)
        }
        // Return row, swapping from Little to Big endian
        return row.map { CFSwapInt16HostToBig($0) }
    }

    return matrix
}
Martin Pilch
  • 3,245
  • 3
  • 38
  • 61