/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program 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 General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.rcp.layermanager.layersrc.shapefile; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.glayer.Layer; import com.bc.ceres.glayer.LayerType; import com.bc.ceres.grender.Rendering; import org.geotools.feature.FeatureCollection; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.map.DefaultMapContext; import org.geotools.map.MapContext; import org.geotools.map.MapLayer; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.renderer.label.LabelCacheImpl; import org.geotools.renderer.lite.LabelCache; import org.geotools.renderer.lite.StreamingRenderer; import org.geotools.styling.Fill; import org.geotools.styling.PolygonSymbolizer; import org.geotools.styling.Stroke; import org.geotools.styling.Style; import org.geotools.styling.StyleBuilder; import org.geotools.styling.TextSymbolizer; import org.geotools.styling.visitor.DuplicatingStyleVisitor; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.expression.Expression; import org.opengis.referencing.crs.CoordinateReferenceSystem; import java.awt.Color; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.beans.PropertyChangeEvent; import java.util.HashMap; import java.util.Map; /** * A layer that renders a feature collection using a given style. * <p> * Unstable API. Use at own risk. * * @author Marco Peters * @author Marco Zühlke * @version $Revision: $ $Date: $ * @since BEAM 4.6 */ public class FeatureLayer extends Layer { private MapContext mapContext; private CoordinateReferenceSystem crs; private StreamingRenderer renderer; private LabelCache labelCache; private double polyFillOpacity = 1.0; private double polyStrokeOpacity = 1.0; private double textOpacity = 1.0; private Rectangle2D modelBounds; public FeatureLayer(LayerType layerType, final FeatureCollection<SimpleFeatureType, SimpleFeature> fc, PropertySet configuration) { super(layerType, configuration); crs = fc.getSchema().getGeometryDescriptor().getCoordinateReferenceSystem(); if (crs == null) { // todo - check me! Why can this happen??? (nf) crs = DefaultGeographicCRS.WGS84; } final ReferencedEnvelope envelope = new ReferencedEnvelope(fc.getBounds(), crs); modelBounds = new Rectangle2D.Double(envelope.getMinX(), envelope.getMinY(), envelope.getWidth(), envelope.getHeight()); mapContext = new DefaultMapContext(crs); final Style style = (Style) configuration.getValue(FeatureLayerType.PROPERTY_NAME_SLD_STYLE); mapContext.addLayer(fc, style); renderer = new StreamingRenderer(); workaroundLabelCacheBug(); style.accept(new RetrievingStyleVisitor()); renderer.setContext(mapContext); } @Override protected Rectangle2D getLayerModelBounds() { return modelBounds; } public double getPolyFillOpacity() { return polyFillOpacity; } public double getPolyStrokeOpacity() { return polyStrokeOpacity; } public double getTextOpacity() { return textOpacity; } public void setPolyFillOpacity(double opacity) { if (opacity != polyFillOpacity) { polyFillOpacity = opacity; applyOpacity(); fireLayerDataChanged(null); } } public void setPolyStrokeOpacity(double opacity) { if (opacity != polyStrokeOpacity) { polyStrokeOpacity = opacity; applyOpacity(); fireLayerDataChanged(null); } } public void setTextOpacity(double opacity) { if (opacity != textOpacity) { textOpacity = opacity; applyOpacity(); fireLayerDataChanged(null); } } @Override protected void fireLayerPropertyChanged(PropertyChangeEvent event) { if ("transparency".equals(event.getPropertyName())) { applyOpacity(); } super.fireLayerPropertyChanged(event); } private void workaroundLabelCacheBug() { Map<Object, Object> hints = (Map<Object, Object>) renderer.getRendererHints(); if (hints == null) { hints = new HashMap<Object, Object>(); } if (hints.containsKey(StreamingRenderer.LABEL_CACHE_KEY)) { labelCache = (LabelCache) hints.get(StreamingRenderer.LABEL_CACHE_KEY); } else { labelCache = new LabelCacheImpl(); hints.put(StreamingRenderer.LABEL_CACHE_KEY, labelCache); } renderer.setRendererHints(hints); } @Override protected void renderLayer(final Rendering rendering) { Rectangle bounds = rendering.getViewport().getViewBounds(); final AffineTransform v2mTransform = rendering.getViewport().getViewToModelTransform(); Rectangle2D bounds2D = v2mTransform.createTransformedShape(bounds).getBounds2D(); ReferencedEnvelope mapArea = new ReferencedEnvelope(bounds2D, crs); mapContext.setAreaOfInterest(mapArea); labelCache.clear(); // workaround for labelCache bug final AffineTransform modelToViewTransform = rendering.getViewport().getModelToViewTransform(); renderer.paint(rendering.getGraphics(), bounds, mapArea, modelToViewTransform); } private void applyOpacity() { final MapLayer layer = mapContext.getLayer(0); if (layer != null) { Style style = layer.getStyle(); DuplicatingStyleVisitor copyStyle = new ApplyingStyleVisitor(); style.accept(copyStyle); layer.setStyle((Style) copyStyle.getCopy()); } } private class ApplyingStyleVisitor extends DuplicatingStyleVisitor { private final Expression polyFillExp; private final Expression polyStrokeExp; private final Expression textExp; private final Fill defaultTextFill; private ApplyingStyleVisitor() { StyleBuilder sb = new StyleBuilder(); final double layerOpacity = 1.0 - getTransparency(); polyFillExp = sb.literalExpression(polyFillOpacity * layerOpacity); polyStrokeExp = sb.literalExpression(polyStrokeOpacity * layerOpacity); textExp = sb.literalExpression(textOpacity * layerOpacity); defaultTextFill = sb.createFill(Color.BLACK, textOpacity * layerOpacity); } @Override public void visit(PolygonSymbolizer poly) { super.visit(poly); PolygonSymbolizer polyCopy = (PolygonSymbolizer) pages.peek(); Fill polyFill = polyCopy.getFill(); if (polyFill != null) { polyFill.setOpacity(polyFillExp); } Stroke polyStroke = polyCopy.getStroke(); if (polyStroke != null) { polyStroke.setOpacity(polyStrokeExp); } } @Override public void visit(TextSymbolizer text) { super.visit(text); TextSymbolizer textCopy = (TextSymbolizer) pages.peek(); Fill textFill = textCopy.getFill(); if (textFill != null) { textFill.setOpacity(textExp); } else { textCopy.setFill(defaultTextFill); } } } private class RetrievingStyleVisitor extends DuplicatingStyleVisitor { @Override public void visit(PolygonSymbolizer poly) { super.visit(poly); PolygonSymbolizer polyCopy = (PolygonSymbolizer) pages.peek(); Fill polyFill = polyCopy.getFill(); if (polyFill != null) { Expression opacityExpression = polyFill.getOpacity(); if (opacityExpression != null) { polyFillOpacity = (opacityExpression.evaluate(opacityExpression, Double.class)); } } Stroke polyStroke = polyCopy.getStroke(); if (polyStroke != null) { Expression opacityExpression = polyStroke.getOpacity(); if (opacityExpression != null) { polyStrokeOpacity = opacityExpression.evaluate(opacityExpression, Double.class); } } } @Override public void visit(TextSymbolizer text) { super.visit(text); TextSymbolizer textCopy = (TextSymbolizer) pages.peek(); Fill textFill = textCopy.getFill(); if (textFill != null) { Expression opacityExpression = textFill.getOpacity(); if (opacityExpression != null) { textOpacity = opacityExpression.evaluate(opacityExpression, Double.class); } } } } }