Here is an example of making a shapefile using a CSV file as a starting point.
Goals
The tutorial covers the following:
- Producing Features from Scratch
- Use of GeometryFactory to build a Point
- Writing out a Shapefile
- Forcing a Projection
- Building a FeatureType
Sample Data
To start with you will need a CSV file
"Longitude","Latitude","Name" -123.31,48.4,Victoria 0,52,London
Example Code
Here is the Skeleton of the application, we will fill in the method contents as we go:
package org.geotools.demo; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.HashMap; import java.util.Map; import javax.swing.JFileChooser; import javax.swing.filechooser.FileFilter; import org.geotools.data.DataStoreFactorySpi; import org.geotools.data.DataUtilities; import org.geotools.data.DefaultTransaction; import org.geotools.data.FeatureStore; import org.geotools.data.Transaction; import org.geotools.data.shapefile.ShapefileDataStore; import org.geotools.data.shapefile.ShapefileDataStoreFactory; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureCollections; import org.geotools.feature.simple.SimpleFeatureBuilder; import org.geotools.feature.simple.SimpleFeatureTypeBuilder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; public class Csv2Shape { /** * This example takes a CSV file and produces a shapefile. * <p> * The interesting part of this example is the use of a Factory when creating objects. * <pre><code> * GeometryFactory factory = new GeometryFactory(); * Point point = factory.createPoint( new Coordinate(longitude,latitude)); * </code></pre> * These two classes come from the JTS Topology Suite responsible for the * "rocket science" aspect of GIS - determining the relationships between * geometry shapes. * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { File file = getCSVFile(args); final SimpleFeatureType TYPE = DataUtilities.createType("Location", "location:Point,name:String"); // see createFeatureType(); FeatureCollection collection = FeatureCollections.newCollection(); BufferedReader reader = new BufferedReader( new FileReader( file )); try { String line = reader.readLine(); System.out.println( "Header: "+ line ); for( line = reader.readLine(); line != null; line = reader.readLine()){ String split[] = line.split("\\,"); double longitude = Double.parseDouble( split[0] ); double latitude = Double.parseDouble( split[1] ); String name = split[2]; GeometryFactory factory = new GeometryFactory(); Point point = factory.createPoint( new Coordinate(longitude,latitude)); SimpleFeature feature = SimpleFeatureBuilder.build( TYPE, new Object[]{point, name}, null ); collection.add( feature ); } } finally { reader.close(); } File newFile = getNewShapeFile( file ); DataStoreFactorySpi factory = new ShapefileDataStoreFactory(); Map<String,Object> create = new HashMap<String,Object>(); create.put("url", newFile.toURI().toURL() ); create.put("create spatial index",Boolean.TRUE); ShapefileDataStore newDataStore = (ShapefileDataStore) factory.createNewDataStore( create ); newDataStore.createSchema( TYPE ); newDataStore.forceSchemaCRS( DefaultGeographicCRS.WGS84 ); Transaction transaction = new DefaultTransaction("create"); String typeName = newDataStore.getTypeNames()[0]; FeatureStore featureStore = (FeatureStore) newDataStore.getFeatureSource( typeName ); featureStore.setTransaction(transaction); try { featureStore.addFeatures(collection); transaction.commit(); } catch (Exception problem){ problem.printStackTrace(); transaction.rollback(); } finally { transaction.close(); } System.exit(0); } private static File getNewShapeFile(File file) { String path = file.getAbsolutePath(); String newPath = path.substring(0,path.length()-4)+".shp"; JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle("Save shapefile"); chooser.setSelectedFile( new File( newPath )); chooser.setFileFilter( new FileFilter(){ public boolean accept( File f ) { return f.isDirectory() || f.getPath().endsWith("shp") || f.getPath().endsWith("SHP"); } public String getDescription() { return "Shapefiles"; } }); int returnVal = chooser.showSaveDialog(null); if(returnVal != JFileChooser.APPROVE_OPTION) { System.exit(0); } File newFile = chooser.getSelectedFile(); if( newFile.equals( file )){ System.out.println("Cannot replace "+file); System.exit(0); } return newFile; } private static File getCSVFile(String[] args) throws FileNotFoundException { File file; if (args.length == 0){ JFileChooser chooser = new JFileChooser(); chooser.setDialogTitle("Open CSV file"); chooser.setFileFilter( new FileFilter(){ public boolean accept( File f ) { return f.isDirectory() || f.getPath().endsWith("csv") || f.getPath().endsWith("CSV"); } public String getDescription() { return "Comma Seperated Value"; } }); int returnVal = chooser.showOpenDialog( null ); if(returnVal != JFileChooser.APPROVE_OPTION) { System.exit(0); } file = chooser.getSelectedFile(); System.out.println("Opening CVS file: " + file.getName()); } else { file = new File( args[0] ); } if (!file.exists()){ throw new FileNotFoundException( file.getAbsolutePath() ); } return file; } }
Running the Application
- When you run this application it will prompt you for the location of a CSV file to read, and then a shapefile to create
Building a SimpleFeatureType
We are going to build a SimpleFeatureType using SimpleFeatureTypeBuilder. In the example above we created a SimpleFeatureType using the following snippet:
final SimpleFeatureType TYPE = DataUtilities.createType("Location", "location:Point,name:String");
I often use a constant to hold the SimpleFeatureType; because the SimpleFeatureType class is immutable I find tracking them as final variables helps me remember what they are.
The createSchema method is fine for a quick example; but has a couple of disadvantages:
- you cannot specify the CoordinateReferneceSystem of your "location" attribute
- you cannot specify the max string length (so your DBF files may be bigger than strictly required).
- the javadocs on the createSchema parameters are a bit hard to follow
Here is how to use SimpleFeatureTypeBuilder to accomplish the same result:
SimpleFeatureType createFeatureType(){
SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName( "Location" );
builder.setCRS( DefaultGeographicCRS.WGS84 );
//add attributes in order
builder.add( "Location", Point.class );
builder.length(15).add( "Name", String.class );
//build the type
final SimpleFeatureType LOCATION = builder.buildFeatureType();
return LOCATION;
}
Alternative for GeoTools 2.4 - use AttributeTypeFactory
The AttributeTypeFactory.newAttributeType is used to create both AttributeTypes and GeometryAttributeTypes. The factory will create a GoemetryAttributeType when a JTS Geometry class is used, and you can use the "metdata" paramters to specify the CoordinateRefernenceSystem.
final AttributeType LOCATION = AttributeTypeFactory.newAttributeType( "Location", Point.class, false, 0, null, DefaultGeographicCRS.WGS84); final AttributeType NAME = AttributeTypeFactory.newAttributeType( "Name", // name of attribute type String.class, // attribute type binding true, // nillable 15 // 15 characters allowed );
You can use FeatureTypeBuilder to bundle these up into a FeatureType to describe the contents of your shapefile.
FeatureTypeBuilder builder = FeatureTypeBuilder.newInstance("Landmarks");
builder.addType( LOCATION );
builder.addType( NAME );
FeatureType type = builder.getFeatureType();
The AttributeTypeFactory implementation is a bit messed up, you can <b>only</b> use the static final methods to get anything done. This is something we are fixing for GeoTools 2.5.
You will also need to use type when creating a feature:
Feature feature = TYPE.create( null, new Object[]{point, name});
For more information on the differences: