Internal representation of a KML style element.
        private static void ComputeIconTranslationValues(KMLStyle style, KmlPlaceMarkerSymbol ms, BitmapImage bi)
        {
            if (bi.PixelWidth > 0)
            {
                ms.Width = bi.PixelWidth;
            }
            if (bi.PixelHeight > 0)
            {
                ms.Height = bi.PixelHeight;
            }

            switch (style.IconHotspotUnitsX)
            {
            case HotSpotUnitType.Pixels:
                ms.TranslateX = style.IconHotspotX * -1;
                break;

            case HotSpotUnitType.Fraction:
                ms.TranslateX = (bi.PixelWidth * style.IconHotspotX) * -1;
                break;
            }

            switch (style.IconHotspotUnitsY)
            {
            case HotSpotUnitType.Pixels:
                ms.TranslateY = (ms.Height - style.IconHotspotY) * -1;
                break;

            case HotSpotUnitType.Fraction:
                ms.TranslateY = (bi.PixelHeight * style.IconHotspotY) * -1;
                break;
            }
        }
Exemple #2
0
        private static void ComputeIconTranslationValues(KMLStyle style, KmlPlaceMarkerSymbol ms, BitmapImage bi)
        {
            // To match sizing of Google Earth, default size of point images is 40x40
            // Note: the iconScale will be applied later globally to the symbol
            ms.Height = 40;
            ms.Width  = 40;

            switch (style.IconHotspotUnitsX)
            {
            case HotSpotUnitType.Pixels:
                ms.TranslateX = style.IconHotspotX * -1;
                break;

            case HotSpotUnitType.Fraction:
                ms.TranslateX = (ms.Width * style.IconHotspotX) * -1;
                break;
            }

            switch (style.IconHotspotUnitsY)
            {
            case HotSpotUnitType.Pixels:
                ms.TranslateY = (ms.Height - style.IconHotspotY) * -1;
                break;

            case HotSpotUnitType.Fraction:
                ms.TranslateY = (ms.Height * style.IconHotspotY) * -1;
                break;
            }
        }
Exemple #3
0
 /// <summary>
 /// Adds a KML style to the list.
 /// </summary>
 /// <param name="key">Unique key associated with the style, typically an id, filename or GUID.</param>
 /// <param name="style">Style description.</param>
 public void AddStyle(string key, KMLStyle style)
 {
     if (styles.ContainsKey(key))
     {
         styles[key] = style;
     }
     else
     {
         styles.Add(key, style);
     }
 }
 /// <summary>
 /// Copies a KML style object contents to another KML object.
 /// </summary>
 /// <param name="from">The source of the copy (contents copied to instance invoking this method).</param>
 public void CopyFrom(KMLStyle from)
 {
     this.StyleId           = from.StyleId;
     this.IconHref          = from.IconHref;
     this.IconHotspotX      = from.IconHotspotX;
     this.IconHotspotY      = from.IconHotspotY;
     this.IconHotspotUnitsX = from.IconHotspotUnitsX;
     this.IconHotspotUnitsY = from.IconHotspotUnitsY;
     this.IconHeading       = from.IconHeading;
     this.IconScale         = from.IconScale;
     this.IconImage         = from.IconImage;
     this.LineWidth         = from.LineWidth;
     this.LineColor         = from.LineColor;
     this.PolyFill          = from.PolyFill;
     this.PolyOutline       = from.PolyOutline;
     this.PolyFillColor     = from.PolyFillColor;
     this.BalloonText       = from.BalloonText;
 }
