Package org.geotools.geojson.feature

Source Code of org.geotools.geojson.feature.FeatureJSON

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2002-2010, 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.geojson.feature;

import static org.geotools.geojson.GeoJSONUtil.array;
import static org.geotools.geojson.GeoJSONUtil.entry;
import static org.geotools.geojson.GeoJSONUtil.string;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

import org.geotools.data.crs.ForceCoordinateSystemFeatureResults;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.SchemaException;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geojson.GeoJSONUtil;
import org.geotools.geojson.geom.GeometryJSON;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.json.simple.JSONArray;
import org.json.simple.JSONAware;
import org.json.simple.JSONStreamAware;
import org.json.simple.parser.JSONParser;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

/**
* Reads and writes feature objects to and from geojson.
* <p>
* <pre>
* SimpleFeature feature = ...;
*
* FeatureJSON io = new FeatureJSON();
* io.writeFeature(feature, "feature.json"));
*
* Iterator<Feature> features = io.streamFeatureCollection("features.json");
* while(features.hasNext()) {
*   feature = features.next();
*   ...
* }
* </pre>
* </p>
* @author Justin Deoliveira, OpenGeo
*
*
*
*
* @source $URL$
*/
public class FeatureJSON {

    GeometryJSON gjson;
    SimpleFeatureType featureType;
    AttributeIO attio;
    boolean encodeFeatureBounds = false;
    boolean encodeFeatureCollectionBounds = false;
    boolean encodeFeatureCRS = false;
    boolean encodeFeatureCollectionCRS = false;
    boolean encodeNullValues = false;
   
    public FeatureJSON() {
        this(new GeometryJSON());
    }

    public FeatureJSON(GeometryJSON gjson) {
        this.gjson = gjson;
        attio = new DefaultAttributeIO();
    }

    /**
     * Sets the target feature type for parsing.
     * <p>
     * Setting the target feature type will help the geojson parser determine the type of feature
     * properties during properties. When the type is not around all properties are returned as
     * a string.
     * </p>
    
     * @param featureType The feature type. Parsed features will reference this feature type.
     */
    public void setFeatureType(SimpleFeatureType featureType) {
        this.featureType = featureType;
        this.attio = new FeatureTypeAttributeIO(featureType);
    }

    /**
     * Sets the flag controlling whether feature bounds are encoded.
     * 
     * @see #isEncodeFeatureBounds()
     */
    public void setEncodeFeatureBounds(boolean encodeFeatureBounds) {
        this.encodeFeatureBounds = encodeFeatureBounds;
    }
   
    /**
     * The flag controlling whether feature bounds are encoded.
     * <p>
     * When set each feature object will contain a "bbox" attribute whose value is an array 
     * containing the elements of the bounding box (in x1,y1,x2,y2 order) of the feature
     * </p>
     */
    public boolean isEncodeFeatureBounds() {
        return encodeFeatureBounds;
    }
   
    /**
     * Sets the flag controlling whether feature collection bounds are encoded.
     * 
     * @see #isEncodeFeatureCollectionBounds()
     */
    public void setEncodeFeatureCollectionBounds(boolean encodeFeatureCollectionBounds) {
        this.encodeFeatureCollectionBounds = encodeFeatureCollectionBounds;
    }
   
    /**
     * The flag controlling whether feature collection bounds are encoded.
     * <p>
     * When set the feature collection object will contain a "bbox" attribute whose value is an
     * array containing elements of the bounding box (in x1,y1,x2,y2 order) of the feature
     * collection.
     * </p>
     */
    public boolean isEncodeFeatureCollectionBounds() {
        return encodeFeatureCollectionBounds;
    }
   
    /**
     * Sets the flag controlling whether feature coordinate reference systems are encoded.
     * 
     * @see #isEncodeFeatureCRS()
     */
    public void setEncodeFeatureCRS(boolean encodeFeatureCRS) {
        this.encodeFeatureCRS = encodeFeatureCRS;
    }
   
    /**
     * The flag controlling whether feature coordinate reference systems are encoded.
     * <p>
     * When set each feature object will contain a "crs" attribute describing the
     * coordinate reference system of the feature.
     * </p>
     *
     */
    public boolean isEncodeFeatureCRS() {
        return encodeFeatureCRS;
    }
   
