org.geotools.data.mongodb.MongoResultSet.java Source code

Java tutorial

Introduction

Here is the source code for org.geotools.data.mongodb.MongoResultSet.java

Source

package org.geotools.data.mongodb;

import java.util.ArrayList;
import java.util.Set;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.bson.BSONObject;
import org.bson.types.BasicBSONList;
import org.bson.types.ObjectId;
import org.geotools.data.mongodb.MongoLayer.GeometryType;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;

/**
 * Handles conversion of GeoServer query results from mongo GeoJSON format back to GeoServer
 * compatible features
 * 
 * @author Gerald Gay, Data Tactics Corp.
 * @author Alan Mangan, Data Tactics Corp.
 * @source $URL$
 * 
 *         (C) 2011, Open Source Geospatial Foundation (OSGeo)
 * 
 * @see The GNU Lesser General Public License (LGPL)
 */
/* 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; either version
 * 2.1 of the License, or (at your option) any later version.
 * 
 * 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.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA */
public class MongoResultSet {

    private MongoLayer layer = null;
    private ArrayList<SimpleFeature> features = null;
    private ReferencedEnvelope bounds = null;
    double minX = 180;
    double maxX = -180;
    double minY = 90;
    double maxY = -90;
    /** Package logger */
    static private final Logger log = MongoPluginConfig.getLog();

    static private final PrecisionModel pm = new PrecisionModel();
    /** GeometryFactory with given precision model */
    static private final GeometryFactory geoFactory = new GeometryFactory(pm, -1);

    public MongoResultSet(MongoLayer layer, BasicDBObject query) {
        this.layer = layer;
        bounds = new ReferencedEnvelope(0, 0, 0, 0, layer.getCRS());
        features = new ArrayList<SimpleFeature>();
        if (query != null) {
            buildFeatures(query);
        }
    }

    /**
     * Build features for given layer; convert mongo collection records to equivalent geoTools
     * SimpleFeatureBuilder
     * 
     * @param query mongoDB query (empty to find all)
     */
    private void buildFeatures(BasicDBObject query) {
        if (layer == null) {
            log.warning("buildFeatures called, but layer is null");
            return;
        }
        Mongo mongo = null;
        try {
            if (layer.getGeometryType() == null) {
                return;
            }
            mongo = new Mongo(layer.getConfig().getHost(), layer.getConfig().getPort());
            DB db = mongo.getDB(layer.getConfig().getDB());
            DBCollection coll = db.getCollection(layer.getName());
            DBCursor cur = coll.find(query);
            minX = 180;
            maxX = -180;
            minY = 90;
            maxY = -90;
            SimpleFeatureBuilder fb = new SimpleFeatureBuilder(layer.getSchema());
            // use SimpleFeatureBuilder.set(name, value) rather than add(value) since
            // attributes not in guaranteed order
            log.finer("cur.count()=" + cur.count());

            while (cur.hasNext()) {
                DBObject dbo = cur.next();
                if (dbo == null) {
                    continue;
                }

                // get mongo id and ensure valid
                if (dbo.get("_id") instanceof ObjectId) {
                    ObjectId oid = (ObjectId) dbo.get("_id");
                    fb.set("_id", oid.toString());
                } else if (dbo.get("_id") instanceof String) {
                    fb.set("_id", dbo.get("_id"));
                } else {
                    throw new MongoPluginException("_id is invalid type: " + dbo.get("_id").getClass());
                }

                // ensure geometry defined
                DBObject geo = (DBObject) dbo.get("geometry");
                if (geo == null || geo.get("type") == null
                        || (geo.get("coordinates") == null && geo.get("geometries") == null)) {
                    continue;
                }

                // GeometryType of current record
                GeometryType recordGeoType = GeometryType.valueOf((String) geo.get("type"));
                // skip record if its geo type does not match layer geo type
                if (!layer.getGeometryType().equals(recordGeoType)) {
                    continue;
                }

                // create Geometry for given type
                Geometry recordGeometry = createGeometry(recordGeoType, geo);
                if (recordGeometry != null) {
                    fb.set("geometry", recordGeometry);
                    // set non-geometry properties for feature (GeoJSON.properties)
                    DBObject props = (DBObject) dbo.get("properties");
                    setProperties(fb, "properties", props);
                    features.add(fb.buildFeature(null));
                    bounds = new ReferencedEnvelope(minX, maxX, minY, maxY, layer.getCRS());
                } else {
                    fb.reset();
                }
            }
        } catch (Throwable t) {
            log.severe("Error building layer " + layer.getName() + "; " + t.getLocalizedMessage());
        }
        if (mongo != null) {
            mongo.close();
        }
    }

