框架
标注平台核心是渲染+编辑。渲染由fabric支持,编辑由ID支持。
渲染
首先,我们基于fabric.util.createClass方法,继承出新的类:NPoint、NPolyline、NPolygon、NText。
- NPoint继承fabric.Circle
- NPolyline继承fabric.Polyline
- NPolygon继承fabric.Path
- NRectangle继承fabric.Rect
- NText继承fabric.Text
有人可能问,NPolygon为什么继承fabric.Path?这是因为fabric.Polygon继承自fabric.Polyline,所以不支持孔洞(hole),而fabric.Path可以解决这个问题。后续可以开一期专门讲具体实现。
按照前几章的内容,有了这五个类,我们可以对其渲染进行自定义扩展,例如带箭头的线,有填充色的面等等。
编辑
Entity
标注数据在标注平台中解析为Entity(openstreetmap的数据格式,当然这种格式自定义即可,不一定要用这种)。Entity是抽象类,Node、Way、Relation继承自Entity。
entity的基本格式如下:
{
id: 'n-1', // id
geometry: {...}, // 几何
type: 'node', // 类型
tags: {...}, // 属性
members: [{...}], // 关系
v: 0 // 版本号
}
对于标注平台而言,id、geometry和tags应该是我们重点关注的内容。特别是tags,将要存放我们标注的信息,无论是图像分割还是图像标注。
Graph
我们知道,ID中有个核心类叫作Graph,这是用于存储数据的。每一次编辑,都会有一版新的Graph推入栈中,从而实现撤销、重做功能。
如何往Graph中插入数据?需要借助History类。
History
History是管理Graph的类,封装了各种操作Graph的方法,例如pop、perform、replace、merge、reset等。
比如我们加载了一些存量的标注数据,我们将其解析为entity。然后,我们可以通过history.merge方法,将存量数据加载到baseGraph中(第一版Graph)。
在后续的编辑操作中,我们仅需要通过history.perform或者history.replace这两个方法即可。前者表示本次编辑操作需要存入一个新的Graph,后者表示本次编辑操作存入当前Graph即可。
撤销调用history.undo,将当前指针前移一版Graph,重做调用history.redo,将当前指针后移一版Graph。
所有History修改Graph的方法,都需要传入一个方法数组,这种方法称为action。
Action
action的输入和输出都是Graph,我们在方法内部写修改Graph的具体实现。
action = (graph) => {
// 修改graph
return graph;
}
一般情况下,在action内部,我们会修改或者新增一些Entity,然后通过 graph = graph.replace(entity)的方法,将数据存入Graph。而修改或者新增Entity的方法,最终会调用Entity基类里的方法update,通过拷贝原始entity的属性值,并用新的值覆盖,创建一个新的Entity实例。
那么,标注平台集成那么多编辑工具,有没有什么设计模式可以抽象出一些交互模式?
答案是有的。ID提供了更加抽象的类mode和behavior来解决交互模式问题。
Mode
在ID中,同一个时间点只能有一种mode,例如当前处于浏览模式、选中模式、绘制模式等等。当你处于一种模式中,你就只能执行当前模式下所包含的行为(behavior)。
如果要进入另一个mode,需要先退出当前mode(exit),卸载bahvior(off),然后才会进入新mode(enter),并且注册hehavior(add),这样确保交互模式的统一和切换。所有工具都可以抽象(封装)为一个mode+若干behavior的组合模式。这样,当你要使用某个编辑工具,只需要进入这个工具的mode即可。你的实际交互逻辑,都写在behavior中。
Behavior
何为behavior?本质上就是一种固定的行为,例如,画线、画点、画面、画矩形、插形点等等。behavior有什么能力呢?你可以在behavior内做任何和你当前mode有关的行为,例如,绑定某个快捷键,监听某个事件等等。需要注意的是,任何监听,都应该在卸载behavior时注销,否则监听事件可能会造成内存泄漏。
对于标注平台,我们需要的工具有,画点、画线、画面、画矩形。而这些绘制工具的实现逻辑,也恰恰是标注平台的基础。一个好的编辑工具,不仅需要具备兼容性,还要可扩展。同时需要考虑标注过程可能出现的问题,并给予解决。举个最简单的例子,用户在绘制线的过程中,如果某一个点画错了,需要支持撤销,如何实现?简单来说,抛开渲染 ,你需要在画线的behavior中监听history的undo事件。
总结
整体而言,fabric负责搞定一切渲染,简单样式足以。而强大的地图编辑器ID,用于图片编辑,简直是小菜一碟。以上,整体框架敲定。
预告
下一章,我们讲讲如何为way、node扩展出fabric渲染方法。