/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008 - 2014, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. */ package org.geotoolkit.display2d.container.stateless; import java.awt.RenderingHints; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.sis.feature.FeatureExt; import org.apache.sis.feature.FeatureTypeExt; import org.apache.sis.feature.ViewFeatureType; import org.apache.sis.geometry.Envelope2D; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.crs.DefaultTemporalCRS; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.data.FeatureStoreContentEvent; import org.geotoolkit.data.FeatureStoreListener; import org.geotoolkit.data.FeatureStoreManagementEvent; import org.geotoolkit.data.FeatureStoreRuntimeException; import org.geotoolkit.data.memory.GenericCachedFeatureIterator; import org.geotoolkit.data.query.Query; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.data.session.Session; import org.geotoolkit.display.PortrayalException; import org.geotoolkit.display.SearchArea; import org.geotoolkit.display.VisitFilter; import org.geotoolkit.display.canvas.RenderingContext; import org.geotoolkit.display.canvas.control.CanvasMonitor; import org.geotoolkit.display2d.GO2Hints; import org.geotoolkit.display2d.GO2Utilities; import static org.geotoolkit.display2d.GO2Utilities.*; import org.geotoolkit.display2d.canvas.J2DCanvas; import org.geotoolkit.display2d.canvas.RenderingContext2D; import org.geotoolkit.display2d.container.stateless.StatelessCollectionLayerJ2D.RenderingIterator; import org.geotoolkit.display2d.primitive.DefaultSearchAreaJ2D; import org.geotoolkit.display2d.primitive.GraphicJ2D; import org.geotoolkit.display2d.primitive.ProjectedFeature; import org.geotoolkit.display2d.primitive.ProjectedObject; import org.geotoolkit.display2d.primitive.SearchAreaJ2D; import org.geotoolkit.display2d.style.CachedRule; import org.geotoolkit.display2d.style.CachedSymbolizer; import org.geotoolkit.display2d.style.renderer.SymbolizerRenderer; import org.geotoolkit.display2d.style.renderer.SymbolizerRendererService; import org.geotoolkit.factory.Hints; import org.geotoolkit.factory.HintsPending; import org.opengis.util.GenericName; import org.geotoolkit.filter.DefaultLiteral; import org.geotoolkit.filter.DefaultPropertyName; import org.geotoolkit.filter.FilterUtilities; import org.geotoolkit.filter.binaryspatial.LooseBBox; import org.geotoolkit.filter.binaryspatial.UnreprojectedLooseBBox; import org.geotoolkit.geometry.DefaultBoundingBox; import org.geotoolkit.map.FeatureMapLayer; import org.geotoolkit.map.GraphicBuilder; import org.geotoolkit.map.MapLayer; import org.geotoolkit.referencing.operation.matrix.XAffineTransform; import org.geotoolkit.style.MutableRule; import org.geotoolkit.style.StyleUtilities; import org.opengis.display.primitive.Graphic; import org.opengis.feature.MismatchedFeatureException; import org.opengis.filter.Filter; import org.opengis.filter.expression.Expression; import org.opengis.filter.expression.PropertyName; import org.opengis.filter.identity.FeatureId; import org.opengis.geometry.BoundingBox; import org.opengis.geometry.Envelope; import org.opengis.metadata.extent.GeographicBoundingBox; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.operation.TransformException; import org.opengis.style.Rule; import org.opengis.style.Symbolizer; import org.apache.sis.geometry.Envelopes; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.util.Utilities; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.opengis.feature.PropertyNotFoundException; import org.opengis.feature.PropertyType; /** * Single object to represent a complete feature map layer. * This is a Stateless graphic object. * * @author johann sorel (Geomatys) * @module */ public class StatelessFeatureLayerJ2D extends StatelessCollectionLayerJ2D<FeatureMapLayer> implements FeatureStoreListener{ protected FeatureStoreListener.Weak weakSessionListener = new FeatureStoreListener.Weak(this); protected Query currentQuery = null; // symbols margins, in objective CRS units, used to expand query and intersection enveloppes. private double symbolsMargin = 0.0; public StatelessFeatureLayerJ2D(final J2DCanvas canvas, final FeatureMapLayer layer){ super(canvas, layer); final Session session = layer.getCollection().getSession(); weakSessionListener.registerSource(session); } @Override public void structureChanged(FeatureStoreManagementEvent event) { if(item.isVisible() && getCanvas().isAutoRepaint()){ //TODO should call a repaint only on this graphic getCanvas().repaint(); } } @Override public void contentChanged(FeatureStoreContentEvent event) { if(item.isVisible() && getCanvas().isAutoRepaint()){ //TODO should call a repaint only on this graphic getCanvas().repaint(); } } @Override protected StatelessContextParams getStatefullParameters(final RenderingContext2D context){ params.update(context); //expand the search area by the maximum symbol size if(symbolsMargin>0 && params.objectiveJTSEnvelope!=null){ params.objectiveJTSEnvelope = new com.vividsolutions.jts.geom.Envelope(params.objectiveJTSEnvelope); params.objectiveJTSEnvelope.expandBy(symbolsMargin); } return params; } /** * {@inheritDoc } */ @Override public void paintLayer(final RenderingContext2D renderingContext) { //search for a special graphic renderer final GraphicBuilder<GraphicJ2D> builder = (GraphicBuilder<GraphicJ2D>) item.getGraphicBuilder(GraphicJ2D.class); if(builder != null){ //let the parent class handle it super.paintLayer(renderingContext); return; } if(Boolean.TRUE.equals(item.getUserProperty(MapLayer.USERKEY_STYLED_FEATURE))){ //feature have self defined styles. renderStyledFeature(renderingContext); return; } //first extract the valid rules at this scale final List<Rule> validRules = getValidRules(renderingContext,item,item.getCollection().getFeatureType()); //we perform a first check on the style to see if there is at least //one valid rule at this scale, if not we just continue. if(validRules.isEmpty()){ return; } //extract the used names Set<String> names = propertiesNames(validRules); if(names.contains("*")){ //we need all properties names = null; } //calculate max symbol size, to expand search envelope. symbolsMargin = 0.0; for (Rule rule : validRules) { for (Symbolizer s : rule.symbolizers()) { final CachedSymbolizer cs = GO2Utilities.getCached(s, null); symbolsMargin = Math.max(symbolsMargin, cs.getMargin(null, renderingContext)); } } if (Double.isNaN(symbolsMargin) || Double.isInfinite(symbolsMargin)) { //symbol margin can not be pre calculated, expect a max of 300pixels symbolsMargin = 300f; } if (symbolsMargin > 0) { final double scale = XAffineTransform.getScale(renderingContext.getDisplayToObjective()); symbolsMargin = scale * symbolsMargin; } final FeatureCollection candidates; try { //optimize candidates = (FeatureCollection)optimizeCollection(renderingContext, names, validRules); } catch (Exception ex) { renderingContext.getMonitor().exceptionOccured(ex, Level.WARNING); return; } //get the expected result type final FeatureType expected = candidates.getFeatureType(); //calculate optimized rules and included filter + expressions final CachedRule[] rules = toCachedRules(validRules, expected); paintVectorLayer(rules, candidates, renderingContext); } /** * Render styled features. * * @param context */ private void renderStyledFeature(final RenderingContext2D context){ final CanvasMonitor monitor = context.getMonitor(); final FeatureCollection candidates; try { candidates = (FeatureCollection)optimizeCollection(context); } catch (Exception ex) { context.getMonitor().exceptionOccured(ex, Level.WARNING); return; } final RenderingIterator statefullIterator = getIterator(candidates, context, getStatefullParameters(context)); //prepare the rendering parameters if(monitor.stopRequested()) return; try{ while(statefullIterator.hasNext()){ if(monitor.stopRequested()) return; final ProjectedObject projectedCandidate = statefullIterator.next(); final Feature feature = (Feature) projectedCandidate.getCandidate(); final List<Symbolizer> symbolizers; try{ symbolizers = (List<Symbolizer>) feature.getPropertyValue(FeatureExt.ATTRIBUTE_SYMBOLIZERS.toString()); }catch(PropertyNotFoundException ex){ continue; } if(symbolizers==null) continue; for(Symbolizer symbolizer : symbolizers){ final SymbolizerRendererService srs = GO2Utilities.findRenderer(symbolizer.getClass()); final CachedSymbolizer cs = srs.createCachedSymbolizer(symbolizer); final SymbolizerRenderer sr = srs.createRenderer(cs, context); try { sr.portray(projectedCandidate); } catch (PortrayalException ex) { monitor.exceptionOccured(ex, Level.WARNING); } } } }finally{ try { statefullIterator.close(); } catch (IOException ex) { getLogger().log(Level.WARNING, null, ex); } } } @Override protected Collection<?> optimizeCollection(final RenderingContext2D context, final Set<String> requieredAtts, final List<Rule> rules) throws Exception { currentQuery = prepareQuery(context, item, requieredAtts, rules, symbolsMargin); //we detach feature since we are going to use a cache. currentQuery.getHints().put(HintsPending.FEATURE_DETACHED,Boolean.TRUE); final Query query = currentQuery; FeatureCollection col = ((FeatureCollection)item.getCollection()).subCollection(query); col = GenericCachedFeatureIterator.wrap(col, 1000); return col; } protected Collection<?> optimizeCollection(final RenderingContext2D context) throws Exception { currentQuery = prepareQuery(context, item, symbolsMargin); //we detach feature since we are going to use a cache. currentQuery.getHints().put(HintsPending.FEATURE_DETACHED,Boolean.TRUE); final Query query = currentQuery; FeatureCollection col = ((FeatureCollection)item.getCollection()).subCollection(query); col = GenericCachedFeatureIterator.wrap(col, 1000); return col; } @Override protected FeatureId id(Object candidate) { return FeatureExt.getId((Feature)candidate); } @Override protected RenderingIterator getIterator(final Collection<?> features, final RenderingContext2D renderingContext, final StatelessContextParams params) { final Hints iteHints = new Hints(HintsPending.FEATURE_DETACHED, Boolean.FALSE); final FeatureIterator iterator = ((FeatureCollection)features).iterator(iteHints); final ProjectedFeature projectedFeature = new ProjectedFeature(params); return new GraphicIterator(iterator, projectedFeature); } /** * {@inheritDoc } */ @Override public List<Graphic> getGraphicAt(final RenderingContext rdcontext, final SearchArea mask, final VisitFilter filter, List<Graphic> graphics) { if(!item.isSelectable()) return graphics; if(!(rdcontext instanceof RenderingContext2D)) return graphics; final RenderingContext2D c2d = (RenderingContext2D) rdcontext; //nothing visible so no possible selection if (!item.isVisible()) return graphics; final GenericName featureTypeName = item.getCollection().getFeatureType().getName(); final CachedRule[] rules = GO2Utilities.getValidCachedRules(item.getStyle(), c2d.getSEScale(), featureTypeName,null); //we perform a first check on the style to see if there is at least //one valid rule at this scale, if not we just return null. if (rules.length == 0) { return graphics; } if(graphics == null) graphics = new ArrayList<>(); if(mask instanceof SearchAreaJ2D){ return searchGraphicAt(item, rules, c2d, (SearchAreaJ2D)mask, filter, graphics); }else{ return searchGraphicAt(item, rules, c2d, new DefaultSearchAreaJ2D(mask), filter, graphics); } } protected List<Graphic> searchGraphicAt(final FeatureMapLayer layer, final CachedRule[] rules, final RenderingContext2D renderingContext, final SearchAreaJ2D mask, final VisitFilter visitFilter, final List<Graphic> graphics) { final Query query; try { final Set<String> attributs = GO2Utilities.propertiesCachedNames(rules); //add identifier property final FeatureType type = getUserObject().getCollection().getFeatureType(); try{ type.getProperty(AttributeConvention.IDENTIFIER_PROPERTY.toString()); attributs.add(AttributeConvention.IDENTIFIER_PROPERTY.toString()); }catch(PropertyNotFoundException ex){} query = prepareQuery(renderingContext, layer, attributs, null, symbolsMargin); } catch (PortrayalException ex) { renderingContext.getMonitor().exceptionOccured(ex, Level.WARNING); return graphics; } final FeatureCollection features; try{ features = ((FeatureCollection)layer.getCollection()).subCollection(query); }catch(DataStoreException ex){ renderingContext.getMonitor().exceptionOccured(ex, Level.WARNING); //can not continue this layer with this error return graphics; } //we do not check if the collection is empty or not since //it can be a very expensive operation final StatelessContextParams params = getStatefullParameters(renderingContext); // iterate and find the first graphic that hit the given point final FeatureIterator iterator; try{ iterator = features.iterator(); }catch(FeatureStoreRuntimeException ex){ renderingContext.getMonitor().exceptionOccured(ex, Level.WARNING); return graphics; } //prepare the renderers final DefaultCachedRule preparedRenderers = new DefaultCachedRule(rules, renderingContext); final ProjectedFeature projectedFeature = new ProjectedFeature(params); try{ while(iterator.hasNext()){ Feature feature = iterator.next(); projectedFeature.setCandidate(feature); boolean painted = false; for(int i=0;i<preparedRenderers.elseRuleIndex;i++){ final CachedRule rule = preparedRenderers.rules[i]; final Filter ruleFilter = rule.getFilter(); //test if the rule is valid for this feature if (ruleFilter == null || ruleFilter.evaluate(feature)) { painted = true; for(SymbolizerRenderer renderer : preparedRenderers.renderers[i]){ if(renderer.hit(projectedFeature, mask, visitFilter)){ if(feature != null) graphics.add( new ProjectedFeature(getCanvas(), layer, feature) ); break; } } } } //the feature hasn't been painted, paint it with the 'else' rules if(!painted){ for(int i=preparedRenderers.elseRuleIndex; i<preparedRenderers.rules.length; i++){ final CachedRule rule = preparedRenderers.rules[i]; final Filter ruleFilter = rule.getFilter(); //test if the rule is valid for this feature if (ruleFilter == null || ruleFilter.evaluate(feature)) { for(SymbolizerRenderer renderer : preparedRenderers.renderers[i]){ if(renderer.hit(projectedFeature, mask, visitFilter)){ if(feature != null) graphics.add( new ProjectedFeature(getCanvas(), layer, feature) ); break; } } } } } } }finally{ iterator.close(); } return graphics; } /** * Creates an optimal query to send to the datastore, knowing which properties are knowned and * the appropriate bounding box to filter. */ protected static Query prepareQuery(final RenderingContext2D renderingContext, final FeatureMapLayer layer, final Set<String> styleRequieredAtts, final List<Rule> rules, double symbolsMargin) throws PortrayalException{ final FeatureCollection fs = layer.getCollection(); final FeatureType schema = fs.getFeatureType(); PropertyType geomDesc = null; try { geomDesc = schema.getProperty(AttributeConvention.GEOMETRY_PROPERTY.toString()); } catch(PropertyNotFoundException ex){}; final BoundingBox bbox = optimizeBBox(renderingContext, layer, symbolsMargin); final CoordinateReferenceSystem layerCRS = FeatureExt.getCRS(schema); final RenderingHints hints = renderingContext.getRenderingHints(); //search used geometries boolean allDefined = true; final Set<String> geomProperties = new HashSet<>(); if(rules!=null){ for(Rule r : rules){ for(Symbolizer s : r.symbolizers()){ final Expression expGeom = s.getGeometry(); if(expGeom instanceof PropertyName){ geomProperties.add( ((PropertyName)expGeom).getPropertyName() ); }else{ allDefined = false; } } } }else{ allDefined = false; } if(geomDesc!=null && !allDefined){ geomProperties.add(geomDesc.getName().toString()); } Filter filter; //final Envelope layerBounds = layer.getBounds(); //we better not do any call to the layer bounding box before since it can be //really expensive, the featurestore is the best placed to check if he might //optimize the filter. //if( ((BoundingBox)bbox).contains(new DefaultBoundingBox(layerBounds))){ //the layer bounds overlaps the bbox, no need for a spatial filter // filter = Filter.INCLUDE; //}else{ //make a bbox filter if(!geomProperties.isEmpty()){ if(geomProperties.size()==1){ final String geomAttName = geomProperties.iterator().next(); if (layerCRS != null) { filter = new UnreprojectedLooseBBox(FILTER_FACTORY.property(geomAttName),new DefaultLiteral<>(bbox)); } else { filter = new LooseBBox(FILTER_FACTORY.property(geomAttName),new DefaultLiteral<>(bbox)); } }else{ //make an OR filter with all geometries final List<Filter> geomFilters = new ArrayList<>(); for(String geomAttName : geomProperties){ geomFilters.add(new LooseBBox(FILTER_FACTORY.property(geomAttName),new DefaultLiteral<>(bbox))); } filter = FILTER_FACTORY.or(geomFilters); } }else{ filter = Filter.EXCLUDE; } //} //concatenate geographic filter with data filter if there is one if(layer.getQuery() != null && layer.getQuery().getFilter() != null){ filter = FILTER_FACTORY.and(filter,layer.getQuery().getFilter()); } final Set<String> copy = new HashSet<>(); //concatenate with temporal range if needed ---------------------------- for (final FeatureMapLayer.DimensionDef def : layer.getExtraDimensions()) { final CoordinateReferenceSystem crs = def.getCrs(); final Envelope canvasEnv = renderingContext.getCanvasObjectiveBounds(); final Envelope dimEnv; try { dimEnv = Envelopes.transform(canvasEnv, crs); } catch (TransformException ex) { continue; } Object min = dimEnv.getMinimum(0); Object max = dimEnv.getMaximum(0); if(crs instanceof DefaultTemporalCRS){ min = ((DefaultTemporalCRS)crs).toDate((Double)min); max = ((DefaultTemporalCRS)crs).toDate((Double)max); } final Filter dimFilter = FILTER_FACTORY.and( FILTER_FACTORY.or( FILTER_FACTORY.isNull(def.getLower()), FILTER_FACTORY.lessOrEqual(def.getLower(), FILTER_FACTORY.literal(max) )), FILTER_FACTORY.or( FILTER_FACTORY.isNull(def.getUpper()), FILTER_FACTORY.greaterOrEqual(def.getUpper(), FILTER_FACTORY.literal(min) )) ); filter = FILTER_FACTORY.and(filter, dimFilter); //add extra dimension property name on attributes list for retype. if (def.getLower() instanceof DefaultPropertyName) { copy.add(((DefaultPropertyName)def.getLower()).getPropertyName()); } if (def.getUpper() instanceof DefaultPropertyName) { copy.add(((DefaultPropertyName)def.getUpper()).getPropertyName()); } } final FeatureType expected; final String[] atts; if(styleRequieredAtts == null){ //all properties are requiered expected = schema; atts = null; }else{ final Set<String> attributs = styleRequieredAtts; copy.addAll(attributs); copy.addAll(geomProperties); atts = copy.toArray(new String[copy.size()]); //check that properties names does not hold sub properties values, if one is found //then we reduce it to the first parent property. for(int i=0; i<atts.length; i++){ String attName = atts[i]; int index = attName.indexOf('/'); if(index == 0){ //remove all xpath elements attName = attName.substring(1); //remove first slash final Pattern pattern = Pattern.compile("(\\{[^\\{\\}]*\\})|(\\[[^\\[\\]]*\\])|/{1}"); final Matcher matcher = pattern.matcher(attName); final StringBuilder sb = new StringBuilder(); int position = 0; while (matcher.find()) { final String match = matcher.group(); sb.append(attName.substring(position, matcher.start())); position = matcher.end(); if(match.charAt(0) == '/'){ //we don't query precisely sub elements position = attName.length(); break; }else if(match.charAt(0) == '{'){ sb.append(match); }else if(match.charAt(0) == '['){ //strip indexes or xpath searches } } sb.append(attName.substring(position)); atts[i] = sb.toString(); } } try { expected = new ViewFeatureType(schema, atts); } catch (MismatchedFeatureException ex) { throw new PortrayalException(ex); } } //combine the filter with rule filters---------------------------------- if(rules != null){ List<Filter> rulefilters = new ArrayList<>(); for(Rule rule : rules){ if(rule.isElseFilter()){ //we can't append styling filters, an else rule match all features rulefilters = null; break; } final Filter rf = rule.getFilter(); if(rf == null || rf == Filter.INCLUDE){ //we can't append styling filters, this rule matchs all features. rulefilters = null; break; } rulefilters.add(rf); } if(rulefilters != null){ final Filter combined; if(rulefilters.size() == 1){ //we can optimze here, since we pass the filter on the query, we can remove //the filter on the rule. final MutableRule mr = StyleUtilities.copy(rules.get(0)); mr.setFilter(null); rules.set(0, mr); combined = rulefilters.get(0); }else{ combined = FILTER_FACTORY.or(rulefilters); } if(filter != Filter.INCLUDE){ filter = FILTER_FACTORY.and(filter,combined); }else{ filter = combined; } } } //optimize the filter--------------------------------------------------- filter = FilterUtilities.prepare(filter,Feature.class,expected); final Hints queryHints = new Hints(); final QueryBuilder qb = new QueryBuilder(); qb.setTypeName(schema.getName()); qb.setFilter(filter); qb.setProperties(atts); //resampling and ignore flag only works when we know the layer crs if(layerCRS != null){ //add resampling ------------------------------------------------------- Boolean resample = (hints == null) ? null : (Boolean) hints.get(GO2Hints.KEY_GENERALIZE); if(!Boolean.FALSE.equals(resample)){ //we only disable resampling if it is explictly specified final double[] res = renderingContext.getResolution(layerCRS); //adjust with the generalization factor final Number n = (hints==null) ? null : (Number)hints.get(GO2Hints.KEY_GENERALIZE_FACTOR); final double factor; if(n != null){ factor = n.doubleValue(); }else{ factor = GO2Hints.GENERALIZE_FACTOR_DEFAULT.doubleValue(); } res[0] *= factor; res[1] *= factor; qb.setResolution(res); } //add ignore flag ------------------------------------------------------ //TODO this is efficient but erases values, when plenty of then are to be rendered //we should find another way to handle this //if(!GO2Utilities.visibleMargin(rules, 1.01f, renderingContext)){ // //style does not expend itself further than the feature geometry // //that mean geometries smaller than a pixel will not be renderer or barely visible // queryHints.put(HintsPending.KEY_IGNORE_SMALL_FEATURES, renderingContext.getResolution(layerCRS)); //} } //add reprojection ----------------------------------------------------- //we don't reproject, the reprojection may produce curves but JTS can not represent those. //so we generate those curves in java2d shapes by doing the transformation ourself. //TODO wait for a new geometry implementation //qb.setCRS(renderingContext.getObjectiveCRS2D()); //set the acumulated hints qb.setHints(queryHints); return qb.buildQuery(); } /** * Creates an optimal query to send to the datastore, knowing which properties are knowned and * the appropriate bounding box to filter. */ protected static Query prepareQuery(final RenderingContext2D renderingContext, final FeatureMapLayer layer, double symbolsMargin) throws PortrayalException{ final FeatureCollection fs = layer.getCollection(); final FeatureType schema = fs.getFeatureType(); final PropertyType geomDesc = FeatureExt.getDefaultGeometryAttribute(schema); final BoundingBox bbox = optimizeBBox(renderingContext,layer,symbolsMargin); final CoordinateReferenceSystem layerCRS = FeatureExt.getCRS(schema); final String geomAttName = (geomDesc!=null)? geomDesc.getName().toString() : null; final RenderingHints hints = renderingContext.getRenderingHints(); Filter filter; //final Envelope layerBounds = layer.getBounds(); //we better not do any call to the layer bounding box before since it can be //really expensive, the featurestore is the best placed to check if he might //optimize the filter. //if( ((BoundingBox)bbox).contains(new DefaultBoundingBox(layerBounds))){ //the layer bounds overlaps the bbox, no need for a spatial filter // filter = Filter.INCLUDE; //}else{ //make a bbox filter if(geomAttName != null){ if (layerCRS != null) { filter = new UnreprojectedLooseBBox(FILTER_FACTORY.property(geomAttName),new DefaultLiteral<>(bbox)); } else { filter = new LooseBBox(FILTER_FACTORY.property(geomAttName),new DefaultLiteral<>(bbox)); } }else{ filter = Filter.EXCLUDE; } //} //concatenate geographic filter with data filter if there is one if(layer.getQuery() != null && layer.getQuery().getFilter() != null){ filter = FILTER_FACTORY.and(filter,layer.getQuery().getFilter()); } //concatenate with temporal range if needed ---------------------------- for (final FeatureMapLayer.DimensionDef def : layer.getExtraDimensions()) { final CoordinateReferenceSystem crs = def.getCrs(); final Envelope canvasEnv = renderingContext.getCanvasObjectiveBounds(); final Envelope dimEnv; try { dimEnv = Envelopes.transform(canvasEnv, crs); } catch (TransformException ex) { continue; } final Filter dimFilter = FILTER_FACTORY.and( FILTER_FACTORY.lessOrEqual(FILTER_FACTORY.literal(dimEnv.getMinimum(0)), def.getLower()), FILTER_FACTORY.greaterOrEqual(FILTER_FACTORY.literal(dimEnv.getMaximum(0)), def.getUpper())); filter = FILTER_FACTORY.and(filter, dimFilter); } //optimize the filter--------------------------------------------------- filter = FilterUtilities.prepare(filter,Feature.class,schema); final Hints queryHints = new Hints(); final QueryBuilder qb = new QueryBuilder(); qb.setTypeName(schema.getName()); qb.setFilter(filter); //resampling and ignore flag only works when we know the layer crs if(layerCRS != null){ //add resampling ------------------------------------------------------- Boolean resample = (hints == null) ? null : (Boolean) hints.get(GO2Hints.KEY_GENERALIZE); if(!Boolean.FALSE.equals(resample)){ //we only disable resampling if it is explictly specified final double[] res = renderingContext.getResolution(layerCRS); //adjust with the generalization factor final Number n = (hints==null) ? null : (Number)hints.get(GO2Hints.KEY_GENERALIZE_FACTOR); final double factor; if(n != null){ factor = n.doubleValue(); }else{ factor = GO2Hints.GENERALIZE_FACTOR_DEFAULT.doubleValue(); } res[0] *= factor; res[1] *= factor; qb.setResolution(res); } //add ignore flag ------------------------------------------------------ //TODO this is efficient but erases values, when plenty of then are to be rendered //we should find another way to handle this //if(!GO2Utilities.visibleMargin(rules, 1.01f, renderingContext)){ // //style does not expend itself further than the feature geometry // //that mean geometries smaller than a pixel will not be renderer or barely visible // queryHints.put(HintsPending.KEY_IGNORE_SMALL_FEATURES, renderingContext.getResolution(layerCRS)); //} } //add reprojection ----------------------------------------------------- //we don't reproject, the reprojection may produce curves but JTS can not represent those. //so we generate those curves in java2d shapes by doing the transformation ourself. //TODO wait for a new geometry implementation //qb.setCRS(renderingContext.getObjectiveCRS2D()); //set the acumulated hints qb.setHints(queryHints); return qb.buildQuery(); } private static BoundingBox optimizeBBox(RenderingContext2D renderingContext, FeatureMapLayer layer, double symbolsMargin){ BoundingBox bbox = renderingContext.getPaintingObjectiveBounds2D(); final CoordinateReferenceSystem bboxCRS = bbox.getCoordinateReferenceSystem(); final CanvasMonitor monitor = renderingContext.getMonitor(); final CoordinateReferenceSystem layerCRS = FeatureExt.getCRS(layer.getCollection().getFeatureType()); //expand the search area by the maximum symbol size if(symbolsMargin>0){ final GeneralEnvelope env = new GeneralEnvelope(bbox); env.setRange(0, env.getMinimum(0)-symbolsMargin, env.getMaximum(0)+symbolsMargin); env.setRange(1, env.getMinimum(1)-symbolsMargin, env.getMaximum(1)+symbolsMargin); bbox = new DefaultBoundingBox(env); } //layer crs may be null if it define an abstract collection //or if the crs is defined only on the feature geometry if(layerCRS != null && !Utilities.equalsIgnoreMetadata(layerCRS,bboxCRS)){ //BBox and layer bounds have different CRS. reproject bbox bounds Envelope env; try{ env = Envelopes.transform(bbox, layerCRS); if(GeneralEnvelope.castOrCopy(env).isEmpty()){ //possible NaN values or out of crs validity area GeneralEnvelope benv = GeneralEnvelope.castOrCopy(bbox); benv.normalize(); env = Envelopes.transform(benv, layerCRS); } }catch(TransformException ex){ //TODO is fixed in geotidy, the result envelope will have infinte values where needed //TODO should do something about this, since canvas bounds may be over the crs bounds monitor.exceptionOccured(ex, Level.WARNING); env = new Envelope2D(); }catch(IllegalArgumentException ex){ //looks like the coordinate of the bbox are outside of the crs valide area. //some crs raise this error, other not. //if so we should reduce our bbox to the valide extent of the crs. monitor.exceptionOccured(ex, Level.WARNING); final GeographicBoundingBox gbox = CRS.getGeographicBoundingBox(layerCRS); if(gbox == null){ env = new GeneralEnvelope(layerCRS); }else{ env = new GeneralEnvelope(gbox); } }catch(Exception ex){ //we should not catch this but we must not block the canvas monitor.exceptionOccured(ex, Level.WARNING); return null; } //TODO looks like the envelope after transform operation doesnt have always exactly the same CRS. //fix CRS classes method and remove the two next lines. env = new GeneralEnvelope(env); ((GeneralEnvelope)env).setCoordinateReferenceSystem(layerCRS); bbox = new DefaultBoundingBox(env); } return bbox; } private static class GraphicIterator implements RenderingIterator{ private final FeatureIterator ite; private final ProjectedFeature projected; public GraphicIterator(final FeatureIterator ite, final ProjectedFeature projected) { this.ite = ite; this.projected = projected; } @Override public boolean hasNext() { return ite.hasNext(); } @Override public ProjectedFeature next() { projected.setCandidate(ite.next()); return projected; } @Override public void remove() { throw new UnsupportedOperationException("Not supported."); } @Override public void close() throws IOException { ite.close(); } } }