Task 163: .E00 File Format

Task 163: .E00 File Format

File Format Specifications for the .E00 File Format

The .E00 file format is an ESRI ArcInfo Interchange File, a proprietary ASCII-based format used for exporting and importing geospatial data such as vector coverages, raster grids, triangulated irregular networks (TINs), and associated attribute tables from ESRI's ArcInfo software. It facilitates data transfer between systems, particularly those not connected via networks. The format is line-oriented, with sections identified by three-letter codes (e.g., ARC, GRD), and it supports optional compression using a variant of Lempel-Ziv. ESRI has not published official specifications, but reverse-engineered documentation, such as the analysis from AVCE00 and FME documentation, provides detailed insights. The file begins with an "EXP" header indicating compression level (0 for uncompressed, 1 for compressed), followed by data sections, and ends with "EOS". Data is organized into subfiles for geometry, attributes, and metadata, with precision specified as single (2) or double (3). For coverages, sections include ARC (arcs), CNT (centroids), PAL (polygons), and others; for grids, the GRD section contains raster data with dimensions and cell values; for TINs, sections like TIN and NOD describe nodes and edges; INFO tables store attributes.

1. List of All Properties of This File Format Intrinsic to Its File System

The .E00 format is not a file system but a self-contained data interchange format. Its intrinsic properties refer to embedded structural and metadata elements that define the file's organization, data integrity, and geospatial characteristics. Based on reverse-engineered specifications, the following is a comprehensive list:

  • Compression Status: Indicates if the file is uncompressed (level 0), partially compressed, or fully compressed (level 1), determined from the EXP header.
  • Precision Level: Single-precision (code 2, 7-8 significant digits) or double-precision (code 3, 14-15 significant digits), specified at the start of each data section.
  • Coverage Name: The name of the exported dataset, embedded in the EXP header line.
  • Export Version: The ArcInfo version used for export, implied through structure (e.g., REV 7 for certain field lengths).
  • Bounding Box: Minimum and maximum X/Y coordinates defining the spatial extent, stored in the BND section as four floating-point values.
  • Tolerance Values: Geometric tolerances for fuzzy, dangle, and node matching, listed in the TOL section as 10 floating-point values.
  • Projection Definition: Coordinate system and projection parameters, stored as text in the PRJ section.
  • Tick Points: Reference points for registration, in the TIC section, including ID, X, and Y coordinates for each tick.
  • Log Entries: Export history and processing logs, in the LOG section as text lines.
  • Feature Counts: Number of arcs, points, polygons, centroids, and labels, derived from section sizes (e.g., ARC, CNT, PAL, LAB).
  • Attribute Table Definitions: Structure of INFO tables (e.g., PAT for polygons/points, AAT for arcs), including field names, types, widths, and decimal places from IFO headers.
  • Geometry Data: Coordinates and topology for features, such as arc nodes, polygon boundaries, and centroid locations in sections like ARC, PAL, CNT.
  • Raster Grid Parameters (for grids): Cell size, row/column counts, no-data value, and statistics (min, max, mean, stddev), in the GRD section header.
  • TIN Parameters (for TINs): Node counts, edge lists, and hull definitions in TIN and NOD sections.
  • End-of-Section Markers: Specific terminators like "-1 0 0 0 0 0 0" for most sections, "EOS" for file end.
  • File Splitting Indicator: Presence of continuation files (.e01, .e02, etc.) for large datasets.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .E00 File Dump

The following is an HTML page with embedded JavaScript suitable for embedding in a Ghost blog post. It allows users to drag and drop a .E00 file, parses it to extract the listed properties, and displays them on the screen. The parser handles basic uncompressed .E00 files for coverages; compressed files require decompression first.

.E00 File Property Dumper
Drag and drop .E00 file here

4. Python Class for .E00 File Handling

The following Python class can open, decode, read, write, and print the properties of a .E00 file. It supports basic uncompressed coverages; compression handling requires external decompression.

