目前Web实现矢量渲染的主流技术包括SVG、VML和WebGL。相对而言,VML是一种较古老的技术,虽然未成为W3C标准,但被早期的IE浏览器(IE9以下)和微软Office广泛使用,目前已经远离了浏览器战场。所以可供选择的仅剩SVG和WebGL。SVG是XML的一个子集,秉承了一个标签对应一条数据的原则,目前经常被使用于数据量较小的web项目,比如图表和地铁图。Web矢量地图的数据量非常庞大,举个例子,如下图所示的一个512px*512px的瓦片,其数据量是一个接近5位数的二维数组。而这个瓦片仅仅是最简单的大陆和海洋轮廓,同尺寸街道图的数据量更加庞大。

处理庞大的数据量必然对性能的要求非常苛刻,况且由于中间隔着一层浏览器,Web地图并不能完全发挥CPU的计算能力。在有限的CPU资源下如果能够借助其他计算资源则必事半功倍,能够调用GPU资源的WebGL便成为了唯一的选择。

SVG不适合开发Web矢量地图的原因主要有两点:

  • 无法借助GPU提高性能;
  • Web地图交互非常频繁,比如移动、缩放、旋转等等,如果使用SVG则需要借助频繁操作DOM实现,而DOM操作是浏览器最消耗性能的行为。

技术选型

确定了底层技术-WebGL之后,接下来需要选择合适的辅助技术,针对目标有两点:

  • JavaScript
  • 构建工具

WebGL渲染与CSS无关,所以CSS开发框架的选型对整体的影响微乎其微,在此略过。

WebGL可以理解为OpenGL在浏览器环境下的变种,保留了OpenGL ES的语义和规范,提供相对简洁的JavaScript API。绝大部分的shader可以实现WebGL和OpenGL的共用。开发WebGL shader的语言GLSL是一种语法接近C的强类型编程语言。这一点对于习惯了JavaScript的前端开发者们需要一定的调整。既然是调整,那么不妨调整的彻底一些:将整体开发都引入强类型的概念。目前支持在JavaScript中引入强类型的主流框架有两种:TypeScript和Flow.js。TypeScript是JavaScript的强类型超集,Flow则更接近于一种类型注解或者注释工具。相对而言,引入Flow的成本更低,你可以自由决定哪些文件开启或者关闭类型检查,仅仅需要在文件顶部添加一行注释:

// @flow

所以Flow非常适合现有的项目进行迁移,而如果使用TypeScript则更需要将全部源代码进行改写。好在目前要做的项目并没有历史包袱,所以Flow的这点优势并不能作为技术选型的决定性因素。

最终选择TypeScript的原因有以下几点:

  • 语法更严谨甚至有些繁琐,但习惯之后非常顺手;
  • 生态更丰富,目前大部分主流第三方库均提供TypeScript支持。

ES6正式推出了Typed Array标准,但其实早在ES6之前,支持WebGL的浏览器就已经提供了强类型数组的API,目的是为了提高计算性能。

构建工具的选择相对比较多,Webpack、Rollup、gulp都是非常优秀的工具。最终选择Webpack的原因非常简单:比较熟。

构建配置

Webpack的配置与常规的web项目大体相同,需要注意的两点是:

  • TypeScript与Babel的配合
  • shader的构建

TypeScript&Babel

TypeScript本身支持编译为ES5或ES3,即将tsconfig.json的编译选型target修改为"es5"/"es3":

{
"compilerOptions": {
"target": "es5"
}
}

TypeScript编译器对于语法规范的转译功能可以满足绝大多数ES6新功能,但是其功能的全面性相比较Babel仍然有些不足,所以为了对编译进行更精准的控制,项目中采用的方案是将TypeScript首先转译为ES6语法,再借助Babel将其转译为ES5,即将tsconfig.json中的compilerOptions.target设置为"es6",webpack配置如下:

module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
use: ['babel-loader','awesome-typescript-loader']
}
}

