/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package de.cismet.cismap.commons.featureservice.factory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.PrecisionModel;
import com.vividsolutions.jts.index.strtree.STRtree;
import org.deegree.model.feature.Feature;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.feature.GMLFeatureCollectionDocument;
import org.deegree.model.feature.schema.FeatureType;
import org.deegree.model.feature.schema.PropertyType;
import org.deegree.model.spatialschema.JTSAdapter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.List;
import java.util.Vector;
import javax.swing.SwingWorker;
import de.cismet.cismap.commons.BoundingBox;
import de.cismet.cismap.commons.CrsTransformer;
import de.cismet.cismap.commons.features.DefaultFeatureServiceFeature;
import de.cismet.cismap.commons.features.ShapeFeature;
import de.cismet.cismap.commons.featureservice.FeatureServiceAttribute;
import de.cismet.cismap.commons.featureservice.LayerProperties;
import de.cismet.cismap.commons.featureservice.factory.FeatureFactory.TooManyFeaturesException;
import de.cismet.cismap.commons.interaction.CismapBroker;
/**
* Feature Factory that supports of GML documents.<br/>
* For the reading of GML documents Degree2 Libraries are used.
*
* @author Pascal Dihé
* @version $Revision$, $Date$
*/
public class GMLFeatureFactory extends DegreeFeatureFactory<DefaultFeatureServiceFeature, String>
implements CachingFeatureFactory<DefaultFeatureServiceFeature, String> {
//~ Instance fields --------------------------------------------------------
protected int maxCachedFeatureCount = 150000;
protected URI documentURI;
protected GMLFeatureCollectionDocument gmlDocument;
protected boolean initialised = false;
protected STRtree degreeFeaturesTree = null;
protected Vector<FeatureServiceAttribute> featureServiceAttributes;
protected BufferedReader documentReader;
protected Geometry envelope;
//~ Constructors -----------------------------------------------------------
/**
* Creates a new GMLFeatureFactory object.
*
* @param layerProperties DOCUMENT ME!
* @param documentURL DOCUMENT ME!
* @param maxCachedFeatureCount DOCUMENT ME!
* @param workerThread DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
public GMLFeatureFactory(final LayerProperties layerProperties,
final URI documentURL,
final int maxCachedFeatureCount,
final SwingWorker workerThread) throws Exception {
this.layerProperties = layerProperties;
this.documentURI = documentURL;
this.maxCachedFeatureCount = maxCachedFeatureCount;
try {
this.parseGMLFile(workerThread);
this.initialised = true;
} catch (Exception ex) {
logger.error("SW[" + workerThread + "]: error parsing gml file", ex);
if (DEBUG && (gmlDocument != null)) {
if (logger.isDebugEnabled()) {
logger.debug(gmlDocument.getAsString());
}
}
this.cleanup();
}
}
/**
* Creates a new GMLFeatureFactory object.
*
* @param gmlff DOCUMENT ME!
*/
protected GMLFeatureFactory(final GMLFeatureFactory gmlff) {
super(gmlff);
this.maxCachedFeatureCount = gmlff.maxCachedFeatureCount;
this.documentURI = gmlff.documentURI;
this.gmlDocument = gmlff.gmlDocument;
this.degreeFeaturesTree = gmlff.degreeFeaturesTree;
this.featureServiceAttributes = new Vector(gmlff.featureServiceAttributes);
this.initialised = gmlff.initialised;
}
//~ Methods ----------------------------------------------------------------
/**
* DOCUMENT ME!
*
* @param degreeFeature DOCUMENT ME!
* @param index DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
@Override
protected DefaultFeatureServiceFeature createFeatureInstance(final Feature degreeFeature, final int index)
throws Exception {
final DefaultFeatureServiceFeature gmlFeature = new DefaultFeatureServiceFeature();
int currentSrid = -1;
// auto generate Ids!
gmlFeature.setId(index);
try {
gmlFeature.setGeometry(JTSAdapter.export(degreeFeature.getGeometryPropertyValues()[geometryIndex]));
currentSrid = CrsTransformer.extractSridFromCrs(degreeFeature.getGeometryPropertyValues()[geometryIndex]
.getCoordinateSystem().getPrefixedName());
gmlFeature.getGeometry().setSRID(currentSrid);
} catch (Exception e) {
gmlFeature.setGeometry(JTSAdapter.export(degreeFeature.getDefaultGeometryPropertyValue()));
}
// store the feature in the spatial index structure
gmlFeature.setGeometry(CrsTransformer.transformToDefaultCrs(gmlFeature.getGeometry()));
this.degreeFeaturesTree.insert(gmlFeature.getGeometry().getEnvelopeInternal(), gmlFeature);
if (envelope == null) {
envelope = gmlFeature.getGeometry().getEnvelope();
envelope.setSRID(currentSrid);
} else {
envelope = envelope.getEnvelope().union(gmlFeature.getGeometry().getEnvelope());
}
return gmlFeature;
}
/**
* DOCUMENT ME!
*/
protected synchronized void cleanup() {
if (this.documentReader != null) {
try {
documentReader.close();
} catch (IOException ex) {
}
documentReader = null;
System.gc();
}
this.gmlDocument = null;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
public boolean isLazy() {
return false;
}
/**
* DOCUMENT ME!
*
* @param workerThread DOCUMENT ME!
*
* @throws Exception DOCUMENT ME!
*/
protected synchronized void parseGMLFile(final SwingWorker workerThread) throws Exception {
logger.info("SW[" + workerThread + "]: initialising GMLFeatureFactory with document: '" + documentURI + "'");
final long start = System.currentTimeMillis();
envelope = null;
this.documentReader = new BufferedReader(new InputStreamReader(
new FileInputStream(new File(this.documentURI))));
this.gmlDocument = new GMLFeatureCollectionDocument();
this.gmlDocument.load(this.documentReader, "http://dummyID");
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " initialising gml document ")) {
this.cleanup();
return;
}
// check if thread is canceled .........................................
int max = this.gmlDocument.getFeatureCount();
if (DEBUG) {
if (logger.isDebugEnabled()) {
logger.debug("SW[" + workerThread + "]: " + max + " features found in gml file");
}
}
if (max > this.maxCachedFeatureCount) {
logger.error("SW[" + workerThread + "]: number of features in gml file (" + max
+ ") exceeds maximum of supported features (" + this.maxCachedFeatureCount + ")");
max = this.maxCachedFeatureCount;
}
if (max == 0) {
logger.error("SW[" + workerThread + "]: no features found in gml file");
throw new Exception("no features found in gml file '" + this.documentURI + "'");
}
this.degreeFeaturesTree = new STRtree(max);
// parse features ........................................................
// ParsingProgressListener progressListener = new ParsingProgressListener(workerThread, max, 100);
// this.gmlDocument.addFeatureProgressListener(progressListener);
final FeatureCollection featureCollection = gmlDocument.parse();
if (DEBUG) {
if (logger.isDebugEnabled()) {
logger.debug("SW[" + workerThread + "]: " + featureCollection.size() + " features parsed");
}
}
this.cleanup();
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " parsing gml document ")) {
return;
}
// check if thread is canceled .........................................
if (featureCollection.size() > 0) {
final Feature type = featureCollection.getFeature(0);
logger.info("SW[" + workerThread + "]: creating " + type.getProperties().length
+ " featureServiceAttributes from first parsed degree feature");
featureServiceAttributes = new Vector(type.getProperties().length);
for (final PropertyType pt : type.getFeatureType().getProperties()) {
// ToDo was ist wenn zwei Geometrien dabei sind
featureServiceAttributes.add(
new FeatureServiceAttribute(pt.getName().getAsString(), Integer.toString(pt.getType()), true));
}
} else {
logger.error("could not create feature service attributes, no valid gml fetures found");
}
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " creating feature service attributes")) {
return;
}
// check if thread is canceled .........................................
this.processFeatureCollection(workerThread, featureCollection.toArray(), initialised);
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " processing parsed features")) {
return;
}
// check if thread is canceled .........................................
logger.info("parsing, converting and initialising " + max + " gml features took "
+ (System.currentTimeMillis() - start) + " ms");
}
/**
* DOCUMENT ME!
*/
@Override
public synchronized void flush() {
logger.warn("flushing cached features");
this.lastCreatedfeatureVector.clear();
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
public int getMaxCachedFeatureCount() {
return this.maxCachedFeatureCount;
}
/**
* DOCUMENT ME!
*
* @param maxCachedFeatureCount DOCUMENT ME!
*/
@Override
public void setMaxCachedFeatureCount(final int maxCachedFeatureCount) {
this.maxCachedFeatureCount = maxCachedFeatureCount;
}
/**
* Get the value of documentURL.
*
* @return the value of documentURL
*/
public URI getDocumentURI() {
return documentURI;
}
/**
* Set the value of documentURL.
*
* @param documentURI new value of documentURL
*/
public synchronized void setDocumentURI(final URI documentURI) {
this.documentURI = documentURI;
}
/**
* DOCUMENT ME!
*
* @param query DOCUMENT ME!
* @param boundingBox DOCUMENT ME!
* @param workerThread DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws TooManyFeaturesException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
@Override
public synchronized List<DefaultFeatureServiceFeature> createFeatures(final String query,
final BoundingBox boundingBox,
final SwingWorker workerThread) throws TooManyFeaturesException, Exception {
return createFeatures_internal(query, boundingBox, workerThread, 0, 0, null, true);
}
/**
* DOCUMENT ME!
*
* @param workerThread DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws TooManyFeaturesException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
@Override
public synchronized Vector<FeatureServiceAttribute> createAttributes(final SwingWorker workerThread)
throws TooManyFeaturesException, Exception {
if ((this.featureServiceAttributes == null) || (this.featureServiceAttributes.size() == 0)) {
logger.warn("SW[" + workerThread + "]: Factory not correctopy initialised, parsing gml file");
this.parseGMLFile(workerThread);
}
if ((this.featureServiceAttributes == null) || (this.featureServiceAttributes.size() == 0)) {
logger.error("SW[" + workerThread + "]: no attributes could be found in gml file");
throw new Exception("no attributes could be found in gml file '" + this.documentURI + "'");
}
return this.featureServiceAttributes;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
protected boolean isGenerateIds() {
return true;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
@Override
public GMLFeatureFactory clone() {
return new GMLFeatureFactory(this);
}
// public static void main(String args[])
// {
// BasicConfigurator.configure();
//
// try
// {
// Logger.getLogger(GMLFeatureFactory.class).setLevel(org.apache.log4j.Level.ALL);
// GMLFeatureFactory gmlFeatureFactory = new GMLFeatureFactory(
// new DefaultLayerProperties(), new URI("file:///D:/W/fs.gml"), 50000, null);
// gmlFeatureFactory.logger.info("OK");
// } catch (Throwable t)
// {
// t.printStackTrace();
// }
// }
@Override
public int getFeatureCount(final String query, final BoundingBox bb) {
return this.degreeFeaturesTree.size();
}
@Override
public synchronized List<DefaultFeatureServiceFeature> createFeatures(final String query,
final BoundingBox boundingBox,
final SwingWorker workerThread,
final int offset,
final int limit,
final FeatureServiceAttribute[] orderBy) throws TooManyFeaturesException, Exception {
return createFeatures_internal(query, boundingBox, workerThread, offset, limit, orderBy, false);
}
/**
* DOCUMENT ME!
*
* @param query DOCUMENT ME!
* @param boundingBox DOCUMENT ME!
* @param workerThread DOCUMENT ME!
* @param offset DOCUMENT ME!
* @param limit DOCUMENT ME!
* @param orderBy DOCUMENT ME!
* @param saveAsLastCreated DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws TooManyFeaturesException DOCUMENT ME!
* @throws Exception DOCUMENT ME!
*/
private synchronized List<DefaultFeatureServiceFeature> createFeatures_internal(final String query,
final BoundingBox boundingBox,
final SwingWorker workerThread,
final int offset,
final int limit,
final FeatureServiceAttribute[] orderBy,
final boolean saveAsLastCreated) throws TooManyFeaturesException, Exception {
if (!this.initialised) {
logger.warn("SW[" + workerThread + "]: Factory not correclty initialised, parsing gml file");
this.parseGMLFile(workerThread);
this.initialised = true;
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " initialisation")) {
return null;
}
// check if thread is canceled .........................................
}
final long start = System.currentTimeMillis();
final Coordinate[] polyCords = new Coordinate[5];
polyCords[0] = new Coordinate(boundingBox.getX1(), boundingBox.getY1());
polyCords[1] = new Coordinate(boundingBox.getX1(), boundingBox.getY2());
polyCords[2] = new Coordinate(boundingBox.getX2(), boundingBox.getY2());
polyCords[3] = new Coordinate(boundingBox.getX2(), boundingBox.getY1());
polyCords[4] = new Coordinate(boundingBox.getX1(), boundingBox.getY1());
final GeometryFactory geomFactory = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING),
CrsTransformer.extractSridFromCrs(CismapBroker.getInstance().getSrs().getCode()));
final Polygon boundingPolygon = geomFactory.createPolygon(geomFactory.createLinearRing(polyCords), null);
List<DefaultFeatureServiceFeature> selectedFeatures;
if (featuresAlreadyInMemory(boundingPolygon, query)) {
selectedFeatures = createFeaturesFromMemory(query, boundingPolygon);
} else {
selectedFeatures = this.degreeFeaturesTree.query(
boundingPolygon.getEnvelopeInternal());
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " quering spatial index structure")) {
return null;
}
// check if thread is canceled .........................................
logger.info("SW[" + workerThread + "]: " + selectedFeatures.size()
+ " features selected by bounding box out of " + this.degreeFeaturesTree.size()
+ " in spatial index");
if (DEBUG) {
if (logger.isDebugEnabled()) {
logger.debug("SW[" + workerThread + "]: quering spatial index for bounding box took "
+ (System.currentTimeMillis() - start) + " ms");
}
}
}
if (selectedFeatures.size() > this.getMaxFeatureCount()) {
throw new TooManyFeaturesException("features in selected area " + selectedFeatures.size()
+ " exceeds max feature count " + this.getMaxFeatureCount());
} else if (selectedFeatures.isEmpty()) {
logger.warn("SW[" + workerThread + "]: no features found in selected bounding box");
return null;
}
if ((orderBy != null) && (orderBy.length > 0)) {
sortFeatureList(selectedFeatures, orderBy);
}
if (offset > 0) {
selectedFeatures = selectedFeatures.subList(offset, selectedFeatures.size());
}
if ((limit > 0) && (selectedFeatures.size() > limit)) {
selectedFeatures = selectedFeatures.subList(0, limit);
}
this.reEvaluteExpressions(selectedFeatures, workerThread);
// check if thread is canceled .........................................
if (this.checkCancelled(workerThread, " saving LastCreatedFeatures ")) {
return null;
}
// check if thread is canceled .........................................
if (saveAsLastCreated) {
this.updateLastCreatedFeatures(selectedFeatures, boundingPolygon, query);
}
return new Vector<DefaultFeatureServiceFeature>(selectedFeatures);
}
/**
* DOCUMENT ME!
*
* @return the envelope
*/
public Geometry getEnvelope() {
return envelope;
}
/**
* DOCUMENT ME!
*
* @param envelope the envelope to set
*/
public void setEnvelope(final Geometry envelope) {
this.envelope = envelope;
}
}