    /**
     * Sets the flag controlling whether feature collection coordinate reference systems are encoded.
     * 
     * @see #isEncodeFeatureCollectionCRS()
     */
    public void setEncodeFeatureCollectionCRS(boolean encodeFeatureCollectionCRS) {
        this.encodeFeatureCollectionCRS = encodeFeatureCollectionCRS;
    }
   
    /**
     * The flag controlling whether feature collection coordinate reference systems are encoded.
     * <p>
     * When set the feature collection object will contain a "crs" attribute describing the
     * coordinate reference system of the feature collection.
     * </p>
     */
    public boolean isEncodeFeatureCollectionCRS() {
        return encodeFeatureCollectionCRS;
    }
   
    /**
     * Sets the flag controlling whether properties with null values are encoded.
     *
     * @see #isEncodeNullValues()
     */
    public void setEncodeNullValues(boolean encodeNullValues) {
        this.encodeNullValues = encodeNullValues;
    }

    /**
     * The flag controlling whether properties with null values are encoded.
     * <p>
     * When set, properties with null values are encoded as null in the GeoJSON document.
     * </p>
     */
    public boolean isEncodeNullValues() {
        return encodeNullValues;
    }

    /**
     * Writes a feature as GeoJSON.
     *
     * @param feature The feature.
     * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details.
     */
    public void writeFeature(SimpleFeature feature, Object output) throws IOException {
        GeoJSONUtil.encode(new FeatureEncoder(feature).toJSONString(), output);
    }

    /**
     * Writes a feature as GeoJSON.
     * <p>
     * This method calls through to {@link #writeFeature(FeatureCollection, Object)}
     * </p>
     * @param feature The feature.
     * @param output The output stream.
     */
    public void writeFeature(SimpleFeature feature, OutputStream output) throws IOException {
        writeFeature(feature, (Object)output);
    }

    /**
     * Writes a feature as GeoJSON returning the result as a string.
     *
     * @param geometry The geometry.
     *
     * @return The geometry encoded as GeoJSON
     */
    public String toString(SimpleFeature feature) throws IOException {
        StringWriter w = new StringWriter();
        writeFeature(feature, w);
        return w.toString();
    }
   
    /**
     * Reads a feature from GeoJSON.
     *
     * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     * @return The feature.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public SimpleFeature readFeature(Object input) throws IOException {
        return GeoJSONUtil.parse(new FeatureHandler(
            featureType != null ? new SimpleFeatureBuilder(featureType): null, attio
        ), input, false);
    }

    /**
     * Reads a feature from GeoJSON.
     * <p>
     * This method calls through to {@link #readFeature(Object)}
     * </p>
     * @param input The input stream.
     * @return The feature.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public SimpleFeature readFeature(InputStream input) throws IOException {
        return readFeature((Object)input);
    }

    /**
     * Writes a feature collection as GeoJSON.
     *
     * @param features The feature collection.
     * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details.
     */
    public void writeFeatureCollection(FeatureCollection features, Object output) throws IOException {
        LinkedHashMap obj = new LinkedHashMap();
        obj.put("type", "FeatureCollection");

        final ReferencedEnvelope bounds = features.getBounds();
        final CoordinateReferenceSystem crs = bounds != null ? bounds.getCoordinateReferenceSystem() : null;

        if (bounds != null) {
            if (encodeFeatureCollectionBounds) {
                obj.put("bbox", new JSONStreamAware() {
                    public void writeJSONString(Writer out) throws IOException {
                        JSONArray.writeJSONString(Arrays.asList(bounds.getMinX(),
                                bounds.getMinY(), bounds.getMaxX(), bounds.getMaxY()), out);
                    }
                });
            }
        }
       
        if( crs != null ){
            if (encodeFeatureCollectionCRS || !isStandardCRS( crs)) {
                obj.put("crs", createCRS(crs));
            }
        }

        obj.put("features", new FeatureCollectionEncoder(features, gjson));
        GeoJSONUtil.encode(obj, output);
    }

