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;
        }