Search in sources :

Example 1 with FILTER_FACTORY

use of org.geotoolkit.display2d.GO2Utilities.FILTER_FACTORY in project geotoolkit by Geomatys.

the class RenderingRoutines method prepareQuery.

/**
 * Creates an optimal query to send to the datastore, knowing which properties are knowned and
 * the appropriate bounding box to filter.
 */
public static Query prepareQuery(final RenderingContext2D renderingContext, FeatureSet fs, final MapLayer layer, final Set<String> styleRequieredAtts, final List<Rule> rules, double symbolsMargin) throws PortrayalException {
    final FeatureType schema;
    try {
        schema = fs.getType();
    } catch (DataStoreException ex) {
        throw new PortrayalException(ex.getMessage(), ex);
    }
    // Note: do not use layer boundary to define the target bbox, because it can be expensive.
    // Anyway, the target resource will be better to determine clipping between rendering boundaries and its own.
    final Envelope bbox = optimizeBBox(renderingContext, fs, symbolsMargin);
    final CoordinateReferenceSystem layerCRS = FeatureExt.getCRS(schema);
    final RenderingHints hints = renderingContext.getRenderingHints();
    /*
         * To restrict queried values to the rendering area, we must identify what geometries are used by the style.
         * For each applied symbol, there are 3 possible cases:
         * - if the rule uses default geometries, they will be added to the geometry property list after the loop
         * - The geometric expression is a value reference, we can safely register it in geometric properties. The
         *   reference xpath is unwrapped in a set to ensure we won't create any doublon filters.
         * - If the geometry property is a complex expression(Ex: a value computed from non geometric fields), we keep
         *   it as is to apply a filter directly upon it. Note that even if it's an expression derived from geometric
         *   fields, we cannot apply spatial filter on them, because the expression could drastically change topology.
         *   For example, if the expression is 'buffer', the result geometry would be larger than any of its operands.
         *   TODO: such cases are maybe manageable by replacing bbox filter by a distance filter based upon the buffer
         *   distance. But would it do more good than harm ?
         */
    boolean isDefaultGeometryNeeded = rules == null || rules.isEmpty();
    final Set<String> geomProperties = new HashSet<>();
    final Set<Expression> complexProperties = new HashSet<>();
    if (rules != null) {
        for (Rule r : rules) {
            for (Symbolizer s : r.symbolizers()) {
                final Expression expGeom = s.getGeometry();
                if (isNil(expGeom))
                    isDefaultGeometryNeeded = true;
                else if (expGeom instanceof ValueReference)
                    geomProperties.add(((ValueReference) expGeom).getXPath());
                else
                    complexProperties.add(expGeom);
            }
        }
    }
    if (isDefaultGeometryNeeded) {
        try {
            final PropertyType defaultGeometry = FeatureExt.getDefaultGeometry(schema);
            final String geomName = Features.getLinkTarget(defaultGeometry).orElseGet(() -> defaultGeometry.getName().toString());
            geomProperties.add(geomName);
        } catch (PropertyNotFoundException e) {
            throw new PortrayalException("Default geometry cannot be determined. " + "However, it is needed to properly define filtering rules.");
        } catch (IllegalStateException e) {
            // If there's multiple geometric properties, and no primary one, we will use them all
            schema.getProperties(true).stream().filter(p -> !Features.getLinkTarget(p).isPresent()).filter(AttributeConvention::isGeometryAttribute).map(p -> p.getName().toString()).forEach(geomProperties::add);
        }
    }
    if (!complexProperties.isEmpty()) {
        LOGGER.fine("A style rule uses complex geometric properties. It can severly affect performance");
    }
    final Optional<Filter> spatialFilter = Stream.concat(geomProperties.stream().map(FILTER_FACTORY::property), complexProperties.stream()).<Filter>map(expression -> FILTER_FACTORY.bbox(expression, bbox)).reduce(FILTER_FACTORY::or);
    Filter userFilter = null;
    // concatenate geographic filter with data filter if there is one
    if (layer != null) {
        Query query = layer.getQuery();
        if (query instanceof FeatureQuery) {
            userFilter = ((FeatureQuery) query).getSelection();
        }
    }
    Filter filter;
    if (spatialFilter.isPresent()) {
        if (userFilter == null)
            filter = spatialFilter.get();
        else
            // Note: we give priority to the spatial filter here, because it is our main use case: rendering is driven
            // by bounding box.
            filter = FILTER_FACTORY.and(spatialFilter.get(), userFilter);
    } else if (userFilter == null) {
        throw new PortrayalException("No spatial filter can be determined from style rules, and no user filter specified." + "We refuse dataset full-scan. To authorize it, manually specify Filter 'INCLUDE' on your map layer.");
    } else {
        LOGGER.warning("Spatial filter cannot be determined for rendering. However, user has provided a custom filter that we'll use as sole filtering policy");
        filter = userFilter;
    }
    final Set<String> copy = new HashSet<>();
    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);
        try {
            // always include the identifier if it exist
            schema.getProperty(AttributeConvention.IDENTIFIER);
            copy.add(AttributeConvention.IDENTIFIER);
        } catch (PropertyNotFoundException ex) {
        // no id, ignore it
        }
        atts = copy.toArray(new String[copy.size()]);
        // 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
                // remove first slash
                attName = attName.substring(1);
                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 ViewMapper(schema, atts).getMappedType();
        } catch (MismatchedFeatureException ex) {
            throw new PortrayalException(ex);
        }
    }
    // combine the filter with rule filters----------------------------------
    if (rules != null) {
        List<Filter<Object>> 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 org.geotoolkit.storage.feature.query.Query qb = new org.geotoolkit.storage.feature.query.Query();
    qb.setTypeName(schema.getName());
    qb.setSelection(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
            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);
            try {
                res = renderingContext.getResolution(CRS.forCode("EPSG:3395"));
                res[0] *= factor;
                res[1] *= factor;
                qb.setLinearResolution(Quantities.create(res[0], Units.METRE));
            } catch (FactoryException ex) {
                throw new PortrayalException(ex.getMessage(), ex);
            }
        }
    // 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(Hints.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;
}
Also used : Expression(org.opengis.filter.Expression) Hints(org.geotoolkit.factory.Hints) Envelope2D(org.apache.sis.geometry.Envelope2D) CRS(org.apache.sis.referencing.CRS) PortrayalException(org.geotoolkit.display.PortrayalException) RenderingHints(java.awt.RenderingHints) Envelope(org.opengis.geometry.Envelope) GeographicBoundingBox(org.opengis.metadata.extent.GeographicBoundingBox) FeatureType(org.opengis.feature.FeatureType) Matcher(java.util.regex.Matcher) ValueReference(org.opengis.filter.ValueReference) TransformException(org.opengis.referencing.operation.TransformException) GeneralEnvelope(org.apache.sis.geometry.GeneralEnvelope) Feature(org.opengis.feature.Feature) Utilities(org.apache.sis.util.Utilities) MutableFeatureTypeStyle(org.geotoolkit.style.MutableFeatureTypeStyle) MismatchedFeatureException(org.opengis.feature.MismatchedFeatureException) Set(java.util.Set) FeatureIterator(org.geotoolkit.storage.feature.FeatureIterator) Literal(org.opengis.filter.Literal) FilterUtilities(org.geotoolkit.filter.FilterUtilities) List(java.util.List) Stream(java.util.stream.Stream) ProjectedFeature(org.geotoolkit.display2d.primitive.ProjectedFeature) FeatureExt(org.geotoolkit.feature.FeatureExt) Optional(java.util.Optional) Pattern(java.util.regex.Pattern) Rule(org.opengis.style.Rule) Envelopes(org.apache.sis.geometry.Envelopes) Query(org.apache.sis.storage.Query) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) FactoryException(org.opengis.util.FactoryException) MutableRule(org.geotoolkit.style.MutableRule) ViewMapper(org.geotoolkit.feature.ViewMapper) GO2Utilities(org.geotoolkit.display2d.GO2Utilities) ProjectedObject(org.geotoolkit.display2d.primitive.ProjectedObject) STYLE_FACTORY(org.geotoolkit.display2d.GO2Utilities.STYLE_FACTORY) FeatureQuery(org.apache.sis.storage.FeatureQuery) FILTER_FACTORY(org.geotoolkit.display2d.GO2Utilities.FILTER_FACTORY) FeatureTypeStyle(org.opengis.style.FeatureTypeStyle) ArrayList(java.util.ArrayList) Level(java.util.logging.Level) HashSet(java.util.HashSet) RenderingContext2D(org.geotoolkit.display2d.canvas.RenderingContext2D) LOGGER(org.geotoolkit.display2d.GO2Utilities.LOGGER) DataStoreException(org.apache.sis.storage.DataStoreException) CanvasMonitor(org.geotoolkit.display.canvas.control.CanvasMonitor) FeatureStoreRuntimeException(org.geotoolkit.storage.feature.FeatureStoreRuntimeException) GO2Hints(org.geotoolkit.display2d.GO2Hints) MutableStyle(org.geotoolkit.style.MutableStyle) Units(org.apache.sis.measure.Units) MapLayer(org.apache.sis.portrayal.MapLayer) Iterator(java.util.Iterator) Quantities(org.apache.sis.measure.Quantities) IOException(java.io.IOException) Features(org.apache.sis.feature.Features) PropertyNotFoundException(org.opengis.feature.PropertyNotFoundException) StyleUtilities(org.geotoolkit.style.StyleUtilities) PropertyType(org.opengis.feature.PropertyType) AttributeConvention(org.apache.sis.internal.feature.AttributeConvention) FeatureSet(org.apache.sis.storage.FeatureSet) Closeable(java.io.Closeable) Filter(org.opengis.filter.Filter) Symbolizer(org.opengis.style.Symbolizer) FeatureType(org.opengis.feature.FeatureType) PropertyNotFoundException(org.opengis.feature.PropertyNotFoundException) Query(org.apache.sis.storage.Query) FeatureQuery(org.apache.sis.storage.FeatureQuery) Hints(org.geotoolkit.factory.Hints) RenderingHints(java.awt.RenderingHints) GO2Hints(org.geotoolkit.display2d.GO2Hints) Matcher(java.util.regex.Matcher) FactoryException(org.opengis.util.FactoryException) ArrayList(java.util.ArrayList) PropertyType(org.opengis.feature.PropertyType) FeatureQuery(org.apache.sis.storage.FeatureQuery) Envelope(org.opengis.geometry.Envelope) GeneralEnvelope(org.apache.sis.geometry.GeneralEnvelope) Feature(org.opengis.feature.Feature) ProjectedFeature(org.geotoolkit.display2d.primitive.ProjectedFeature) RenderingHints(java.awt.RenderingHints) CoordinateReferenceSystem(org.opengis.referencing.crs.CoordinateReferenceSystem) HashSet(java.util.HashSet) ValueReference(org.opengis.filter.ValueReference) Pattern(java.util.regex.Pattern) DataStoreException(org.apache.sis.storage.DataStoreException) FILTER_FACTORY(org.geotoolkit.display2d.GO2Utilities.FILTER_FACTORY) ViewMapper(org.geotoolkit.feature.ViewMapper) Symbolizer(org.opengis.style.Symbolizer) MutableRule(org.geotoolkit.style.MutableRule) Expression(org.opengis.filter.Expression) Filter(org.opengis.filter.Filter) MismatchedFeatureException(org.opengis.feature.MismatchedFeatureException) Rule(org.opengis.style.Rule) MutableRule(org.geotoolkit.style.MutableRule) PortrayalException(org.geotoolkit.display.PortrayalException)

Aggregations

RenderingHints (java.awt.RenderingHints)1 Closeable (java.io.Closeable)1 IOException (java.io.IOException)1 ArrayList (java.util.ArrayList)1 HashSet (java.util.HashSet)1 Iterator (java.util.Iterator)1 List (java.util.List)1 Optional (java.util.Optional)1 Set (java.util.Set)1 Level (java.util.logging.Level)1 Matcher (java.util.regex.Matcher)1 Pattern (java.util.regex.Pattern)1 Stream (java.util.stream.Stream)1 Features (org.apache.sis.feature.Features)1 Envelope2D (org.apache.sis.geometry.Envelope2D)1 Envelopes (org.apache.sis.geometry.Envelopes)1 GeneralEnvelope (org.apache.sis.geometry.GeneralEnvelope)1 AttributeConvention (org.apache.sis.internal.feature.AttributeConvention)1 Quantities (org.apache.sis.measure.Quantities)1 Units (org.apache.sis.measure.Units)1