kopia lustrzana https://github.com/dl2alf/AirScout
389 wiersze
14 KiB
C#
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));
|
|
}
|
|
}
|
|
} |