source: trunk/webmap/html/js/webmap/modules.js @ 15

Last change on this file since 15 was 15, checked in by wcaarls, 11 years ago

Imported webmap at revision 1169

File size: 12.6 KB
Line 
1/// Map modules to control the view using the mouse.
2/**
3 * \param map The map.
4 */
5webmap.modules.map.MouseViewControl = function(map) {
6        this.map        = map;
7        this.hide_click = false;
8       
9        this.mouseDownListener  = this.mouseDown.bind(this);
10        this.mouseUpListener    = this.mouseUp.bind(this);
11        this.mouseMoveListener  = this.mouseMove.bind(this);
12        this.mouseWheelListener = this.mouseWheel.bind(this);
13        this.mouseClickListener = this.mouseClick.bind(this);
14       
15        this.map.root.addEventListener("mousedown",      this.mouseDownListener,  false);
16        this.map.root.addEventListener("mouseup",        this.mouseUpListener,    false);
17        this.map.root.addEventListener("mouseleave",     this.mouseUpListener,    false);
18        this.map.root.addEventListener("wheel",          this.mouseWheelListener, false);
19        this.map.root.addEventListener("mousewheel",     this.mouseWheelListener, false);
20        this.map.root.addEventListener("DOMMouseScroll", this.mouseWheelListener, false);
21        this.map.root.addEventListener("click",          this.mouseClickListener, true);
22}
23
24/// Destroy the module and clean up.
25webmap.modules.map.MouseViewControl.prototype.destroy = function() {
26        this.map.root.removeEventListener("mousedown",      this.mouseDownListener,  false);
27        this.map.root.removeEventListener("mouseup",        this.mouseUpListener,    false);
28        this.map.root.removeEventListener("mousemove",      this.mouseMoveListener,  false);
29        this.map.root.removeEventListener("mouseleave",     this.mouseUpListener,    false);
30        this.map.root.removeEventListener("wheel",          this.mouseWheelListener, false);
31        this.map.root.removeEventListener("mousewheel",     this.mousewheelListener, false);
32        this.map.root.removeEventListener("DOMMouseScroll", this.mousewheelListener, false);
33        this.map.root.removeEventListener("click",          this.mouseClickListener, true);
34}
35
36/// Handle mouse down events.
37webmap.modules.map.MouseViewControl.prototype.mouseDown = function(event) {
38        // Normalize the event and stop the default action.
39        event = event || window.event;
40        event.preventDefault();
41       
42        // Remember the mouse coordinates.
43        this.mouse_x     = event.clientX;
44        this.mouse_y     = event.clientY;
45        this.mouse_moved = false;
46        this.map.root.addEventListener("mousemove",  this.mouseMoveListener, false);
47}
48
49/// Handle mouse move events.
50webmap.modules.map.MouseViewControl.prototype.mouseMove = function(event) {
51        // Skip the event if the mouse wasn't pressed on the map.
52        if (this.mouse_x === undefined) return;
53       
54        // Normalize the event and prevent default actions.
55        event = event || window.event;
56        event.preventDefault();
57       
58        // Scroll and remember the mouse coordinates.
59        var diff_x = (event.clientX - this.mouse_x);
60        var diff_y = (event.clientY - this.mouse_y);
61        this.map.translate(diff_x, diff_y);
62        this.mouse_x     = event.clientX;
63        this.mouse_y     = event.clientY;
64        this.mouse_moved = true;
65}
66
67/// Capture click events and shove them under the carpet.
68webmap.modules.map.MouseViewControl.prototype.mouseClick = function(event) {
69        if (this.hide_click) {
70                event = event || window.event;
71                event.preventDefault();
72                event.stopPropagation();
73                this.hide_click = false;
74        }
75}
76
77/// Handle mouse up events.
78webmap.modules.map.MouseViewControl.prototype.mouseUp = function(event) {
79        // Skip the event if the mouse wasn't pressed on the map.
80        if (this.mouse_x === undefined) return;
81       
82        // Normalize the event and prevent default actions.
83        event = event || window.event;
84        event.preventDefault();
85       
86        // Scroll and clear the mouse coordinates.
87        var diff_x = (event.clientX - this.mouse_x);
88        var diff_y = (event.clientY - this.mouse_y);
89       
90        // If the mouse has moved since going down, it's not a click but a drag.
91        this.hide_click = diff_x || diff_y || this.mouse_moved;
92       
93        this.map.translate(diff_x, diff_y);
94        this.mouse_x     = undefined;
95        this.mouse_y     = undefined;
96        this.mouse_moved = undefined;
97        this.map.root.removeEventListener("mousemove", this.mouseMoveListener, false);
98       
99}
100
101/// Handle mouse scroll events.
102webmap.modules.map.MouseViewControl.prototype.mouseWheel = function(event) {
103        event = event || window.event;
104        event.preventDefault();
105       
106        // Get the X and Y coordinates of the mouse within the SVG viewport.
107        var bounds = this.map.getBoundingRect();
108        var x = event.clientX - bounds.left;
109        var y = event.clientY - bounds.top;
110       
111        // Determine (or estimate) the amount of lines scrolled.
112        var lines = 0;
113       
114        // Modern wheel events.
115        if (event.deltaMode !== undefined && event.deltaY !== undefined) {
116                switch (event.deltaMode) {
117                        case 0x00: lines = event.deltaY / 14; break; // About 14 pixels per line?
118                        case 0x01: lines = event.deltaY;      break; // About exactly 1 line per line.
119                        case 0x02: lines = event.deltaY * 10; break; // Lets just call a page 10 lines.
120                }
121        // Old Gecko DOMMouseScroll events.
122        } else if (event.detail) {
123                switch (event.detail) {
124                        case  32768: lines =  10;          break; // Lets just call a page 10 lines.
125                        case -32768: lines = -10;          break; // Lets just call a page 10 lines.
126                        default:     lines = event.detail; break; // About exactly 1 line per line.
127                }
128        // Old mousewheel events.
129        } else if (event.wheelDelta) {
130                lines = event.wheelDelta / -40.0;
131        }
132       
133        // Handle the scroll.
134        if (event.shiftKey) {
135                this.map.rotate(5.0 * lines, this.map.canvas_width * 0.5, this.map.canvas_height * 0.5); // Rotate -5 degrees per line.
136        } else {
137                this.map.scale(Math.pow(1.15, -lines / 3.0), x, y); // Zoom 15% per 3 lines.
138        }
139}
140
141
142/// Map modules to show the name of the currently selected robot in a HTML element.
143/**
144 * The module works by replacing the text content of the given HTML element.
145 *
146 * \param map         The map.
147 * \param element     The HTML element to display the name in.
148 * \param default_msg The message to show when there is no selected robot.
149 */
150webmap.modules.map.ShowSelected = function(map, element, default_msg) {
151        if (default_msg === undefined || default_msg === null) default_msg = "No selection";
152       
153        this.map                 = map;
154        this.default_msg         = default_msg;
155        this.element             = element;
156        this.onSelect();
157}
158
159/// Destroy the module and clean up.
160webmap.modules.map.ShowSelected.prototype.destroy = function() {
161        this.element.textContent = "";
162}
163
164/// Handle selection changes by updating the text content of the DOM element.
165webmap.modules.map.ShowSelected.prototype.onSelect = function() {
166        this.element.textContent =  this.map.selected ? this.map.selected.name : this.default_msg;
167}
168
169
170/// Module to allow keyboard control of robots.
171/**
172 * \param map            The map.
173 * \param speed          The forward speed.
174 * \param rotation_speed The rotational speed in rad/s.
175 */
176webmap.modules.map.Teleop = function(map, speed, rotation_speed) {
177        this.map   = map;
178        this.speed = speed;
179        this.rotation_speed = rotation_speed;
180       
181        // Initial keyboard state.
182        this.forward  = false;
183        this.backward = false;
184        this.left     = false;
185        this.right    = false;
186       
187        // Timeout ID so we can cancel it later.
188        this.timeout  = null;
189       
190        // Set up event listeners.
191        this.keydown_listener = this.keyEvent.bind(this, true);
192        this.keyup_listener   = this.keyEvent.bind(this, false);
193        this.blur_listener    = this.blur.bind(this);
194        document.body.addEventListener("keydown", this.keydown_listener, false);
195        document.body.addEventListener("keyup",   this.keyup_listener,   false);
196        document.addEventListener("blur", this.blur_listener, false);
197}
198
199/// Destroy the module and clean up.
200webmap.modules.map.Teleop.prototype.destroy = function() {
201        document.body.removeEventListener("keydown", this.keydown_listener, false);
202        document.body.removeEventListener("keyup",   this.keyup_listener,   false);
203        document.removeEventListener("blur", this.blur_listener, false);
204}
205
206/// Send the twist message based on the state of the keyboard.
207webmap.modules.map.Teleop.prototype.actuate = function() {
208        if (this.map && this.map.selected) {
209                var robot = this.map.selected;
210                var x = 0;
211                var rz = 0;
212                if (this.forward)  x  += this.speed;
213                if (this.backward) x  -= this.speed;
214                if (this.left)     rz += this.rotation_speed;
215                if (this.right)    rz -= this.rotation_speed;
216                if (x < 0) rz *= -1;
217                this.map.selected.twist(x, 0, 0, 0, 0, rz);
218                this.timeout = window.setTimeout(this.actuate.bind(this), 100);
219        }
220}
221
222/// When the document loses focus, make sure we forget all pressed keys.
223webmap.modules.map.Teleop.prototype.blur = function() {
224        // Reset keyboard state.
225        this.forward  = false;
226        this.backward = false;
227        this.left     = false;
228        this.right    = false;
229       
230        // Cancel any running timeout.
231        if (this.timeout !== null) {
232                window.clearTimeout(this.timeout);
233                this.timeout = null;
234        }
235}
236
237/// Handle key events on the map.
238/**
239 * \param down If true, the key has been pressed, otherwise it has been unpressed.
240 * \param event The event that triggered the listener.
241 */
242webmap.modules.map.Teleop.prototype.keyEvent = function(down, event) {
243        event = event || window.event;
244       
245        // Remember the state of interesting keys.
246        var interested = false;
247        switch (event.keyCode) {
248                case 87: // W (87) means forwards.
249                        interested    = true;
250                        this.forward  = down;
251                        break;
252                case 83: // S (83) means backwards.
253                        interested    = true;
254                        this.backward = down;
255                        break;
256                case 65: // A (65) means left.
257                        interested    = true;
258                        this.left     = down;
259                        break;
260                case 68: // D (68) means right.
261                        interested    = true;
262                        this.right    = down;
263                        break;
264        }
265       
266        // Start or stop the timeouts.
267        if (this.forward || this.backward || this.left || this.right) {
268                if (this.timeout === null) this.timeout = window.setTimeout(this.actuate.bind(this), 100);
269        } else if (this.timeout !== null) {
270                window.clearTimeout(this.timeout);
271                this.timeout = null;
272        }
273       
274        if (interested) event.preventDefault();
275}
276
277/*//////////////////
278// Robot modules
279// This section of the file contains modules for robots.
280//////////////////*/
281
282/// Create a new laser scanner sensor module.
283/**
284 * \param robot      The robot.
285 * \param connection The ROS connection.
286 * \param topic      The name of the LaserScan topic.
287 * \param x          The X offset relative from robot origin.
288 * \param y          The Y offset relative from the robot origin.
289 * \param angle      The rotation of the sensor in degrees.
290 * \param max_dots   (Optional) Maximum number of dots to show. Defaults to 50.
291 * \param throttle   (Optional) The minimum time in milliseconds between receiving two updates. Defaults to 300.
292 *
293 * An angle of 0 degrees means the sensor is looking along the positive X axis of the robot.
294 */
295webmap.modules.robot.LaserScanner = function(robot, connection, topic, x, y, angle, max_dots, throttle) {
296        if (max_dots === undefined || max_dots === null) max_dots = 50;
297        if (throttle === undefined || throttle === null) throttle = 300;
298       
299        /// Locally unique ID.
300        this.id = webmap.generateId();
301       
302        this.robot        = robot;
303        this.connection   = connection;
304        this.topic        = topic;
305        this.svg          = document.createElementNS(webmap.svgns, "g");
306        this.svg.setAttribute("class", "laserscan");
307        this.dots         = [];
308       
309        this.x = x;
310        this.y = y;
311        this.angle = angle;
312       
313        // Create the dots to visualize the readings.
314        for (var i = 0; i < max_dots; ++i) {
315                var dot = document.createElementNS(webmap.svgns, "circle");
316                dot.setAttribute("cx",    0);
317                dot.setAttribute("cy",    0);
318                dot.setAttribute("r",     0.05);
319                dot.setAttribute("class", "dot");
320                dot.style.display = "none";
321                dot = this.svg.appendChild(dot);
322                this.dots.push(dot);
323        }
324       
325        // Add the SVG.
326        this.robot.svg.appendChild(this.svg);
327       
328        // Subsribe to the data topic.
329        this.connection.subscribe(this.handleData.bind(this), this.topic, null, throttle, null, null, null, this.id);
330}
331
332/// Destroy the module and clean up.
333webmap.modules.robot.LaserScanner.prototype.destroy = function() {
334        this.connection.unsubscribe(this.topic, this.id);
335        this.svg.parentNode.removeChild(this.svg);
336}
337
338/// Handle sensor data by updating the drawing.
339webmap.modules.robot.LaserScanner.prototype.handleData = function(msg) {
340        // Add a dot for all valid readings.
341        var count = Math.min(this.dots.length, msg.ranges.length);
342       
343        for (var i = 0; i < this.dots.length; ++i) {
344                // If a dot has no reading, make it invisible.
345                if (i >= count) {
346                        this.dots[i].style.display = "none";
347                        continue;
348                }
349               
350                var index = Math.round(i * msg.ranges.length / count);
351                var angle = ((msg.angle_min + index * msg.angle_increment) / Math.PI * 180 + 360) % 360;
352                var range = msg.ranges[index];
353               
354                // Don't render invalid readings.
355                if (range < msg.range_min || range > msg.range_max) {
356                        this.dots[i].style.display = "none";
357                        continue;
358                }
359               
360                // Place the dot.
361                var transform = webmap.svg.createSVGMatrix().rotate(angle).translate(range, 0);
362                this.dots[i].transform.baseVal.initialize(this.dots[i].transform.baseVal.createSVGTransformFromMatrix(transform));
363                this.dots[i].style.display = "inline";
364        }
365}
Note: See TracBrowser for help on using the repository browser.