Added by jgarnett, last edited by jgarnett on Apr 16, 2008  (view change)

Labels

 
(None)

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

  1. 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: