Skip to content

Commit

Permalink
[GEOT-5918] GeoPackage data store performance improvements - screenma…
Browse files Browse the repository at this point in the history
…p hint support
  • Loading branch information
aaime committed Jan 1, 2018
1 parent 03e6b2f commit 852e8a5
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 119 deletions.
Expand Up @@ -16,24 +16,9 @@
*/
package org.geotools.jdbc;

import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import org.geotools.data.FeatureReader;
import org.geotools.data.Query;
import org.geotools.data.Transaction;
Expand All @@ -46,6 +31,7 @@
import org.geotools.filter.identity.FeatureIdImpl;
import org.geotools.geometry.jts.CurvedGeometryFactory;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.renderer.ScreenMap;
import org.geotools.util.Converters;
import org.geotools.util.logging.Logging;
import org.opengis.feature.FeatureFactory;
Expand All @@ -58,10 +44,25 @@
import org.opengis.feature.type.Name;
import org.opengis.filter.identity.FeatureId;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.CoordinateSequenceFactory;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Reader for jdbc datastore
Expand Down Expand Up @@ -108,6 +109,12 @@ public class JDBCFeatureReader implements FeatureReader<SimpleFeatureType, Simp
* hints
*/
protected Hints hints;

/**
* Screenmap for feature skipping behaviour
*/
protected ScreenMap screenMap;

/**
* current transaction
*/
Expand All @@ -116,6 +123,12 @@ public class JDBCFeatureReader implements FeatureReader<SimpleFeatureType, Simp
* flag indicating if the iterator has another feature
*/
protected Boolean next;

/**
* The next feature to be returned
*/
private SimpleFeature nextFeature;

/**
* feature builder
*/
Expand All @@ -141,7 +154,7 @@ public class JDBCFeatureReader implements FeatureReader<SimpleFeatureType, Simp
protected int offset = 0;

protected JDBCReaderCallback callback = JDBCReaderCallback.NULL;

