blob: c706caee80c20f5162e4791c9880c38344753e18 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.sis.storage.shapefile;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.Iterator;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.GeometryFactory;
import org.locationtech.jts.geom.Point;
import org.apache.sis.feature.builder.FeatureTypeBuilder;
import org.apache.sis.util.Utilities;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.FeatureQuery;
import org.apache.sis.storage.FeatureSet;
import org.apache.sis.feature.privy.AttributeConvention;
// Test dependencies
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
// Specific to the main branch:
import org.apache.sis.feature.AbstractFeature;
import org.apache.sis.feature.DefaultFeatureType;
import org.apache.sis.feature.DefaultAttributeType;
import org.apache.sis.filter.Filter;
/**
*
* @author Johann Sorel (Geomatys)
*/
public class ShapefileStoreTest {
private static final GeometryFactory GF = new GeometryFactory();
@Test
public void testStream() throws URISyntaxException, DataStoreException {
final URL url = ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
try (final ShapefileStore store = new ShapefileStore(Paths.get(url.toURI()))) {
//check feature type
final DefaultFeatureType type = store.getType();
assertEquals("point", type.getName().toString());
assertEquals(9, type.getProperties(true).size());
assertNotNull(type.getProperty("sis:identifier"));
assertNotNull(type.getProperty("sis:envelope"));
assertNotNull(type.getProperty("sis:geometry"));
final var geomProp = (DefaultAttributeType) type.getProperty("geometry");
final var idProp = (DefaultAttributeType) type.getProperty("id");
final var textProp = (DefaultAttributeType) type.getProperty("text");
final var integerProp = (DefaultAttributeType) type.getProperty("integer");
final var floatProp = (DefaultAttributeType) type.getProperty("float");
final var dateProp = (DefaultAttributeType) type.getProperty("date");
assertEquals(Point.class, geomProp.getValueClass());
assertEquals(Long.class, idProp.getValueClass());
assertEquals(String.class, textProp.getValueClass());
assertEquals(Long.class, integerProp.getValueClass());
assertEquals(Double.class, floatProp.getValueClass());
assertEquals(LocalDate.class, dateProp.getValueClass());
try (Stream<AbstractFeature> stream = store.features(false)) {
Iterator<AbstractFeature> iterator = stream.iterator();
assertTrue(iterator.hasNext());
AbstractFeature feature1 = iterator.next();
assertEquals(1L, feature1.getPropertyValue("id"));
assertEquals("text1", feature1.getPropertyValue("text"));
assertEquals(10L, feature1.getPropertyValue("integer"));
assertEquals(20.0, feature1.getPropertyValue("float"));
assertEquals(LocalDate.of(2023, 10, 27), feature1.getPropertyValue("date"));
Point pt1 = (Point) feature1.getPropertyValue("geometry");
assertTrue(iterator.hasNext());
AbstractFeature feature2 = iterator.next();
assertEquals(2L, feature2.getPropertyValue("id"));
assertEquals("text2", feature2.getPropertyValue("text"));
assertEquals(40L, feature2.getPropertyValue("integer"));
assertEquals(60.0, feature2.getPropertyValue("float"));
assertEquals(LocalDate.of(2023, 10, 28), feature2.getPropertyValue("date"));
Point pt2 = (Point) feature2.getPropertyValue("geometry");
assertFalse(iterator.hasNext());
}
}
}
/**
* Test optimized envelope filter.
*/
@Test
public void testEnvelopeFilter() throws URISyntaxException, DataStoreException {
final URL url = ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
try (final ShapefileStore store = new ShapefileStore(Paths.get(url.toURI()))) {
final DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
final GeneralEnvelope env = new GeneralEnvelope(CommonCRS.WGS84.normalizedGeographic());
env.setRange(0, 2, 3);
env.setRange(1, 42, 43);
final FeatureQuery query = new FeatureQuery();
query.setSelection(ff.bbox(ff.property("geometry"), env));
FeatureSet featureset = store.subset(query);
//ensure we obtained an optimized version
assertEquals("org.apache.sis.storage.shapefile.ShapefileStore$AsFeatureSet", featureset.getClass().getName());
try (Stream<AbstractFeature> stream = featureset.features(false)) {
Iterator<AbstractFeature> iterator = stream.iterator();
assertTrue(iterator.hasNext());
AbstractFeature feature = iterator.next();
assertEquals(2L, feature.getPropertyValue("id"));
assertEquals("text2", feature.getPropertyValue("text"));
assertEquals(40L, feature.getPropertyValue("integer"));
assertEquals(60.0, feature.getPropertyValue("float"));
assertEquals(LocalDate.of(2023, 10, 28), feature.getPropertyValue("date"));
Point pt2 = (Point) feature.getPropertyValue("geometry");
assertFalse(iterator.hasNext());
}
}
}
/**
* Test optimized field selection.
*/
@Test
public void testFieldFilter() throws URISyntaxException, DataStoreException {
final URL url = ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
try (final ShapefileStore store = new ShapefileStore(Paths.get(url.toURI()))) {
final FeatureQuery query = new FeatureQuery();
query.setProjection("text", "float");
FeatureSet featureset = store.subset(query);
//ensure we obtained an optimized version
assertEquals("org.apache.sis.storage.shapefile.ShapefileStore$AsFeatureSet", featureset.getClass().getName());
try (Stream<AbstractFeature> stream = featureset.features(false)) {
Iterator<AbstractFeature> iterator = stream.iterator();
assertTrue(iterator.hasNext());
AbstractFeature feature1 = iterator.next();
assertEquals("text1", feature1.getPropertyValue("text"));
assertEquals(20.0, feature1.getPropertyValue("float"));
assertTrue(iterator.hasNext());
AbstractFeature feature2 = iterator.next();
assertEquals("text2", feature2.getPropertyValue("text"));
assertEquals(60.0, feature2.getPropertyValue("float"));
assertFalse(iterator.hasNext());
}
}
}
@Test
public void testFiles() throws URISyntaxException, DataStoreException {
final URL url = ShapefileStoreTest.class.getResource("/org/apache/sis/storage/shapefile/point.shp");
try (final ShapefileStore store = new ShapefileStore(Paths.get(url.toURI()))) {
Path[] componentFiles = store.getComponentFiles();
assertEquals(5, componentFiles.length);
assertTrue(componentFiles[0].toString().endsWith("point.shp"));
assertTrue(componentFiles[1].toString().endsWith("point.shx"));
assertTrue(componentFiles[2].toString().endsWith("point.dbf"));
assertTrue(componentFiles[3].toString().endsWith("point.prj"));
assertTrue(componentFiles[4].toString().endsWith("point.cpg"));
}
}
/**
* Test creating a new shapefile.
*/
@Test
public void testCreate(@TempDir final Path folder) throws URISyntaxException, DataStoreException, IOException {
final Path temp = folder.resolve("test.shp");
final String name = temp.getFileName().toString().split("\\.")[0];
try (final ShapefileStore store = new ShapefileStore(temp)) {
Path[] componentFiles = store.getComponentFiles();
assertEquals(0, componentFiles.length);
{//create type
final DefaultFeatureType type = createType();
store.updateType(type);
}
{//check files have been created
componentFiles = store.getComponentFiles();
assertEquals(5, componentFiles.length);
assertTrue(componentFiles[0].toString().endsWith(name+".shp"));
assertTrue(componentFiles[1].toString().endsWith(name+".shx"));
assertTrue(componentFiles[2].toString().endsWith(name+".dbf"));
assertTrue( componentFiles[3].toString().endsWith(name+".prj"));
assertTrue(componentFiles[4].toString().endsWith(name+".cpg"));
}
{// check created type
DefaultFeatureType type = store.getType();
assertEquals(name, type.getName().toString());
assertEquals(9, type.getProperties(true).size());
assertNotNull(type.getProperty("sis:identifier"));
assertNotNull(type.getProperty("sis:envelope"));
assertNotNull(type.getProperty("sis:geometry"));
final var geomProp = (DefaultAttributeType) type.getProperty("geometry");
final var idProp = (DefaultAttributeType) type.getProperty("id");
final var textProp = (DefaultAttributeType) type.getProperty("text");
final var integerProp = (DefaultAttributeType) type.getProperty("integer");
final var floatProp = (DefaultAttributeType) type.getProperty("float");
final var dateProp = (DefaultAttributeType) type.getProperty("date");
final DefaultAttributeType crsChar = (DefaultAttributeType) geomProp.characteristics().get(AttributeConvention.CRS);
assertTrue(Utilities.equalsIgnoreMetadata(CommonCRS.WGS84.geographic(),crsChar.getDefaultValue()));
assertEquals(Point.class, geomProp.getValueClass());
assertEquals(Integer.class, idProp.getValueClass());
assertEquals(String.class, textProp.getValueClass());
assertEquals(Integer.class, integerProp.getValueClass());
assertEquals(Double.class, floatProp.getValueClass());
assertEquals(LocalDate.class, dateProp.getValueClass());
}
}
}
/**
* Test adding features to a shapefile.
*/
@Test
public void testAddFeatures(@TempDir final Path folder) throws URISyntaxException, DataStoreException, IOException {
final Path temp = folder.resolve("test.shp");
try (final ShapefileStore store = new ShapefileStore(temp)) {
DefaultFeatureType type = createType();
store.updateType(type);
type = store.getType();
AbstractFeature feature1 = createFeature1(type);
AbstractFeature feature2 = createFeature2(type);
store.add(List.of(feature1, feature2).iterator());
Object[] result = store.features(false).toArray();
assertEquals(2, result.length);
assertEquals(feature1, result[0]);
assertEquals(feature2, result[1]);
}
}
/**
* Test remove features from a shapefile.
*/
@Test
public void testRemoveFeatures(@TempDir final Path folder) throws DataStoreException, IOException {
final Path temp = folder.resolve("test.shp");
try (final ShapefileStore store = new ShapefileStore(temp)) {
DefaultFeatureType type = createType();
store.updateType(type);
type = store.getType();
AbstractFeature feature1 = createFeature1(type);
AbstractFeature feature2 = createFeature2(type);
store.add(List.of(feature1, feature2).iterator());
//remove first feature
final DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
final Filter<AbstractFeature> filter = ff.equal(ff.property("id"), ff.literal(1));
store.removeIf(filter);
Object[] result = store.features(false).toArray();
assertEquals(1, result.length);
assertEquals(feature2, result[0]);
}
}
/**
* Test replacing features in a shapefile.
*/
@Test
public void testReplaceFeatures(@TempDir final Path folder) throws DataStoreException, IOException {
final Path temp = folder.resolve("test.shp");
try (final ShapefileStore store = new ShapefileStore(temp)) {
DefaultFeatureType type = createType();
store.updateType(type);
type = store.getType();
AbstractFeature feature1 = createFeature1(type);
AbstractFeature feature2 = createFeature2(type);
store.add(List.of(feature1, feature2).iterator());
//remove first feature
final DefaultFilterFactory<AbstractFeature, Object, Object> ff = DefaultFilterFactory.forFeatures();
final Filter<AbstractFeature> filter = ff.equal(ff.property("id"), ff.literal(1));
store.replaceIf(filter, new UnaryOperator<AbstractFeature>() {
@Override
public AbstractFeature apply(AbstractFeature feature) {
feature.setPropertyValue("id",45);
return feature;
}
});
Object[] result = store.features(false).toArray();
assertEquals(2, result.length);
AbstractFeature f1 = (AbstractFeature) result[0];
assertEquals(45, f1.getPropertyValue("id"));
assertEquals(feature2, result[1]);
}
}
private static DefaultFeatureType createType() {
final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
ftb.setName("test");
ftb.addAttribute(Integer.class).setName("id");
ftb.addAttribute(String.class).setName("text");
ftb.addAttribute(Integer.class).setName("integer");
ftb.addAttribute(Float.class).setName("float");
ftb.addAttribute(LocalDate.class).setName("date");
ftb.addAttribute(Point.class).setName("geometry").setCRS(CommonCRS.WGS84.geographic());
return ftb.build();
}
private static AbstractFeature createFeature1(DefaultFeatureType type) {
AbstractFeature feature = type.newInstance();
feature.setPropertyValue("geometry", GF.createPoint(new Coordinate(10,20)));
feature.setPropertyValue("id", 1);
feature.setPropertyValue("text", "some text 1");
feature.setPropertyValue("integer", 123);
feature.setPropertyValue("float", 123.456);
feature.setPropertyValue("date", LocalDate.of(2023, 5, 12));
return feature;
}
private static AbstractFeature createFeature2(DefaultFeatureType type) {
AbstractFeature feature = type.newInstance();
feature.setPropertyValue("geometry", GF.createPoint(new Coordinate(30,40)));
feature.setPropertyValue("id", 2);
feature.setPropertyValue("text", "some text 2");
feature.setPropertyValue("integer", 456);
feature.setPropertyValue("float", 456.789);
feature.setPropertyValue("date", LocalDate.of(2030, 6, 21));
return feature;
}
}