Subversion Repositories splitter

Rev

Rev 163 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (c) 2009.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program 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
 * General Public License for more details.
 */


package uk.me.parabola.splitter;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Splits a density map into multiple areas, none of which
 * exceed the desired threshold.
 *
 * @author Chris Miller
 */

public class SplittableDensityArea implements SplittableArea {
        private DensityMap densities;

        public SplittableDensityArea(DensityMap densities) {
                this.densities = densities;
        }

        @Override
        public Area getBounds() {
                return densities.getBounds();
        }


        public double getAspectRatio() {
                Area bounds = densities.getBounds();
                int width1 = (int) (densities.getWidth() * Math.cos(Math.toRadians(Utils.toDegrees(bounds.getMinLat()))));
                int width2 = (int) (densities.getWidth() * Math.cos(Math.toRadians(Utils.toDegrees(bounds.getMaxLat()))));
                int width = Math.max(width1, width2);          
                int height = densities.getHeight();
                double ratio = ((double)width)/height;
                return ratio;
        }
       
       
        @Override
        public List<Area> split(int maxNodes) {
                if (densities == null || densities.getNodeCount() == 0)
                        return Collections.emptyList();

                Area bounds = densities.getBounds();
                if (densities.getNodeCount() <= maxNodes) {
                        System.out.println("Area " + bounds + " contains " + Utils.format(densities.getNodeCount())
                                        + " nodes. DONE!");
                        densities = null;
                        return Collections.singletonList(bounds);
                }

                if (densities.getWidth() < 4 && densities.getHeight() < 4) {
                        System.out.println("Area " + bounds + " contains " + Utils.format(densities.getNodeCount())
                                                        + " nodes but is already at the minimum size so can't be split further");
                        return Collections.singletonList(bounds);
                }

                // Decide whether to split vertically or horizontally and go ahead with the split


                SplittableDensityArea[] splitResult = null;

                Integer splitX = getSplitHoriz();
                Integer splitY = getSplitVert();
               
                // Try to split it based on dimension.
                if (getAspectRatio() <= 1.0 && densities.getHeight() >= 4 && splitY != null) {
                        splitResult = splitVert(splitY);
                }
                // Either the natural split is horizontal, or no good vertical split. Try horizontal.
                if (splitResult == null && densities.getWidth() >= 4 && splitX != null) {
                        splitResult = splitHoriz(splitX);
                }
                // If the natural horizontal split failed. Try vertical.
                if (getAspectRatio() > 1.0 && splitResult == null && densities.getHeight() >= 4 && splitY != null) {
                        splitResult = splitVert(splitY);
                }
                // No dice. Use this as-is.
                if (splitResult == null) {
                        System.out.println("Area " + bounds + " contains " + Utils.format(densities.getNodeCount())
                                        + " nodes but can't be split further");
                        return Collections.singletonList(bounds);
                }
                densities = null;
                return mixResults(
                                splitResult[0].split(maxNodes),
                                splitResult[1].split(maxNodes));               
        }

        /** Merge two result lists of regions */
        List<Area> mixResults(List<Area> a1, List<Area> a2) {
                List<Area> results = new ArrayList<Area>();
       
                Iterator<Area> i0 = a1.iterator();
                Iterator<Area> i1 = a2.iterator();

                while (i0.hasNext() && i1.hasNext()) {
                    results.add(i0.next());
                    results.add(i1.next());
                }

                while (i0.hasNext()) {
                    results.add(i0.next());
                }
                while (i1.hasNext()) {
                    results.add(i1.next());
                }
                Collections.reverse(results);
                return results;
        }

        /**
         * Split into left and right areas. Requires width >= 4 (so that we can have a even midpoint.
         */

        protected Integer getSplitHoriz() {
                long sum = 0, weightedSum = 0;

                for (int x = 0; x < densities.getWidth(); x++) {
                        for (int y = 0; y < densities.getHeight(); y++) {
                                int count = densities.getNodeCount(x, y);
                                sum += count;
                                weightedSum += (count * x);
                        }
                }
                return limit(0, densities.getWidth(), (int) (weightedSum / sum));
        }
               
        /** Get the actual split areas */
        protected SplittableDensityArea[] splitHoriz(int splitX) {
               
                Area bounds = densities.getBounds();
                int mid = bounds.getMinLong() + (splitX << densities.getShift());
                Area leftArea = new Area(bounds.getMinLat(), bounds.getMinLong(), bounds.getMaxLat(), mid);
                Area rightArea = new Area(bounds.getMinLat(), mid, bounds.getMaxLat(), bounds.getMaxLong());
                DensityMap left = densities.subset(leftArea);
                DensityMap right = densities.subset(rightArea);

                return new SplittableDensityArea[] {new SplittableDensityArea(left), new SplittableDensityArea(right)};
        }

        /**
         * Split into top and bottom areas. Requires height >= 4 (so that we can have a even midpoint.
         */

        protected Integer getSplitVert() {
                long sum = 0, weightedSum = 0;
                for (int y = 0; y < densities.getHeight(); y++) {
                        for (int x = 0; x < densities.getWidth(); x++) {
                                int count = densities.getNodeCount(x, y);
                                sum += count;
                                weightedSum += (count * y);
                        }
                }
                return limit(0, densities.getHeight(), (int) (weightedSum / sum));
        }

        /** Get the actual split areas */
        protected SplittableDensityArea[] splitVert(int splitY) {
               
                Area bounds = densities.getBounds();
                int mid = bounds.getMinLat() + (splitY << densities.getShift());
                Area bottomArea = new Area(bounds.getMinLat(), bounds.getMinLong(), mid, bounds.getMaxLong());
                Area topArea = new Area(mid, bounds.getMinLong(), bounds.getMaxLat(), bounds.getMaxLong());
                DensityMap bottom = densities.subset(bottomArea);
                DensityMap top = densities.subset(topArea);

                return new SplittableDensityArea[]{new SplittableDensityArea(bottom), new SplittableDensityArea(top)};
        }

        /** return calcOffset if it is in the middle three quantiles, use the first or last quantile otherwise. */
        private Integer limit(int first, int second, long calcOffset) {
                int mid = first + (int) calcOffset;
                int limitoff = (second - first) / 5;
                if (mid - first < limitoff)
                        mid = first + limitoff;
                else if (second - mid < limitoff)
                        mid = second - limitoff;

                if (mid % 2 != 0)
                        mid--;
                if (mid == first || mid == second)
                        return null;
               
                return mid;
        }
}