net.refractions.udig.legend.ui.LegendGraphic.java Source code

Java tutorial

Introduction

Here is the source code for net.refractions.udig.legend.ui.LegendGraphic.java

Source

/* uDig - User Friendly Desktop Internet GIS client
 * http://udig.refractions.net
 * (C) 2005, Refractions Research Inc.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
 * License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
 */
package net.refractions.udig.legend.ui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.refractions.udig.mapgraphic.MapGraphic;
import net.refractions.udig.mapgraphic.MapGraphicContext;
import net.refractions.udig.mapgraphic.internal.MapGraphicResource;
import net.refractions.udig.mapgraphic.style.FontStyle;
import net.refractions.udig.mapgraphic.style.FontStyleContent;
import net.refractions.udig.mapgraphic.style.LocationStyleContent;
import net.refractions.udig.project.IBlackboard;
import net.refractions.udig.project.ILayer;
import net.refractions.udig.project.internal.Layer;
import net.refractions.udig.project.internal.StyleBlackboard;
import net.refractions.udig.project.ui.internal.LayerGeneratedGlyphDecorator;
import net.refractions.udig.ui.PlatformGIS;
import net.refractions.udig.ui.graphics.AWTSWTImageUtils;
import net.refractions.udig.ui.graphics.SLDs;
import net.refractions.udig.ui.graphics.ViewportGraphics;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.type.Name;

/**
 * Draw a legend based on looking at the current list layer list.
 * 
 * @author Amr
 * @since 1.0.0
 */
public class LegendGraphic implements MapGraphic {

    private int verticalMargin; // distance between border and icons/text
    private int horizontalMargin; // distance between border and icons/text
    private int verticalSpacing; // distance between layers
    private int horizontalSpacing; // space between image and text
    private Color foregroundColour;
    private Color backgroundColour;
    private int indentSize;

    private int imageWidth;
    private int imageHeight; // size of glyph image