    /**
     * Check for GeoJSON default (EPSG:4326 in easting/northing order).
     *
     * @return true if crs is the default for GeoJSON
     * @throws NoSuchAuthorityCodeException
     * @throws FactoryException
     */
    private boolean isStandardCRS(CoordinateReferenceSystem crs) {
        if( crs == null ){
            return true;
        }
        try {
            CoordinateReferenceSystem standardCRS = CRS.decode("EPSG:4326"); // Consider CRS:84 due to axis order
            return CRS.equalsIgnoreMetadata(crs, standardCRS);
        } catch (Exception unexpected) {
            return false; // no way to tell
        }
    }

    /**
     * Writes a feature collection as GeoJSON.
     * <p>
     * This method calls through to {@link #writeFeatureCollection(FeatureCollection, Object)}
     * </p>
     * @param features The feature collection.
     * @param output The output strema to write to.
     */
    public void writeFeatureCollection(FeatureCollection features, OutputStream output) throws IOException {
        writeFeatureCollection(features, (Object)output);
    }

    /**
     * Reads a feature collection from GeoJSON.
     * <p>
     * Warning that this method will load the entire feature collection into memory. For large
     * feature collections {@link #streamFeatureCollection(Object)} should be used.
     * </p>
     *
     * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     * @return The feature collection.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public FeatureCollection readFeatureCollection(Object input) throws IOException {
        DefaultFeatureCollection features = new DefaultFeatureCollection(null, null);
        FeatureCollectionIterator it = (FeatureCollectionIterator) streamFeatureCollection(input);
        while(it.hasNext()) {
            features.add(it.next());
        }

        //check for the case of a crs specified post features in the json
        if (features.getSchema() != null
                && features.getSchema().getCoordinateReferenceSystem() == null
                && it.getHandler().getCRS() != null ) {
            try {
                return new ForceCoordinateSystemFeatureResults(features, it.getHandler().getCRS());
            } catch (SchemaException e) {
                throw (IOException) new IOException().initCause(e);
            }
        }
        return features;
    }

    /**
     * Reads a feature collection from GeoJSON.
     * <p>
     * Warning that this method will load the entire feature collection into memory. For large
     * feature collections {@link #streamFeatureCollection(Object)} should be used.
     * </p>
     * <p>
     * This method calls through to {@link #readFeatureCollection(Object)}.
     * </p>
     *
     * @param input The input stream.
     * @return The feature collection.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public FeatureCollection readFeatureCollection(InputStream input) throws IOException {
        return readFeatureCollection((Object)input);
    }

    /**
     * Reads a feature collection from GeoJSON streaming back the contents via an iterator.
     *
     * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     *
     * @return A feature iterator.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public FeatureIterator<SimpleFeature> streamFeatureCollection(Object input) throws IOException {
        return new FeatureCollectionIterator(input);
    }

    /**
     * Writes a feature collection as GeoJSON returning the result as a string.
     *
     * @param features The feature collection.
     *
     * @return The feature collection encoded as GeoJSON
     */
    public String toString(FeatureCollection features) throws IOException {
        StringWriter w = new StringWriter();
        writeFeatureCollection(features, w);
        return w.toString();
    }
   
    /**
     * Writes a coordinate reference system as GeoJSON.
     *
     * @param crs The coordinate reference system.
     * @param output The output. See {@link GeoJSONUtil#toWriter(Object)} for details.
     */
    public void writeCRS(CoordinateReferenceSystem crs, Object output) throws IOException {
        GeoJSONUtil.encode(createCRS(crs), output);
    }

    /**
     * Writes a coordinate reference system as GeoJSON.
     * <p>
     * This method calls through to {@link #writeCRS(CoordinateReferenceSystem, Object)}
     * </p>
     * @param crs The coordinate reference system.
     * @param output The output stream.
     */
    public void writeCRS(CoordinateReferenceSystem crs, OutputStream output) throws IOException {
        writeCRS(crs, (Object) output);
    }
   
