HeatMapLayer.cs
上传用户:huazai0421
上传日期:2008-05-30
资源大小:405k
文件大小:15k
源码类别:

SilverLight

开发平台:

C#

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Threading;
  5. using System.Windows;
  6. using System.Windows.Media;
  7. using System.Windows.Media.Imaging;
  8. using ESRI.ArcGIS.Client;
  9. using ESRI.ArcGIS.Client.Geometry;
  10. using Silverlight.Samples;
  11. namespace ESRI.ArcGIS.Samples
  12. {
  13. /// <summary>
  14. /// Heat Map layer
  15. /// </summary>
  16. public class HeatMapLayer : DynamicLayer
  17. {
  18. BackgroundWorker renderThread; //background thread used for generating the heat map
  19. ESRI.ArcGIS.Client.Geometry.PointCollection heatMapPoints;
  20. private Envelope fullExtent; //cached value of the calculated full extent
  21. private struct HeatPoint
  22. {
  23. public int X;
  24. public int Y;
  25. }
  26. private struct ThreadSafeGradientStop
  27. {
  28. public double Offset;
  29. public Color Color;
  30. }
  31. bool generateRandomData;
  32. public bool GenerateRandomData
  33. {
  34. get { return generateRandomData; }
  35. set
  36. {
  37. generateRandomData = value;
  38. if (value)
  39. {
  40. //-130,30,-70,50
  41. Random rand = new Random();
  42. for (int i = 0; i < 1000; i++)
  43. {
  44. double x = rand.NextDouble() * 60 - 130;
  45. double y = rand.NextDouble() * 40 + 20;
  46. HeatMapPoints.Add(new ESRI.ArcGIS.Client.Geometry.MapPoint(x, y));
  47. }
  48. }
  49. }
  50. }
  51. /// <summary>
  52. /// Initializes a new instance of the <see cref="HeatMapLayer"/> class.
  53. /// </summary>
  54. public HeatMapLayer()
  55. {
  56. GradientStopCollection stops = new GradientStopCollection();
  57. stops.Add(new GradientStop() { Color = Colors.Transparent, Offset = 0 });
  58. stops.Add(new GradientStop() { Color = Colors.Blue, Offset = .5 });
  59. stops.Add(new GradientStop() { Color = Colors.Red, Offset = .75 });
  60. stops.Add(new GradientStop() { Color = Colors.Yellow, Offset = .8 });
  61. stops.Add(new GradientStop() { Color = Colors.White, Offset = 1 });
  62. Gradient = stops;
  63. HeatMapPoints = new ESRI.ArcGIS.Client.Geometry.PointCollection();
  64. //Create a separate thread for rendering the heatmap layer.
  65. renderThread = new BackgroundWorker() { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
  66. renderThread.ProgressChanged += new ProgressChangedEventHandler(renderThread_ProgressChanged);
  67. renderThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(renderThread_RunWorkerCompleted);
  68. renderThread.DoWork += new DoWorkEventHandler(renderThread_DoWork);
  69. }
  70. /// <summary>
  71. /// The full extent of the layer.
  72. /// </summary>
  73. public override Envelope FullExtent
  74. {
  75. get
  76. {
  77. if (fullExtent == null && heatMapPoints != null && heatMapPoints.Count > 0)
  78. {
  79. fullExtent = new Envelope();
  80. foreach (MapPoint p in heatMapPoints)
  81. {
  82. fullExtent = fullExtent.Union(p.Extent);
  83. }
  84. }
  85. return fullExtent;
  86. }
  87. protected set { throw new NotSupportedException(); }
  88. }
  89. /// <summary>
  90. /// Identifies the <see cref="Interval"/> dependency property.
  91. /// </summary>
  92. public static readonly DependencyProperty IntensityProperty =
  93. DependencyProperty.Register("Intensity", typeof(double), typeof(HeatMapLayer),
  94. new PropertyMetadata(10.0, OnIntensityPropertyChanged));
  95. /// <summary>
  96. /// Gets or sets the interval.
  97. /// </summary>
  98. public double Intensity
  99. {
  100. get { return (double)GetValue(IntensityProperty); }
  101. set { SetValue(IntensityProperty, value); }
  102. }
  103. /// <summary>
  104. /// IntervalProperty property changed handler. 
  105. /// </summary>
  106. /// <param name="d">HeatMapLayer that changed its Interval.</param>
  107. /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
  108. private static void OnIntensityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  109. {
  110. if ((double)e.NewValue < 1)
  111. throw new ArgumentOutOfRangeException("Intensity");
  112. HeatMapLayer dp = d as HeatMapLayer;
  113. dp.OnLayerChanged();
  114. }
  115. /// <summary>
  116. /// Gets or sets the heat map points.
  117. /// </summary>
  118. /// <value>The heat map points.</value>
  119. public ESRI.ArcGIS.Client.Geometry.PointCollection HeatMapPoints
  120. {
  121. get { return heatMapPoints; }
  122. set
  123. {
  124. if (heatMapPoints != null)
  125. heatMapPoints.CollectionChanged -= heatMapPoints_CollectionChanged;
  126. heatMapPoints = value;
  127. if (heatMapPoints != null)
  128. heatMapPoints.CollectionChanged += heatMapPoints_CollectionChanged;
  129. fullExtent = null;
  130. }
  131. }
  132. /// <summary>
  133. /// Handles the CollectionChanged event of the heatMapPoints control.
  134. /// </summary>
  135. /// <param name="sender">The source of the event.</param>
  136. /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
  137. private void heatMapPoints_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
  138. {
  139. fullExtent = null;
  140. OnLayerChanged();
  141. }
  142. /// <summary>
  143. /// Identifies the <see cref="Gradient"/> dependency property.
  144. /// </summary>
  145. public static readonly DependencyProperty GradientProperty =
  146. DependencyProperty.Register("Gradient", typeof(GradientStopCollection), typeof(HeatMapLayer),
  147. new PropertyMetadata(null, OnGradientPropertyChanged));
  148. /// <summary>
  149. /// Gets or sets the heat map gradient.
  150. /// </summary>
  151. public GradientStopCollection Gradient
  152. {
  153. get { return (GradientStopCollection)GetValue(GradientProperty); }
  154. set { SetValue(GradientProperty, value); }
  155. }
  156. /// <summary>
  157. /// GradientProperty property changed handler. 
  158. /// </summary>
  159. /// <param name="d">HeatMapLayer that changed its Gradient.</param>
  160. /// <param name="e">DependencyPropertyChangedEventArgs.</param> 
  161. private static void OnGradientPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  162. {
  163. HeatMapLayer dp = d as HeatMapLayer;
  164. dp.OnLayerChanged();
  165. }
  166. /// <summary>
  167. /// Gets the source image to display in the dynamic layer. Override this to generate
  168. /// or modify images.
  169. /// </summary>
  170. /// <param name="extent">The extent of the image being request.</param>
  171. /// <param name="width">The width of the image being request.</param>
  172. /// <param name="height">The height of the image being request.</param>
  173. /// <param name="onComplete">The method to call when the image is ready.</param>
  174. /// <seealso cref="OnProgress"/>
  175. protected override void GetSource(Envelope extent, int width, int height, DynamicLayer.OnImageComplete onComplete)
  176. {
  177. if (!IsInitialized)
  178. {
  179. onComplete(null, -1, -1, null);
  180. return;
  181. }
  182. if (renderThread != null && renderThread.IsBusy)
  183. {
  184. renderThread.CancelAsync(); //render already running. Cancel current process.
  185. while (renderThread.IsBusy) //wait for thread to cancel
  186. {
  187. Thread.Sleep(10);
  188. }
  189. }
  190. //Accessing a GradientStop collection from a non-UI thread is not allowed,
  191. //so we used a private class gradient collection
  192. List<ThreadSafeGradientStop> stops = new List<ThreadSafeGradientStop>(Gradient.Count);
  193. foreach (GradientStop stop in Gradient)
  194. {
  195. stops.Add(new ThreadSafeGradientStop() { Color = stop.Color, Offset = stop.Offset });
  196. }
  197. //Gradients must be sorted by offset
  198. stops.Sort((ThreadSafeGradientStop g1, ThreadSafeGradientStop g2) => { return g1.Offset.CompareTo(g2.Offset); });
  199. List<HeatPoint> points = new List<HeatPoint>();
  200. double res = extent.Width / width;
  201. //adjust extent to include points slightly outside the view so pan won't affect the outcome
  202. Envelope extent2 = new Envelope(extent.XMin - Intensity * res, extent.YMin - Intensity * res,
  203. extent.XMax + Intensity * res, extent.YMax + Intensity * res);
  204. //get points within the extent and transform them to pixel space
  205. foreach (MapPoint p in HeatMapPoints) 
  206. {
  207. if (p.X >= extent2.XMin && p.Y >= extent2.YMin &&
  208. p.X <= extent2.XMax && p.Y <= extent2.YMax)
  209. {
  210. points.Add(new HeatPoint() { 
  211. X = (int)Math.Round((p.X - extent.XMin) / res), 
  212. Y = (int)Math.Round((extent.YMax - p.Y) / res) 
  213. });
  214. }
  215. }
  216. //Start the render thread
  217. renderThread.RunWorkerAsync(
  218. new object[] { extent, width, height, (int)Math.Round(this.Intensity), stops, points, onComplete });
  219. }
  220. /// <summary>
  221. /// Stops loading of any pending images
  222. /// </summary>
  223. protected override void Cancel()
  224. {
  225. if (renderThread != null && renderThread.IsBusy)
  226. {
  227. renderThread.CancelAsync();
  228. }
  229. base.Cancel();
  230. }
  231. /// <summary>
  232. /// Handles the DoWork event of the renderThread control. This is where we
  233. /// render the heatmap outside the UI thread.
  234. /// </summary>
  235. /// <param name="sender">The source of the event.</param>
  236. /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance 
  237. /// containing the event data.</param>
  238. private void renderThread_DoWork(object sender, DoWorkEventArgs e)
  239. {
  240. BackgroundWorker worker = (BackgroundWorker)sender;
  241. object[] args = (object[])e.Argument;
  242. Envelope extent = (Envelope)args[0];
  243. int width = (int)args[1];
  244. int height = (int)args[2];
  245. int size = (int)args[3];
  246. List<ThreadSafeGradientStop> stops = (List<ThreadSafeGradientStop>)args[4];
  247. List<HeatPoint> points = (List<HeatPoint>)args[5];
  248. OnImageComplete onComplete = (OnImageComplete)args[6];
  249. size = size * 2 + 1;
  250. ushort[] matrix = CreateDistanceMatrix(size);
  251. int[] output = new int[width * height];
  252. foreach (HeatPoint p in points)
  253. {
  254. AddPoint(matrix, size, p.X, p.Y, output, width);
  255. if (worker.CancellationPending)
  256. {
  257. e.Cancel = true;
  258. e.Result = null;
  259. return;
  260. }
  261. }
  262. matrix = null;
  263. int max = 0;
  264. foreach (int val in output) //find max - used for scaling the intensity
  265. if (max < val) max = val;
  266. //If we only have single points in the view, don't show them with too much intensity.
  267. if (max < 2) max = 2; 
  268. PngEncoder ei = new PngEncoder(width, height);
  269. for (int idx = 0; idx < height; idx++)      // Height (y)
  270. {
  271. int rowstart = ei.GetRowStart(idx);
  272. for (int jdx = 0; jdx < width; jdx++)     // Width (x)
  273. {
  274. Color c = InterpolateColor(output[idx * width + jdx] / (float)max, stops);
  275. ei.SetPixelAtRowStart(jdx, rowstart, c.R, c.G, c.B, c.A);
  276. }
  277. if (worker.CancellationPending)
  278. {
  279. e.Cancel = true;
  280. e.Result = null;
  281. output = null;
  282. ei = null;
  283. return;
  284. }
  285. //Raise the progress event for each line rendered
  286. worker.ReportProgress((idx + 1) * 100 / height);
  287. }
  288. stops.Clear();
  289. output = null;
  290. // Get stream and set image source
  291. e.Result = new object[] { ei, width, height, extent, onComplete };
  292. }
  293. /// <summary>
  294. /// Handles the RunWorkerCompleted event of the renderThread control.
  295. /// </summary>
  296. /// <param name="sender">The source of the event.</param>
  297. /// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
  298. private void renderThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
  299. {
  300. if (e.Cancelled || e.Result == null) return;
  301. object[] result = (object[])e.Result;
  302. PngEncoder ei = (PngEncoder)result[0];
  303. int width = (int)result[1];
  304. int height = (int)result[2];
  305. Envelope extent = (Envelope)result[3];
  306. OnImageComplete onComplete = (OnImageComplete)result[4];
  307. BitmapImage image = new BitmapImage();
  308. image.SetSource(ei.GetImageStream());
  309. onComplete(image, width, height, extent);
  310. }
  311. /// <summary>
  312. /// Handles the ProgressChanged event of the renderThread control and fires the layer progress event.
  313. /// </summary>
  314. /// <param name="sender">The source of the event.</param>
  315. /// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/> instance containing the event data.</param>
  316. private void renderThread_ProgressChanged(object sender, ProgressChangedEventArgs e)
  317. {
  318. //Raise the layer progress event
  319. OnProgress(e.ProgressPercentage);
  320. }
  321. /// <summary>
  322. /// Lienarly interpolates a color from a list of colors.
  323. /// </summary>
  324. /// <param name="value">The value relative to the gradient stop offsets.</param>
  325. /// <param name="stops">The color stops sorted by the offset.</param>
  326. /// <returns></returns>
  327. private static Color InterpolateColor(float value, List<ThreadSafeGradientStop> stops)
  328. {
  329. if (value < 1 / 255f)
  330. return Colors.Transparent;
  331. if (stops == null || stops.Count == 0)
  332. return Colors.Black;
  333. if (stops.Count == 1)
  334. return stops[0].Color;
  335. if (stops[0].Offset >= value) //clip to bottom
  336. return stops[0].Color;
  337. else if (stops[stops.Count - 1].Offset <= value) //clip to top
  338. return stops[stops.Count - 1].Color;
  339. int i = 0;
  340. for (i = 1; i < stops.Count; i++)
  341. {
  342. if (stops[i].Offset > value)
  343. {
  344. Color start = stops[i - 1].Color;
  345. Color end = stops[i].Color;
  346. double frac = (value - stops[i - 1].Offset) / (stops[i].Offset - stops[i - 1].Offset);
  347. byte R = (byte)Math.Round((start.R * (1 - frac) + end.R * frac));
  348. byte G = (byte)Math.Round((start.G * (1 - frac) + end.G * frac));
  349. byte B = (byte)Math.Round((start.B * (1 - frac) + end.B * frac));
  350. byte A = (byte)Math.Round((start.A * (1 - frac) + end.A * frac));
  351. return Color.FromArgb(A, R, G, B);
  352. }
  353. }
  354. return stops[stops.Count - 1].Color; //should never happen
  355. }
  356. /// <summary>
  357. /// Adds a heat map point to the intensity matrix.
  358. /// </summary>
  359. /// <param name="distanceMatrix">The distance matrix.</param>
  360. /// <param name="size">The size of the distance matrix.</param>
  361. /// <param name="x">x.</param>
  362. /// <param name="y">y</param>
  363. /// <param name="intensityMap">The intensity map.</param>
  364. /// <param name="width">The width of the intensity map..</param>
  365. private static void AddPoint(ushort[] distanceMatrix, int size, int x, int y, int[] intensityMap, int width)
  366. {
  367. for (int i = 0; i < size * 2 - 1; i++)
  368. {
  369. int start = (y - size + 1 + i) * width + x - size;
  370. for (int j = 0; j < size * 2 - 1; j++)
  371. {
  372. if (j + x - size < 0 || j + x - size >= width) continue;
  373. int idx = start + j;
  374. if (idx < 0 || idx >= intensityMap.Length)
  375. continue;
  376. intensityMap[idx] += distanceMatrix[i * (size * 2 - 1) + j];
  377. }
  378. }
  379. }
  380. /// <summary>
  381. /// Creates the distance matrix.
  382. /// </summary>
  383. /// <param name="size">The size of the matrix (must be and odd number).</param>
  384. /// <returns></returns>
  385. private static ushort[] CreateDistanceMatrix(int size)
  386. {
  387. int width = size * 2 - 1;
  388. ushort[] matrix = new ushort[(int)Math.Pow(width, 2)];
  389. for (int i = 0; i < width; i++)
  390. {
  391. for (int j = 0; j < width; j++)
  392. {
  393. matrix[i * width + j] = (ushort)Math.Max((size - (Math.Sqrt(Math.Pow(i - size + 1, 2) + Math.Pow(j - size + 1, 2)))), 0);
  394. }
  395. }
  396. return matrix;
  397. }
  398. }
  399. }