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

gov.nasa.worldwind.ogc.kml.impl.KMLPointPlacemarkImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.ogc.kml.impl;

import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.event.Message;
import gov.nasa.worldwind.ogc.kml.*;
import gov.nasa.worldwind.pick.PickedObject;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;

import java.awt.*;
import java.io.IOException;

/**
 * Implements the Point case of a KML Placemark element.
 *
 * @author tag
 * @version $Id: KMLPointPlacemarkImpl.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class KMLPointPlacemarkImpl extends PointPlacemark implements KMLRenderable
{
    protected final KMLPlacemark parent;
    protected boolean highlightAttributesResolved = false;
    protected boolean normalAttributesResolved = false;

    /** Indicates the time at which the image source was specified. */
    protected long iconRetrievalTime;
    /** Indicates the time at which the highlight image source was specified. */
    protected long highlightIconRetrievalTime;

    public static final double DEFAULT_LABEL_SCALE_THRESHOLD = 1.0;
    /**
     * Placemark labels with a scale less than this threshold will only be drawn when the placemark is highlighted. This
     * logic supports KML files with many placemarks with small labels, and drawing all the labels would be too
     * cluttered.
     */
    protected double labelScaleThreshold = DEFAULT_LABEL_SCALE_THRESHOLD;

    /**
     * Create an instance.
     *
     * @param tc        the current {@link KMLTraversalContext}.
     * @param placemark the Placemark element containing the Point.
     * @param geom      the {@link KMLPoint} geometry.
     *
     * @throws NullPointerException     if the geometry is null.
     * @throws IllegalArgumentException if the parent placemark or the traversal context is null.
     */
    public KMLPointPlacemarkImpl(KMLTraversalContext tc, KMLPlacemark placemark, KMLAbstractGeometry geom)
    {
        super(((KMLPoint) geom).getCoordinates());

        if (tc == null)
        {
            String msg = Logging.getMessage("nullValue.TraversalContextIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        if (placemark == null)
        {
            String msg = Logging.getMessage("nullValue.ParentIsNull");
            Logging.logger().severe(msg);
            throw new IllegalArgumentException(msg);
        }

        this.parent = placemark;

        KMLPoint point = (KMLPoint) geom;

        this.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); // KML default

        if (point.isExtrude())
            this.setLineEnabled(true);

        String altMode = point.getAltitudeMode();
        if (!WWUtil.isEmpty(altMode))
        {
            if ("clampToGround".equals(altMode))
                this.setAltitudeMode(WorldWind.CLAMP_TO_GROUND);
            else if ("relativeToGround".equals(altMode))
                this.setAltitudeMode(WorldWind.RELATIVE_TO_GROUND);
            else if ("absolute".equals(altMode))
                this.setAltitudeMode(WorldWind.ABSOLUTE);
        }

        if (this.parent.getVisibility() != null)
            this.setVisible(this.parent.getVisibility());

        if (placemark.getName() != null)
        {
            this.setLabelText(placemark.getName());
            this.setValue(AVKey.DISPLAY_NAME, placemark.getName());
        }

        String description = placemark.getDescription();
        if (description != null)
            this.setValue(AVKey.DESCRIPTION, description);

        if (placemark.getSnippetText() != null)
            this.setValue(AVKey.SHORT_DESCRIPTION, placemark.getSnippetText());

        this.setValue(AVKey.CONTEXT, this.parent);
    }

    public void preRender(KMLTraversalContext tc, DrawContext dc)
    {
        // Intentionally left blank; KML point placemark does nothing during the preRender phase.
    }

    public void render(KMLTraversalContext tc, DrawContext dc)
    {
        // If the attributes are not inline or internal then they might not be resolved until the external KML
        // document is resolved. Therefore check to see if resolution has occurred.

        if (this.isHighlighted())
        {
            if (!this.highlightAttributesResolved)
            {
                PointPlacemarkAttributes a = this.getHighlightAttributes();
                if (a == null || a.isUnresolved())
                {
                    a = this.makeAttributesCurrent(KMLConstants.HIGHLIGHT);
                    if (a != null)
                    {
                        this.setHighlightAttributes(a);
                        if (!a.isUnresolved())
                            this.highlightAttributesResolved = true;
                    }
                    else
                    {
                        // There are no highlight attributes, so we can stop looking for them. Note that this is
                        // different from having unresolved highlight attributes (handled above).
                        this.highlightAttributesResolved = true;
                    }
                }
            }
        }
        else
        {
            if (!this.normalAttributesResolved)
            {
                PointPlacemarkAttributes a = this.getAttributes();
                if (a == null || a.isUnresolved())
                {
                    a = this.makeAttributesCurrent(KMLConstants.NORMAL);
                    if (a != null)
                    {
                        this.setAttributes(a);
                        if (!a.isUnresolved())
                            this.normalAttributesResolved = true;
                    }
                    else
                    {
                        // There are no normal attributes, so we can stop looking for them.  Note that this is different
                        // from having unresolved attributes (handled above).
                        this.normalAttributesResolved = true;
                    }
                }
            }
        }

        this.render(dc);
    }

    protected void determineActiveAttributes()
    {
        super.determineActiveAttributes();

        if (this.mustRefreshIcon())
        {
            String path = this.getActiveAttributes().getImageAddress();

            if (!WWUtil.isEmpty(path))
            {
                // Evict the resource from the file store if there is a cached resource older than the icon update
                // time. This prevents fetching a stale resource out of the cache when the Icon is updated.
                boolean highlighted = this.isHighlighted();
                this.parent.getRoot().evictIfExpired(path,
                    highlighted ? this.highlightIconRetrievalTime : this.iconRetrievalTime);
                this.textures.remove(path);
            }
        }
    }

    /**
     * Indicates whether or not the icon resource has expired.
     *
     * @return True if the icon has expired and must be refreshed.
     */
    protected boolean mustRefreshIcon()
    {
        String mode;
        long retrievalTime;

        if (this.isHighlighted())
        {
            mode = KMLConstants.HIGHLIGHT;
            retrievalTime = this.highlightIconRetrievalTime;
        }
        else
        {
            mode = KMLConstants.NORMAL;
            retrievalTime = this.iconRetrievalTime;
        }

        KMLIconStyle iconStyle = (KMLIconStyle) this.parent.getSubStyle(new KMLIconStyle(null), mode);
        KMLIcon icon = iconStyle.getIcon();
        return icon != null && icon.getUpdateTime() > retrievalTime;
    }

    /**
     * {@inheritDoc} Overridden to set the expiration time of the placemark's icon based on the HTTP headers of the
     * linked resource.
     */
    protected WWTexture initializeTexture(String address)
    {
        WWTexture texture = super.initializeTexture(address);
        if (texture != null)
        {
            // Query the KMLRoot for the expiration time.
            long expiration = this.parent.getRoot().getExpiration(address);

            // Set the Icon's expiration. This has no effect if the refreshMode is not onExpire.
            String mode = this.isHighlighted() ? KMLConstants.HIGHLIGHT : KMLConstants.NORMAL;
            KMLIconStyle iconStyle = (KMLIconStyle) this.parent.getSubStyle(new KMLIconStyle(null), mode);
            KMLIcon icon = iconStyle.getIcon();
            if (icon != null)
                icon.setExpirationTime(expiration);

            if (this.isHighlighted())
                this.highlightIconRetrievalTime = System.currentTimeMillis();
            else
                this.iconRetrievalTime = System.currentTimeMillis();
        }

        return texture;
    }

    /** {@inheritDoc} */
    @Override
    protected PickedObject createPickedObject(DrawContext dc, Color pickColor)
    {
        PickedObject po = super.createPickedObject(dc, pickColor);

        // Add the KMLPlacemark to the picked object as the context of the picked object.        
        po.setValue(AVKey.CONTEXT, this.parent);
        return po;
    }

    /**
     * Draw the label if the label scale is greater than the label scale threshold, if the image scale is zero (only the
     * text is rendered, there is no image), or if the placemark is highlighted.
     *
     * @return True if the label must be drawn.
     */
    @Override
    protected boolean mustDrawLabel()
    {
        double labelScale = this.getActiveAttributes().getLabelScale() != null
            ? this.getActiveAttributes().getLabelScale() : PointPlacemarkAttributes.DEFAULT_LABEL_SCALE;
        double imageScale = this.getActiveAttributes().getScale() != null
            ? this.getActiveAttributes().getScale() : PointPlacemarkAttributes.DEFAULT_IMAGE_SCALE;

        return this.isHighlighted() || labelScale >= this.getLabelScaleThreshold() || imageScale == 0;
    }

    /**
     * Determine and set the {@link PointPlacemark} attributes from the KML Feature fields.
     *
     * @param attrType the type of attributes, either {@link KMLConstants#NORMAL} or {@link KMLConstants#HIGHLIGHT}.
     *
     * @return The new attributes, or null if there are no attributes defined. Returns a partially empty attributes
     *         bundle marked unresolved if any of placemark KML styles are unresolved.
     */
    protected PointPlacemarkAttributes makeAttributesCurrent(String attrType)
    {
        boolean hasLineStyle = false;
        boolean hasIconStyle = false;
        boolean hasLabelStyle = false;

        PointPlacemarkAttributes attrs = this.getInitialAttributes(
            this.isHighlighted() ? KMLConstants.HIGHLIGHT : KMLConstants.NORMAL);

        // Get the KML sub-style for Line attributes. Map them to Shape attributes.

        KMLAbstractSubStyle subStyle = this.parent.getSubStyle(new KMLLineStyle(null), attrType);
        if (subStyle.hasFields() && (!this.isHighlighted() || KMLUtil.isHighlightStyleState(subStyle)))
        {
            hasLineStyle = true;
            this.assembleLineAttributes(attrs, (KMLLineStyle) subStyle);
            if (subStyle.hasField(AVKey.UNRESOLVED))
                attrs.setUnresolved(true);
        }

        subStyle = this.parent.getSubStyle(new KMLIconStyle(null), attrType);
        if (subStyle.hasFields() && (!this.isHighlighted() || KMLUtil.isHighlightStyleState(subStyle)))
        {
            hasIconStyle = true;
            this.assemblePointAttributes(attrs, (KMLIconStyle) subStyle);
            if (subStyle.hasField(AVKey.UNRESOLVED))
                attrs.setUnresolved(true);
        }

        subStyle = this.parent.getSubStyle(new KMLLabelStyle(null), attrType);
        if (subStyle.hasFields() && (!this.isHighlighted() || KMLUtil.isHighlightStyleState(subStyle)))
        {
            hasLabelStyle = true;
            this.assembleLabelAttributes(attrs, (KMLLabelStyle) subStyle);
            if (subStyle.hasField(AVKey.UNRESOLVED))
                attrs.setUnresolved(true);
        }

        // Return the attributes only if we actually found a KML style. If no style was found, return null instead of an
        // empty attributes bundle. If a style was found, but could not be resolved, we will return a partially empty
        // attributes bundle that is marked unresolved.
        if (hasLineStyle || hasIconStyle || hasLabelStyle)
            return attrs;
        else
            return null;
    }

    protected PointPlacemarkAttributes assemblePointAttributes(PointPlacemarkAttributes attrs, KMLIconStyle style)
    {
        KMLIcon icon = style.getIcon();
        if (icon != null && icon.getHref() != null)
        {
            // The icon reference may be to a support file within a KMZ file, so check for that. If it's not, then just
            // let the normal PointPlacemark code resolve the reference.
            String href = icon.getHref();
            String localAddress = null;
            try
            {
                localAddress = this.parent.getRoot().getSupportFilePath(href);
            }
            catch (IOException e)
            {
                String message = Logging.getMessage("generic.UnableToResolveReference", href);
                Logging.logger().warning(message);
            }
            attrs.setImageAddress((localAddress != null ? localAddress : href));
        }
        // If the Icon element is present, but there is no href, draw a point instead of the default icon.
        else if (icon != null && WWUtil.isEmpty(icon.getHref()))
        {
            attrs.setUsePointAsDefaultImage(true);
        }

        // Assign the other attributes defined in the KML Feature element.

        if (style.getColor() != null)
            attrs.setImageColor(WWUtil.decodeColorABGR(style.getColor()));

        if (style.getColorMode() != null && "random".equals(style.getColorMode()))
            attrs.setImageColor(WWUtil.makeRandomColor(attrs.getImageColor()));

        if (style.getScale() != null)
            attrs.setScale(style.getScale());

        if (style.getHeading() != null)
        {
            attrs.setHeading(style.getHeading());
            attrs.setHeadingReference(AVKey.RELATIVE_TO_GLOBE); // KML spec is not clear about this
        }

        if (style.getHotSpot() != null)
        {
            KMLVec2 hs = style.getHotSpot();
            attrs.setImageOffset(new Offset(hs.getX(), hs.getY(), KMLUtil.kmlUnitsToWWUnits(hs.getXunits()),
                KMLUtil.kmlUnitsToWWUnits(hs.getYunits())));
        }
        else
        {
            // By default, use the center of the image as the offset.
            attrs.setImageOffset(new Offset(0.5, 0.5, AVKey.FRACTION, AVKey.FRACTION));
        }

        return attrs;
    }

    protected PointPlacemarkAttributes assembleLineAttributes(PointPlacemarkAttributes attrs, KMLLineStyle style)
    {
        // Assign the attributes defined in the KML Feature element.

        if (style.getWidth() != null)
            attrs.setLineWidth(style.getWidth());

        if (style.getColor() != null)
            attrs.setLineColor(style.getColor());

        if (style.getColorMode() != null && "random".equals(style.getColorMode()))
            attrs.setLineMaterial(new Material(WWUtil.makeRandomColor(attrs.getLineColor())));

        return attrs;
    }

    protected PointPlacemarkAttributes assembleLabelAttributes(PointPlacemarkAttributes attrs, KMLLabelStyle style)
    {
        // Assign the attributes defined in the KML Feature element.

        if (style.getScale() != null)
            attrs.setLabelScale(style.getScale());

        if (style.getColor() != null)
            attrs.setLabelColor(style.getColor());

        if (style.getColorMode() != null && "random".equals(style.getColorMode()))
            attrs.setLabelMaterial(new Material(WWUtil.makeRandomColor(attrs.getLabelColor())));

        return attrs;
    }

    /**
     * Get the initial attributes for this feature. These attributes will be changed to reflect the feature's style.
     *
     * @param attrType {@link KMLConstants#NORMAL} or {@link KMLConstants#HIGHLIGHT}.
     *
     * @return New placemark attributes.
     */
    @SuppressWarnings({"UnusedDeclaration"})
    protected PointPlacemarkAttributes getInitialAttributes(String attrType)
    {
        return new PointPlacemarkAttributes();
    }

    /**
     * Get the label scale threshold. The placemark label will be drawn if the label scale is greater than or equal to
     * this threshold, or if the placemark is highlighted.
     *
     * @return Label scale threshold.
     *
     * @see #setLabelScaleThreshold(double)
     */
    public double getLabelScaleThreshold()
    {
        return this.labelScaleThreshold;
    }

    /**
     * Set the label scale threshold. The placemark label will be drawn if the label scale is greater or equal to than
     * this threshold, or if the placemark is highlighted.
     *
     * @param labelScaleThreshold New label scale threshold.
     *
     * @see #getLabelScaleThreshold()
     */
    public void setLabelScaleThreshold(double labelScaleThreshold)
    {
        this.labelScaleThreshold = labelScaleThreshold;
    }

    @Override
    public void onMessage(Message message)
    {
        super.onMessage(message);

        if (KMLAbstractObject.MSG_STYLE_CHANGED.equals(message.getName()))
        {
            this.normalAttributesResolved = false;
            this.highlightAttributesResolved = false;

            if (this.getAttributes() != null)
                this.getAttributes().setUnresolved(true);
            if (this.getHighlightAttributes() != null)
                this.getHighlightAttributes().setUnresolved(true);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy