org.gdms.driver.shapefile.ShapefileDriver.java Source code

Java tutorial

Introduction

Here is the source code for org.gdms.driver.shapefile.ShapefileDriver.java

Source

/**
 * The GDMS library (Generic Datasource Management System)
 * is a middleware dedicated to the management of various kinds of
 * data-sources such as spatial vectorial data or alphanumeric. Based
 * on the JTS library and conform to the OGC simple feature access
 * specifications, it provides a complete and robust API to manipulate
 * in a SQL way remote DBMS (PostgreSQL, H2...) or flat files (.shp,
 * .csv...).
 *
 * Gdms is distributed under GPL 3 license. It is produced by the "Atelier SIG"
 * team of the IRSTV Institute <http://www.irstv.fr/> CNRS FR 2488.
 *
 * Copyright (C) 2007-2012 IRSTV FR CNRS 2488
 *
 * This file is part of Gdms.
 *
 * Gdms is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * Gdms is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * Gdms. If not, see <http://www.gnu.org/licenses/>.
 *
 * For more information, please consult: <http://www.orbisgis.org/>
 *
 * or contact directly:
 * info@orbisgis.org
 */
package org.gdms.driver.shapefile;

import com.vividsolutions.jts.geom.*;
import java.io.*;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.apache.log4j.Logger;
import org.cts.crs.CRSException;
import org.cts.crs.CoordinateReferenceSystem;
import org.cts.parser.prj.PrjWriter;
import org.gdms.data.DataSourceFactory;
import org.gdms.data.schema.*;
import org.gdms.data.types.*;
import org.gdms.data.values.Value;
import org.gdms.data.values.ValueFactory;
import org.gdms.driver.*;
import org.gdms.driver.dbf.DBFDriver;
import org.gdms.driver.driverManager.DriverManager;
import org.gdms.geometryUtils.GeometryClean;
import org.gdms.source.SourceManager;
import org.orbisgis.progress.ProgressMonitor;
import org.orbisgis.utils.FileUtils;

public final class ShapefileDriver extends AbstractDataSet implements FileReadWriteDriver {

    public static final String DRIVER_NAME = "Shapefile driver";
    private static final GeometryFactory GF = new GeometryFactory();
    private Envelope envelope;
    private DBFDriver dbfDriver;
    private DataSet dataSet;
    private ShapefileReader reader;
    private IndexFile shxFile;
    private DataSourceFactory dataSourceFactory;
    private Schema schema;
    private DefaultMetadata metadata;
    private static final Logger LOG = Logger.getLogger(ShapefileDriver.class);
    private File file;
    private CoordinateReferenceSystem crs;

    @Override
    public void close() throws DriverException {
        LOG.trace("Closing");
        try {
            if (reader != null) {
                reader.close();
            }
            reader = null;
            if (shxFile != null) {
                shxFile.close();
            }
            shxFile = null;
            if (dbfDriver != null) {
                dbfDriver.close();
            }
            dbfDriver = null;
        } catch (IOException e) {
            throw new DriverException(e);
        }
    }

