1 | define([ |
---|
2 | "dojo/_base/declare", |
---|
3 | "dojo/_base/connect", |
---|
4 | "dojo/_base/html", |
---|
5 | "dojo/_base/lang", |
---|
6 | "dojo/_base/event", |
---|
7 | "dojo/_base/window" |
---|
8 | ], function(declare, connect, html, lang, event, win){ |
---|
9 | |
---|
10 | return declare("dojox.geo.openlayers.TouchInteractionSupport", null, { |
---|
11 | // summary: |
---|
12 | // class to handle touch interactions on a OpenLayers.Map widget |
---|
13 | // tags: |
---|
14 | // private |
---|
15 | |
---|
16 | _map: null, |
---|
17 | _centerTouchLocation: null, |
---|
18 | _touchMoveListener: null, |
---|
19 | _touchEndListener: null, |
---|
20 | _initialFingerSpacing: null, |
---|
21 | _initialScale: null, |
---|
22 | _tapCount: null, |
---|
23 | _tapThreshold: null, |
---|
24 | _lastTap: null, |
---|
25 | |
---|
26 | constructor: function(map){ |
---|
27 | // summary: |
---|
28 | // Constructs a new TouchInteractionSupport instance |
---|
29 | // map: OpenLayers.Map |
---|
30 | // the Map widget this class provides touch navigation for. |
---|
31 | this._map = map; |
---|
32 | this._centerTouchLocation = new OpenLayers.LonLat(0, 0); |
---|
33 | |
---|
34 | var div = this._map.div; |
---|
35 | |
---|
36 | // install touch listeners |
---|
37 | connect.connect(div, "touchstart", this, this._touchStartHandler); |
---|
38 | connect.connect(div, "touchmove", this, this._touchMoveHandler); |
---|
39 | connect.connect(div, "touchend", this, this._touchEndHandler); |
---|
40 | |
---|
41 | this._tapCount = 0; |
---|
42 | this._lastTap = { |
---|
43 | x: 0, |
---|
44 | y: 0 |
---|
45 | }; |
---|
46 | this._tapThreshold = 100; // square distance in pixels |
---|
47 | |
---|
48 | }, |
---|
49 | |
---|
50 | _getTouchBarycenter: function(touchEvent){ |
---|
51 | // summary: |
---|
52 | // returns the midpoint of the two first fingers (or the first finger location if only one) |
---|
53 | // touchEvent: TouchEvent |
---|
54 | // a touch event |
---|
55 | // returns: |
---|
56 | // the midpoint as an {x,y} object. |
---|
57 | // tags: |
---|
58 | // private |
---|
59 | var touches = touchEvent.touches; |
---|
60 | var firstTouch = touches[0]; |
---|
61 | var secondTouch = null; |
---|
62 | if(touches.length > 1){ |
---|
63 | secondTouch = touches[1]; |
---|
64 | }else{ |
---|
65 | secondTouch = touches[0]; |
---|
66 | } |
---|
67 | |
---|
68 | var marginBox = html.marginBox(this._map.div); |
---|
69 | |
---|
70 | var middleX = (firstTouch.pageX + secondTouch.pageX) / 2.0 - marginBox.l; |
---|
71 | var middleY = (firstTouch.pageY + secondTouch.pageY) / 2.0 - marginBox.t; |
---|
72 | |
---|
73 | return { |
---|
74 | x: middleX, |
---|
75 | y: middleY |
---|
76 | }; // Object |
---|
77 | |
---|
78 | }, |
---|
79 | |
---|
80 | _getFingerSpacing: function(touchEvent){ |
---|
81 | // summary: |
---|
82 | // computes the distance between the first two fingers |
---|
83 | // touchEvent: Event |
---|
84 | // a touch event |
---|
85 | // returns: float |
---|
86 | // a distance. -1 if less that 2 fingers |
---|
87 | // tags: |
---|
88 | // private |
---|
89 | var touches = touchEvent.touches; |
---|
90 | var spacing = -1; |
---|
91 | if(touches.length >= 2){ |
---|
92 | var dx = (touches[1].pageX - touches[0].pageX); |
---|
93 | var dy = (touches[1].pageY - touches[0].pageY); |
---|
94 | spacing = Math.sqrt(dx * dx + dy * dy); |
---|
95 | } |
---|
96 | return spacing; |
---|
97 | }, |
---|
98 | |
---|
99 | _isDoubleTap: function(touchEvent){ |
---|
100 | // summary: |
---|
101 | // checks whether the specified touchStart event is a double tap |
---|
102 | // (i.e. follows closely a previous touchStart at approximately the same location) |
---|
103 | // touchEvent: TouchEvent |
---|
104 | // a touch event |
---|
105 | // returns: boolean |
---|
106 | // true if this event is considered a double tap |
---|
107 | // tags: |
---|
108 | // private |
---|
109 | var isDoubleTap = false; |
---|
110 | var touches = touchEvent.touches; |
---|
111 | if((this._tapCount > 0) && touches.length == 1){ |
---|
112 | // test distance from last tap |
---|
113 | var dx = (touches[0].pageX - this._lastTap.x); |
---|
114 | var dy = (touches[0].pageY - this._lastTap.y); |
---|
115 | var distance = dx * dx + dy * dy; |
---|
116 | if(distance < this._tapThreshold){ |
---|
117 | isDoubleTap = true; |
---|
118 | }else{ |
---|
119 | this._tapCount = 0; |
---|
120 | } |
---|
121 | } |
---|
122 | this._tapCount++; |
---|
123 | this._lastTap.x = touches[0].pageX; |
---|
124 | this._lastTap.y = touches[0].pageY; |
---|
125 | setTimeout(lang.hitch(this, function(){ |
---|
126 | this._tapCount = 0; |
---|
127 | }), 300); |
---|
128 | |
---|
129 | return isDoubleTap; |
---|
130 | }, |
---|
131 | |
---|
132 | _doubleTapHandler: function(touchEvent){ |
---|
133 | // summary: |
---|
134 | // action performed on the map when a double tap was triggered |
---|
135 | // touchEvent: TouchEvent |
---|
136 | // a touch event |
---|
137 | // tags: |
---|
138 | // private |
---|
139 | |
---|
140 | // perform a basic 2x zoom on touch |
---|
141 | var touches = touchEvent.touches; |
---|
142 | var marginBox = html.marginBox(this._map.div); |
---|
143 | var offX = touches[0].pageX - marginBox.l; |
---|
144 | var offY = touches[0].pageY - marginBox.t; |
---|
145 | // clicked map point before zooming |
---|
146 | var mapPoint = this._map.getLonLatFromPixel(new OpenLayers.Pixel(offX, offY)); |
---|
147 | // zoom increment power |
---|
148 | this._map.setCenter(new OpenLayers.LonLat(mapPoint.lon, mapPoint.lat), this._map.getZoom() + 1); |
---|
149 | }, |
---|
150 | |
---|
151 | _touchStartHandler: function(touchEvent){ |
---|
152 | // summary: |
---|
153 | // action performed on the map when a touch start was triggered |
---|
154 | // touchEvent: Event |
---|
155 | // a touch event |
---|
156 | // tags: |
---|
157 | // private |
---|
158 | event.stop(touchEvent); |
---|
159 | |
---|
160 | // test double tap |
---|
161 | if(this._isDoubleTap(touchEvent)){ |
---|
162 | this._doubleTapHandler(touchEvent); |
---|
163 | return; |
---|
164 | } |
---|
165 | |
---|
166 | // compute map midpoint between fingers |
---|
167 | var middlePoint = this._getTouchBarycenter(touchEvent); |
---|
168 | |
---|
169 | this._centerTouchLocation = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); |
---|
170 | |
---|
171 | // store initial finger spacing to compute zoom later |
---|
172 | this._initialFingerSpacing = this._getFingerSpacing(touchEvent); |
---|
173 | |
---|
174 | // store initial map scale |
---|
175 | this._initialScale = this._map.getScale(); |
---|
176 | |
---|
177 | // install touch move and up listeners (if not done by other fingers before) |
---|
178 | if(!this._touchMoveListener){ |
---|
179 | this._touchMoveListener = connect.connect(win.global, "touchmove", this, this._touchMoveHandler); |
---|
180 | } |
---|
181 | if(!this._touchEndListener){ |
---|
182 | this._touchEndListener = connect.connect(win.global, "touchend", this, this._touchEndHandler); |
---|
183 | } |
---|
184 | }, |
---|
185 | |
---|
186 | _touchEndHandler: function(touchEvent){ |
---|
187 | // summary: |
---|
188 | // action performed on the map when a touch end was triggered |
---|
189 | // touchEvent: Event |
---|
190 | // a touch event |
---|
191 | // tags: |
---|
192 | // private |
---|
193 | event.stop(touchEvent); |
---|
194 | |
---|
195 | var touches = touchEvent.touches; |
---|
196 | |
---|
197 | if(touches.length == 0){ |
---|
198 | // disconnect listeners only when all fingers are up |
---|
199 | if(this._touchMoveListener){ |
---|
200 | connect.disconnect(this._touchMoveListener); |
---|
201 | this._touchMoveListener = null; |
---|
202 | } |
---|
203 | if(this._touchEndListener){ |
---|
204 | connect.disconnect(this._touchEndListener); |
---|
205 | this._touchEndListener = null; |
---|
206 | } |
---|
207 | }else{ |
---|
208 | // recompute touch center |
---|
209 | var middlePoint = this._getTouchBarycenter(touchEvent); |
---|
210 | |
---|
211 | this._centerTouchLocation = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); |
---|
212 | } |
---|
213 | }, |
---|
214 | |
---|
215 | _touchMoveHandler: function(touchEvent){ |
---|
216 | // summary: |
---|
217 | // action performed on the map when a touch move was triggered |
---|
218 | // touchEvent: Event |
---|
219 | // a touch event |
---|
220 | // tags: |
---|
221 | // private |
---|
222 | |
---|
223 | // prevent browser interaction |
---|
224 | event.stop(touchEvent); |
---|
225 | |
---|
226 | var middlePoint = this._getTouchBarycenter(touchEvent); |
---|
227 | |
---|
228 | // compute map offset |
---|
229 | var mapPoint = this._map.getLonLatFromPixel(new OpenLayers.Pixel(middlePoint.x, middlePoint.y)); |
---|
230 | var mapOffsetLon = mapPoint.lon - this._centerTouchLocation.lon; |
---|
231 | var mapOffsetLat = mapPoint.lat - this._centerTouchLocation.lat; |
---|
232 | |
---|
233 | // compute scale factor |
---|
234 | var scaleFactor = 1; |
---|
235 | var touches = touchEvent.touches; |
---|
236 | if(touches.length >= 2){ |
---|
237 | var fingerSpacing = this._getFingerSpacing(touchEvent); |
---|
238 | scaleFactor = fingerSpacing / this._initialFingerSpacing; |
---|
239 | // weird openlayer bug: setting several times the same scale value lead to visual zoom... |
---|
240 | this._map.zoomToScale(this._initialScale / scaleFactor); |
---|
241 | } |
---|
242 | |
---|
243 | // adjust map center on barycenter |
---|
244 | var currentMapCenter = this._map.getCenter(); |
---|
245 | this._map.setCenter(new OpenLayers.LonLat(currentMapCenter.lon - mapOffsetLon, currentMapCenter.lat |
---|
246 | - mapOffsetLat)); |
---|
247 | |
---|
248 | } |
---|
249 | }); |
---|
250 | }); |
---|