Package org.geoscript.js.feature

Source Code of org.geoscript.js.feature.Feature

package org.geoscript.js.feature;

import org.geoscript.js.GeoObject;
import org.geoscript.js.geom.Bounds;
import org.geoscript.js.geom.Geometry;
import org.geoscript.js.io.JSON;
import org.geoscript.js.proj.Projection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.ScriptRuntime;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.mozilla.javascript.Wrapper;
import org.mozilla.javascript.annotations.JSConstructor;
import org.mozilla.javascript.annotations.JSFunction;
import org.mozilla.javascript.annotations.JSGetter;
import org.mozilla.javascript.annotations.JSSetter;
import org.mozilla.javascript.annotations.JSStaticFunction;
import org.opengis.feature.GeometryAttribute;
import org.opengis.feature.Property;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

public class Feature extends GeoObject implements Wrapper {

    /** serialVersionUID */
    private static final long serialVersionUID = 4426338466323185386L;
   
    /**
     * The GeoTools feature backing this feature.
     */
    private SimpleFeature feature;

    /**
     * Layer from which this feature was accessed.  Used to persist
     * modifications.
     */
    Scriptable layer;

    /**
     * Prototype constructor.
     */
    public Feature() {
    }
   
    /**
     * Constructor with SimpleFeature (from Java).
     * @param scope
     * @param feature
     */
    public Feature(Scriptable scope, SimpleFeature feature) {
        this.feature = feature;
        setParentScope(scope);
        this.setPrototype(Module.getClassPrototype(Feature.class));
    }

    /**
     * Constructor from config object.
     * @param config
     */
    private Feature(NativeObject config) {
        Scriptable scope = ScriptableObject.getTopLevelScope(config);
        config = prepConfig(config);
        Schema schema = null;
        if (config.has("schema", config)) {
            Object schemaObj = config.get("schema", config);
            if (schemaObj instanceof Schema) {
                schema = (Schema) schemaObj;
            } else if (schemaObj instanceof Scriptable) {
                schema = new Schema(scope, (Scriptable) schemaObj);
            } else {
                throw ScriptRuntime.constructError("Error", "Cannot create schema from provided value: " + Context.toString(schemaObj));
            }
        }

        Object propertiesObj = getOptionalMember(config, "properties", NativeObject.class, "Object");
        NativeObject properties = null;
        if (propertiesObj != null) {
            properties = (NativeObject) propertiesObj;
        }

        if (schema == null) {
            if (properties != null) {
                schema = Schema.fromValues(scope, properties);
            } else {
                throw ScriptRuntime.constructError("Error", "Feature config must include schema or properties.");
            }
        }

        SimpleFeatureBuilder builder = new SimpleFeatureBuilder((SimpleFeatureType) schema.unwrap());

        if (properties != null) {
            Object[] names = properties.getIds();
            for (Object nameObj : names) {
                String name = (String) nameObj;
                if (schema.get(name) == null) {
                    throw ScriptRuntime.constructError("Error", "Feature schema has no field with the given name: " + name);
                }
                Object value = properties.get(name, properties);
                builder.set(name, jsToJava(value));
            }
        }
       
        String id = null;
        if (config.has("id", config)) {
            id = Context.toString(config.get("id", config));
        }

        feature = builder.buildFeature(id);
    }
   
    /**
     * Translate a GeoJSON config into a Feature config.
     * @param config
     * @return
     */
    private NativeObject prepConfig(NativeObject config) {
        Scriptable scope = config.getParentScope();
        Object propertiesObj = getOptionalMember(config, "properties", NativeObject.class, "Object");
        NativeObject properties = null;
        if (propertiesObj != null) {
            properties = (NativeObject) propertiesObj;
        }

        Geometry geometry = null;
        if (config.has("geometry", config)) {
            // GeoJSON config
            Object geometryObj = config.get("geometry", config);
            if (!(geometryObj instanceof NativeObject)) {
                throw ScriptRuntime.constructError("Error",
                        "Expected geometry member to be an object, got: " + Context.toString(geometryObj));
            }
            geometryObj = JSON.readObj((NativeObject) geometryObj);
            if (!(geometryObj instanceof Geometry)) {
                throw ScriptRuntime.constructError("Error", "Expected geometry memeber to be a valid geometry, got: " + Context.toString(geometryObj));
            }
            geometry = (Geometry) geometryObj;
            config.delete("geometry");
            // allow passing a geometry only with no other properties
            if (properties == null) {
                Context cx = Context.enter();
                try {
                    properties = (NativeObject) cx.newObject(scope);
                } finally {
                    Context.exit();
                }
                config.put("properties", config, properties);
            }
        }
        if (geometry != null && properties != null) {
            properties.put("geometry", properties, geometry);
        }
        return config;
    }