    @Override
    public void open() throws DriverException {
        LOG.trace("Opening");
        try {
            FileInputStream shpFis = new FileInputStream(file);
            reader = new ShapefileReader(shpFis.getChannel());
            File shx = FileUtils.getFileWithExtension(file, "shx");
            if (shx == null || !shx.exists()) {
                throw new DriverException("The file " + file.getAbsolutePath() + " has no corresponding .shx file");
            }
            FileInputStream shxFis = new FileInputStream(shx);
            shxFile = new IndexFile(shxFis.getChannel());

            ShapefileHeader header = reader.getHeader();
            envelope = new Envelope(new Coordinate(header.minX(), header.minY()),
                    new Coordinate(header.maxX(), header.maxY()));

            ShapeType type = header.getShapeType();

            dbfDriver = new DBFDriver();
            dbfDriver.setDataSourceFactory(dataSourceFactory);
            File dbf = FileUtils.getFileWithExtension(file, "dbf");
            if (dbf == null || !dbf.exists()) {
                throw new DriverException("The file " + file.getAbsolutePath() + " has no corresponding .dbf file");
            }
            dbfDriver.setFile(dbf);
            dbfDriver.open();

            // registering DataSet
            dataSet = dbfDriver.getTable(DriverManager.DEFAULT_SINGLE_TABLE_NAME);

            Constraint dc;
            //We can force the type of the data in the GDMS table according to the type
            //given in the ShapeFile. This variable is here for this task.
            int gtype;
            // In case of a geometric type, the GeometryConstraint is mandatory
            if (type.id == ShapeType.POINT.id) {
                gtype = Type.POINT;
                dc = new Dimension3DConstraint(2);
            } else if (type.id == ShapeType.ARC.id) {
                gtype = Type.MULTILINESTRING;
                dc = new Dimension3DConstraint(2);
            } else if (type.id == ShapeType.POLYGON.id) {
                gtype = Type.MULTIPOLYGON;
                dc = new Dimension3DConstraint(2);
            } else if (type.id == ShapeType.MULTIPOINT.id) {
                gtype = Type.MULTIPOINT;
                dc = new Dimension3DConstraint(2);
            } else if (type.id == ShapeType.POINTZ.id) {
                gtype = Type.POINT;
                dc = new Dimension3DConstraint(3);
            } else if (type.id == ShapeType.ARCZ.id) {
                gtype = Type.MULTILINESTRING;
                dc = new Dimension3DConstraint(3);
            } else if (type.id == ShapeType.POLYGONZ.id) {
                gtype = Type.MULTIPOLYGON;
                dc = new Dimension3DConstraint(3);
            } else if (type.id == ShapeType.MULTIPOINTZ.id) {
                gtype = Type.MULTIPOINT;
                dc = new Dimension3DConstraint(3);
            } else {
                throw new DriverException("Unknown geometric type !");
            }

            Constraint[] constraints;

            File prj = FileUtils.getFileWithExtension(file, "prj");
            if (prj != null && prj.exists()) {
                crs = DataSourceFactory.getCRSFactory().createFromPrj(prj);
                if (crs != null) {
                    CRSConstraint cc = new CRSConstraint(crs);
                    constraints = new Constraint[] { dc, cc };
                } else {
                    constraints = new Constraint[] { dc };
                }
            } else {
                constraints = new Constraint[] { dc };
            }

            metadata.clear();
            metadata.addField(0, "the_geom", gtype, constraints);
            metadata.addAll(dataSet.getMetadata());

        } catch (IOException e) {
            throw new DriverException(e);
        } catch (ShapefileException e) {
            throw new DriverException(e);
        } catch (InvalidTypeException e) {
            throw new DriverException(e);
        } catch (CRSException e) {
            throw new DriverException(e);
        }
    }

    @Override
    public void setDataSourceFactory(DataSourceFactory dsf) {
        this.dataSourceFactory = dsf;
    }

    @Override
    public String getDriverId() {
        return DRIVER_NAME;
    }

    @Override
    public TypeDefinition[] getTypesDefinitions() {
        List<TypeDefinition> result = new LinkedList<TypeDefinition>(
                Arrays.asList(new DBFDriver().getTypesDefinitions()));
        result.add(new DefaultTypeDefinition("Point", Type.POINT, Constraint.DIMENSION_3D_GEOMETRY));
        result.add(new DefaultTypeDefinition("MultiPoint", Type.MULTIPOINT, Constraint.DIMENSION_3D_GEOMETRY));
        result.add(new DefaultTypeDefinition("MultiPolygon", Type.MULTIPOLYGON, Constraint.DIMENSION_3D_GEOMETRY));
        result.add(new DefaultTypeDefinition("MultiLineString", Type.MULTILINESTRING,
                Constraint.DIMENSION_3D_GEOMETRY));
        return result.toArray(new TypeDefinition[result.size()]);
    }

    @Override
    public void copy(File in, File out) throws IOException {
        File inDBF = FileUtils.getFileWithExtension(in, "dbf");
        File inSHX = FileUtils.getFileWithExtension(in, "shx");
        File outDBF = FileUtils.getFileWithExtension(out, "dbf");
        File outSHX = FileUtils.getFileWithExtension(out, "shx");
        org.apache.commons.io.FileUtils.copyFile(inDBF, outDBF);
        org.apache.commons.io.FileUtils.copyFile(inSHX, outSHX);
        org.apache.commons.io.FileUtils.copyFile(in, out);
    }