    public void draw(MapGraphicContext context) {

        IBlackboard blackboard = context.getLayer().getStyleBlackboard();
        LegendStyle legendStyle = (LegendStyle) blackboard.get(LegendStyleContent.ID);
        if (legendStyle == null) {
            legendStyle = LegendStyleContent.createDefault();
            blackboard.put(LegendStyleContent.ID, legendStyle);
        }

        Rectangle locationStyle = (Rectangle) blackboard.get(LocationStyleContent.ID);
        if (locationStyle == null) {
            locationStyle = new Rectangle(-1, -1, -1, -1);
            blackboard.put(LocationStyleContent.ID, locationStyle);
        }

        FontStyle fontStyle = (FontStyle) blackboard.get(FontStyleContent.ID);
        if (fontStyle == null) {
            fontStyle = new FontStyle();
            blackboard.put(FontStyleContent.ID, fontStyle);
        }

        this.backgroundColour = legendStyle.backgroundColour;
        this.foregroundColour = legendStyle.foregroundColour;
        this.horizontalMargin = legendStyle.horizontalMargin;
        this.verticalMargin = legendStyle.verticalMargin;
        this.horizontalSpacing = legendStyle.horizontalSpacing;
        this.verticalSpacing = legendStyle.verticalSpacing;
        this.indentSize = legendStyle.indentSize;
        this.imageHeight = legendStyle.imageHeight;
        this.imageWidth = legendStyle.imageWidth;

        final ViewportGraphics graphics = context.getGraphics();

        if (fontStyle.getFont() != null) {
            graphics.setFont(fontStyle.getFont());
        }

        List<Map<ILayer, LegendEntry[]>> layers = new ArrayList<Map<ILayer, LegendEntry[]>>();

        int longestRow = 0; // used to calculate the width of the graphic
        final int[] numberOfEntries = new int[1]; // total number of entries to
        // draw
        numberOfEntries[0] = 0;

        /*
         * Set up the layers that we want to draw so we can operate just on
         * those ones. Layers at index 0 are on the bottom of the map, so we
         * must iterate in reverse.
         * 
         * While we are doing this, determine the longest row so we can properly
         * draw the graphic's border.
         */
        Dimension imageSize = new Dimension(imageWidth, imageHeight);
        Dimension textSize = new Dimension(0, graphics.getFontHeight());

        for (int i = context.getMapLayers().size() - 1; i >= 0; i--) {
            ILayer layer = context.getMapLayers().get(i);

            if (!(layer.getGeoResource() instanceof MapGraphicResource) && layer.isVisible()) {

                if (layer.hasResource(MapGraphic.class)) {
                    // don't include mapgraphics
                    continue;
                }
                String layerName = layer.getName();
                if (layerName == null) {
                    layerName = null;
                }
                LegendEntry layerEntry = new LegendEntry(layerName);

                FeatureTypeStyle[] styles = locateStyle(layer);
                LegendEntry[] entries = null;
                if (styles == null) {
                    // we should have a label but no style
                    entries = new LegendEntry[] { layerEntry };
                } else {
                    List<Rule> rules = rules(styles);
                    int ruleCount = rules.size();

                    if (ruleCount == 1 && layer.getGeoResource().canResolve(GridCoverage.class)) {
                        // grid coverage with single rule; lets see if it is a
                        // theming style
                        List<LegendEntry> cmEntries = ColorMapLegendCreator.findEntries(styles, imageSize,
                                textSize);
                        if (cmEntries != null) {
                            cmEntries.add(0, layerEntry); // add layer legend
                            // entry
                            entries = cmEntries.toArray(new LegendEntry[cmEntries.size()]);
                        }
                    }
                    if (entries == null) {
                        List<LegendEntry> localEntries = new ArrayList<LegendEntry>();
                        if (ruleCount == 1) {
                            // only one rule so apply this to the layer legend
                            // entry
                            layerEntry.setRule(rules.get(0));
                        }
                        localEntries.add(layerEntry); // add layer legend entry

                        if (ruleCount > 1) {
                            // we have more than one rule so there is likely
                            // some
                            // themeing going on; add each of these rules
                            for (Rule rule : rules) {
                                LegendEntry rentry = new LegendEntry(rule);
                                localEntries.add(rentry);
                            }
                        }
                        entries = localEntries.toArray(new LegendEntry[localEntries.size()]);
                    }
                }
                layers.add(Collections.singletonMap(layer, entries));

                // compute maximum length for each entry
                for (int j = 0; j < entries.length; j++) {
                    StringBuilder sb = new StringBuilder();
                    for (int k = 0; k < entries[j].getText().length; k++) {
                        sb.append(entries[j].getText()[k]);
                    }
                    Rectangle2D bounds = graphics.getStringBounds(sb.toString());
                    int length = indentSize + imageWidth + horizontalSpacing + (int) bounds.getWidth();

                    if (length > longestRow) {
                        longestRow = length;
                    }
                    numberOfEntries[0]++;
                }
            }
        }

        if (numberOfEntries[0] == 0) {
            // nothing to draw!
            return;
        }

        final int rowHeight = Math.max(imageHeight, graphics.getFontHeight()); // space
        // allocated
        // to
        // each
        // layer

        if (locationStyle.width == 0 || locationStyle.height == 0) {
            // we want to change the location style as needed
            // but not change the saved one so we create a copy here
            locationStyle = new Rectangle(locationStyle);
            if (locationStyle.width == 0) {
                // we want to grow to whatever size we need
                int width = longestRow + horizontalMargin * 2;
                locationStyle.width = width;
            }
            if (locationStyle.height == 0) {
                // we want to grow to whatever size we need
                int height = rowHeight * numberOfEntries[0] + verticalMargin * 2;
                for (int i = 0; i < layers.size(); i++) {
                    Map<ILayer, LegendEntry[]> map = layers.get(i);
                    final LegendEntry[] entries = map.values().iterator().next();
                    for (int j = 0; j < entries.length; j++) {
                        if (entries[j].getSpacingAfter() == null) {
                            height += verticalSpacing;
                        } else {
                            height += entries[j].getSpacingAfter();
                        }
                    }
                }
                locationStyle.height = height - verticalSpacing;
            }
        }

        // ensure box within the display
        Dimension displaySize = context.getMapDisplay().getDisplaySize();
        if (locationStyle.x < 0) {
            locationStyle.x = displaySize.width - locationStyle.width + locationStyle.x;
        }
        if ((locationStyle.x + locationStyle.width + 6) > displaySize.width) {
            locationStyle.x = displaySize.width - locationStyle.width - 5;
        }

        if (locationStyle.y < 0) {
            locationStyle.y = displaySize.height - locationStyle.height - 5 + locationStyle.y;
        }
        if ((locationStyle.y + locationStyle.height + 6) > displaySize.height) {
            locationStyle.y = displaySize.height - locationStyle.height - 5;
        }

        graphics.setClip(
                new Rectangle(locationStyle.x, locationStyle.y, locationStyle.width + 1, locationStyle.height + 1));

        /*
         * Draw the box containing the layers/icons
         */
        drawOutline(graphics, context, locationStyle);

        /*
         * Draw the layer names/icons
         */
        final int[] rowsDrawn = new int[1];
        rowsDrawn[0] = 0;
        final int[] x = new int[1];
        x[0] = locationStyle.x + horizontalMargin;
        final int[] y = new int[1];
        y[0] = locationStyle.y + verticalMargin;

        if (fontStyle.getFont() != null) {
            graphics.setFont(fontStyle.getFont());
        }

        for (int i = 0; i < layers.size(); i++) {
            Map<ILayer, LegendEntry[]> map = layers.get(i);
            final ILayer layer = map.keySet().iterator().next();
            final LegendEntry[] entries = map.values().iterator().next();

            try {
                layer.getGeoResources().get(0).getInfo(null);
            } catch (Exception ex) {
            }

            PlatformGIS.syncInDisplayThread(new Runnable() {
                public void run() {
                    for (int i = 0; i < entries.length; i++) {
                        BufferedImage awtIcon = null;
                        if (entries[i].getRule() != null) {
                            // generate icon from use
                            ImageDescriptor descriptor = LayerGeneratedGlyphDecorator.generateStyledIcon(layer,
                                    entries[i].getRule());
                            if (descriptor == null) {
                                descriptor = LayerGeneratedGlyphDecorator.generateIcon((Layer) layer);
                            }
                            if (descriptor != null) {
                                awtIcon = AWTSWTImageUtils.convertToAWT(descriptor.getImageData());
                            }
                        } else if (entries[i].getIcon() != null) {
                            // use set icon
                            awtIcon = AWTSWTImageUtils.convertToAWT(entries[i].getIcon().getImageData());
                        } else {
                            // no rule, no icon, try default for layer
                            ImageDescriptor descriptor = LayerGeneratedGlyphDecorator.generateIcon((Layer) layer);
                            if (descriptor != null) {
                                awtIcon = AWTSWTImageUtils.convertToAWT(descriptor.getImageData());
                            }
                        }
                        drawRow(graphics, x[0], y[0], awtIcon, entries[i].getText(), i != 0,
                                entries[i].getTextPosition());

                        y[0] += rowHeight;
                        if ((rowsDrawn[0] + 1) < numberOfEntries[0]) {
                            if (entries[i].getSpacingAfter() != null) {
                                y[0] += entries[i].getSpacingAfter();
                            } else {
                                y[0] += verticalSpacing;
                            }
                        }
                        rowsDrawn[0]++;
                    }

                }
            });
        }
        // clear the clip so we don't affect other rendering processes
        graphics.setClip(null);
    }