    /**
     * Create a properties map for the provided crs.
     *
     * @param crs CoordinateReferenceSystem or null for default
     * @return properties map naming crs identifier
     * @throws IOException
     */
    Map<String,Object> createCRS(CoordinateReferenceSystem crs) throws IOException {
        Map<String,Object> obj = new LinkedHashMap<String,Object>();
        obj.put("type", "name");
       
        Map<String,Object> props = new LinkedHashMap<String, Object>();
        if( crs == null ){
            props.put("name", "EPSG:4326");
        }
        else {
            try {
                String identifier = CRS.lookupIdentifier(crs, true);
                props.put("name", identifier);
            }
            catch (FactoryException e) {
                throw (IOException) new IOException("Error looking up crs identifier").initCause(e);
            }
        }
        obj.put("properties", props);
        return obj;
    }
   
    /**
     * Reads a coordinate reference system from GeoJSON.
     * <p>
     * This method only handles named coordinate reference system objects.
     * </p>
     *
     * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     * @return The coordinate reference system.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public CoordinateReferenceSystem readCRS(Object input) throws IOException {
        return GeoJSONUtil.parse(new CRSHandler(), input, false);
    }

    /**
     * Reads a coordinate reference system from GeoJSON.
     * <p>
     * This method only handles named coordinate reference system objects.
     * </p>
     * <p>
     * This method calls through to {@link #readCRS(Object)}
     * </p>
     *
     * @param input The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     * @return The coordinate reference system.
     *
     * @throws IOException In the event of a parsing error or if the input json is invalid.
     */
    public CoordinateReferenceSystem readCRS(InputStream input) throws IOException {
        return readCRS((Object)input);
    }
   

    /**
     * Reads the {@link SimpleFeatureType} of a GeoJSON feature collection. In the
     * worst case, it will parse all features searching for attributes not present
     * in previous features.
     *
     * @param input
     *          The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     * @param nullValuesEncoded
     *          if the input has null values encoded. If this flag is set to true
     *          and the GeoJSON doesn't actually encode null values, only the
     *          first feature attributes will be discovered.
     * @return The feature collection schema
     *
     * @throws IOException
     *           In the event of a parsing error or if the input json is invalid.
     */
    public SimpleFeatureType readFeatureCollectionSchema(Object input, boolean nullValuesEncoded) throws IOException {
        return GeoJSONUtil.parse(new FeatureTypeHandler(nullValuesEncoded), input, false);
    }


    /**
     * Reads the {@link SimpleFeatureType} of a GeoJSON feature collection. In the
     * worst case, it will parse all features searching for attributes not present
     * in previous features.
     *
     * @param input
     *          The input. See {@link GeoJSONUtil#toReader(Object)} for details.
     * @param nullValuesEncoded
     *          if the input has null values encoded. If this flag is set to true
     *          and the GeoJSON doesn't actually encode null values, only the
     *          first feature attributes will be discovered.
     * @return The feature collection schema
     *
     * @throws IOException
     *           In the event of a parsing error or if the input json is invalid.
     */
    public SimpleFeatureType readFeatureCollectionSchema(InputStream input, boolean nullValuesEncoded) throws IOException {
        return readFeatureCollectionSchema((Object)input, false);
    }

    /**
     * Writes a coordinate reference system as GeoJSON returning the result as a string.
     *
     * @param crs The coordinate reference system.
     *
     * @return The coordinate reference system encoded as GeoJSON
     */
    public String toString(CoordinateReferenceSystem crs) throws IOException {
        StringWriter writer = new StringWriter();
        writeCRS(crs, writer);
        return writer.toString();
   }

    class FeatureEncoder implements JSONAware {

        SimpleFeatureType featureType;
        SimpleFeature feature;
       
        public FeatureEncoder(SimpleFeature feature) {
            this(feature.getType());
            this.feature = feature;
        }
       
        public FeatureEncoder(SimpleFeatureType featureType) {
            this.featureType = featureType;
        }
       
