private void CreateFeatureDefinition(KMLStyle kmlStyle, XElement feature, XElement geometry, KmlLayerContext context) { if (feature == null) return; // should not happen XNamespace kmlNS = feature.Name.Namespace; if (feature.Name.LocalName == "Placemark") { // kmlStyle is null when the placemark doesn't reference any shared style (or a shared style that we are not able to download) // in this case, use a default style if (kmlStyle == null) kmlStyle = new KMLStyle(); // Determine what kind of feature is present in the placemark. If an input geometry is present, then the // style has already been determined and this method is being called recursively for each child element // of a multi-geometry placemarker. XElement geomElement = null; if (geometry != null) { geomElement = geometry; } else { geomElement = GetFeatureType(feature); // Override any settings from the inline style "Style" node XElement styleElement = feature.Element(kmlNS + "Style"); if (styleElement != null) { GetStyle(styleElement, kmlStyle); } } PlacemarkDescriptor fd = null; if (geomElement != null && geomElement.Name != null) { switch (geomElement.Name.LocalName) { case "Point": fd = ExtractPoint(kmlStyle, geomElement); break; case "LineString": fd = ExtractPolyLine(kmlStyle, geomElement); break; case "LinearRing": fd = ExtractLinearRing(kmlStyle, geomElement); break; case "Polygon": fd = ExtractPolygon(kmlStyle, geomElement); break; case "MultiGeometry": foreach (XElement item in geomElement.Elements()) { // Use recursion to walk the hierarchy of embedded definitions CreateFeatureDefinition(kmlStyle, feature, item, context); } break; case "LatLonBox": ExtractFeatureStyleInfo(kmlStyle, feature); fd = ExtractLatLonBox(kmlStyle, geomElement); break; } // If a feature definition was created, then assign attributes and add to collection if (fd != null) { if (fd.Geometry != null) fd.Geometry.SpatialReference = new SpatialReference(4326); XElement descElement = feature.Element(kmlNS + "description"); if (descElement != null) fd.Attributes.Add("description", descElement.Value); XElement nameElement = feature.Element(kmlNS + "name"); if (nameElement != null) fd.Attributes.Add("name", nameElement.Value); if (atomNS != null) { // Initialize to parent value Uri atomHrefValue = context.AtomHref; // If node exists, has attributes, and can be successfully extracted, then extract // this value. XElement atomHrefElement = feature.Element(atomNS + "link"); if (atomHrefElement != null && atomHrefElement.HasAttributes) { string tempHref = GetAtomHref(atomHrefElement); if (!String.IsNullOrEmpty(tempHref)) atomHrefValue = new Uri(tempHref); } // If a value was extracted or assigned from a parent, then add to attributes if (atomHrefValue != null) fd.Attributes.Add("atomHref", atomHrefValue); // AtomAuthor : Initialize to parent value string atomValue = context.AtomAuthor; // If node exists, has attributes, and can be successfully extracted, then extract // this value. XElement atomAuthorElement = feature.Element(atomNS + "author"); if (atomAuthorElement != null) { string tempAuthor = GetAtomAuthor(atomAuthorElement); if (!String.IsNullOrEmpty(tempAuthor)) atomValue = tempAuthor; } // If a value was extracted or assigned from a parent, then add to attributes if (!String.IsNullOrEmpty(atomValue)) fd.Attributes.Add("atomAuthor", atomValue); } // Extract extended information XElement extendedDataElement = feature.Element(kmlNS + "ExtendedData"); if (extendedDataElement != null) { List<KmlExtendedData> extendedList = new List<KmlExtendedData>(); IEnumerable<XElement> dataElements = from e in extendedDataElement.Descendants(kmlNS + "Data") select e; foreach (XElement data in dataElements) { XAttribute name = data.Attribute("name"); if (name != null) { KmlExtendedData listItem = new KmlExtendedData(); listItem.Name = name.Value; foreach (XElement dataChild in data.Descendants()) { if (dataChild.Name == kmlNS + "displayName") listItem.DisplayName = dataChild.Value; else if (dataChild.Name == kmlNS + "value") listItem.Value = dataChild.Value; } extendedList.Add(listItem); } } if (extendedList.Count > 0) fd.Attributes.Add("extendedData", extendedList); } featureDefs.AddPlacemark(fd); } } } else if (feature.Name.LocalName == "GroundOverlay") { XElement latLonBoxElement = feature.Element(kmlNS + "LatLonBox"); if (latLonBoxElement != null) { GroundOverlayDescriptor fd = new GroundOverlayDescriptor(); fd.Envelope = ExtractEnvelope(latLonBoxElement); XElement rotationElement = latLonBoxElement.Element(kmlNS + "rotation"); if (rotationElement != null) fd.Rotation = GetDoubleValue(rotationElement); XElement colorElement = feature.Element(kmlNS + "color"); if (colorElement != null) fd.Color = GetColorFromHexString(colorElement.Value); else fd.Color = System.Windows.Media.Colors.White; // Default = white XElement iconElement = feature.Element(kmlNS + "Icon"); if (iconElement != null) { XElement href = iconElement.Element(kmlNS + "href"); if (href != null) { fd.IconHref = href.Value; } } featureDefs.AddGroundOverlay(fd); } } }
/// <summary> /// Takes features in the KML element and converts them into equivalent features /// and adds them to the FeatureDefinition. /// Only the direct children of the KML element are converted. /// </summary> /// <param name="context">Context containing the XElement with the KML definition to be converted.</param> /// <param name="credentials">The credentials.</param> /// <returns></returns> public FeatureDefinition Convert(KmlLayerContext context, System.Net.ICredentials credentials = null) { 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); }); } // 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 } // 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; } if (container.HasAttributes && container.Attribute(KmlLayer.FolderIdAttributeName) != null) { containerInfo.FolderId = (int)container.Attribute(KmlLayer.FolderIdAttributeName); } 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)); } 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; } // 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); } if (!featureDefs.groundOverlays.Any() && !featureDefs.placemarks.Any() && featureDefs.containers.Count() == 1 && folderId == 0 && string.IsNullOrEmpty(featureDefs.containers.First().Url)) { // Avoid useless level when there is no groundoverlay, no placemark and only one folder or document at the root level ContainerInfo container = featureDefs.containers.First(); Dictionary<string, KMLStyle> styles = featureDefs.styles.ToDictionary(style => style.Key, style => style.Value); KmlLayerContext childContext = new KmlLayerContext { Element = container.Element, // The XElement that the KML layer has to process Styles = styles, Images = context.Images, AtomAuthor = container.AtomAuthor, AtomHref = container.AtomHref }; featureDefs.hasRootContainer = true; return Convert(childContext, credentials); } return featureDefs; }