Press "Enter" to skip to content

开源深度学习计算平台ImJoy解析:12 — 基于web的绘图库OpenLayers

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

ImJoy有一个很有用的插件或独立应用,叫做 Kaibu ,它可以展示普通的位图、矢量图及vtk、stl等3D格式的数据。

 

比如如下展示:

 

其就是位图(png格式)、矢量图(json格式)、3D模型(stl格式)的一个叠加。

 

Kaibu主要用了两个JS库,一个是 OpenLayers ,一个是 ITK-VTK ,前者用于展示矢量图形、普通位图等数据,且对地图的展示异常强大,后者用于展示在医疗及科学计算中常用的3D图像、网格、点集等。

 

这一篇主要介绍OpenLayers的相关知识。

 

配置环境

 

OpenLayers workshop releases 里下载最新的资料包。

 

安装依赖:

 

npm install

 

启动:

 

npm start

这会启动一个开发服务器。可以通过
http://localhost:1234 查看一个“欢迎”的弹出窗口,以及
http://localhost:1234/doc/

查看说明文档。

 

开发入门

 

这一部分会通过OpenLayers map来创建一个简单的web页面。

 

在OpenLayers中,一个map是在web页面中被渲染的一系列“层”layers的集合。OpenLayers支持很多种layers:

 

(1)针对平铺光栅切片数据的Tile layer;

 

(2)针对位图图像的Image layer;

 

(3)针对矢量数据的Vector layer;

 

(4)针对平铺矢量切片数据的Vector tile layer。

 

除了这些layers,一个map还可以通过一系列的控制(即在map上面的UI元素)和交互(即与map进行交互反馈的部件)来进行配置。

 

为了创建一个map,需要通过HTML中的元素来创建(如一个 <div> 元素),以及一些样式来指定合适的尺寸。

 

HTML页面

 

将项目根目录中的 index.html 里的内容替换为如下代码(注释写在了代码中):

 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>OpenLayers</title>
    <style>
      /*引入OpenLayers的样式*/      @import "node_modules/ol/ol.css";
    </style>
    <style>
      /*该部分样式使得map容器完全充满整个页面*/      html, body, #map-container {
        margin: 0;
        height: 100%;
        width: 100%;
        font-family: sans-serif;
      }
    </style>
  </head>
  <body>
    <!-- 该div标签是map的渲染容器 -->
    <div></div>
    <!-- 引入相关的js代码 -->
    <script src="./main.js" type="module"></script>
  </body>
</html>

 

具体应用

 

将项目根目录中的 main.js 里的内容替换为如下代码:

 

// 从OpenLayers中导入必要的模块
import OSM from 'ol/source/OSM';
import TileLayer from 'ol/layer/Tile';
import {Map, View} from 'ol';
import {fromLonLat} from 'ol/proj';
// 创建一个Map对象
new Map({
  // 目标是HTML中的那个div元素
  target: 'map-container',
  layers: [
    // 具体的layer是使用了Tile Layer
    new TileLayer({
      source: new OSM(),
    }),
  ],
  // view定义了初始的中心点和缩放比例
  view: new View({
    // 中心点的指定是通过fromLonLat函数获取地理坐标
    center: fromLonLat([0, 0]),
    zoom: 2,
  }),
});

 

效果

 

此时打开 http://localhost:1234 ,会看到世界地图:

 

矢量数据

 

在这一部分,将会创建一个可以操作矢量数据的编辑器,使得用户可以导入数据、绘制形状、修改已有形状及导出结果等。

 

本部分会使用 GeoJSON 数据,不过OpenLayers支持其他大量的矢量数据格式。

 

渲染GeoJSON

 

在开发编辑功能之前,先看一下基本的对矢量数据的渲染功能。

 

在项目的data路径下有一个名为 countries.json 的GeoJSON文件,这里将加载该数据并在地图上渲染出来。

 

首先,编辑一下刚才的 index.html ,这里新加一行控制背景颜色的代码:

 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>OpenLayers</title>
    <style>
      @import "node_modules/ol/ol.css";
    </style>
    <style>
      html, body, #map-container {
        margin: 0;
        height: 100%;
        width: 100%;
        font-family: sans-serif;
        /*新加了这一行来控制背景颜色*/        background-color: #04041b;
      }
    </style>
  </head>
  <body>
    <div></div>
    <script src="./main.js" type="module"></script>
  </body>
</html>

 