class E00Handler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.properties = {}
        self.lines = []

    def open_and_decode(self):
        with open(self.filepath, 'r') as f:
            self.lines = f.readlines()
        self.decode_properties()

    def decode_properties(self):
        self.properties = {
            'compressionStatus': 'Uncompressed',
            'precisionLevel': 'Unknown',
            'coverageName': '',
            'boundingBox': [],
            'toleranceValues': [],
            'projectionDefinition': '',
            'tickPoints': [],
            'logEntries': [],
            'featureCounts': {'arcs': 0, 'points': 0, 'polygons': 0},
            'attributeTableDefinitions': [],
            'geometryData': 'Summary only',
            'rasterGridParameters': None,
            'tinParameters': None,
            'endOfSectionMarkers': [],
            'fileSplittingIndicator': False
        }
        current_section = ''
        i = 0
        while i < len(self.lines):
            line = self.lines[i].strip()
            if line.startswith('EXP'):
                parts = line.split()
                self.properties['coverageName'] = parts[2] if len(parts) > 2 else ''
                self.properties['compressionStatus'] = 'Uncompressed' if parts[1] == '0' else 'Compressed'
            elif line.startswith(('ARC', 'GRD', 'TIN')):
                current_section = line[:3]
                self.properties['precisionLevel'] = 'Single' if line.split()[1] == '2' else 'Double'
            elif line.startswith('BND'):
                i += 1
                self.properties['boundingBox'] = [float(x) for x in self.lines[i].strip().split()]
            elif line.startswith('TOL'):
                i += 1
                self.properties['toleranceValues'] = [float(x) for x in self.lines[i].strip().split()]
            elif line.startswith('PRJ'):
                proj = []
                i += 1
                while not self.lines[i].strip().startswith('EOP'):
                    proj.append(self.lines[i].strip())
                    i += 1
                self.properties['projectionDefinition'] = '\n'.join(proj)
            elif line.startswith('TIC'):
                self.properties['tickPoints'] = []
                i += 1
                while not self.lines[i].strip().startswith('-1'):
                    self.properties['tickPoints'].append([float(x) for x in self.lines[i].strip().split()])
                    i += 1
            elif line.startswith('LOG'):
                self.properties['logEntries'] = []
                i += 1
                while not self.lines[i].strip().startswith('EOL'):
                    self.properties['logEntries'].append(self.lines[i].strip())
                    i += 1
            elif line.startswith('IFO'):
                table_def = {'name': self.lines[i+1].strip(), 'fields': []}
                i += 2
                while not self.lines[i].strip().startswith('EOI'):
                    table_def['fields'].append(self.lines[i].strip())
                    i += 1
                self.properties['attributeTableDefinitions'].append(table_def)
            elif line.startswith('GRD'):
                i += 1
                parts = self.lines[i].strip().split()
                self.properties['rasterGridParameters'] = {
                    'rows': int(parts[0]),
                    'cols': int(parts[1]),
                    'cellSize': float(self.lines[i+1].strip().split()[0])
                }
            if '-1 0 0 0 0 0 0' in line:
                self.properties['endOfSectionMarkers'].append(current_section)
            if current_section == 'ARC':
                self.properties['featureCounts']['arcs'] += 1
            elif current_section == 'CNT':
                self.properties['featureCounts']['polygons'] += 1
            elif current_section == 'LAB':
                self.properties['featureCounts']['points'] += 1
            i += 1

    def print_properties(self):
        import json
        print(json.dumps(self.properties, indent=4))

    def write_file(self, output_path):
        with open(output_path, 'w') as f:
            f.writelines(self.lines)

# Example usage:
# handler = E00Handler('example.e00')
# handler.open_and_decode()
# handler.print_properties()
# handler.write_file('output.e00')

5. Java Class for .E00 File Handling

The following Java class can open, decode, read, write, and print the properties of a .E00 file. It uses buffered I/O for efficiency and handles basic uncompressed files.

import java.io.*;
import java.util.*;

public class E00Handler {
    private String filepath;
    private Map<String, Object> properties;
    private List<String> lines;

    public E00Handler(String filepath) {
        this.filepath = filepath;
        this.properties = new HashMap<>();
        this.lines = new ArrayList<>();
    }

