Subversion Repositories splitter

Rev

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

/*
 * Copyright (c) 2014, Gerd Petermann
 *
 * 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 it.unimi.dsi.fastutil.ints.IntArrayList;

import java.awt.Rectangle;

/**
         * This class implements a "view" on a rectangle covering a part
         * of a {@link DensityMap}.
         * It contains the sum of all nodes in this area and has methods to
         * help splitting it into smaller parts.
         *
         * It extends java.awt.Rectangle because that implements a useful
         * hashCode method.
         * We want to keep the memory footprint of this class small as
         * many instances are kept in maps.
         * @author GerdP
         *
         */

        class Tile extends Rectangle{
                /**
                 *
                 */

                private final EnhancedDensityMap densityInfo;
                public final long count;
//              int bestSplit;
               
                /**
                 * Create tile for whole density map.
                 * @param densityInfo
                 */

                public Tile(EnhancedDensityMap densityInfo) {
                        this(densityInfo, 0, 0, densityInfo.getDensityMap().getWidth(),
                                        densityInfo.getDensityMap().getHeight(),
                                        densityInfo.getNodeCount());
                }

                /**
                 * create a tile with unknown number of nodes
                 * @param r the rectangle
                 * @param densityInfo
                 */

                public Tile(EnhancedDensityMap densityInfo, Rectangle r) {
                        super(r);
                        if (r.x < 0 || r.y < 0
                                || r.x + r.width > densityInfo.getDensityMap().getWidth()
                                || r.y + r.height > densityInfo.getDensityMap().getHeight())
                        throw new IllegalArgumentException("Rectangle doesn't fit into density map");
                       
                        this.densityInfo = densityInfo;
                        count = calcCount();
                }

                /**
                 * create a tile with a known number of nodes
                 * @param densityInfo
                 * @param x
                 * @param y
                 * @param width
                 * @param height
                 * @param count caller must ensure that this value is correct. See also verify()
                 */

                private Tile(EnhancedDensityMap densityInfo, int x,int y, int width, int height, long count) {
                        super(x,y,width,height);
                        this.densityInfo = densityInfo;
                        this.count = count;
//                      if (!verify()){
//                              System.out.println(count + " <> " + calcCount());
//                              assert false;
//                      }
                }

                /**
                 * @return true if the saved count value is correct.
                 */

                public boolean verify(){
                        return (count == calcCount());
                }
               
                public IntArrayList genXTests(TileMetaInfo smi) {
                        int start = this.findValidStartX(smi);
                        int end = this.findValidEndX(smi);
                        return genTests(start, end);
                }

                public IntArrayList genYTests(TileMetaInfo smi) {
                        int start = this.findValidStartY(smi);
                        int end = this.findValidEndY(smi);
                        return genTests(start, end);
                }
               
                public static IntArrayList genTests(int start, int end) {
                        if (end-start < 0)
                                return new IntArrayList(1);
                        int mid = (start + end) / 2;
                        int toAdd = end-start+1;
                        IntArrayList list = new IntArrayList(toAdd);
                        for (int i = 0; i <= mid; i++){
                                int pos = mid + i;
                                if (pos >= start && pos <= end)
                                        list.add(pos);
                                if (list.size() >= toAdd)
                                        break;
                                if (i == 0)
                                        continue;
                                pos = mid - i;
                                if (pos >= start && pos <= end)
                                        list.add(pos);
                        }
                        return list;
                }



                /**
                 * calculate the numnber of nodes in this tile
                 * @return
                 */

                private long calcCount(){
                        long sum = 0;
                        for (int i=0;i<height;i++){
                                sum += getRowSum(i);
                        }
                        return sum;
                }
               
                /**
                 * Calculate the sum of all grid elements within a row
                 * @param row the row within the tile (0..height-1)
                 * @return
                 */

                public long getRowSum(int row) {
                        assert row >= 0 && row < height;
                        int mapRow = row + y;
                        long sum = 0;
                        int[] vector = densityInfo.getMapRow(mapRow);
                        if (vector != null){
                                int lastX = x + width;
                                for (int i = x; i < lastX; i++)
                                        sum += vector[i];
                        }
                        return sum;
                }
                private long getRowSum(int row, long []rowSums){
                        if (rowSums[row] < 0)
                                rowSums[row] = getRowSum(row);
                        return rowSums[row];
                }
               
                /**
                 * Calculate the sum of all grid elements within a column.
                 * @param col the column within the tile
                 * @return
                 */

                public long getColSum(int col) {
                        assert col >= 0 && col < width;
                        int mapCol = col + x;
                        long sum = 0;
                        int[] vector = densityInfo.getMapCol(mapCol);
                        if (vector != null){
                                int lastY = y + height;
                                for (int i = y; i < lastY; i++)
                                        sum += vector[i];
                        }
                        return sum;
                }
                private long getColSum(int col, long[] colSums){
                        if (colSums[col] < 0)
                                colSums[col] = getColSum(col);
                        return colSums[col];
                }

                /**
                 * Find first y so that sums of columns for 0-y is > count/2
                 * Update corresponding fields in smi.
                 *
                 * @param smi fields firstNonZeroX, horMidPos and horMidSum may be updated
                 * @return true if the above fields are usable
                 */

                public int findHorizontalMiddle(TileMetaInfo smi) {
                        if (count == 0 || width < 2)
                                smi.setHorMidPos(0);
                        else {
                                int start = (smi.getFirstNonZeroX() > 0) ? smi.getFirstNonZeroX() : 0;
                                long sum = 0;
                                long lastSum = 0;
                                long target = count/2;

                                for (int pos = start; pos <= width; pos++) {
                                        lastSum = sum;
                                        sum += getColSum(pos, smi.getColSums());
                                        if (sum == 0)
                                                continue;
                                        if (lastSum <= 0)
                                                smi.setFirstNonZeroX(pos);
                                        if (sum > target){
                                                if (sum - target < target - lastSum && pos + 1 < width){
                                                        smi.setHorMidPos(pos+1);
                                                        smi.setHorMidSum(sum);
                                                } else {
                                                        smi.setHorMidPos(pos);
                                                        smi.setHorMidSum(lastSum);
                                                }
                                                break;
                                        }
                                }
                        }
                        return smi.getHorMidPos();
                }

                /**
                 * Find first x so that sums of rows for 0-x is > count/2.
                 * Update corresponding fields in smi.
                 * @param smi fields firstNonZeroY, vertMidPos, and vertMidSum may be updated
                 * @return true if the above fields are usable
                 */

                public int findVerticalMiddle(TileMetaInfo smi) {
                        if (count == 0 || height < 2)
                                smi.setVertMidPos(0);
                        else {
                                long sum = 0;
                                long lastSum;
                                long target = count/2;
                                int start = (smi.getFirstNonZeroY() > 0) ? smi.getFirstNonZeroY() : 0;
                                for (int pos = start; pos <= height; pos++) {
                                        lastSum = sum;
                                        sum += getRowSum(pos, smi.getRowSums());
                                        if (sum == 0)
                                                continue;
                                        if (lastSum <= 0)
                                                smi.setFirstNonZeroY(pos);
                                        if (sum > target) {
                                                if (sum - target < target - lastSum && pos + 1 < height) {
                                                        smi.setVertMidPos(pos + 1);
                                                        smi.setVertMidSum(sum);
                                                } else {
                                                        smi.setVertMidPos(pos);
                                                        smi.setVertMidSum(lastSum);
                                                }
                                                break;
                                        }
                                }
                        }
                        return smi.getVertMidPos();
                }              

                /**
                 * Split at the given horizontal position.
                 * @param splitX the horizontal split line
                 * @return true if result in smi is OK
                 */

                public boolean splitHoriz(int splitX, TileMetaInfo smi) {
                        if (splitX <= 0 || splitX >= width)
                                return false;
                        long sum = 0;
                       
                        if (splitX <= width / 2){
                                int start = (smi.getFirstNonZeroX() > 0) ? smi.getFirstNonZeroX() : 0;
                                for (int pos = start; pos < splitX; pos++) {
                                        sum += getColSum(pos, smi.getColSums());
                                }
                        } else {
                                int end = (smi.getLastNonZeroX() > 0) ? smi.getLastNonZeroX() + 1: width;
                                for (int pos = splitX; pos < end; pos++) {
                                        sum += getColSum(pos, smi.getColSums());
                                }
                                sum = count - sum;
                        }
                        if (sum < smi.getMinNodes() || count - sum < smi.getMinNodes())
                                return false;
                        assert splitX > 0 && splitX < width;
                        Tile[] parts = smi.getParts();
                        parts[0] = new Tile(densityInfo, x, y, splitX, height, sum);
                        parts[1] = new Tile(densityInfo, x + splitX, y, width - splitX,height, count - sum);
                        assert smi.getParts()[0].width + smi.getParts()[1].width == this.width;
                        return true;
                }

                /**
                 * Split at the given vertical position.
                 * @param splitY the vertical split line
                 * @return true if result in smi is OK
                 */

                public boolean splitVert(int splitY, TileMetaInfo smi) {
                        if (splitY <= 0 || splitY >= height)
                                return false;
                        long sum = 0;
                       
                        if (splitY <= height / 2){
                                int start = (smi.getFirstNonZeroY() > 0) ? smi.getFirstNonZeroY() : 0;
                                for (int pos = start; pos < splitY; pos++) {
                                        sum += getRowSum(pos, smi.getRowSums());
                                }
                        } else {
                                int end = (smi.getLastNonZeroY() > 0) ? smi.getLastNonZeroY()+1 : height;
                                for (int pos = splitY; pos < end; pos++) {
                                        sum += getRowSum(pos, smi.getRowSums());
                                }
                                sum = count - sum;
                        }

                        if (sum < smi.getMinNodes() || count - sum < smi.getMinNodes())
                                return false;
                        assert splitY > 0 && splitY < height;
                        Tile[] parts = smi.getParts();
                        parts[0] = new Tile(densityInfo, x, y, width, splitY, sum);
                        parts[1] = new Tile(densityInfo, x, y + splitY, width, height- splitY, count- sum);
                        assert parts[0].height + parts[1].height == this.height;
                       
                        return true;
                }

                /**
                 *
                 * @param smi
                 * @return lowest horizontal position at which a split will work regarding minNodes
                 */

                public int findValidStartX(TileMetaInfo smi) {
                        if (smi.getValidStartX() >= 0)
                                return smi.getValidStartX();
                        long sum = 0;
                        int start = (smi.getFirstNonZeroX() > 0) ? smi.getFirstNonZeroX() : 0;
                        for (int i = start; i < width; i++) {
                                sum += getColSum(i, smi.getColSums());
                                if (sum == 0)
                                        continue;
                                if (smi.getFirstNonZeroX() < 0)
                                        smi.setFirstNonZeroX(i);
                                if (sum >= smi.getMinNodes()) {
                                        int splitPos = i + 1;
                                        smi.setValidStartX(splitPos);
                                        return splitPos;
                                }
                        }
                        smi.setValidStartX(width);
                        return width;
                }

                /**
                 *
                 * @param smi
                 * @return highest position at which all columns on the right have a sum < minNodes
                 */

                public int findValidEndX(TileMetaInfo smi) {
                        if (smi.getValidEndX() < 0){
                                int end = smi.getLastNonZeroX() > 0 ? smi.getLastNonZeroX() : width - 1;
                                long sum = 0;
                                for (int i = end; i >= 0; --i) {
                                        sum += getColSum(i, smi.getColSums());
                                        if (sum > 0 && smi.getLastNonZeroX() < 0)
                                                smi.setLastNonZeroX(i);
                                        if (sum >= smi.getMinNodes()){
                                                smi.setValidEndX(i);
                                                break;
                                        }
                                }
                        }
                        return smi.getValidEndX();
                }
               
                /**
                 *
                 * @param smi
                 * @return lowest vertical position at which a split will work regarding minNodes
                 * or height if no such position exists
                 */

                public int findValidStartY(TileMetaInfo smi) {
                        if (smi.getValidStartY() > 0)
                                return smi.getValidStartY();
                        long sum = 0;
                        int start = (smi.getFirstNonZeroY() > 0) ? smi.getFirstNonZeroY() : 0;
                        for (int i = start; i < height; i++) {
                                sum += getRowSum(i, smi.getRowSums());
                                if (sum == 0)
                                        continue;
                                if (smi.getFirstNonZeroY() < 0)
                                        smi.setFirstNonZeroY(i);
                                if (sum >= smi.getMinNodes()){
                                        int splitPos = i+1;
                                        smi.setValidStartY(splitPos);
                                        return splitPos;
                                }
                        }
                        smi.setValidStartY(height);
                        return height;
                }

                /**
                 *
                 * @param smi
                 * @return highest position at which all upper rows have a sum < minNodes
                 */

                public int findValidEndY(TileMetaInfo smi) {
                        if (smi.getValidEndY() < 0){
                                int end = smi.getLastNonZeroY() > 0 ? smi.getLastNonZeroY() : height - 1;
                                long sum = 0;
                                for (int i = end; i >= 0; --i) {
                                        sum += getRowSum(i, smi.getRowSums());
                                        if (sum > 0 && smi.getLastNonZeroY() < 0)
                                                smi.setLastNonZeroY(i);
                                        if (sum >= smi.getMinNodes()){
                                                smi.setValidEndY(i);
                                                break;
                                        }
                                }
                        }
                        return smi.getValidEndY();
                }
               
                public int findFirstXHigher(TileMetaInfo smi, long limit){
                        long sum = 0;
                        int start = (smi.getFirstNonZeroX() > 0) ? smi.getFirstNonZeroX() : 0;
                        for (int i = start; i < width; i++) {
                                sum += getColSum(i, smi.getColSums());
                                if (sum == 0)
                                        continue;
                                if (smi.getFirstNonZeroX() < 0)
                                        smi.setFirstNonZeroX(i);
                                if (sum > limit){
                                        return i;
                                       
                                }
                        }
                        return height;
                }

                public int findFirstYHigher(TileMetaInfo smi, long limit){
                        long sum = 0;
                        int start = (smi.getFirstNonZeroY() > 0) ? smi.getFirstNonZeroY() : 0;
                        for (int i = start; i < height; i++) {
                                sum += getRowSum(i, smi.getRowSums());
                                if (sum == 0)
                                        continue;
                                if (smi.getFirstNonZeroY() < 0)
                                        smi.setFirstNonZeroY(i);
                                if (sum > limit){
                                        return i;
                                }
                        }
                        return height;
                }

               
                /**
                 *  
                 * @return aspect ratio of this tile
                 */

                public double getAspectRatio() {
                        return densityInfo.getAspectRatio(this);
                }
               
                /**
                 * Calculate the trimmed tile so that it has no empty outer rows or columns.
                 * Does not change the tile itself.
                 * @return the trimmed version of the tile.
                 */

                public Tile trim() {
                        int minX = -1;
                        for (int i = 0; i < width; i++) {
                                if (getColSum(i) > 0){
                                        minX = x + i;
                                        break;
                                }
                        }
                        int maxX = -1;
                        for (int i = width - 1; i >= 0; i--) {
                                if (getColSum(i) > 0){
                                        maxX = x + i;
                                        break;
                                }
                        }
                        int minY = -1;
                        for (int i = 0; i < height; i++) {
                                if (getRowSum(i) > 0){
                                        minY = y + i;
                                        break;
                                }
                        }
                        int maxY = -1;
                        for (int i = height - 1; i >= 0; i--) {
                                if (getRowSum(i) > 0){
                                        maxY = y + i;
                                        break;
                                }
                        }

                        assert minX <= maxX;
                        assert minY <= maxY;
                        return new Tile(densityInfo, minX, minY, maxX - minX + 1, maxY - minY + 1, count);
                }
               
               
                @Override
                public String toString(){
                        Area area = densityInfo.getDensityMap().getArea(x,y,width,height);
                        return  (area.toString() + " with " + Utils.format(count) + " nodes");
//                      StringBuilder sb = new StringBuilder();
//                      sb.append("(");
//                      sb.append(x);
//                      sb.append(",");
//                      sb.append(y);
//                      sb.append(",");
//                      sb.append(width);
//                      sb.append(",");
//                      sb.append(height);
//                      sb.append(") with ");
//                      sb.append(Utils.format(count));
//                      sb.append(" nodes and ratio ");
//                      sb.append(getAspectRatio());
//                      return sb.toString();          
                }
        }