Skip to content

Commit

Permalink
add synced map view option
Browse files Browse the repository at this point in the history
  • Loading branch information
danbjoseph committed Apr 24, 2023
1 parent 1e6b2e8 commit 90bf96c
Show file tree
Hide file tree
Showing 3 changed files with 409 additions and 56 deletions.
40 changes: 32 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,39 @@
<link rel="stylesheet" href="./libs/leaflet/leaflet.css" />
<script src="./libs/leaflet/leaflet.js"></script>
<script src="./libs/leaflet-side-by-side.js"></script>
<script src="./libs/leaflet-sync.js"></script>

<link rel="stylesheet" href="./libs/bootstrap.min.css" />

<style>
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#map {
#sideBySideMap {
position: absolute;
top: 54px;
bottom: 0;
width: 100%;
}
#syncMap1 {
position: absolute;
top: 54px;
bottom: 0;
left: 0;
width: 50%;
/* float: left; */
}
#syncMap2 {
position: absolute;
top: 54px;
bottom: 0;
right: 0;
width: 50%;
/* float: right; */
}
#controls {
padding: 8px;
border-bottom: 1px solid black;
Expand All @@ -48,20 +67,23 @@
padding: 2px;
opacity: 0.8;
}
#btnInfo{
#controlBtns{
position: absolute;
top: 60px;
right: 8px;
background: white;
box-shadow: 1px 1px 4px -2px #000;
z-index: 1000000;
}
</style>
</head>
<body>
<button id="btnInfo" type="button" class="btn btn-default btn-sm" data-toggle="modal" data-target="#infoModal">
Info
</button>
<div id="controlBtns">
<button id="btnChangeComparison" type="button" data-comparison="sideBySide" class="btn btn-light btn-sm">
Switch to synced comparison
</button>
<button id="btnInfo" type="button" class="btn btn-light btn-sm" data-toggle="modal" data-target="#infoModal">
Info
</button>
</div>

<div class="modal fade" id="infoModal" tabindex="-1" role="dialog" aria-labelledby="infoModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
Expand Down Expand Up @@ -110,7 +132,9 @@ <h5 class="modal-title" id="infoModalLabel">Data Information</h5>
</div>
</div>
</div>
<div id='map'></div>
<div id="sideBySideMap"></div>
<div id="syncMap1"></div>
<div id="syncMap2"></div>

<script>

Expand Down
272 changes: 272 additions & 0 deletions libs/leaflet-sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
// from https://github.com/jieter/Leaflet.Sync
// retrieved on 2023-04-07

/*
* Extends L.Map to synchronize the interaction on one map to one or more other maps.
*/

