/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2014, Open Source Geospatial Foundation (OSGeo) * * This library 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 2.1 of the License. * * This library 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. */ package org.geotools.process.spatialstatistics.storage; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.math.BigInteger; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.FeatureWriter; import org.geotools.data.shapefile.dbf.DbaseFileException; import org.geotools.data.shapefile.dbf.DbaseFileWriter; import org.geotools.data.shapefile.files.ShpFileType; import org.geotools.data.shapefile.files.ShpFiles; import org.geotools.data.shapefile.files.StorageFile; import org.geotools.data.shapefile.shp.JTSUtilities; import org.geotools.data.shapefile.shp.ShapeHandler; import org.geotools.data.shapefile.shp.ShapeType; import org.geotools.data.shapefile.shp.ShapefileWriter; import org.geotools.data.simple.SimpleFeatureCollection; import org.geotools.data.simple.SimpleFeatureIterator; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.feature.FeatureTypes; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.referencing.crs.CoordinateReferenceSystem; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; /** * Shapefile Feature Inserter * * @author Minpa Lee, MangoSystem * * @source $URL$ */ @SuppressWarnings("nls") public class ShapefileFeatureInserter implements IFeatureInserter { protected static final Logger LOGGER = Logging.getLogger(ShapefileFeatureInserter.class); static final int DBF_LIMIT = 10; // 1. FeatureInserter boolean isSuccess = false; String dataStore; String outptuName; CoordinateReferenceSystem sourceCRS; SimpleFeatureBuilder sfBuilder; List<FieldMap> fieldMaps = new ArrayList<FieldMap>(); // 2. Shapefile & dbf private Map<ShpFileType, StorageFile> storageFiles;; private ShapeType shapeType; private int shapefileLength = 100; private int numberOfFeatures = 0; private ShapeHandler handler; private ShapefileWriter shpWriter; private DbaseFileWriter dbfWriter; private FileChannel dbfChannel; private Charset dbfCharset = Charset.forName(DataStoreFactory.DEFAULT_CHARSET); private TimeZone dbfTimeZone = TimeZone.getDefault();; private DbaseFileHeader2 dbfHeader; private byte[] writeFlags; private Object[] transferCache; private Envelope bounds; public ShapefileFeatureInserter(String folder, SimpleFeatureType inputSchema) { this.sourceCRS = inputSchema.getCoordinateReferenceSystem(); this.dataStore = folder; this.outptuName = inputSchema.getTypeName(); this.fieldMaps.clear(); this.sfBuilder = new SimpleFeatureBuilder(inputSchema); try { init(inputSchema, dataStore, outptuName); } catch (IOException e) { LOGGER.log(Level.FINER, e.getMessage(), e); } } @Override public FeatureWriter<SimpleFeatureType, SimpleFeature> getFeatureWriter() { return null; } @Override public SimpleFeatureSource getFeatureSource() { try { if (isSuccess) { return DataStoreFactory.getShapefile(dataStore, outptuName); } } catch (IOException e) { LOGGER.log(Level.FINER, e.getMessage(), e); } return null; } @Override public SimpleFeatureCollection getFeatureCollection() throws IOException { SimpleFeatureSource featureSource = getFeatureSource(); if (featureSource == null) { return null; } else { return featureSource.getFeatures(); } } @Override public int getFlushInterval() { return 0; } @Override public void setFlushInterval(int flushInterval) { } @Override public int getFeatureCount() { return numberOfFeatures; } @Override public SimpleFeature buildFeature() throws IOException { return sfBuilder.buildFeature(null); } @Override public void write(SimpleFeatureCollection featureCollection) throws IOException { SimpleFeatureIterator iter = featureCollection.features(); try { while (iter.hasNext()) { final SimpleFeature feature = iter.next(); SimpleFeature newFeature = this.buildFeature(); this.copyAttributes(feature, newFeature, true); this.write(newFeature); } } finally { iter.close(); } } @Override public void write(SimpleFeature newFeature) throws IOException { Geometry geometry = (Geometry) newFeature.getDefaultGeometry(); geometry = JTSUtilities.convertToCollection(geometry, shapeType); if (geometry == null || geometry.isEmpty()) { return; } shapefileLength += (handler.getLength(geometry) + 8); shpWriter.writeGeometry(geometry); bounds.expandToInclude(geometry.getEnvelopeInternal()); // writing attributes int pos = 0; for (int index = 0; index < writeFlags.length; index++) { // skip geometries if (writeFlags[index] > 0) { switch (dbfHeader.getFieldType(pos)) { case 'C': case 'c': final Object objValue = newFeature.getAttribute(index); if (objValue == null) { transferCache[pos] = objValue; } else { final int len = dbfHeader.getFieldLength(pos); String strVal = objValue.toString(); if (strVal.getBytes().length > len) { // GeoTools DbaseFileWriter는 한글문제를 고려하지 않음 transferCache[pos] = substringBytes(strVal, len); } else { transferCache[pos] = objValue; } } break; default: transferCache[pos] = newFeature.getAttribute(index); break; } pos++; } } dbfWriter.write(transferCache); numberOfFeatures++; } @Override public void rollback() throws IOException { this.close(); isSuccess = false; // delete shapefile final ShpFiles shpFiles = getShapeFiles(dataStore, outptuName); shpFiles.delete(); } @Override public void rollback(Exception e) throws IOException { rollback(); LOGGER.log(Level.WARNING, e.getMessage(), e); } @Override public void close() throws IOException { if (numberOfFeatures > 0) { // rewrite header shpWriter.writeHeaders(bounds, shapeType, numberOfFeatures, shapefileLength); dbfHeader.setNumRecords(numberOfFeatures); dbfChannel.position(0); dbfHeader.writeHeader(dbfChannel); } writePrj(sourceCRS); shpWriter.close(); dbfWriter.close(); shpWriter = null; dbfWriter = null; StorageFile.replaceOriginals(storageFiles.values().toArray(new StorageFile[0])); storageFiles.clear(); isSuccess = true; } @Override public void close(SimpleFeatureIterator featureIter) throws IOException { close(); if (featureIter != null) { featureIter.close(); } } @Override public SimpleFeature copyAttributes(SimpleFeature source, SimpleFeature target, boolean copyGeometry) { if (this.fieldMaps.size() == 0) { fieldMaps = FieldMap.buildMap(source.getFeatureType(), target.getFeatureType()); } for (FieldMap fieldMap : this.fieldMaps) { if (fieldMap.isGeometry) { if (copyGeometry) { target.setDefaultGeometry(source.getDefaultGeometry()); } } else { target.setAttribute(fieldMap.destID, source.getAttribute(fieldMap.soruceID)); } } return target; } @Override public void clearFieldMaps() { this.fieldMaps.clear(); } private ShpFiles getShapeFiles(String dataStore, String outptuName) throws IOException { if (!outptuName.toLowerCase().endsWith(".shp")) { outptuName = outptuName + ".shp"; } String shapeFileName = dataStore + outptuName; if (!dataStore.endsWith(File.separator)) { shapeFileName = dataStore + File.separator + outptuName; } return new ShpFiles(shapeFileName); } private void init(SimpleFeatureType featureType, String dataStore, String outptuName) throws IOException { // initialize variables shapefileLength = 100; numberOfFeatures = 0; bounds = new Envelope(); final ShpFiles shpFiles = getShapeFiles(dataStore, outptuName); shpFiles.delete(); storageFiles = new HashMap<ShpFileType, StorageFile>(); storageFiles.put(ShpFileType.SHP, shpFiles.getStorageFile(ShpFileType.SHP)); storageFiles.put(ShpFileType.SHX, shpFiles.getStorageFile(ShpFileType.SHX)); storageFiles.put(ShpFileType.DBF, shpFiles.getStorageFile(ShpFileType.DBF)); storageFiles.put(ShpFileType.PRJ, shpFiles.getStorageFile(ShpFileType.PRJ)); dbfHeader = createDbaseHeader(featureType); // open underlying writers FileChannel shpChannel = storageFiles.get(ShpFileType.SHP).getWriteChannel(); FileChannel shxChannel = storageFiles.get(ShpFileType.SHX).getWriteChannel(); shpWriter = new ShapefileWriter(shpChannel, shxChannel); dbfChannel = storageFiles.get(ShpFileType.DBF).getWriteChannel(); dbfWriter = new DbaseFileWriter(dbfHeader, dbfChannel, dbfCharset, dbfTimeZone); shpWriter.writeHeaders(new Envelope(), shapeType, numberOfFeatures, shapefileLength); } private void writePrj(CoordinateReferenceSystem crs) throws IOException { if (crs == null) { LOGGER.warning("PRJ file not generated for null CoordinateReferenceSystem"); return; } FileWriter prjWriter = new FileWriter(storageFiles.get(ShpFileType.PRJ).getFile()); try { String wkt = crs.toWKT().replaceAll("\n", "").replaceAll(" ", ""); prjWriter.write(wkt); } finally { prjWriter.close(); } } private DbaseFileHeader2 createDbaseHeader(SimpleFeatureType featureType) throws IOException, DbaseFileException { DbaseFileHeader2 header = new DbaseFileHeader2(); final int attributeCount = featureType.getAttributeCount(); writeFlags = new byte[attributeCount]; List<String> uniqueFields = new ArrayList<String>(); int dbfFieldCount = 0; for (int index = 0; index < attributeCount; index++) { AttributeDescriptor descriptor = featureType.getDescriptor(index); Class<?> binding = descriptor.getType().getBinding(); String columnName = descriptor.getLocalName(); if (columnName.length() > DBF_LIMIT) { LOGGER.warning(columnName + " has more than 10 characters, and truncated"); columnName = columnName.substring(0, 10); // must be a unique name if (uniqueFields.contains(columnName)) { int increment = 0; while (true) { int subLen = increment >= 10 ? 7 : 8; columnName = columnName.substring(0, subLen) + "_" + (++increment); if (!uniqueFields.contains(columnName)) { break; } } } } if (Geometry.class.isAssignableFrom(binding)) { shapeType = JTSUtilities.getShapeType(binding); handler = shapeType.getShapeHandler(new GeometryFactory()); } else { int fieldLen = FeatureTypes.getFieldLength(descriptor); if (fieldLen == FeatureTypes.ANY_LENGTH) { fieldLen = 255; } if ((binding == Integer.class) || (binding == Short.class) || (binding == Byte.class)) { header.addColumn(columnName, 'N', Math.min(fieldLen, 9), 0); } else if (binding == Long.class) { header.addColumn(columnName, 'N', Math.min(fieldLen, 19), 0); } else if (binding == BigInteger.class) { header.addColumn(columnName, 'N', Math.min(fieldLen, 33), 0); } else if (Number.class.isAssignableFrom(binding)) { int l = Math.min(fieldLen, 33); int d = Math.max(l - 2, 0); header.addColumn(columnName, 'N', l, d); } else if (java.util.Date.class.isAssignableFrom(binding) && Boolean.getBoolean("org.geotools.shapefile.datetime")) { header.addColumn(columnName, '@', fieldLen, 0); } else if (java.util.Date.class.isAssignableFrom(binding) || Calendar.class.isAssignableFrom(binding)) { header.addColumn(columnName, 'D', fieldLen, 0); } else if (binding == Boolean.class) { header.addColumn(columnName, 'L', 1, 0); } else if (CharSequence.class.isAssignableFrom(binding) || binding == java.util.UUID.class) { header.addColumn(columnName, 'C', Math.min(254, fieldLen), 0); } else { LOGGER.warning("Unable to write : " + binding.getName()); continue; } dbfFieldCount++; writeFlags[index] = (byte) 1; } uniqueFields.add(columnName); } // dbf transfer buffer transferCache = new Object[dbfFieldCount]; return header; } private String substringBytes(String value, int byte_len) { int retLength = 0; int tempSize = 0; for (int index = 1; index <= value.length(); index++) { int asc = value.charAt(index - 1); if (asc > 127) { if (byte_len >= tempSize + 2) { tempSize += 2; retLength++; } else { return value.substring(0, retLength); } } else { if (byte_len > tempSize) { tempSize++; retLength++; } } } return value.substring(0, retLength); } }