    /**
     * Set non-geometry properties for feature (GeoJSON.properties)
     * 
     * @param fb SimpleFeatureBuilder, properties defined in dotted notation, e.g.
     *            "properties.name", \ "properties.nested.attr" etc.
     * @param base property name (called recursively, "properties" first time through)
     * @param dbo JSON (BasicDBObject) or Array (BasicBSONList) object
     */
    @SuppressWarnings("rawtypes")
    private void setProperties(SimpleFeatureBuilder fb, String base, BSONObject dbo) {
        Set<String> cols = dbo.keySet();

        for (String col : cols) {
            Object dbcol = dbo.get(col);
            // recurse for nested JSON objects and arrays
            if (dbcol instanceof BasicDBObject || dbcol instanceof BasicBSONList) {
                setProperties(fb, base + "." + col, (BSONObject) dbcol);
            } else {
                Class featureBinding = fb.getFeatureType().getType(base + "." + col).getBinding();
                Class dboBinding = dbo.get(col).getClass();
                // set if bindings match
                if (dboBinding.equals(featureBinding)) {
                    fb.set(base + "." + col, dbo.get(col));
                }
                // if bindings mismatch, but feature binding is String then set using toString()
                // or if bindings subclass Number then cast (possibly lossy)
                else if (featureBinding.equals(String.class) || (featureBinding.getSuperclass().equals(Number.class)
                        && dboBinding.getSuperclass().equals(Number.class))) {
                    try {
                        fb.set(base + "." + col, dbo.get(col).toString());
                    }
                    // ignore nfe if unable to convert
                    catch (NumberFormatException ne) {
                    }
                }
            }
        }
    }

    public SimpleFeatureType getSchema() {
        return layer.getSchema();
    }

    /**
     * Get Feature references by index
     * @param idx
     * @return SimpleFeature, null if idx out of bounds
     */
    public SimpleFeature getFeature(int idx) throws IndexOutOfBoundsException {
        if (idx < 0 || idx >= features.size())
            throw new IndexOutOfBoundsException("Index " + idx + " exceeds features size of " + features.size());
        return features.get(idx);
    }

    public int getCount() {
        return features.size();
    }

    public ReferencedEnvelope getBounds() {
        return bounds;
    }

    /**
     * Paginate result features using startIndex and maxFeatures
     * 
     * @param startIndex starting index (>= 0)
     * @param maxFeatures max features to return (> 0)
     */
    public void paginateFeatures(int startIndex, int maxFeatures) {
        int endIndex = startIndex + maxFeatures;
        if (startIndex >= 0 && maxFeatures > 0 && endIndex < features.size()) {
            features = new ArrayList<SimpleFeature>(features.subList(startIndex, endIndex));
        }
    }

    /**
     * Create a Coordinate from given coordinates list
     * 
     * @param coords list of coords
     * @return Coordinate, may be null if coords invalid
     */
    private Coordinate createCoordinate(BasicDBList coords) {
        double x = 0.0;
        double y = 0.0;
        boolean success = true;
        Coordinate coord = null;
        try {
            x = Double.parseDouble(coords.get(0).toString());
            y = Double.parseDouble(coords.get(1).toString());
            if ((x < -180) || (x > 180))
                success = false;
            if ((y < -90) || (y > 90))
                success = false;
            if (success) {
                if (x < minX)
                    minX = x;
                if (x > maxX)
                    maxX = x;
                if (y < minY)
                    minY = y;
                if (y > maxY)
                    maxY = y;
            }
            coord = new Coordinate(x, y);
        } catch (Throwable t) {
            log.log(Level.SEVERE, t.getLocalizedMessage(), t);
            coord = null;
        }
        return coord;
    }

    /**
     * Create a Point from given coordinates
     * 
     * @param coords list of coords
     * @return Point, may be null if coords invalid
     */
    private Point createPoint(BasicDBList coords) {
        Coordinate coord = createCoordinate(coords);
        Point pt = null;
        if (coord != null) {
            pt = geoFactory.createPoint(coord);
        }
        return pt;
    }

    /**
     * Create a Polygon from given coordinates
     * 
     * @param polyCoords as mongo BasicDBList, 1st list is outer shell, any subsequent lists inner
     *            holes
     * @return Polygon, may be null if coordinates invalid
     */
    private Polygon createPolygon(BasicDBList polyCoords) {
        Vector<ArrayList<Coordinate>> rings = new Vector<ArrayList<Coordinate>>();
        boolean success = true;
        for (Object polys : polyCoords) {
            BasicDBList inner = (BasicDBList) polys;
            ArrayList<Coordinate> ring = new ArrayList<Coordinate>();
            for (Object obj : inner) {
                BasicDBList aPoint = (BasicDBList) obj;
                Coordinate coord = createCoordinate(aPoint);
                ring.add(coord);
            }
            rings.add(ring);
        } // end outer loop

        // have vector of rings; 1st is outer ring/shell, rest are innner rings/holes
        Polygon poly = null;
        if (success && rings.size() > 0) {
            Coordinate[] shellCoords = new Coordinate[rings.get(0).size()];
            shellCoords = rings.get(0).toArray(shellCoords);
            LinearRing shell = geoFactory.createLinearRing(shellCoords);
            LinearRing[] holes = null;
            // construct holes if any present
            if (rings.size() > 1) {
                holes = new LinearRing[rings.size() - 1];
                for (int i = 1; i < rings.size(); i++) {
                    Coordinate[] holeCoords = new Coordinate[rings.get(i).size()];
                    holeCoords = rings.get(i).toArray(holeCoords);
                    holes[i - 1] = geoFactory.createLinearRing(holeCoords);
                }
            }
            poly = geoFactory.createPolygon(shell, holes);
        }
        return poly;
    }

    /**
     * Create a LineString from given coordinates list
     * 
     * @param coords list of coords
     * @return LineString, may be null if coords invalid
     */
    private LineString createLineString(BasicDBList outer) {
        Coordinate[] coords = new Coordinate[outer.size()];
        int i = 0;
        for (Object lineCoords : outer) {
            coords[i++] = createCoordinate((BasicDBList) lineCoords);
        }
        LineString lineString = geoFactory.createLineString(coords);
        return lineString;
    }

    /**
     * Create a Geometry object; GeometryCollection, Point, MultiPoint, Polygon etc.
     * 
     * @param type Geometry type to create
     * @param geoElement coordinates
     * @return Geometry, may be null if coordinates null/invalid, or type invalid
     */
    private Geometry createGeometry(GeometryType type, DBObject coordinates) {
        Geometry geometryObj = null;

        // GeometryCollection different; has geometries field rather than coordinates
        if (type.equals(GeometryType.GeometryCollection)) {
            if (!coordinates.containsField("geometries")) {
                log.warning("No geometries detected for GeometryCollection, skipping.");
                return geometryObj;
            }
            BasicDBList geometryList = (BasicDBList) coordinates.get("geometries");
            int i = 0;
            Geometry[] geometries = new Geometry[geometryList.size()];
            for (Object geoElement : geometryList) {
                String subType = (String) ((BasicDBList) geoElement).get("type");
                GeometryType geoType = GeometryType.valueOf(subType);
                geometries[i++] = createGeometry(geoType, (DBObject) geoElement);
            }
            geometryObj = geoFactory.createGeometryCollection(geometries);
        }

        // all other geometry types; Point, Polygon etc.
        else {
            if (!coordinates.containsField("coordinates")) {
                return geoFactory.createPoint((Coordinate) null);
            }

            BasicDBList coords = (BasicDBList) coordinates.get("coordinates");
            int i = 0;

            switch (type) {
            case LineString:
                geometryObj = createLineString(coords);
                break;

            case Point:
                geometryObj = createPoint(coords);
                break;

            case Polygon:
                geometryObj = createPolygon(coords);
                break;

            case MultiLineString:
                LineString[] lines = new LineString[coords.size()];
                for (Object lineCoords : coords) {
                    lines[i++] = createLineString((BasicDBList) lineCoords);
                }
                geometryObj = geoFactory.createMultiLineString(lines);
                break;

            case MultiPoint:
                Point[] points = new Point[coords.size()];
                for (Object obj : coords) {
                    BasicDBList aPoint = (BasicDBList) obj;
                    points[i++] = createPoint(aPoint);
                }
                geometryObj = geoFactory.createMultiPoint(points);
                break;

            case MultiPolygon:
                Polygon[] polys = new Polygon[coords.size()];
                for (Object polyCoords : coords) {
                    polys[i++] = createPolygon((BasicDBList) polyCoords);
                }
                geometryObj = geoFactory.createMultiPolygon(polys);
                break;
            }
        }

        return geometryObj;
    }

}