    private List<Rule> rules(FeatureTypeStyle[] styles) {
        List<Rule> rules = new ArrayList<Rule>();
        for (FeatureTypeStyle featureTypeStyle : styles) {
            rules.addAll(featureTypeStyle.rules());
        }
        return rules;
    }

    private void drawRow(ViewportGraphics graphics, int x, int y, RenderedImage icon, String[] text, boolean indent,
            int position) {

        if (text.length == 0) {
            return;
        }
        Rectangle2D stringBounds = graphics.getStringBounds(text[0]);

        /*
         * Center the smaller item (text or icon) according to the taller one.
         */
        int textVerticalOffset = 0;
        int iconVerticalOffset = 0;

        if ((position | SWT.CENTER) == position) {
            if (imageHeight == (int) stringBounds.getHeight()) {
                // items are the same height; do nothing.
            } else if (imageHeight > (int) stringBounds.getHeight()) {
                int difference = imageHeight - (int) stringBounds.getHeight();
                textVerticalOffset = difference / 2;
            } else if (imageHeight < (int) stringBounds.getHeight()) {
                int difference = (int) stringBounds.getHeight() - imageHeight;
                iconVerticalOffset = difference / 2;
            }
        } else if ((position | SWT.TOP) == position) {
            // do nothing; position everything at top
            textVerticalOffset = (int) (graphics.getFontAscent() * -0.6);
        }

        if (indent) {
            x += indentSize;
        }

        if (icon != null) {
            graphics.drawImage(icon, x, y + iconVerticalOffset);
            x += imageWidth;
        }

        if (text != null && text[0].length() != 0) {
            graphics.drawString(text[0], x + horizontalMargin,
                    y + textVerticalOffset - (int) (graphics.getFontHeight() - graphics.getFontAscent()),
                    ViewportGraphics.ALIGN_LEFT, ViewportGraphics.ALIGN_TOP);
        }

        if (text != null && text.length > 1) {
            //draw last label at bottom of range
            String end = text[text.length - 1];

            graphics.drawString(end, x + horizontalMargin, y + imageHeight + (int) (graphics.getFontAscent() * 0.3),
                    ViewportGraphics.ALIGN_LEFT, ViewportGraphics.ALIGN_BOTTOM);
        }

    }

