The problem background

Recently, the software POIKit needs to provide the conversion between GEOJSON and SHP data, and consider using GeoTools to achieve this function. GeoTools is an open source Java GIS library based on OGC specification. Support reading and conversion of vector data formats such as CSV, GEOJSON, ShapeFile and WFS, but the official website only provides a tutorial on CSV conversion to SHP, and there are not many articles on the conversion of the two data at home and abroad. After some setbacks, I found a simple way to realize the conversion of the two data.

Install GeoTools using Maven

For this build using Maven, the reference to GeoTools in POM.xml is as follows:

<repositories>
    <repository>
        <id>osgeo</id>
        <name>OSGeo Release Repository</name>
        <url>https://repo.osgeo.org/repository/release/</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <releases>
            <enabled>true</enabled>
        </releases>
    </repository>
</repositories>
<properties>
    <geotools.version>25.0</geotools.version>
</properties>
<dependency>
    <! -- ShapeFile component -->
    <groupId>org.geotools</groupId>
    <artifactId>gt-shapefile</artifactId>
    <version>${geotools.version}</version>
</dependency>
<dependency>
    <! -- GeoJSON component -->
    <groupId>org.geotools</groupId>
    <artifactId>gt-geojson</artifactId>
    <version>${geotools.version}</version>
</dependency>
<dependency>
    <! Geojson data store -->
    <groupId>org.geotools</groupId>
    <artifactId>gt-geojsondatastore</artifactId>
    <version>${geotools.version}</version>
</dependency>
Copy the code

Note: Domestic users typically configured ali cloud image, and some of the mirror configuration tutorial is often wrong, tend to mirrorOf parameter is set to *, in this case, ali cloud mirror can intercept all maven requests, and to their own mirror warehouse request data download, but in fact, ali cloud mirror image is only available for the central resources, No GeoTools resources are included, so in this case, Maven can’t get the JAR packages we need. Therefore, we need to set the mirrorOf parameter value to central and configure repository.

<! -- Aliyun Mirror -->
<mirror>
    <id>nexus-aliyun</id>
    <mirrorOf>central</mirrorOf>
    <name>Nexus aliyun</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
Copy the code

These are the brief reasons for configuring Repository. For details, see The introduction of GeoTools in Maven – Repository and Mirror.

GeoJSON To Shp

If you’re too lazy To look at the analysis, you can jump directly To GeoJSON To Shp To see the full code.

Correctly converting GeoJSON to Shp requires the following:

  1. Spatial data and attribute data can be displayed normally.
  2. If the GeoJSON file is configured with CRS properties, you need to read THE CRS to set the SHP coordinate system; otherwise, set it to WGS84.
  3. To avoid garbled characters, SHP data should be provided in CPG format;

Assume that the geoJSON file path is geojsonPath and the output SHP file path is shpPath.

First, get FeatureCollection from the file path:

InputStream in = new FileInputStream(geojsonPath);
GeometryJSON gjson = new GeometryJSON();
FeatureJSON fjson = new FeatureJSON(gjson);
FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(in);
Copy the code

By the way: What if it’s a GeoJSON string? Only need to:

Reader reader = new StringReader(geojson);
GeometryJSON gjson = new GeometryJSON();
FeatureJSON fjson = new FeatureJSON(gjson);
FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(reader);
Copy the code

Geotools specifies that when converting to SHP, the space property must be first and is forced to be named the_geom, so you need to get all the geoJSON properties and create the_geom property:

SimpleFeatureType schema = features.getSchema();
GeometryDescriptor geom = schema.getGeometryDescriptor();
// All properties of the geojson file
List<AttributeDescriptor> attributes = schema.getAttributeDescriptors();
// Geojson file space type
GeometryType geomType = null;
// Store geoJSON non-spatial attributes
List<AttributeDescriptor> attribs = new ArrayList<>();
for (AttributeDescriptor attrib : attributes) {
    AttributeType type = attrib.getType();
    if (type instanceof GeometryType) {
        geomType = (GeometryType) type;
    } else{ attribs.add(attrib); }}if (geomType == null)
    return false;