    public void openAndDecode() throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        }
        decodeProperties();
    }

    private void decodeProperties() {
        properties.put("compressionStatus", "Uncompressed");
        properties.put("precisionLevel", "Unknown");
        properties.put("coverageName", "");
        properties.put("boundingBox", new ArrayList<Double>());
        properties.put("toleranceValues", new ArrayList<Double>());
        properties.put("projectionDefinition", "");
        properties.put("tickPoints", new ArrayList<List<Double>>());
        properties.put("logEntries", new ArrayList<String>());
        Map<String, Integer> featureCounts = new HashMap<>();
        featureCounts.put("arcs", 0);
        featureCounts.put("points", 0);
        featureCounts.put("polygons", 0);
        properties.put("featureCounts", featureCounts);
        properties.put("attributeTableDefinitions", new ArrayList<Map<String, Object>>());
        properties.put("geometryData", "Summary only");
        properties.put("rasterGridParameters", null);
        properties.put("tinParameters", null);
        properties.put("endOfSectionMarkers", new ArrayList<String>());
        properties.put("fileSplittingIndicator", false);

        String currentSection = "";
        for (int i = 0; i < lines.size(); i++) {
            String line = lines.get(i).trim();
            if (line.startsWith("EXP")) {
                String[] parts = line.split("\\s+");
                properties.put("coverageName", parts.length > 2 ? parts[2] : "");
                properties.put("compressionStatus", "0".equals(parts[1]) ? "Uncompressed" : "Compressed");
            } else if (line.startsWith("ARC") || line.startsWith("GRD") || line.startsWith("TIN")) {
                currentSection = line.substring(0, 3);
                String[] parts = line.split("\\s+");
                properties.put("precisionLevel", "2".equals(parts[1]) ? "Single" : "Double");
            } else if (line.startsWith("BND")) {
                i++;
                String[] parts = lines.get(i).trim().split("\\s+");
                List<Double> bbox = new ArrayList<>();
                for (String p : parts) bbox.add(Double.parseDouble(p));
                properties.put("boundingBox", bbox);
            } else if (line.startsWith("TOL")) {
                i++;
                String[] parts = lines.get(i).trim().split("\\s+");
                List<Double> tols = new ArrayList<>();
                for (String p : parts) tols.add(Double.parseDouble(p));
                properties.put("toleranceValues", tols);
            } else if (line.startsWith("PRJ")) {
                StringBuilder proj = new StringBuilder();
                i++;
                while (!lines.get(i).trim().startsWith("EOP")) {
                    proj.append(lines.get(i).trim()).append("\n");
                    i++;
                }
                properties.put("projectionDefinition", proj.toString().trim());
            } else if (line.startsWith("TIC")) {
                List<List<Double>> ticks = new ArrayList<>();
                i++;
                while (!lines.get(i).trim().startsWith("-1")) {
                    String[] parts = lines.get(i).trim().split("\\s+");
                    List<Double> tick = new ArrayList<>();
                    for (String p : parts) tick.add(Double.parseDouble(p));
                    ticks.add(tick);
                    i++;
                }
                properties.put("tickPoints", ticks);
            } else if (line.startsWith("LOG")) {
                List<String> logs = new ArrayList<>();
                i++;
                while (!lines.get(i).trim().startsWith("EOL")) {
                    logs.add(lines.get(i).trim());
                    i++;
                }
                properties.put("logEntries", logs);
            } else if (line.startsWith("IFO")) {
                Map<String, Object> tableDef = new HashMap<>();
                tableDef.put("name", lines.get(++i).trim());
                List<String> fields = new ArrayList<>();
                i++;
                while (!lines.get(i).trim().startsWith("EOI")) {
                    fields.add(lines.get(i).trim());
                    i++;
                }
                tableDef.put("fields", fields);
                ((List<Map<String, Object>>) properties.get("attributeTableDefinitions")).add(tableDef);
            } else if (line.startsWith("GRD")) {
                i++;
                String[] parts = lines.get(i).trim().split("\\s+");
                Map<String, Object> gridParams = new HashMap<>();
                gridParams.put("rows", Integer.parseInt(parts[0]));
                gridParams.put("cols", Integer.parseInt(parts[1]));
                i++;
                gridParams.put("cellSize", Double.parseDouble(lines.get(i).trim().split("\\s+")[0]));
                properties.put("rasterGridParameters", gridParams);
            }
            if (line.contains("-1 0 0 0 0 0 0")) {
                ((List<String>) properties.get("endOfSectionMarkers")).add(currentSection);
            }
            if ("ARC".equals(currentSection)) ((Map<String, Integer>) properties.get("featureCounts")).put("arcs", ((Map<String, Integer>) properties.get("featureCounts")).get("arcs") + 1);
            else if ("CNT".equals(currentSection)) ((Map<String, Integer>) properties.get("featureCounts")).put("polygons", ((Map<String, Integer>) properties.get("featureCounts")).get("polygons") + 1);
            else if ("LAB".equals(currentSection)) ((Map<String, Integer>) properties.get("featureCounts")).put("points", ((Map<String, Integer>) properties.get("featureCounts")).get("points") + 1);
        }
    }

    public void printProperties() {
        System.out.println(properties);
    }

    public void writeFile(String outputPath) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputPath))) {
            for (String line : lines) {
                writer.write(line + "\n");
            }
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     E00Handler handler = new E00Handler("example.e00");
    //     handler.openAndDecode();
    //     handler.printProperties();
    //     handler.writeFile("output.e00");
    // }
}