    /**
     * Constructor from config object (without new keyword).
     * @param scope
     * @param config
     */
    public Feature(Scriptable scope, NativeObject config) {
        this(config);
        setParentScope(scope);
        this.setPrototype(Module.getClassPrototype(Feature.class));
    }

    @JSConstructor
    public static Object constructor(Context cx, Object[] args, Function ctorObj, boolean inNewExpr) {
        if (args.length != 1) {
            throw ScriptRuntime.constructError("Error", "Constructor takes a single argument");
        }
        Feature feature = null;
        Object arg = args[0];
        if (arg instanceof NativeObject) {
            NativeObject config = (NativeObject) arg;
            if (inNewExpr) {
                feature = new Feature(config);
            } else {
                feature = new Feature(config.getParentScope(), config);
            }
        } else {
            throw ScriptRuntime.constructError("Error", "Could not create feature from argument: " + Context.toString(arg));
        }
        return feature;
    }

    public Object unwrap() {
        return feature;
    }
   
    @JSGetter
    public String getId() {
        return feature.getIdentifier().toString();
    }
   
    @JSGetter
    public String getGeometryName() {
        GeometryAttribute geomProp = feature.getDefaultGeometryProperty();
        String name = null;
        if (geomProp != null) {
            name = geomProp.getName().toString();
        }
        return name;
    }
   
    @JSGetter
    public Geometry getGeometry() {
        String name = getGeometryName();
        return (Geometry) get(name);
    }
   
    @JSSetter
    public void setGeometry(Geometry geometry) {
        String name = getGeometryName();
        if (name == null) {
            throw ScriptRuntime.constructError("Error", "Feature schema has no geometry field");
        }
        set(name, geometry);
    }
   
    @JSGetter
    public Projection getProjection() {
        Projection projection = null;
        SimpleFeatureType featureType = feature.getFeatureType();
        GeometryDescriptor descriptor = featureType.getGeometryDescriptor();
        if (descriptor != null) {
            CoordinateReferenceSystem crs = descriptor.getCoordinateReferenceSystem();
            if (crs != null) {
                projection = new Projection(getParentScope(), crs);
            }
        }
        return projection;
    }
   
    @JSGetter
    public Bounds getBounds() {
        Bounds bounds = null;
        Geometry geometry = getGeometry();
        if (geometry != null) {
            bounds = geometry.getBounds();
        }
        return bounds;
    }

    @JSGetter
    public Schema getSchema() {
        return new Schema(getParentScope(), feature.getFeatureType());
    }

    @JSFunction
    public Feature set(String name, Object value) {
        SimpleFeatureType featureType = feature.getFeatureType();
        AttributeDescriptor descriptor = featureType.getDescriptor(name);
        if (descriptor == null) {
            throw ScriptRuntime.constructError("Error", "Feature schema has no such field: " + name);
        }
        String geomName = getGeometryName();
        if (geomName != null && geomName.equals(name)) {
            if (!(value instanceof Geometry)) {
                throw ScriptRuntime.constructError("Error", "Attempted to set geometry property to a non-geometry object: " + Context.toString(value));
            }
            Projection featureProj = getProjection();
            Geometry geometry = (Geometry) value;
            Projection geomProj = geometry.getProjection();
            if (featureProj != null) {
                if (geomProj != null) {
                    if (!featureProj.equals(geometry.getProjection())) {
                        value = geometry.transform(featureProj);
                    }
                }
            } else if (geomProj != null) {
                throw ScriptRuntime.constructError("Error", "Cannot add a geometry with a projection to a feature without a projection");
            }
        }
        feature.setAttribute(name, jsToJava(value));
        if (layer != null) {
            // queue modifications
            Object queueModifiedObj = ScriptableObject.getProperty(layer, "queueModified");
            if (!(queueModifiedObj instanceof Function)) {
                throw new RuntimeException("Unable to access queueModified method of layer.");
            }
            Function queueModified = (Function) queueModifiedObj;
            Context cx = getCurrentContext();
            Scriptable scope = getParentScope();
            Object[] args = { this, name };
            queueModified.call(cx, scope, layer, args);
        }
        return this;
    }

