/* Spatial Operations & Editing Tools for uDig
*
* Axios Engineering under a funding contract with:
* Diputación Foral de Gipuzkoa, Ordenación Territorial
*
* http://b5m.gipuzkoa.net
* http://www.axios.es
*
* (C) 2006, Diputación Foral de Gipuzkoa, Ordenación Territorial (DFG-OT).
* DFG-OT agrees to licence under Lesser General Public License (LGPL).
*
* 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 es.axios.udig.ui.commons.util;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import net.refractions.udig.catalog.IGeoResource;
import net.refractions.udig.project.ILayer;
import net.refractions.udig.project.IMap;
import net.refractions.udig.project.render.IViewportModel;
import net.refractions.udig.ui.ProgressManager;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Coordinate;
import es.axios.udig.ui.commons.internal.i18n.Messages;
/**
* Layer Utilities
* <p>
* Commons operations to get and set layer components.
* </p>
*
* @author Mauricio Pazos (www.axios.es)
* @author Gabriel Roldan (www.axios.es)
* @since 1.1.0
*/
public final class LayerUtil {
private LayerUtil() {
// util class
}
/**
* Creates a Query for the selected features of <code>layer</code> or all the features if it
* has no selection set and <code>fallbackToWholeLayer</code> is <code>true</code>.
*
* @param layer
* @return a Query for the selection
*/
private static DefaultQuery getSelectionQuery( final ILayer layer ) {
final boolean queryForSelectedFeatures = true;
final Query selectionQuery = layer.getQuery(queryForSelectedFeatures);
final DefaultQuery layerQuery = new DefaultQuery(selectionQuery);
CoordinateReferenceSystem layerCrs = layer.getCRS();
if (ILayer.UNKNOWN_CRS == layerCrs) {
CoordinateReferenceSystem mapCRS = MapUtil.getCRS(layer.getMap());
layerQuery.setCoordinateSystem(mapCRS);
}
Filter filter = selectionQuery.getFilter();
// no selection? perform over the whole layer
if (Filter.EXCLUDE.equals(filter)) {
layerQuery.setFilter(Filter.INCLUDE);
}
return layerQuery;
}
/**
* Compoutes the number of features selected. It will return all if any feature is selected
*
* @param layer an <code>ILayer</code> that can resolve to <code>FeatureSource</code> for
* which to compute the number of selected features.
* @return the number of features selected, if any, or the total number of features in the layer
* if no features are selected,
* @throws IOException
*/
public static int getCountOfSelectedFeatures( final ILayer layer ) throws IOException {
return getCountOfSelectedFeatures(layer, Filter.INCLUDE);
}
/**
* Computes the number of features selected. It will return all if any feature is selected.
*
* @param layer
* @param filter
* @return the count of features in layer or Integer.MAX_VALUE if the feature collection has
* more than Integer.MAX_VALUE features
* @throws IOException
*/
public static int getCountOfSelectedFeatures( final ILayer layer, final Filter filter ) throws IOException {
FeatureSource<SimpleFeatureType, SimpleFeature> source = layer.getResource(FeatureSource.class, null);
assert source != null;
final Query query = getSelectionQuery(layer);
FeatureCollection<SimpleFeatureType, SimpleFeature> features = source.getFeatures(query);
// if empty select all layer is computed (filter none)
if (features.isEmpty()) {
features = source.getFeatures(filter);
}
int count = GeoToolsUtils.computeCollectionSize(features);
return count;
}
/**
* Returns the features selected in layer.
*
* @param layer the layer to obtain the selected features from
* @param fallbackToWholeLayer wether to return the whole layer's features if no selection is
* set for the layer
* @return FeatureCollection<SimpleFeatureType, SimpleFeature> holding the selection
* @throws IOException if occurs getting the selection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> getSelectedFeatures( final ILayer layer ) throws IOException {
return getSelectedFeatures(layer, Filter.INCLUDE);
}
/**
* Retruns all layer's features
*
* @param layer
* @return all features
* @throws IOException
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> findAllFeatures( final ILayer layer ) throws IOException {
return getSelectedFeatures(layer, Filter.INCLUDE);
}
/**
* Returns the features selected in layer or all feature if there is not any selected feature
*
* @param layer the layer to obtain the selected features from
* @param fallbackToWholeLayer wether to return the whole layer's features if no selection is
* set for the layer
* @param extraFilter non null Filter to append to the layer's selection filter. Use
* {@link Filter#NONE} instead of <code>null</code> to indicate no additional filter.
* @return FeatureCollection<SimpleFeatureType, SimpleFeature> holding the selection
* @throws IOException if occurs getting the selection
*/
public static FeatureCollection<SimpleFeatureType, SimpleFeature> getSelectedFeatures( final ILayer layer,
final Filter extraFilter ) throws IOException {
assert layer != null;
assert extraFilter != null;
FeatureSource<SimpleFeatureType, SimpleFeature> source = layer.getResource(FeatureSource.class, ProgressManager.instance()
.get());
if (source == null) {
return FeatureCollections.newCollection();
}
FeatureCollection<SimpleFeatureType, SimpleFeature> features;
DefaultQuery selectionQuery = getSelectionQuery(layer);
if (!Filter.INCLUDE.equals(extraFilter)) {
Filter selectionFilter = selectionQuery.getFilter();
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
Filter filter = ff.and( selectionFilter, extraFilter );
selectionQuery.setFilter( filter );
features = source.getFeatures(selectionQuery);
} else {
features = source.getFeatures();
}
return features;
}
/**
* Traverses the <code>map</code>'s layers ensuring it does not already contains a layer
* named <code>name</code> and returns a non existent layer name, either by returning
* <code>name</code> itself, or by adding a number postfix to it.
*
* @param map
* @param name
* @return a non already existent layer name in the map
*/
public static String findNewLayerName( final IMap map, final String name ) {
String newLayerName = name;
Set<String> layerNames = new HashSet<String>();
for( ILayer layer : map.getMapLayers() ) {
layerNames.add(layer.getName());
}
int matches = 1;
while( layerNames.contains(newLayerName) ) {
matches++;
newLayerName = name + matches;
}
return newLayerName;
}
/**
* Returns a non existing type name in <code>ds</code> based on the proposed <code>name</code>.
*
* @param existingLayer the layer from which to inspect its enclosing map and referred DataStore
* to avoid duplicated type names
* @param name a proposed type name to base the returned name on
* @return <code>name</code> if it don't already exists in the layer's map nor in its
* DataStore. A non existent type name in the data store with <code>name</code> as
* prefix otherwise.
* @throws IOException
*/
public static String findNewLayerName( final ILayer existingLayer, final String name ) throws IOException {
String newLayerName = findNewLayerName(existingLayer.getMap(), name);
IGeoResource resource = existingLayer.findGeoResource(FeatureSource.class);
if (resource != null) {
FeatureSource<SimpleFeatureType, SimpleFeature> source = resource.resolve(FeatureSource.class, new NullProgressMonitor());
DataStore ds = (DataStore) source.getDataStore();
String[] dsTypes = ds.getTypeNames();
Set<String> typeNames = new HashSet<String>(Arrays.asList(dsTypes));
int matches = 1;
while( typeNames.contains(newLayerName) ) {
matches++;
newLayerName = name + matches;
}
}
return newLayerName;
}
/**
* Returns the layer's CRS ensuring it is not {@link ILayer#UNKNOWN_CRS}
* <p>
* If <code>sourceLayer.getCRS()</code> is {@link ILayer#UNKNOWN_CRS}, it means the
* underlying FeatureSource<SimpleFeatureType, SimpleFeature> has no CRS defined, thus, for the sake of performing spatial
* operations, the Map's CRS is returned instead.
* </p>
*
* @param sourceLayer
* @return the layer's CRS of the Map's one of the layer's CRS is {@link ILayer#UNKNOWN_CRS}
*/
public static CoordinateReferenceSystem getCrs( ILayer sourceLayer ) {
assert sourceLayer != null;
CoordinateReferenceSystem crs = sourceLayer.getCRS();
if (ILayer.UNKNOWN_CRS == crs) {
IMap map = sourceLayer.getMap();
IViewportModel viewportModel = map.getViewportModel();
crs = viewportModel.getCRS();
}
return crs;
}
/**
* @param layer
* @return the throw message
*/
private static String buildThrowMessage( final ILayer layer ) {
return MessageFormat.format(Messages.LayerUtil_CanNotResolveFeatureSource, layer.getName());
}
public static Coordinate mapToLayer( IMap map, ILayer layer, Coordinate coordInMapCrs ) {
try {
MathTransform toLayer = layer.mapToLayerTransform();
double[] src = {coordInMapCrs.x, coordInMapCrs.y};
double[] dst = new double[2];
toLayer.transform(src, 0, dst, 0, 1);
return new Coordinate(dst[0], dst[1]);
} catch (IOException ex) {
throw new RuntimeException(ex);
} catch (TransformException ex) {
throw new RuntimeException(ex);
}
}
}