    private FeatureTypeStyle[] locateStyle(ILayer layer) {
        StyleBlackboard blackboard = (StyleBlackboard) layer.getStyleBlackboard();
        if (blackboard == null) {
            return null;
        }

        Style sld = (Style) blackboard.lookup(Style.class);
        if (sld == null) {
            return null;
        }

        List<FeatureTypeStyle> styles = new ArrayList<FeatureTypeStyle>();
        String layerTypeName = null;
        if (layer.getSchema() != null && layer.getSchema().getTypeName() != null) {
            layerTypeName = layer.getSchema().getTypeName();
        }
        for (FeatureTypeStyle style : sld.featureTypeStyles()) {
            Set<Name> names = style.featureTypeNames();
            if (names.size() == 0) {
                styles.add(style);
            } else {
                for (Name name : names) {
                    if (name.getLocalPart().equals(SLDs.GENERIC_FEATURE_TYPENAME)
                            || (layerTypeName != null && layerTypeName.equals(name.getLocalPart()))) {
                        styles.add(style);
                        break;
                    }
                }
            }
        }
        return styles.toArray(new FeatureTypeStyle[0]);
    }

    private void drawOutline(ViewportGraphics graphics, MapGraphicContext context, Rectangle locationStyle) {
        Rectangle outline = new Rectangle(locationStyle.x, locationStyle.y, locationStyle.width,
                locationStyle.height);

        // reserve this area free of labels!
        context.getLabelPainter().put(outline);

        graphics.setColor(backgroundColour);
        graphics.fill(outline);

        graphics.setColor(foregroundColour);
        graphics.setBackground(backgroundColour);
        graphics.draw(outline);
    }
}