1/* 2 * Extends L.Map to synchronize the interaction on one map to one or more other maps. 3 */ 4 5(function () { 6 var NO_ANIMATION = { 7 animate: false, 8 reset: true, 9 disableViewprereset: true 10 }; 11 12 L.Sync = function () {}; 13 /* 14 * Helper function to compute the offset easily. 15 * 16 * The arguments are relative positions with respect to reference and target maps of 17 * the point to sync. If you provide ratioRef=[0, 1], ratioTarget=[1, 0] will sync the 18 * bottom left corner of the reference map with the top right corner of the target map. 19 * The values can be less than 0 or greater than 1. It will sync points out of the map. 20 */ 21 L.Sync.offsetHelper = function (ratioRef, ratioTarget) { 22 var or = L.Util.isArray(ratioRef) ? ratioRef : [0.5, 0.5]; 23 var ot = L.Util.isArray(ratioTarget) ? ratioTarget : [0.5, 0.5]; 24 return function (center, zoom, refMap, targetMap) { 25 var rs = refMap.getSize(); 26 var ts = targetMap.getSize(); 27 var pt = refMap.project(center, zoom) 28 .subtract([(0.5 - or[0]) * rs.x, (0.5 - or[1]) * rs.y]) 29 .add([(0.5 - ot[0]) * ts.x, (0.5 - ot[1]) * ts.y]); 30 return refMap.unproject(pt, zoom); 31 }; 32 }; 33 34 35 L.Map.include({ 36 sync: function (map, options) { 37 this._initSync(); 38 options = L.extend({ 39 noInitialSync: false, 40 syncCursor: false, 41 syncCursorMarkerOptions: { 42 radius: 10, 43 fillOpacity: 0.3, 44 color: '#da291c', 45 fillColor: '#fff' 46 }, 47 offsetFn: function (center, zoom, refMap, targetMap) { 48 // no transformation at all 49 return center; 50 } 51 }, options); 52 53 // prevent double-syncing the map: 54 if (this._syncMaps.indexOf(map) === -1) { 55 this._syncMaps.push(map); 56 this._syncOffsetFns[L.Util.stamp(map)] = options.offsetFn; 57 } 58 59 if (!options.noInitialSync) {
60 map.setView( 61 options.offsetFn(this.getCenter(), this.getZoom(), this, map), 62 this.getZoom(), NO_ANIMATION); 63 } 64 if (options.syncCursor) { 65 if (typeof map.cursor === 'undefined') { 66 map.cursor = L.circleMarker([0, 0], options.syncCursorMarkerOptions).addTo(map); 67 } 68 69 this._cursors.push(map.cursor); 70 71 this.on('mousemove', this._cursorSyncMove, this); 72 this.on('mouseout', this._cursorSyncOut, this); 73 } 74 75 // on these events, we should reset the view on every synced map 76 // dragstart is due to inertia 77 this.on('resize zoomend', this._selfSetView); 78 this.on('moveend', this._syncOnMoveend); 79 this.on('dragend', this._syncOnDragend); 80 return this; 81 }, 82 83 84 // unsync maps from each other 85 unsync: function (map) { 86 var self = this; 87 88 if (this._cursors) { 89 this._cursors.forEach(function (cursor, indx, _cursors) { 90 if (cursor === map.cursor) { 91 _cursors.splice(indx, 1); 92 } 93 }); 94 } 95 96 // TODO: hide cursor in stead of moving to 0, 0 97 if (map.cursor) { 98 map.cursor.setLatLng([0, 0]); 99 } 100 101 if (this._syncMaps) { 102 this._syncMaps.forEach(function (synced, id) { 103 if (map === synced) { 104 delete self._syncOffsetFns[L.Util.stamp(map)]; 105 self._syncMaps.splice(id, 1); 106 } 107 }); 108 } 109 110 if (!this._syncMaps || this._syncMaps.length == 0) { 111 // no more synced maps, so these events are not needed. 112 this.off('resize zoomend', this._selfSetView); 113 this.off('moveend', this._syncOnMoveend); 114 this.off('dragend', this._syncOnDragend); 115 } 116 117 return this; 118 }, 119 120 // Checks if the map is synced with anything or a specifyc map 121 isSynced: function (otherMap) { 122 var has = (this.hasOwnProperty('_syncMaps') && Object.keys(this._syncMaps).length > 0); 123 if (has && otherMap) { 124 // Look for this specific map 125 has = false; 126 this._syncMaps.forEach(function (synced) { 127 if (otherMap == synced) { has = true; } 128 }); 129 } 130 return has; 131 }, 132 133 134 // Callbacks for events... 135 _cursorSyncMove: function (e) { 136 this._cursors.forEach(function (cursor) { 137 cursor.setLatLng(e.latlng); 138 }); 139 }, 140 141 _cursorSyncOut: function (e) { 142 this._cursors.forEach(function (cursor) { 143 // TODO: hide cursor in stead of moving to 0, 0 144 cursor.setLatLng([0, 0]); 145 }); 146 }, 147 148 _selfSetView: function (e) { 149 // reset the map, and let setView synchronize the others. 150 this.setView(this.getCenter(), this.getZoom(), NO_ANIMATION); 151 }, 152 153 _syncOnMoveend: function (e) { 154 if (this._syncDragend) { 155 // This is 'the moveend' after the dragend.
156 // Without inertia, it will be right after, 157 // but when inertia is on, we need this to detect that. 158 this._syncDragend = false; // before calling setView! 159 this._selfSetView(e); 160 this._syncMaps.forEach(function (toSync) { 161 toSync.fire('moveend'); 162 }); 163 } 164 }, 165 166 _syncOnDragend: function (e) { 167 // It is ugly to have state, but we need it in case of inertia. 168 this._syncDragend = true; 169 }, 170 171 172 // overload methods on originalMap to replay interactions on _syncMaps; 173 _initSync: function () { 174 if (this._syncMaps) { 175 return; 176 } 177 var originalMap = this; 178 179 this._syncMaps = []; 180 this._cursors = []; 181 this._syncOffsetFns = {}; 182 183 L.extend(originalMap, { 184 setView: function (center, zoom, options, sync) { 185 // Use this sandwich to disable and enable viewprereset 186 // around setView call 187 function sandwich (obj, fn) { 188 var viewpreresets = []; 189 var doit = options && options.disableViewprereset && obj && obj._events; 190 if (doit) { 191 // The event viewpreresets does an invalidateAll, 192 // that reloads all the tiles. 193 // That causes an annoying flicker. 194 viewpreresets = obj._events.viewprereset; 195 obj._events.viewprereset = []; 196 } 197 var ret = fn(obj); 198 if (doit) { 199 // restore viewpreresets event to its previous values 200 obj._events.viewprereset = viewpreresets; 201 } 202 return ret; 203 } 204 205 // Looks better if the other maps 'follow' the active one, 206 // so call this before _syncMaps 207 var ret = sandwich(this, function (obj) { 208 return L.Map.prototype.setView.call(obj, center, zoom, options); 209 }); 210 211 if (!sync) { 212 originalMap._syncMaps.forEach(function (toSync) { 213 sandwich(toSync, function (obj) { 214 return toSync.setView( 215 originalMap._syncOffsetFns[L.Util.stamp(toSync)](center, zoom, originalMap, toSync), 216 zoom, options, true); 217 }); 218 }); 219 } 220 221 return ret; 222 }, 223 224 panBy: function (offset, options, sync) { 225 if (!sync) { 226 originalMap._syncMaps.forEach(function (toSync) { 227 toSync.panBy(offset, options, true); 228 }); 229 } 230 return L.Map.prototype.panBy.call(this, offset, options); 231 }, 232 233 _onResize: function (event, sync) { 234 if (!sync) { 235 originalMap._syncMaps.forEach(function (toSync) { 236 toSync._onResize(event, true); 237 }); 238 } 239 return L.Map.prototype._onResize.call(this, event); 240 }, 241 242 _stop: function (sync) { 243 L.Map.prototype._stop.call(this); 244 if (!sync) { 245 originalMap._syncMaps.forEach(function (toSync) { 246 toSync._stop(true); 247 }); 248 } 249 } 250 }); 251 252 originalMap.dragging._draggable._updatePosition = function () { 253 L.Draggable.prototype._updatePosition.call(this); 254 var self = this; 255 originalMap._syncMaps.forEach(function (toSync) { 256 L.DomUtil.setPosition(toSync.dragging._draggable._element, self._newPos); 257 toSync.eachLayer(function (layer) { 258 if (layer._google !== undefined) { 259 var offsetFn = originalMap._syncOffsetFns[L.Util.stamp(toSync)]; 260 var center = offsetFn(originalMap.getCenter(), originalMap.getZoom(), originalMap, toSync); 261 layer._google.setCenter(center); 262 } 263 }); 264 toSync.fire('move'); 265 }); 266 }; 267 } 268 }); 269})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.