Exemple #5
0
 private static void GetRendererInfo(PlacemarkDescriptor feature, KMLStyle style, out string label, out string description)
 {
     label       = null;
     description = null;
     if (string.IsNullOrEmpty(style.StyleId))
     {
         //if the feature is not using a shared style -> create an entry by graphic with the name as ident
         if (feature.Attributes.ContainsKey("name"))
         {
             label = feature.Attributes["name"].ToString();
         }
         if (feature.Attributes.ContainsKey("description"))
         {
             description = feature.Attributes["description"].ToString();
         }
     }
     else
     {
         //if the feature is using a shared style -> create an entry for the style
         label = style.StyleId;
     }
 }
		private void GetStyleUrlAsync(string styleUrl, XDocument xDoc, System.Net.ICredentials credentials, Action<KMLStyle> callback)
		{
			KMLStyle kmlStyle = new KMLStyle();

			if (!String.IsNullOrEmpty(styleUrl))
			{
				// If the style url begins with a # symbol, then it is a reference to a style
				// defined within the current KML file. Otherwise it is a reference to a style
				// in an external file which must be downloaded and processed.
				if (styleUrl.Substring(0, 1) == "#")
				{
					// Remove first character (which is "#")
					string styleId = styleUrl.Substring(1);

					if (xDoc == null)
					{
						if (featureDefs.styles.ContainsKey(styleId))
							kmlStyle.CopyFrom(featureDefs.styles[styleId]);
					}
					else
					{
						XElement style = xDoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "Style" && (string)e.Attribute("id") == styleId);

						// Make sure the style was found and use first element
						if (style != null)
						{
							GetStyle(style, kmlStyle);
						}
						else
						{
							// See if the styleURL value is associated with a StyleMap node
							style = xDoc.Descendants().FirstOrDefault(e => e.Name.LocalName == "StyleMap" && (string)e.Attribute("id") == styleId);

							// Make sure the style map was found and use first element
							if (style != null)
							{
								GetStyleMapAsync(style, xDoc, credentials, callback);
								return;
							}
						}
					}
				}
				else
				{
					DownloadStyleAsync(styleUrl, credentials, callback);
					return;
				}
			}

			callback(kmlStyle);
		}
		/// <summary>
		/// Gets the 'normal' style of a style map.
		/// Getting this style may need recursive download.
		/// When the style is ready -> execute the callback.
		/// </summary>
		/// <remarks>
		/// The 'highlight' style is not used by the KmlLayer.
		/// </remarks>
		/// <param name="styleMap">The style map element to parse.</param>
		/// <param name="xDoc">The xDocument the style map is part of.</param>
		/// <param name="credentials">The credentials.</param>
		/// <param name="callback">The callback to call when the style is downloaded (if needed).</param>
		private void  GetStyleMapAsync(XElement styleMap, XDocument xDoc, ICredentials credentials, Action<KMLStyle> callback)
		{
			XNamespace kmlNS = styleMap.Name.Namespace;
			KMLStyle kmlStyle = null;
			foreach (XElement pair in styleMap.Descendants(kmlNS + "Pair"))
			{
				XElement key = pair.Element(kmlNS + "key");
				if (key != null)
				{
					if (key.Value == "normal")
					{
						XElement style = pair.Element(kmlNS + "Style");
						if (style != null)
						{
							kmlStyle = new KMLStyle();
							GetStyle(style, kmlStyle);
						}
						else
						{
							XElement styleUrl = pair.Element(kmlNS + "styleUrl");
							if (styleUrl != null)
							{
								XAttribute styleIdAttribute = styleMap.Attribute("id");
								string styleId = styleIdAttribute == null ? null : styleIdAttribute.Value;

								// Get the style from the styleUrl. This may need to downloading an external KML file
								GetStyleUrlAsync(styleUrl.Value, xDoc, credentials
									, kmlstyle =>
										{
											//// After obtaining the style (which may have involved recursion and downloading external KML files
											//// to resolve style URLs) be sure to always overwrite the styleId with the name given to this StyleMap.
											if (styleId != null && kmlstyle != null)
												kmlstyle.StyleId = styleId;
											callback(kmlstyle);
										});
								return;
							}
						}
					}
				}
			}

			// execute the callback with the found style (or null if not found)
			callback(kmlStyle);
		}
        /// <summary>
        /// Extracts a point from the input element and applies style information to the placemark descriptor.
        /// </summary>
        /// <param name="kmlStyle">KML Style information.</param>
        /// <param name="point">Point geometry information.</param>
		/// <returns>A PlacemarkDescriptor object representing the feature.</returns>
        private static PlacemarkDescriptor ExtractPoint(KMLStyle kmlStyle, XElement point)
        {
			XNamespace kmlNS = point.Name.Namespace;
			XElement coord = point.Element(kmlNS + "coordinates");
            if (coord != null)
            {
                // Extract geometry
                ESRI.ArcGIS.Client.Geometry.Geometry geom = ExtractCoordinate(coord.Value);
                if (geom != null)
                {
                    // Create symbol and use style information
                    PointSymbolDescriptor sym = new PointSymbolDescriptor();
                    sym.style = kmlStyle;

                    // Create feature descriptor from geometry and other information
                    PlacemarkDescriptor fd = new PlacemarkDescriptor()
                    {
                        Geometry = geom,
                        Symbol = sym
                    };

                    return fd;
                }
            }

            return null;
        }
        /// <summary>
        /// Extracts a polygon from the input element and applies style information to the placemark descriptor.
        /// </summary>
        /// <param name="kmlStyle">KML Style information.</param>
        /// <param name="geomElement">Polygon geometry information.</param>
        /// <returns>A PlacemarkDescriptor object representing the feature.</returns>
        private static PlacemarkDescriptor ExtractPolygon(KMLStyle kmlStyle, XElement geomElement)
        {
			XNamespace kmlNS = geomElement.Name.Namespace;
			ESRI.ArcGIS.Client.Geometry.Polygon polygon = new Polygon();

            // Extract outer polygon boundary
            XElement boundary;
            boundary = geomElement.Element(kmlNS + "outerBoundaryIs");
            if (boundary != null)
            {
                ESRI.ArcGIS.Client.Geometry.PointCollection pts = ExtractRing(boundary);
                if (pts != null && pts.Count > 0)
                {
                    polygon.Rings.Add(pts);
                }
            }

            // Extract holes (if any)
            IEnumerable<XElement> holes =
                from e in geomElement.Descendants(kmlNS + "innerBoundaryIs")
                select e;
            foreach (XElement hole in holes)
            {
                ESRI.ArcGIS.Client.Geometry.PointCollection pts = ExtractRing(hole);
                if (pts != null && pts.Count > 0)
                {
                    polygon.Rings.Add(pts);
                }
            }
            
            // Create symbol and use style information
            PolygonSymbolDescriptor sym = new PolygonSymbolDescriptor();
            sym.style = kmlStyle;

            if (polygon.Rings.Count > 0)
            {
                // Create feature descriptor from geometry and other information
                return new PlacemarkDescriptor()
                {
                    Geometry = polygon,
                    Symbol = sym
                };
            }

            return null;
        }
        private static void ExtractFeatureStyleInfo(KMLStyle kmlStyle, XElement placemark)
        {
        	XNamespace kmlNS = placemark.Name.Namespace;
            XElement colorElement = placemark.Element(kmlNS + "color");
            if (colorElement != null)
                kmlStyle.PolyFillColor = GetColorFromHexString(colorElement.Value);

            XElement iconElement = placemark.Element(kmlNS + "Icon");
            if (iconElement != null)
                kmlStyle.IconHref = iconElement.Value.Trim();
        }
		private static void StoreZipfileAndCallback(KMLStyle kmlStyle, Action<KMLStyle> callback, ZipFile zipFile)
		{
			if (zipFile != null)
			{
				if (kmlStyle.ZipFile == null && !String.IsNullOrEmpty(kmlStyle.IconHref))
				{
					kmlStyle.ZipFile = zipFile;
				}
				else
				{
					zipFile.Dispose();
				}
			}
			callback(kmlStyle);
		}
        /// <summary>
        /// Constructs a KMLStyle object that represents KML style contained in the input XElement.
        /// </summary>
        /// <param name="style">XElement containing KML style definition.</param>
        /// <param name="kmlStyle">KMLStyle object representing input style.</param>
        private static void GetStyle(XElement style, KMLStyle kmlStyle)
        {
            XNamespace kmlNS = style.Name.Namespace;
            XAttribute styleId = style.Attribute("id");
            if (styleId != null)
            {
                kmlStyle.StyleId = styleId.Value;
            }

            // If style contains an BalloonStyle, then extract that information
            XElement balloonStyle = style.Element(kmlNS + "BalloonStyle");
            if (balloonStyle != null)
            {
                XElement text = balloonStyle.Element(kmlNS + "text");
                if (text != null)
                {
                    kmlStyle.BalloonText = text.Value;
                }
            }

            // If style contains an IconStyle, then extract that information
            XElement iconStyle = style.Element(kmlNS + "IconStyle");
            if (iconStyle != null)
            {
                XElement icon = iconStyle.Element(kmlNS + "Icon");
                if (icon != null)
                {
                    XElement href = icon.Element(kmlNS + "href");
                    if (href != null)
                    {
                        string iconUrl = href.Value;
                        const string googlePal = "root://icons/palette-";
                        if(iconUrl.StartsWith(googlePal, StringComparison.OrdinalIgnoreCase))
                        {
                            // Replace Google earth built-in palette URL by the real URL
                            int x = 0;
                            int y = 0;
                            int numPalette = 0;
                            XElement xElement = icon.Element(kmlNS + "x");
                            if (xElement != null)
                                int.TryParse(xElement.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out x);
                            XElement yElement = icon.Element(kmlNS + "y");
                            if (yElement != null)
                                int.TryParse(yElement.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out y);
                            string pal = iconUrl.Substring(googlePal.Length, 1);
                            int.TryParse(pal, NumberStyles.Integer, CultureInfo.InvariantCulture, out numPalette);
                            if (numPalette > 0)
                            {
                                int numIcon = 8 * (7 - y/32) + x/32;
                                iconUrl = string.Format("http://maps.google.com/mapfiles/kml/pal{0}/icon{1}.png", numPalette, numIcon);
                            }
                        }

                        kmlStyle.IconHref = iconUrl;

                    }
                }

                // Extract IconColor
                XElement iconColor = iconStyle.Element(kmlNS + "color");
                if (iconColor != null)
                {
                    kmlStyle.IconColor = GetColorFromHexString(iconColor.Value);
                }

                // If the hotspot element is present, make use of it
                XElement hotspot = iconStyle.Element(kmlNS + "hotSpot");
                if (hotspot != null)
                {
                    XAttribute units;
                    XAttribute val;

                    units = hotspot.Attribute("xunits");
                    if (units != null)
                    {
                        try
                        {
                            kmlStyle.IconHotspotUnitsX = (HotSpotUnitType)Enum.Parse(typeof(HotSpotUnitType), units.Value, true);
                            val = hotspot.Attribute("x");
                            if (val != null)
                            {
                                double x;
                                if (double.TryParse(val.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out x))
                                    kmlStyle.IconHotspotX = x;
                            }
                        }
                        catch { }
                    }

                    units = hotspot.Attribute("yunits");
                    if (units != null)
                    {
                        try
                        {
                            kmlStyle.IconHotspotUnitsY = (HotSpotUnitType)Enum.Parse(typeof(HotSpotUnitType), units.Value, true);
                            val = hotspot.Attribute("y");
                            if (val != null)
                            {
                                double y;
                                if (double.TryParse(val.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out y))
                                    kmlStyle.IconHotspotY = y;
                            }
                        }
                        catch { }
                    }
                }

                // If the heading element is present, make use of it
                XElement heading = iconStyle.Element(kmlNS + "heading");
                if (heading != null)
                {
                    double degrees;
                    if (double.TryParse(heading.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out degrees))
                        kmlStyle.IconHeading = degrees;
                }

                // If the scale element is present, make use of it
                XElement scale = iconStyle.Element(kmlNS + "scale");
                if (scale != null)
                {
                    double scaleAmount;
                    if (double.TryParse(scale.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out scaleAmount))
                        kmlStyle.IconScale = scaleAmount;
                }
            }

            // If style contains a LineStyle, then extract that information
            XElement lineStyle = style.Element(kmlNS + "LineStyle");
            if (lineStyle != null)
            {
                XElement color = lineStyle.Element(kmlNS + "color");
                if (color != null)
                {
                    kmlStyle.LineColor = GetColorFromHexString(color.Value);
                }
                XElement width = lineStyle.Element(kmlNS + "width");
                if (width != null)
                {
                    double widthVal;
                    if (double.TryParse(width.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out widthVal))
                        kmlStyle.LineWidth = widthVal;
                }
            }

            // If style contains a PolyStyle, then extract that information
            XElement polyStyle = style.Element(kmlNS + "PolyStyle");
            if (polyStyle != null)
            {
                XElement color = polyStyle.Element(kmlNS + "color");
                if (color != null)
                {
                    kmlStyle.PolyFillColor = GetColorFromHexString(color.Value);
                }
                XElement fill = polyStyle.Element(kmlNS + "fill");
                if (fill != null)
                {
                    kmlStyle.PolyFill = StringToBool(fill.Value);
                }
                XElement outline = polyStyle.Element(kmlNS + "outline");
                if (outline != null)
                {
                    kmlStyle.PolyOutline = StringToBool(outline.Value);
                }
            }
        }
