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

org.dstadler.ctw.utils.MatrixUtils Maven / Gradle / Ivy

There is a newer version: 1.0.13
Show newest version
package org.dstadler.ctw.utils;

import static org.dstadler.ctw.utils.Constants.SQUARE_SIZE;
import static org.dstadler.ctw.utils.Constants.TILE_ZOOM;

import java.awt.Rectangle;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang3.tuple.Pair;
import org.dstadler.commons.logging.jdk.LoggerFactory;

import com.google.common.base.Preconditions;

/**
 * Some helpers to work with matrices for some algorithms,
 * e.g. largest rectangle and largest square.
 *
 * Usually the matric is populated from "tiles" or "squares"
 * and then largest square or largest rectangle are computed
 * in a second step.
 */
public class MatrixUtils {
	private static final Logger log = LoggerFactory.make();

	/**
	 * Create an 2-dimensional matrix in between of minEast/maxEast, minNorth, maxNorth
	 * with "1" for each square that is covered.
	 *
	 * @return The initialized matrix with "0" for not covered and "1" for covered
	 */
	public static int[][] populateMatrix(Set squares,
			double minEast,
			double minNorth,
			double maxEast,
			double maxNorth,
			int utmZoneFilter) {
		int xSquares = (int) ((maxEast - minEast) / SQUARE_SIZE) + 1;
		int ySquares = (int) ((maxNorth - minNorth) / SQUARE_SIZE) + 1;

		if (log.isLoggable(Level.FINE)) {
			log.fine("Having min/max: " +
					"\nEasting: " + minEast + "/" + maxEast +
					"\nNorthing: " + minNorth + "/" + maxNorth +
					"\nx,y: " + xSquares + "," + ySquares +
					"\nlat/lng: " + new UTMRefWithHash(utmZoneFilter, 'T', minEast, minNorth).toLatLng() + " - " +
					new UTMRefWithHash(utmZoneFilter, 'U', maxEast, maxNorth).toLatLng() +
					"\nUTM: " + new UTMRefWithHash(utmZoneFilter, 'T', minEast, minNorth) + " - " +
					new UTMRefWithHash(utmZoneFilter, 'U', maxEast, maxNorth));
		}

		// Min: Ref:  29U 436444.6222519411 5916707.037366929: (53.395620002685064, -9.955800000062068)

		int[][] M = new int[ySquares][xSquares];
		for (UTMRefWithHash square : squares) {
			// for now only calculate for Zone 33 as otherwise computing
			// easting would need to take the zone into account
			if (square.getLngZone() != utmZoneFilter) {
				continue;
			}

			int x = (int) ((square.getEasting() - minEast) / SQUARE_SIZE);
			int y = (int) ((square.getNorthing() - minNorth) / SQUARE_SIZE);

			Preconditions.checkState(x >= 0 && x <= xSquares,
					"Expect x to be positive and at max %s, failed with %s for %s and %s",
					xSquares, x, square, minEast);
			Preconditions.checkState(y >= 0 && y <= ySquares,
					"Expect y to be positive and at max %s, failed with %s for %s and %s",
					ySquares, y, square, minNorth);
			Preconditions.checkState(M[y][x] == 0,
					"Expect element to be not yet set, failed for %s and %s,%s",
					square, x, y);

			M[y][x] = 1;
		}
		return M;
	}

	/**
	 * Create an 2-dimensional matrix in between of minEast/maxX, minNorth, maxNorth
	 * with "1" for each square that is covered.
	 *
	 * @return The initialized matrix with "0" for not covered and "1" for covered
	 */
	public static int[][] populateMatrix(Set squares,
			int minX,
			int minY,
			int maxX,
			int maxY) {
		int xSquares = (maxX - minX) + 1;
		int ySquares = (maxY - minY) + 1;

		if (log.isLoggable(Level.FINE)) {
			log.fine("Having min/max: " +
					"\nX: " + minX + "/" + maxX +
					"\nY: " + minY + "/" + maxY +
					"\nx,y: " + xSquares + "," + ySquares +
					"\nlat/lng: " + new OSMTile(TILE_ZOOM, minX, minY).toLatLng() + " - " +
					new OSMTile(TILE_ZOOM, maxX, maxY).toLatLng());
		}

		// Min: Ref:  29U 436444.6222519411 5916707.037366929: (53.395620002685064, -9.955800000062068)

		int[][] M = new int[ySquares][xSquares];
		for (OSMTile square : squares) {
			int x = (square.getXTile() - minX);
			int y = (square.getYTile() - minY);

			Preconditions.checkState(x >= 0 && x <= xSquares,
					"Expect x to be positive and at max %s, failed with %s for %s and %s",
					xSquares, x, square, minX);
			Preconditions.checkState(y >= 0 && y <= ySquares,
					"Expect y to be positive and at max %s, failed with %s for %s and %s",
					ySquares, y, square, minY);
			Preconditions.checkState(M[y][x] == 0,
					"Expect element to be not yet set, failed for %s and %s,%s",
					square, x, y);

			M[y][x] = 1;
		}

		return M;
	}

