/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2015, Open Source Geospatial Foundation (OSGeo) * * 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.geotools.renderer.lite; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.Rectangle; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.geotools.data.FeatureSource; import org.geotools.data.Query; import org.geotools.factory.CommonFactoryFinder; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.DirectLayer; import org.geotools.map.FeatureLayer; import org.geotools.map.Layer; import org.geotools.map.MapContent; import org.geotools.map.MapViewport; import org.geotools.map.event.MapLayerListener; import org.geotools.renderer.style.SLDStyleFactory; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Style; import org.geotools.styling.StyleFactory; import org.geotools.util.logging.Logging; /** * Data structure holding a MapContent that has its own compositing base * * @author Andrea Aime - GeoSolutions */ class CompositingGroup { private static final Logger LOGGER = Logging.getLogger(CompositingGroup.class); private static StyleFactory STYLE_FACTORY = CommonFactoryFinder.getStyleFactory(); public static List<CompositingGroup> splitOnCompositingBase(Graphics2D graphics, Rectangle screenSize, MapContent mc) { List<CompositingGroup> result = new ArrayList<>(); List<Layer> layers = new ArrayList<>(); for (Layer layer : mc.layers()) { Style style = layer.getStyle(); if (layer instanceof DirectLayer) { layers.add(new WrappingDirectLayer((DirectLayer) layer)); } else if (layer instanceof ZGroupLayer) { ZGroupLayer zLayer = (ZGroupLayer) layer; if(zLayer.isCompositingBase()) { addToCompositingMapContents(graphics, screenSize, result, layers); } layers.add(layer); } else { List<Style> styles = splitOnCompositingBase(style); for (Style s : styles) { FeatureTypeStyle firstFts = s.featureTypeStyles().get(0); if (isCompositingBase(firstFts) && !layers.isEmpty()) { addToCompositingMapContents(graphics, screenSize, result, layers); } if (s == style) { layers.add(layer); } else { // all the streaming renderer cares about is normally contained // in a feature layer FeatureLayer clone = new FeatureLayer(layer.getFeatureSource(), s); clone.setQuery(layer.getQuery()); clone.setVisible(layer.isVisible()); clone.setSelected(layer.isSelected()); clone.getUserData().putAll(layer.getUserData()); layers.add(clone); } } } } // do we have it simple? if (!layers.isEmpty()) { addToCompositingMapContents(graphics, screenSize, result, layers); } return result; } private static void addToCompositingMapContents(Graphics2D graphics, Rectangle screenSize, List<CompositingGroup> compositingContents, List<Layer> layers) { Composite composite = getComposite(layers); addToCompositingMapContents(graphics, screenSize, compositingContents, layers, composite); } private static void addToCompositingMapContents(Graphics2D graphics, Rectangle screenSize, List<CompositingGroup> compositingContents, List<Layer> layers, Composite composite) { Graphics2D cmcGraphic; if (compositingContents.size() == 0 && !hasAlphaCompositing(layers)) { cmcGraphic = graphics; } else { cmcGraphic = new DelayedBackbufferGraphic(graphics, screenSize); } MapContent current = new MapContent(); current.addLayers(layers); CompositingGroup cmc = new CompositingGroup(cmcGraphic, current, composite); compositingContents.add(cmc); layers.clear(); } private static Composite getComposite(List<Layer> layers) { Layer layer = layers.get(0); if(layer instanceof ZGroupLayer) { return ((ZGroupLayer) layer).getComposite(); } Style styles = layer.getStyle(); List<FeatureTypeStyle> featureTypeStyles = styles.featureTypeStyles(); if (featureTypeStyles.size() > 0) { FeatureTypeStyle firstFts = featureTypeStyles.get(0); Composite composite = SLDStyleFactory.getComposite(firstFts.getOptions()); return composite; } else { return null; } } /** * Returns true if alpha compositing is used anywhere in the style * * @param currentFeature * @return */ private static boolean hasAlphaCompositing(List<Layer> layers) { AlphaCompositeVisitor visitor = new AlphaCompositeVisitor(); for (Layer layer : layers) { Style style = layer.getStyle(); style.accept(visitor); if (visitor.alphaComposite) { return true; } } return false; } private static List<Style> splitOnCompositingBase(Style style) { List<Style> styles = new ArrayList<>(); List<FeatureTypeStyle> featureTypeStyles = new ArrayList<>(); for (FeatureTypeStyle fts : style.featureTypeStyles()) { if (isCompositingBase(fts)) { addToStyles(styles, featureTypeStyles); } featureTypeStyles.add(fts); } addToStyles(styles, featureTypeStyles); return styles; } private static void addToStyles(List<Style> styles, List<FeatureTypeStyle> featureTypeStyles) { if (!featureTypeStyles.isEmpty()) { Style s = STYLE_FACTORY.createStyle(); s.featureTypeStyles().addAll(featureTypeStyles); styles.add(s); featureTypeStyles.clear(); } } static boolean isCompositingBase(FeatureTypeStyle fts) { return "true".equalsIgnoreCase(fts.getOptions().get(FeatureTypeStyle.COMPOSITE_BASE)); } Graphics2D graphics; MapContent mapContent; Composite composite; CompositingGroup(Graphics2D graphics, MapContent mapContent, Composite composite) { super(); this.graphics = graphics; this.mapContent = mapContent; this.composite = composite; } public Graphics2D getGraphics() { return graphics; } public MapContent getMapContent() { return mapContent; } public Composite getComposite() { return composite; } /** * Wraps direct layer so that dispose does not get called when wrapping * inside a compositing group * @author Andrea Aime */ static class WrappingDirectLayer extends DirectLayer { DirectLayer delegate; public WrappingDirectLayer(DirectLayer delegate) { super(); this.delegate = delegate; //this prevents the annoying message about not calling predispose //as we have no listeners it has no effect super.preDispose(); } public void draw(Graphics2D graphics, MapContent map, MapViewport viewport) { delegate.draw(graphics, map, viewport); } public void preDispose() { //do nothing so as not to kill off the layer //before the label cache is completed } public void setTitle(String title) { delegate.setTitle(title); } public boolean isSelected() { return delegate.isSelected(); } public ReferencedEnvelope getBounds() { return delegate.getBounds(); } public void addMapLayerListener(MapLayerListener listener) { delegate.addMapLayerListener(listener); } public boolean equals(Object arg0) { return delegate.equals(arg0); } public String getTitle() { return delegate.getTitle(); } public boolean isVisible() { return delegate.isVisible(); } public void setVisible(boolean visible) { delegate.setVisible(visible); } public void setSelected(boolean selected) { delegate.setSelected(selected); } public Map<String, Object> getUserData() { return delegate.getUserData(); } public void removeMapLayerListener(MapLayerListener listener) { delegate.removeMapLayerListener(listener); } public Style getStyle() { return delegate.getStyle(); } public FeatureSource<?, ?> getFeatureSource() { return delegate.getFeatureSource(); } public Query getQuery() { return delegate.getQuery(); } public int hashCode() { return delegate.hashCode(); } public String toString() { return delegate.toString(); } } }