Webpack编译TypeScript的loader有两个:ts-loaderawesome-typescript-loader。最终选择后者的原因当然不是因为它的名字中有个awesome,而是相对于前者,awesome-typescript-loader能够提供一些更加便利的功能,比如alias-别名。

如果源码的目录结构比较复杂,引用一个模块时可能需要写很长的路径名称,比如:

import Utils from '../../../utils';

为了令代码具有更好的易读性,我们通常借助一些工具将模块的引用设置较短的别名。Webpack也有此功能,通过resolve配置模块的别名:

resolve: {
alias: {
'utils': path.resolve(__dirname,'src/utils')
}
}

但遗憾的是ts-loaderawesome-typescript-loader并不能直接使用Webpack的alias配置,源码中直接使用模块别名将会抛出not found错误,请注意这个错误是TypeScript编译器抛出而非Webpack。解决方案很简单:在tsconfig.json中配置模块别名。如下:

{
"paths": {
"utils/*": ["src/utils/*"]
}
}

但这并不是最终的解决方案,因为如果使用ts-loader作为Webpack集成的话,Webpack并不能获取tsconfig.json的别名配置,也就是说,Webpack将会抛出not found错误。awesome-typescript-loader很好地解决了这个问题,它可以将tsconfig.json的别名配置映射至Webpack的resolve.alias。当然,如果你仍然坚持使用ts-loader也可以解决,如果你不怕麻烦的话:在Webpack中手动配置同样的resolve.alias

另外需要注意的是,使用awesome-typescript-loader需要在Webpack的resolve中创建对应的插件:

const TsConfigPathsPlugin = require('awesome-typescript-loader').TsConfigPathsPlugin;

module.exports = {
module: {
rules: [{
test: /\.ts$/,
exclude: /node_modules/,
use: ['babel-loader','awesome-typescript-loader']
},
// other rules
]
},
resolve: {
plugins: [
new TsConfigPathsPlugin({
configFileName: Path.resolve(__dirname,'../tsconfig.json')
})
]
}
}

shader

WebGL创建shader的流程为:

  1. 首先创建指定类型的shader实例;
  2. 将shader源码与实例绑定;
  3. 编译shader。

示例代码如下:

const source = `
precision mediump float; attribute vec2 a_pos; uniform vec4 u_color;
uniform vec2 u_resolution;
uniform vec2 u_translate; varying vec4 v_color; void main() {
vec2 real_poistion = (a_pos+u_translate) / u_resolution * 2.0 - 1.0;
gl_Position = vec4(real_poistion * vec2(1, 1), 0, 1);
v_color = u_color;
}`;
// 创建shader实例
const Shader = gl.createShader(gl.VERTEX_SHADER);
// 绑定shader源码
gl.shaderSource(Shader,source);
// 编译
gl.compileShader(Shader);

shader的源码以字符串的形式绑定至shader实例,也就是说,不论shader的源码是用什么编程语言编写(比如可以按照上述代码中用JavaScript字符串编写,也可以直接用glsl语言编写),一定要保证以字符串的形式引入shader源码模块。秉承这项原则,最简单的shader构建方案便是上述代码中的字符串形式。比如将上述示例代码中的shader源码单独抽离为vertex.js如下:

export default `
precision mediump float; attribute vec2 a_pos; uniform vec4 u_color;
uniform vec2 u_resolution;
uniform vec2 u_translate; varying vec4 v_color; void main() {
vec2 real_poistion = (a_pos+u_translate) / u_resolution * 2.0 - 1.0;
gl_Position = vec4(real_poistion * vec2(1, 1), 0, 1);
v_color = u_color;
}`;

然后在主文件中引入:

import VertexShaderSource from './vertex.js';
// 创建shader实例
const Shader = gl.createShader(gl.VERTEX_SHADER);
// 绑定shader源码
gl.shaderSource(Shader,VertexShaderSource);
// 编译
gl.compileShader(Shader);