    @JSFunction
    public Object get(String name) {
        Object value;
        if (feature.getFeatureType().getDescriptor(name) == null) {
            value = Context.getUndefinedValue();
        } else {
            value = javaToJS(feature.getAttribute(name), getParentScope());
            if (value instanceof Geometry) {
                ((Geometry) value).setProjection(getProjection());
            }
        }
        return value;
    }

    /**
     * Set the layer from which this feature was accessed.
     * TODO: Remove this from the API when Layer is implemented as a wrapper.
     * @param layer
     * @return
     */
    @JSSetter
    public void setLayer(Scriptable layer) {
        if (this.layer != null) {
            // TODO: clear queued modifications?
        }
        this.layer = layer;
    }
   
    @JSGetter
    public Scriptable getLayer() {
        return layer;
    }

   
    @JSGetter
    public NativeObject getProperties() {
        Context cx = getCurrentContext();
        Scriptable scope = getParentScope();
        NativeObject properties = (NativeObject) cx.newObject(scope);
        for (Property property : feature.getProperties()) {
            String name = property.getName().toString();
            properties.put(name, properties, get(name));
        }
        return properties;
    }
   
    @JSSetter
    public void setProperties(Scriptable properties) {
        Object[] names = properties.getIds();
        for (Object nameObj : names) {
            String name = (String) nameObj;
            set(name, properties.get(name, properties));
        }
    }
   
    @JSGetter
    public Scriptable getConfig() {
        Scriptable config = super.getConfig();
       
        // add schema
        Schema schema = getSchema();
        Scriptable schemaConfig = schema.getConfig();
        config.put("schema", config, schemaConfig);

        // add default geometry member
        Geometry geometry = getGeometry();
        Scriptable geomConfig = null;
        if (geometry != null) {
            geomConfig = geometry.getConfig();
        }
        config.put("geometry", config, geomConfig);

        // add all other properties
        Context cx = getCurrentContext();
        Scriptable scope = getParentScope();
        Scriptable properties = cx.newObject(scope);
        Scriptable values = getProperties();
        String geometryName = getGeometryName();
        Object[] names = values.getIds();
        for (Object nameObj : names) {
            String name = (String) nameObj;
            if (!name.equals(geometryName)) {
                properties.put(name, properties, values.get(name, values));
            }
        }
        config.put("properties", config, properties);
        return config;
    }

    @JSStaticFunction
    public static Feature from_(Scriptable featureObj) {
        SimpleFeature feature = null;
        if (featureObj instanceof Wrapper) {
            Object obj = ((Wrapper) featureObj).unwrap();
            if (obj instanceof SimpleFeature) {
                feature = (SimpleFeature) obj;
            }
        }
        if (feature == null) {
            throw ScriptRuntime.constructError("Error", "Cannot create feature from " + Context.toString(featureObj));
        }
        return new Feature(getTopLevelScope(featureObj), feature);
    }

    public String toFullString() {
        NativeObject properties = getProperties();
        Object[] names = properties.getIds();
        String repr = "";
        int length = names.length;
        for (int i=0; i<length; ++i) {
            String name = (String) names[i];
            Object value = get(name);
            if (value instanceof Geometry) {
                value = "<" + value.getClass().getSimpleName() + ">";
            } else if (value instanceof String) {
                value = '"' + (String) value + '"';
            } else {
                value = Context.toString(value);
            }
            repr += name + ": " + value;
            if (i < length - 1) {
                repr += ", ";
            }
        }
        return repr;
    }


}
TOP

Related Classes of org.geoscript.js.feature.Feature

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.