
做版图的同行大概都经历过这种时刻花了两周时间把一块模块的版图收拾得干干净净DRC 清了、LVS 过了打包一并发给对接方两天后收到一句话——“你这份 GDSII 坐标精度和我们的库不一致层号表也对不上麻烦重出。”于是又得回去翻 PDK 文档、翻工艺说明重新设单位、重导层映射一整天的活。这类问题几乎全部出在数据交换这一环而不是设计本身。GDSII 就是这样一份“人人都得用、但很少有人真正读一遍规范”的格式。它诞生在上世纪八十年代到今天依然是版图数据在设计与制造之间流转的主干道。这篇文章我想把它彻底拆开讲一遍文件里到底写了什么、那些字段为什么这么设计、用脚本怎么生成和自查、从一份 GDSII 到最终交付给制造方中间还会被改造成什么样、以及我这些年踩过的那些坑。不管你是刚接触版图的学生还是已经做过几次交付的老手下面这些内容都能直接拿去用。1. GDSII 到底是什么一份活化石级的版图交换协议1.1 它存的不是“设计”而是“一堆带层号的几何图形”先把最容易误解的一点说清楚GDSII 里没有晶体管、没有网表、没有器件参数也没有任何设计意图。它只记录了几何信息——某个层号上有一个多边形顶点坐标是这些整数某个位置上引用了一个叫某某的子单元旋转了多少度、放大了多少倍。除此之外它对图形代表什么一无所知。这种“无知”恰恰是它能活四十年的根本原因。任何一种数据格式只要试图承载设计语义就必然和某一家工具的模型绑定换个平台就得做语义映射映射过程必然丢信息。而 GDSII 把自己压缩到最底层只搬运几何。任何工具都能读写任何流程都能拼接代价是你必须自己保证几何的正确性格式不会帮你。我常用一个类比GDSII 就像一张只标注了线宽和层高的建筑平面图它告诉你墙在哪里、多厚但不告诉你这间是卧室还是厨房。施工队照着能盖出来但盖出来是不是你想要的得靠你自己核对。1.2 一个文件里同时装着“图”和“图章”GDSII 是层次化格式这一点很多人用过但没深想过。它把版图组织成一棵结构树每个节点叫一个结构structure常被叫作 cell树根是顶层结构叶子是只含几何的基础单元。上层结构通过引用reference把下层结构“盖”到自己身上可以是单个引用也可以是一次性排出 m 行 n 列的阵列引用。这个设计的价值在于复用。一个标准单元在版图里出现几千次文件里只存一份几何加几千条引用记录体积能差出三四个数量级。但代价也很明显任何一次引用变换平移、旋转、镜像、缩放都会累积误差而且不同工具对嵌套引用的变换合成顺序处理不一定完全一致。我在做跨平台数据转换时遇到过顶层整体镜像、下层再有旋转的情况两边算出来的位置差了半个图形宽度——原因就是变换矩阵的乘法顺序理解不同。1.3 为什么 OASIS 出来了二十年GDSII 还在一线OASIS 在压缩率、坐标范围、重复结构表达、字符串表等方面全面优于 GDSII一个典型版图转成 OASIS 通常能小五到十倍。按理说早该替换了但现实是它只替换了一半。原因很实在一是生态惯性参考流程、检查脚本、验收规范全是围绕 GDSII 建的改一处要牵动一整条链二是可读性GDSII 结构简单到可以手写解析器出问题时能快速定位OASIS 的变长编码和压缩表让排错成本高不少三是“最小公约数”效应——你的上下游可能有一方只认 GDSII那你就得交 GDSII。下面这张表是我在实际项目里做的取舍参考不一定适用于所有场景但方向大体一致维度GDSIIOASIS坐标表示32 位有符号整数变长整数范围大得多典型体积基准约为 GDSII 的 1/5 到 1/10重复结构只支持规则阵列支持多种重复方式表达力强字符串定长记录逐个存字符串表重复内容只存一次解析难度低可手写中高需处理压缩表工具兼容性事实标准几乎全支持主流工具都支持老旧脚本未必常见用法交付、跨平台交换内部存储、超大版图归档我自己的习惯是内部工作副本用 OASIS 存减少磁盘和加载时间对外交付前统一转 GDSII并保留一份转换日志记录两边的图形数量、层号集合、包围盒方便对方核对。1.4 它在整条链路上的位置一份版图从设计者手里到晶圆上会经过这几个环节版图编辑与验证DRC、LVS、寄生提取、后仿、交付数据准备、掩模数据生成、掩模制造、光刻。GDSII 主要活跃在前两个环节但从第三个环节开始它就会被改造成别的形态。关键在于每经过一次交接数据就可能变形一次。你在编辑器里看到的多边形到了掩模数据里可能已经被切成了几十个梯形你标注的层号到了对方系统里可能被映射成了完全不同的编号。所以理解 GDSII 不能只理解“怎么存”还要理解“它会被谁、以什么方式读走”。2. 拆开二进制文件结构与你真正需要关心的字段2.1 一切都是记录四字节头加数据体的极简结构GDSII 是纯二进制、大端序高位字节在前。整个文件由一条条记录首尾相接构成没有全局索引没有文件头魔数解析器就是从头读到尾遇到 ENDLIB 就停。每条记录的结构极其规整前 2 个字节是记录总长度包含这 2 字节本身第 3 个字节是记录类型第 4 个字节是数据类型后面是数据体。长度字段是 2 字节无符号整数所以单条记录最长 65535 字节这也是为什么复杂多边形的顶点必须拆成多条 XY 记录。数据类型只有六种0 表示无数据体1 是位数组2 字节2 是 2 字节有符号整数3 是 4 字节有符号整数5 是 8 字节实数6 是 ASCII 字符串。注意 4 号4 字节实数在规范里保留但基本不用遇到的话按 8 字节处理多半会错位。拿文件第一条记录举例一个 6.0 版本的 GDSII 开头是这六个字节00 06 00 02 02 58拆开看00 06是长度 600是记录类型 0HEADER02是数据类型 22 字节整数02 58是值 600代表版本 6.0。整份文件就是靠这种“自描述”的记录串起来的一条读错后面全乱所以解析器必须严格校验长度字段。2.2 主要记录一览真正需要盯的就那么十几个规范里定义的记录类型有几十种但日常打交道、需要关心语义的其实不多。我把常用的整理成了下面这张表编号是我按规范核对的实际写解析器时建议对着正式文档再确认一遍因为不同版本的说明文档在个别条目上有出入。记录名编号数据体作用与注意点HEADER02 字节整数版本号600 表示 6.0BGNLIB112 个 2 字节整数库起始含修改时间和访问时间各 6 项LIBNAME2字符串库名规范限制 32 字符UNITS32 个 8 字节实数数据库精度定义全文最关键的两条数据ENDLIB4无库结束缺失基本等于文件被截断BGNSTR512 个 2 字节整数结构起始同样带时间戳STRNAME6字符串结构名规范限制 32 字符ENDSTR7无结构结束BOUNDARY8无多边形开始标志PATH9无路径开始标志SREF10无单元引用开始AREF11无阵列引用开始TEXT12无文本标注开始LAYER132 字节整数层号DATATYPE142 字节整数数据类型同一层内的子分类WIDTH154 字节整数路径线宽负数表示绝对宽度XY164 字节整数序列坐标点成对出现ENDEL17无元素结束SREFNAME18字符串被引用的结构名COLROW192 个 2 字节整数阵列的列数、行数TEXTTYPE202 字节整数文本类型STRING22字符串文本内容STRANS23位数组变换标志含镜像位和绝对标志位MAG248 字节实数放大倍率ANGLE258 字节实数旋转角度逆时针为正PATHTYPE302 字节整数路径端点样式0 方头 1 圆头 2 延伸半宽 4 自定义PROPATTR412 字节整数自定义属性名PROPVALUE42字符串自定义属性值BOX43无注释框多数流程直接忽略BOXTYPE442 字节整数注释框类型看清楚这张表你会发现一个事实GDSII 能表达的东西真的很少。没有贝塞尔曲线、没有圆弧、没有布尔运算、没有参数化。所有复杂形状都必须先被离散成折线所有曲线都必须先被切成短线段。这是它“通用”的代价也是很多精度问题的源头。2.3 八字节实数这份格式里最容易翻车的地方GDSII 的实数字段不是 IEEE 754而是一套以 16 为基的浮点表示。八个字节里第一个字节拆成 1 位符号位加 7 位指数后七个字节是 56 位尾数。公式是值 (-1)^符号 × 尾数 × 16^(指数 - 64)其中尾数被解释为一个纯小数取值必须落在 [1/16, 1) 区间内靠指数钳制来保证。这个“必须落在该区间”就是规格化的含义。举个具体例子数值 1.0 的编码是41 10 00 00 00 00 00 00推导一下1.0 需要写成尾数乘 16 的幂取尾数 0.0625也就是 1/16指数就是 1因为 0.0625 × 16 1.0。指数编码 64 1 65 二进制 1000001符号位 0拼起来第一字节是 0100 0001即 0x41。尾数 0.0625 的二进制是 0.0001放进 56 位里就是最高 4 位为 0001其余全 0也就是 0x10 后面跟六个 0x00。这个格式的精度大约相当于 16 位十进制有效数字表示常见单位值完全够用。但坑在于它不像 IEEE 754 那样人人熟悉很多自制解析器会直接用struct.unpack(d)去读结果读出来是垃圾数然后 UNITS 就变成了天文数字整个版图的坐标全错。我自己早期写过一个转换脚本就犯了这个错症状是所有图形缩到了原来的千万分之一看起来像“什么都没了”。还有一处更隐蔽的坑浮点表示的基数是 16相邻可表示数之间的间距是跳跃的同一个十进制小数在不同工具里写出来最低字节可能有微小差异。对 UNITS 来说这通常无害但如果哪天你做了逐字节比对的版本管理就会出现“内容没改但文件哈希变了”的情况别慌是编码层的事。2.4 UNITS 记录所有坐标偏移问题的总根源UNITS 里的两个数是整个文件里最应该先看的两条数据。它们的含义经常被搞混我用一句话定义清楚第一个数一个用户单位等于多少个数据库单位。用户单位通常就是微米。第二个数一个数据库单位等于多少米。典型值(0.001, 1e-9)一个用户单位1 微米 1000 个数据库单位一个数据库单位 1 纳米。也就是说文件里所有坐标的整数 1代表实际尺寸 1 纳米。另一种常见的(0.0001, 1e-10)一个数据库单位 0.1 纳米。精度高十倍坐标数值也大十倍。怎么选核心矛盾是精度与范围的权衡。GDSII 的坐标是 32 位有符号整数最大值 2147483647。我算一下不同精度下的物理上限数据库单位1 个坐标单位对应坐标最大值对应物理长度1 nm0.001 微米约 2147 毫米0.5 nm0.0005 微米约 1073 毫米0.1 nm0.0001 微米约 214.7 毫米0.05 nm0.00005 微米约 107 毫米看起来都很宽裕但别忘了这是“单点坐标绝对值上限”不是“芯片尺寸上限”。如果你把版图原点放在芯片左下角芯片本身 30 毫米见方那用到 0.1 纳米精度时最大坐标约 3 亿离上限还有七倍余量够用。但如果版图里有人把一个辅助标记放在芯片外很远的地方或者叠了个坐标系原点偏移很大的模块就可能溢出。溢出不会报错会直接回绕成负数图形飞到另一个角落去。注意坐标溢出是静默失败没有任何警告。交付前务必扫一遍所有坐标的绝对值确认离 2^31 有足够余量。还有一个实践中的细节数据库单位必须和工艺的设计栅格对齐。如果工艺要求所有图形落在 5 纳米栅格上而你的数据库单位是 1 纳米那坐标只要是 5 的倍数就没问题但如果你随手画了一个落在 3 纳米处的边它虽然被合法存储了却违反了工艺栅格要求某些检查会报“off-grid”。2.5 结构体与引用嵌套、变换和那个容易搞错的阵列定义BGNSTR 到 ENDSTR 之间是一个结构体。开头是 STRNAME然后是若干个元素最后 ENDSTR 收尾。结构之间不能交叉嵌套只能通过引用建立层次关系这一点和编程语言里的函数调用很像。单个引用 SREF 的记录顺序是SREF、SREFNAME、可选的 STRANS、可选的 MAG、可选的 ANGLE、XY。XY 只有一个点就是被引用结构的原点落在父结构坐标系里的位置。STRANS 是一个 2 字节位数组最高位表示镜像还有两位分别表示“放大倍率是绝对值”和“角度是绝对值”——后两位的意思是这个变换不再继承上层变换直接以全局坐标系为准。这两位的处理是不同工具差异最大的地方之一我建议除非有明确理由否则不要在嵌套引用里用绝对标志。阵列引用 AREF 稍微复杂一点。它用 COLROW 记录列数和行数两个 2 字节整数然后 XY 里给三个点第一个是阵列原点第二个定义列方向的向量第三个定义行方向的向量。列间距等于第二点减第一点的向量除以列数行间距同理。这里有几个坑一是列数行数用 2 字节有符号整数表示理论范围是 -32768 到 32767超过就溢出。有些工具在列数特别大时会偷偷拆成多个 AREF有些则直接报错。二是负数列数在部分工具里被解释为“反方向排列”但这不在规范里跨工具基本不可移植。我遇到过一次某工具导出的负行数阵列在另一个工具里被当成 0 行处理整块图形凭空消失查了大半天才定位到 COLROW。三是 AREF 的间距向量必须是精确的整数倍关系否则会累积舍入误差。如果你的阵列间距本身不是数据库单位的整数倍那排到第一千个单元时位置可能已经偏了。2.6 几何元素多边形、路径、文本各自的门道BOUNDARY 是最常用的元素记录序列是 LAYER、DATATYPE、XY。XY 里的点必须首尾闭合也就是最后一个点等于第一个点而且至少要有四个点构成一个三角形。少一个点很多工具会拒收某些工具会自动帮你补上结果就是同一份数据在不同平台读出来图形数量不一样。关于孔洞挖空的多边形GDSII 规范本身没有“带孔多边形”这个概念实际做法有两种一种是用外层多边形加内层多边形、靠绕向顺逆时针区分另一种是直接用布尔运算把带孔形状拆成若干个简单多边形。前一种做法在跨工具时风险很高因为绕向约定并不统一。我的建议是导出前统一做一次绕向规范化或者干脆拆成简单多边形。PATH 用于画线记录序列是 LAYER、DATATYPE、PATHTYPE、WIDTH、XY可选加扩展信息。PATHTYPE 控制端点样式0 是平头1 是圆头2 是方头向外延伸半个线宽4 是自定义延伸量。WIDTH 是 4 字节有符号整数正数表示线宽会被上层的 MAG 缩放影响负数表示绝对宽度、不随缩放变化。这个负数语义很容易被忽略一旦你的层次里存在非 1 的放大倍率正负号的区别就会让线宽差出几倍。还有一个实践要点PATH 和 BOUNDARY 在几何上是等价的路径可以转换成有宽度的多边形但在检查工具和掩模数据准备工具里两者可能走完全不同的代码路径。我就遇到过路径在某个检查工具里被忽略、转换多边形之后才被识别的情况。所以交付前把 PATH 全部转成 BOUNDARY 是一个稳妥做法代价是文件变大。TEXT 用于标注记录里除了位置和内容还有 PRESENTATION控制对齐方式和 TEXTTYPE。绝大多数流程对文本只做展示不参与几何运算。但有些流程会拿文本做标记层这时候要注意字体和大小在不同工具里渲染不一致别指望它精确对齐。BOX 记录是早期遗留用于给区域加注释框现代流程基本直接忽略。千万别用 BOX 去画实际的图形层某些检查工具看不见它等到发现时已经晚了。PROPATTR 和 PROPVALUE 用来挂自定义键值对很多工具支持但也有很多工具在读入时静默丢弃。所以关键信息不要只存在属性里该写进图层就写进图层。3. 从零生成、回读与自查一份能落地的实操流程3.1 工具组合与选型理由我常用的组合是三件套各司其职第一件是一个开源的跨平台版图查看与编辑工具用来目视检查、做布尔运算、跑简单的设计规则检查、做两份数据的异或比对。选它的理由是可脚本化、跨平台、启动快处理几 GB 的文件也不至于卡死。第二件是一个基于脚本语言的版图生成库用来程序化生成图形、批量改层号、做结构化自查。选脚本而不是图形界面是因为交付前的检查项往往是几十条规则靠手点必漏。第三件是对接方提供的工艺文件和层映射表这是唯一权威的数据源任何疑问都以它为准不要凭经验猜。说明上面提到的工具我用功能代称不指名具体产品避免变成广告。你在自己的环境里找对应能力的工具即可能力对得上就行。3.2 生成一个带层次和阵列的最小可用样例下面这段代码演示了完整流程建库、定义精度、建底层单元、建顶层单元、放单引用和阵列引用、写出文件。库名用了占位符函数语义和你手头的库基本一一对应改改名字就能跑。import layoutapi as la # 库名做了替换语义与常见版图脚本库一致 # 第一步建库同时把数据库精度钉死 lib la.Library(namedemo_lib, unit1e-6, precision1e-9) # unit1e-6 表示用户单位是 1 微米 # precision1e-9 表示数据库单位是 1 纳米 # 这两个值最终会被写成 UNITS 记录里的两个实数 # 第二步建一个基础单元里面放几条金属线 base lib.new_cell(METAL_BAR) # 画一个 2 微米 x 0.2 微米的长方形层号 10数据类型 0 # 注意所有坐标都要换成数据库单位的整数这里 1 微米 1000 base.add_polygon( [(0, 0), (2000, 0), (2000, 200), (0, 200)], layer10, datatype0 ) # 画一条路径线宽 0.1 微米端点平头 base.add_path( [(0, 400), (2000, 400)], width100, # 100 个数据库单位 0.1 微米 layer11, datatype0, pathtype0 ) # 第三步建顶层单元 top lib.new_cell(TOP) # 单引用把 base 放在 (10000, 10000) 处旋转 90 度 ref1 la.Reference(base, origin(10000, 10000), rotation90) top.add(ref1) # 阵列引用3 列 5 行列间距 5 微米行间距 3 微米 aref la.Reference( base, origin(20000, 20000), columns3, rows5, spacing(5000, 3000) # 列间距、行间距单位是数据库单位 ) top.add(aref) # 第四步写文件 lib.write_gds(demo.gds) print(cell 数量:, len(lib.cells)) print(顶层 cell:, lib.top_level()[0].name)这段代码跑完会得到一个几百字节的小文件但里面已经包含了 HEADER、BGNLIB、LIBNAME、UNITS、两个 BGNSTR/ENDSTR、BOUNDARY、PATH、SREF、AREF、ENDLIB 几乎全部关键记录。拿十六进制编辑器打开它对照第 2 节那张记录表一条条看是理解 GDSII 最快的方式比读十页文档都管用。这里有个细节值得单独说坐标系和阵列参数必须用整数也就是数据库单位的整数倍。上面写(2000, 0)而不是(2.0, 0)就是因为一旦传了浮点库内部会帮你做舍入而舍入方向和时机未必是你能控制的。宁可自己在业务代码里就把单位换算算清楚也不要依赖库的隐式转换。3.3 回读自查交付前必须跑的那几项生成完不算完一定要读回来验证。我通常跑下面这几个检查写成脚本一次执行输出一份报告附在交付包里。第一个检查是结构树完整性顶层结构有几个、总结构数多少、有没有孤立结构没被任何结构引用、也不是顶层。孤立结构通常是删改过程中留下的垃圾白白占体积某些严格流程还会报错。第二个检查是包围盒。算出整个版图的坐标最小值和最大值换算成毫米和预期的芯片尺寸对一遍。这一步能抓到绝大多数“整体偏移”和“坐标溢出”问题。如果包围盒比预期大出几十倍先怀疑单位设错了再怀疑有没有漏删的辅助图形。第三个检查是分层统计。按 layer 和 datatype 组合统计多边形数量、路径数量、总面积。这是最有效的丢层检测手段如果你的金属层应该有上万个多边形结果统计出来是 0那基本就是层映射出问题了。第四个检查是栅格合规性。扫一遍所有坐标看是否都能被工艺要求的最小栅格整除。不能整除的点就是潜在的 off-grid。下面这段是回读检查的核心逻辑示例import layoutapi as la lib la.read_gds(demo.gds) # 一次性展平得到全局坐标下的所有多边形 # 注意大版图不要全展平内存扛不住要按结构逐层取样 flat lib.flatten() xs [pt[0] for poly in flat.polygons for pt in poly.points] ys [pt[1] for poly in flat.polygons for pt in poly.points] dbu lib.precision # 数据库单位单位是米 print(X 范围: %.3f ~ %.3f 毫米 % (min(xs)*dbu*1e3, max(xs)*dbu*1e3)) print(Y 范围: %.3f ~ %.3f 毫米 % (min(ys)*dbu*1e3, max(ys)*dbu*1e3)) # 分层统计 from collections import Counter stat Counter((p.layer, p.datatype) for p in flat.polygons) for key in sorted(stat): print(层 %d/%d: %d 个多边形 % (key[0], key[1], stat[key])) # 栅格检查假设工艺要求 5 纳米栅格 grid 5 # 数据库单位是 1 纳米则 5 表示 5 纳米 bad [pt for poly in flat.polygons for pt in poly.points if pt[0] % grid or pt[1] % grid] print(非栅格点数量:, len(bad))3.4 层映射表整条链上最容易丢东西的一环层映射表定义了设计层名到 GDSII 层号、数据类型对的对应关系。不同工具用的格式不一样有的是纯文本两列有的是几列的表格有的是脚本片段。内容大体是这样层名用途layerdatatype备注NWN 阱10OD有源区20PO多晶硅30M1第一层金属100M1_PIN第一层金属引脚标注101同层不同数据类型M2第二层金属110VIA1通孔200TEXT文字标注630不参与制造DUMMY填充900交付前需确认是否保留这张表看似简单坑却最多。第一它是方向性的设计层名到层号是一个方向反过来解析时又是另一个方向两边必须保持一致否则你做一次往返转换就会丢层。第二同层多数据类型很容易漏比如引脚标注用的 10/1如果映射表只写了 10/0那所有引脚标注都会消失而引脚标注往往影响后续的探针和封装设计。第三不同工具对“未映射层”的默认行为不同有的是原样保留有的是直接丢弃有的是映射到默认层。丢弃是最危险的因为不报错。我的做法是映射表只维护一份所有转换脚本都从这一份读绝不手写第二份每次转换后对比输入输出文件的层号集合有差异就直接中止流程。3.5 交付前自检清单这份清单我贴在工位上很多年了条目随着踩坑不断增加现在大概稳定在这个形态检查项判断标准出问题的典型后果数据库单位与工艺一致与工艺文件规定值完全相同整体缩放错误坐标范围绝对值远小于 2^31且与芯片尺寸吻合图形回绕、飞到别处顶层结构唯一只有一个顶层其余都被引用交付方读错顶层结构名长度与字符不超过 32 字符只用字母数字下划线名称截断导致冲突层号集合与映射表完全一致无多余层多余层被当作真实图形多边形闭合性首尾点相等顶点数不少于 4图形被判非法或自动修补路径已转多边形交付版本中 PATH 数量为 0检查工具忽略部分图形文本层处理明确约定是否保留文字被误当作图形制造文件完整性末尾存在 ENDLIB 记录文件被截断校验和与随附说明文件一致传输损坏未被发现4. 从 GDSII 到掩模交付链路上的几个变形点4.1 图形切割为什么你的数据到了掩模厂会变大好几倍掩模写入设备不管是电子束还是激光能曝光的形状是有限的通常是矩形和少量简单梯形。你版图里那些奇形怪状的多边形包括斜边、圆弧离散出来的短折线都必须先被切割成设备能识别的简单形状。这个过程叫切割或分片。切割是个纯粹的数据变换几何形状理论上保持不变但实际会带来三个后果。第一是数据量膨胀一个复杂多边形切成几十个小梯形很常见整体膨胀三到五倍属于正常形状复杂的层比如接触孔阵列、存储阵列能到十倍。第二是切割算法的差异不同工具的切割策略不同切出来的形状边界可能落在略微不同的位置虽然在容差内但如果你拿切割前后的数据做严格比对会看到大量差异。第三是顶点合并切割工具有时会把共线顶点合并掉导致多边形的顶点数变化。这件事对设计者的实际影响是交付的 GDSII 不一定是你最终看到的样子。如果你的图形里有极窄的缝隙或者极尖的角切割之后可能出现零宽度的退化图形。所以有些流程要求提交前先做一次“切割预演”把明显会被切坏的地方修掉。4.2 光学邻近校正与数据量的二次膨胀在先进节点上掩模数据还要经过光学邻近校正处理。简单说因为光刻时相邻图形会互相影响需要对图形做“预先变形”让最终在晶圆上印出来的形状符合设计意图。这一步会把原本规整的矩形改成带各种凸起和凹陷的复杂多边形数据量再涨几倍。所以一个完整的链条下来掩