gov.nasa.worldwind.ogc.kml.impl.KMLModelPlacemarkImpl 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.*;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.ogc.collada.*;
import gov.nasa.worldwind.ogc.collada.impl.ColladaTraversalContext;
import gov.nasa.worldwind.ogc.kml.*;
import gov.nasa.worldwind.render.DrawContext;
import gov.nasa.worldwind.util.*;
import javax.xml.stream.XMLStreamException;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.atomic.*;
/**
* Class to load and render a COLLADA model as the geometry of a KML Placemark.
*
* @author pabercrombie
* @version $Id: KMLModelPlacemarkImpl.java 1838 2014-02-05 20:48:12Z dcollins $
*/
public class KMLModelPlacemarkImpl extends WWObjectImpl implements KMLRenderable, ColladaResourceResolver
{
/** Model rendered by this class. */
protected KMLModel model;
/** Placemark that contains the model. */
protected KMLPlacemark parent;
/** Reference to the COLLADA root that contains the parsed COLLADA file. */
protected AtomicReference colladaRoot = new AtomicReference();
/**
* Time, in milliseconds since the Epoch, at which this placemark's model resource was last retrieved. Initially
* -1
, indicating that the resource has not been retrieved.
*/
protected AtomicLong resourceRetrievalTime = new AtomicLong(-1);
/**
* Map specified by the KML Model's ResourceMap element. The map relates relative references within the COLLADA file
* to paths relative to the KML document.
*/
protected Map resourceMap;
/** Traversal context for rendering the ColladaRoot. */
protected ColladaTraversalContext colladaTraversalContext = new ColladaTraversalContext();
/**
* Create an instance.
*
* @param tc the current {@link KMLTraversalContext}.
* @param placemark the Placemark element containing the Point.
* @param geom the {@link gov.nasa.worldwind.ogc.kml.KMLPoint} geometry.
*
* @throws NullPointerException if the geometry is null.
* @throws IllegalArgumentException if the parent placemark or the traversal context is null.
*/
public KMLModelPlacemarkImpl(KMLTraversalContext tc, KMLPlacemark placemark, KMLAbstractGeometry geom)
{
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);
}
if (geom == null)
{
String msg = Logging.getMessage("nullValue.GeometryIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
this.model = (KMLModel) geom;
this.parent = placemark;
this.resourceMap = this.createResourceMap(this.model);
}
/**
* Build the resource map from the KML Model's ResourceMap element.
*
* @param model Model from which to create the resource map.
*
* @return Map that relates relative paths in the COLLADA document to paths relative to the KML document.
*/
protected Map createResourceMap(KMLModel model)
{
Map map = new HashMap();
KMLResourceMap resourceMap = model.getResourceMap();
if (resourceMap == null)
return Collections.emptyMap();
for (KMLAlias alias : resourceMap.getAliases())
{
if (alias != null && !WWUtil.isEmpty(alias.getSourceRef()) && !WWUtil.isEmpty(alias.getTargetHref()))
{
map.put(alias.getSourceRef(), alias.getTargetHref());
}
}
return map.size() > 0 ? map : Collections.emptyMap();
}
/**
* Specifies the Collada resource referenced by this placemark, or null
if this placemark has no
* resource.
*
* @param root the Collada resource referenced by this placemark. May be null
.
*/
protected void setColladaRoot(ColladaRoot root)
{
if (root != null)
this.configureColladaRoot(root);
this.colladaRoot.set(root);
}
/**
* Indicates the Collada resource referenced by this placemark. This returns null
if this placemark has
* no resource.
*
* @return this placemark's Collada resource, or null
to indicate that this placemark has no resource.
*
* @see #setColladaRoot(gov.nasa.worldwind.ogc.collada.ColladaRoot)
*/
protected ColladaRoot getColladaRoot()
{
return this.colladaRoot.get();
}
/**
* Apply the model's position, orientation, and scale to a COLLADA root.
*
* @param root COLLADA root to configure.
*/
protected void configureColladaRoot(ColladaRoot root)
{
root.setResourceResolver(this);
Position refPosition = this.model.getLocation().getPosition();
root.setPosition(refPosition);
root.setAltitudeMode(KMLUtil.convertAltitudeMode(this.model.getAltitudeMode(), WorldWind.CLAMP_TO_GROUND)); // KML default
KMLOrientation orientation = this.model.getOrientation();
if (orientation != null)
{
Double d = orientation.getHeading();
if (d != null)
root.setHeading(Angle.fromDegrees(d));
d = orientation.getTilt();
if (d != null)
root.setPitch(Angle.fromDegrees(-d));
d = orientation.getRoll();
if (d != null)
root.setRoll(Angle.fromDegrees(-d));
}
KMLScale scale = this.model.getScale();
if (scale != null)
{
Double x = scale.getX();
Double y = scale.getY();
Double z = scale.getZ();
Vec4 modelScale = new Vec4(
x != null ? x : 1.0,
y != null ? y : 1.0,
z != null ? z : 1.0);
root.setModelScale(modelScale);
}
}
/** {@inheritDoc} */
public void preRender(KMLTraversalContext tc, DrawContext dc)
{
if (this.mustRetrieveResource())
this.requestResource(dc);
ColladaRoot root = this.getColladaRoot();
if (root != null)
{
this.colladaTraversalContext.initialize();
root.preRender(this.colladaTraversalContext, dc);
}
}
/** {@inheritDoc} */
public void render(KMLTraversalContext tc, DrawContext dc)
{
ColladaRoot root = this.getColladaRoot();
if (root != null)
{
this.colladaTraversalContext.initialize();
root.render(this.colladaTraversalContext, dc);
}
}
/**
* Resolve COLLADA references relative to the COLLADA document. If the reference is relative then it will resolved
* relative to the .dae file, not the kml file. If the COLLADA document may be contained in a KMZ archive the
* resources will be resolved relative to the .dae file within the archive. Normally references in a KMZ are
* resolved relative to the root of the archive, but Model references are an exception. See
* https://developers.google.com/kml/documentation/kmzarchives and https://developers.google.com/kml/documentation/kmlreference#model
*
* {@inheritDoc}.
*/
public String resolveFilePath(String path) throws IOException
{
KMLLink link = this.model.getLink();
// Check the resource map to see if an alias is defined for this resource.
String alias = this.resourceMap.get(path);
if (alias != null)
path = alias;
// If the path is relative then resolve it relative to the COLLADA file.
File f = new File(path);
if (!f.isAbsolute() && link != null && link.getHref() != null)
{
try
{
URI base = new URI(null, link.getHref(), null);
URI ref = new URI(null, path, null);
path = base.resolve(ref).getPath();
}
catch (URISyntaxException ignored)
{
// Ignored
}
}
Object o = this.parent.getRoot().resolveReference(path);
if (o instanceof URL || o instanceof String)
return o.toString();
return null;
}
/**
* Returns whether this placemark must retrieve its model resource. This always returns false
if this
* placemark has no KMLLink
.
*
* @return true
if this placemark must retrieve its model resource, otherwise false
.
*/
protected boolean mustRetrieveResource()
{
KMLLink link = this.model.getLink();
if (link == null)
return false;
// The resource must be retrieved if the link has been updated since the resource was
// last retrieved, or if the resource has never been retrieved.
return this.getColladaRoot() == null || link.getUpdateTime() > this.resourceRetrievalTime.get();
}
/**
* Thread's off a task to determine whether the resource is local or remote and then retrieves it either from disk
* cache or a remote server.
*
* @param dc the current draw context.
*/
protected void requestResource(DrawContext dc)
{
if (WorldWind.getTaskService().isFull())
return;
KMLLink link = this.model.getLink();
if (link == null)
return;
String address = link.getAddress(dc);
if (address != null)
address = address.trim();
if (WWUtil.isEmpty(address))
return;
WorldWind.getTaskService().addTask(new RequestTask(this, address));
}
/**
* Initiates a retrieval of the model referenced by this placemark. Once the resource is retrieved and loaded, this
* calls {@link #setColladaRoot(ColladaRoot)}
to specify this link's new network resource, and sends an
* {@link gov.nasa.worldwind.avlist.AVKey#RETRIEVAL_STATE_SUCCESSFUL}
property change event to this
* link's property change listeners.
*
* This does nothing if this KMLNetworkLink
has no KMLLink
.
*
* @param address the address of the resource to retrieve
*/
protected void retrieveModel(String address) throws IOException, XMLStreamException
{
Object o = this.parent.getRoot().resolveReference(address);
if (o == null)
return;
ColladaRoot root = ColladaRoot.createAndParse(o);
if (root == null)
return;
this.setColladaRoot(root);
this.resourceRetrievalTime.set(System.currentTimeMillis());
this.parent.getRoot().requestRedraw();
}
/** Attempts to find this model link resource file locally, and if that fails attempts to find it remotely. */
protected static class RequestTask implements Runnable
{
/** The link associated with this request. */
protected final KMLModelPlacemarkImpl placemark;
/** The resource's address. */
protected final String address;
/**
* Construct a request task for a specified network link resource.
*
* @param placemark the placemark for which to construct the request task.
* @param address the address of the resource to request.
*/
protected RequestTask(KMLModelPlacemarkImpl placemark, String address)
{
if (placemark == null)
{
String message = Logging.getMessage("nullValue.ObjectIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (address == null)
{
String message = Logging.getMessage("nullValue.PathIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
this.placemark = placemark;
this.address = address;
}
public void run()
{
if (Thread.currentThread().isInterrupted())
return; // the task was cancelled because it's a duplicate or for some other reason
try
{
this.placemark.retrieveModel(this.address);
}
catch (IOException e)
{
String message = Logging.getMessage("generic.ExceptionWhileReading", e.getMessage());
Logging.logger().warning(message);
}
catch (XMLStreamException e)
{
String message = Logging.getMessage("generic.ExceptionAttemptingToParseXml", e.getMessage());
Logging.logger().warning(message);
}
}
@Override
public boolean equals(Object o)
{
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
RequestTask that = (RequestTask) o;
if (!this.address.equals(that.address))
return false;
//noinspection RedundantIfStatement
if (!this.placemark.equals(that.placemark))
return false;
return true;
}
@Override
public int hashCode()
{
int result = placemark.hashCode();
result = 31 * result + address.hashCode();
return result;
}
@Override
public String toString()
{
return this.address;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy