/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.server.test.it.qp; import com.foundationdb.qp.expression.IndexBound; import com.foundationdb.qp.expression.IndexKeyRange; import com.foundationdb.qp.operator.API; import com.foundationdb.qp.operator.Cursor; import com.foundationdb.qp.operator.Operator; import com.foundationdb.qp.row.Row; import com.foundationdb.qp.rowtype.IndexRowType; import com.foundationdb.qp.rowtype.RowType; import com.foundationdb.qp.rowtype.Schema; import com.foundationdb.qp.rowtype.TableRowType; import com.foundationdb.server.api.dml.SetColumnSelector; import com.foundationdb.server.error.OutOfRangeException; import com.foundationdb.server.spatial.BoxLatLon; import com.foundationdb.server.spatial.Spatial; import com.geophile.z.Space; import com.geophile.z.SpatialObject; import com.geophile.z.spatialobject.jts.JTS; import com.geophile.z.spatialobject.jts.JTSSpatialObject; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import org.junit.Test; import java.math.BigDecimal; import java.util.*; import static com.foundationdb.qp.operator.API.cursor; import static com.foundationdb.qp.operator.API.indexScan_Default; import static java.lang.Math.abs; import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; public class SpatialLatLonTableIndexScanIT extends OperatorITBase { @Override protected boolean doAutoTransaction() { return false; } @Override protected void setupCreateSchema() { point = createTable( "schema", "point", "id int not null", "before int not null", // id mod 3 "after int not null", // id mod 5 "lat decimal(11, 7)", "lon decimal(11, 7)", "primary key(id)"); createSpatialTableIndex("schema", "point", "lat_lon", 0, 2, "lat", "lon"); createSpatialTableIndex("schema", "point", "before_lat_lon", 1, 2, "before", "lat", "lon"); createSpatialTableIndex("schema", "point", "lat_lon_after", 0, 2, "lat", "lon", "after"); createSpatialTableIndex("schema", "point", "before_lat_lon_after", 1, 2, "before", "lat", "lon", "after"); } @Override protected void setupPostCreateSchema() { pointRowType = schema.tableRowType(table(point)); pointOrdinal = pointRowType.table().getOrdinal(); latLonIndexRowType = indexType(point, "lat", "lon"); beforeLatLonIndexRowType = indexType(point, "before", "lat", "lon"); latLonAfterIndexRowType = indexType(point, "lat", "lon", "after"); beforeLatLonAfterIndexRowType = indexType(point, "before", "lat", "lon", "after"); space = Spatial.createLatLonSpace(); queryContext = queryContext(adapter); queryBindings = queryContext.createBindings(); } protected int lookaheadQuantum() { return 1; } @Test public void testLoad() { loadDB(); try (TransactionContext t = new TransactionContext()) { // Check (lat, lon) index Operator plan = indexScan_Default(latLonIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{z, id}; } }); compareRows(rows(latLonIndexRowType .physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (before, lat, lon) index Operator plan = indexScan_Default(beforeLatLonIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{before(id), z, id}; } }); compareRows(rows(beforeLatLonIndexRowType .physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (lat, lon, after) index Operator plan = indexScan_Default(latLonAfterIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{z, after(id), id}; } }); compareRows(rows(latLonAfterIndexRowType .physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (before, lat, lon, after) index Operator plan = indexScan_Default(beforeLatLonAfterIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{before(id), z, after(id), id}; } }); compareRows(rows(beforeLatLonAfterIndexRowType .physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } } @Test public void testLoadAndRemove() { loadDB(); try (TransactionContext t = new TransactionContext()) { // Delete rows with odd ids for (Integer id : zToId.ids()) { if ((id % 2) == 1) { deleteRow(point, id, before(id), after(id), lats.get(id), lons.get(id)); } } } try (TransactionContext t = new TransactionContext()) { // Check (lat, lon) index Operator plan = indexScan_Default(latLonIndexRowType); int rowsRemaining = zToId.ids().size() / 2; if ((zToId.ids().size() % 2) == 1) { rowsRemaining += 1; } long[][] expected = zToId.toArray(rowsRemaining, new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { // Only even ids should remain return (id % 2) == 0 ? new long[]{z, id} : null; } }); compareRows(rows(latLonIndexRowType .physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (before, lat, lon) index Operator plan = indexScan_Default(beforeLatLonIndexRowType); int rowsRemaining = zToId.ids().size() / 2; if ((zToId.ids().size() % 2) == 1) { rowsRemaining += 1; } long[][] expected = zToId.toArray(rowsRemaining, new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { // Only even ids should remain return (id % 2) == 0 ? new long[]{before(id), z, id} : null; } }); compareRows(rows(beforeLatLonIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (lat, lon, after) index Operator plan = indexScan_Default(latLonAfterIndexRowType); int rowsRemaining = zToId.ids().size() / 2; if ((zToId.ids().size() % 2) == 1) { rowsRemaining += 1; } long[][] expected = zToId.toArray(rowsRemaining, new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { // Only even ids should remain return (id % 2) == 0 ? new long[]{z, after(id), id} : null; } }); compareRows(rows(latLonAfterIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (before, lat, lon, after) index Operator plan = indexScan_Default(beforeLatLonAfterIndexRowType); int rowsRemaining = zToId.ids().size() / 2; if ((zToId.ids().size() % 2) == 1) { rowsRemaining += 1; } long[][] expected = zToId.toArray(rowsRemaining, new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { // Only even ids should remain return (id % 2) == 0 ? new long[]{before(id), z, after(id), id} : null; } }); compareRows(rows(beforeLatLonAfterIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } } @Test public void testLoadAndUpdate() { loadDB(); int n = lats.size(); zToId.clear(); try (TransactionContext t = new TransactionContext()) { // Increment y values for (int id = 0; id < n; id++) { BigDecimal lat = lats.get(id); BigDecimal lon = lons.get(id); Row before = row(point, id, before(id), after(id), lat, lon); Row after = row(point, id, before(id), after(id), lat, lon.add(BigDecimal.ONE)); long z = Spatial.shuffle(space, lat.doubleValue(), lon.doubleValue() + 1); zToId.add(z, id); updateRow(before, after); } } try (TransactionContext t = new TransactionContext()) { // Check (lat, lon) index Operator plan = indexScan_Default(latLonIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{z, id}; } }); compareRows(rows(latLonIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (before, lat, lon) index Operator plan = indexScan_Default(beforeLatLonIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{before(id), z, id}; } }); compareRows(rows(beforeLatLonIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (lat, lon, after) index Operator plan = indexScan_Default(latLonAfterIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{z, after(id), id}; } }); compareRows(rows(latLonAfterIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } try (TransactionContext t = new TransactionContext()) { // Check (before, lat, lon, after) index Operator plan = indexScan_Default(beforeLatLonAfterIndexRowType); long[][] expected = zToId.toArray(new ZToIdMapping.ExpectedRowCreator() { @Override public long[] fields(long z, int id) { return new long[]{before(id), z, after(id), id}; } }); compareRows(rows(beforeLatLonAfterIndexRowType.physicalRowType(), sort(expected)), cursor(plan, queryContext, queryBindings)); } } @Test public void testSpatialQueryLatLon() { loadDB(); final int TRIALS = 100; BigDecimal latLo; BigDecimal latHi; BigDecimal lonLo; BigDecimal lonHi; for (int i = 0; i < TRIALS; i++) { try (TransactionContext t = new TransactionContext()) { latLo = randomLat(); latHi = randomLat(); if (latLo.compareTo(latHi) > 0) { BigDecimal swap = latLo; latLo = latHi; latHi = swap; } lonLo = randomLon(); lonHi = randomLon(); if (lonLo.compareTo(lonHi) > 0) { BigDecimal swap = lonLo; lonLo = lonHi; lonHi = swap; } // Get the right answer Set<Integer> expected = new HashSet<>(); for (int id = 0; id < lats.size(); id++) { BigDecimal lat = lats.get(id); BigDecimal lon = lons.get(id); if (latLo.compareTo(lat) <= 0 && lat.compareTo(latHi) <= 0 && lonLo.compareTo(lon) <= 0 && lon.compareTo(lonHi) <= 0) { expected.add(id); } } // Get the query result using the (lat, lon) index Set<Integer> actual = new HashSet<>(); SpatialObject queryBox = BoxLatLon.newBox(latLo.doubleValue(), latHi.doubleValue(), lonLo.doubleValue(), lonHi.doubleValue()); IndexBound boxBound = new IndexBound(row(latLonIndexRowType, queryBox), new SetColumnSelector(0, 0)); IndexKeyRange box = IndexKeyRange.spatialObject(latLonIndexRowType, boxBound); Operator plan = indexScan_Default(latLonIndexRowType, box, lookaheadQuantum()); Cursor cursor = API.cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; while ((row = cursor.next()) != null) { assertSame(latLonIndexRowType.physicalRowType(), row.rowType()); long z = getLong(row, 0); int id = getLong(row, 1).intValue(); assertEquals(expectedHKey(id), row.hKey().toString()); actual.add(id); } // There should be no false negatives assertTrue(actual.containsAll(expected)); } } } @Test public void testSpatialQueryWithWraparound() { loadDB(); final int TRIALS = 100; BigDecimal latLo; BigDecimal latHi; BigDecimal lonLo; BigDecimal lonHi; for (int i = 0; i < TRIALS; i++) { try (TransactionContext t = new TransactionContext()) { latLo = randomLat(); latHi = randomLat(); if (latLo.compareTo(latHi) > 0) { BigDecimal swap = latLo; latLo = latHi; latHi = swap; } lonLo = randomLon(); lonHi = randomLon(); if (lonLo.compareTo(lonHi) < 0) { // Guarantee wraparound BigDecimal swap = lonLo; lonLo = lonHi; lonHi = swap; } // Get the right answer Set<Integer> expected = new HashSet<>(); for (int id = 0; id < lats.size(); id++) { BigDecimal lat = lats.get(id); BigDecimal lon = lons.get(id); if (latLo.compareTo(lat) <= 0 && lat.compareTo(latHi) <= 0 && lonLo.compareTo(lon) <= 0 && lon.compareTo(lonHi) <= 0) { expected.add(id); } } // Get the query result Set<Integer> actual = new HashSet<>(); SpatialObject queryBox = BoxLatLon.newBox(latLo.doubleValue(), latHi.doubleValue(), lonLo.doubleValue(), lonHi.doubleValue()); IndexBound boxBound = new IndexBound(row(latLonIndexRowType, queryBox), new SetColumnSelector(0, 0)); IndexKeyRange box = IndexKeyRange.spatialObject(latLonIndexRowType, boxBound); Operator plan = indexScan_Default(latLonIndexRowType, box, lookaheadQuantum()); Cursor cursor = API.cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; while ((row = cursor.next()) != null) { assertSame(latLonIndexRowType.physicalRowType(), row.rowType()); long z = getLong(row, 0); int id = getLong(row, 1).intValue(); assertEquals(expectedHKey(id), row.hKey().toString()); actual.add(id); } // There should be no false negatives assertTrue(actual.containsAll(expected)); } } } @Test public void testHybridQueryLatLon() { loadDB(); final int TRIALS = 100; BigDecimal latLo; BigDecimal latHi; BigDecimal lonLo; BigDecimal lonHi; for (int i = 0; i < TRIALS; i++) { try (TransactionContext t = new TransactionContext()) { latLo = randomLat(); latHi = randomLat(); if (latLo.compareTo(latHi) > 0) { BigDecimal swap = latLo; latLo = latHi; latHi = swap; } lonLo = randomLon(); lonHi = randomLon(); if (lonLo.compareTo(lonHi) > 0) { BigDecimal swap = lonLo; lonLo = lonHi; lonHi = swap; } // before = id mod 3, so try before = 0, 1, 2 for (int before = 0; before <= 2; before++) { // Get the right answer Set<Integer> expected = new HashSet<>(); for (int id = 0; id < lats.size(); id++) { BigDecimal lat = lats.get(id); BigDecimal lon = lons.get(id); if (before(id) == before && latLo.compareTo(lat) <= 0 && lat.compareTo(latHi) <= 0 && lonLo.compareTo(lon) <= 0 && lon.compareTo(lonHi) <= 0) { expected.add(id); } } // Get the query result using the (before, lat, lon) index Set<Integer> actual = new HashSet<>(); SpatialObject queryBox = BoxLatLon.newBox(latLo.doubleValue(), latHi.doubleValue(), lonLo.doubleValue(), lonHi.doubleValue()); IndexBound boxBound = new IndexBound(row(beforeLatLonIndexRowType, before, queryBox), new SetColumnSelector(0, 1)); IndexKeyRange box = IndexKeyRange.spatialObject(beforeLatLonIndexRowType, boxBound); Operator plan = indexScan_Default(beforeLatLonIndexRowType, box, lookaheadQuantum()); Cursor cursor = API.cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; while ((row = cursor.next()) != null) { assertSame(beforeLatLonIndexRowType.physicalRowType(), row.rowType()); int rowBefore = getLong(row, 0).intValue(); int rowId = getLong(row, 2).intValue(); assertEquals(before, rowBefore); assertEquals(expectedHKey(rowId), row.hKey().toString()); actual.add(rowId); } // There should be no false negatives assertTrue(actual.containsAll(expected)); } } } } @Test public void testNearPoint() { loadDB(); final int N = 100; for (int i = 0; i < N; i++) { try (TransactionContext t = new TransactionContext()) { BigDecimal queryLat = randomLat(); BigDecimal queryLon = randomLon(); long zStart = Spatial.shuffle(space, queryLat.doubleValue(), queryLon.doubleValue()); IndexBound zStartBound = new IndexBound(row(latLonIndexRowType, queryLat, queryLon), new SetColumnSelector(0, 1)); IndexKeyRange zStartRange = IndexKeyRange.around(latLonIndexRowType, zStartBound); Operator plan = indexScan_Default(latLonIndexRowType, zStartRange, lookaheadQuantum()); Cursor cursor = API.cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; long previousDistance = Long.MIN_VALUE; int count = 0; while ((row = cursor.next()) != null) { assertSame(latLonIndexRowType.physicalRowType(), row.rowType()); long zActual = getLong(row, 0); int id = getLong(row, 1).intValue(); BigDecimal lat = lats.get(id); BigDecimal lon = lons.get(id); long zExpected = Spatial.shuffle(space, lat.doubleValue(), lon.doubleValue()); assertEquals(zExpected, zActual); assertEquals(expectedHKey(id), row.hKey().toString()); long distance = abs(zExpected - zStart); assertTrue(distance >= previousDistance); previousDistance = distance; count++; } assertEquals(zToId.ids().size(), count); } } } @Test public void testHybridNearPoint() { loadDB(); final int N = 100; for (int i = 0; i < N; i++) { try (TransactionContext t = new TransactionContext()) { BigDecimal queryLat = randomLat(); BigDecimal queryLon = randomLon(); long zStart = Spatial.shuffle(space, queryLat.doubleValue(), queryLon.doubleValue()); for (int before = 0; before <= 2; before++) { // Expected SortedMap<Long, List<Integer>> distanceToId = new TreeMap<>(); for (Map.Entry<Long, List<Integer>> entry : zToId) { long z = entry.getKey(); for (Integer id : entry.getValue()) { if (before(id) == before) { long distance = abs(z - zStart); List<Integer> ids = distanceToId.get(distance); if (ids == null) { ids = new ArrayList<>(); distanceToId.put(distance, ids); } ids.add(id); } } } List<Integer> expectedIdByDistance = new ArrayList<>(); for (List<Integer> ids : distanceToId.values()) { for (Integer id : ids) { expectedIdByDistance.add(id); } } // Actual IndexBound zStartBound = new IndexBound(row(beforeLatLonIndexRowType, before, queryLat, queryLon), new SetColumnSelector(0, 1, 2)); IndexKeyRange zStartRange = IndexKeyRange.around(beforeLatLonIndexRowType, zStartBound); Operator plan = indexScan_Default(beforeLatLonIndexRowType, zStartRange, lookaheadQuantum()); Cursor cursor = API.cursor(plan, queryContext, queryBindings); cursor.openTopLevel(); Row row; long previousDistance = Long.MIN_VALUE; List<Integer> actualIdByDistance = new ArrayList<>(); while ((row = cursor.next()) != null) { assertSame(beforeLatLonIndexRowType.physicalRowType(), row.rowType()); int beforeActual = getLong(row, 0).intValue(); assertEquals(before, beforeActual); long zActual = getLong(row, 1); int id = getLong(row, 2).intValue(); BigDecimal lat = lats.get(id); BigDecimal lon = lons.get(id); long zExpected = Spatial.shuffle(space, lat.doubleValue(), lon.doubleValue()); assertEquals(zExpected, zActual); assertEquals(expectedHKey(id), row.hKey().toString()); long distance = abs(zExpected - zStart); assertTrue(distance >= previousDistance); previousDistance = distance; actualIdByDistance.add(id); } Collections.sort(expectedIdByDistance); Collections.sort(actualIdByDistance); assertEquals(expectedIdByDistance, actualIdByDistance); } } } } @Test public void testLongitudeBounds() { goodBox(0, 0, 0, 179); goodBox(0, 0, 0, 180); goodBox(0, 0, 0, 181); goodBox(0, 0, 0, 359); goodBox(0, 0, 0, 360); goodBox(0, 0, 0, 361); goodBox(0, 0, 0, 539); goodBox(0, 0, 0, 540); badBox(0, 0, 0, 541); goodBox(0, 0, 179, 0); goodBox(0, 0, 180, 0); goodBox(0, 0, 181, 0); goodBox(0, 0, 359, 0); goodBox(0, 0, 360, 0); goodBox(0, 0, 361, 0); goodBox(0, 0, 539, 0); goodBox(0, 0, 540, 0); badBox(0, 0, 541, 0); goodBox(0, 0, 0, -179); goodBox(0, 0, 0, -180); goodBox(0, 0, 0, -181); goodBox(0, 0, 0, -359); goodBox(0, 0, 0, -360); goodBox(0, 0, 0, -361); goodBox(0, 0, 0, -539); goodBox(0, 0, 0, -540); badBox(0, 0, 0, -541); goodBox(0, 0, -179, 0); goodBox(0, 0, -180, 0); goodBox(0, 0, -181, 0); goodBox(0, 0, -359, 0); goodBox(0, 0, -360, 0); goodBox(0, 0, -361, 0); goodBox(0, 0, -539, 0); goodBox(0, 0, -540, 0); badBox(0, 0, -541, 0); } @Test public void testLatitudeBounds() { goodBox(0, 89, 0, 0); goodBox(0, 90, 0, 0); goodBox(0, 91, 0, 0); goodBox(0, 181, 0, 0); goodBox(0, 361, 0, 0); goodBox(0, 449, 0, 0); goodBox(0, 450, 0, 0); badBox(0, 451, 0, 0); badBox(89, 0, 0, 0); badBox(90, 0, 0, 0); badBox(91, 0, 0, 0); badBox(0, -89, 0, 0); badBox(0, -90, 0, 0); badBox(0, -91, 0, 0); goodBox(-89, 0, 0, 0); goodBox(-90, 0, 0, 0); goodBox(-91, 0, 0, 0); goodBox(-181, 0, 0, 0); goodBox(-361, 0, 0, 0); goodBox(-449, 0, 0, 0); goodBox(-450, 0, 0, 0); badBox(-451, 0, 0, 0); } private void loadDB() { try (TransactionContext t = new TransactionContext()) { int id = 0; for (long y = LAT_LO; y <= LAT_HI; y += DLAT) { for (long x = LON_LO; x < LON_HI; x += DLON) { BigDecimal lat = new BigDecimal(y); BigDecimal lon = new BigDecimal(x); Row row = row(point, id, before(id), after(id), lat, lon); writeRow(row); long z = Spatial.shuffle(space, lat.doubleValue(), lon.doubleValue()); zToId.add(z, id); lats.add(lat); lons.add(lon); zs.add(z); id++; } } } } private BigDecimal randomLat() { return new BigDecimal(random.nextDouble() * LAT_RANGE + LAT_LO); } private BigDecimal randomLon() { return new BigDecimal(random.nextDouble() * LON_RANGE + LON_LO); } private static long before(long id) { return id % 3; } private static long after(long id) { return id % 5; } private Row[] rows(RowType rowType, long[][] x) { Row[] rows = new Row[x.length]; for (int i = 0; i < x.length; i++) { long[] a = x[i]; Object[] oa = new Object[a.length]; for (int j = 0; j < a.length; j++) { oa[j] = a[j]; } rows[i] = row(rowType, oa); } return rows; } private String expectedHKey(int id) { return String.format("{%s,(long)%s}", pointOrdinal, id); } private long[][] sort(long[][] a) { Arrays.sort(a, new Comparator<long[]>() { @Override public int compare(long[] x, long[] y) { for (int i = 0; i < x.length; i++) { if (x[i] < y[i]) { return -1; } if (x[i] > y[i]) { return 1; } } return 0; } }); return a; } private JTSSpatialObject box(double xLo, double xHi, double yLo, double yHi) { Coordinate[] coords = new Coordinate[5]; coords[0] = new Coordinate(xLo, yLo); coords[1] = new Coordinate(xLo, yHi); coords[2] = new Coordinate(xHi, yHi); coords[3] = new Coordinate(xHi, yLo); coords[4] = coords[0]; return JTS.spatialObject(space, FACTORY.createPolygon(FACTORY.createLinearRing(coords), null)); } private void goodBox(int latLo, int latHi, int lonLo, int lonHi) { BoxLatLon.newBox(latLo, latHi, lonLo, lonHi); } private void badBox(int latLo, int latHi, int lonLo, int lonHi) { try { goodBox(latLo, latHi, lonLo, lonHi); fail(); } catch (OutOfRangeException e) { // Expected } } private static final int LAT_LO = -90; private static final int LAT_HI = 90; private static final int LON_LO = -180; private static final int LON_HI = 180; private static final int LAT_RANGE = LAT_HI - LAT_LO; private static final int LON_RANGE = LON_HI - LON_LO; private static final int DLAT = 10; private static final int DLON = 10; private static final GeometryFactory FACTORY = new GeometryFactory(); private int point; private TableRowType pointRowType; private int pointOrdinal; private IndexRowType latLonIndexRowType; private IndexRowType beforeLatLonIndexRowType; private IndexRowType latLonAfterIndexRowType; private IndexRowType beforeLatLonAfterIndexRowType; private Space space; private ZToIdMapping zToId = new ZToIdMapping(); List<BigDecimal> lats = new ArrayList<>(); // indexed by id List<BigDecimal> lons = new ArrayList<>(); // indexed by id List<Long> zs = new ArrayList<>(); // indexed by id Random random = new Random(123456); }