public async Task<Image> GetStaticMapImageForCenterPushPin(MapRequest mapRequest)
        {
            var mapCenterPushPin = mapRequest.CenterPushpin;
            var mapCenterPoint = mapCenterPushPin.Coordinate;
            var mapDescription = mapRequest.MapDescription;
            var centerPoint = string.Format("{0},{1}", mapCenterPoint.Latitude, mapCenterPoint.Longitude);
            var mapPathParams = new SortedList<int, string>
                                    {
                                        { 1, mapDescription.ImagerySet.ToString() },
                                        { 2, centerPoint },
                                        { 3, mapDescription.ZoomLevel.ToString() }
                                    };

            var mapImageQueryArgs = new List<KeyValuePair<string, string>>();

            AddDisplayPushpins(mapRequest, mapImageQueryArgs);

            AddMapSizeQueryArguments(mapDescription, mapImageQueryArgs);

            AddDeclutterPinsQueryArguments(mapDescription.DeclutterPins, mapImageQueryArgs);

            AddMapKeyQueryArguments(this.BingAPIKey, mapImageQueryArgs);

            var mapImage = await this.restClient.DownloadImage(BingRestImageryRoadmapsEndpoint, mapPathParams, mapImageQueryArgs);

            return mapImage.ReSize(mapDescription.MapImageScaleFactor);
        }
        public async Task<Stream> GenerateMapStream(MapRequest mapDescriptionRequest)
        {            
            var stream = new MemoryStream();

            using (var mapImage = await this.GenerateMapImage(mapDescriptionRequest))
            {
                mapImage.Save(stream, mapDescriptionRequest.MapDescription.ImageFormat);    
            }
            
            return stream;
        }
        /// <inheritdoc />
        public async Task<byte[]> GenerateMap(
            MapRequest mapDescriptionRequest)
        {            
            byte[] mapImageBytes;
            
            using (var mapImage = await this.GenerateMapImage(mapDescriptionRequest))
            {
                mapImageBytes = mapImage == null ? null : mapImage.ToByteArray(mapDescriptionRequest.MapDescription.ImageFormat);
            }

            return mapImageBytes;
        }
        private static void AddDisplayPushpins(MapRequest mapRequest, List<KeyValuePair<string, string>> mapImageQueryArgs)
        {
            //If pushping presented and want them to be printed. what do do for map area?
            IEnumerable<MapPushpin> displayPushpins = null;
            if (mapRequest.Pushpins != null)
            {
                displayPushpins = mapRequest.Pushpins.Where(pp => !pp.HidePushpin);                
            }

            if (displayPushpins != null || mapRequest.CenterPushpin != null)
            {
                AddPushPinQueryArgumentsWithLabels(displayPushpins, mapRequest.CenterPushpin, mapImageQueryArgs);
            }            
        }
        public async Task<Image> GetStaticMapImage(MapBoundingBox mapAreaBoundingBox, MapRequest mapRequest)
        {
            var mapDescription = mapRequest.MapDescription;

            var mapPathParams = new SortedList<int, string> { { 1, mapDescription.ImagerySet.ToString() } };

            var mapImageQueryArgs = new List<KeyValuePair<string, string>>();
            
            AddMapSizeQueryArguments(mapDescription, mapImageQueryArgs);
            
            AddMapAreaQueryArguments(mapAreaBoundingBox, mapImageQueryArgs);

            AddDisplayPushpins(mapRequest, mapImageQueryArgs);

            AddMapKeyQueryArguments(this.BingAPIKey, mapImageQueryArgs);

            var mapImage = await this.restClient.DownloadImage(BingRestImageryRoadmapsEndpoint, mapPathParams, mapImageQueryArgs);

            return mapImage.ReSize(mapDescription.MapImageScaleFactor);
        }
        /// <inheritdoc />
        public async Task<Image> GenerateMapImage(
            MapRequest mapDescriptionRequest)
        {
            if (mapDescriptionRequest == null)
            {
                throw new ArgumentNullException("mapDescriptionRequest");
            }

            if (mapDescriptionRequest.MapDescription == null)
            {
                throw new NoNullAllowedException("MapDescription");
            }

            List<MapPoint> pushpinCoordinates = null;
            MapBoundingBox mapAreaBoundingBox = null;
            Image mapImage = null;
            if (mapDescriptionRequest.MapBoundingBoxType == MapBoundingBoxType.BestFitPoints)
            {
                if (mapDescriptionRequest.Pushpins == null || !mapDescriptionRequest.Pushpins.Any())
                {
                    throw new ArgumentNullException("MapBoundingBoxType.BestFitPushPin doesnt allow mapDescriptionRequest.Pushpins to be null.");
                }

                if (mapDescriptionRequest.Pushpins.Count() == 1)
                {
                    throw new NotSupportedException("MapBoundingBoxType.BestFitPoints must have more than or equal to two points. Change to MapBoundingBoxType.CenterPushPin if you want center point and zoom level based besetfit view.");
                }

                var pushpinList = mapDescriptionRequest.Pushpins.ToList();
                pushpinCoordinates = pushpinList.Select(pp => pp.Coordinate).ToList();

                mapAreaBoundingBox = this.mapCalculations.CalculateBoundingBox(pushpinCoordinates);

                mapImage = await this.imageryProvider.GetStaticMapImage(
                mapAreaBoundingBox,
                mapDescriptionRequest);

                var customPushpins = mapDescriptionRequest.Pushpins.Where(pp => pp.CustomPushpinIcon != null);
                if (customPushpins.Any())
                {
                    var mapMetadata =
                        await this.imageryProvider.GetStaticMapMetaData(mapAreaBoundingBox, mapDescriptionRequest);

                    
                    var mapShapeLayer = await this.mapShapeLayerDrawing.DrawShapeLayerAsync(mapDescriptionRequest.Geometry, mapAreaBoundingBox, mapDescriptionRequest.MapDescription);
                               

                    var mapLayers = new List<Image>();

                    if (mapShapeLayer != null)
                    {
                        mapLayers.Add(mapShapeLayer);
                    }

                    var pushpinMapLayer = this.mapLayerDrawing.GetMapLayer(mapMetadata, mapDescriptionRequest);
                    mapLayers.Add(pushpinMapLayer);

                    mapImage = this.MergeMapLayers(mapImage, mapLayers);
                }                
            }
            else if (mapDescriptionRequest.MapBoundingBoxType == MapBoundingBoxType.ExplicitBoundingBox)
            {
                if (mapDescriptionRequest.MapExplicitBoundingBox == null)
                {
                    throw new ArgumentNullException("MapExplicitBoundingBox cant be null when using MapBoundingBoxType.ExplicitBoundingBox mode.");                    
                }

                var mapLayers = new List<Image>();

                mapAreaBoundingBox = mapDescriptionRequest.MapExplicitBoundingBox;
                mapImage = await this.imageryProvider.GetStaticMapImage(
                mapAreaBoundingBox,
                mapDescriptionRequest);

                var mapShapeLayer = await this.mapShapeLayerDrawing.DrawShapeLayerAsync(mapDescriptionRequest.Geometry, mapAreaBoundingBox, mapDescriptionRequest.MapDescription);
                if (mapShapeLayer != null)
                {
                    mapLayers.Add(mapShapeLayer);
                }

                mapImage = this.MergeMapLayers(mapImage, mapLayers);

            }
            else if (mapDescriptionRequest.MapBoundingBoxType == MapBoundingBoxType.CenterPushPin)
            {
                if (mapDescriptionRequest.CenterPushpin == null)
                {
                    throw new ArgumentNullException("Center Pushpin cant be null when using MapBoundingBoxType.CenterPushPin mode.");
                }

                mapImage = await this.imageryProvider.GetStaticMapImageForCenterPushPin(mapDescriptionRequest);
            }
            else
            {
                throw new NotSupportedException("Unsupported MapBoundingBoxType");
            }                        

            mapImage.Save(@"D:\Temp\MapImages\" + Guid.NewGuid() + "." + mapDescriptionRequest.MapDescription.ImageFormat, mapDescriptionRequest.MapDescription.ImageFormat);
            
            return mapImage;            
        }
        public Image GetMapLayer(IMapMetadata analyticMapMetadata, MapRequest mapRequest)
        {
            MapDescription mapDescription = mapRequest.MapDescription;
            IEnumerable<MapPushpin> pushpinDescriptions = mapRequest.Pushpins;

            if (pushpinDescriptions == null || !pushpinDescriptions.Any())
            {
                throw new ArgumentNullException("pushpinDescriptions");
            }

            if (analyticMapMetadata == null)
            {
                throw new ArgumentNullException("analyticMapMetadata");
            }

            if (mapDescription == null)
            {
                throw new ArgumentNullException("mapDescription");
            }

            var scaleFactor = mapDescription.MapImageScaleFactor;
            var width = (int)(analyticMapMetadata.MapWidth * scaleFactor);
            var height = (int)(analyticMapMetadata.MapHeight * scaleFactor);
            var image = new Bitmap(width, height);

            using (var g = Graphics.FromImage(image))
            {
                g.Clear(Color.Transparent);
                foreach (var pushPinDescription in pushpinDescriptions.OrderBy(pp => pp.Zindex))
                {
                    var pushPinMetadata = this.GetPushpinMetadata(pushPinDescription.Coordinate, analyticMapMetadata);

                    if (pushPinMetadata == null)
                    {
                        continue;
                    }

                    var pushPinImage = this.GetPushPinImage(
                        pushPinDescription.CustomPushpinIcon,
                        pushPinDescription.Label,
                        mapDescription);

                    if (pushPinImage == null)
                    {
                        continue;
                    }

                    var pushPinOffsetX = GetPushPinOffsetX(pushPinMetadata, scaleFactor, pushPinImage);
                    var pushPinOffsetY = GetPushPinOffsetY(
                        pushPinMetadata,
                        scaleFactor,
                        pushPinImage);

                    g.DrawImage(pushPinImage, pushPinOffsetX, pushPinOffsetY, pushPinImage.Width, pushPinImage.Height);
                }

                g.Flush();
            }

            return image;
        }
        public async Task<IMapMetadata> GetStaticMapMetaData(
            MapBoundingBox mapAreaBoundingBox,            
            MapRequest mapRequest)
        {
            var mapDescription = mapRequest.MapDescription;
            IEnumerable<MapPoint> mapPushPinCoordinates = mapRequest.Pushpins.Where(pp=>pp.CustomPushpinIcon != null).Select(p=>p.Coordinate);

            if (mapAreaBoundingBox == null)
            {
                throw new ArgumentNullException("mapAreaBoundingBox");
            }

            var pushPinCoordinates = mapPushPinCoordinates as IList<MapPoint> ?? mapPushPinCoordinates.ToList();
            if (mapPushPinCoordinates == null || !pushPinCoordinates.Any())
            {
                throw new ArgumentNullException("mapPushPinCoordinates");
            }

            var mapPathParams = new SortedList<int, string> { { 1, mapDescription.ImagerySet.ToString() } };

            var metaDataQueryArgs = new List<KeyValuePair<string, string>>
                                        {
                                            new KeyValuePair<string, string>(
                                                "o",
                                                "json"),
                                            new KeyValuePair<string, string>(
                                                "mapmetadata",
                                                "1"),
                                        };

            AddMapSizeQueryArguments(mapDescription, metaDataQueryArgs);

            AddMapAreaQueryArguments(mapAreaBoundingBox, metaDataQueryArgs);            

            AddMapKeyQueryArguments(this.BingAPIKey, metaDataQueryArgs);

            var bingMetaData = await this.GetBingMapMetadata(pushPinCoordinates, mapPathParams, metaDataQueryArgs);

            if (bingMetaData == null)
            {
                return null;
            }

            // NOTE: do this in try catch incase ResourceSets or MetadataResources returned from BING are null.
            try
            {
                return bingMetaData.ResourceSets.FirstOrDefault().MetadataResources.FirstOrDefault();
            }
            catch (Exception ex)
            {
                throw new InvalidDataException("invalid MetaData from Bing REST api", ex);
            }
        }