public FeatureDefinition Convert(KmlLayerContext context)
{
ICredentials credentials = context.Credentials;
#if !SILVERLIGHT
var clientCertificate = context.ClientCertificate;
#endif
XElement xElement = context.Element;
XNamespace kmlNS = xElement.Name.Namespace;
_waitHelper.Reset();
// Remove any existing features so only those contained in the input KML element file are stored
featureDefs.Clear();
// Process the styles if they are not already known (the styles are shared by all folders/documents, so process them only once)
if (context.Styles == null)
{
featureDefs.styles = new Dictionary<string, KMLStyle>();
// Find all Style elements that have an ID and can thus be referenced by other styleURLs.
IEnumerable<XElement> styles = xElement.Descendants().Where(e => e.Name.LocalName == "Style" && (string)e.Attribute("id") != null);
foreach (XElement style in styles)
{
KMLStyle kmlStyle = new KMLStyle();
GetStyle(style, kmlStyle);
featureDefs.AddStyle(kmlStyle.StyleId, kmlStyle);
}
// Find all StyleMap elements that have an ID and can thus be referenced by other styleURLs.
IEnumerable<XElement> styleMaps = xElement.Descendants().Where(e => e.Name.LocalName == "StyleMap" && (string)e.Attribute("id") != null);
foreach (XElement map in styleMaps)
{
// A style map may need to download styles in other documents.
// Need to use asynchronous pattern.
GetStyleMapAsync(map, null, credentials
, kmlStyle =>
{
if (kmlStyle != null)
featureDefs.AddStyle(kmlStyle.StyleId, kmlStyle);
}
#if !SILVERLIGHT
, clientCertificate
#endif
);
}
// Wait for getting all styles before creating the feature definition
_waitHelper.Wait();
}
else
{
foreach (var style in context.Styles)
featureDefs.AddStyle(style.Key, style.Value);
}
// Process the optional NetworkLinkControl
XElement networkLinkControl = xElement.Element(kmlNS + "NetworkLinkControl");
if (networkLinkControl != null)
{
featureDefs.networkLinkControl = new NetworkLinkControl();
XElement minRefreshPeriod = networkLinkControl.Element(kmlNS + "minRefreshPeriod");
if (minRefreshPeriod != null)
featureDefs.networkLinkControl.MinRefreshPeriod = GetDoubleValue(minRefreshPeriod);
}
// Find the containers which will be represented by a sublayer i.e Document/Folder/NetworkLink
foreach (XElement container in xElement.Elements().Where(element => element.Name.LocalName == "Folder" || element.Name.LocalName == "Document" || element.Name.LocalName == "NetworkLink"))
{
ContainerInfo containerInfo = new ContainerInfo
{
Element = container,
Url = null, // only for networklink
Visible = true,
AtomAuthor = context.AtomAuthor, // Use parent value by default
AtomHref = context.AtomHref // Use parent value by default
};
XNamespace kmlContainerNS = container.Name.Namespace;
if (container.Name.LocalName == "NetworkLink")
{
string hrefValue = "";
string composite = "";
string layerids = "";
// Link takes precedence over Url from KML version 2.1 and later:
XElement url = container.Element(kmlContainerNS + "Link") ?? container.Element(kmlContainerNS + "Url");
if (url != null)
{
XElement href = url.Element(kmlContainerNS + "href");
if (href != null)
{
hrefValue = href.Value;
}
// This next section is to parse special elements that only occur when an ArcGIS Server KML
// is to be processed.
XElement view = url.Element(kmlContainerNS + "viewFormat");
if (view != null)
{
int begIdx = view.Value.IndexOf("Composite");
if (begIdx != -1)
{
int endIdx = view.Value.IndexOf("&", begIdx);
if (endIdx != -1)
composite = view.Value.Substring(begIdx, endIdx - begIdx);
}
begIdx = view.Value.IndexOf("LayerIDs");
if (begIdx != -1)
{
int endIdx = view.Value.IndexOf("&", begIdx);
if (endIdx != -1)
layerids = view.Value.Substring(begIdx, endIdx - begIdx);
}
}
// If network link URL is successfully extracted, then add to container list
if (!String.IsNullOrEmpty(hrefValue))
{
// extract refreshInterval
XElement refreshMode = url.Element(kmlContainerNS + "refreshMode");
if (refreshMode != null && refreshMode.Value == "onInterval")
{
XElement refreshInterval = url.Element(kmlContainerNS + "refreshInterval");
if (refreshInterval != null)
containerInfo.RefreshInterval = GetDoubleValue(refreshInterval);
else
containerInfo.RefreshInterval = 4; // default value
}
XElement viewRefreshMode = url.Element(kmlContainerNS + "viewRefreshMode");
if (viewRefreshMode != null)
{
ViewRefreshMode viewRefreshModeEnum;
try // Enum.TryParse doesn't exist in 3.5
{
viewRefreshModeEnum = (ViewRefreshMode)Enum.Parse(typeof(ViewRefreshMode), viewRefreshMode.Value, true);
containerInfo.ViewRefreshMode = viewRefreshModeEnum;
}
catch{}
}
// the following values are for processing specialized ArcGIS Server KML links
// generated from REST endpoints.
if (!String.IsNullOrEmpty(composite))
hrefValue += "?" + composite;
if (!String.IsNullOrEmpty(layerids))
{
if (!String.IsNullOrEmpty(hrefValue))
hrefValue += "&" + layerids;
else
hrefValue += "?" + layerids;
}
containerInfo.Url = hrefValue;
}
else
containerInfo = null; // Link without href. Should not happen. Skip it.
}
else
containerInfo = null; // NetworkLink without Link/Url. Should not happen. Skip it.
}
else
{
// Folder or Document XElement
XElement linkElement = container.Elements(atomNS + "link").Where(element => element.HasAttributes).FirstOrDefault();
if (linkElement != null)
{
// Overwrite global default value only upon successful extraction from element
string tempHref = GetAtomHref(linkElement);
if (!String.IsNullOrEmpty(tempHref))
containerInfo.AtomHref = new Uri(tempHref);
}
XElement authorElement = container.Element(atomNS + "author");
if (authorElement != null)
{
// Overwrite global default value only upon successful extraction from element
string tempAuthor = GetAtomAuthor(authorElement);
if (!String.IsNullOrEmpty(tempAuthor))
containerInfo.AtomAuthor = tempAuthor;
}
}
if (containerInfo != null)
{
XElement visibilityElement = container.Element(kmlContainerNS + "visibility");
if (visibilityElement != null)
{
containerInfo.Visible = GetBooleanValue(visibilityElement);
}
XElement nameElement = container.Element(kmlContainerNS + "name");
if (nameElement != null)
{
containerInfo.Name = nameElement.Value.Trim();
}
containerInfo.RegionInfo = ExtractRegion(container);
// Look for a listItemType element that can be set to 'checkHideChildren' to prevent cildren to be seen in the legend
XElement listItemTypeElement = container.XPathSelectElement("Style/ListStyle/listItemType", kmlContainerNS);
if (listItemTypeElement != null)
{
if (listItemTypeElement.Value == "checkHideChildren")
containerInfo.HideChildren = true;
}
if (container.HasAttributes && container.Attribute(KmlLayer.FolderIdAttributeName) != null)
{
containerInfo.FolderId = (int)container.Attribute(KmlLayer.FolderIdAttributeName);
}
containerInfo.TimeExtent = ExtractTimeExtent(container);
featureDefs.AddContainer(containerInfo);
}
}
// Process all children placemarks or groundoverlays
foreach (XElement element in xElement.Elements().Where(element => element.Name == kmlNS + "Placemark" || element.Name == kmlNS + "GroundOverlay" ))
{
// Establish baseline style if a "styleUrl" setting is present
XElement styleElement = element.Element(kmlNS + "styleUrl");
if (styleElement != null)
{
// get the style asynchronously and create the feature definition as soon as the style is there
XElement featureElement = element;
GetStyleUrlAsync(styleElement.Value, null, credentials, kmlStyle => CreateFeatureDefinition(kmlStyle, featureElement, null, context)
#if !SILVERLIGHT
, clientCertificate
#endif
);
}
else
{
// Create feature definition synchronously using default KML style, meta data and placemark information
CreateFeatureDefinition(null, element, null, context);
}
}
// Get the name of the XElement
XElement nameXElement = xElement.Element(kmlNS + "name");
if (nameXElement != null && string.IsNullOrEmpty(featureDefs.name))
{
featureDefs.name = nameXElement.Value.Trim();
}
// At this point, some inner styles are possibly on the way to being downloaded and so the feature definitions are not created yet
// Wait for all downloads to be sure all feature definitions are created before terminating the background worker
_waitHelper.Wait();
int folderId = 0;
if (xElement.HasAttributes && xElement.Attribute(KmlLayer.FolderIdAttributeName) != null)
{
folderId = (int)xElement.Attribute(KmlLayer.FolderIdAttributeName);
}
ContainerInfo singleContainer = featureDefs.containers.Count() == 1 ? featureDefs.containers.First() : null;
if (!featureDefs.groundOverlays.Any() && !featureDefs.placemarks.Any() && singleContainer != null && folderId == 0
&& (singleContainer.RegionInfo == null || !singleContainer.RegionInfo.HasLods())
&& string.IsNullOrEmpty(singleContainer.Url) && singleContainer.TimeExtent == null)
{
// Avoid useless level when there is no groundoverlay, no placemark and only one folder or document at the root level without any lod info
Dictionary<string, KMLStyle> styles = featureDefs.styles.ToDictionary(style => style.Key, style => style.Value);
KmlLayerContext childContext = new KmlLayerContext
{
Element = singleContainer.Element, // The XElement that the KML layer has to process
Styles = styles,
Images = context.Images,
AtomAuthor = singleContainer.AtomAuthor,
AtomHref = singleContainer.AtomHref,
Credentials = context.Credentials
#if !SILVERLIGHT
,ClientCertificate = context.ClientCertificate
#endif
};
featureDefs.hasRootContainer = true;
return Convert(childContext);
}
return featureDefs;
}