// Create the_geom type with geomType
GeometryTypeImpl gt = new GeometryTypeImpl(new NameImpl("the_geom"), geomType.getBinding(),
        geom.getCoordinateReferenceSystem() == null ? DefaultGeographicCRS.WGS84 : geom.getCoordinateReferenceSystem(), // If no user is specified, the default value is wGS84
        geomType.isIdentified(), geomType.isAbstract(), geomType.getRestrictions(),
        geomType.getSuper(), geomType.getDescription());

// Create space attributes according to the_geom type
GeometryDescriptor geomDesc = new GeometryDescriptorImpl(gt, new NameImpl("the_geom"), geom.getMinOccurs(),
        geom.getMaxOccurs(), geom.isNillable(), geom.getDefaultValue());

// The the_geom attribute must be the first
attribs.add(0, geomDesc);
Copy the code

Next, create a schema that can be converted to a shapefile based on the created attribs and the original schema information, and use try-with-resources to get the output features set:

SimpleFeatureType outSchema = new SimpleFeatureTypeImpl(schema.getName(), attribs, geomDesc, schema.isAbstract(),
        schema.getRestrictions(), schema.getSuper(), schema.getDescription());
List<SimpleFeature> outFeatures = new ArrayList<>();
try (FeatureIterator<SimpleFeature> features2 = features.features()) {
    while (features2.hasNext()) {
        SimpleFeature f = features2.next();
        SimpleFeature reType = DataUtilities.reType(outSchema, f, true); reType.setAttribute(outSchema.getGeometryDescriptor().getName(), f.getAttribute(schema.getGeometryDescriptor().getName())); outFeatures.add(reType); }}Copy the code

Finally, according to the websiteCSV to SHPTutorial, we can write features to shapefile method.

The complete code for this feature is as follows:

/** * Save features to SHP format **@paramFeatures *@paramTYPE Element TYPE *@paramShpPath SHP save path *@returnWhether the file is saved successfully */
public static boolean saveFeaturesToShp(List<SimpleFeature> features, SimpleFeatureType TYPE, String shpPath) {
    try {
        ShapefileDataStoreFactory dataStoreFactory = new ShapefileDataStoreFactory();
        File shpFile = new File(shpPath);
        Map<String, Serializable> params = new HashMap<>();
        params.put("url", shpFile.toURI().toURL());
        params.put("create spatial index", Boolean.TRUE);

        ShapefileDataStore newDataStore =
                (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
        newDataStore.setCharset(StandardCharsets.UTF_8);

        newDataStore.createSchema(TYPE);

        Transaction transaction = new DefaultTransaction("create");
        String typeName = newDataStore.getTypeNames()[0];
        SimpleFeatureSource featureSource = newDataStore.getFeatureSource(typeName);

        if (featureSource instanceof SimpleFeatureStore) {
            SimpleFeatureStore featureStore = (SimpleFeatureStore) featureSource;
            SimpleFeatureCollection collection = new ListFeatureCollection(TYPE, features);
            featureStore.setTransaction(transaction);
            try {
                featureStore.addFeatures(collection);
                FileUtil.generateCpgFile(shpPath, StandardCharsets.UTF_8);
                transaction.commit();
            } catch (Exception problem) {
                problem.printStackTrace();
                transaction.rollback();
            } finally{ transaction.close(); }}else {
            System.out.println(typeName + " does not support read/write access"); }}catch (IOException e) {
        return false;
    }
    return true;
}

/**
 * GeoJson to Shp
 *
 * @paramGeojsonPath Path to the geoJSON file *@paramShpPath SHP file path *@returnWhether the conversion was successful */
public static boolean transformGeoJsonToShp(String geojsonPath, String shpPath) {
    try {
        // open geojson
        InputStream in = new FileInputStream(geojsonPath);
        GeometryJSON gjson = new GeometryJSON();
        FeatureJSON fjson = new FeatureJSON(gjson);
        FeatureCollection<SimpleFeatureType, SimpleFeature> features = fjson.readFeatureCollection(in);
        // convert schema for shapefile
        SimpleFeatureType schema = features.getSchema();
        GeometryDescriptor geom = schema.getGeometryDescriptor();
        // Geojson file properties
        List<AttributeDescriptor> attributes = schema.getAttributeDescriptors();
        // Geojson file space type (must be first)
        GeometryType geomType = null;
        List<AttributeDescriptor> attribs = new ArrayList<>();
        for (AttributeDescriptor attrib : attributes) {
            AttributeType type = attrib.getType();
            if (type instanceof GeometryType) {
                geomType = (GeometryType) type;
            } else{ attribs.add(attrib); }}if (geomType == null)
            return false;

        // Create GT using geomType
        GeometryTypeImpl gt = new GeometryTypeImpl(new NameImpl("the_geom"), geomType.getBinding(),
                geom.getCoordinateReferenceSystem() == null ? DefaultGeographicCRS.WGS84 : geom.getCoordinateReferenceSystem(), // If no user is specified, the default value is wGS84
                geomType.isIdentified(), geomType.isAbstract(), geomType.getRestrictions(),
                geomType.getSuper(), geomType.getDescription());

        // Create an identifier
        GeometryDescriptor geomDesc = new GeometryDescriptorImpl(gt, new NameImpl("the_geom"), geom.getMinOccurs(),
                geom.getMaxOccurs(), geom.isNillable(), geom.getDefaultValue());

        // The the_geom attribute must be the first
        attribs.add(0, geomDesc);

        SimpleFeatureType outSchema = new SimpleFeatureTypeImpl(schema.getName(), attribs, geomDesc, schema.isAbstract(),
                schema.getRestrictions(), schema.getSuper(), schema.getDescription());
        List<SimpleFeature> outFeatures = new ArrayList<>();
        try (FeatureIterator<SimpleFeature> features2 = features.features()) {
            while (features2.hasNext()) {
                SimpleFeature f = features2.next();
                SimpleFeature reType = DataUtilities.reType(outSchema, f, true); reType.setAttribute(outSchema.getGeometryDescriptor().getName(), f.getAttribute(schema.getGeometryDescriptor().getName())); outFeatures.add(reType); }}return saveFeaturesToShp(outFeatures, outSchema, shpPath);
    } catch (IOException e) {
        e.printStackTrace();
        return false; }}Copy the code

Shp To GeoJSON

GeoTools has many tutorials on shapefiles and is well supported and relatively simple, but it should be noted that converting shapefiles to GEOJSON should generate CRS. The conversion code is as follows:

public static boolean transformShpToGeoJson(String shpPath, String geojsonPath) {
    try {
        File file = new File(shpPath);
        FileDataStore myData = FileDataStoreFinder.getDataStore(file);
        // Set the decoding mode
        ((ShapefileDataStore) myData).setCharset(StandardCharsets.UTF_8);
        SimpleFeatureSource source = myData.getFeatureSource();
        SimpleFeatureType schema = source.getSchema();
        Query query = new Query(schema.getTypeName());

        FeatureCollection<SimpleFeatureType, SimpleFeature> collection = source.getFeatures(query);
        FeatureJSON fjson = new FeatureJSON();
        File geojson = new File(geojsonPath);
        try (FeatureIterator<SimpleFeature> featureIterator = collection.features();
             StringWriter writer = new StringWriter();
             BufferedWriter buffer = new BufferedWriter(Files.newBufferedWriter(geojson.toPath(), StandardCharsets.UTF_8))) {
            writer.write("{\"type\":\"FeatureCollection\",\"crs\":");
            fjson.writeCRS(schema.getCoordinateReferenceSystem(), writer);
            writer.write(",");
            writer.write("\"features\":");
            writer.write("[");
            while (featureIterator.hasNext()) {
                SimpleFeature feature = featureIterator.next();
                fjson.writeFeature(feature, writer);
                if (featureIterator.hasNext())
                    writer.write(",");
            }
            writer.write("]");
            writer.write("}");
            buffer.write(writer.toString());
            return true;
        } catch (IOException e) {
            return false; }}catch (IOException e) {
        return false; }}Copy the code

Afterword.

Conversion of GeoJSON to Shapefile was a very common problem with GISer, and the software HE developed, POIKit, provided this functionality. Currently, geoJSON is converted to SHP, and SHP is converted to GeoJSON/CSV.

code

You can find my Spatial Format conversion utility class here.