All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geotools.feature.visitor.MedianVisitor Maven / Gradle / Ivy

Go to download

The main module contains the GeoTools public interfaces that are used by other GeoTools modules (and GeoTools applications). Where possible we make use industry standard terms as provided by OGC and ISO standards. The formal GeoTools public api consists of gt-metadata, jts and the gt-main module. The main module contains the default implementations that are available provided to other GeoTools modules using our factory system. Factories are obtained from an appropriate FactoryFinder, giving applications a chance configure the factory used using the Factory Hints facilities. FilterFactory ff = CommonFactoryFinder.getFilterFactory(); Expression expr = ff.add( expression1, expression2 ); If you find yourself using implementation specific classes chances are you doing it wrong: Expression expr = new AddImpl( expression1, expressiom2 );

The newest version!
/*
 *    GeoTools - The Open Source Java GIS Toolkit
 *    http://geotools.org
 *
 *    (C) 2005-2008, 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.feature.visitor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.filter.IllegalFilterException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;

/**
 * Calculates the median of an attribute in all features of a collection
 *
 * @author Cory Horner, Refractions
 * @since 2.2.M2
 */
public class MedianVisitor implements FeatureCalc, FeatureAttributeVisitor {
    private Expression expr;
    private List list = new ArrayList();
    /**
     * This var is only used to store the median for optimized functions, where we don't have a
     * complete list, but just the answer instead (merging will be disabled until some cool code is
     * written to handle this). Only setValue(median) should write to this var. If this value is not
     * null, it takes priority over list.
     */
    private Object median = null;

    public MedianVisitor(String attributeTypeName) {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        expr = factory.property(attributeTypeName);
    }

    public MedianVisitor(int attributeTypeIndex, SimpleFeatureType type)
            throws IllegalFilterException {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        expr = factory.property(type.getDescriptor(attributeTypeIndex).getLocalName());
    }

    public MedianVisitor(String attrName, SimpleFeatureType type) throws IllegalFilterException {
        FilterFactory factory = CommonFactoryFinder.getFilterFactory(null);
        expr = factory.property(type.getDescriptor(attrName).getLocalName());
    }

    public MedianVisitor(Expression expr) throws IllegalFilterException {
        this.expr = expr;
    }

    public void init(SimpleFeatureCollection collection) {
        // do nothing
    }

    @Override
    public List getExpressions() {
        return Arrays.asList(expr);
    }

    @Override
    public Optional> getResultType(List inputTypes) {
        return CalcUtil.reflectInputTypes(1, inputTypes);
    }

    public void visit(SimpleFeature feature) {
        visit((org.opengis.feature.Feature) feature);
    }

    public void visit(org.opengis.feature.Feature feature) {
        /** Visitor function */
        Object result = expr.evaluate(feature);
        if (result == null) {
            return; // skip
        }

        if (result instanceof Comparable) {
            Comparable value = (Comparable) result;
            list.add(value);
        } else {
            throw new IllegalStateException("Expression is not comparable!");
        }
    }

    public Expression getExpression() {
        return expr;
    }

    /** Return the median of all features in the collection */
    public Object getMedian() {
        if (median != null) {
            // median was overwritten by an optimization
            return median;
        } else {
            // we're got a list of items, determine the median...
            Object newMedian = findMedian(list);
            if (newMedian == null) {
                throw new IllegalStateException("Must visit before median value is ready!");
            }
            return newMedian;
        }
    }

    /** Reset the stored information about the median. */
    public void reset() {
        this.list.clear();
        this.median = null;
    }

    public CalcResult getResult() {
        if (median != null) {
            // median was overwritten by an optimization
            return new MedianResult(median);
        } else if (list.size() < 1) {
            // no items in the list
            return CalcResult.NULL_RESULT;
        } else {
            // we have a list; create a CalcResult containing the list
            return new MedianResult(list);
        }
    }

    public void setValue(List list) {
        reset();
        this.list = list;
    }

    public void setValue(Comparable median) {
        reset();
        this.median = median;
    }

    public static class MedianResult extends AbstractCalcResult {
        private List list;
        /**
         * When an optimization is used, median will have a value and list will not. This var takes
         * priority over list.
         */
        private Object median;

        public MedianResult(List newList) {
            this.list = newList;
            this.median = null;
        }

        public MedianResult(Object median) {
            this.list = null;
            this.median = median;
        }

        public List getList() {
            return list;
        }

        public Object getValue() {
            if (median != null) {
                return median;
            } else {
                return findMedian(list);
            }
        }

        public boolean isCompatible(CalcResult targetResults) {
            // list each calculation result which can merge with this type of result
            if (targetResults instanceof MedianResult || targetResults == CalcResult.NULL_RESULT)
                return true;
            return false;
        }

        public boolean isOptimized() {
            if (median != null) return true;
            else return false;
        }

        public CalcResult merge(CalcResult resultsToAdd) {
            if (!isCompatible(resultsToAdd)) {
                throw new IllegalArgumentException("Parameter is not a compatible type");
            }

            if (resultsToAdd == CalcResult.NULL_RESULT) {
                return this;
            }

            if (resultsToAdd instanceof MedianResult) {
                MedianResult moreResults = (MedianResult) resultsToAdd;
                // ensure both MedianResults are NOT optimized
                if (isOptimized() || moreResults.isOptimized()) {
                    throw new IllegalArgumentException(
                            "Optimized median results cannot be merged.");
                }
                // merge away...
                List toAdd = (ArrayList) moreResults.getList();
                List newList = new ArrayList();
                // extract each item to an array, and convert to a common data type
                int size = list.size() + toAdd.size();
                Object[] values = new Object[size];
                int i;
                for (i = 0; i < list.size(); i++) values[i] = list.get(i);
                for (int j = 0; j < toAdd.size(); j++) values[i + j] = toAdd.get(j);
                Class bestClass = CalcUtil.bestClass(values);
                for (int k = 0; k < size; k++) {
                    if (values[k].getClass() != bestClass)
                        values[k] = CalcUtil.convert(values[k], bestClass);
                    newList.add((Comparable) values[k]);
                }
                return new MedianResult(newList);
            } else {
                throw new IllegalArgumentException(
                        "The CalcResults claim to be compatible, but the appropriate merge method has not been implemented.");
            }
        }
    }

    /**
     * Given a list, determines the median value and returns it. For numbers, the middle value is
     * returned, or the average of the two middle numbers if there are an even number of elements.
     * For non-numeric values (strings, etc) where the number of elements is even, a list containing
     * the two middle elements is returned.
     *
     * @param list an arraylist which is to be sorted and its median extracted
     * @return the median
     */
    private static Object findMedian(List list) {
        if (list.size() < 1) {
            return null;
        }
        Object median;
        Collections.sort(list);

        int index = -1;
        index = (int) list.size() / 2;

        if ((list.size() % 2) == 0) {
            // even number of elements, so we must average the 2 middle ones, or
            // return a list for non-numeric elements
            Object input1 = list.get(index - 1);
            Object input2 = list.get(index);

            if ((input1 instanceof Number) && (input2 instanceof Number)) {
                Number num1 = (Number) input1;
                Number num2 = (Number) input2;
                Number[] numbers = new Number[2];
                numbers[0] = num1;
                numbers[1] = num2;
                median = CalcUtil.average(numbers);
            } else { // NaN
                // return a list containing the two middle elements
                List newList = new ArrayList();
                newList.add(input1);
                newList.add(input2);
                median = newList;
            }
        } else {
            // an odd number of elements are in the list, so we simply return
            // the one in the middle, which we've already calculated the index
            // for.
            median = list.get(index);
        }
        return median;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy