Skip to main content

视图切换功能

需求:

需要用OpenLayers实现视图切换,即在某一视图下,可以保存当前的视图状态,经过平移/缩放等改变视图的操作之后,通过点击按钮等交互动作可以回到上一视图,并且能够按顺序在保存的视图之间按顺序逐个切换。

分析:
核心部分是view状态的处理,实际只需要保存对应视图的extent即可。
视图的切换可以通过view.fit(extent)实现。
难点在视图状态的保留与切换,需要维护一个队列,并实现交互的逻辑。

实现:
难点的交互部分用了一个现成的轮子:ol-ext

ol-ext提供了一个扩展的interaction类:ol.interaction.UndoRedo,它维护了一个状态队列和位置指针,可以通过API实现对队列中的状态进行访问,并通过自定义的undo、redo函数实现操作的撤销跟回滚。

另外我还用了一个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组件实现“上一步”“下一步”的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的“驱动”部分是这样一个结构:

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

需要保存状态的时候,要调用:

ol.interaction.UndoRedo.push("key",{
before:<beforeStatus>
after:<afterStatus>
})

传入undo和redo的参数s是一个状态指针,包含了相对当前状态的前一状态和后一状态;

保存当前状态的saveExtent()中,要做的事情是将变化前的extent保存给before,当前状态保存为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"
    href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
  <link rel="stylesheet" href="https://openlayers.org/en/latest/css/ol.css" />
  <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>

发表回复