    private static Geometry convertGeometry(Geometry geom, ShapeType type) throws DriverException {

        Geometry retVal = null;

        if ((geom == null) || geom.isEmpty()) {
            if ((geom instanceof Point) || (geom instanceof MultiPoint)) {
                retVal = new GeometryFactory().createMultiPoint((Point[]) null);
            } else if ((geom instanceof LineString) || (geom instanceof MultiLineString)) {
                retVal = new GeometryFactory().createMultiLineString((LineString[]) null);
            } else if ((geom instanceof Polygon) || (geom instanceof MultiPolygon)) {
                retVal = new GeometryFactory().createMultiPolygon((Polygon[]) null);
            } else {
                retVal = new GeometryFactory().createMultiPoint((Point[]) null);
            }
        } else {
            if (type == ShapeType.NULL) {
                return geom;
            }

            if ((type == ShapeType.POINT) || (type == ShapeType.POINTZ)) {
                if ((geom instanceof Point)) {
                    retVal = geom;
                } else if (geom instanceof MultiPoint) {
                    MultiPoint mp = (MultiPoint) geom;
                    if (mp.getNumGeometries() == 1) {
                        retVal = mp.getGeometryN(0);
                    }
                }
            } else if ((type == ShapeType.MULTIPOINT) || (type == ShapeType.MULTIPOINTZ)) {
                if ((geom instanceof Point)) {
                    retVal = GF.createMultiPoint(new Point[] { (Point) geom });
                } else if (geom instanceof MultiPoint) {
                    retVal = geom;
                }
            } else if ((type == ShapeType.POLYGON) || (type == ShapeType.POLYGONZ)) {
                if (geom instanceof Polygon) {
                    Polygon p = GeometryClean.makeGoodShapePolygon((Polygon) geom);
                    retVal = GF.createMultiPolygon(new Polygon[] { p });
                } else if (geom instanceof MultiPolygon) {
                    retVal = GeometryClean.makeGoodShapeMultiPolygon((MultiPolygon) geom);
                }
            } else if ((type == ShapeType.ARC) || (type == ShapeType.ARCZ)) {
                if ((geom instanceof LineString)) {
                    retVal = GF.createMultiLineString(new LineString[] { (LineString) geom });
                } else if (geom instanceof MultiLineString) {
                    retVal = geom;
                }
            }
        }
        if (retVal == null) {
            throw new DriverException("Cannot mix geometry types in a shapefile. " + "ShapeType: " + type.name
                    + " -> Geometry: " + geom.toText());
        }

        return retVal;
    }

    @Override
    public void createSource(String path, Metadata metadata, DataSourceFactory dataSourceFactory)
            throws DriverException {
        LOG.trace("Creating source file");
        // write dbf
        String dbfFile = replaceExtension(new File(path), ".dbf").getAbsolutePath();
        DBFDriver tempDbfDriver = new DBFDriver();
        tempDbfDriver.setDataSourceFactory(dataSourceFactory);
        tempDbfDriver.createSource(dbfFile, new DBFMetadata(metadata), dataSourceFactory);

        // write shapefile and shx
        try {
            FileOutputStream shpFis = new FileOutputStream(new File(path));
            final FileOutputStream shxFis = new FileOutputStream(replaceExtension(new File(path), ".shx"));

            ShapefileWriter writer = new ShapefileWriter(shpFis.getChannel(), shxFis.getChannel());
            Type geomType = getGeometryType(metadata);
            int dimension = getGeometryDimension(null, metadata);
            int typeCode = geomType.getTypeCode();
            if (typeCode == Type.NULL || (typeCode & Type.GEOMETRY) == 0) {
                throw new DriverException("Shapefiles need a " + "specific geometry type");
            }
            ShapeType shapeType = getShapeType(geomType, dimension);
            writer.writeHeaders(new Envelope(0, 0, 0, 0), shapeType, 0, 100);
            writer.close();
            writeprj(replaceExtension(new File(path), ".prj"), metadata);
        } catch (FileNotFoundException e) {
            throw new DriverException(e);
        } catch (IOException e) {
            throw new DriverException(e);
        }
    }

    /**
    * Write a CRS as a WKT representation in a file
    *
    * @param path
    * @param crs
    * @throws DriverException
    */
    private void writeprj(File file, Metadata metadata) throws DriverException {
        int fieldIndex = MetadataUtilities.getSpatialFieldIndex(metadata);
        if (fieldIndex != -1) {
            Constraint[] c = metadata.getFieldType(fieldIndex).getConstraints(Constraint.CRS);
            if (c.length != 0) {
                crs = ((CRSConstraint) c[0]).getCRS();
            }
            if (crs != null) {
                String prj = PrjWriter.crsToWKT(crs);
                try {
                    FileUtils.setContents(file, prj);
                } catch (IOException ex) {
                    throw new DriverException("Cannot write the prj", ex);
                }
            }
        }
    }

    /**
     * We try to retrieve the type of the geometry recorded in metadata.
     * @param metadata
     * @return
     * @throws DriverException 
     */
    private Type getGeometryType(Metadata metadata) throws DriverException {
        for (int i = 0; i < metadata.getFieldCount(); i++) {
            if (TypeFactory.isVectorial(metadata.getFieldType(i).getTypeCode())) {
                return metadata.getFieldType(i);
            }
        }

        throw new IllegalArgumentException("The data " + "source doesn't contain any spatial field");
    }

    private int getGeometryDimension(DataSet dataSource, Metadata metadata) throws DriverException {
        for (int i = 0; i < metadata.getFieldCount(); i++) {
            //We search for the vectorial type.
            if (TypeFactory.isVectorial(metadata.getFieldType(i).getTypeCode())) {
                //Found it ! Let's check if there is a Dimension3DConstraint somewhere...
                Dimension3DConstraint c = (Dimension3DConstraint) metadata.getFieldType(i)
                        .getConstraint(Constraint.DIMENSION_3D_GEOMETRY);
                if (c == null) {
                    //There is not. We search for the first not null geometry,
                    //and consider its dimension to be the same as for all the 
                    //other ones...
                    if (dataSource != null) {
                        for (int j = 0; j < dataSource.getRowCount(); j++) {
                            Geometry g = dataSource.getFieldValue(j, i).getAsGeometry();
                            if (g != null && g.getCoordinate() != null) {
                                if (Double.isNaN(g.getCoordinate().z)) {
                                    return 2;
                                } else {
                                    return 3;
                                }
                            }
                        }
                    }

                    // ...and if there isn't any geometry in the DataSet,
                    //we are in 2D.
                    return 2;
                } else {
                    //There is a Dimension3DConstraint. Let's use it !
                    return c.getDimension();
                }
            }
        }

        throw new IllegalArgumentException("The data " + "source doesn't contain any spatial field");
    }

    /**
     * 
     * @param geometryType
     * @param dimension
     * @return
     * @throws DriverException 
     */
    private ShapeType getShapeType(Type geometryType, int dimension) throws DriverException {
        int typeCode = geometryType.getTypeCode();
        switch (typeCode) {
        case Type.POINT:
            if (dimension == 2) {
                return ShapeType.POINT;
            } else {
                return ShapeType.POINTZ;
            }
        case Type.MULTIPOINT:
            if (dimension == 2) {
                return ShapeType.MULTIPOINT;
            } else {
                return ShapeType.MULTIPOINTZ;
            }
        case Type.LINESTRING:
        case Type.MULTILINESTRING:
            if (dimension == 2) {
                return ShapeType.ARC;
            } else {
                return ShapeType.ARCZ;
            }
        case Type.POLYGON:
        case Type.MULTIPOLYGON:
            if (dimension == 2) {
                return ShapeType.POLYGON;
            } else {
                return ShapeType.POLYGONZ;
            }
        case Type.GEOMETRY:
        case Type.GEOMETRYCOLLECTION:
            Constraint cons = geometryType.getConstraint(Constraint.DIMENSION_2D_GEOMETRY);
            if (cons == null) {
                throw new DriverException("Shapefiles need a specific geometry type");
            } else {
                switch (Integer.valueOf(cons.getConstraintValue())) {
                case 0:
                    if (dimension == 2) {
                        return ShapeType.MULTIPOINT;
                    } else {
                        return ShapeType.MULTIPOINTZ;
                    }
                case 1:
                    if (dimension == 2) {
                        return ShapeType.ARC;
                    } else {
                        return ShapeType.ARCZ;
                    }
                case 2:
                    if (dimension == 2) {
                        return ShapeType.POLYGON;
                    } else {
                        return ShapeType.POLYGONZ;
                    }
                default:
                    throw new DriverException("Not a valid geometry constraint code.");
                }
            }

        }
        //Geometry and GeometryCollection are not valid shape types.
        return null;

    }