        public String toJSONString(SimpleFeature feature) {
            StringBuilder sb = new StringBuilder();
            sb.append("{");
           
            //type
            entry("type", "Feature", sb);
            sb.append(",");
           
            //crs
            if (encodeFeatureCRS) {
                CoordinateReferenceSystem crs =
                    feature.getFeatureType().getCoordinateReferenceSystem();
                if (crs != null) {
                    try {
                        string("crs", sb).append(":");
                        sb.append(FeatureJSON.this.toString(crs)).append(",");
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            //bounding box
            if (encodeFeatureBounds) {
                BoundingBox bbox = feature.getBounds();
                string("bbox", sb).append(":");
                sb.append(gjson.toString(bbox)).append(",");
            }
           
            //geometry
            if (feature.getDefaultGeometry() != null) {
                string("geometry", sb).append(":")
                .append(gjson.toString((Geometry) feature.getDefaultGeometry()));   
                sb.append(",");
            }
           
            //properties
            int gindex = featureType.getGeometryDescriptor() != null ?
                    featureType.indexOf(featureType.getGeometryDescriptor().getLocalName()) :
                    -1;
           
            string("properties", sb).append(":").append("{");
            boolean attributesWritten = false;
            for (int i = 0; i < featureType.getAttributeCount(); i++) {
                AttributeDescriptor ad = featureType.getDescriptor(i);
               
                // skip the default geometry, it's already encoded
                if (i == gindex) {
                    continue;
                }
               
                Object value = feature.getAttribute(i);

                if (!encodeNullValues && value == null) {
                    //skip
                    continue;
                }
               
                attributesWritten = true;
               
                // handle special types separately, everything else as a string or literal
                if (value instanceof Envelope) {
                    array(ad.getLocalName(), gjson.toString((Envelope)value), sb);
                } else if (value instanceof BoundingBox) {
                    array(ad.getLocalName(), gjson.toString((BoundingBox)value), sb);
                } else if (value instanceof Geometry) {
                    string(ad.getLocalName(), sb).append(":")
                    .append(gjson.toString((Geometry) value));
                } else {
                    entry(ad.getLocalName(), value, sb);
                }
                sb.append(",");
            }
           
            if(attributesWritten) {
                sb.setLength(sb.length()-1);
            }
            sb.append("},");
           
            //id
            entry("id", feature.getID(), sb);
           
            sb.append("}");
            return sb.toString();
        }
       
        public String toJSONString() {
            return toJSONString(feature);
        }
    }
   
    class FeatureCollectionEncoder implements JSONStreamAware {

        FeatureCollection features;
        GeometryJSON gjson;
       
        public FeatureCollectionEncoder(FeatureCollection features, GeometryJSON gjson) {
            this.features = features;
            this.gjson = gjson;
        }
       
        public void writeJSONString(Writer out) throws IOException {
            FeatureEncoder featureEncoder =
                new FeatureEncoder((SimpleFeatureType) features.getSchema());
           
            out.write("[");
            FeatureIterator i = features.features();
            try {
                if (i.hasNext()) {
                    SimpleFeature f = (SimpleFeature) i.next();
                    out.write(featureEncoder.toJSONString(f));
                   
                    while(i.hasNext()) {
                        out.write(",");
                        f = (SimpleFeature) i.next();
                        out.write(featureEncoder.toJSONString(f));
                    }
                }
            }
            finally {
                if( i != null ){
                    i.close();
                }
            }
            out.write("]");
            out.flush();
        }
    }

    class FeatureCollectionIterator implements FeatureIterator<SimpleFeature> {

        Reader reader;
        FeatureCollectionHandler handler;
        JSONParser parser;
        SimpleFeature next;
       
        FeatureCollectionIterator(Object input) {
            try {
                this.reader = GeoJSONUtil.toReader(input);
            }
            catch (IOException e) {
                throw new RuntimeException(e);
            }
            this.parser = new JSONParser();
        }
       
        FeatureCollectionHandler getHandler() {
            return handler;
        }
       
        public boolean hasNext() {
            if (next != null) {
                return true;
            }
           
            if (handler == null) {
                handler = new FeatureCollectionHandler(featureType,  attio);
                //handler = GeoJSONUtil.trace(handler, IFeatureCollectionHandler.class);
            }
            next = readNext();
            return next != null;
        }

        public SimpleFeature next() {
            SimpleFeature feature = next;
            next = null;
            return feature;
        }
       
        SimpleFeature readNext() {
            try {
                parser.parse(reader, handler, true);
                return handler.getValue();
            }
            catch(Exception e) {
                throw new RuntimeException(e);
            }
           
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public void close() {
            reader = null;
            parser = null;
            handler = null;
        }
    }
}
TOP

Related Classes of org.geotools.geojson.feature.FeatureJSON

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.