6. JavaScript Class for .E00 File Handling

The following JavaScript class (for Node.js) can open, decode, read, write, and print the properties of a .E00 file. It uses the 'fs' module.

const fs = require('fs');

class E00Handler {
    constructor(filepath) {
        this.filepath = filepath;
        this.properties = {};
        this.lines = [];
    }

    openAndDecode() {
        this.lines = fs.readFileSync(this.filepath, 'utf8').split('\n');
        this.decodeProperties();
    }

    decodeProperties() {
        this.properties = {
            compressionStatus: 'Uncompressed',
            precisionLevel: 'Unknown',
            coverageName: '',
            boundingBox: [],
            toleranceValues: [],
            projectionDefinition: '',
            tickPoints: [],
            logEntries: [],
            featureCounts: { arcs: 0, points: 0, polygons: 0 },
            attributeTableDefinitions: [],
            geometryData: 'Summary only',
            rasterGridParameters: null,
            tinParameters: null,
            endOfSectionMarkers: [],
            fileSplittingIndicator: false
        };
        let currentSection = '';
        for (let i = 0; i < this.lines.length; i++) {
            let line = this.lines[i].trim();
            if (line.startsWith('EXP')) {
                let parts = line.split(/\s+/);
                this.properties.coverageName = parts[2] || '';
                this.properties.compressionStatus = parts[1] === '0' ? 'Uncompressed' : 'Compressed';
            } else if (line.startsWith('ARC') || line.startsWith('GRD') || line.startsWith('TIN')) {
                currentSection = line.substring(0, 3);
                let parts = line.split(/\s+/);
                this.properties.precisionLevel = parts[1] === '2' ? 'Single' : 'Double';
            } else if (line.startsWith('BND')) {
                i++;
                this.properties.boundingBox = this.lines[i].trim().split(/\s+/).map(Number);
            } else if (line.startsWith('TOL')) {
                i++;
                this.properties.toleranceValues = this.lines[i].trim().split(/\s+/).map(Number);
            } else if (line.startsWith('PRJ')) {
                let proj = [];
                i++;
                while (!this.lines[i].trim().startsWith('EOP')) {
                    proj.push(this.lines[i].trim());
                    i++;
                }
                this.properties.projectionDefinition = proj.join('\n');
            } else if (line.startsWith('TIC')) {
                this.properties.tickPoints = [];
                i++;
                while (!this.lines[i].trim().startsWith('-1')) {
                    this.properties.tickPoints.push(this.lines[i].trim().split(/\s+/).map(Number));
                    i++;
                }
            } else if (line.startsWith('LOG')) {
                this.properties.logEntries = [];
                i++;
                while (!this.lines[i].trim().startsWith('EOL')) {
                    this.properties.logEntries.push(this.lines[i].trim());
                    i++;
                }
            } else if (line.startsWith('IFO')) {
                let tableDef = { name: this.lines[++i].trim(), fields: [] };
                i++;
                while (!this.lines[i].trim().startsWith('EOI')) {
                    tableDef.fields.push(this.lines[i].trim());
                    i++;
                }
                this.properties.attributeTableDefinitions.push(tableDef);
            } else if (line.startsWith('GRD')) {
                i++;
                let parts = this.lines[i].trim().split(/\s+/);
                this.properties.rasterGridParameters = {
                    rows: parseInt(parts[0]),
                    cols: parseInt(parts[1]),
                    cellSize: parseFloat(this.lines[++i].trim().split(/\s+/)[0])
                };
            }
            if (line.includes('-1 0 0 0 0 0 0')) {
                this.properties.endOfSectionMarkers.push(currentSection);
            }
            if (currentSection === 'ARC') this.properties.featureCounts.arcs++;
            else if (currentSection === 'CNT') this.properties.featureCounts.polygons++;
            else if (currentSection === 'LAB') this.properties.featureCounts.points++;
        }
    }

