AirScout/OxyPlot/Source/Examples/ExampleLibrary/Annotations/TileMapAnnotation.cs

389 wiersze
14 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TileMapAnnotation.cs" company="OxyPlot">
// Copyright (c) 2014 OxyPlot contributors
// </copyright>
// <summary>
// Provides an annotation that shows a tile based map.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ExampleLibrary
{
using OxyPlot;
using OxyPlot.Annotations;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
/// <summary>
/// Provides an annotation that shows a tile based map.
/// </summary>
/// <remarks>The longitude and latitude range of the map is defined by the range of the x and y axis, respectively.</remarks>
public class TileMapAnnotation : Annotation
{
/// <summary>
/// The image cache.
/// </summary>
private readonly Dictionary<string, OxyImage> images = new Dictionary<string, OxyImage>();
/// <summary>
/// The download queue.
/// </summary>
private readonly Queue<string> queue = new Queue<string>();
/// <summary>
/// The current number of downloads
/// </summary>
private int numberOfDownloads;
/// <summary>
/// Initializes a new instance of the <see cref="TileMapAnnotation" /> class.
/// </summary>
public TileMapAnnotation()
{
this.TileSize = 256;
this.MinZoomLevel = 0;
this.MaxZoomLevel = 20;
this.Opacity = 1.0;
this.MaxNumberOfDownloads = 8;
}
/// <summary>
/// Gets or sets the max number of simultaneous downloads.
/// </summary>
/// <value>The max number of downloads.</value>
public int MaxNumberOfDownloads { get; set; }
/// <summary>
/// Gets or sets the URL.
/// </summary>
/// <value>The URL.</value>
public string Url { get; set; }
/// <summary>
/// Gets or sets the copyright notice.
/// </summary>
/// <value>The copyright notice.</value>
public string CopyrightNotice { get; set; }
/// <summary>
/// Gets or sets the size of the tiles.
/// </summary>
/// <value>The size of the tiles.</value>
public int TileSize { get; set; }
/// <summary>
/// Gets or sets the min zoom level.
/// </summary>
/// <value>The min zoom level.</value>
public int MinZoomLevel { get; set; }
/// <summary>
/// Gets or sets the max zoom level.
/// </summary>
/// <value>The max zoom level.</value>
public int MaxZoomLevel { get; set; }
/// <summary>
/// Gets or sets the opacity.
/// </summary>
/// <value>The opacity.</value>
public double Opacity { get; set; }
/// <summary>
/// Renders the annotation on the specified context.
/// </summary>
/// <param name="rc">The render context.</param>
public override void Render(IRenderContext rc)
{
base.Render(rc);
var lon0 = this.XAxis.ActualMinimum;
var lon1 = this.XAxis.ActualMaximum;
var lat0 = this.YAxis.ActualMinimum;
var lat1 = this.YAxis.ActualMaximum;
// the desired number of tiles horizontally
double tilesx = this.PlotModel.Width / this.TileSize;
// calculate the desired zoom level
var n = tilesx / (((lon1 + 180) / 360) - ((lon0 + 180) / 360));
var zoom = (int)Math.Round(Math.Log(n) / Math.Log(2));
if (zoom < this.MinZoomLevel)
{
zoom = this.MinZoomLevel;
}
if (zoom > this.MaxZoomLevel)
{
zoom = this.MaxZoomLevel;
}
// find tile coordinates for the corners
double x0, y0;
LatLonToTile(lat0, lon0, zoom, out x0, out y0);
double x1, y1;
LatLonToTile(lat1, lon1, zoom, out x1, out y1);
double xmax = Math.Max(x0, x1);
double xmin = Math.Min(x0, x1);
double ymax = Math.Max(y0, y1);
double ymin = Math.Min(y0, y1);
var clippingRectangle = this.GetClippingRect();
// Add the tiles
for (var x = (int)xmin; x < xmax; x++)
{
for (var y = (int)ymin; y < ymax; y++)
{
string uri = this.GetTileUri(x, y, zoom);
var img = this.GetImage(uri, rc.RendersToScreen);
if (img == null)
{
continue;
}
// transform from tile coordinates to lat/lon
double latitude0, latitude1, longitude0, longitude1;
TileToLatLon(x, y, zoom, out latitude0, out longitude0);
TileToLatLon(x + 1, y + 1, zoom, out latitude1, out longitude1);
// transform from lat/lon to screen coordinates
var s00 = this.Transform(longitude0, latitude0);
var s11 = this.Transform(longitude1, latitude1);
var r = OxyRect.Create(s00.X, s00.Y, s11.X, s11.Y);
// draw the image
rc.DrawClippedImage(clippingRectangle, img, r.Left, r.Top, r.Width, r.Height, this.Opacity, true);
}
}
// draw the copyright notice
var p = new ScreenPoint(clippingRectangle.Right - 5, clippingRectangle.Bottom - 5);
var textSize = rc.MeasureText(this.CopyrightNotice, this.ActualFont, this.ActualFontSize, this.ActualFontWeight);
rc.DrawRectangle(new OxyRect(p.X - textSize.Width - 2, p.Y - textSize.Height - 2, textSize.Width + 4, textSize.Height + 4), OxyColor.FromAColor(200, OxyColors.White), OxyColors.Undefined);
rc.DrawText(
p,
this.CopyrightNotice,
OxyColors.Black,
this.ActualFont,
this.ActualFontSize,
this.ActualFontWeight,
0,
HorizontalAlignment.Right,
VerticalAlignment.Bottom);
}
/// <summary>
/// Transforms a position to a tile coordinate.
/// </summary>
/// <param name="latitude">The latitude.</param>
/// <param name="longitude">The longitude.</param>
/// <param name="zoom">The zoom.</param>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
private static void LatLonToTile(double latitude, double longitude, int zoom, out double x, out double y)
{
// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
int n = 1 << zoom;
double lat = latitude / 180 * Math.PI;
x = (longitude + 180.0) / 360.0 * n;
y = (1.0 - (Math.Log(Math.Tan(lat) + (1.0 / Math.Cos(lat))) / Math.PI)) / 2.0 * n;
}
/// <summary>
/// Transforms a tile coordinate (x,y) to a position.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <param name="zoom">The zoom.</param>
/// <param name="latitude">The latitude.</param>
/// <param name="longitude">The longitude.</param>
private static void TileToLatLon(double x, double y, int zoom, out double latitude, out double longitude)
{
int n = 1 << zoom;
longitude = (x / n * 360.0) - 180.0;
double lat = Math.Atan(Math.Sinh(Math.PI * (1 - (2 * y / n))));
latitude = lat * 180.0 / Math.PI;
}
/// <summary>
/// Gets the image from the specified uri.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="asyncLoading">Get the image asynchronously if set to <c>true</c>. The plot model will be invalidated when the image has been downloaded.</param>
/// <returns>The image.</returns>
/// <remarks>This method gets the image from cache, or starts an async download.</remarks>
private OxyImage GetImage(string uri, bool asyncLoading)
{
OxyImage img;
if (this.images.TryGetValue(uri, out img))
{
return img;
}
if (!asyncLoading)
{
return this.Download(uri);
}
lock (this.queue)
{
// 'reserve' an image (otherwise multiple downloads of the same uri may happen)
this.images[uri] = null;
this.queue.Enqueue(uri);
}
this.BeginDownload();
return null;
}
/// <summary>
/// Downloads the image from the specified URI.
/// </summary>
/// <param name="uri">The URI.</param>
/// <returns>The image</returns>
private OxyImage Download(string uri)
{
OxyImage img = null;
var mre = new ManualResetEvent(false);
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
request.BeginGetResponse(
r =>
{
try
{
if (request.HaveResponse)
{
var response = request.EndGetResponse(r);
var stream = response.GetResponseStream();
var ms = new MemoryStream();
stream.CopyTo(ms);
var buffer = ms.ToArray();
img = new OxyImage(buffer);
this.images[uri] = img;
}
}
catch (Exception e)
{
var ie = e;
while (ie != null)
{
System.Diagnostics.Debug.WriteLine(ie.Message);
ie = ie.InnerException;
}
}
finally
{
mre.Set();
}
},
request);
mre.WaitOne();
return img;
}
/// <summary>
/// Starts the next download in the queue.
/// </summary>
private void BeginDownload()
{
if (this.numberOfDownloads >= this.MaxNumberOfDownloads)
{
return;
}
string uri = this.queue.Dequeue();
var request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "GET";
Interlocked.Increment(ref this.numberOfDownloads);
request.BeginGetResponse(
r =>
{
Interlocked.Decrement(ref this.numberOfDownloads);
try
{
if (request.HaveResponse)
{
var response = request.EndGetResponse(r);
var stream = response.GetResponseStream();
this.DownloadCompleted(uri, stream);
}
}
catch (Exception e)
{
var ie = e;
while (ie != null)
{
System.Diagnostics.Debug.WriteLine(ie.Message);
ie = ie.InnerException;
}
}
},
request);
}
/// <summary>
/// The download completed, set the image.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="result">The result.</param>
private void DownloadCompleted(string uri, Stream result)
{
if (result == null)
{
return;
}
var ms = new MemoryStream();
result.CopyTo(ms);
var buffer = ms.ToArray();
var img = new OxyImage(buffer);
this.images[uri] = img;
lock (this.queue)
{
// Clear old items in the queue, new ones will be added when the plot is refreshed
foreach (var queuedUri in this.queue)
{
// Remove the 'reserved' image
this.images.Remove(queuedUri);
}
this.queue.Clear();
}
this.PlotModel.InvalidatePlot(false);
if (this.queue.Count > 0)
{
this.BeginDownload();
}
}
/// <summary>
/// Gets the tile URI.
/// </summary>
/// <param name="x">The tile x.</param>
/// <param name="y">The tile y.</param>
/// <param name="zoom">The zoom.</param>
/// <returns>The uri.</returns>
private string GetTileUri(int x, int y, int zoom)
{
string url = this.Url.Replace("{X}", x.ToString(CultureInfo.InvariantCulture));
url = url.Replace("{Y}", y.ToString(CultureInfo.InvariantCulture));
return url.Replace("{Z}", zoom.ToString(CultureInfo.InvariantCulture));
}
}
}