package org.opensha2.geo; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkElementIndex; import static com.google.common.base.Preconditions.checkPositionIndex; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Strings.padStart; import static org.opensha2.internal.TextUtils.NEWLINE; import com.google.common.collect.ArrayTable; import com.google.common.collect.ContiguousSet; import com.google.common.collect.DiscreteDomain; import com.google.common.collect.Range; import java.util.Iterator; import java.util.Set; /** * An immutable, tabular grid of locations that supplies row and column data as * {@link LocationList}s. * * <p>Internally, the grid is backed by a Guava {@link ArrayTable}. * * @author Peter Powers */ public final class LocationGrid implements Iterable<Location> { private final ArrayTable<Integer, Integer, Location> grid; // starts are inclusive, ends are exclusive private final int rowStart; private final int rowWidth; private final int rowEnd; private final int columnStart; private final int columnWidth; private final int columnEnd; // true if entire grid is being used private final boolean master; private LocationGrid( ArrayTable<Integer, Integer, Location> grid, int rowStart, int rowWidth, int columnStart, int columnWidth) { this.grid = grid; this.rowStart = rowStart; this.rowWidth = rowWidth; this.rowEnd = rowStart + rowWidth; this.columnStart = columnStart; this.columnWidth = columnWidth; this.columnEnd = columnStart + columnWidth; this.master = rowStart == 0 && rowWidth == grid.rowKeyList().size() && columnStart == 0 && columnWidth == grid.columnKeyList().size(); } /** * Return the number of {@code Location}s in this grid. */ public int size() { return rowWidth * columnWidth; } /** * Return the number of rows in this grid. */ public int rows() { return rowWidth; } /** * Return the row at index. * @param index of the row to retrieve * @throws IndexOutOfBoundsException if {@code index < 0 || index >= rows()} */ public LocationList row(int index) { return new Row(rowStart + checkElementIndex(index, rows())); } /** * Return the first row. */ public LocationList firstRow() { return row(0); } /** * Return the last row. */ public LocationList lastRow() { return row(rows() - 1); } /** * Return the number of columns in this grid. */ public int columns() { return columnWidth; } /** * Return the column at index. * @param index of the column to retrieve * @throws IndexOutOfBoundsException if * {@code index < 0 || index >= columns()} */ public LocationList column(int index) { return new Column(columnStart + checkElementIndex(index, columns())); } /** * Return the first column. */ public LocationList firstColumn() { return column(0); } /** * Return the last column. */ public LocationList lastColumn() { return column(columns() - 1); } /** * Return a new grid that is a window into this one. The specified window * dimensions must be less than or equal to the dimensions of this grid. * * @param rowStart first row of window * @param rowWidth number of rows in the window * @param columnStart first column of window * @param columnWidth */ public LocationGrid window(int rowStart, int rowWidth, int columnStart, int columnWidth) { checkElementIndex(rowStart, this.rowWidth); checkPositionIndex(rowStart + rowWidth, this.rowWidth); checkElementIndex(columnStart, this.columnWidth); checkPositionIndex(columnStart + columnWidth, this.columnWidth); return new LocationGrid( this.grid, this.rowStart + rowStart, rowWidth, this.columnStart + columnStart, columnWidth); } /** * Return the parent grid. Method returns itself unless it was created using * one or more calls to {@link #window(int, int, int, int)}. In this case, a * grid equivalent to this grid's greatest ancestor is returned. */ public LocationGrid parent() { return master ? this : new LocationGrid( grid, 0, grid.rowKeyList().size(), 0, grid.columnKeyList().size()); } /** * Lazily compute the bounds of the {@code Location}s in this grid. Method * delegates to {@link Locations#bounds(Iterable)}. */ public Bounds bounds() { return Locations.bounds(this); } @Override public String toString() { StringBuilder sb = new StringBuilder("LocationGrid [") .append(rowWidth).append(" x ") .append(columnWidth).append("]") .append(" window=").append(!master); if (!master) { sb.append(" [parent ") .append(grid.rowKeyList().size()).append("r x ") .append(grid.columnKeyList().size()).append("c]"); } sb.append(NEWLINE); LocationList firstRow = firstRow(); int lastColumnIndex = rowWidth - 1; int lastRowIndex = columnWidth - 1; appendCorner(sb, 0, 0, firstRow.first()); appendCorner(sb, 0, lastColumnIndex, firstRow.last()); LocationList lastRow = lastRow(); appendCorner(sb, lastRowIndex, 0, lastRow.first()); appendCorner(sb, lastRowIndex, lastColumnIndex, lastRow.last()); if (size() < 1024) { sb.append("Locations:").append(NEWLINE); for (int i = 0; i < rows(); i++) { for (int j = 0; j < columns(); j++) { appendLocation(sb, i, j, grid.at(i, j)); } sb.append(NEWLINE); } } return sb.toString(); } private static void appendCorner(StringBuilder builder, int row, int column, Location loc) { builder.append("Corner: "); appendLocation(builder, row, column, loc); } private static void appendLocation(StringBuilder builder, int row, int column, Location loc) { builder.append(padStart(Integer.toString(row), 5, ' ')) .append(padStart(Integer.toString(column), 5, ' ')) .append(" ").append(loc) .append(NEWLINE); } @Override public Iterator<Location> iterator() { return new Iterator<Location>() { private int rowIndex = rowStart; private int columnIndex = columnStart; @Override public boolean hasNext() { return columnIndex < columnEnd && rowIndex < rowEnd; } @Override public Location next() { Location loc = grid.at(rowIndex, columnIndex++); if (columnIndex == columnEnd) { rowIndex++; } return loc; } @Override public void remove() { throw new UnsupportedOperationException(); } }; } private class Row extends LocationList { private final int rowIndex; private Row(int rowIndex) { this.rowIndex = rowIndex; } @Override public int size() { return grid.columnKeyList().size(); } @Override public Location get(int index) { return grid.at(rowIndex, index); } @Override public Iterator<Location> iterator() { return new Iterator<Location>() { private int columnIndex; @Override public boolean hasNext() { return columnIndex < columnEnd; } @Override public Location next() { return grid.at(rowIndex, columnIndex++); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } private class Column extends LocationList { private final int columnIndex; private Column(int columnIndex) { this.columnIndex = columnIndex; } @Override public int size() { return grid.rowKeyList().size(); } @Override public Location get(int index) { return grid.at(index, columnIndex); } @Override public Iterator<Location> iterator() { return new Iterator<Location>() { private int rowIndex; @Override public boolean hasNext() { return rowIndex < rowEnd; } @Override public Location next() { return grid.at(rowIndex++, columnIndex); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } /** * Return a new builder. * * @param rows expected number of rows * @param columns expected number of columns */ public static Builder builder(int rows, int columns) { return new Builder(rows, columns); } /** * A single-use builder of {@code LocationGrid}s. Use * {@link LocationGrid#builder(int, int)} to create new builder instances. */ public static class Builder { private final ArrayTable<Integer, Integer, Location> grid; private boolean built = false; private Builder(int rows, int columns) { grid = ArrayTable.create( ContiguousSet.create(Range.closedOpen(0, rows), DiscreteDomain.integers()), ContiguousSet.create(Range.closedOpen(0, columns), DiscreteDomain.integers())); }; /** * Set the Location at the specified {@code row} and {@code column} indices. * * @param row index of location to set * @param column index of location to set * @param loc to set * @return this {@code Builder} */ public Builder set(int row, int column, Location loc) { grid.set(row, column, loc); return this; } /** * Fill a row with the specified {@code Location}s. * * @param index of row to fill * @param locs to fill row with * @return this {@code Builder} */ public Builder fillRow(int index, LocationList locs) { checkArgument(locs.size() == grid.columnKeyList().size()); int column = 0; for (Location loc : locs) { grid.set(index, column++, loc); } return this; } /** * Fill a row with the specified {@code Location}s. * * @param index of column to fill * @param locs to fill column with * @return this {@code Builder} */ public Builder fillColumn(int index, LocationList locs) { checkArgument(locs.size() == grid.rowKeyList().size()); int row = 0; for (Location loc : locs) { grid.set(row++, index, loc); } return this; } /** * Return a newly created {@code LocationGrid}. */ public LocationGrid build() { checkState(!grid.containsValue(null), "Some Locations have not been set"); checkState(!built, "This builder has already been used"); return new LocationGrid( grid, 0, grid.rowKeyList().size(), 0, grid.columnKeyList().size()); } } // TODO clean public static void main(String[] args) { Set<Integer> strikeIndices = ContiguousSet.create( Range.closedOpen(0, 9), DiscreteDomain.integers()); Set<Integer> dipIndices = ContiguousSet.create( Range.closedOpen(0, 4), DiscreteDomain.integers()); ArrayTable<Integer, Integer, Location> t = ArrayTable.create(dipIndices, strikeIndices); for (int dipIndex : dipIndices) { for (int strikeIndex : strikeIndices) { Location loc = Location.create( 34.0 + 0.2 * strikeIndex, -117.4 + 0.1 * dipIndex); t.set(dipIndex, strikeIndex, loc); } } LocationGrid grid2 = new LocationGrid(t, 0, dipIndices.size(), 0, strikeIndices.size()); System.out.println(grid2); int rows = 4; int cols = 9; Builder b = builder(4, 9); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { Location loc = Location.create( 34.0 + 0.2 * j, -117.4 + 0.1 * i); b.set(i, j, loc); } } System.out.println(b.grid); LocationGrid grid = b.build(); System.out.println(grid.grid.rowKeyList()); System.out.println(grid.grid.columnKeyList()); System.out.println(grid); // System.out.println(grid.lastColumn()); // Iterator<Location> it = grid.firstRow().iterator(); // System.out.println(it); // TODO test itertator.remove() } }