Andrea Aime ha scritto:
Anyways, for the small shapefile set I have rendering times around
40ms, for the big one around 0.7s.
Neither of them seem slow to me, compared to the amount of data
being rendered.
Ah, for those interested, the attached files use a dusted off
version of my old in memory spatially indexed feature cache.
It basically sticks all of the features in a fast in memory
spatial index and queries them
It's just a demo of what can be done, it's a use case I don't
care about, it's bad design since the cache is all or nothing
so it does not scale, but if all you care is speed on a small
dataset it can be of some use.
Btw, in my tests it brings rendering time of the big dataset
down to 0.5s, but does not work well with the small dataset
as the shapefiles have broken information in their headers
about the spatial extents of the shapefile itself and I did
not care to try to fix it.
But hey, if anybody is interested in this case, pick it up
and improve it :-)
Ah, there is also an unsupported caching module in GeoTools,
which should be more flexible, but I don't know how well
it performs
Cheers
Andrea
--
Andrea Aime
OpenGeo - http://opengeo.org
Expert service straight from the developers.
package org.geotools;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.ResourceInfo;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.geotools.feature.DefaultFeatureCollection;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureCollections;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.feature.simple.SimpleFeatureTypeBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.Feature;
import org.opengis.feature.IllegalAttributeException;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.And;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.BBOX;
import org.opengis.filter.spatial.BinarySpatialOperator;
import org.opengis.filter.spatial.Contains;
import org.opengis.filter.spatial.Crosses;
import org.opengis.filter.spatial.DWithin;
import org.opengis.filter.spatial.Equals;
import org.opengis.filter.spatial.Intersects;
import org.opengis.filter.spatial.Overlaps;
import org.opengis.filter.spatial.Touches;
import org.opengis.filter.spatial.Within;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.index.SpatialIndex;
import com.vividsolutions.jts.index.strtree.STRtree;
/**
* A caching feature source for fast data access.
*
*/
public class CachingFeatureSource implements FeatureSource {
private FeatureSource wrapped;
private SpatialIndex index;
private boolean dirty;
private Query cachedQuery;
private Envelope cachedBounds;
private SimpleFeatureType cachedSchema;
private Envelope originalBounds;
private static FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
private static final Set<Class> supportedFilterTypes = new HashSet<Class>(Arrays.asList(
BBOX.class, Contains.class, Crosses.class, DWithin.class, Equals.class,
Intersects.class, Overlaps.class, Touches.class, Within.class));
public CachingFeatureSource(FeatureSource original) throws IOException {
this.wrapped = original;
this.originalBounds = original.getBounds();
if (originalBounds == null)
originalBounds = new Envelope(-Double.MAX_VALUE, Double.MAX_VALUE, -Double.MAX_VALUE,
Double.MAX_VALUE);
}
private void fillCache(Query query) throws IOException {
System.out.println("Refilling cache from " + query);
FeatureCollection features = wrapped.getFeatures(query);
FeatureIterator fi = features.features();
index = null;
STRtree newIndex = new STRtree();
while (fi.hasNext()) {
// consider turning all geometries into packed ones, to save space
Feature f = fi.next();
newIndex.insert(ReferencedEnvelope.reference(f.getBounds()), f);
}
fi.close();
index = newIndex;
cachedQuery = query;
cachedSchema = (SimpleFeatureType) features.getSchema();
cachedBounds = getEnvelope(query.getFilter());
dirty = false;
}
public void addFeatureListener(FeatureListener listener) {
wrapped.addFeatureListener(listener);
}
public void removeFeatureListener(FeatureListener listener) {
wrapped.removeFeatureListener(listener);
}
public DataStore getDataStore() {
return (DataStore) wrapped.getDataStore();
}
public ReferencedEnvelope getBounds() throws IOException {
return wrapped.getBounds();
}
public ReferencedEnvelope getBounds(Query query) throws IOException {
return wrapped.getBounds(query);
}
public int getCount(Query query) throws IOException {
return wrapped.getCount(query);
}
public FeatureType getSchema() {
return wrapped.getSchema();
}
public FeatureCollection getFeatures() throws IOException {
return getFeatures(Filter.INCLUDE);
}
public FeatureCollection getFeatures(Filter filter) throws IOException {
return getFeatures(new DefaultQuery(wrapped.getSchema().getName().getLocalPart(), filter));
}
public FeatureCollection getFeatures(Query query) throws IOException {
String schemaName = wrapped.getSchema().getName().getLocalPart();
if (query.getTypeName() != null && !schemaName.equals(query.getTypeName())) {
throw new DataSourceException("Typename mismatch, query asks for '"
+ query.getTypeName() + " but this feature source provides '" + schemaName
+ "'");
}
if (index == null || dirty || !isSubQuery(query)) {
fillCache(query);
}
return getFeatureCollection(query, getEnvelope(query.getFilter()));
}
private FeatureCollection getFeatureCollection(Query query, Envelope bounds) throws IOException {
try {
SimpleFeatureType alternate = cachedSchema;
if (query.getPropertyNames() != Query.ALL_NAMES) {
alternate = SimpleFeatureTypeBuilder.retype(cachedSchema, query.getPropertyNames());
if (alternate.equals(cachedSchema))
alternate = cachedSchema;
}
Filter f = query.getFilter();
if (f != null && f.equals(Filter.EXCLUDE))
f = null;
List featureList = index.query(bounds);
SimpleFeature[] features = (SimpleFeature[]) featureList.toArray(new SimpleFeature[featureList.size()]);
FeatureCollection collection = new DefaultFeatureCollection(null, cachedSchema);
for (int i = 0; i < features.length; i++) {
SimpleFeature curr = features[i];
if (f != null && !f.evaluate(curr))
continue;
if (alternate != cachedSchema)
curr = reType(alternate, curr);
collection.add(curr);
}
return collection;
} catch (Exception e) {
throw new DataSourceException(
"Error occurred extracting features from the spatial index", e);
}
}
/**
* Same as DataUtilities.reType, but without the cloning that uselessly wastes CPU cycles...
*
* @param featureType
* @param feature
* @return
* @throws IllegalAttributeException
*/
public static SimpleFeature reType(SimpleFeatureType featureType, SimpleFeature feature)
throws IllegalAttributeException {
FeatureType origional = feature.getFeatureType();
if (featureType.equals(origional)) {
return SimpleFeatureBuilder.copy(feature);
}
String id = feature.getID();
int numAtts = featureType.getAttributeCount();
Object[] attributes = new Object[numAtts];
String xpath;
for (int i = 0; i < numAtts; i++) {
AttributeDescriptor curAttType = featureType.getDescriptor(i);
attributes[i] = feature.getAttribute(curAttType.getLocalName());
}
return SimpleFeatureBuilder.build(featureType, attributes, id);
}
boolean isSubQuery(Query query) {
// no cached data?
if (cachedQuery == null)
return false;
// do we miss some properties?
String[] cachedPropNames = cachedQuery.getPropertyNames();
String[] propNames = query.getPropertyNames();
if (cachedPropNames != Query.ALL_NAMES
&& (propNames == Query.ALL_NAMES || !Arrays.asList(cachedPropNames).containsAll(
Arrays.asList(propNames))))
return false;
Filter[] filters = splitFilters(query);
Filter[] cachedFilters = splitFilters(cachedQuery);
if (!filters[0].equals(cachedFilters[0]))
return false;
Envelope envelope = getEnvelope(filters[1]);
return cachedBounds.contains(envelope);
}
Envelope getEnvelope(Filter filter) {
Envelope result = originalBounds;
if (filter instanceof And) {
Envelope bounds = new Envelope();
for (Iterator iter = ((And) filter).getChildren().iterator(); iter.hasNext();) {
Filter f = (Filter) iter.next();
Envelope e = getEnvelope(f);
if (e == null)
return null;
else
bounds.expandToInclude(e);
}
result = bounds;
} else if (filter instanceof BinarySpatialOperator) {
BinarySpatialOperator gf = (BinarySpatialOperator) filter;
if (supportedFilterTypes.contains(gf.getClass())) {
Expression lg = gf.getExpression1();
Expression rg = gf.getExpression2();
if (lg instanceof Literal) {
Geometry g = (Geometry) ((Literal) lg).getValue();
if (rg instanceof PropertyName)
result = g.getEnvelopeInternal();
} else if (rg instanceof Literal) {
Geometry g = (Geometry) ((Literal) rg).getValue();
if (lg instanceof PropertyName)
result = g.getEnvelopeInternal();
}
}
}
return result.intersection(originalBounds);
}
/**
* Splits a query into two parts, a spatial component that can be turned into a bbox filter (by
* including some more feature in the result) and a residual component that we cannot address
* with the spatial index
*
* @param query
*/
Filter[] splitFilters(Query query) {
Filter filter = query.getFilter();
if (filter == null || filter.equals(Filter.EXCLUDE)) {
return new Filter[] { Filter.EXCLUDE, bboxFilter(originalBounds) };
}
if (!(filter instanceof And)) {
Envelope envelope = getEnvelope(filter);
if (envelope == null)
return new Filter[] { Filter.EXCLUDE, bboxFilter(originalBounds) };
else
return new Filter[] { Filter.EXCLUDE, bboxFilter(envelope) };
}
And and = (And) filter;
List residuals = new ArrayList();
List bboxBacked = new ArrayList();
for (Iterator it = and.getChildren().iterator(); it.hasNext();) {
Filter child = (Filter) it.next();
if (getEnvelope(child) != null) {
bboxBacked.add(child);
} else {
residuals.add(child);
}
}
return new Filter[] { (Filter) ff.and(residuals), (Filter) ff.and(bboxBacked) };
}
private BBOX bboxFilter(Envelope bbox) {
// GeometryFilterImpl gf = (GeometryFilterImpl) ff
// .createGeometryFilter(GeometryFilter.GEOMETRY_BBOX);
// gf.setExpression1(ff.createAttributeExpression(wrapped.getSchema().getDefaultGeometry()));
// gf.setExpression2(ff.createBBoxExpression(bbox));
// return gf;
return ff.bbox(wrapped.getSchema().getGeometryDescriptor().getLocalName(), bbox.getMinX(),
bbox.getMinY(), bbox.getMaxX(), bbox.getMaxY(), null);
}
public ResourceInfo getInfo() {
return wrapped.getInfo();
}
public Name getName() {
return wrapped.getName();
}
public QueryCapabilities getQueryCapabilities() {
return wrapped.getQueryCapabilities();
}
public Set getSupportedHints() {
HashSet hints = new HashSet(wrapped.getSupportedHints());
hints.remove(Hints.FEATURE_DETACHED);
return hints;
}
}package org.geotools;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import org.geotools.data.DataStore;
import org.geotools.data.DefaultRepository;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.DefaultMapLayer;
import org.geotools.map.MapContext;
import org.geotools.map.MapLayer;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.LineSymbolizer;
import org.geotools.styling.Mark;
import org.geotools.styling.PointSymbolizer;
import org.geotools.styling.PolygonSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.swing.JMapFrame;
import org.geotools.swing.JMapPane;
import org.geotools.swing.data.JFileDataStoreChooser;
import org.geotools.swing.event.MapPaneAdapter;
import org.geotools.swing.event.MapPaneEvent;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.FilterFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
public class MultiShape extends JMapFrame {
private MapContext context;
private String title;
private DefaultRepository repository = new DefaultRepository();
static StyleFactory styleFactory = CommonFactoryFinder.getStyleFactory(null);
static FilterFactory filterFactory = CommonFactoryFinder.getFilterFactory(null);
public void loadShapefilesfromDirectory(String dirPath) throws IOException {
File directory = new File(dirPath);
if (!directory.exists() || !directory.isDirectory())
throw new IllegalArgumentException("Invalid path " + dirPath);
File[] shapes = directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".shp");
}
});
for (File f : shapes) {
addShapefile(f.toURL(), true);
}
// expand area of interest
try {
context.setAreaOfInterest(context.getLayerBounds());
} catch (Exception e) {
e.printStackTrace();
}
}
public void loadShapefile() throws IOException {
File file = JFileDataStoreChooser.showOpenFile("shp", this);
if (file != null) {
addShapefile(file.toURL(), true);
}
}
public MultiShape(String frameTitle) throws IOException {
setTitle(frameTitle);
enableLayerTable(true);
enableStatusBar(true);
enableToolBar(true);
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
JMenu menu = new JMenu("File");
JMenuItem item = new JMenuItem("Open...");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
loadShapefile();
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
menu.add(item);
item = new JMenuItem("Get scale");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JMapPane mapPane = getMapPane();
if (mapPane != null) {
AffineTransform tr = mapPane.getScreenToWorldTransform();
if (tr != null) {
System.out.println("x scale: " + tr.getScaleX());
System.out.println("Y scale: " + tr.getScaleY());
System.out.println("pane area: " + mapPane.getVisibleRect());
}
}
}
});
menu.add(item);
menuBar.add(menu);
initComponents();
loadShapefilesfromDirectory("/home/aaime/devel/gisData/tiger2009/virginia_fallschurch");
// loadShapefilesfromDirectory("/home/aaime/devel/gisData/tiger2009/montana_gallatin");
enableLayerTable(false);
getMapPane().addMapPaneListener(new MapPaneAdapter() {
long start;
@Override
public void onRenderingStarted(MapPaneEvent ev) {
start = System.currentTimeMillis();
}
@Override
public void onRenderingStopped(MapPaneEvent ev) {
System.out.println("Map rendered in " + (System.currentTimeMillis() - start)
/ 1000.0 + "s");
}
});
}
public static void main(String[] args) throws IOException {
// Creating a new JMapFrame
final MultiShape viewer = new MultiShape("Shapefile viewer");
viewer.setSize(800, 800);
viewer.setVisible(true);
}
public boolean addShapefile(URL shapefileURL, boolean defaultStyle) throws IOException {
if (shapefileURL == null) {
throw new IllegalArgumentException("shapefileURL must not be null");
}
ShapefileDataStore dstore = null;
DataStore found = repository.dataStore(shapefileURL.toString());
if (found != null && found instanceof ShapefileDataStore) {
dstore = (ShapefileDataStore) found;
} else {
try {
dstore = new ShapefileDataStore(shapefileURL);
} catch (MalformedURLException urlEx) {
throw new RuntimeException(urlEx);
}
repository.register(shapefileURL.toString(), dstore);
}
/*
* We assume from this point that the shapefile exists and is accessible
*/
String typeName = dstore.getTypeNames()[0];
// Create a basic Style to render the features
Style style = null;
SimpleFeatureType schema = dstore.getSchema();
Class geomType = schema.getGeometryDescriptor().getType().getBinding();
if (Polygon.class.isAssignableFrom(geomType)
|| MultiPolygon.class.isAssignableFrom(geomType)) {
style = createPolygonStyle();
} else if (LineString.class.isAssignableFrom(geomType)
|| MultiLineString.class.isAssignableFrom(geomType)) {
style = createLineStyle();
} else {
style = createPointStyle();
}
MapLayer layer = new DefaultMapLayer(new CachingFeatureSource(dstore.getFeatureSource(typeName)), style);
addLayer(layer);
return true;
}
/**
* Add a map layer to those displayed. If no {...@linkplain org.geotools.map.MapContext} has been
* set explicitly, a new instance of {...@linkplain org.geotools.map.DefaultMapContext} will be
* created.
*/
public void addLayer(MapLayer layer) {
if (context == null) {
CoordinateReferenceSystem crs = layer.getBounds().getCoordinateReferenceSystem();
if (crs == null) {
crs = DefaultGeographicCRS.WGS84;
}
context = new DefaultMapContext(crs);
context.setTitle(title);
setMapContext(context);
StreamingRenderer renderer = new StreamingRenderer();
HashMap hints = new HashMap();
hints.put(StreamingRenderer.OPTIMIZED_DATA_LOADING_KEY, Boolean.TRUE);
renderer.setRendererHints(hints);
// renderer.setJava2DHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING,
// RenderingHints.VALUE_ANTIALIAS_ON));
setRenderer(renderer);
}
context.addLayer(layer);
}
// Extension with Styles
/**
* Create a Style to draw polygon features with a thin blue outline and a cyan fill
*/
private static Style createPolygonStyle() {
// create a partially opaque outline stroke
Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLUE), filterFactory
.literal(1), null); /* filterFactory.literal(0.5)); */
// create a partial opaque fill
Fill fill = styleFactory.createFill(filterFactory.literal(Color.CYAN), null);
/* filterFactory.literal(0.5)); */
/*
* Setting the geometryPropertyName arg to null signals that we want to draw the default
* geometry of features
*/
PolygonSymbolizer sym = styleFactory.createPolygonSymbolizer(stroke, fill, null);
Rule rule = styleFactory.createRule();
rule.symbolizers().add(sym);
FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[] { rule });
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
/**
* Create a Style to draw line features as thin blue lines
*/
private static Style createLineStyle() {
Stroke stroke = styleFactory.createStroke(filterFactory.literal(Color.BLUE), filterFactory
.literal(1));
/*
* Setting the geometryPropertyName arg to null signals that we want to draw the default
* geomettry of features
*/
LineSymbolizer sym = styleFactory.createLineSymbolizer(stroke, null);
Rule rule = styleFactory.createRule();
rule.symbolizers().add(sym);
FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[] { rule });
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
/**
* Create a Style to draw point features as circles with blue outlines and cyan fill
*/
private static Style createPointStyle() {
Graphic gr = styleFactory.createDefaultGraphic();
Mark mark = styleFactory.getCircleMark();
mark.setStroke(styleFactory.createStroke(filterFactory.literal(Color.BLUE), filterFactory
.literal(1)));
mark.setFill(styleFactory.createFill(filterFactory.literal(Color.CYAN)));
gr.graphicalSymbols().clear();
gr.graphicalSymbols().add(mark);
gr.setSize(filterFactory.literal(5));
/*
* Setting the geometryPropertyName arg to null signals that we want to draw the default
* geomettry of features
*/
PointSymbolizer sym = styleFactory.createPointSymbolizer(gr, null);
Rule rule = styleFactory.createRule();
rule.symbolizers().add(sym);
FeatureTypeStyle fts = styleFactory.createFeatureTypeStyle(new Rule[] { rule });
Style style = styleFactory.createStyle();
style.featureTypeStyles().add(fts);
return style;
}
}------------------------------------------------------------------------------
Throughout its 18-year history, RSA Conference consistently attracts the
world's best and brightest in the field, creating opportunities for Conference
attendees to learn about information security's most important issues through
interactions with peers, luminaries and emerging and established companies.
http://p.sf.net/sfu/rsaconf-dev2dev
_______________________________________________
Geotools-gt2-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/geotools-gt2-users