/// <summary>
        /// Builds the url for requesting tiles.
        /// </summary>
        /// <param name="key">Tile coordinate.</param>
        /// <returns>Tile url.</returns>
        public string GetTileUrl(MapSectionByTileKey key)
        {
            var url = new StringBuilder();

            // derive url from SOAP service url
            url.Append(this.url.Replace("/ws/", "/rest/"));

            // append tile coordinate
            url.AppendFormat("/tile/{0}/{1}/{2}", key.zoomLevel, key.x, key.y);

            // setup and append the profile part of the request
            url.AppendFormat("/{0}{1}", CustomProfile,
                             string.Join("", EffectiveXMapLayers.Select(layer => layer.AsProfileSuffix()).ToArray()));

            // a valid object information sink requires us to request JSON
            // But: If we're dealing with base layers only, we don't need to request object information.
            if (MapObjectInformationSink != null && HasRealCustomLayers)
            {
                url.Append("/json");
            }

            // and finally append token authentication when set
            if (AuthenticateWithToken)
            {
                url.AppendFormat("?xtok={0}", Password);
            }

            // return full url string
            return(url.ToString());
        }
        /// <summary>
        /// Requests a tile; Please refer to GetImageStream(int, int, int) for a description.
        /// </summary>
        /// <param name="key">The tile coordinate in form of a MapSectionByTileKey.</param>
        /// <returns>The stream wrapping the tile image.</returns>
        private Stream GetImageStream(MapSectionByTileKey key)
        {
            // create and initialize request
            var req = WebRequest.Create(GetTileUrl(key));

            req.Timeout = 8000;

            // get response
            var resp = req.GetResponse();

            // return response as is if content type is not application/json
            if (resp.ContentType != "application/json")
            {
                return(resp.GetResponseStream());
            }

            // response is in json format; we need to read and parse the json string
            try
            {
                using (var reader = new StreamReader(resp.GetResponseStream()))
                {
                    var jsonResponse = JSON.Parse(reader.ReadToEnd());

                    // parse the object information and call the object information sink, when set
                    MapObjectInformationSink?.Invoke(
                        ParseFeatures(jsonResponse),
                        new TileMapRectangle(key.x, key.y, key.zoomLevel),
                        new Size(256, 256)
                        );

                    // read and parse the image
                    return(new MemoryStream(Convert.FromBase64String(jsonResponse["image"].Value)));
                }
            }
            // be sure to close reponse when done
            finally { resp.Close(); }
        }