/* Copyright 2013 The jeo project. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jeo.vector;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Set;
import com.google.common.collect.Iterators;
import io.jeo.TestData;
import io.jeo.data.Cursor;
import io.jeo.geom.Bounds;
import io.jeo.geom.Geom;
import io.jeo.proj.Proj;
import org.junit.Before;
import org.junit.Test;
import org.osgeo.proj4j.CoordinateReferenceSystem;
import com.google.common.base.Predicate;
import com.google.common.collect.Sets;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Point;
/**
* Abstract test case that exercises all aspects of the {@link VectorDataset} interface.
* <p>
* This test uses the {@link TestData#states()} dataset as a basis for testing and test implementors
* must override {@link #createVectorData()} and return an instance backed by the states data.
* </p>
*
* @author Justin Deoliveira, OpenGeo
*/
public abstract class VectorApiTestBase {
VectorDataset data;
@Before
public final void setUp() throws Exception {
init();
data = createVectorData();
}
protected void init() throws Exception {
}
protected abstract VectorDataset createVectorData() throws Exception;
@Test
public void testGetName() {
assertEquals("states", data.name());
}
@Test
public void testSchema() throws IOException {
Schema schema = data.schema();
assertNotNull(schema);
assertNotNull(schema.geometry());
}
@Test
public void testBounds() throws IOException {
Envelope bbox = data.bounds();
assertNotNull(bbox);
assertEquals(-124.73, bbox.getMinX(), 0.01);
assertEquals(24.96, bbox.getMinY(), 0.01);
assertEquals(-66.96, bbox.getMaxX(), 0.01);
assertEquals(49.37, bbox.getMaxY(), 0.01);
}
@Test
public void testCRS() throws IOException {
CoordinateReferenceSystem crs = data.crs();
assertNotNull(crs);
CoordinateReferenceSystem geo = Proj.EPSG_4326;
Point p = Proj.reproject(Geom.point(115.37, 51.08), crs, geo);
assertEquals(115.37, p.getX(), 0.01);
assertEquals(51.08, p.getY(), 0.01);
}
@Test
public void testCount() throws IOException {
// count all
assertEquals(49, data.count(new VectorQuery()));
// count within bounds
Set<String> abbrs = Sets.newHashSet("MO", "OK", "TX", "NM", "AR", "LA");
Envelope bbox = new Envelope(-106.649513, -93.507217, 25.845198, 36.493877);
assertEquals(abbrs.size(), data.count(new VectorQuery().bounds(bbox)));
// count with spatial filters
assertEquals(abbrs.size(), data.count(new VectorQuery().filter(String.format(Locale.ROOT,"INTERSECTS(%s, %s)",
data.schema().geometry().name(), Bounds.toPolygon(bbox)))));
// count with attribute filters
assertEquals(1, data.count(new VectorQuery().filter("STATE_NAME = 'Texas'")));
assertEquals(48, data.count(new VectorQuery().filter("STATE_NAME <> 'Texas'")));
assertEquals(2, data.count(new VectorQuery().filter("P_MALE > P_FEMALE")));
assertEquals(3, data.count(new VectorQuery().filter("P_MALE >= P_FEMALE")));
// count with logical filters
assertEquals(1, data.count(new VectorQuery().filter("P_MALE > P_FEMALE AND SAMP_POP > 200000")));
assertEquals(5, data.count(new VectorQuery().filter("P_MALE > P_FEMALE OR SAMP_POP > 2000000")));
assertEquals(1, data.count(new VectorQuery().filter("P_MALE > P_FEMALE AND NOT SAMP_POP > 200000")));
// count with id filters
String fid = fidFor(data, "STATE_NAME = 'Texas'");
assertEquals(1, data.count(new VectorQuery().filter(String.format(Locale.ROOT,"IN ('%s')", fid))));
}
@Test
public void testCursorRead() throws Exception {
// all
assertEquals(49, data.read(new VectorQuery()).count());
// limit offset
assertEquals(39, data.read(new VectorQuery().offset(10)).count());
assertEquals(10, data.read(new VectorQuery().limit(10)).count());
// bounds
Envelope bbox = new Envelope(-106.649513, -93.507217, 25.845198, 36.493877);
assertCovered(data.read(new VectorQuery().bounds(bbox)), "MO", "OK", "TX", "NM", "AR", "LA");
// spatial filter
assertCovered(data.read(new VectorQuery().filter(String.format(Locale.ROOT, "INTERSECTS(%s, %s)",
data.schema().geometry().name(), Bounds.toPolygon(bbox)))),
"MO", "OK", "TX", "NM", "AR", "LA");
// comparison filter
assertCovered(data.read(new VectorQuery().filter("STATE_NAME = 'Texas'")), "TX");
assertNotCovered(data.read(new VectorQuery().filter("STATE_NAME <> 'Texas'")), "TX");
assertCovered(data.read(new VectorQuery().filter("P_MALE > P_FEMALE")), "NV", "CA");
assertCovered(data.read(new VectorQuery().filter("P_MALE >= P_FEMALE")), "NV", "CA", "WY");
// logic filters
assertCovered(
data.read(new VectorQuery().filter("P_MALE > P_FEMALE AND SAMP_POP > 200000")), "CA");
assertCovered(data.read(new VectorQuery().filter("P_MALE > P_FEMALE OR SAMP_POP > 2000000")),
"TX", "NY", "PA", "NV", "CA");
assertCovered(
data.read(new VectorQuery().filter("P_MALE > P_FEMALE AND NOT SAMP_POP > 200000")), "NV");
// id filter
String fid = fidFor(data, "STATE_NAME = 'Texas'");
assertCovered(data.read(new VectorQuery().filter(String.format(Locale.ROOT, "IN ('%s')", fid))), "TX");
// in filter
assertCovered(data.read(new VectorQuery().filter("STATE_NAME IN ('Texas','Iowa')")), "TX", "IA");
// between
assertCovered(
data.read(new VectorQuery().filter(String.format(Locale.ROOT, "SAMP_POP BETWEEN %s AND %s", 70000, 80000))), "DC");
// math
assertCovered(data.read(new VectorQuery().filter("SAMP_POP / 2 = 36348")), "DC");
assertCovered(data.read(new VectorQuery().filter("(P_FEMALE - P_MALE) > .05")), "DC");
// like
assertCovered(data.read(new VectorQuery().filter("STATE_NAME LIKE 'Calif%'")), "CA");
// null
assertCount(49, data, "P_MALE IS NOT NULL");
assertCount(0, data, "P_MALE IS NULL");
// missing properties
assertCount(0, data, "MISSING IS NULL");
assertCount(0, data, "MISSING > 5");
assertCount(0, data, "MISSING + 5 > 5");
assertCount(0, data, "EQUALS(MISSING, POINT(0 0))");
assertCount(49, data, "MISSING > 5 OR P_MALE IS NOT NULL");
}
@Test
public void testFeature() throws Exception {
Cursor<Feature> cursor = data.read(new VectorQuery());
Feature next;
try {
assertTrue(cursor.hasNext());
next = cursor.next();
} finally {
cursor.close();
}
assertTrue(next.has(data.schema().geometry().name()));
assertTrue(next.has("STATE_NAME"));
assertFalse(next.has("NOT THERE AT ALL"));
// add a query and check all fields returned.
// ensures attribute filtering operations don't remove erroneously
cursor = data.read(new VectorQuery().filter("STATE_ABBR = 'CA'"));
assertTrue(cursor.hasNext());
Feature feature = cursor.next();
assertEquals(data.schema().fields().size(), feature.map().size());
for (Field f: data.schema().fields()) {
assertTrue(feature.has(f.name()));
}
}
@Test
public void testFeatureFields() throws Exception {
// reverse the order as they appear in gpkg/postgis schema
Cursor<Feature> cursor = data.read(new VectorQuery().fields("STATE_NAME", "SAMP_POP"));
Feature next;
try {
assertTrue(cursor.hasNext());
next = cursor.next();
} finally {
cursor.close();
}
assertTrue(next.map().size() == 2);
// make sure query reduction doesn't result in missing id but hard to
// test specifics across drivers as they return differing values now
assertTrue(next.id() != null && next.id().length() > 0);
// this should be here but hard to test specific value as order varies
assertTrue(next.get("STATE_NAME") != null);
assertTrue(next.get("SAMP_POP") != null);
// reduced fields but filter referencing other field
cursor = data.read(new VectorQuery().fields("STATE_NAME", "SAMP_POP").filter("STATE_ABBR = 'CA'"));
try {
assertTrue(cursor.hasNext());
next = cursor.next();
} finally {
cursor.close();
}
assertEquals(2, next.map().size());
assertEquals("California", next.get("STATE_NAME"));
assertEquals(3792553, ((Number)next.get("SAMP_POP")).intValue());
}
void assertNotCovered(Cursor<Feature> cursor, String... abbrs) throws IOException {
final Set<String> set = Sets.newHashSet(abbrs);
try {
Iterators.find(cursor.iterator(), new Predicate<Feature>() {
@Override
public boolean apply(Feature input) {
return set.contains(input.get("STATE_ABBR"));
}
});
fail();
}
catch(NoSuchElementException expected) {}
}
void assertCovered(Cursor<Feature> cursor, String... abbrs) throws IOException {
Set<String> set = Sets.newHashSet(abbrs);
int count = 0;
while(cursor.hasNext()) {
set.remove(cursor.next().get("STATE_ABBR"));
count++;
}
cursor.close();
assertTrue("expected empty set, found " + set, set.isEmpty());
assertEquals(abbrs.length, count);
}
void assertCount(int expected, VectorDataset dataSet, String filter) throws IOException {
VectorQuery q = new VectorQuery().filter(filter);
assertEquals(expected, dataSet.count(q));
assertEquals(expected, dataSet.read(q).count());
}
String fidFor(VectorDataset dataset, String filter) throws IOException {
Cursor<Feature> c = dataset.read(new VectorQuery().filter(filter));
try {
assertTrue(c.hasNext());
return c.next().id();
}
finally {
c.close();
}
}
}