OpenLayers 6 中實現(xiàn)“上一視圖”、“下一視圖”的視圖切換功能,類似arcgis for js的Navigation

需求:

需要用OpenLayers實現(xiàn)視圖切換,即在某一視圖下,可以保存當前的視圖狀態(tài),經(jīng)過平移/縮放等改變視圖的操作之后,通過點擊按鈕等交互動作可以回到上一視圖,并且能夠按順序在保存的視圖之間按順序逐個切換。

分析:

核心部分是view狀態(tài)的處理,實際只需要保存對應(yīng)視圖的extent即可。
視圖的切換可以通過view.fit(extent)實現(xiàn)。
難點在視圖狀態(tài)的保留與切換,需要維護一個隊列,并實現(xiàn)交互的邏輯。

實現(xiàn):

難點的交互部分用了一個現(xiàn)成的輪子:ol-ext

ol-ext提供了一個擴展的interaction類:ol.interaction.UndoRedo,它維護了一個狀態(tài)隊列和位置指針,可以通過API實現(xiàn)對隊列中的狀態(tài)進行訪問,并通過自定義的undo、redo函數(shù)實現(xiàn)操作的撤銷跟回滾。

另外我還用了一個ol-ext中的按鈕控件,用作界面交互操作(用法略)。

UndoRedo組件跟ol原生的其他組件用法類似:

	var undoInteraction = new ol.interaction.UndoRedo();
		map.addInteraction(undoInteraction);

然后向地圖上添加按鈕控件:

    var bar = new ol.control.Bar({
    			group: true,
    			controls: [
    				new ol.control.Button({
    					html: '<i class="fa fa-undo" ></i>',
    					title: '上一視圖',
    					handleClick: function () {
    						undoInteraction.undo();
    					}
    				}),
    				new ol.control.Button({
    					html: '<i class="fa fa-repeat" ></i>',
    					title: '下一視圖',
    					handleClick: function () {
    						undoInteraction.redo();
    					}
    				}),
    				new ol.control.Button({
    					html: '<i class="fa fa-save" ></i>',
    					title: '保存當前視圖',
    					handleClick: function () {
    						saveExtent();
    					}
    				}),
    			]
    		});
    		mainbar.addControl(bar);

undoInteraction.undo()和 undoInteraction.redo()就是UndoRedo組件實現(xiàn)“上一步”“下一步”的API。按鈕控件就很簡單了,略過不談。

接下來核心部分是邏輯處理:

    var extent = map.getView().calculateExtent(map.getSize());
    		undoInteraction.define(
    			'extent',
    			function (s) {
    				extent = s.before;
    				map.getView().fit(extent, {
    					nearest: true,
    					duration: 1000,
    					easing: ol.easing.easeOut
    				});
    			},
    			function (s) {
    				extent = s.after;
    				map.getView().fit(extent, {
    					nearest: true,
    					duration: 1000,
    					easing: ol.easing.easeOut
    				});
    			}
    		);
     
    		function saveExtent() {
    			var before = extent.slice();
    			extent = map.getView().calculateExtent(map.getSize())
    			// use blockStart / blockEnd to stack many undo in a same action
    			// undoInteraction.blockStart();
    			// Add undo style action
    			if(!ol.extent.equals(before, extent))
    			undoInteraction.push("extent", {
    				before: before,
    				after: extent
    			});
    			// undoInteraction.blockEnd();
    		}
    		saveExtent();

ol.interaction.UndoRedo的“驅(qū)動”部分是這樣一個結(jié)構(gòu):

ol.interaction.UndoRedo.define("key", function undo(s), function redo(s))

需要保存狀態(tài)的時候,要調(diào)用:

ol.interaction.UndoRedo.push("key",{

before:<beforeStatus>

after:<afterStatus>

})

傳入undo和redo的參數(shù)s是一個狀態(tài)指針,包含了相對當前狀態(tài)的前一狀態(tài)和后一狀態(tài);

保存當前狀態(tài)的saveExtent()中,要做的事情是將變化前的extent保存給before,當前狀態(tài)保存為after。

邏輯還是比較簡單的。
全部代碼:

    <!DOCTYPE html>
    <html>
    <head>
    	<title>ol-ext: Undo/redo</title>
    	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    	<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
    	<link rel="stylesheet"
    		>
    	<link rel="stylesheet"  />
    	<script type="text/javascript" src="https://openlayers.org/en/latest/build/ol.js"></script>
    	<link rel="stylesheet" href="./js/dist/ol-ext.css" />
    	<script type="text/javascript" src="./js/dist/ol-ext.js"></script>
    	<style>
    		html,
    		body {
    			margin: 0;
    			height: 100%;
    		}
     
    		#map {
    			position: absolute;
    			top: 0;
    			bottom: 0;
    			width: 100%;
    		}
    	</style>
     
    </head>
     
    <body>
    	<div id="map" class='map'></div>
    	<script type="text/javascript">
    		var map = new ol.Map({
    			target: 'map',
    			view: new ol.View({
    				zoom: 6,
    				center: [12956993.538677, 4854668.540448]
    			}),
    			layers: [
    				new ol.layer.Tile({
    					source: new ol.source.OSM()
    				})
    			]
    		});
    		var mainbar = new ol.control.Bar();
    		map.addControl(mainbar);
    		var undoInteraction = new ol.interaction.UndoRedo();
    		map.addInteraction(undoInteraction);
    		var bar = new ol.control.Bar({
    			group: true,
    			controls: [
    				new ol.control.Button({
    					html: '<i class="fa fa-undo" ></i>',
    					title: '上一視圖',
    					handleClick: function () {
    						undoInteraction.undo();
    					}
    				}),
    				new ol.control.Button({
    					html: '<i class="fa fa-repeat" ></i>',
    					title: '下一視圖',
    					handleClick: function () {
    						undoInteraction.redo();
    					}
    				}),
    				new ol.control.Button({
    					html: '<i class="fa fa-save" ></i>',
    					title: '保存當前視圖',
    					handleClick: function () {
    						saveExtent();
    					}
    				}),
    			]
    		});
    		mainbar.addControl(bar);
    		var extent = map.getView().calculateExtent(map.getSize());
    		undoInteraction.define(
    			'extent',
    			function (s) {
    				extent = s.before;
    				map.getView().fit(extent, {
    					nearest: true,
    					duration: 1000,
    					easing: ol.easing.easeOut
    				});
    			},
    			function (s) {
    				extent = s.after;
    				map.getView().fit(extent, {
    					nearest: true,
    					duration: 1000,
    					easing: ol.easing.easeOut
    				});
    			}
    		);
     
    		function saveExtent() {
    			var before = extent.slice();
    			extent = map.getView().calculateExtent(map.getSize())
    			// use blockStart / blockEnd to stack many undo in a same action
    			// undoInteraction.blockStart();
    			// Add undo style action
    			if(!ol.extent.equals(before, extent))
    			undoInteraction.push("extent", {
    				before: before,
    				after: extent
    			});
    			// undoInteraction.blockEnd();
    		}
    		saveExtent();
    	</script>
    </body>
     
    </html>