/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2010-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.map;
import java.io.IOException;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureEvent;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.event.MapLayerEvent;
import org.geotools.referencing.CRS;
import org.geotools.styling.Style;
import org.opengis.feature.Feature;
import org.opengis.feature.type.FeatureType;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Layer responsible for rendering vector information provided by a FeatureSource.
* <p>
* The FeatureLayer combines:
* <ul>
* <li>data: FeatureSource</li>
* <li>style: Style</li>
* </ul>
* Please note that a StyleLayerDescriptor (defined by SLD) document is usually used to describe the
* rendering requirements for an entire Map; while a Style (defined by SE) is focused on a single
* layer of content
* @since 2.7
* @version 8.0
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/library/render/src/main/java/org/geotools/map/FeatureLayer.java $
*/
public class FeatureLayer extends StyleLayer {
/** FeatureSource offering content for display */
protected FeatureSource<? extends FeatureType, ? extends Feature> featureSource;
/** Query use to limit content of featureSource */
protected Query query;
/** Listener to forward feature source events as layer events */
protected FeatureListener sourceListener;
/**
* Creates a new instance of FeatureLayer
*
* @param featureSource
* the data source for this layer
* @param style
* the style used to represent this layer
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public FeatureLayer(FeatureSource featureSource, Style style) {
super(style);
this.featureSource = featureSource;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public FeatureLayer(FeatureSource featureSource, Style style, String title) {
super(style,title);
this.featureSource = featureSource;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public FeatureLayer(FeatureCollection collection, Style style) {
super(style);
this.featureSource = DataUtilities.source(collection);
this.style = style;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public FeatureLayer(FeatureCollection collection, Style style, String title) {
super( style, title );
this.featureSource = DataUtilities.source(collection);
}
/**
* Used to connect/disconnect a FeatureListener if any map layer listeners are registered.
*/
protected synchronized void connectDataListener(boolean listen) {
if (sourceListener == null) {
sourceListener = new FeatureListener() {
public void changed(FeatureEvent featureEvent) {
fireMapLayerListenerLayerChanged(MapLayerEvent.DATA_CHANGED);
}
};
}
if (listen) {
featureSource.addFeatureListener(sourceListener);
} else {
featureSource.removeFeatureListener(sourceListener);
}
}
@Override
public void dispose() {
if (featureSource != null) {
if (sourceListener != null) {
featureSource.removeFeatureListener(sourceListener);
}
featureSource = null;
}
style = null;
query = null;
super.dispose();
}
/**
* Get the feature source for this layer.
*
* @return feature source for the contents of this layer
*/
@Override
public FeatureSource<?,?> getFeatureSource() {
return featureSource;
}
/**
* Get the feature source for this layer.
*
* @return SimpleFeatureSource for this layer, or null if not available
*/
public SimpleFeatureSource getSimpleFeatureSource() {
if (featureSource instanceof SimpleFeatureSource) {
return (SimpleFeatureSource) featureSource;
}
return null; // not available
}
/**
* Returns the definition query (filter) for this layer. If no definition query has been defined
* {@link Query.ALL} is returned.
*
* @return Query used to process content prior to display, or Query.ALL to indicate all content
* is used
*/
public Query getQuery() {
if (query == null) {
return Query.ALL;
} else {
return query;
}
}
/**
* Sets a definition query for the layer which acts as a filter for the features that the layer
* will draw.
*
* <p>
* A consumer must ensure that this query is used in combination with the bounding box filter
* generated on each map interaction to limit the number of features returned to those that
* complains both the definition query and relies inside the area of interest.
* </p>
* <p>
* IMPORTANT: only include attribute names in the query if you want them to be ALWAYS returned.
* It is desirable to not include attributes at all but let the layer user (a renderer?) to
* decide wich attributes are actually needed to perform its requiered operation.
* </p>
*
* @param query
*/
public void setQuery(Query query) {
this.query = query;
fireMapLayerListenerLayerChanged(MapLayerEvent.FILTER_CHANGED);
}
@Override
public ReferencedEnvelope getBounds() {
try {
ReferencedEnvelope bounds;
if(query != null) {
bounds = featureSource.getBounds(query);
} else {
bounds = featureSource.getBounds();
}
if( bounds != null ){
FeatureType schema = featureSource.getSchema();
CoordinateReferenceSystem schemaCrs = schema.getCoordinateReferenceSystem();
CoordinateReferenceSystem boundsCrs = bounds.getCoordinateReferenceSystem();
if( boundsCrs == null && schemaCrs != null ){
LOGGER.warning("Bounds crs not defined; assuming bounds from schema are correct for "+featureSource );
bounds = new ReferencedEnvelope(bounds.getMinX(),bounds.getMaxX(),bounds.getMinY(),bounds.getMaxY(),schemaCrs);
}
if( boundsCrs != null && schemaCrs != null && !CRS.equalsIgnoreMetadata(boundsCrs, schemaCrs)){
LOGGER.warning("Bounds crs and schema crs are not consistent; forcing the use of the schema crs so they are consistent" );
//bounds = bounds.transform(schemaCrs, true );
bounds = new ReferencedEnvelope(bounds.getMinX(),bounds.getMaxX(),bounds.getMinY(),bounds.getMaxY(),schemaCrs);
}
return bounds;
}
} catch (IOException e) {
// feature bounds unavailable
}
CoordinateReferenceSystem crs = featureSource.getSchema().getCoordinateReferenceSystem();
if (crs != null) {
// returns the envelope based on the CoordinateReferenceSystem
Envelope envelope = CRS.getEnvelope(crs);
if (envelope != null) {
return new ReferencedEnvelope(envelope); // nice!
}
else {
return new ReferencedEnvelope(crs); // empty bounds
}
}
else {
return null; // unknown
}
}
}