地图可视化实践录:TopoJSON学习
本文介绍TopoJSON,并给出示例和效果图。
知识点
前面已经演示过GeoJSON,本文将学习TopoJSON。TopoJSON 是 GeoJSON 的扩展和优化格式,主要特点是使用种拓扑结构的编码方式,即不直接存储每个地理要素的完整坐标,而是存储它们共享的边界,即只保存一张共享的、无重复的边界线的底图,这也是其名称Topo(拓扑)的来源。
在GeoJSON中,相邻要素的边界会被重复存储,导致数据冗余。特殊是共享边界较多的情况下,数据量特别明显。
TopoJSON 核心结构字段如下:
{"type": "Topology", // 必填,固定为 "Topology",表明这是一个 TopoJSON 对象"objects": { // 必填,包含所有地理要素的容器"countries": { ... },"cities": { ... }},"arcs": [ // 必填,存储所有共享的弧段(边界线)[[0,0], [1,0], [1,1], [0,1]], // 弧段1[[1,1], [0,1]] // 弧段2],"transform": { // 可选,坐标变换参数,用于减小文件体积"scale": [0.001, 0.001], // 缩放比例"translate": [100, -50] // 平移偏移}
}
说明如下:
- type: TopoJSON 文件的根级字段,其值固定为字符串 “Topology”。表示该JSON是TopoJSON 拓扑结构。
- objects:对象集合,用于存放所有具体的地理要素。自定义名称,用于对地理要素进行逻辑分组,如下文使用到的
roads。 TopoJSON 文件可以在 objects 字段内包含多个这样的命名对象。 - arcs:弧段数组, TopoJSON 最核心字段必填项。包含了构成地图所有共享边界线的坐标序列,这些边界线即为“弧段”。工作机制:将地图中所有相邻图形的公共边界提取出来,作为一条唯一的弧段存储一次。每个图形要求通过索引来引用这些弧段,以重建自身所需的形状。这种方式彻底消除了 GeoJSON 中存在的坐标冗余,是 TopoJSON 文件体积显著减小的根本原因。
在 objects 内部,需定义每个地理要素对象,包含以下几个重要字段:
- type(几何类型):描述该要素的几何形态,,如"Polygon"(面)、“LineString”(线)、“Point”(点)或 “GeometryCollection”(几何集合)。
- arcs(弧段引用):数组形式,用于引用 arcs 数组中的索引,指明如何用这些弧段拼凑出当前要素的形状。使用负索引(如 -1)表示反向使用该弧段,这对于构建多边形的“洞”或确保边界方向正确至关重要。
- properties(属性):一个可选的键值对对象,用于存储与该地理要素相关的所有非几何信息,例如名称、人口、编码等。本文定义了画线的颜色字段。
以下是一个简单的可用的示例。
{"type": "Topology","transform": {"scale": [0.0001, 0.0001],"translate": [108.353777,22.810760]},"objects": {"nanning_area": {"type": "GeometryCollection","geometries": [{"type": "Polygon","properties": {"name": "nn", "type": "urban"},"arcs": [[0]]},{"type": "LineString","properties": {"name": "place", "type": "river"},"arcs": [1]},{"type": "Point","properties": {"name": "南湖公园", "type": "transport"},"coordinates": [15, 3]}]}},"arcs": [[[0,0], [25,0], [25,15], [0,15], [0,0]],[[-5,8], [5,7], [12,6], [20,8]]]
}
实践
转换
前面已积累有GeoJSON文件,需将其转换成TopoJSON,使用geo2topo 命令转换。在安装nodejs环境的命令行中下载:
npm install -g topojson-server
转换命令很简单:
geo2topo roads=myroute_geo.json > my.json
myroute_geo.json文件已在代码仓库中,可直接使用。输出文件为my.json,其中roads为TopoJSON的对象名称。将转换结果格式后,内容如下:
{"type": "Topology","objects": {"roads": {"type": "GeometryCollection","geometries": [{"type": "LineString","arcs": [0,1,2],"properties": {"color": "#FF0000","name": "小鸡村到大鸡村"}},{"type": "LineString","arcs": [3,1,4,5],"properties": {"color": "#00FF00","name": "某立交到狮山公园"}},{"type": "LineString","arcs": [6,1,4,7],"properties": {"color": "#0000FF","name": "某立交到某检测站"}},{"type": "Point","coordinates": [108.355996,22.856245],"properties": {"name": "小鸡村"}},{"type": "Point","coordinates": [108.351785,22.872607],"properties": {"name": "小鸡村"}}]}},"arcs": [[[108.355996,22.856245],[108.355997,22.856245],[108.356491,22.856224],[108.356877,22.856202],[108.357494,22.856175],[108.358438,22.856122],[108.359371,22.85609],[108.35949,22.85609],[108.359774,22.85609],[108.360224,22.856084],[108.360305,22.856084],[108.360573,22.85609],[108.360847,22.8561],[108.360975,22.8561],[108.362107,22.856127]],[[108.362107,22.856127],[108.36273,22.856213],[108.363025,22.856261],[108.363625,22.856433],[108.364033,22.856572],[108.364382,22.856674],[108.364865,22.856862],[108.364999,22.85691],[108.365133,22.856958],[108.366039,22.857388],[108.366286,22.857533],[108.366646,22.857758],[108.36759,22.858434],[108.367692,22.858461],[108.367987,22.858557],[108.368089,22.858563],[108.368148,22.858557],[108.368191,22.858546],[108.368282,22.858514],[108.368351,22.858466],[108.36841,22.858412],[108.368453,22.858348],[108.36848,22.858278],[108.368496,22.858192],[108.368496,22.858128],[108.36847,22.85801],[108.368426,22.857935],[108.368394,22.857886],[108.36833,22.857833],[108.368271,22.857801],[108.36818,22.857768],[108.368062,22.857758],[108.367965,22.857768],[108.36789,22.85779],[108.367842,22.857811],[108.367756,22.85787],[108.367708,22.857924],[108.367606,22.858048],[108.367515,22.858112],[108.36701,22.858949],[108.366672,22.859415],[108.366308,22.859871],[108.366125,22.860065],[108.366007,22.860199],[108.365878,22.860327],[108.365744,22.860461],[108.36509,22.861057],[108.364859,22.861234],[108.364591,22.861422],[108.364511,22.861459],[108.364344,22.861513],[108.363486,22.86206],[108.363153,22.862232],[108.362703,22.862425],[108.362381,22.862548],[108.361769,22.862763],[108.361211,22.862935]],[[108.361211,22.862935],[108.360943,22.863128],[108.360831,22.863149],[108.360471,22.86323],[108.359811,22.863331],[108.359334,22.863385],[108.358937,22.863406],[108.358395,22.863417],[108.358272,22.863417],[108.358111,22.863417],[108.357891,22.863417],[108.357773,22.863417],[108.357038,22.863412],[108.356662,22.863412],[108.356442,22.863417],[108.356453,22.863557],[108.356464,22.863643],[108.35655,22.864597],[108.356603,22.865064],[108.35663,22.865263],[108.356636,22.865365],[108.356759,22.866502],[108.356915,22.867929],[108.356941,22.868331],[108.357,22.869195],[108.356963,22.869329],[108.356915,22.870203],[108.356904,22.870407],[108.356893,22.870622],[108.35685,22.872016],[108.356845,22.872548],[108.356383,22.872601],[108.354361,22.872585],[108.353015,22.872596],[108.35192,22.872606],[108.351785,22.872607]],[[108.362106,22.856127],[108.362107,22.856127]],[[108.361211,22.862935],[108.360734,22.863058],[108.360418,22.863122],[108.359763,22.863224],[108.359468,22.863256],[108.358931,22.863288],[108.358368,22.863299],[108.357789,22.863299],[108.355016,22.863299],[108.354334,22.863315],[108.354114,22.863321],[108.353369,22.863305],[108.352226,22.863305],[108.347543,22.863305],[108.347167,22.863305],[108.346046,22.863305],[108.345478,22.863326],[108.344984,22.863466],[108.343874,22.863439],[108.342994,22.863439],[108.341417,22.863471],[108.340698,22.863471],[108.339695,22.863455],[108.338681,22.863455],[108.337898,22.86346],[108.337415,22.863444],[108.336857,22.863412],[108.33653,22.863364],[108.33631,22.86331]],[[108.33631,22.86331],[108.336261,22.862548],[108.336294,22.86242],[108.336396,22.862081],[108.336535,22.861803],[108.336696,22.861674],[108.336825,22.861529],[108.337216,22.860488],[108.337427,22.859949]],[[108.362105,22.856127],[108.362107,22.856127]],[[108.33631,22.86331],[108.336197,22.863643],[108.336154,22.863761],[108.335945,22.864152],[108.33587,22.86426],[108.335677,22.864785],[108.33558,22.865064],[108.335526,22.865246],[108.335398,22.865799],[108.335109,22.867793]]],"bbox": [108.335109,22.856084,108.368496,22.872607]
}
下载topojson-client
转换成的json文件不能直接在leaflet中使用,需用相应的接口转换成GeoJSON来显示。
本文使用topojson-client展示topojson,使用的文件为topojson-client.min.js。下载地址:https://github.com/topojson/topojson-client,也可以直接打开地址https://unpkg.com/topojson-client获取。本文使用的版本为3.1.0,具体文件为https://unpkg.com/topojson-client@3.1.0/dist/topojson-client.min.js。
展示
引入topojson-client.min.js文件:
<script src="mymap/maptool/topojson-client.min.js"></script>
核心代码如下:
// 找到topoJson第一个对象
function autoConvertTopoToGeo(topoData) {const objectNames = Object.keys(topoData.objects);if (objectNames.length === 0) {throw new Error('TopoJSON 中没有找到任何对象');}const objectName = objectNames[0];return topojson.feature(topoData, topoData.objects[objectName]);
}function showMyTopoSON(inputStr) {try {const topoData = JSON.parse(inputStr);const geoJSON = autoConvertTopoToGeo(topoData);// console.log("dddd", JSON.stringify(geoJSON))currentLayer = drawOneLayer(mymap, geoJSON, {color: "#ff0000", weight: 2})addToLayers(currentLayer);// 自动调整地图视野到恰当的位置mymap.fitBounds(currentLayer.getBounds());return "GeoJSON 加载成功";} catch (error) {return 'GeoJSON 格式错误: ' + error.message;}
}
效果图如下:

可以尝试去掉某些弧段的引用,如:
{"type": "LineString","arcs": [6,7],"properties": {"color": "#0000FF","name": "某立交到某检测站"}},
效果图如下:

小结
本文是根据实际需求而形成的,笔者某工程用到了很多路线,在大量重复的数据,使用TopoJSON优化后,数量体积由15MB下降到9MB,效果还是比较明显的。
代码
文中列出了主要的代码片段,另有相关的工程demo,已上传到github仓库。因不定时更新,代码不一定与文中严格对应,以代码仓库为准。如使用,请自行根据实际情况修改。
仓库地址:https://github.com/latelee/mapdemo 。
本文涉及文件:100.综合示例.html、myroute_topo.json、myroute_geo.json,函数为showMyTopoSON。