(function () {
var NO_ANIMATION = {
animate: false,
reset: true,
disableViewprereset: true
};

L.Sync = function () {};
/*
* Helper function to compute the offset easily.
*
* The arguments are relative positions with respect to reference and target maps of
* the point to sync. If you provide ratioRef=[0, 1], ratioTarget=[1, 0] will sync the
* bottom left corner of the reference map with the top right corner of the target map.
* The values can be less than 0 or greater than 1. It will sync points out of the map.
*/
L.Sync.offsetHelper = function (ratioRef, ratioTarget) {
var or = L.Util.isArray(ratioRef) ? ratioRef : [0.5, 0.5];
var ot = L.Util.isArray(ratioTarget) ? ratioTarget : [0.5, 0.5];
return function (center, zoom, refMap, targetMap) {
var rs = refMap.getSize();
var ts = targetMap.getSize();
var pt = refMap.project(center, zoom)
.subtract([(0.5 - or[0]) * rs.x, (0.5 - or[1]) * rs.y])
.add([(0.5 - ot[0]) * ts.x, (0.5 - ot[1]) * ts.y]);
return refMap.unproject(pt, zoom);
};
};


L.Map.include({
sync: function (map, options) {
this._initSync();
options = L.extend({
noInitialSync: false,
syncCursor: false,
syncCursorMarkerOptions: {
radius: 10,
fillOpacity: 0.3,
color: '#da291c',
fillColor: '#fff'
},
offsetFn: function (center, zoom, refMap, targetMap) {
// no transformation at all
return center;
}
}, options);

// prevent double-syncing the map:
if (this._syncMaps.indexOf(map) === -1) {
this._syncMaps.push(map);
this._syncOffsetFns[L.Util.stamp(map)] = options.offsetFn;
}

if (!options.noInitialSync) {
map.setView(
options.offsetFn(this.getCenter(), this.getZoom(), this, map),
this.getZoom(), NO_ANIMATION);
}
if (options.syncCursor) {
if (typeof map.cursor === 'undefined') {
map.cursor = L.circleMarker([0, 0], options.syncCursorMarkerOptions).addTo(map);
}

this._cursors.push(map.cursor);

this.on('mousemove', this._cursorSyncMove, this);
this.on('mouseout', this._cursorSyncOut, this);
}

// on these events, we should reset the view on every synced map
// dragstart is due to inertia
this.on('resize zoomend', this._selfSetView);
this.on('moveend', this._syncOnMoveend);
this.on('dragend', this._syncOnDragend);
return this;
},


// unsync maps from each other
unsync: function (map) {
var self = this;

if (this._cursors) {
this._cursors.forEach(function (cursor, indx, _cursors) {
if (cursor === map.cursor) {
_cursors.splice(indx, 1);
}
});
}

// TODO: hide cursor in stead of moving to 0, 0
if (map.cursor) {
map.cursor.setLatLng([0, 0]);
}

if (this._syncMaps) {
this._syncMaps.forEach(function (synced, id) {
if (map === synced) {
delete self._syncOffsetFns[L.Util.stamp(map)];
self._syncMaps.splice(id, 1);
}
});
}

if (!this._syncMaps || this._syncMaps.length == 0) {
// no more synced maps, so these events are not needed.
this.off('resize zoomend', this._selfSetView);
this.off('moveend', this._syncOnMoveend);
this.off('dragend', this._syncOnDragend);
}

return this;
},

// Checks if the map is synced with anything or a specifyc map
isSynced: function (otherMap) {
var has = (this.hasOwnProperty('_syncMaps') && Object.keys(this._syncMaps).length > 0);
if (has && otherMap) {
// Look for this specific map
has = false;
this._syncMaps.forEach(function (synced) {
if (otherMap == synced) { has = true; }
});
}
return has;
},


// Callbacks for events...
_cursorSyncMove: function (e) {
this._cursors.forEach(function (cursor) {
cursor.setLatLng(e.latlng);
});
},

_cursorSyncOut: function (e) {
this._cursors.forEach(function (cursor) {
// TODO: hide cursor in stead of moving to 0, 0
cursor.setLatLng([0, 0]);
});
},

_selfSetView: function (e) {
// reset the map, and let setView synchronize the others.
this.setView(this.getCenter(), this.getZoom(), NO_ANIMATION);
},

_syncOnMoveend: function (e) {
if (this._syncDragend) {
// This is 'the moveend' after the dragend.
// Without inertia, it will be right after,
// but when inertia is on, we need this to detect that.
this._syncDragend = false; // before calling setView!
this._selfSetView(e);
this._syncMaps.forEach(function (toSync) {
toSync.fire('moveend');
});
}
},

_syncOnDragend: function (e) {
// It is ugly to have state, but we need it in case of inertia.
this._syncDragend = true;
},


// overload methods on originalMap to replay interactions on _syncMaps;
_initSync: function () {
if (this._syncMaps) {
return;
}
var originalMap = this;

this._syncMaps = [];
this._cursors = [];
this._syncOffsetFns = {};

L.extend(originalMap, {
setView: function (center, zoom, options, sync) {
// Use this sandwich to disable and enable viewprereset
// around setView call
function sandwich (obj, fn) {
var viewpreresets = [];
var doit = options && options.disableViewprereset && obj && obj._events;
if (doit) {
// The event viewpreresets does an invalidateAll,
// that reloads all the tiles.
// That causes an annoying flicker.
viewpreresets = obj._events.viewprereset;
obj._events.viewprereset = [];
}
var ret = fn(obj);
if (doit) {
// restore viewpreresets event to its previous values
obj._events.viewprereset = viewpreresets;
}
return ret;
}

// Looks better if the other maps 'follow' the active one,
// so call this before _syncMaps
var ret = sandwich(this, function (obj) {
return L.Map.prototype.setView.call(obj, center, zoom, options);
});

if (!sync) {
originalMap._syncMaps.forEach(function (toSync) {
sandwich(toSync, function (obj) {
return toSync.setView(
originalMap._syncOffsetFns[L.Util.stamp(toSync)](center, zoom, originalMap, toSync),
zoom, options, true);
});
});
}

return ret;
},

panBy: function (offset, options, sync) {
if (!sync) {
originalMap._syncMaps.forEach(function (toSync) {
toSync.panBy(offset, options, true);
});
}
return L.Map.prototype.panBy.call(this, offset, options);
},

_onResize: function (event, sync) {
if (!sync) {
originalMap._syncMaps.forEach(function (toSync) {
toSync._onResize(event, true);
});
}
return L.Map.prototype._onResize.call(this, event);
},

_stop: function (sync) {
L.Map.prototype._stop.call(this);
if (!sync) {
originalMap._syncMaps.forEach(function (toSync) {
toSync._stop(true);
});
}
}
});

originalMap.dragging._draggable._updatePosition = function () {
L.Draggable.prototype._updatePosition.call(this);
var self = this;
originalMap._syncMaps.forEach(function (toSync) {
L.DomUtil.setPosition(toSync.dragging._draggable._element, self._newPos);
toSync.eachLayer(function (layer) {
if (layer._google !== undefined) {
var offsetFn = originalMap._syncOffsetFns[L.Util.stamp(toSync)];
var center = offsetFn(originalMap.getCenter(), originalMap.getZoom(), originalMap, toSync);
layer._google.setCenter(center);
}
});
toSync.fire('move');
});
};
}
});
})();
Loading

0 comments on commit 90bf96c

Please sign in to comment.