这种书写方式优点是不需要对Webpack进行任何配置,但是却等于放弃了IDE对glsl语法的高亮、纠错等辅助功能。如果shader源码只有几行倒也没什么大问题,但是对于几十上百行的代码如果没有高亮辅助的话可能会严重影响开发效率。

解决这个问题的办法要从两方面入手:

  • 令Webpack能够正确编译glsl代码;
  • 令TypeScript能够将glsl模块与ts模块融合。

第一个问题很好解决,因为我们的目的是把glsl模块引入到js模块中并且作为字符串使用,所以Webpack要做的就是将glsl源码构建为字符串即可:

{
test: /\.glsl$/,
loader: 'raw-loader'
}

raw-loader的功能是将被引入的文件内容转换为字符串。

除了强类型带来的开发模式转变以外,TypeScript最大的问题是不能自动识别ts以外的任何其他类型模块,即使最普遍的JSON也不行。比如下述代码在TypeScript环境下会报not found错误:

import Data from '../data.json';

这时候需要用到TypeScript的声明文件。声明文件的作用简单来说就是告知TypeScript编译器一些必要的信息以便被正确识别。比如声明一些全局的类型(type)、接口(interface)、模块(module)等。

默认情况下,TypeScript编译器会自动识别源码和node_modules目录中@types文件夹内的声明文件,你也可以通过配置tsconfig.jsoncompilerOptions.typeRoots指定声明文件目录。针对上文提到的TypeScript不识别glsl和json模块问题,我们在源码目录的@types文件夹中创建声明文件global.d.ts,内容如下:

declare module '*.glsl';
declare module '*.json';
declare type WidthAndHeight = {
width: number;
height: number;
};

上述代码中声明了三个信息:

  • 声明glsl后缀类型的文件为可识别模块;
  • 声明json后缀类型的文件为可识别模块;
  • 声明全局类型WidthAndHeight,此类型将在任何源码文件中直接使用。

在以上配置的基础上还有一个注意事项:与ES6 modules不同的是,TypeScript引入declare声明的非ts模块并不能将其内容自动转化为默认导出,即export default。比如在ES6环境下引入一个json文件:

import JsonData from './data.json';

而在TypeScript环境下需要使用以下语法:

import * as JsonData from './data.json';

示例代码

具体代码可以参考demo:https://github.com/ihardcoder/demo_ts-webgl-webpack