public JDBCFeatureReader( String sql, Connection cx, JDBCFeatureSource featureSource, SimpleFeatureType featureType, Query query )
throws SQLException {
init( featureSource, featureType, query );
Expand Down Expand Up @@ -214,6 +227,9 @@ protected void init( JDBCFeatureSource featureSource, SimpleFeatureType featureT
if (linearizationTolerance != null) {
geometryFactory = new CurvedGeometryFactory(geometryFactory, linearizationTolerance);
}

// screenmap support
this.screenMap = (ScreenMap) hints.get(Hints.SCREENMAP);

// create a feature builder using the factory hinted or the one coming
// from the datastore
Expand Down Expand Up @@ -271,6 +287,9 @@ public JDBCFeatureReader( JDBCFeatureReader other ) {

public void setNext(Boolean next) {
this.next = next;
if (next == null) {
nextFeature = null;
}
}

public SimpleFeatureType getFeatureType() {
Expand All @@ -290,9 +309,15 @@ public boolean hasNext() throws IOException {

if (next == null) {
try {
callback.beforeNext(rs);
next = Boolean.valueOf(rs.next());
callback.afterNext(rs, next);
while (nextFeature == null && !Boolean.FALSE.equals(next)) {
callback.beforeNext(rs);
next = Boolean.valueOf(rs.next());
callback.afterNext(rs, next);

if (next) {
nextFeature = readNextFeature();
}
}

if (!next) {
callback.finish(this);
Expand All @@ -305,6 +330,116 @@ public boolean hasNext() throws IOException {

return next.booleanValue();
}

protected SimpleFeature readNextFeature() throws IOException {
//grab the connection
Connection cx;
try {
cx = st.getConnection();
}
catch (SQLException e) {
throw (IOException) new IOException().initCause(e);
}

// figure out the fid
String fid;

try {
fid = dataStore.encodeFID(pkey,rs,offset);
if (fid == null) {
//fid could be null during an outer join
return null;
}
// wrap the fid in the type name
fid = featureType.getTypeName() + "." + fid;
} catch (Exception e) {
throw new RuntimeException("Could not determine fid from primary key", e);
}

// round up attributes
final int attributeCount = featureType.getAttributeCount();
int[] attributeRsIndex = buildAttributeRsIndex();
for(int i = 0; i < attributeCount; i++) {
AttributeDescriptor type = featureType.getDescriptor(i);

try {
Object value = null;

// is this a geometry?
if (type instanceof GeometryDescriptor) {
GeometryDescriptor gatt = (GeometryDescriptor) type;

//read the geometry
try {
value = dataStore.getSQLDialect()
.decodeGeometryValue(gatt, rs, offset+attributeRsIndex[i],
geometryFactory, cx, hints);
} catch (IOException e) {
throw new RuntimeException(e);
}

if (value != null) {
//check to see if a crs was set
Geometry geometry = (Geometry) value;
if ( geometry.getUserData() == null ) {
//if not set, set from descriptor
geometry.setUserData( gatt.getCoordinateReferenceSystem() );
}

try {
// is position already busy skip it
if (screenMap != null) {
if (screenMap.canSimplify(geometry.getEnvelopeInternal())) {
if (screenMap.checkAndSet(geometry.getEnvelopeInternal())) {
return null;
} else {
value = screenMap.getSimplifiedShape(geometry);
}
}
}
} catch (TransformException e) {
if(LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Failed to process screenmap checks, proceeding without", e);
}
}

}

} else {
value = rs.getObject(offset+attributeRsIndex[i]);
}

// they value may need conversion. We let converters chew the initial
// value towards the target type, if the result is not the same as the
// original, then a conversion happened and we may want to report it to the
// user (being the feature type reverse engineerd, it's unlikely a true
// conversion will be needed)
if(value != null) {
Class binding = type.getType().getBinding();
Object converted = Converters.convert(value, binding);
if(converted != null && converted != value) {
value = converted;
if (dataStore.getLogger().isLoggable(Level.FINER)) {
String msg = value + " is not of type " + binding.getName()
+ ", attempting conversion";
dataStore.getLogger().finer(msg);
}
}
}

builder.add(value);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}

// create the feature
try {
return builder.buildFeature(fid);
} catch (IllegalAttributeException e) {
throw new RuntimeException(e);
}
}

protected void ensureNext() {
if (next == null) {
Expand All @@ -324,101 +459,23 @@ public SimpleFeature next() throws IOException, IllegalArgumentException,
ensureOpen();
if(!hasNext()) {
throw new NoSuchElementException("No more features in this reader, you should call " +
"hasNext() to check for feature availability");
}

//grab the connection
Connection cx;
try {
cx = st.getConnection();
}
catch (SQLException e) {
throw (IOException) new IOException().initCause(e);
}

// figure out the fid
String fid;

try {
fid = dataStore.encodeFID(pkey,rs,offset);
if (fid == null) {
//fid could be null during an outer join
return null;
}
// wrap the fid in the type name
fid = featureType.getTypeName() + "." + fid;
} catch (Exception e) {
throw new RuntimeException("Could not determine fid from primary key", e);
"hasNext() to check for feature availability");
}

// round up attributes
final int attributeCount = featureType.getAttributeCount();
int[] attributeRsIndex = buildAttributeRsIndex();
for(int i = 0; i < attributeCount; i++) {
AttributeDescriptor type = featureType.getDescriptor(i);

try {
Object value = null;

// is this a geometry?
if (type instanceof GeometryDescriptor) {
GeometryDescriptor gatt = (GeometryDescriptor) type;

//read the geometry
try {
value = dataStore.getSQLDialect()
.decodeGeometryValue(gatt, rs, offset+attributeRsIndex[i],
geometryFactory, cx, hints);
} catch (IOException e) {
throw new RuntimeException(e);
}

if (value != null) {
//check to see if a crs was set
Geometry geometry = (Geometry) value;
if ( geometry.getUserData() == null ) {
//if not set, set from descriptor
geometry.setUserData( gatt.getCoordinateReferenceSystem() );
}
}
} else {
value = rs.getObject(offset+attributeRsIndex[i]);
}

// they value may need conversion. We let converters chew the initial
// value towards the target type, if the result is not the same as the
// original, then a conversion happened and we may want to report it to the
// user (being the feature type reverse engineerd, it's unlikely a true
// conversion will be needed)
if(value != null) {
Class binding = type.getType().getBinding();
Object converted = Converters.convert(value, binding);
if(converted != null && converted != value) {
value = converted;
if (dataStore.getLogger().isLoggable(Level.FINER)) {
String msg = value + " is not of type " + binding.getName()
+ ", attempting conversion";
dataStore.getLogger().finer(msg);
}
}
}

builder.add(value);
} catch (SQLException e) {
throw new RuntimeException(e);
}

// join readers share the same resultset among many readers, won't call hasNext() on each
if (nextFeature == null) {
nextFeature = readNextFeature();
}

// create the feature
try {
return builder.buildFeature(fid);
} catch (IllegalAttributeException e) {
throw new RuntimeException(e);
if (nextFeature == null && screenMap != null) {
throw new IllegalStateException("Feature joining currently not supported along screenmap");
}

return nextFeature;
} finally {
// reset the next flag. We do this in a finally block to make sure we
// move to the next record no matter what, if the current one could
// not be read there is no salvation for it anyways
nextFeature = null;
next = null;
}
}
Expand Down
Expand Up @@ -75,7 +75,7 @@ public SimpleFeature next() throws IOException, IllegalArgumentException,
}

//reset next flag
next = null;
setNext(null);

if( this.featureSource.getEntry().getState(tx).hasListener() ){
// record bounds as we have a listener who will be interested
Expand Down

0 comments on commit 852e8a5

Please sign in to comment.