然后将 main.js 中的内容替换为如下代码:

 

// 导入GeoJSON包来读写该格式的数据
import GeoJSON from 'ol/format/GeoJSON';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
new Map({
  target: 'map-container',
  layers: [
    // layer使用的是处理和渲染矢量数据的VectorLayer
    new VectorLayer({
      // VectorSource用来获取GeoJSON数据,并管理空间索引
      source: new VectorSource({
        format: new GeoJSON(),
        // 导入data目录下的JSON文件
        url: './data/countries.json',
      }),
    }),
  ],
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});

 

效果如下:

 

因为我们会重载这个页面很多次,目前代码下每次重载页面都会回到初始的view方式,即初始的中心点和缩放大小。如果能每次重载都能保持map在相同的位置就能节省很多人力。

 

此时可以借助 ol-hashed 包实现,修改代码如下:

 

import GeoJSON from 'ol/format/GeoJSON';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
// 导入ol-hashed包
import sync from 'ol-hashed';
// 将Map对象分配到一个变量上
const map = new Map({
  target: 'map-container',
  layers: [
    new VectorLayer({
      source: new VectorSource({
        format: new GeoJSON(),
        url: './data/countries.json',
      }),
    }),
  ],
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
// 将上面的变量传递给sync函数
sync(map);

 

此时,你会发现,将地图移动和缩放到某一特定程度后,下次重新载入代码仍然保持该视角不变。

 

拖放

 

对于要实现的编辑器,想要允许用户能够导入自己的数据进行编辑。为此,这里将添加 DragAndDrop 功能。

 

跟以前一样,这里仍只处理GeoJSON这种数据,不过该交互也支持其他类型的数据格式。

 

修改 main.js 为:

 

import GeoJSON from 'ol/format/GeoJSON';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import sync from 'ol-hashed';
// 导入DragAndDrop包
import DragAndDrop from 'ol/interaction/DragAndDrop';
// 定义一个Map对象,只指定它的目标和视图
const map = new Map({
  target: 'map-container',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
sync(map);
// 创建数据源VectorSource,但是里面没有任何数据
const source = new VectorSource();
// 创建VectorLayer,里面的source传入上面定义的空的source
const layer = new VectorLayer({
  source: source,
});
// 将layer添加到map中
map.addLayer(layer);
// 对map添加拖放交互
map.addInteraction(
  new DragAndDrop({
    // 将拖放动作作用在Vector Source上
    source: source,
    // 指定GeoJSON格式
    formatConstructors: [GeoJSON],
  })
);

 

此时就能将GeoJSON文件拖放到该页面上,从而进行渲染。

 

修改特征

 

现在可以将数据拖放到编辑器中,下面是添加“修改”功能。

 

实现方式是使用 Modify 交互。

 

修改 main.js 为:

 

import DragAndDrop from 'ol/interaction/DragAndDrop';
import GeoJSON from 'ol/format/GeoJSON';
import Map from 'ol/Map';
// 导入Modify包
import Modify from 'ol/interaction/Modify';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import sync from 'ol-hashed';
const map = new Map({
  target: 'map-container',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
sync(map);
const source = new VectorSource();
const layer = new VectorLayer({
  source: source,
});
map.addLayer(layer);
map.addInteraction(
  new DragAndDrop({
    source: source,
    formatConstructors: [GeoJSON],
  })
);
// 在map上添加Modify交互,并配置交互对象
map.addInteraction(
  new Modify({
    source: source,
  })
);

此时就可以拖动顶点来修改特征。也可以使用
Alt+Click

来删除顶点。

 

绘制特征

 

接下来添加 Draw 交互来使得用户可以绘制新的特征,并添加到数据中。

 

import DragAndDrop from 'ol/interaction/DragAndDrop';
// 导入Draw包
import Draw from 'ol/interaction/Draw';
import GeoJSON from 'ol/format/GeoJSON';
import Map from 'ol/Map';
import Modify from 'ol/interaction/Modify';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import sync from 'ol-hashed';
const map = new Map({
  target: 'map-container',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
sync(map);
const source = new VectorSource();
const layer = new VectorLayer({
  source: source,
});
map.addLayer(layer);
map.addInteraction(
  new DragAndDrop({
    source: source,
    formatConstructors: [GeoJSON],
  })
);
map.addInteraction(
  new Modify({
    source: source,
  })
);
// 在map上添加Draw交互
map.addInteraction(
  new Draw({
    // 指定绘制形状,该值可以是任意的GeoJSON的几何形状
    type: 'Polygon',
    // 配置交互对象
    source: source,
  })
);

 

自动吸附

 

上面的绘制功能添加后,可以发现,当绘制图形时,很难沿着之前的图形进行精确绘制。

 

此时可以添加 snap 功能,当鼠标移动到某个像素一定范围内时,就能自动吸附到该像素,从而完成精确绘制。

 

import DragAndDrop from 'ol/interaction/DragAndDrop';
import Draw from 'ol/interaction/Draw';
import GeoJSON from 'ol/format/GeoJSON';
import GeometryType from 'ol/geom/GeometryType';
import Map from 'ol/Map';
import Modify from 'ol/interaction/Modify';
// 添加Snap包
import Snap from 'ol/interaction/Snap';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import sync from 'ol-hashed';
const map = new Map({
  target: 'map-container',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
sync(map);
const source = new VectorSource();
const layer = new VectorLayer({
  source: source,
});
map.addLayer(layer);
map.addInteraction(
  new DragAndDrop({
    source: source,
    formatConstructors: [GeoJSON],
  })
);
map.addInteraction(
  new Modify({
    source: source,
  })
);
map.addInteraction(
  new Draw({
    source: source,
    type: GeometryType.POLYGON,
  })
);
// 在map上添加Snap交互
map.addInteraction(
  new Snap({
    // 配置作用对象
    source: source,
  })
);

 

下载特征

 

当上传数据,且对其编辑后,希望能下载特征。

 

为了能实现这个功能,这里将特征数据序列化为GeoJSON数据,然后创建一个带 download 属性的 <a> 元素,这样就能触发浏览器的文件保存对话框。

 

同时,在map上添加一个按钮,可以使得用户清除现在的特征,重新绘制。

 

修改 index.html 为:

 

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>OpenLayers</title>
    <style>
      @import "node_modules/ol/ol.css";
    </style>
    <style>
      html, body, #map-container {
        margin: 0;
        height: 100%;
        width: 100%;
        font-family: sans-serif;
        background-color: #04041b;
      }
      /*对id为tools的div进行样式设定*/      #tools {
        position: absolute;
        top: 1rem;
        right: 1rem;
      }
      /*对id为tools中的两个后代a元素设定样式*/      /*css语法可以参见这里:*/      /*https://www.runoob.com/css/css-combinators.html*/      #tools a {
        display: inline-block;
        padding: 0.5rem;
        background: white;
        cursor: pointer;
      }
      /*! [tools] */    </style>
  </head>
  <body>
    <div></div>
    <script src="./main.js" type="module"></script>
    <!-- 新增一个div元素,里面包含了两个a元素 -->
    <div>
      <a>Clear</a>
      <a download="features.json">Download</a>
    </div>
  </body>
</html>

 

修改 main.js 为:

 

import GeoJSON from 'ol/format/GeoJSON';
import GeometryType from 'ol/geom/GeometryType';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import sync from 'ol-hashed';
import {DragAndDrop, Draw, Modify, Snap} from 'ol/interaction';
const map = new Map({
  target: 'map-container',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
sync(map);
const source = new VectorSource();
const layer = new VectorLayer({
  source: source,
});
map.addLayer(layer);
map.addInteraction(
  new DragAndDrop({
    source: source,
    formatConstructors: [GeoJSON],
  })
);
map.addInteraction(
  new Modify({
    source: source,
  })
);
map.addInteraction(
  new Draw({
    source: source,
    type: GeometryType.POLYGON,
  })
);
map.addInteraction(
  new Snap({
    source: source,
  })
);
// 实现清除功能
// 首先通过DOM选取clear按钮
const clear = document.getElementById('clear');
// 对该按钮添加鼠标事件
clear.addEventListener('click', function () {
  source.clear();
});
// 实现下载功能
// 这里序列化数据为GeoJSON格式
const format = new GeoJSON({featureProjection: 'EPSG:3857'});
// 通过DOM获取download按钮
const download = document.getElementById('download');
// 因为这里是期望随时都能下载最新的数据,所以将数据获取及序列化的工作绑定在source的change事件上
// 即,只要source改变,download按钮所能获得的数据就是最新的source
source.on('change', function () {
  // 获得特征
  const features = source.getFeatures();
  // 序列化特征
  const json = format.writeFeatures(features);
  // 这里将原json字符串转换成URI组成部分,将附加到下载按钮的href中
  download.href =
    'data:application/json;charset=utf-8,' + encodeURIComponent(json);
});

 

效果如下:

 

配置绘图样式

 

前面的编辑功能都是使用了默认样式,这里增加更多的属性来使得编辑功能更加强大,比如设置画笔宽度、设置填充颜色等。

 

静态样式

 

如果单纯想将样式都调成一个模样,那幺可以直接简单地将样式固定即可,如下面代码:

 

const layer = new VectorLayer({
  source: source,
  style: new Style({
    fill: new Fill({
      color: 'red'
    }),
    stroke: new Stroke({
      color: 'white'
    })
  })
});

 

即都填充成红色,笔画都是白色。

 

动态样式

 

更多情况下,动态样式使用得更多,即按照一定的规则自动设置样式。

 

如下面:

 

constlayer = newVectorLayer({
  source: source,
  style: function(feature, resolution) {
    constname = feature.get('name').toUpperCase();
    returnname < "N"? style1 : style2; // assuming these are created elsewhere}
});

 

就是根据feature的name来设置样式,如果是 A-M ,就用style1,如果是 N-Z ,则使用style2。

 

所以设定好规则非常重要。

 

下面将展示如何根据几何区域设定样式。

 

修改 main.js 为:

 

import DragAndDrop from 'ol/interaction/DragAndDrop';
import Draw from 'ol/interaction/Draw';
import GeoJSON from 'ol/format/GeoJSON';
import GeometryType from 'ol/geom/GeometryType';
import Map from 'ol/Map';
import Modify from 'ol/interaction/Modify';
import Snap from 'ol/interaction/Snap';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
import sync from 'ol-hashed';
// 导入必要的样式库
import {Fill, Stroke, Style} from 'ol/style';
// 导入colormap包
import colormap from 'colormap';
// 从OpenLayers导入getArea包
import {getArea} from 'ol/sphere';
// --- 根据面积计算颜色:开始 ----
const min = 1e8; // the smallest area
const max = 2e13; // the biggest area
const steps = 50;
const ramp = colormap({
  colormap: 'blackbody',
  nshades: steps,
});
function clamp(value, low, high) {
  return Math.max(low, Math.min(value, high));
}
function getColor(feature) {
  const area = getArea(feature.getGeometry());
  const f = Math.pow(clamp((area - min) / (max - min), 0, 1), 1 / 2);
  const index = Math.round(f * (steps - 1));
  return ramp[index];
}
// --- 根据面积计算颜色:结束 ----
const map = new Map({
  target: 'map-container',
  view: new View({
    center: [0, 0],
    zoom: 2,
  }),
});
sync(map);
const source = new VectorSource();
// 添加样式
const layer = new VectorLayer({
  source: source,
  style: function (feature) {
    return new Style({
      fill: new Fill({
        color: getColor(feature),
      }),
      stroke: new Stroke({
        color: 'rgba(255,255,255,0.8)',
      }),
    });
  },
});
map.addLayer(layer);
map.addInteraction(
  new DragAndDrop({
    source: source,
    formatConstructors: [GeoJSON],
  })
);
map.addInteraction(
  new Modify({
    source: source,
  })
);
map.addInteraction(
  new Draw({
    source: source,
    type: GeometryType.POLYGON,
  })
);
map.addInteraction(
  new Snap({
    source: source,
  })
);
const clear = document.getElementById('clear');
clear.addEventListener('click', function () {
  source.clear();
});
const format = new GeoJSON({featureProjection: 'EPSG:3857'});
const download = document.getElementById('download');
source.on('change', function () {
  const features = source.getFeatures();
  const json = format.writeFeatures(features);
  download.href = 'data:text/json;charset=utf-8,' + json;
});

 

效果如下:

 

移动端地图和数据集成

 

这一部分将创建一个移动端的地图来展示用户的GPS位置和朝向。该项目的目的是为了展示怎样将OpenLayers与浏览器的API及第三方工具进行集成。

 

具体地,仅使用几行代码即可调用浏览器的关于地理位置的API,从而得到GPS位置,以及使用 kompas 库通过设备的陀螺仪获得朝向。然后,通过使用Vector Layer,就能很轻易地在地图上显示结果。

 

因为这一部分需要移动端的配合,不再具体分析。

 

更多用法留坑待填。

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。