Hard to wrap the question into a title. I build a custom ReprojectionClass today to transform a whole SimpleFeatureCollection into a different CRS (currently hard coded to EPSG:3857

The transformation part works very well. But somehow there is an error I guess. I wrapped this class into a WPS Process to test it and I got back invalid data-types. When I want to get the result as json for example I can see the transformed geometries. The values are correct but it seems like the geometry just gets printed into the attribute table..?

Could you take a look at my code whether this is the correct way to create a SimpleFeatureCollection? (code mainly taken from Ian Turton and the GeoTools help)

import java.util.List;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.JTS;
import org.geotools.referencing.CRS;
import org.locationtech.jts.geom.Geometry;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.AttributeType;
import org.opengis.feature.type.GeometryType;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.geometry.coordinate.Polygon;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

public class ReprojectFeatureClass {

    public ReprojectFeatureClass() {


    public SimpleFeatureCollection reprojectCollection(SimpleFeatureCollection input) {

        DefaultFeatureCollection featureCollection = new DefaultFeatureCollection();
        CoordinateReferenceSystem newCRS;
        CoordinateReferenceSystem oldCRS = input.getSchema().getCoordinateReferenceSystem();

        try {

            newCRS = CRS.decode("EPSG:3857", true);
            MathTransform transform = CRS.findMathTransform(oldCRS, newCRS);

            SimpleFeatureIterator transformationIterator = input.features();
            SimpleFeature feature = null;

            while (transformationIterator.hasNext()) {
                feature = transformationIterator.next();
                Geometry to_transform = (Geometry) feature.getDefaultGeometry();

                // written by Ian Turton
                // https://gis.stackexchange.com/questions/298451/creating-buffer-around-features-using-geotools

                SimpleFeatureType schema = feature.getFeatureType();
                SimpleFeatureTypeBuilder ftBuilder = new SimpleFeatureTypeBuilder();

                for (AttributeDescriptor attrib : schema.getAttributeDescriptors()) {
                    AttributeType type = attrib.getType();

                    if (type instanceof GeometryType) {
                      String oldGeomAttrib = attrib.getLocalName();
                      ftBuilder.add(oldGeomAttrib, Polygon.class);
                    } else {

                SimpleFeatureType nSchema = ftBuilder.buildFeatureType();
                SimpleFeatureBuilder builder = new SimpleFeatureBuilder(nSchema);
                List<Object> atts = feature.getAttributes();
                for (int i = 0; i < atts.size(); i++) {
                  if (atts.get(i) instanceof Geometry) {
                      Geometry transformedGeometry = JTS.transform(((Geometry) to_transform), transform); // transformation takes part here
                      atts.set(i, transformedGeometry);

                SimpleFeature nFeature = builder.buildFeature(null, atts.toArray());


        } catch (NoSuchAuthorityCodeException e) {
            // TODO Auto-generated catch block
        } catch (FactoryException e) {
            // TODO Auto-generated catch block
        } catch (MismatchedDimensionException e) {
            // TODO Auto-generated catch block
        } catch (TransformException e) {
            // TODO Auto-generated catch block

        //Geometry transformedGeometry = JTS.transform(((Geometry) value), transform);

        return featureCollection;


Here is an example input:

"type": "FeatureCollection",
"name": "jsonm",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "id": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ 10.130721990327313, 51.608634889408897 ], [ 10.201811051897559, 51.608634889408897 ], [ 10.15313049886576, 51.536773120647673 ], [ 10.100586409879055, 51.573863065814756 ], [ 10.130721990327313, 51.608634889408897 ] ] ] ] } }

and this is the output:


The input was imported to GeoServer as Shapefile. The json variant here is just a quick conversion made by QGIS (to post it here).

If I transform the original file to EPSG:3857 in QGIS and save the result as GeoJSON I can see that the calculated coordinates match exactly. Just the way the json is build by the WPS process is misformated I guess ...?

  • can you add some example input and output? – Ian Turton Feb 10 at 10:59
  • Ok I added my test data – Leo.1906 Feb 11 at 8:03

It looks like the problem is that you don't set the default geometry, if you compare your code to FeatureTypes.transform() that seems to be the only obvious difference.

 * Forces the specified CRS on geometry attributes (all or some, depends on the parameters).
 * @param schema the original schema
 * @param crs the forced crs
 * @param forceOnlyMissing if true, will force the specified crs only on the attributes that do
 *     miss one
 * @return
 * @throws SchemaException
public static SimpleFeatureType transform(
        SimpleFeatureType schema, CoordinateReferenceSystem crs, boolean forceOnlyMissing)
        throws SchemaException {
    SimpleFeatureTypeBuilder tb = new SimpleFeatureTypeBuilder();

    GeometryDescriptor defaultGeometryType = null;
    for (int i = 0; i < schema.getAttributeCount(); i++) {
        AttributeDescriptor attributeType = schema.getDescriptor(i);
        if (attributeType instanceof GeometryDescriptor) {
            GeometryDescriptor geometryType = (GeometryDescriptor) attributeType;
            AttributeDescriptor forced;

            if (!forceOnlyMissing || geometryType.getCoordinateReferenceSystem() == null) {

            tb.add(geometryType.getLocalName(), geometryType.getType().getBinding());
        } else {
    if (schema.getGeometryDescriptor() != null) {

    tb.setSuperType((SimpleFeatureType) schema.getSuper());

    return tb.buildFeatureType();
  • Thank you for the code example. I copied the setDefaultGeometry part into my code and now the generated json works fine. The creation of a shapefile as output still fails though. Strange thing, right? But nevertheless, when my generated SimpleFeatureCollection seems to be ok now I should be able to work on with that in memory. No need for me to generate a Shapefile as output .. – Leo.1906 Feb 11 at 10:07
  • Ok, changing the import from org.opengis.geometry.coordinate.Polygon; to org.opengis.geometry.coordinate.Polygon; solved that problem – Leo.1906 Feb 11 at 10:33
  • After further testing with more complex data I must say that it is still not working properly.The method creates sometimes invalid geometries ... – Leo.1906 Feb 11 at 10:40
  • there is no general method to go from 4326 to 3857 with out some geometries ending up as invalid (anything +/-85 latitude for example). – Ian Turton Feb 11 at 10:52
  • Yes that might be true but those polygons get destroyed totally so that they are no longer closed for example. QGIS transformes those geometries perfectly. But anyway I will not use this code so there is no need for me to figure this out. This was just an answer to my prvious problem here: gis.stackexchange.com/questions/307957/… But I have this problem figured out now. I will post an answer there soon. Thank you aggain for your help and all the effort you provide here at stackexchange! Your code examples always help me. – Leo.1906 Feb 11 at 12:21

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.