/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.layers.kml;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.avlist.AVList;
import gov.nasa.worldwind.avlist.AVListImpl;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.ogc.kml.KMLAbstractFeature;
import gov.nasa.worldwind.ogc.kml.KMLConstants;
import gov.nasa.worldwind.ogc.kml.KMLRoot;
import gov.nasa.worldwind.ogc.kml.impl.KMLController;
import gov.nasa.worldwind.ogc.kml.io.KMLDoc;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.WWIO;
import gov.nasa.worldwind.util.WWUtil;
import gov.nasa.worldwind.util.WWXML;
import gov.nasa.worldwind.util.layertree.KMLLayerTreeNode;
import gov.nasa.worldwind.util.tree.TreeNode;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.logging.Level;
import javax.xml.xpath.XPath;
import org.w3c.dom.Element;
import au.gov.ga.earthsci.worldwind.common.downloader.Downloader;
import au.gov.ga.earthsci.worldwind.common.downloader.RetrievalHandler;
import au.gov.ga.earthsci.worldwind.common.downloader.RetrievalResult;
import au.gov.ga.earthsci.worldwind.common.layers.Hierarchical;
import au.gov.ga.earthsci.worldwind.common.layers.kml.relativeio.RelativeKMLFile;
import au.gov.ga.earthsci.worldwind.common.layers.kml.relativeio.RelativeKMLInputStream;
import au.gov.ga.earthsci.worldwind.common.layers.kml.relativeio.RelativeKMZFile;
import au.gov.ga.earthsci.worldwind.common.layers.kml.relativeio.RelativeKMZInputStream;
import au.gov.ga.earthsci.worldwind.common.util.Loader;
import au.gov.ga.earthsci.worldwind.common.util.URLUtil;
import au.gov.ga.earthsci.worldwind.common.util.XMLUtil;
/**
* A {@link Layer} that parses and renders KML content from a provided KML
* source.
*/
public class KMLLayer extends RenderableLayer implements Loader, Hierarchical
{
private boolean loading = false;
private LoadingListenerList loadingListeners = new LoadingListenerList();
private HierarchicalListenerList hierarchicalListeners = new HierarchicalListenerList();
private Object lock = new Object();
private TreeNode node;
public KMLLayer(Element domElement, AVList params)
{
this(getParamsFromDocument(domElement, params));
}
public static AVList getParamsFromDocument(Element domElement, AVList params)
{
if (domElement == null)
{
String message = Logging.getMessage("nullValue.DocumentIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (params == null)
params = new AVListImpl();
XPath xpath = WWXML.makeXPath();
XMLUtil.checkAndSetURLParam(domElement, params, AVKey.URL, "URL", xpath);
return params;
}
public KMLLayer(AVList params)
{
setValues(params);
final URL url = (URL) params.getValue(AVKey.URL);
if (url == null)
{
throw new IllegalArgumentException("KML url undefined");
}
loading = true;
notifyLoadingListeners();
RetrievalHandler handler = new RetrievalHandler()
{
@Override
public void handle(RetrievalResult result)
{
loadKml(url, result.getAsInputStream());
}
};
Downloader.downloadIfModified(url, handler, handler, false);
}
public KMLLayer(URL sourceUrl, InputStream stream, AVList params)
{
setValues(params);
setValue(AVKey.URL, sourceUrl);
loadKml(sourceUrl, stream);
}
protected void loadKml(final URL url, final InputStream inputStream)
{
Thread thread = new Thread()
{
@Override
public void run()
{
//don't allow multiple threads to attempt load at the same time
synchronized (lock)
{
loading = true;
notifyLoadingListeners();
try
{
String contentType = WWIO.makeMimeTypeForSuffix(WWIO.getSuffix(url.getPath()));
boolean isKmz = KMLConstants.KMZ_MIME_TYPE.equals(contentType);
File file = URLUtil.urlToFile(url);
KMLDoc doc;
if (file != null)
{
doc =
isKmz ? new RelativeKMZFile(URLUtil.urlToFile(url), url.toString(), null)
: new RelativeKMLFile(URLUtil.urlToFile(url), url.toString(), null);
}
else
{
InputStream stream = inputStream != null ? inputStream : WWIO.openStream(url);
doc =
isKmz ? new RelativeKMZInputStream(stream, url.toURI(), url.toString(), null)
: new RelativeKMLInputStream(stream, url.toURI(), url.toString(), null);
}
KMLRoot root;
try
{
//Attempt to create an instance of the CustomKMLRoot object, for loading
//COLLADA models. This is done via reflection, so there's no requirement
//for the library to be in the classpath.
Class<?> colladaKmlRootClass =
Class.forName("gov.nasa.worldwind.ogc.kml.custom.CustomKMLRoot");
Constructor<?> c = colladaKmlRootClass.getConstructor(KMLDoc.class);
root = (KMLRoot) c.newInstance(doc);
}
catch (Exception e)
{
root = new KMLRoot(doc);
}
root.parse();
KMLController controller = new KMLController(root);
addRenderable(controller);
setName(formName(url, root));
node = new KMLLayerTreeNode(KMLLayer.this, root);
notifyHierarchicalListeners(node);
}
catch (Exception e)
{
String message = "Error parsing KML";
Logging.logger().log(Level.SEVERE, message, e);
throw new IllegalArgumentException(message, e);
}
finally
{
loading = false;
notifyLoadingListeners();
}
}
}
};
thread.setDaemon(true);
thread.start();
}
//from KMLViewer.java
public static String formName(Object kmlSource, KMLRoot kmlRoot)
{
KMLAbstractFeature rootFeature = kmlRoot.getFeature();
if (rootFeature != null && !WWUtil.isEmpty(rootFeature.getName()))
return rootFeature.getName();
if (kmlSource instanceof File)
return ((File) kmlSource).getName();
if (kmlSource instanceof URL)
return ((URL) kmlSource).getPath();
if (kmlSource instanceof String && WWIO.makeURL((String) kmlSource) != null)
return WWIO.makeURL((String) kmlSource).getPath();
return "KML Layer";
}
@Override
public boolean isLoading()
{
return loading;
}
@Override
public void addLoadingListener(LoadingListener listener)
{
loadingListeners.add(listener);
}
@Override
public void removeLoadingListener(LoadingListener listener)
{
loadingListeners.remove(listener);
}
protected void notifyLoadingListeners()
{
loadingListeners.notifyListeners(this, isLoading());
}
protected void notifyHierarchicalListeners(TreeNode node)
{
hierarchicalListeners.notifyListeners(this, node);
}
@Override
public void addHierarchicalListener(HierarchicalListener listener)
{
synchronized (lock)
{
hierarchicalListeners.add(listener);
if (node != null)
{
//already loaded, so notify immediately
notifyHierarchicalListeners(node);
}
}
}
@Override
public void removeHierarchicalListener(HierarchicalListener listener)
{
hierarchicalListeners.remove(listener);
}
}