    @Override
    public void writeFile(final File file, final DataSet dataSource, ProgressMonitor pm) throws DriverException {
        LOG.trace("Writing file " + file.getAbsolutePath());
        // write dbf
        DBFDriver tempDbfDriver = new DBFDriver();
        tempDbfDriver.setDataSourceFactory(dataSourceFactory);
        tempDbfDriver.writeFile(replaceExtension(file, ".dbf"), new DBFRowProvider(dataSource), pm);

        // write shapefile and shx
        try {
            final long rowCount = dataSource.getRowCount();
            pm.startTask("Writing geometries", rowCount);
            FileOutputStream shpFis = new FileOutputStream(file);
            final FileOutputStream shxFis = new FileOutputStream(replaceExtension(file, ".shx"));

            ShapefileWriter writer = new ShapefileWriter(shpFis.getChannel(), shxFis.getChannel());
            Envelope fullExtent = DriverUtilities.getFullExtent(dataSource);
            Metadata outMetadata = dataSource.getMetadata();
            //What geometries are we about to process ?
            Type geomType = getGeometryType(outMetadata);
            //We have a look at the dimension. Are we in 3D ?
            int dimension = getGeometryDimension(dataSource, outMetadata);
            ShapeType shapeType;
            //We shall analyse the type code.
            int typeCode = geomType.getTypeCode();
            //Are we dealing with a general vectorial type ?
            boolean isGeneral = typeCode == Type.GEOMETRY || typeCode == Type.GEOMETRYCOLLECTION;
            //Do we have a constraint upon dimension ?
            boolean noDimCons = geomType.getConstraint(Constraint.DIMENSION_3D_GEOMETRY) == null;
            if (isGeneral && typeCode != Type.NULL && noDimCons) {
                //We're in a case that is too general for a shape... let's
                //try to improve this as far as we can.
                LOG.warn("No geometry type in the " + "metadata. Will take the type of the first geometry");
                shapeType = getFirstShapeType(dataSource, dimension);
                if (shapeType == null) {
                    throw new IllegalArgumentException("A " + "geometry type have to be specified");
                }
            } else {
                shapeType = getShapeType(geomType, dimension);
            }
            int fileLength = computeSize(dataSource, shapeType);
            writer.writeHeaders(fullExtent, shapeType, (int) rowCount, fileLength);
            final int spatialFieldIndex = MetadataUtilities.getSpatialFieldIndex(dataSource.getMetadata());
            for (int i = 0; i < rowCount; i++) {
                if (i >= 100 && i % 100 == 0) {
                    if (pm.isCancelled()) {
                        break;
                    } else {
                        pm.progressTo(i);
                    }
                }

                Value v = dataSource.getFieldValue(i, spatialFieldIndex);

                if (!v.isNull()) {
                    writer.writeGeometry(convertGeometry(v.getAsGeometry(), shapeType));
                } else {
                    writer.writeGeometry(null);
                }
            }
            pm.progressTo(rowCount);
            writer.close();
            writeprj(replaceExtension(file, ".prj"), dataSource.getMetadata());
        } catch (FileNotFoundException e) {
            throw new DriverException(e);
        } catch (IOException e) {
            throw new DriverException(e);
        } catch (ShapefileException e) {
            throw new DriverException(e);
        }
        pm.endTask();
    }

    private File replaceExtension(File file, String suffix) {
        String prefix = file.getAbsolutePath();
        prefix = prefix.substring(0, prefix.lastIndexOf('.'));
        return new File(prefix + suffix);
    }

    private ShapeType getFirstShapeType(DataSet ds, int dimension) throws DriverException {
        final int spatialFieldIndex = MetadataUtilities.getSpatialFieldIndex(ds.getMetadata());
        for (int i = 0; i < ds.getRowCount(); i++) {
            Value v = ds.getFieldValue(i, spatialFieldIndex);
            if (!v.isNull()) {
                return getShapeType(v.getAsGeometry(), dimension);
            }
        }

        return null;
    }

    private ShapeType getShapeType(Geometry geom, int dimension) {
        if (geom instanceof Point) {
            if (dimension == 2) {
                return ShapeType.POINT;
            } else {
                return ShapeType.POINTZ;
            }
        } else if ((geom instanceof LineString) || (geom instanceof MultiLineString)) {
            if (dimension == 2) {
                return ShapeType.ARC;
            } else {
                return ShapeType.ARCZ;
            }
        } else if ((geom instanceof Polygon) || (geom instanceof MultiPolygon)) {
            if (dimension == 2) {
                return ShapeType.POLYGON;
            } else {
                return ShapeType.POLYGONZ;
            }
        } else if (geom instanceof MultiPoint) {
            if (Double.isNaN(geom.getCoordinate().z)) {
                return ShapeType.MULTIPOINT;
            } else {
                return ShapeType.MULTIPOINTZ;
            }
        } else {
            throw new IllegalArgumentException("Unrecognized geometry type : " + geom.getClass());
        }
    }

