/** * H2GIS is a library that brings spatial support to the H2 Database Engine * <http://www.h2database.com>. H2GIS is developed by CNRS * <http://www.cnrs.fr/>. * * This code is part of the H2GIS project. H2GIS is free software; * you can redistribute it and/or modify it under the terms of the GNU * Lesser General Public License as published by the Free Software Foundation; * version 3.0 of the License. * * H2GIS 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 Lesser General Public License * for more details <http://www.gnu.org/licenses/>. * * * For more information, please consult: <http://www.h2gis.org/> * or contact directly: info_at_h2gis.org */ package org.h2gis.functions.io.shp.internal; import com.vividsolutions.jts.geom.Geometry; import org.h2gis.functions.io.FileDriver; import org.h2gis.functions.io.dbf.internal.DBFDriver; import org.h2gis.functions.io.dbf.internal.DbaseFileHeader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; /** * Merge ShapeFileReader and DBFReader. * TODO Handle SHP without SHX and/or DBF * * How to use: * * In Write mode, * Declare fields by calling {@link SHPDriver#initDriver(java.io.File, ShapeType, org.h2gis.drivers.dbf.internal.DbaseFileHeader)} * then write row using * * * @author Nicolas Fortin */ public class SHPDriver implements FileDriver { private DBFDriver dbfDriver = new DBFDriver(); private File shpFile; private File shxFile; private File dbfFile ; private ShapefileReader shapefileReader; private ShapefileWriter shapefileWriter; private IndexFile shxFileReader; private int geometryFieldIndex = 0; private ShapeType shapeType; public File prjFile; private int srid =0; /** * @param geometryFieldIndex The geometry field index in getRow() array. */ public void setGeometryFieldIndex(int geometryFieldIndex) { this.geometryFieldIndex = geometryFieldIndex; } /** * Insert values in the row * @param values * @throws IOException */ @Override public void insertRow(Object[] values) throws IOException { if(!(values[geometryFieldIndex] instanceof Geometry)) { if(values[geometryFieldIndex]==null) { throw new IOException("Shape files do not support NULL Geometry values."); } else { throw new IllegalArgumentException("Field at "+geometryFieldIndex+" should be an instance of Geometry," + " found "+values[geometryFieldIndex].getClass()+" instead."); } } shapefileWriter.writeGeometry((Geometry)values[geometryFieldIndex]); // Extract the DBF part of the row Object[] dbfValues = new Object[values.length - 1]; // Copy DBF data before geometryFieldIndex if(geometryFieldIndex > 0) { System.arraycopy(values, 0, dbfValues, 0, geometryFieldIndex); } // Copy DBF data after geometryFieldIndex if(geometryFieldIndex + 1 < values.length) { System.arraycopy(values, geometryFieldIndex + 1, dbfValues, geometryFieldIndex, dbfValues.length - geometryFieldIndex); } dbfDriver.insertRow(dbfValues); } /** * @return The geometry field index in getRow() array. */ public int getGeometryFieldIndex() { return geometryFieldIndex; } /** * Init Driver for Write mode * @param shpFile * @param shapeType * @param dbaseHeader * @throws IOException */ public void initDriver(File shpFile, ShapeType shapeType, DbaseFileHeader dbaseHeader) throws IOException { String path = shpFile.getAbsolutePath(); String nameWithoutExt = path.substring(0,path.lastIndexOf('.')); this.shpFile = new File(nameWithoutExt+".shp"); this.shxFile = new File(nameWithoutExt+".shx"); this.dbfFile = new File(nameWithoutExt+".dbf"); FileOutputStream shpFos = new FileOutputStream(shpFile); FileOutputStream shxFos = new FileOutputStream(shxFile); shapefileWriter = new ShapefileWriter(shpFos.getChannel(), shxFos.getChannel()); this.shapeType = shapeType; shapefileWriter.writeHeaders(shapeType); dbfDriver.initDriver(dbfFile, dbaseHeader); } /** * Init this driver from existing files, then open theses files. * @param shpFile Shape file path. * @throws IOException */ public void initDriverFromFile(File shpFile) throws IOException { initDriverFromFile(shpFile, null); } /** * Init this driver from existing files, then open theses files. * @param shpFile Shape file path. * @param forceEncoding If defined use this encoding instead of the one defined in dbf header. * @throws IOException */ public void initDriverFromFile(File shpFile, String forceEncoding) throws IOException { // Read columns from files metadata this.shpFile = shpFile; if(!shpFile.exists()){ throw new FileNotFoundException("The following file does not exists: " + shpFile.getPath()); } // Find appropriate file extension for shx and dbf, maybe SHX or Shx.. //String shxFileName = shpFile.getName(); String fileName = shpFile.getAbsolutePath(); final int dotIndex = fileName.lastIndexOf('.'); final String fileNamePrefix = fileName.substring(0, dotIndex).toLowerCase(); DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() { @Override public boolean accept(Path entry) throws IOException { String path = entry.toAbsolutePath().toString().toLowerCase(); if(path.equals(fileNamePrefix+".shx")){ shxFile = entry.toFile(); return true; } else if(path.equals(fileNamePrefix+".dbf")){ dbfFile = entry.toFile(); return true; } else if(path.equals(fileNamePrefix+".prj")){ prjFile = entry.toFile(); return true; } return false; } }; try (DirectoryStream<Path> stream = Files.newDirectoryStream(shpFile.getParentFile().toPath(), filter)) { for (Path pathDir : stream) { //Do nothing } } if(dbfFile != null) { dbfDriver.initDriverFromFile(dbfFile, forceEncoding); } else { throw new IllegalArgumentException("DBF File not found"); } if(shxFile==null){ throw new IllegalArgumentException("SHX File not found"); } FileInputStream shpFis = new FileInputStream(shpFile); shapefileReader = new ShapefileReader(shpFis.getChannel()); FileInputStream shxFis = new FileInputStream(shxFile); shxFileReader = new IndexFile(shxFis.getChannel()); } /** * @return Dbase file header */ public DbaseFileHeader getDbaseFileHeader() { return dbfDriver.getDbaseFileHeader(); } @Override public long getRowCount() { return dbfDriver.getRowCount(); } /** * @return ShapeFile header */ public ShapefileHeader getShapeFileHeader() { return shapefileReader.getHeader(); } @Override public void close() throws IOException { dbfDriver.close(); if(shapefileReader != null) { shapefileReader.close(); shxFileReader.close(); } else if(shapefileWriter != null) { // Update header shapefileWriter.writeHeaders(shapeType); shapefileWriter.close(); } } public int getFieldCount() { return dbfDriver.getFieldCount() + 1; } @Override public Object[] getRow(long rowId) throws IOException { final int fieldCount = getFieldCount(); Object[] values = new Object[fieldCount]; // Copy dbf values Object[] dbfValues = dbfDriver.getRow(rowId); // Copy dbf values before geometryFieldIndex if(geometryFieldIndex > 0) { System.arraycopy(dbfValues, 0, values, 0, geometryFieldIndex); } Geometry geom = shapefileReader.geomAt(shxFileReader.getOffset((int)rowId)); if(geom!=null){ geom.setSRID(getSrid()); } values[geometryFieldIndex] = geom; // Copy dbf values after geometryFieldIndex if(geometryFieldIndex < dbfValues.length) { System.arraycopy(dbfValues, geometryFieldIndex, values, geometryFieldIndex + 1, dbfValues.length); } return values; } /** * Set a SRID code that will be used for geometries. * @param srid */ public void setSRID(int srid) { this.srid=srid; } /** * Get the SRID code * @return */ public int getSrid() { return srid; } }