PageSourceSearch

https://vdotplanning.com/epdo/js/L.Map.Sync.js

js vdotplanning.com collected 2026-10-05 05:14:35 UTC 10,366 bytes, 269 lines download raw bytes

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.