All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.render.MultiLabelPointPlacemark Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2017 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.render;

import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.util.OGLTextRenderer;

import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;

/**
 * a PointPlacemark that supports a variable number of left-aligned sub-labels
 *
 * this is an alternative for PointPlacemarks with associated GlobeAnnotations. It does not provide the full
 * capabilities of a MultiLineTextRenderer but can optionally set a different font for the sub labels.
 *
 * The use case for MultiLabelPointPlacemark is a large number of dynamically updated objects that frequently change
 * with respect to positions and single sub-labels in a context where we can not tolerate delays between image and
 * label rendering of the same object.
 *
 * MultiLabelPointPlacemark also tries to address the problem that some label colors require excessively large
 * or fat fonts to be readable against a multi-colored green/brown map background. Instead of using a fill color,
 * we draw strings with a lighter and a black backdrop. It is therefore recommended to use label PLAIN label fonts
 *
 * Created by pcmehlitz on 7/1/17.
 */
public class MultiLabelPointPlacemark extends PointPlacemark {

    ArrayList subLabels = new ArrayList(4);
    Font subFont = null;

    float labelHeight = 0;  // computed on first use
    float subHeight = 0; // computed on first use

    public MultiLabelPointPlacemark(Position position) {
        super(position);
    }

    public void setSubLabelFont(Font font) {
        subFont = font;
    }

    public void addSubLabelText (String text) {
        subLabels.add(text);
    }

    public void setSubLabelText (int idx, String text) {
        if (text == null) {
            subLabels.remove(idx);
        } else {
            subLabels.set(idx, text);
        }
    }

    public void removeAllLabels() {
        setLabelText(null);
        removeSubLabels();
    }

    public void removeSubLabels() {
        subLabels.clear();
    }

    public void removeSubLabelText (int idx) {
        subLabels.remove(idx);
    }

    public boolean hasLabel() {
        return labelText != null;
    }
    public boolean hasSubLabels() {
        return !subLabels.isEmpty();
    }

    @Override
    protected void renderLabelText(DrawContext dc, String labelText, float x, float y, Font font, Color color, Color backgroundColor) {
        TextRenderer textRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), font);
        TextRenderer subRenderer = null;

        if (!subLabels.isEmpty()) {
            if (subFont!= null) {
                subRenderer = OGLTextRenderer.getOrCreateTextRenderer(dc.getTextRendererCache(), subFont);
            }
            if (labelHeight == 0) {
                labelHeight = getLineHeight(textRenderer);
            }
            if (subHeight == 0 && subLabels.size() > 1){
                subHeight = (subRenderer != null) ? getLineHeight(subRenderer) : labelHeight;
            }
        }

        try {
            textRenderer.begin3DRendering();
            drawString(labelText, textRenderer, x, y, color, backgroundColor);
            if (!subLabels.isEmpty() && subRenderer == null) drawSubLabels(textRenderer, x, y, color, backgroundColor);
        } finally {
            textRenderer.end3DRendering();
        }

        if (subRenderer != null) {
            try {
                subRenderer.begin3DRendering();
                drawSubLabels(subRenderer, x, y, color, backgroundColor);
            } finally {
                subRenderer.end3DRendering();
            }
        }
    }

    // Color.brighter() only multiplies, i.e. does not change colors that have 0 or 255 component values
    static Color lighter (Color clr) {
        int shift = 127;
        int r = Math.min(255,clr.getRed() + shift);
        int g = Math.min(255,clr.getGreen() + shift);
        int b = Math.min(255,clr.getBlue() + shift);
        return new Color(r,g,b);
    }

    // avoid tons of redundant color objects
    static HashMap lightColors = new HashMap();
    static Color lastColor = null;
    static Color lastLightColor = null;

    // we are going to render a lot of objects of the same color so cache the result and save redundant lookups
    static Color getLightColor(Color color) {
        if (color == lastColor) {
            return lastLightColor;
        } else {
            Color lightColor = lightColors.get(color);
            if (lightColor == null) {
                lightColor = lighter(color);
                lightColors.put(color, lightColor);
            }
            lastColor = color;
            lastLightColor = lightColor;
            return lightColor;
        }
    }


    /**
     * we use a different rendering scheme here than our base class, which only uses the label color on a
     * transparent black backdrop. While this works for "light" colors such as yellow and cyan, it does not
     * work on map background for "dark" colors such as blue and red. Instead of filling the background we use
     * a slightly more expensive approach and render the string three times, with both a lighter and a darker
     * backdrop. We compute and cache the lighter color since high contrast such as white is disturbing, but we
     * always use opaque black as the dark backdrop. This seems to be the best compromise across all label colors, but
     * since labels appear more "fat" is also works best with PLAIN fonts
     */
    protected void drawString (String text, TextRenderer textRenderer, float x, float y, Color color, Color backgroundColor){
        textRenderer.setColor(getLightColor(color));
        textRenderer.draw3D(text, x - 1, y + 1, 0, 1);

        textRenderer.setColor(Color.BLACK);  // instead of transparent backgroundColor
        textRenderer.draw3D(text, x + 1, y - 1, 0, 1);

        textRenderer.setColor(color);
        textRenderer.draw3D(text, x, y, 0, 1);
    }

    protected void drawSubLabels (TextRenderer textRenderer, float x, float y, Color color, Color backgroundColor){
        y -= labelHeight;
        // instead of synchronizing with label setters we favor throughput and just ignore concurrent change exceptions.
        // in the rare case this happens the labels will get redrawn anyways
        try {
            for (int i=0; i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy