/*
* Created on 12.01.2005
*
* SVN header information:
* $Author: michaudm $
* $Rev: 2259 $
* $Date: 2011-05-08 12:47:24 +0200 (So, 08. Mai 2011) $
* $Id: FeatureCollectionTools.java 2259 2011-05-08 10:47:24Z michaudm $s
*/
package org.openjump.core.apitools;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.openjump.core.apitools.comparisonandsorting.ObjectComparator;
import org.openjump.core.apitools.objecttyperoles.PirolFeatureCollection;
import org.openjump.util.metaData.MetaInformationHandler;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jump.feature.AttributeType;
import com.vividsolutions.jump.feature.BasicFeature;
import com.vividsolutions.jump.feature.Feature;
import com.vividsolutions.jump.feature.FeatureCollection;
import com.vividsolutions.jump.feature.FeatureDataset;
import com.vividsolutions.jump.feature.FeatureSchema;
import com.vividsolutions.jump.workbench.model.Layer;
import com.vividsolutions.jump.workbench.plugin.PlugInContext;
import com.vividsolutions.jump.workbench.ui.EditTransaction;
import de.fho.jump.pirol.utilities.FormulaParsing.FormulaValue;
import de.fho.jump.pirol.utilities.attributes.AttributeInfo;
import de.fho.jump.pirol.utilities.debugOutput.DebugUserIds;
import de.fho.jump.pirol.utilities.debugOutput.PersonalLogger;
/**
* Class to speed up handling of FeatureCollections (or lists of features) during progamming by implementing
* a set of common tasks.
* Most functions can be used in a static way, but on the other hand for each FeatureCollection a instance of this class can be invoked.
* This might be more convenient, if there is more than one thing to do with the same feature collection.
*
* @author Ole Rahn, Stefan Steiniger
* <br>
* <br>FH Osnabrück - University of Applied Sciences Osnabrück,
* <br>Project: PIROL (2005),
* <br>Subproject: Daten- und Wissensmanagement
*
* @version $Rev: 2259 $
* [sstein] - 22.Feb.2009 - modified to work in OpenJUMP
*/
public class FeatureCollectionTools extends ToolToMakeYourLifeEasier {
protected FeatureCollection fc = null;
protected List<Feature> featureList;
private HashMap<Integer, Feature> fid2Object = new HashMap<Integer, Feature>();
protected static PersonalLogger logger = new PersonalLogger(DebugUserIds.ALL);
public FeatureCollectionTools( FeatureCollection fc ){
super();
this.fc = fc;
this.featureList = this.fc.getFeatures();
}
public FeatureCollectionTools( List<Feature> fcl ){
super();
this.featureList = fcl;
}
/**
* gets the Feature with the given FID.
*@param fid FID to look for
*@return the feature
*/
public Feature getFeature( int fid ){
Integer FID = new Integer(fid);
if (!this.fid2Object.containsKey(FID)){
this.fid2Object.put(FID,FeatureCollectionTools.getFeatureFromCollection(this.featureList, fid));
}
return (Feature)this.fid2Object.get(FID);
}
/**
* Get the feature with the specified ID from the List
*@param features array with Feature objects
*@param fid the feature ID we are looking for
*@return feature with specified FID if exists, else null
*/
public static Feature getFeatureFromCollection( List<Feature> features, int fid ){
return FeatureCollectionTools.getFeatureFromCollection((Feature[])features.toArray(new Feature[0]), fid);
}
/**
* Get the feature with the specified ID from the array
*@param features array with Feature objects
*@param fid the feature ID we are looking for
*@return feature with specified FID if exists, else null
*/
public static Feature getFeatureFromCollection( Feature[] features, int fid ){
Feature feat;
for ( int i=0; i < features.length; i++ ){
feat = features[i];
if (feat.getID() == fid){
return feat;
}
}
return null;
}
/**
* deep copies a the FeatureSchema, the Features and their Geometries
* @param fc the FeatureCollection to copy
* @return the new copied FeatureCollection
*/
public final static PirolFeatureCollection cloneFeatureCollection(FeatureCollection fc){
FeatureSchema clonedSchema = (FeatureSchema)fc.getFeatureSchema().clone();
FeatureDataset newFc = new FeatureDataset(clonedSchema);
Feature[] featuresToCopy = FeatureCollection2FeatureArray(fc);
Feature newFeat = null;
AttributeType attrType = null;
for (int i=0; i<featuresToCopy.length; i++){
newFeat = new BasicFeature(clonedSchema);
for (int attr=0; attr<clonedSchema.getAttributeCount(); attr++){
attrType = clonedSchema.getAttributeType(attr);
/**
* FIX for Null Values.
* Added by mweller 24.07.2007
*/
if(featuresToCopy[i].getAttribute(attr) != null){
if (attrType.equals(AttributeType.DOUBLE)){
newFeat.setAttribute(attr, new Double(((Double)featuresToCopy[i].getAttribute(attr)).doubleValue()) );
} else if (attrType.equals(AttributeType.INTEGER)){
newFeat.setAttribute(attr, new Integer(((Integer)featuresToCopy[i].getAttribute(attr)).intValue()) );
} else if (attrType.equals(AttributeType.STRING)){
newFeat.setAttribute(attr, ((String)featuresToCopy[i].getAttribute(attr)).trim() );
} else if (attrType.equals(AttributeType.GEOMETRY)){
newFeat.setAttribute(attr, ((Geometry)featuresToCopy[i].getAttribute(attr)).clone() );
} else if (attrType.equals(AttributeType.DATE)){
newFeat.setAttribute(attr, ((Date)featuresToCopy[i].getAttribute(attr)).clone() );
} else if (attrType.equals(AttributeType.OBJECT)){
logger.printError("not implemented!");
newFeat.setAttribute(attr, (featuresToCopy[i].getAttribute(attr)) );
}
}
else{
newFeat.setAttribute(attr, null );
}
}
newFc.add(newFeat);
}
return MetaInformationHandler.createPirolFeatureCollection(newFc);
}
/**
* Get the feature with the specified ID from the FeatureCollection
*@param features array with Feature objects
*@param fid the feature ID we are looking for
*@return feature with specified FID if exists, else null
*/
public static Feature getFeatureFromCollection( FeatureCollection features, int fid ){
return FeatureCollectionTools.getFeatureFromCollection(features.getFeatures(), fid);
}
/**
*@param features list of features to calculate the mean for
*@param attr name of attribute to calculate the mean for
*@return the mean (double because a mean of integers will very likely be a double)
*/
public static double getAritmeticMiddleForAttribute( Feature[] features, String attr ){
if (features == null || features.length==0) return Double.NaN;
Feature f = features[0];
FeatureSchema fs = f.getSchema();
int attrInd = fs.getAttributeIndex(attr);
return FeatureCollectionTools.getAritmeticMiddleForAttribute( features, attrInd );
}
/**
* Method to calculate means (or modes) for the attributes given. If means or modes
* are calulated depends on the attribute type of each given attribute.
* This method is hopefully faster, than calculation each mean (mode) in an extra loop,
* if there are more means (modes) to calculate than one...
*@param features list of features to calculate mean/modes for
*@param attrs array of attribute names to calculate mean/modes for
*@return array of objects, representing means (or modes) --> e.g. Double, Integer or String objects.
*/
public static Object[] getMeanOrModeForAttributes( Feature[] features, String[] attrs ){
if (features==null || features.length==0) return null;
int numVals = features.length;
int numAttrs = attrs.length;
FeatureSchema fs = features[0].getSchema();
Object[] meansOrModes = new Object[numAttrs];
AttributeType[] ats = new AttributeType[numAttrs];
Object[] sumsOrMaps = new Object[numAttrs];
boolean[] atIsNumeric = new boolean[numAttrs];
for (int i=0; i<numAttrs; i++){
ats[i] = fs.getAttributeType(attrs[i]);
atIsNumeric[i] = FeatureCollectionTools.isAttributeTypeNumeric(ats[i]);
if (atIsNumeric[i]){
sumsOrMaps[i] = new Double(0);
} else {
// Map; no. of max. occurances; modus value
sumsOrMaps[i] = new Object[]{ new HashMap(), new Integer(0), null };
}
}
Feature currFeat;
Double sum;
Object value, modus;
Map<Object,Object> map;
Feature[] featArray = features;
Integer maxOcc;
int occ = 0;
for (int i=0; i<featArray.length; i++){
currFeat = featArray[i];
for (int j=0; j<numAttrs; j++){
if (currFeat.getAttribute(attrs[j]) == null){
// value is skipped
FeatureCollectionTools.logger.printMinorError("skipped a value (NULL), when calculating mean for " + attrs[j]);
}
else if (atIsNumeric[j]){
sum = ((Double)sumsOrMaps[j]);
sumsOrMaps[j] = new Double(sum.doubleValue() + ObjectComparator.getDoubleValue(currFeat.getAttribute(attrs[j])));
}
else {
value = currFeat.getAttribute(attrs[j]);
if (value.getClass().equals(String.class)){
((String)value).trim();
}
map = (Map)((Object[])sumsOrMaps[j])[0];
maxOcc = (Integer)((Object[])sumsOrMaps[j])[1];
modus = ((Object[])sumsOrMaps[j])[2];
if ( map.containsKey(value) ){
occ = ((Integer)map.get(value)).intValue();
occ += 1;
map.remove(value);
} else {
occ = 1;
}
map.put(value,new Integer(occ));
if (occ > maxOcc.intValue()){
maxOcc = new Integer(occ);
modus = value;
}
sumsOrMaps[j] = new Object[]{map,maxOcc,modus};
}
}
}
for ( int i=0; i<meansOrModes.length; i++ ){
if (atIsNumeric[i]){
meansOrModes[i] = new Double( ((Double)sumsOrMaps[i]).doubleValue()/((double)numVals) );
} else {
meansOrModes[i] = ((Object[])sumsOrMaps[i])[2];
}
}
return meansOrModes;
}
/**
* Creates an envelope object for the features in the given array. This is an easy way
* to get an envelope for a subset of a layer's features.
*@param features array of Feature object to create an envelope for
*@return the envelope containing the given features
*/
public static Envelope getEnvelopeForFeatures(Feature[] features){
Envelope env = null;
Feature feat;
for (int i=0; i<features.length; i++){
feat = features[i];
if (env==null){
env = new Envelope(feat.getGeometry().getCoordinate());
} else {
env.expandToInclude(feat.getGeometry().getCoordinate());
}
}
return env;
}
/**
*@param features list of features to calculate the mean for
*@param attr index of attribute to calculate the mean for
*@return the mean (double because a mean of integers will very likely be a double)
*/
public static double getAritmeticMiddleForAttribute( List<Feature> features, int attr ){
return FeatureCollectionTools.getAritmeticMiddleForAttribute((Feature[])features.toArray(new Feature[0]), attr);
}
/**
*@param featArray array of features to calculate the mean for
*@param attr index of attribute to calculate the mean for
*@return the mean (double because a mean of integers will very likely be a double)
*/
public static double getAritmeticMiddleForAttribute( Feature[] featArray, int attr ){
double sumVals = 0;
int numVals = 0;
if (featArray.length==0) {
logger.printWarning("no features in list - return value will be NAN!");
return Double.NaN;
}
Feature f = featArray[0];
FeatureSchema fs = f.getSchema();
if ( FeatureCollectionTools.isAttributeTypeNumeric(fs.getAttributeType(attr)) ){
//Iterator iter = features.iterator();
Feature feat;
double value;
for (int i=0; i<featArray.length; i++){
feat = featArray[i];
value = ObjectComparator.getDoubleValue(feat.getAttribute(attr));
sumVals += value;
numVals ++;
}
}
return (sumVals/(double)numVals);
}
/**
* Calculates the center of mass for the gives features' geometries.
* It's like getCentroid(), but puts out one center of mass for N features instead of N centers for N features.
*@param features the features
*@return the point, representing the center of mass
*/
public static Geometry getCenterOfMass( Feature[] features ){
double sumX = 0, sumY = 0;
GeometryFactory gf = new GeometryFactory();
if (features==null || features.length==0) return null;
Feature feat;
for (int i=0; i<features.length; i++){
feat = features[i];
sumX += feat.getGeometry().getCoordinate().x;
sumY += feat.getGeometry().getCoordinate().y;
}
double newX = sumX / (double)features.length;
double newY = sumY / (double)features.length;
return gf.createPoint(new Coordinate(newX, newY));
}
/**
*@param features list of features
*@param attr name of attribute
*@return number of differnt values in the given features attributes or -1 if an error occurs
*/
public static Set getSetOfDifferentAttributeValues( Feature[] features, String attr ){
Feature f = features[0];
FeatureSchema fs = f.getSchema();
int attrInd = fs.getAttributeIndex(attr);
return FeatureCollectionTools.getSetOfDifferentAttributeValues( features, attrInd );
}
/**
*@param features list of features
*@param attr index of attribute
*@return number of differnt values in the given features attributes or -1 if an error occurs
*/
public static Set<Object> getSetOfDifferentAttributeValues( Feature[] features, int attr ){
Feature[] featArray = features;
int numFeats = featArray.length;
HashSet<Object> differentValues = new HashSet<Object>();
Object val;
for (int i=numFeats-1; i>=0; i--){
val = featArray[i].getAttribute(attr);
//if (!differentValues.contains(val))
differentValues.add(val);
}
return differentValues;
}
/**
*@param features list of features
*@param attr name of attribute
*@return number of differnt values in the given features attributes or -1 if an error occurs
*/
public static int getNumOfDifferentAttributeValues( Feature[] features, String attr ){
Feature f = features[0];
FeatureSchema fs = f.getSchema();
int attrInd = fs.getAttributeIndex(attr);
return FeatureCollectionTools.getNumOfDifferentAttributeValues( features, attrInd );
}
/**
*@param features list of features
*@param attr index of attribute
*@return number of differnt values in the given features attributes or -1 if an error occurs
*/
public static int getNumOfDifferentAttributeValues( Feature[] features, int attr ){
return FeatureCollectionTools.getSetOfDifferentAttributeValues(features,attr).size();
}
/**
*@param features list of features to calculate the mode for
*@param attr name of attribute to calculate the mode for
*@return the mode
*/
public static Object getModusForAttribute( Feature[] features, String attr ){
Feature f = features[0];
FeatureSchema fs = f.getSchema();
int attrInd = fs.getAttributeIndex(attr);
return FeatureCollectionTools.getModusForAttribute( features, attrInd );
}
/**
* Counts the number of appearances of each value of the given attributes within the given features.
* (Somewhat like a histogram for each given attribute, but each histogram class is just a single value.)
* @param features array of Features to count value appearances for
* @param attrs array of attribute indices of attributes to count value appearances for
* @return Array of mappings of values to number of appearances
*/
public final static HashMap<Object,Integer>[] getValueAppearancesCount( Feature[] features, int[] attrs ){
HashMap<Object,Integer>[] value2NumAppearanceMaps = new HashMap[attrs.length];
for (int i=0; i<attrs.length; i++){
value2NumAppearanceMaps[i] = new HashMap<Object,Integer>();
}
Feature feat;
Object value;
for (int i=0; i<features.length; i++){
feat = features[i];
for (int attInd=0; attInd<attrs.length; attInd++){
value = feat.getAttribute(attrs[attInd]);
if (!value2NumAppearanceMaps[attInd].containsKey(value)){
value2NumAppearanceMaps[attInd].put(value, 1);
} else {
value2NumAppearanceMaps[attInd].put(value, value2NumAppearanceMaps[attInd].get(value).intValue()+1);
}
}
}
return value2NumAppearanceMaps;
}
/**
*@param features list of features to calculate the mode for
*@param attr index of attribute to calculate the mode for
*@return the mode
*/
public static Object getModusForAttribute( Feature[] features, int attr ){
HashMap<Object,Integer> map = new HashMap<Object,Integer>();
Object modus = null;
int maxNr = 0;
if (features==null || features.length==0) return null;
Feature[] featArray = features;
Feature feat;
Object value;
int occ, numFeats = featArray.length;
for (int i=0; i<numFeats; i++){
feat = featArray[i];
value = feat.getAttribute(attr);
if (value.getClass().getName().equals(String.class.getName())){
((String)value).trim();
}
if ( map.containsKey(value) ){
occ = ((Integer)map.get(value)).intValue();
occ += 1;
map.remove(value);
} else {
occ = 1;
}
map.put(value,new Integer(occ));
if (occ > maxNr){
maxNr = occ;
modus = value;
}
}
return modus;
}
/**
* deletes the given features from the map. It thereby creates an EditTransaction that
* enables the user to undo the deletion.
*@param features features to be deleted
*@param context curr. PlugIn context
*/
public static void deleteFeatures( List<Feature> features, PlugInContext context ){
Map layer2FeatList = LayerTools.getLayer2FeatureMap(features, context);
Layer[] layersWithFeatures = (Layer[])layer2FeatList.keySet().toArray(new Layer[0]);
Feature[] selFeatsOfLayer;
for ( int i=0; i<layersWithFeatures.length; i++ ){
EditTransaction edtr = new EditTransaction(new ArrayList(), "delete features", layersWithFeatures[i], true, true, context.getLayerViewPanel());
selFeatsOfLayer = (Feature[])((List)layer2FeatList.get(layersWithFeatures[i])).toArray(new Feature[0]);
for (int j=0; j<selFeatsOfLayer.length; j++){
edtr.deleteFeature(selFeatsOfLayer[j]);
}
edtr.commit();
edtr.clearEnvelopeCaches();
}
}
/**
* "deep copys" the given Feature
*@param feat the feature to be copied
*@return copy of the feature
*/
public static Feature copyFeature( Feature feat ){
Feature newFeat = new BasicFeature(feat.getSchema());
int numAttr = feat.getSchema().getAttributeCount();
for ( int i=0; i<numAttr; i++ ){
newFeat.setAttribute( feat.getSchema().getAttributeName(i), feat.getAttribute(i) );
}
newFeat.setGeometry((Geometry)feat.getGeometry());
return newFeat;
}
/**
* "deep copys" the given Feature and thereby sets the given feature schema.
* The new FeatureSchema should have the same attribute names for copying.
* The new FeatureSchmema can have less or more attributes.
*@param feat the feature to be copied
*@param newFs the new feature schema
*@return copy of the feature
*/
public static Feature copyFeatureAndSetFeatureSchema( Feature feat, FeatureSchema newFs ){
feat = feat.clone(true);
FeatureSchema fs = feat.getSchema();
Feature newFeat = new BasicFeature(newFs);
int numAttr = feat.getSchema().getAttributeCount();
for ( int i=0; i<numAttr; i++ ){
String attrName = fs.getAttributeName(i);
if (newFs.hasAttribute(attrName)){
newFeat.setAttribute( fs.getAttributeName(i), feat.getAttribute(fs.getAttributeName(i)) );
}
}
newFeat.setGeometry((Geometry)feat.getGeometry());
return newFeat;
}
public String getUniqueAttributeName(String attr){
return FeatureCollectionTools.getUniqueAttributeName( this.fc, attr );
}
public static String getUniqueAttributeName(FeatureCollection fc, String attr){
FeatureSchema fs = fc.getFeatureSchema();
String newName = new String(attr);
String suffix = "";
for ( int i=2; FeatureCollectionTools.attributeExistsInSchema(fs, newName+suffix); i++ ){
suffix = " ("+i+")";
}
return newName+suffix;
}
protected static boolean attributeExistsInSchema(FeatureSchema fs, String attr){
try {
int index = fs.getAttributeIndex(attr);
return (index>=0);
} catch (RuntimeException e) {
return false;
}
}
public PirolFeatureCollection addAttributeToFeatureCollection( String attr, AttributeType at, Object defaultVal ){
if (this.fc != null)
return FeatureCollectionTools.addAttributeToFeatureCollection( this.fc, attr, at, defaultVal, true );
return null;
}
public static boolean isAttributeTypeNumeric( AttributeType at ){
return ( at.equals(AttributeType.DOUBLE) || at.equals(AttributeType.INTEGER) );
}
/**
* Method to apply a given formula to the given, new attribute of the given featureCollection
*@param oldFc old FeatureCollection that will be replaced by the new one
*@param attrInfo information for the new attribute
*@param formula the parsed formula
*@return FeatureCollection containing the new attribute
*/
public static PirolFeatureCollection applyFormulaToFeatureCollection( FeatureCollection oldFc, AttributeInfo attrInfo, FormulaValue formula, boolean clearOldFeatureCollection ){
PirolFeatureCollection newFc = FeatureCollectionTools.addAttributeToFeatureCollection( oldFc, attrInfo, clearOldFeatureCollection );
Feature[] features = (Feature[])newFc.getFeatures().toArray(new Feature[0]);
Feature feat;
int numFeats = features.length, attrInd = newFc.getFeatureSchema().getAttributeIndex(attrInfo.getUniqueAttributeName());
for (int i=0; i<numFeats; i++){
feat = features[i];
feat.setAttribute(attrInd, new Double(formula.getValue(feat)));
if (i%500 == 0)
logger.printDebug("done: " + i);
}
return newFc;
}
public static PirolFeatureCollection addAttributeToFeatureCollection( FeatureCollection fc, AttributeInfo attrInfo ){
return FeatureCollectionTools.addAttributeToFeatureCollection(fc, attrInfo.getUniqueAttributeName(), attrInfo.getAttributeType(), attrInfo.getNullValue(), true);
}
public static PirolFeatureCollection addAttributeToFeatureCollection( FeatureCollection fc, AttributeInfo attrInfo, boolean clearOldFeatureCollection ){
return FeatureCollectionTools.addAttributeToFeatureCollection(fc, attrInfo.getUniqueAttributeName(), attrInfo.getAttributeType(), attrInfo.getNullValue(), clearOldFeatureCollection);
}
/**
* Method to add a new attribute to an existing FeatureCollection. Since there is no "legal" way to
* add an attribute to a FeatureCollection, this method creates (and returns) a new FeatureCollection with a new
* FeatureSchema to do this operation. If a layer is to be manipulated the new FeatureCollection has to be set
* as THE FeatureCollection for the layer...
*@param fc the old feature collection
*@param attr name of the new attribute
*@param at type of the new attribute
*@param defaultVal the initial value for the attribute, since we do not want NULL values in the attribute table
*@param clearOldFeatureCollection if true the old feature collection will be erased to save RAM
*@return new FeatureCollection with a new FeatureSchema including the new attribute
*/
public static PirolFeatureCollection addAttributeToFeatureCollection( FeatureCollection fc, String attr, AttributeType at, Object defaultVal, boolean clearOldFeatureCollection ){
FeatureSchema fs = (FeatureSchema)fc.getFeatureSchema().clone();
fs.addAttribute(attr,at);
MetaInformationHandler mih = new MetaInformationHandler(MetaInformationHandler.createPirolFeatureCollection(fc));
PirolFeatureCollection newFc = MetaInformationHandler.createPirolFeatureCollection(new FeatureDataset(fs));
if (mih.containsMetaInformation()){
newFc.setMetaInformation(mih.getExistentMetaInformationMap());
}
mih = null;
Feature feat, newFeat;
if (at.equals(AttributeType.INTEGER)){
if (Double.class.isInstance(defaultVal)){
defaultVal = new Integer( ((Double)defaultVal).intValue() );
}
}
Feature[] featArray = (Feature[])fc.getFeatures().toArray(new Feature[0]);
if (clearOldFeatureCollection)
fc.clear();
int featuresDone = 0;
for ( int i=featArray.length-1; i>=0; i--, featuresDone++ ){
feat = featArray[i];
newFeat = FeatureCollectionTools.copyFeatureAndSetFeatureSchema(feat,fs);
newFeat.setAttribute(attr, defaultVal);
newFc.add(newFeat);
if (i%10000 == 0 && i!=0){
featArray = (Feature[])FeatureCollectionTools.resizeArray(featArray, featArray.length - featuresDone);
featuresDone = 0;
if (i%50000 == 0){
System.gc();
}
}
}
featArray = null;
return newFc;
}
/**
* Method to add a new attribute to an existing FeatureCollection. Since there is no "legal" way to
* add an attribute to a FeatureCollection, this method creates (and returns) a new FeatureCollection with a new
* FeatureSchema to do this operation. If a layer is to be manipulated the new FeatureCollection has to be set
* as THE FeatureCollection for the layer...
*@param fc the old feature collection
*@param attr name of the new attribute
*@param at type of the new attribute
*@param defaultVal the initial value for the attribute, since we do not want NULL values in the attribute table
*@return new FeatureCollection with a new FeatureSchema including the new attribute
*/
public static PirolFeatureCollection addAttributeToFeatureCollection( FeatureCollection fc, String attr, AttributeType at, Object defaultVal ){
return FeatureCollectionTools.addAttributeToFeatureCollection(fc, attr, at, defaultVal, true);
}
/**
* Adds multiple attributes to the FeatureCollection
*@param attributeInfos list containing the attributes (as AttributeInfo objects) to be added
*@return a new FeatureCollection containing the old and the new attributes
*
*@see AttributeInfo
*/
public PirolFeatureCollection addAttributesToFeatureCollection( List attributeInfos ){
return FeatureCollectionTools.addAttributesToFeatureCollection(this.fc, attributeInfos);
}
/**
* Adds multiple attributes to a FeatureCollection
*@param fc the source feature collection
*@param attributeInfos list containing the attributes (as AttributeInfo objects) to be added
*@return a new FeatureCollection containing the old and the new attributes
*
*@see AttributeInfo
*/
public static PirolFeatureCollection addAttributesToFeatureCollection( FeatureCollection fc, List<AttributeInfo> attributeInfos ){
return FeatureCollectionTools.addAttributesToFeatureCollection(fc, (AttributeInfo[])attributeInfos.toArray(new AttributeInfo[0]));
}
public static PirolFeatureCollection addAttributesToFeatureCollection( FeatureCollection fc, AttributeInfo[] attributeInfos ){
return FeatureCollectionTools.addAttributesToFeatureCollection(fc, attributeInfos, true);
}
/**
* Adds multiple attributes to a FeatureCollection
*@param fc the source feature collection
*@param attributeInfos array containing the attributes to be added
*@param clearOriginalFeatureCollection set true, if you want to save RAM by
*clearing the original FeatureCollection, false if you still want to use it.
*@return a new FeatureCollection containing the old and the new attributes
*/
public static PirolFeatureCollection addAttributesToFeatureCollection( FeatureCollection fc, AttributeInfo[] attributeInfos, boolean clearOriginalFeatureCollection ){
FeatureSchema fs = (FeatureSchema)fc.getFeatureSchema().clone();
MetaInformationHandler mih = new MetaInformationHandler(MetaInformationHandler.createPirolFeatureCollection(fc));
AttributeInfo attrInfo;
for (int i=0; i<attributeInfos.length; i++){
attrInfo = attributeInfos[i];
fs.addAttribute(attrInfo.getUniqueAttributeName(), attrInfo.getAttributeType());
}
PirolFeatureCollection newFc = MetaInformationHandler.createPirolFeatureCollection(new FeatureDataset(fs));
if (mih.containsMetaInformation()){
newFc.setMetaInformation(mih.getExistentMetaInformationMap());
}
mih = null;
Feature feat, newFeat;
Feature[] featArray = (Feature[])fc.getFeatures().toArray(new Feature[0]);
if (clearOriginalFeatureCollection)
fc.clear();
int featuresDone = 0;
for ( int i=featArray.length-1; i>=0; i--, featuresDone++ ){
feat = featArray[i];
newFeat = FeatureCollectionTools.copyFeatureAndSetFeatureSchema(feat,fs);
for (int j=0; j<attributeInfos.length; j++){
attrInfo = attributeInfos[j];
newFeat.setAttribute(attrInfo.getUniqueAttributeName(), attrInfo.getNullValue());
}
newFc.add(newFeat);
if (i%10000 == 0 && i!=0){
featArray = (Feature[])FeatureCollectionTools.resizeArray(featArray, featArray.length - featuresDone);
featuresDone = 0;
if (i%50000 == 0){
System.gc();
}
logger.printDebug("adding attribute, features left to do " + i);
}
}
featArray = null;
return newFc;
}
/**
* Reallocates an array with a new size, and copies the contents
* of the old array to the new array.
* @param oldArray the old array, to be reallocated.
* @param newSize the new array size.
* @return A new array with the same contents.
*/
public static Object resizeArray (Object oldArray, int newSize) {
int oldSize = java.lang.reflect.Array.getLength(oldArray);
Class elementType = oldArray.getClass().getComponentType();
Object newArray = java.lang.reflect.Array.newInstance(
elementType,newSize);
int preserveLength = Math.min(oldSize,newSize);
if (preserveLength > 0)
System.arraycopy (oldArray,0,newArray,0,preserveLength);
return newArray;
}
public static double[] getMinMaxAttributeValue( Feature[] featArray, FeatureSchema fs, String attr ){
double[] minmax = new double[]{ Double.MAX_VALUE, -Double.MAX_VALUE };
if (fs.getAttributeType(attr) == AttributeType.INTEGER || fs.getAttributeType(attr) == AttributeType.DOUBLE){
Feature feat;
double value;
for ( int i=featArray.length-1; i>=0; i-- ){
feat = featArray[i];
if (feat.getAttribute(attr) != null){
value = ObjectComparator.getDoubleValue(feat.getAttribute(attr));
if (value<minmax[0]){
minmax[0] = value;
}
if (value>minmax[1]){
minmax[1] = value;
}
} else {
FeatureCollectionTools.logger.printMinorError("skipped value (NULL), when checking min./max. values for Attribute " + attr);
}
}
}
return minmax;
}
public static double getSumAttributeValue( Feature[] featArray, FeatureSchema fs, String attr ){
double sum = 0;
if (fs.getAttributeType(attr) == AttributeType.INTEGER || fs.getAttributeType(attr) == AttributeType.DOUBLE){
Feature feat;
double value;
for ( int i=featArray.length-1; i>=0; i-- ){
feat = featArray[i];
if (feat.getAttribute(attr) != null){
value = ObjectComparator.getDoubleValue(feat.getAttribute(attr));
sum = sum + value;
} else {
FeatureCollectionTools.logger.printMinorError("skipped value (NULL), when checking sum values for Attribute " + attr);
}
}
}
return sum;
}
public static double[] getMinMaxAttributeValue( FeatureCollection features, String attr ){
return FeatureCollectionTools.getMinMaxAttributeValue(FeatureCollectionTools.FeatureCollection2FeatureArray(features), features.getFeatureSchema(), attr);
}
/**
* Converts a given FeatureCollection into an array of Feature, that can - by far - be faster iterated.
*@param fc the feature Collection
*@return an array of the features of the feature collection
*/
public static Feature[] FeatureCollection2FeatureArray( FeatureCollection fc ){
return (Feature[])fc.getFeatures().toArray(new Feature[0]);
}
/**
* Converts a given list of features into an array of Feature, that can - by far - be faster iterated.
*@param features list of features
*@return an array of the features of the feature list
*/
public static Feature[] FeatureCollection2FeatureArray( List<Feature> features ){
return (Feature[])features.toArray(new Feature[0]);
}
/**
* Extracts all points from an input feature and returns them as list of point features.
* Note: for closed Geometry objects the start and end point are extracted -
* i.e. the points may be overlap.
* @param f
* @param accountForRings doesn't add a point for the last coordinate if the geometry is a ring (i.e. first point equals last point)
* @return list of point features
*/
public static ArrayList<Feature> convertToPointFeature(Feature f, boolean accountForRings){
GeometryFactory gf = new GeometryFactory();
ArrayList<Feature> points = new ArrayList<Feature>();
Coordinate[] coords = f.getGeometry().getCoordinates();
int lastCoord = coords.length;
if (accountForRings){
if(coords[lastCoord-1].equals2D(coords[0])){
lastCoord = coords.length-1;
}
}
for (int i=0; i < lastCoord; i++) {
Point pt = gf.createPoint(coords[i]);
Feature fNew = copyFeature(f);
fNew.setGeometry(pt);
points.add(fNew);
}
return points;
}
/**
* Sorts features according to unique attribute values into different lists (on list for each unique attribute).
* TODO: enable sorting for String values
* @param features
* @param idAttribute (must be Double or Integer)
* @return Object[0]: an Array of ArrayLists containing features, Object[1]: an Array of int values containing the unique values
* used for sorting. Can return null if wrong AttributeType. E.g. use<br>
* Object[] myReturns = FeatureCollectionTools.sortFeaturesIntoListsByAttributeValue(pointFeatures, idAttribute); <br>
* individualPts = (ArrayList[])myReturns[0]; <br>
* int[] uniqueValues = (int[])myReturns[1]; <br>
* Note that the order in which the lists are returned is not sorted!
*/
public static Object[] sortFeaturesIntoListsByAttributeValue(FeatureCollection features,
String idAttribute) {
Object[] returnObject = new Object[2];
ArrayList<Feature>[] objectsInClass = null;
AttributeType atype = features.getFeatureSchema().getAttributeType(idAttribute);
//-- only proceed if Id attribute has correct datatype
if ((atype == AttributeType.DOUBLE) || (atype == AttributeType.INTEGER)){
Feature[] fArray = FeatureCollectionTools.FeatureCollection2FeatureArray(features);
//-- retrieve the set of different individuals
Set individuals = FeatureCollectionTools.getSetOfDifferentAttributeValues(fArray, idAttribute);
int[] individualValues = new int[individuals.size()];
//System.out.print("unique values: ");
int i=0;
for (Iterator iterator = individuals.iterator(); iterator.hasNext();) {
Object object = (Object) iterator.next();
int idval = -9998;
if (atype == AttributeType.DOUBLE){
idval = ((Double)object).intValue();
}
else if (atype == AttributeType.INTEGER){
idval = ((Integer)object).intValue();
}
individualValues[i] = idval;
i++;
//System.out.print(idval + ", ");
}
System.out.println();
returnObject[1] = individualValues;
objectsInClass = new ArrayList[individuals.size()];
//-- create the lists
for (int j = 0; j < objectsInClass.length; j++) {
objectsInClass[j] = new ArrayList<Feature>();
}
//-- sort all objects f
for (Iterator iterator = features.iterator(); iterator.hasNext();) {
Feature f = (Feature) iterator.next();
int id = -9999;
if (atype == AttributeType.DOUBLE){
Double val = (Double)f.getAttribute(idAttribute);
id = val.intValue();
}
else if (atype == AttributeType.INTEGER){
Integer val = (Integer)f.getAttribute(idAttribute);
id = val.intValue();
}
//-- search if the ID fits to one of the values
boolean found = false; int j =0;
while(found == false){
if(id == individualValues[j]){
objectsInClass[j].add(f.clone(true));
found = true;
}
//-- stop if all values have been compared
j++;
if((j == individualValues.length) && (found == false)){
found = true;
System.out.println("sortFeaturesIntoListsByAttributeValue: could not assign value to class; value: " + id);
}
}
}
}
else{
System.out.println("sortFeaturesIntoListsByAttributeValue: id AttributeType neither double nor int");
}
returnObject[0] = objectsInClass;
return returnObject;
}
/**
* gets the value of an attribute; it checks if the attribute is of double or int type, otherwise NaN is returned.
* @param f
* @param attributeName
* @return value as double
*/
public static double getNumericalAttributeValue(Feature f, String attributeName){
AttributeType atype = f.getSchema().getAttributeType(attributeName);
if ((atype == AttributeType.DOUBLE) || (atype == AttributeType.INTEGER)){
double value = 0;
if (atype == AttributeType.DOUBLE){
Double val = (Double)f.getAttribute(attributeName);
value = val.doubleValue();
}
else if(atype == AttributeType.INTEGER){
Integer val = (Integer)f.getAttribute(attributeName);
value = val.doubleValue();
}
return value;
}
else{
return Double.NaN;
}
}
/**
* Sorts a list of features according to the values of a attribute. If values are similar the feature
* are ordered the same way as in the input list.
* TODO: this method has been tested only briefly (not exhaustively)
* @param features
* @param attributeNameForSorting attribute needs to be either Integer or Double
* @return list of sorted features; the smallest value will be first in the list.
*/
public static ArrayList<Feature> sortFeatureListByAttributeBeginWithSmallest(
List<Feature> features, String attributeNameForSorting) {
ArrayList<Feature> sortedFeatureList = new ArrayList<Feature>();
ArrayList<Double> sortedValueList = new ArrayList<Double>(); // used to speed up sorting so attribute values
// need not to be derived every time
int i = 0;
boolean first = true;
for (Iterator iterator = features.iterator(); iterator.hasNext();) {
Feature f = (Feature) iterator.next();
if(first){
//-- just add the first feature
sortedFeatureList.add(f.clone(true));
double valuef = FeatureCollectionTools.getNumericalAttributeValue(f, attributeNameForSorting);
sortedValueList.add(valuef);
first = false;
}
else{
//-- get value
double valuef = FeatureCollectionTools.getNumericalAttributeValue(f, attributeNameForSorting);
//-- parse the existing list
boolean isLarger = true;
int j = 0;
while(isLarger){
//-- to speed up things (i.e. avoid cumbersome value derivation), use the sortedValueList instead
double valueFtemp = sortedValueList.get(j);
if(valuef >= valueFtemp){
//-- everything is fine, keep searching
}
else{
//-- valuef is now smaller, squeeze it in right before ftemp (i.e. the same position)
sortedFeatureList.add(j,f.clone(true));
sortedValueList.add(j,valuef);
//-- end the search, and sort the next item from "features"
isLarger = false;
}
//-- if we are already at the end, then we need to insert the feature too
if ( j+1 == sortedFeatureList.size()){
sortedFeatureList.add(f.clone(true));
sortedValueList.add(valuef);
//-- end the search, and sort the next item from "features"
isLarger = false;
}
j++;
}//-- end while
}
i++;
}//-- end for
return sortedFeatureList;
}
}