    printProperties() {
        console.log(JSON.stringify(this.properties, null, 4));
    }

    writeFile(outputPath) {
        fs.writeFileSync(outputPath, this.lines.join('\n'));
    }
}

// Example usage:
// const handler = new E00Handler('example.e00');
// handler.openAndDecode();
// handler.printProperties();
// handler.writeFile('output.e00');

7. C Class for .E00 File Handling

The following C++ class can open, decode, read, write, and print the properties of a .E00 file. It uses standard library for file I/O and basic parsing.

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <map>

class E00Handler {
private:
    std::string filepath;
    std::map<std::string, std::string> properties; // Simplified to string for print
    std::vector<std::string> lines;

public:
    E00Handler(const std::string& fp) : filepath(fp) {}

    void openAndDecode() {
        std::ifstream file(filepath);
        std::string line;
        while (std::getline(file, line)) {
            lines.push_back(line);
        }
        file.close();
        decodeProperties();
    }

    void decodeProperties() {
        // Note: Simplified parsing; full implementation would use more structures
        properties["compressionStatus"] = "Uncompressed";
        properties["precisionLevel"] = "Unknown";
        properties["coverageName"] = "";
        // Add more as strings for simplicity
        std::string currentSection;
        for (size_t i = 0; i < lines.size(); ++i) {
            std::string line = lines[i];
            // Trim line (simple trim)
            line.erase(0, line.find_first_not_of(" \t"));
            line.erase(line.find_last_not_of(" \t") + 1);
            if (line.rfind("EXP", 0) == 0) {
                std::istringstream iss(line);
                std::string token;
                iss >> token >> token;
                properties["compressionStatus"] = (token == "0") ? "Uncompressed" : "Compressed";
                iss >> token;
                properties["coverageName"] = token;
            } else if (line.rfind("ARC", 0) == 0 || line.rfind("GRD", 0) == 0 || line.rfind("TIN", 0) == 0) {
                currentSection = line.substr(0, 3);
                std::istringstream iss(line);
                std::string token;
                iss >> token >> token;
                properties["precisionLevel"] = (token == "2") ? "Single" : "Double";
            } else if (line.rfind("BND", 0) == 0) {
                ++i;
                properties["boundingBox"] = lines[i]; // As string
            } else if (line.rfind("TOL", 0) == 0) {
                ++i;
                properties["toleranceValues"] = lines[i];
            } else if (line.rfind("PRJ", 0) == 0) {
                std::string proj;
                ++i;
                while (lines[i].find("EOP") == std::string::npos) {
                    proj += lines[i] + "\n";
                    ++i;
                }
                properties["projectionDefinition"] = proj;
            } // Add similar for other sections as needed; abbreviated for brevity
            // Feature counts and others can be added similarly
        }
    }

    void printProperties() {
        for (const auto& prop : properties) {
            std::cout << prop.first << ": " << prop.second << std::endl;
        }
    }

    void writeFile(const std::string& outputPath) {
        std::ofstream out(outputPath);
        for (const auto& line : lines) {
            out << line << std::endl;
        }
        out.close();
    }
};

// Example usage:
// int main() {
//     E00Handler handler("example.e00");
//     handler.openAndDecode();
//     handler.printProperties();
//     handler.writeFile("output.e00");
//     return 0;
// }