Exemple #13
0
        /// <summary>
        /// Creates graphic elements and adds them to the graphics layer.
        /// </summary>
        /// <param name="layer">Graphics layer that will have features added to it.</param>
        /// <param name="images">Dictionary of images coming from kmz content or from previous parsing</param>
        public void CreateGraphics(KmlGraphicsLayer layer, IDictionary <string, ImageBrush> images)
        {
            if (layer == null)
            {
                return;
            }

            GraphicCollection   graphics = new GraphicCollection();
            UniqueValueRenderer renderer = new UniqueValueRenderer();             // dummy renderer used to create the legend items (since creation of the swatches from the symbol is not that obvious)

            // Process each metadata feature in the list
            foreach (PlacemarkDescriptor feature in placemarks)
            {
                KMLStyle style = feature.Symbol.style;

                if (style.ZipFile != null)
                {
                    // Look for the image in the zip file
                    if (style.IconImage == null && !String.IsNullOrEmpty(style.IconHref))
                    {
                        style.IconImage = GetIconImage(style.ZipFile, style.IconHref.ToLower());
                    }

                    style.ZipFile.Dispose();
                    style.ZipFile = null;
                }

                //Define handlers upfront so we can unhook from them
#if SILVERLIGHT
                EventHandler <RoutedEventArgs>
#else
                EventHandler
#endif
                imageCompleted = null;
#if SILVERLIGHT
                EventHandler <ExceptionRoutedEventArgs>
#else
                EventHandler <ExceptionEventArgs>
#endif
                imageFailed = null;

                // If the style has an HREF then it is associated with an image
                if (style.IconImage == null && !String.IsNullOrEmpty(style.IconHref))
                {
                    // If the image is already loaded in the image dictionary, use it
                    if (images.ContainsKey(style.IconHref.ToLower()))
                    {
                        style.IconImage = images[style.IconHref.ToLower()];
                    }
                    else
                    {
                        // Get the image using the HREF and store the image in the images dictionary so that if
                        // other features reference it, it is cached
                        style.IconImage = GetIconImage(style.IconHref);
                        if (style.IconImage != null && (style.IconImage as ImageBrush).ImageSource != null)
                        {
                            var bi = (style.IconImage as ImageBrush).ImageSource as BitmapImage;
                            if (bi != null)
                            {
                                imageFailed = (s, e) =>
                                {
                                    var b = s as BitmapImage;
#if SILVERLIGHT
                                    if (imageCompleted != null)
                                    {
                                        b.ImageOpened -= imageCompleted;
                                    }
                                    if (imageFailed != null)
                                    {
                                        b.ImageFailed -= imageFailed;
                                    }
#else
                                    if (imageCompleted != null)
                                    {
                                        b.DownloadCompleted -= imageCompleted;
                                    }
                                    if (imageFailed != null)
                                    {
                                        b.DownloadFailed -= imageFailed;
                                    }
#endif
                                    var key = b.GetValue(BitmapImageKeyProperty) as string;
                                    layer.Dispatcher.BeginInvoke((Action) delegate
                                    {
                                        UpdateGraphicsAndRenderer(layer, renderer, key);
                                    });
                                };

#if SILVERLIGHT
                                bi.ImageFailed += imageFailed;
#else
                                bi.DownloadFailed += imageFailed;
#endif
                            }
                        }
                        images.Add(style.IconHref.ToLower(), style.IconImage);
                    }
                }

                // Create a new graphic from the metadata and construct the symbol using polymorphism
                Graphic g = new Graphic()
                {
                    Geometry   = feature.Geometry,
                    Symbol     = feature.Symbol.CreateSymbol(),
                    TimeExtent = feature.TimeExtent
                };
                g.SetValue(FeaturePlacemarkerDescriptorProperty, feature);
                // Create legend entry
                string label;
                string description;
                GetRendererInfo(feature, style, out label, out description);

                if (!string.IsNullOrEmpty(label) && !renderer.Infos.Any(info => info.Label == label))
                {
                    renderer.Infos.Add(new UniqueValueInfo {
                        Label = label, Description = description, Symbol = g.Symbol
                    });
                }

                // Adjust and assign picture marker symbol properties
                if (g.Geometry is ESRI.ArcGIS.Client.Geometry.MapPoint && g.Symbol is KmlPlaceMarkerSymbol)
                {
                    try
                    {
                        KmlPlaceMarkerSymbol ms = g.Symbol as KmlPlaceMarkerSymbol;

                        // To match sizing of Google Earth, default size of point images is 40x40
                        ms.Height = 40;
                        ms.Width  = 40;

                        ms.Fill      = style.IconImage;
                        ms.IconColor = style.IconColor;

                        // Default to half the pixel size (width and height) if symbol offsets are 0 (supported in wpf and sl3)
                        ImageBrush  ib = ms.Fill;
                        BitmapImage bi = ib.ImageSource as BitmapImage;
#if SILVERLIGHT
                        if (bi.PixelHeight == 0 || bi.PixelWidth == 0)
#else
                        if (bi.IsDownloading)
#endif
                        {
                            imageCompleted = (s, e) =>
                            {
                                var b = s as BitmapImage;
#if SILVERLIGHT
                                if (imageCompleted != null)
                                {
                                    b.ImageOpened -= imageCompleted;
                                }
                                if (imageFailed != null)
                                {
                                    b.ImageFailed -= imageFailed;
                                }
#else
                                if (imageCompleted != null)
                                {
                                    b.DownloadCompleted -= imageCompleted;
                                }
                                if (imageFailed != null)
                                {
                                    b.DownloadFailed -= imageFailed;
                                }
#endif
                                ComputeIconTranslationValues(style, ms, b);
                            };
#if SILVERLIGHT
                            bi.ImageOpened += imageCompleted;
#else
                            bi.DownloadCompleted += imageCompleted;
#endif
                        }
                        else
                        {
                            ComputeIconTranslationValues(style, ms, bi);
                        }
                    }
                    catch
                    {
                        g.Symbol = PointSymbolDescriptor.GetDefaultSymbol();
                        ComputeIconTranslationValues(style, g.Symbol as KmlPlaceMarkerSymbol, ((g.Symbol as KmlPlaceMarkerSymbol).Fill as ImageBrush).ImageSource as BitmapImage);
                        var info = renderer.Infos.FirstOrDefault(i => i.Label == label);
                        if (info != null)
                        {
                            info.Symbol = g.Symbol;
                        }
                    }
                }

                // Copy attributes values from metadata to graphic
                foreach (var attribute in feature.Attributes)
                {
                    g.Attributes.Add(attribute.Key, attribute.Value);
                }

                // If the balloontext property has been assigned a value in the style associated with this
                // graphic feature, then add it to the attributes collection.
                if (!String.IsNullOrEmpty(style.BalloonText))
                {
                    g.Attributes.Add("balloonText", style.BalloonText);
                }

                // Add graphic to graphics layer
                graphics.Add(g);
            }

            layer.Graphics = graphics;

            // keep the renderer for further usage (when QueryLegendInfos is called)
            layer.RendererBasedOnStyle = renderer;
        }
        private static void ComputeIconTranslationValues(KMLStyle style, KmlPlaceMarkerSymbol ms, BitmapImage bi)
        {
            if (bi.PixelWidth > 0)
                ms.Width = bi.PixelWidth;
            if (bi.PixelHeight > 0)
                ms.Height = bi.PixelHeight;

            switch (style.IconHotspotUnitsX)
            {
                case HotSpotUnitType.Pixels:
                    ms.TranslateX = style.IconHotspotX * -1;
                    break;

                case HotSpotUnitType.Fraction:
                    ms.TranslateX = (bi.PixelWidth * style.IconHotspotX) * -1;
                    break;
            }

            switch (style.IconHotspotUnitsY)
            {
                case HotSpotUnitType.Pixels:
                    ms.TranslateY = (ms.Height - style.IconHotspotY) * -1;
                    break;

                case HotSpotUnitType.Fraction:
                    ms.TranslateY = (bi.PixelHeight * style.IconHotspotY) * -1;
                    break;
            }
        }
        /// <summary>
        /// Constructs a KMLStyle object that represents KML style contained in the input XElement.
        /// </summary>
        /// <param name="style">XElement containing KML style definition.</param>
        /// <param name="kmlStyle">KMLStyle object representing input style.</param>
        private static void GetStyle(XElement style, KMLStyle kmlStyle)
        {
        	XNamespace kmlNS = style.Name.Namespace;
            XAttribute styleId = style.Attribute("id");
            if (styleId != null)
            {
                kmlStyle.StyleId = styleId.Value;
            }

            // If style contains an BalloonStyle, then extract that information
            XElement balloonStyle = style.Element(kmlNS + "BalloonStyle");
            if (balloonStyle != null)
            {
                XElement text = balloonStyle.Element(kmlNS + "text");
                if (text != null)
                {
                    kmlStyle.BalloonText = text.Value;
                }
            }

            // If style contains an IconStyle, then extract that information
            XElement iconStyle = style.Element(kmlNS + "IconStyle");
            if (iconStyle != null)
            {
                XElement icon = iconStyle.Element(kmlNS + "Icon");
                if (icon != null)
                {
                    XElement href = icon.Element(kmlNS + "href");
                    if (href != null)
                    {
                        kmlStyle.IconHref = href.Value;
                    }
                }

                // If the hotspot element is present, make use of it
                XElement hotspot = iconStyle.Element(kmlNS + "hotSpot");
                if (hotspot != null)
                {
                    XAttribute units;
                    XAttribute val;

                    units = hotspot.Attribute("xunits");
                    if (units != null)
                    {
                        try
                        {
                            kmlStyle.IconHotspotUnitsX = (HotSpotUnitType)Enum.Parse(typeof(HotSpotUnitType), units.Value, true);
                            val = hotspot.Attribute("x");
                            if (val != null)
                            {
                                double x;
                                if (double.TryParse(val.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out x))
                                    kmlStyle.IconHotspotX = x;
                            }
                        }
                        catch { }
                    }

                    units = hotspot.Attribute("yunits");
                    if (units != null)
                    {
                        try
                        {
                            kmlStyle.IconHotspotUnitsY = (HotSpotUnitType)Enum.Parse(typeof(HotSpotUnitType), units.Value, true);
                            val = hotspot.Attribute("y");
                            if (val != null)
                            {
                                double y;
                                if (double.TryParse(val.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out y))
                                    kmlStyle.IconHotspotY = y;
                            }
                        }
                        catch { }
                    }
                }

                // If the heading element is present, make use of it
                XElement heading = iconStyle.Element(kmlNS + "heading");
                if (heading != null)
                {
                    double degrees;
                    if (double.TryParse(heading.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out degrees))
                        kmlStyle.IconHeading = degrees;
                }

                // If the scale element is present, make use of it
                XElement scale = iconStyle.Element(kmlNS + "scale");
                if (scale != null)
                {
                    double scaleAmount;
                    if (double.TryParse(scale.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out scaleAmount))
                        kmlStyle.IconScale = scaleAmount;
                }
            }

            // If style contains a LineStyle, then extract that information
            XElement lineStyle = style.Element(kmlNS + "LineStyle");
            if (lineStyle != null)
            {
                XElement color = lineStyle.Element(kmlNS + "color");
                if (color != null)
                {
                    kmlStyle.LineColor = GetColorFromHexString(color.Value);
                }
                XElement width = lineStyle.Element(kmlNS + "width");
                if (width != null)
                {
                    double widthVal;
                    if (double.TryParse(width.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out widthVal))
                        kmlStyle.LineWidth = widthVal;
                }
            }

            // If style contains a PolyStyle, then extract that information
            XElement polyStyle = style.Element(kmlNS + "PolyStyle");
            if (polyStyle != null)
            {
                XElement color = polyStyle.Element(kmlNS + "color");
                if (color != null)
                {
                    kmlStyle.PolyFillColor = GetColorFromHexString(color.Value);
                }
                XElement fill = polyStyle.Element(kmlNS + "fill");
                if (fill != null)
                {
                    kmlStyle.PolyFill = StringToBool(fill.Value);
                }
                XElement outline = polyStyle.Element(kmlNS + "outline");
                if (outline != null)
                {
                    kmlStyle.PolyOutline = StringToBool(outline.Value);
                }
            }
        }
        /// <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;
        }
 /// <summary>
 /// Copies a KML style object contents to another KML object.
 /// </summary>
 /// <param name="from">The source of the copy (contents copied to instance invoking this method).</param>
 public void CopyFrom(KMLStyle from)
 {
     this.StyleId = from.StyleId;
     this.IconHref = from.IconHref;
     this.IconHotspotX = from.IconHotspotX;
     this.IconHotspotY = from.IconHotspotY;
     this.IconHotspotUnitsX = from.IconHotspotUnitsX;
     this.IconHotspotUnitsY = from.IconHotspotUnitsY;
     this.IconHeading = from.IconHeading;
     this.IconScale = from.IconScale;
     this.IconImage = from.IconImage;
     this.LineWidth = from.LineWidth;
     this.LineColor = from.LineColor;
     this.PolyFill = from.PolyFill;
     this.PolyOutline = from.PolyOutline;
     this.PolyFillColor = from.PolyFillColor;
     this.BalloonText = from.BalloonText;
     this.IconColor = from.IconColor;
 }
		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>
        /// Adds a KML style to the list.
        /// </summary>
        /// <param name="key">Unique key associated with the style, typically an id, filename or GUID.</param>
        /// <param name="style">Style description.</param>
        public void AddStyle(string key, KMLStyle style)
        {
			if (styles.ContainsKey(key))
				styles[key] = style;
			else
				styles.Add(key, style);
		}
        /// <summary>
        /// Extracts a polygon from the input element and applies style information to the placemark descriptor.
        /// </summary>
        /// <param name="kmlStyle">KML Style information.</param>
        /// <param name="geomElement">Polygon geometry information.</param>
		/// <returns>A PlacemarkDescriptor object representing the feature.</returns>
        private static PlacemarkDescriptor ExtractLatLonBox(KMLStyle kmlStyle, XElement geomElement)
        {
			XNamespace kmlNS = geomElement.Name.Namespace;
			ESRI.ArcGIS.Client.Geometry.Polygon polygon = new Polygon();
            double? north = null, south = null, east = null, west = null;
            double temp;
            XElement boundary;

            // Extract box values
            boundary = geomElement.Element(kmlNS + "north");
            if (boundary != null)
            {
                if (double.TryParse(boundary.Value, System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out temp))
                    north = temp;
            }
            boundary = geomElement.Element(kmlNS + "south");
            if (boundary != null)
            {
                if (double.TryParse(boundary.Value, System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out temp))
                    south = temp;
            }
            boundary = geomElement.Element(kmlNS + "east");
            if (boundary != null)
            {
                if (double.TryParse(boundary.Value, System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out temp))
                    east = temp;
            }
            boundary = geomElement.Element(kmlNS + "west");
            if (boundary != null)
            {
                if (double.TryParse(boundary.Value, System.Globalization.NumberStyles.Float, CultureInfo.InvariantCulture, out temp))
                    west = temp;
            }

            if (north.HasValue && south.HasValue && east.HasValue && west.HasValue)
            {
                ESRI.ArcGIS.Client.Geometry.PointCollection pts = new PointCollection();
                MapPoint mp1 = new MapPoint(west.Value, north.Value);
                pts.Add(mp1);
                MapPoint mp2 = new MapPoint(east.Value, north.Value);
                pts.Add(mp2);
                MapPoint mp3 = new MapPoint(east.Value, south.Value);
                pts.Add(mp3);
                MapPoint mp4 = new MapPoint(west.Value, south.Value);
                pts.Add(mp4);

                polygon.Rings.Add(pts);

                // Create symbol and use style information
                PolygonSymbolDescriptor sym = new PolygonSymbolDescriptor();
                sym.style = kmlStyle;

                // Create feature descriptor from geometry and other information
                return new PlacemarkDescriptor()
                {
                    Geometry = polygon,
                    Symbol = sym
                };
            }

            return null;
        }
		private static void GetRendererInfo(PlacemarkDescriptor feature, KMLStyle style, out string label, out string description)
		{
			label = null;
			description = null;
			if (string.IsNullOrEmpty(style.StyleId))
			{
				//if the feature is not using a shared style -> create an entry by graphic with the name as ident
				if (feature.Attributes.ContainsKey("name"))
					label = feature.Attributes["name"].ToString();
				if (feature.Attributes.ContainsKey("description"))
					description = feature.Attributes["description"].ToString();
			}
			else
			{
				//if the feature is using a shared style -> create an entry for the style
				label = style.StyleId;
			}
		}
        /// <summary>
        /// Extracts a polyline from the input element and applies style information to the placemark descriptor.
        /// </summary>
        /// <param name="kmlStyle">KML Style information.</param>
        /// <param name="line">Polyline geometry information.</param>
		/// <returns>A PlacemarkDescriptor object representing the feature.</returns>
        private static PlacemarkDescriptor ExtractPolyLine(KMLStyle kmlStyle, XElement line)
        {
			XNamespace kmlNS = line.Name.Namespace;
			XElement coord = line.Element(kmlNS + "coordinates");
            if (coord != null)
            {
                // Extract coordinates and build geometry
                ESRI.ArcGIS.Client.Geometry.PointCollection pts = ExtractCoordinates(coord);
                if (pts != null && pts.Count > 0)
                {
                    ESRI.ArcGIS.Client.Geometry.Polyline polyline = new ESRI.ArcGIS.Client.Geometry.Polyline();
                    polyline.Paths.Add(pts);

                    // Create symbol and use style information
                    LineSymbolDescriptor sym = new LineSymbolDescriptor();
                    sym.style = kmlStyle;

                    // Create feature descriptor from geometry and other information
                    return new PlacemarkDescriptor()
                    {
                        Geometry = polyline,
                        Symbol = sym
                    };
                }
            }

            return null;
        }
        private static void ComputeIconTranslationValues(KMLStyle style, KmlPlaceMarkerSymbol ms, BitmapImage bi)
        {
            // To match sizing of Google Earth, default size of point images is 40x40
            // Note: the iconScale will be applied later globally to the symbol
            ms.Height = 40;
            ms.Width = 40;

            switch (style.IconHotspotUnitsX)
            {
                case HotSpotUnitType.Pixels:
                    ms.TranslateX = style.IconHotspotX * -1;
                    break;

                case HotSpotUnitType.Fraction:
                    ms.TranslateX = (ms.Width * style.IconHotspotX) * -1;
                    break;
            }

            switch (style.IconHotspotUnitsY)
            {
                case HotSpotUnitType.Pixels:
                    ms.TranslateY = (ms.Height - style.IconHotspotY) * -1;
                    break;

                case HotSpotUnitType.Fraction:
                    ms.TranslateY = (ms.Height * style.IconHotspotY) * -1;
                    break;
            }
        }