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

src.gov.nasa.worldwind.render.SurfacePolygon Maven / Gradle / Ivy

/*
 * 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.render;

import gov.nasa.worldwind.Exportable;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.Globe;
import gov.nasa.worldwind.ogc.kml.KMLConstants;
import gov.nasa.worldwind.ogc.kml.impl.KMLExportUtil;
import gov.nasa.worldwind.util.*;

import javax.xml.stream.*;
import java.io.*;
import java.util.*;

/**
 * @author dcollins
 * @version $Id: SurfacePolygon.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class SurfacePolygon extends AbstractSurfaceShape implements Exportable
{
    protected List> boundaries = new ArrayList>();

    /** Constructs a new surface polygon with the default attributes and no locations. */
    public SurfacePolygon()
    {
    }

    /**
     * Constructs a new surface polygon with the specified normal (as opposed to highlight) attributes and no locations.
     * Modifying the attribute reference after calling this constructor causes this shape's appearance to change
     * accordingly.
     *
     * @param normalAttrs the normal attributes. May be null, in which case default attributes are used.
     */
    public SurfacePolygon(ShapeAttributes normalAttrs)
    {
        super(normalAttrs);
    }

    /**
     * Constructs a new surface polygon with the default attributes and the specified iterable of locations.
     * 

* Note: If fewer than three locations is specified, no polygon is drawn. * * @param iterable the polygon locations. * * @throws IllegalArgumentException if the locations iterable is null. */ public SurfacePolygon(Iterable iterable) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.setOuterBoundary(iterable); } /** * Constructs a new surface polygon with the specified normal (as opposed to highlight) attributes and the specified * iterable of locations. Modifying the attribute reference after calling this constructor causes this shape's * appearance to change accordingly. *

* Note: If fewer than three locations is specified, no polygon is drawn. * * @param normalAttrs the normal attributes. May be null, in which case default attributes are used. * @param iterable the polygon locations. * * @throws IllegalArgumentException if the locations iterable is null. */ public SurfacePolygon(ShapeAttributes normalAttrs, Iterable iterable) { super(normalAttrs); if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.setOuterBoundary(iterable); } public Iterable getLocations(Globe globe) { return this.getOuterBoundary(); } public Iterable getLocations() { return this.getOuterBoundary(); } public List> getBoundaries() { return this.boundaries; } public void setLocations(Iterable iterable) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.setOuterBoundary(iterable); } public Iterable getOuterBoundary() { return this.boundaries.size() > 0 ? this.boundaries.get(0) : null; } public void setOuterBoundary(Iterable iterable) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (this.boundaries.size() > 0) this.boundaries.set(0, iterable); else this.boundaries.add(iterable); this.onShapeChanged(); } public void addInnerBoundary(Iterable iterable) { if (iterable == null) { String message = Logging.getMessage("nullValue.IterableIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.boundaries.add(iterable); this.onShapeChanged(); } public Position getReferencePosition() { if (this.getOuterBoundary() == null) return null; Iterator iterator = this.getOuterBoundary().iterator(); if (!iterator.hasNext()) return null; return new Position(iterator.next(), 0); } protected List> createGeometry(Globe globe, SurfaceTileDrawContext sdc) { if (this.boundaries.isEmpty()) return null; ArrayList> geom = new ArrayList>(); double edgeIntervalsPerDegree = this.computeEdgeIntervalsPerDegree(sdc); for (Iterable boundary : this.boundaries) { ArrayList drawLocations = new ArrayList(); this.generateIntermediateLocations(boundary, edgeIntervalsPerDegree, true, drawLocations); // Ensure all contours have counter-clockwise winding order. The GLU tessellator we'll use to tessellate // these contours is configured to recognize interior holes when all contours have counter clockwise winding // order. //noinspection StringEquality if (WWMath.computeWindingOrderOfLocations(drawLocations) != AVKey.COUNTER_CLOCKWISE) Collections.reverse(drawLocations); geom.add(drawLocations); } if (geom.isEmpty() || geom.get(0).size() < 3) return null; return geom; } protected void doMoveTo(Position oldReferencePosition, Position newReferencePosition) { if (this.boundaries.isEmpty()) return; for (int i = 0; i < this.boundaries.size(); i++) { ArrayList newLocations = new ArrayList(); for (LatLon ll : this.boundaries.get(i)) { Angle heading = LatLon.greatCircleAzimuth(oldReferencePosition, ll); Angle pathLength = LatLon.greatCircleDistance(oldReferencePosition, ll); newLocations.add(LatLon.greatCircleEndPosition(newReferencePosition, heading, pathLength)); } this.boundaries.set(i, newLocations); } // We've changed the polygon's list of boundaries; flag the shape as changed. this.onShapeChanged(); } //**************************************************************// //******************** Interior Tessellation *****************// //**************************************************************// /** * Overridden to clear the polygon's locations iterable upon an unsuccessful tessellation attempt. This ensures the * polygon won't attempt to re-tessellate itself each frame. * * @param dc the current DrawContext. */ @Override protected void handleUnsuccessfulInteriorTessellation(DrawContext dc) { super.handleUnsuccessfulInteriorTessellation(dc); // If tessellating the polygon's interior was unsuccessful, we modify the polygon's to avoid any additional // tessellation attempts, and free any resources that the polygon won't use. This is accomplished by clearing // the polygon's boundary list. this.boundaries.clear(); this.onShapeChanged(); } //**************************************************************// //******************** Restorable State ***********************// //**************************************************************// protected void doGetRestorableState(RestorableSupport rs, RestorableSupport.StateObject context) { super.doGetRestorableState(rs, context); if (!this.boundaries.isEmpty()) { RestorableSupport.StateObject so = rs.addStateObject(context, "boundaries"); for (Iterable boundary : this.boundaries) { rs.addStateValueAsLatLonList(so, "boundary", boundary); } } } protected void doRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) { super.doRestoreState(rs, context); RestorableSupport.StateObject so = rs.getStateObject(context, "boundaries"); if (so != null) { this.boundaries.clear(); RestorableSupport.StateObject[] sos = rs.getAllStateObjects(so, "boundary"); if (sos != null) { for (RestorableSupport.StateObject boundary : sos) { if (boundary == null) continue; Iterable locations = rs.getStateObjectAsLatLonList(boundary); if (locations != null) this.boundaries.add(locations); } } // We've changed the polygon's list of boundaries; flag the shape as changed. this.onShapeChanged(); } } protected void legacyRestoreState(RestorableSupport rs, RestorableSupport.StateObject context) { super.legacyRestoreState(rs, context); Iterable locations = rs.getStateValueAsLatLonList(context, "locationList"); if (locations == null) locations = rs.getStateValueAsLatLonList(context, "locations"); if (locations != null) this.setOuterBoundary(locations); } /** * Export the polygon to KML as a {@code } element. The {@code output} object will receive the data. This * object must be one of: java.io.Writer java.io.OutputStream javax.xml.stream.XMLStreamWriter * * @param output Object to receive the generated KML. * * @throws XMLStreamException If an exception occurs while writing the KML * @throws IOException if an exception occurs while exporting the data. * @see #export(String, Object) */ protected void exportAsKML(Object output) throws IOException, XMLStreamException { XMLStreamWriter xmlWriter = null; XMLOutputFactory factory = XMLOutputFactory.newInstance(); boolean closeWriterWhenFinished = true; if (output instanceof XMLStreamWriter) { xmlWriter = (XMLStreamWriter) output; closeWriterWhenFinished = false; } else if (output instanceof Writer) { xmlWriter = factory.createXMLStreamWriter((Writer) output); } else if (output instanceof OutputStream) { xmlWriter = factory.createXMLStreamWriter((OutputStream) output); } if (xmlWriter == null) { String message = Logging.getMessage("Export.UnsupportedOutputObject"); Logging.logger().warning(message); throw new IllegalArgumentException(message); } xmlWriter.writeStartElement("Placemark"); String property = (String) getValue(AVKey.DISPLAY_NAME); if (property != null) { xmlWriter.writeStartElement("name"); xmlWriter.writeCharacters(property); xmlWriter.writeEndElement(); } xmlWriter.writeStartElement("visibility"); xmlWriter.writeCharacters(KMLExportUtil.kmlBoolean(this.isVisible())); xmlWriter.writeEndElement(); String shortDescription = (String) getValue(AVKey.SHORT_DESCRIPTION); if (shortDescription != null) { xmlWriter.writeStartElement("Snippet"); xmlWriter.writeCharacters(shortDescription); xmlWriter.writeEndElement(); } String description = (String) getValue(AVKey.BALLOON_TEXT); if (description != null) { xmlWriter.writeStartElement("description"); xmlWriter.writeCharacters(description); xmlWriter.writeEndElement(); } // KML does not allow separate attributes for cap and side, so just use the side attributes. final ShapeAttributes normalAttributes = getAttributes(); final ShapeAttributes highlightAttributes = getHighlightAttributes(); // Write style map if (normalAttributes != null || highlightAttributes != null) { xmlWriter.writeStartElement("StyleMap"); KMLExportUtil.exportAttributesAsKML(xmlWriter, KMLConstants.NORMAL, normalAttributes); KMLExportUtil.exportAttributesAsKML(xmlWriter, KMLConstants.HIGHLIGHT, highlightAttributes); xmlWriter.writeEndElement(); // StyleMap } // Write geometry xmlWriter.writeStartElement("Polygon"); xmlWriter.writeStartElement("extrude"); xmlWriter.writeCharacters("0"); xmlWriter.writeEndElement(); xmlWriter.writeStartElement("altitudeMode"); xmlWriter.writeCharacters("clampToGround"); xmlWriter.writeEndElement(); // Outer boundary Iterable outerBoundary = this.getOuterBoundary(); if (outerBoundary != null) { xmlWriter.writeStartElement("outerBoundaryIs"); KMLExportUtil.exportBoundaryAsLinearRing(xmlWriter, outerBoundary, null); xmlWriter.writeEndElement(); // outerBoundaryIs } // Inner boundaries Iterator> boundaryIterator = boundaries.iterator(); if (boundaryIterator.hasNext()) boundaryIterator.next(); // Skip outer boundary, we already dealt with it above while (boundaryIterator.hasNext()) { Iterable boundary = boundaryIterator.next(); xmlWriter.writeStartElement("innerBoundaryIs"); KMLExportUtil.exportBoundaryAsLinearRing(xmlWriter, boundary, null); xmlWriter.writeEndElement(); // innerBoundaryIs } xmlWriter.writeEndElement(); // Polygon xmlWriter.writeEndElement(); // Placemark xmlWriter.flush(); if (closeWriterWhenFinished) xmlWriter.close(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy