source: src/openlayers_heatmap/script/heatmap.js@ 9750

Last change on this file since 9750 was 8966, checked in by dennisw, 14 years ago

openlayers_heatmap - toegevoegd, voorbeeld met standaard data

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision
  • Property svn:mime-type set to text/plain
File size: 10.0 KB
Line 
1/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
4
5/**
6 * Small class to create a heat or density map for points
7 * on a canvas element.
8 *
9 * Usage:
10 *
11 * var heatMap = new HeatMap(500, 500);
12 * heatMap.addPoint(10, 10);
13 * heatMap.addPoint(15, 15);
14 * ...
15 * var canvas = document.getElementById("canvas");
16 * heatMap.create(canvas);
17 *
18 * The generation of the heat map can also be run in a web worker using
19 * the method 'createAsync(..)' (Chrome 6+ only).
20 *
21 *
22 * @param {int} width - Canvas width
23 * @param {int} height - Canvas height
24 * @param {Array} colorSchema - Optional - a coloring schema
25 */
26var HeatMap = function(width, height, colorSchema) {
27 this.pointSize = 30;
28
29 this.width = width;
30 this.height = height;
31
32 this.points = [];
33
34 this.colorMap = null;
35
36 if (colorSchema === undefined) {
37 this.colorSchema = HeatMap.DEFAULT_SCHEMA;
38 } else {
39 this.colorSchema = colorSchema;
40 }
41
42 /**
43 * Adds a point.
44 *
45 * @param {int} x
46 * @param {int} y
47 */
48 this.addPoint = function(x, y) {
49 this.points.push({x: x, y: y});
50 };
51
52 /**
53 * Creates the heat map with the previously added points.
54 *
55 * @param {Canvas} canvas - Optional
56 * @return {Canvas} The canvas on which the heat map is drawn
57 */
58 this.create = function(canvas) {
59 var context = {
60 canvas: canvas,
61 canvasContext: null
62 };
63
64 // create the intensity map
65 this.prepareForColoring(context);
66
67 // colorize the intensity map
68 context.imageData = this.getImageData(context);
69 HeatMap.colorizeIntensityMask(context.imageData, context.colorMap);
70
71 // write the manipulated image data back to the canvas
72 this.writeImageDataToCanvas(context);
73
74 return this.canvas;
75 };
76
77 /**
78 * Creates the heat map, but the coloring is run in a web worker.
79 *
80 * @param {Canvas} canvas - Optional
81 * @param {Function} callbackDone - Called when the heat map generation is finished. The
82 * function receives a single argument, the drawn canvas.
83 * @param {Function} callbackStatus - Optional, called for progress updates
84 * @param {Function} callbackError - Optional, called in case of an error
85 * @param {String} webworkerPath - Optional, the path to the web worker script
86 * (default: 'heatmap-webworker.js')
87 * @param {Intenger} numberOfWebWorkers - Optional, the number of web workers,
88 * on which the task will be split.
89 */
90 this.createAsync = function(canvas, callbackDone, callbackStatus, callbackError,
91 webworkerPath, numberOfWebWorkers) {
92 if (webworkerPath === undefined) {
93 webworkerPath = "heatmap-webworker.js";
94 }
95 if (numberOfWebWorkers === undefined) {
96 numberOfWebWorkers = 4;
97 }
98
99 var context = {
100 canvas: canvas,
101 canvasContext: null,
102 imageData: null,
103 colorMap: null,
104 heatMap: this
105 };
106
107 // create the intensity map
108 this.prepareForColoring(context);
109
110 // this method will be called when all web workers
111 // completed, successful or not
112 var callbackFinished = function(event) {
113 if (!event.error) {
114 // coloring is finished, execute the callback function
115 callbackDone(context.canvas);
116 } else {
117 // one of the web workers reported an error, redirect this error
118 if (callbackError) {
119 callbackError(event.error);
120 }
121 }
122 };
123
124 var barrier = new CanvasBarrier(
125 numberOfWebWorkers,
126 context.canvasContext,
127 webworkerPath,
128 callbackFinished,
129 callbackStatus,
130 {
131 colorMap: context.colorMap
132 });
133
134 barrier.start();
135 };
136
137 /**
138 * Creates the canvas (if necessary), draws the grey-scale
139 * intensity map and creates a color map.
140 *
141 * @param {Object} context - Optional
142 */
143 this.prepareForColoring = function(context) {
144 this.setupCanvas(context);
145
146 this.createIntensityMask(context);
147 context.colorMap = this.getColorMap();
148 };
149
150 /**
151 * If no canvas is given, a new canvas element is created. Then
152 * the size is set.
153 *
154 * @param {Canvas} canvas
155 */
156 this.setupCanvas = function(context) {
157 if (context.canvas === undefined || context.canvas === null) {
158 context.canvas = document.createElement("canvas");
159 }
160 context.canvasContext = context.canvas.getContext('2d');
161
162 context.canvas.width = this.width;
163 context.canvas.height = this.height;
164 };
165
166 /**
167 * Creates a grey-scale intensity map.
168 *
169 * Every point is drawn on the canvas as circle with a radial gradient
170 * whereas the gradient varies in the alpha value.
171 */
172 this.createIntensityMask = function(context) {
173 // see: https://developer.mozilla.org/en/Canvas_tutorial/Compositing
174 // this.canvasContext.globalCompositeOperation = 'darker'; // ?
175
176 for (var i = 0; i < this.points.length; i++) {
177 var x = this.points[i].x;
178 var y = this.points[i].y;
179
180 var radialGradient = context.canvasContext.createRadialGradient(x, y, 0, x, y, this.pointSize);
181 radialGradient.addColorStop(0, 'rgba(10, 10, 10, 255)');
182 radialGradient.addColorStop(1, 'rgba(10, 10, 10, 0)');
183
184 context.canvasContext.fillStyle = radialGradient;
185 context.canvasContext.fillRect(0, 0, width, height);
186 }
187 };
188
189 this.getImageData = function(context) {
190 return context.canvasContext.getImageData(0, 0, this.width, this.height);
191 };
192
193 this.writeImageDataToCanvas = function(context) {
194 context.canvasContext.putImageData(context.imageData, 0, 0);
195 };
196
197 /**
198 * Create the color map by drawing a linear gradient
199 * on a 256x1 canvas using the color schema.
200 *
201 * @return {ImageData}
202 */
203 this.getColorMap = function() {
204 if (this.colorMap !== null) {
205 return this.colorMap;
206 }
207
208 var colorSchemaCanvas = document.createElement("canvas");
209 var ctx = colorSchemaCanvas.getContext('2d');
210
211 colorSchemaCanvas.width = 256;
212 colorSchemaCanvas.height = 1;
213
214 var linearGradient = ctx.createLinearGradient(0, 0.5 , 256, 0.5);
215
216 for (var i = 0; i < this.colorSchema.length; i++) {
217 var step = this.colorSchema[i][0];
218 var color = this.colorSchema[i][1];
219
220 linearGradient.addColorStop(step, color);
221 }
222
223 ctx.fillStyle = linearGradient;
224 ctx.fillRect(0, 0 , 256, 1);
225
226 this.colorMap = ctx.getImageData(0, 0, 256, 1);
227 return this.colorMap;
228 };
229};
230
231/**
232 * Colorizes the grey-scale intensity map. For each pixel
233 * of the canvas a color is determined by its alpha value
234 * using the color map.
235 *
236 * @param {ImageData} imageData - The grey-scale density map
237 * @param {ImageData} colorMap - The color scheme to use
238 * @param {Function} callbackProgress - Called when the progress changes
239 */
240HeatMap.colorizeIntensityMask = function(imageData, colorMap, callbackProgress) {
241 if (callbackProgress) {
242 var numPixels = imageData.width * imageData.height;
243 var lastProgress = 0;
244 }
245
246 for (var y = 0; y < imageData.height; y++) {
247 for (var x = 0; x < imageData.width; x++) {
248 if (callbackProgress) {
249 // report progress
250 var progress = Math.round((((y * imageData.width) + x) / numPixels) * 100);
251
252 if (progress > lastProgress) {
253 // only report if the progress changed
254 lastProgress = progress;
255 callbackProgress({progress: progress})
256 }
257 }
258
259 var alpha = HeatMap.getPixelValue(imageData, x, y, 3);
260
261 var r = 255;
262 var g = 255;
263 var b = 255;
264 var a = 0;
265
266 if (alpha !== 0) {
267 // only the change the pixel's color if it is not transparent
268 var r = HeatMap.getPixelValue(colorMap, alpha, 0, 0);
269 var g = HeatMap.getPixelValue(colorMap, alpha, 0, 1);
270 var b = HeatMap.getPixelValue(colorMap, alpha, 0, 2);
271 var a = 255;
272 }
273
274 HeatMap.setPixelValue(imageData, x, y, r, 0);
275 HeatMap.setPixelValue(imageData, x, y, g, 1);
276 HeatMap.setPixelValue(imageData, x, y, b, 2);
277 HeatMap.setPixelValue(imageData, x, y, a, 3);
278 }
279 }
280};
281
282HeatMap.getPixelValue = function(imageData, x, y, argb) {
283 return imageData.data[((y*(imageData.width*4)) + (x*4)) + argb];
284};
285
286HeatMap.setPixelValue = function(imageData, x, y, value, argb) {
287 imageData.data[((y*(imageData.width*4)) + (x*4)) + argb] = value;
288};
289
290/**
291 * Color schema borrowed from gheat (http://code.google.com/p/gheat/)
292 */
293HeatMap.DEFAULT_SCHEMA = [
294 [0, 'rgba(255, 255, 255, 0)'],
295 [0.05, '#35343d'],
296 [0.15, '#050555'],
297 [0.3, '#00eaf2'],
298 [0.45, '#00b441'],
299 [0.6, '#dcfc05'],
300 [0.8, '#ff0101'],
301 [1, '#ffeded']
302];
Note: See TracBrowser for help on using the repository browser.