/* 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.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.measure.unit.Unit;
import org.geotools.data.DataUtilities;
import org.geotools.feature.AttributeTypeBuilder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.IllegalAttributeException;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.CoordinateSequenceTransformer;
import org.geotools.geometry.jts.GeometryCoordinateSequenceTransformer;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.resources.CRSUtilities;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.GeometryType;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.OperationNotFoundException;
import org.opengis.referencing.operation.TransformException;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineSegment;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import es.axios.udig.ui.commons.internal.i18n.Messages;
/**
* GeoTools Utils
* <p>
* This class has util and convenint methods to work with GeoTools
* </p>
*
* @author Mauricio Pazos (www.axios.es)
* @author Gabriel Roldan (www.axios.es)
* @since 1.1.0
*/
public class GeoToolsUtils {
private static final String[][] COMMON_LENGTH_UNITS = {
{"km", Messages.GeoToolsUtils_unitName_kilometers}, //$NON-NLS-1$
{"pixel", Messages.GeoToolsUtils_unitName_pixels}, //$NON-NLS-1$
{"ft", Messages.GeoToolsUtils_unitName_feet}, //$NON-NLS-1$
{"yd", Messages.GeoToolsUtils_unitName_yards}, //$NON-NLS-1$
{"in", Messages.GeoToolsUtils_unitName_inches}, //$NON-NLS-1$
{"cm", Messages.GeoToolsUtils_unitName_centimeters}, //$NON-NLS-1$
{"m", Messages.GeoToolsUtils_unitName_meters}}; //$NON-NLS-1$
public static final Unit DEGREES = Unit.valueOf("\u00B0"); //$NON-NLS-1$
public static final Unit PIXEL_UNITS = Unit.valueOf("pixel"); //$NON-NLS-1$
/**
* Commonly used units of length measure
*/
private static Map<Unit, String> commonLengthUnits = new HashMap<Unit, String>();
private GeoToolsUtils() {
// util class
}
/**
* Returns a set of the most commonly used units of measure for measuring lengths at a GIS
* application scale
*
* @return a set of the most commont units to use in operations like buffer, etc
*/
public static Set<Unit> getCommonLengthUnits() {
if (commonLengthUnits.isEmpty()) {
synchronized (commonLengthUnits) {
if (commonLengthUnits.isEmpty()) {
for( int i = 0; i < COMMON_LENGTH_UNITS.length; i++ ) {
Unit unit = Unit.valueOf(COMMON_LENGTH_UNITS[i][0]);
String unitName = COMMON_LENGTH_UNITS[i][1];
commonLengthUnits.put(unit, unitName);
}
commonLengthUnits = Collections.unmodifiableMap(commonLengthUnits);
}
}
}
return commonLengthUnits.keySet();
}
/**
* Returns the localized unit name
*
* @param unit the unit
* @return the localized
*/
public static String getUnitName( Unit unit ) {
assert unit != null;
String unitName = commonLengthUnits.get(unit);
if (unitName == null) {
if (DEGREES.equals(unit)) {
unitName = Messages.GeoToolsUtils_unitName_degrees;
} else {
unitName = unit.toString();
}
}
return unitName;
}
/**
* Adds the matching attributes from <code>source</code> to <code>target</code>
* <p>
* Two attributes match if they have the same name and type.
* </p>
*
* @param source
* @param target
* @throws IllegalAttributeException
*/
public static void match( SimpleFeature source, SimpleFeature target ) throws IllegalAttributeException {
Map<String, Class<?>> sourceTypes = new HashMap<String, Class<?>>();
for( AttributeDescriptor att : source.getFeatureType().getAttributes() ) {
sourceTypes.put(att.getLocalName(), att.getType().getBinding());
}
for( AttributeDescriptor att : target.getFeatureType().getAttributes() ) {
String name = att.getLocalName();
Class<?> sourceBinding = sourceTypes.get(name);
if (sourceBinding != null && sourceBinding == att.getType().getBinding()) {
Object attribute = source.getAttribute(name);
target.setAttribute(name, attribute);
}
}
}
/**
* Returns a representative (first axis with a length) unit of the given crs.
*
* @param crs
* @return a representative unit of the given crs.
*/
public static Unit getDefaultCRSUnit( CoordinateReferenceSystem crs ) {
assert crs != null;
CoordinateSystem coordinateSystem = crs.getCoordinateSystem();
Unit unit = CRSUtilities.getUnit(coordinateSystem);
if (unit == null) {
CoordinateSystemAxis axis = coordinateSystem.getAxis(0);
unit = axis.getUnit();
}
return unit;
}
/**
* Returns the first feature of feature collection
*
* @param featureSet
* @return SimpleFeature first feature in collection
*/
public static SimpleFeature firstFeature( FeatureCollection<SimpleFeatureType, SimpleFeature> featureSet ) {
Iterator iter = null;
try {
iter = featureSet.iterator();
if (!iter.hasNext()) {
return null;
}
return (SimpleFeature) iter.next();
} finally {
if (iter != null) {
featureSet.close(iter);
}
}
}
/**
* @param gFactory
* @param geomCrs
* @param reprojectCrs
* @return a GeometryCoordinateSequenceTransformer configured to transform geometries from
* <code>geomCrs</code> to <code>reprojectCrs</code>
* @throws OperationNotFoundException
*/
public static GeometryCoordinateSequenceTransformer getTransformer(
final GeometryFactory gFactory,
final CoordinateReferenceSystem geomCrs,
final CoordinateReferenceSystem reprojectCrs ) throws OperationNotFoundException {
assert geomCrs != null;
assert reprojectCrs != null;
CoordinateSequenceFactory csFactory;
CoordinateSequenceTransformer csTransformer;
GeometryCoordinateSequenceTransformer transformer;
csFactory = gFactory.getCoordinateSequenceFactory();
csTransformer = new CoordSeqFactoryPreservingCoordinateSequenceTransformer(csFactory);
transformer = new GeometryCoordinateSequenceTransformer(csTransformer);
// MathTransform mathTransform = findMathTransform(geomCrs, reprojectCrs);
MathTransform mathTransform;
try {
mathTransform = CRS.findMathTransform(geomCrs, reprojectCrs, true);
} catch (FactoryException e) {
throw new OperationNotFoundException(e.getMessage());
}
transformer.setMathTransform(mathTransform);
return transformer;
}
// private static MathTransform findMathTransform( final CoordinateReferenceSystem geomCrs,
// final CoordinateReferenceSystem reprojectCrs ) throws OperationNotFoundException {
// MathTransform mathTransform;
//
// CoordinateOperation reprojectOperation;
// try {
// CoordinateOperationFactory coordinateOperationFactory;
// Hints hints = new Hints(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
// coordinateOperationFactory = FactoryFinder.getCoordinateOperationFactory(hints);
// reprojectOperation = coordinateOperationFactory.createOperation(geomCrs, reprojectCrs);
// } catch (OperationNotFoundException e) {
// throw e;
// } catch (FactoryException e) {
// // should not happen
// throw (RuntimeException) new RuntimeException().initCause(e);
// }
// mathTransform = (MathTransform2D) reprojectOperation.getMathTransform();
// return mathTransform;
// }
/**
* Reprojects <code>geom</code> from <code>geomCrs</code> to <code>reprojectCrs</code>
*
* @param geom the geometry to reproject
* @param geomCrs the original CRS of <code>geom</code>
* @param reprojectCrs the CRS to reproject <code>geom</code> onto
* @return <code>geom</code> reprojected from <code>geomCrs</code> to
* <code>reprojectCrs</code>
* @throws OperationNotFoundException if there isn't a transformation in GeoTools to convert
* from <code>geomCrs</code> to <code>reprojectCrs</code>
* @throws TransformException
*/
public static Geometry reproject( final Geometry geom, final CoordinateReferenceSystem geomCrs,
final CoordinateReferenceSystem reprojectCrs ) throws OperationNotFoundException, TransformException {
assert geom != null;
assert geomCrs != null;
assert reprojectCrs != null;
if (geomCrs.equals(reprojectCrs)) {
return geom;
}
if (CRS.equalsIgnoreMetadata(geomCrs, reprojectCrs)) {
return geom;
}
GeometryFactory gFactory = geom.getFactory();
GeometryCoordinateSequenceTransformer transformer;
transformer = getTransformer(gFactory, geomCrs, reprojectCrs);
Geometry geometry;
try {
geometry = transformer.transform(geom);
} catch (TransformException e) {
throw e;
}
return geometry;
}
/**
* Returns the aSimpleFeaturebuilder for a type with the default geometry attribute. The default
* geometry type is <code>Geometry<code>
*
* @return FeatureTypeBuilder
*/
public static SimpleFeatureTypeBuilder createDefaultFeatureType() {
return createDefaultFeatureType(Messages.GeoToolsUtils_FeatureTypeName);
}
/**
* Returns the aSimpleFeaturebuilder for a type with the default geometry attribute The default
* geometry type is <code>Geometry<code>. The Crs will be WGS84
*
* @return FeatureTypeBuilder
*/
public static SimpleFeatureTypeBuilder createDefaultFeatureType( final String typeName ) {
return createDefaultFeatureType(typeName,
DefaultGeographicCRS.WGS84);
}
/**
* Returns the aSimpleFeaturebuilder for a type with the default geometry attribute The default
* geometry type is <code>Geometry<code>.
*
* @param typeName
* @param crs
* @return FeatureTypeBuilder
*/
public static SimpleFeatureTypeBuilder createDefaultFeatureType( final String typeName,
final CoordinateReferenceSystem crs ) {
assert typeName != null;
SimpleFeatureTypeBuilder builder;
builder = new SimpleFeatureTypeBuilder();
builder.setName(typeName);
builder.crs( crs ).add( Messages.GeoToolsUtils_Geometry, Geometry.class );
return builder;
}
/**
* Returns the aSimpleFeaturebuilder for a type with the following attributes present in de
* prototype. The default geometry type is <code>Geometry<code>
*
* @param prototype
* @return FeatureTypeBuilder
*/
public static SimpleFeatureTypeBuilder createDefaultFeatureType( final SimpleFeatureType prototype ) {
assert prototype != null;
final String newTypeName = prototype.getTypeName() + "2"; //$NON-NLS-1$
return createDefaultFeatureType(prototype, newTypeName);
}
/**
* Returns the aSimpleFeaturebuilder for a type with the attributes present in de prototype. The
* default geometry type is <code>Geometry<code>
*
* @param prototype
* @param typeName
* @return FeatureTypeBuilder
*/
public static SimpleFeatureTypeBuilder createDefaultFeatureType( final SimpleFeatureType prototype,
final String typeName ) {
assert prototype != null;
assert typeName != null;
SimpleFeatureTypeBuilder builder;
builder = new SimpleFeatureTypeBuilder();
builder.setName(typeName);
List<AttributeDescriptor> attributes = prototype.getAttributes();
GeometryDescriptor defaultGeometry = prototype.getDefaultGeometry();
for( int i = 0; i < attributes.size(); i++ ) {
AttributeDescriptor att = attributes.get(i);
if (att == defaultGeometry) {
if (att.getType().getBinding() != MultiPolygon.class && att.getType().getBinding() != Polygon.class) {
Class<?> targetGeomType = Polygon.class;
final Class sourceGeomClass = defaultGeometry.getType().getBinding();
if (GeometryCollection.class.isAssignableFrom(sourceGeomClass)) {
targetGeomType = MultiPolygon.class;
}
final String geomTypeName = att.getLocalName();
CoordinateReferenceSystem crs = defaultGeometry.getCRS();
AttributeTypeBuilder build = new AttributeTypeBuilder();
build.setName( geomTypeName );
build.setBinding( targetGeomType );
build.setNillable(true);
build.setCRS(crs);
GeometryType type = build.buildGeometryType();
att = build.buildDescriptor( geomTypeName, type );
}
builder.add( att );
builder.setDefaultGeometry( att.getLocalName() );
} else {
builder.add(att);
}
}
return builder;
}
/**
* Create a newSimpleFeatureofSimpleFeatureType whith the geometry provides. The geometry will be
* adapted to geometry class ofSimpleFeaturetype.
*
* @param type
* @param geometry
* @return a newSimpleFeature
*/
@SuppressWarnings("unchecked")
public static SimpleFeature createFeatureWithGeometry( final SimpleFeatureType type, Geometry geometry ) {
SimpleFeature newFeature;
try {
newFeature = DataUtilities.template(type);
final GeometryDescriptor targetGeometryType = type.getDefaultGeometry();
final String attName = targetGeometryType.getLocalName();
final Class geomClass = targetGeometryType.getType().getBinding();
Geometry geoAdapted = GeometryUtil.adapt(geometry, geomClass);
newFeature.setAttribute(attName, geoAdapted);
return newFeature;
} catch (IllegalAttributeException e) {
final String msg = Messages.GeoToolsUtils_FailCreatingFeature;
throw (RuntimeException) new RuntimeException(msg).initCause(e);
} finally {
}
}
/**
* @param featureType
* @return the dimension of default geometry
*/
public static int getDimensionOf( final SimpleFeatureType featureType ) {
GeometryDescriptor geomAttr = featureType.getDefaultGeometry();
Class geomClass = geomAttr.getType().getBinding();
int dim = GeometryUtil.getDimension(geomClass);
return dim;
}
/**
* Computes the sum of features in theSimpleFeaturecollection.
*
* @param selectedFeatures
* @return the count of features in the collection or Integer.MAX_VALUE if theSimpleFeature
* collection has more than Integer.MAX_VALUE features
*/
public static int computeCollectionSize( FeatureCollection<SimpleFeatureType, SimpleFeature> features ) {
Iterator iter = features.iterator();
int count = 0;
try {
while( iter.hasNext() ) {
iter.next();
count++;
}
} catch (ArithmeticException e) {
count = Integer.MAX_VALUE;
} finally {
features.close(iter);
}
return count;
}
/**
* Utility method to easily reproject a line segment as LineSegment is not a Geometry
*
* @param segment the segment to reproject
* @param segmentCrs the CRS the segment coordinates are in
* @param reprojectCrs the CRS to reproject the segment to
* @return a new line segment built from the reprojected coordinates of <code>segment</code>
* from <code>segmentCrs</code> to <code>reprojectCrs</code>
*/
public static LineSegment reproject( LineSegment segment, CoordinateReferenceSystem segmentCrs,
CoordinateReferenceSystem reprojectCrs ) {
assert segment != null;
assert segmentCrs != null;
assert reprojectCrs != null;
if (segmentCrs.equals(reprojectCrs)) {
return segment;
}
if (CRS.equalsIgnoreMetadata(segmentCrs, reprojectCrs)) {
return segment;
}
MathTransform mathTransform;
try {
mathTransform = CRS.findMathTransform(segmentCrs, reprojectCrs, true);
} catch (FactoryException e) {
throw new RuntimeException(e.getMessage());
}
double[] src = {segment.p0.x, segment.p0.y, segment.p1.x, segment.p1.y};
double[] dst = new double[4];
try {
mathTransform.transform(src, 0, dst, 0, 2);
} catch (TransformException e) {
throw new RuntimeException(e.getMessage());
}
Coordinate p0 = new Coordinate(dst[0], dst[1]);
Coordinate p1 = new Coordinate(dst[2], dst[3]);
LineSegment lineSegment = new LineSegment(p0, p1);
return lineSegment;
}
}