【WebGIS系列】Typescript+WebGL+Webpack开发环境搭建的更多相关文章

  1. TypeScript完全解读(26课时)_1.TypeScript完全解读-开发环境搭建

    1.TypeScript完全解读-开发环境搭建 初始化项目 手动创建文件夹 D:\MyDemos\tsDemo\client-demo 用VSCode打开 npm init:初始化项目 然后我们的项目 ...

  2. Redis学习系列二之.Net开发环境搭建及基础数据结构String字符串

    一.简介 Redis有5种基本数据结构,分别是string.list(列表).hash(字典).set(集合).zset(有序集合),这是必须掌握的5种基本数据结构.注意Redis作为一个键值对缓存系 ...

  3. Zynq7000开发系列-6(QT开发环境搭建:Ubuntu、Zynq)

    操作系统:Ubuntu14.04.5 LTS 64bit Qt:Qt 5.4.2 (qt-opensource-linux-x64-5.4.2.run.qt-everywhere-opensource ...

  4. Zynq7000开发系列-5(OpenCV开发环境搭建:Ubuntu、Zynq)

    操作系统:Ubuntu14.04.5 LTS 64bit OpenCV:OpenCV 3.1.0.opencv_contrib gcc:gcc version 4.8.4 (Ubuntu 4.8.4- ...

  5. hibernate学习系列-----(1)开发环境搭建

    其实一两个月前就在了解hibernate方面的知识了,但一直以来,都没有好好的总结,而且一直使用的是myeclipse,感觉有些傻瓜式的操作就可以搭建起hibernate的开发环境,但这样一点都不好, ...

  6. vue与webpack开发环境搭建:从无到有

    一个vue从无到有的搭建过程. 一.不论是webpack还是vue,最初的第一步就是安装node.js.它是基石. 从官网下载你需要的安装包:官网下载链接:http://nodejs.cn/downl ...

  7. 基于GTK+3 开发远程控制管理软件(C语言实现)系列二 Centos7下开发环境搭建

    一.安装gcc gcc-c++ make等编译工具 yum install gcc gcc-c++ kernel-devel 这一步,其实可以不用做,你在安装Centos7的时候,如果选择开发模式安装 ...

  8. 从零搭建TypeScript与React开发环境

    前言 平时进行开发大多数是基于vue-cli或者create-react-app等官方或者公司内部搭建的脚手架.   我们业务仔做的最多就是npm i和npm run dev或者npm start,然 ...

  9. windows下vue+webpack前端开发环境搭建及nginx部署

    一.开发环境搭建 1.前端框架一般都依赖nodejs,我们首先要安装node.js.请参考http://www.cnblogs.com/wuac/p/6381819.html. 2.由于许多npm的源 ...

随机推荐

  1. Java之Collections.emptyList()、emptySet()、emptyMap()的作用和好处以及要注意的地方

    转自https://www.cnblogs.com/qiumingcheng/p/7126281.html 先说明一下好处有哪些:1,如果你想 new 一个空的 List ,而这个 List 以后也不 ...

  2. 【codeforces 698B】 Fix a Tree

    题目链接: http://codeforces.com/problemset/problem/698/B 题解: 还是比较简单的.因为每个节点只有一个父亲,可以直接建反图,保证出现的环中只有一条路径. ...

  3. 如何使用命令行编译和运行java文件

    相信大家现在一般都在使用IDE环境来开发运行java文件,但我觉得可以在命令行里面简单运行java文件,技多不压身. 接下来我来说一下编译和运行java文件: 第一步,首先下一个入门程序(注意:一定要 ...

  4. dubbo+zookeeper的使用

    我们讨论过Nginx+tomcat组成的集群,这已经是非常灵活的集群技术,但是当我们的系统遇到更大的瓶颈,全部应用的单点服务器已经不能满足我们的需求,这时,我们要考虑另外一种,我们熟悉的内容,就是分布 ...

  5. 重磅!!!微软发布ASP.NET Core 2.2,先睹为快。

    我很高兴地宣布ASP.NET Core 2.2现在作为.NET Core 2.2的一部分提供! 如何获取? 您可以从.NET Core 2.2下载页面下载适用于您的开发机器和构建服务器的新.NET C ...

  6. 解决:git push error: failed to push some refs to

    出现错误的原因是github中的README.md文件不在本地代码目录中. 也就是说我们需要先将远程代码库中的任何文件先pull到本地代码库中,才能push新的代码到github代码库中. 使用如下命 ...

  7. object类和内部类

    1.Object object类是所有类的根类(父类). 如果一个类没有显示继承另外一个类,那么该类一定继承于object类. toString()返回对象字符串的的形式. public class ...

  8. asp.net core 系列之中间件进阶篇-编写自定义中间件(middleware)

    中间件是被用到管道(pipeline)上来处理请求(request)和响应的(response). asp.net core 本身提供了一些内置的中间件,但是有一些场景,你可能会需要写一些自定义的中间 ...

  9. C# 操作Word目录——生成、删除目录

    目录,是指书籍.文档正文前所载的目次,将主要内容以一定次第顺序编排,起指导阅读.检索内容的作用.在Word中生成目录前,需要设置文档相应文字或者段落的大纲级别,根据设定的大纲级别可创建文档的交互式大纲 ...

  10. 【php性能优化】关于写入文件操作的取舍方案

    对于使用php对文件进行写入操作有两种方案一种使用 file_put_contents() 和 fopen()/fwrite()/fclose() 两种方案至于应该怎么选,我觉得应该分情况选择,下面是 ...