Java tutorial
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; } }