/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, 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.data.couchdb;
import com.vividsolutions.jts.geom.Envelope;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.data.couchdb.client.CouchDBUtils;
import org.geotools.data.couchdb.client.CouchDBSpatialView;
import org.geotools.data.couchdb.client.CouchDBException;
import org.geotools.data.couchdb.client.CouchDBView;
import java.io.IOException;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Query;
import org.geotools.data.store.ContentEntry;
import org.geotools.data.store.ContentFeatureStore;
import org.geotools.feature.NameImpl;
import org.geotools.filter.visitor.ExtractBoundsFilterVisitor;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.Name;
import org.opengis.geometry.BoundingBox;
/**
*
* @author Ian Schneider (OpenGeo)
*/
public class CouchDBFeatureStore extends ContentFeatureStore {
private final String viewName;
public CouchDBFeatureStore(ContentEntry entry, Query query) {
super(entry, query);
// local name is <dbname>.<view>
// extract view part
String localName = entry.getName().getLocalPart();
int idx = localName.indexOf('.');
viewName = localName.substring(idx + 1);
}
@Override
protected ReferencedEnvelope getBoundsInternal(Query query) throws IOException {
// there appears to be no way to obtain the bbox from couch documents
// (aka features) without getting all the geometry as well.
// one approach might be to write a view that only returns bbox?
CouchDBSpatialView spatialView = getDataStore().getConnection().spatialView(viewName);
Envelope e = getBBox(query);
JSONObject results;
try {
results = spatialView.get(e.getMinX(),e.getMinY(),e.getMaxX(),e.getMaxY());
} catch (CouchDBException ex) {
throw ex.wrap();
}
Envelope env = null;
JSONArray rows = (JSONArray) results.get("rows");
for (int i = 0; i < rows.size(); i++) {
JSONArray bbox = (JSONArray) ((JSONObject) rows.get(i)).get("bbox");
if (env == null) {
env = new Envelope((Double)bbox.get(0),(Double)bbox.get(1),(Double)bbox.get(2),(Double)bbox.get(3));
} else {
env.expandToInclude((Double)bbox.get(0),(Double)bbox.get(1));
env.expandToInclude((Double)bbox.get(2),(Double)bbox.get(3));
}
}
return new ReferencedEnvelope(env,null);
}
@Override
public CouchDBDataStore getDataStore() {
return (CouchDBDataStore) super.getDataStore();
}
private CouchDBSpatialView spatialView() {
return getDataStore().getConnection().spatialView(viewName);
}
private CouchDBView dataView() {
return getDataStore().getConnection().view(viewName);
}
private Envelope getBBox(Query query) {
Envelope envelope = (Envelope) query.getFilter().accept(ExtractBoundsFilterVisitor.BOUNDS_VISITOR, null);
if (envelope == null || envelope.isNull() || Double.isInfinite(envelope.getArea())) {
envelope = new Envelope(-180, 180, -90, 90);
}
return envelope;
}
@Override
protected int getCountInternal(Query query) throws IOException {
// @todo ianschneider understand query
Envelope e = getBBox(query);
try {
return (int) spatialView().count(e.getMinX(),e.getMinY(),e.getMaxX(),e.getMaxY());
} catch (CouchDBException ex) {
throw ex.wrap();
}
}
@Override
protected FeatureReader<SimpleFeatureType, SimpleFeature> getReaderInternal(Query query) throws IOException {
// @todo ianschneider understand query
JSONObject features;
Envelope e = getBBox(query);
try {
features = spatialView().get(e.getMinX(),e.getMinY(),e.getMaxX(),e.getMaxY());
} catch (CouchDBException ex) {
throw ex.wrap();
}
return new CouchDBFeatureReader(buildFeatureType(), (JSONArray) features.get("rows"));
}
@Override
protected SimpleFeatureType buildFeatureType() throws IOException {
// feature types could be
// 1. created dynamically
// 2. pointed to statically (schema or json)
// a. if design document had schema in it, that'd be conventional
// for now, choice 1
return createFeatureTypeFromData();
}
protected SimpleFeatureType createFeatureTypeFromData() throws IOException {
JSONArray res;
try {
res = dataView().get(1);
} catch (CouchDBException ex) {
throw ex.wrap();
}
if (res.size() == 0) {
throw new IOException("No features exist in this view");
}
JSONObject row = (JSONObject) res.get(0);
row = (JSONObject) row.get("value");
Name fqn = new NameImpl(getDataStore().getNamespaceURI(),getEntry().getTypeName());
return CouchDBUtils.createFeatureType(row, fqn);
}
@Override
protected FeatureWriter<SimpleFeatureType, SimpleFeature> getWriterInternal(Query query, int flags) throws IOException {
switch (flags) {
case 0: throw new IllegalArgumentException( "no write flags set" );
case WRITER_ADD:
return new CouchDBAddFeatureWriter(buildFeatureType(),getDataStore());
case WRITER_UPDATE:
throw new UnsupportedOperationException("Update not supported");
default:
throw new IllegalArgumentException(" cannot handle flags " + flags);
}
}
}