    private int computeSize(DataSet dataSource, ShapeType type) throws DriverException, ShapefileException {
        final int spatialFieldIndex = MetadataUtilities.getSpatialFieldIndex(dataSource.getMetadata());
        int fileLength = 100;
        for (int i = (int) (dataSource.getRowCount() - 1); i >= 0; i--) {
            Value v = dataSource.getFieldValue(i, spatialFieldIndex);
            if (!v.isNull()) {
                // shape length + record (2 ints)
                int size = type.getShapeHandler().getLength(convertGeometry(v.getAsGeometry(), type)) + 8;
                fileLength += size;
            } else {
                // null byte + record (2 ints)
                fileLength += 4 + 8;
            }
        }

        return fileLength;
    }

    @Override
    public boolean isCommitable() {
        return true;
    }

    @Override
    public int getSupportedType() {
        return SourceManager.FILE | SourceManager.VECTORIAL;
    }

    @Override
    public int getType() {
        return SourceManager.FILE | SourceManager.VECTORIAL;
    }

    @Override
    public String validateMetadata(Metadata m) throws DriverException {
        int spatialIndex = -1;
        DefaultMetadata dbfMeta = new DefaultMetadata();
        for (int i = 0; i < m.getFieldCount(); i++) {
            Type fieldType = m.getFieldType(i);
            int typeCode = fieldType.getTypeCode();
            if (TypeFactory.isVectorial(typeCode) && typeCode != Type.NULL) {
                //At this point, we're sure we have a geometry type that is not Type.NULL
                if (spatialIndex != -1) {
                    return "Cannot store sources with several geometries on a shapefile: "
                            + m.getFieldName(spatialIndex) + " and " + m.getFieldName(i) + " found";
                } else {
                    if (typeCode == Type.GEOMETRY) {
                        return "A generic geometry column is not allowed. It must be of a concrete geometry type "
                                + "other than LineString of Polygon.";
                    } else if (typeCode == Type.LINESTRING) {
                        return "Linestrings are not allowed. Use Multilinestrings instead";
                    } else if (typeCode == Type.POLYGON) {
                        return "Polygons are not allowed. Use Multipolygons instead";
                    }
                    Dimension3DConstraint dc = (Dimension3DConstraint) fieldType
                            .getConstraint(Constraint.DIMENSION_3D_GEOMETRY);
                    if (dc == null) {
                        return "A geometry dimension has to be specified";
                    }
                    spatialIndex = i;
                }
            } else {
                dbfMeta.addField(m.getFieldName(i), fieldType);
            }
        }

        if (spatialIndex == -1) {
            return "Missing spatial field";
        }

        return new DBFDriver().validateMetadata(dbfMeta);
    }

    @Override
    public String[] getFileExtensions() {
        return new String[] { "shp" };
    }

    @Override
    public String getTypeDescription() {
        return "Esri shapefile";
    }

    @Override
    public String getTypeName() {
        return "SHP";
    }

    @Override
    public Schema getSchema() throws DriverException {
        return schema;
    }

    @Override
    public DataSet getTable(String name) {
        if (!name.equals(DriverManager.DEFAULT_SINGLE_TABLE_NAME)) {
            return null;
        }
        return this;
    }

    @Override
    public Value getFieldValue(long rowIndex, int fieldId) throws DriverException {
        try {
            if (fieldId == 0) {
                int offset = shxFile.getOffset((int) rowIndex);
                Geometry shape = reader.geomAt(offset);
                return (null == shape) ? null : ValueFactory.createValue(shape, crs);
            } else {
                return dataSet.getFieldValue(rowIndex, fieldId - 1);
            }
        } catch (IOException e) {
            throw new DriverException(e);
        }
    }

    @Override
    public long getRowCount() throws DriverException {
        return shxFile.getRecordCount();
    }

    @Override
    public Number[] getScope(int dimension) throws DriverException {
        if (dimension == X) {
            return new Number[] { envelope.getMinX(), envelope.getMaxX() };
        } else if (dimension == Y) {
            return new Number[] { envelope.getMinY(), envelope.getMaxY() };
        } else {
            return null;
        }
    }

    @Override
    public Metadata getMetadata() throws DriverException {
        return schema.getTableByName(DriverManager.DEFAULT_SINGLE_TABLE_NAME);
    }

    @Override
    public void setFile(File file) {
        this.file = file;
        schema = new DefaultSchema("SHP" + file.getAbsolutePath().hashCode());
        metadata = new DefaultMetadata();
        schema.addTable(DriverManager.DEFAULT_SINGLE_TABLE_NAME, metadata);
    }

    @Override
    public boolean isOpen() {
        return reader != null;
    }
}