	/**
	 * Method for Maximum size square sub-matrix with all "1"s
	 *
	 * Based on https://www.geeksforgeeks.org/maximum-size-sub-matrix-with-all-1s-in-a-binary-matrix/
	 *
	 * @param matrix The matrix with "1" for covered and "0" for not covered.
	 *
	 * @return A pair with the largest covered square and the number of squares covered by
	 * this rectangle.
	 */
	public static Pair maxSubSquare(int[][] matrix) {
		int R = matrix.length; // no of rows in M[][]
		int C = matrix[0].length; // no of columns in M[][]

		int[][] S = new int[R][C];
		/* Set first column of S[][]*/
		for (int i = 0; i < R; i++) {
			S[i][0] = matrix[i][0];
		}

		/* Set first row of S[][]*/
		System.arraycopy(matrix[0], 0, S[0], 0, C);

		/* Construct other entries of S[][]*/
		for (int i = 1; i < R; i++) {
			for (int j = 1; j < C; j++) {
				if (matrix[i][j] == 1) {
					S[i][j] = Math.min(
							S[i][j - 1],
							Math.min(S[i - 1][j],
									S[i - 1][j - 1]))
							+ 1;
				} else {
					S[i][j] = 0;
				}
			}
		}

        /* Find the maximum entry, and indexes of maximum
           entry in S[][] */
		int max_of_s = S[0][0];
		int max_i = 0;
		int max_j = 0;
		for (int i = 0; i < R; i++) {
			for (int j = 0; j < C; j++) {
				if (max_of_s < S[i][j]) {
					max_of_s = S[i][j];
					max_i = i;
					max_j = j;
				}
			}
		}

		return Pair.of(new Rectangle(max_j + 1, max_i, max_of_s, max_of_s), max_of_s * max_of_s);
	}

	// Implementation initially based on
	// https://www.geeksforgeeks.org/maximum-size-rectangle-binary-sub-matrix-1s/
	private static Pair maxHist(int[] row) {
		// Create an empty stack. The stack holds indexes of
		// hist[] array/ The bars stored in stack are always
		// in increasing order of their heights.
		IntStack result = new IntStack();

		int max_area = 0; // Initialize max area in current row (or histogram)
		Rectangle max_col = new Rectangle();

		// Run through all bars of given histogram (or row)
		int i = 0;
		while (i < row.length) {
			// If this bar is higher than the bar on top
			// stack, push it to stack
			if (result.isEmpty()
					|| row[result.peek()] <= row[i]) {
				result.push(i++);
			} else {
				// If this bar is lower than top of stack,
				// then calculate area of rectangle with
				// stack top as the smallest (or minimum
				// height) bar. 'i' is 'right index' for the
				// top and element before top in stack is
				// 'left index'
				max_area = getMaxArea(row, result, max_area, max_col, i);
			}
		}

		// Now pop the remaining bars from stack and
		// calculate area with every popped bar as the
		// smallest bar
		while (!result.isEmpty()) {
			max_area = getMaxArea(row, result, max_area, max_col, i);
		}

		return Pair.of(max_area, max_col);
	}

	private static int getMaxArea(int[] row, IntStack result, int max_area, Rectangle max_col, int i) {
		int top_val = row[result.peek()];
		result.pop();

		final int area;
		if (result.isEmpty()) {
			area = top_val * i;
		} else {
			area = top_val * (i - result.peek() - 1);
		}

		if (area > max_area) {
			max_area = area;
			//log.info("New top area " + max_area + ": " + result.peek());
			max_col.width = result.isEmpty() ? i : (i - result.peek() - 1);
			max_col.height = top_val;
			max_col.x = i;
			//max_col = "Top: " + top_val + ", " + i + (result.isEmpty() ? "" : ", " + (i - result.peek() - 1));
		}

		return max_area;
	}

	public static int findPopulatedRows(int[][] A, boolean[] isRowValue) {
		int count = 0;
		for (int i = 1; i < A.length; i++) {
			for (int j = 0; j < A[0].length; j++) {
				if (A[i][j] == 1) {
					isRowValue[i] = true;
					count++;
					break;
				}
			}
		}

		return count;
	}

	/**
	 * Returns area of the largest rectangle with all 1s in A[][]
	 *
	 * @param A The matrix with "1" for covered and "0" for not covered
	 * @return The rectangle and the covered area of the rectangle
	 */
	public static Pair maxRectangle(int[][] A) {
		boolean[] isY = new boolean[A.length];
		findPopulatedRows(A, isY);

		return maxRectangle(A, isY);
	}

	/**
	 * Returns area of the largest rectangle with all 1s in A[][]
	 *
	 * @param A The matrix with "1" for covered and "0" for not covered
	 * @param isY An array indicating which rows have values. This can be
	 *            used to skip iterating those rows to improve performance
	 * @return The rectangle and the covered area of the rectangle
	 */
	public static Pair maxRectangle(int[][] A, boolean[] isY) {
		// Calculate area for first row and initialize it as
		// result
		Pair ret = maxHist(A[0]);
		int result = ret.getKey();
		Rectangle rect = ret.getValue();

		log.fine("Area of first row: " + ret);

		// iterate over row to find maximum rectangular area
		// considering each row as histogram
		for (int i = 1; i < A.length; i++) {
			// we can skip processing if there is no single item set for this row
			if (!isY[i]) {
				continue;
			}

			for (int j = 0; j < A[0].length; j++) {
				// if A[i][j] is 1 then add A[i -1][j]
				if (A[i][j] == 1) {
					A[i][j] += A[i - 1][j];
				}
			}

			// Update result if area with current row (as
			// last row of rectangle) is more
			ret = maxHist(A[i]);
			int newRet = ret.getKey();
			if (newRet > result) {
				result = newRet;
				rect = ret.getValue();
				rect.y = i;
				log.fine("Area after row " + i + ": " + result + ": " + rect);
			}
		}

		return Pair.of(rect, result);
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy