大规模网格地图渲染

场景

需要对全国范围9万多个方形网格(shp格式文件)在地图上进行着色展示。直接使用leaflet操作geojson数据的话,地图加载慢,拖拽、缩放卡顿。

解决方案

解决这个问题的基本思路是:将geojson数据转化为pbf/png文件并发布成服务,leaflet通过加载pbf/png文件渲染地图,提高效率。

  • geojson转化为mbtiles
    通过tippecanoe将geojson格式转换为mbtiles格式。tippecanoe安装:
git clone https://github.com/mapbox/tippecanoe.git
cd tippecanoe
make –j
make install

tippecanoe使用方法:

tippecanoe -o chred.mbtiles -Z4  -z12 -pC  T3.JSON
# -o 输出文件
# -Z 最大缩放等级
# -z 最小缩放等级
# -pC 生成的文件可以被计算机识别
# --drop-densest-as-needed 抽稀
# chred.mbtiles 输出的mbtiles文件名称
# T3.JSON 输入的geojson文件名称

详细使用方法参见:Tippecanoe矢量切片的命令行参数解析

  • mbtiles发布成pbf文件
    mbtiles发布成pbf文件的方法很多,这里使用mbview实现。安装mbview需要安装nodejs等相关依赖。
yum install nodejs
yum update openssl -y
yum install gcc+ gcc-c++
yum install sqlite-devel
yum install zlib
yum install
zlib-devel

安装mbview:

git clone https://github.com/mapbox/mbview.git
cd mbview
npm install 
npm install -g mbview

如果安装过程中,出现无法创建文件夹的错误,可以尝试:

npm install --unsafe-perm
npm install -g mbview --unsafe-perm

安装完成之后,在nodejs/lib/node_modules/mbview/views/ 文件夹下,存在vector.ejs等四个文件是地图前端生成时的模板。其中,vector.ejs文件中可以修改前端的访问地址,可以将localhost替换为服务器地址,实现远程访问。

map.on('load', function () {
  <% Object.keys(sources).forEach(function (sid) { %>
    map.addSource('<%= sid %>', {
      type: 'vector',
      tiles: [
        'http://localhost:3000/<%= sid %>/{z}/{x}/{y}.pbf'
      ],
      maxzoom: <%= sources[sid].maxzoom %>
    });
    <% include layers %>
  <% }); %>
});

此后,可以通过mbview将mbtiles发布为pbf格式文件:

mbview chred.mbtiles

发布成功后,可以在浏览器中访问http://localhost:3000查看发布的地图服务:

部分网格展示

此时,利用leaflet加载pbf文件会还会遇到跨域访问问题,通过修改nodejs/lib/node_modules/mbview/文件夹下的mbview.js文件,添加app.use段,实现跨域访问pbf文件。

app.get('/', function (req, res) {
  if (format === 'pbf') {
    res.render('vector', config);
  } else {
    res.render('raster', config);
  }
});
app.use(function(req, res, next) {
   // 服务端要支持跨域,否则会出现跨域问题
   res.header("Access-Control-Allow-Origin", "*");
   res.header("Access-Control-Allow-Headers",
       "Origin, X-Requested-With, Content-Type, Accept");
   next();
});
  • leaflet加载pbf文件

pbf发布成功后,可以通过leaflet的插件Leaflet.VectorGrid加载,进一步渲染地图:

//pbf网格图层
var gridLayer = L.vectorGrid.protobuf(
    // 加载pbf文件
    'http://49.234.83.212:3000/chred.mbtiles/{z}/{x}/{y}.pbf',
    {
        rendererFactory: L.canvas.tile,
        attribution: "© CHRED",
        interactive: true,
        getFeatureId: function(feature) {
            return feature.properties.id;
        },
        vectorTileLayerStyles: {
            T3JSON: function(feature) { //T3JSON 是转化mbtiles时layer的名称
                return {
                    fill: true,
                    fillColor: getColor(feature.TT),
                    fillOpacity: 0.7,
                    stroke: false, //不显示网格边框线
                    weight: 0.5,
                    color: "#595959",
                    }
                }
            }
        }
    );

一个例子

数据来源于中国高空间分辨率网格数据(China High Resolution Emission Gridded Database,CHRED):

二氧化碳排放清单可视化

共98000多个网格,加载稍慢,拖拽与缩放无压力。
主要代码:

<body>
    <div id="map"></div>
    <script>
        //根据数值大小对网格进行着色
        function getColor(d) {
            return d > 1000000 ? 'rgba(255,34,0,0.8)' :
                d > 100000  ? 'rgba(255,132,0,0.8)' :
                d > 10000  ? 'rgba(255,230,00.8)' :
                d > 1000  ? 'rgba(161,214,0,0.8)' :
                            'rgba(57,156,0,0.8)';
            }

        //创建地图
        var map = L.map('map').setView([40.9828,120.141], 5);

        //加载基础图层
        var baseLasyer = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
            }).addTo(map);
        
        //加载pbf网格图层
        var gridLayer = L.vectorGrid.protobuf(
            // 加载pbf文件,host:主机名,port:端口号,sid:mbtiles文件名
            'http://host:port/sid/{z}/{x}/{y}.pbf',
            {
                rendererFactory: L.canvas.tile,
                attribution: "© CHRED",
                interactive: true,
                getFeatureId: function(feature) {
                    return feature.properties.id;
                },
                vectorTileLayerStyles: {
                    T3JSON: function(feature) { //T3JSON 是转化mbtiles时layer的名称
                        return {
                            fill: true,
                            fillColor: getColor(feature.TT),
                            fillOpacity: 0.7,
                            stroke: false, //不显示网格边框线
                            weight: 0.5,
                            color: "#595959",
                            }
                        }
                    }
                }
            );
        gridLayer.addTo(map);
        
        //鼠标悬浮显示网格信息
        var info = L.control();
        info.onAdd = function (map) {
            this._div = L.DomUtil.create('div', 'info'); 
            this.update();
            return this._div;
            };
        info.update = function (props) {
            this._div.innerHTML = '<h4 style="color">CO2 indicators</h4>' +  (props ?
                '<b>'+'二氧化碳总排放: ' + props.TT.toFixed(0) + 't' + '</b><br />' :
                '鼠标悬停网格区域显示相关信息');
            };
        info.addTo(map);

        function highlightFeature(e) {
            //更新信息
            var props = e.layer.properties;
            info.update(props);
            }
        function resetHighlight(e) {
            //重置信息
            info.update();
            }
        gridLayer.on({
            mouseover: highlightFeature,
            mouseout: resetHighlight,
            });

        //图层控件
        var baseMap = {}
        var overlayMaps = {
            '二氧化碳总排放':gridLayer ,
            }
        L.control.layers(baseMap,overlayMaps).addTo(map);

        //添加常见控件
        L.control.scale({maxWidth:100,metric:true,imperial:false}).addTo(map); // 比例尺控件
        L.control.locate({ // 定位控件
            position: 'topleft',
            strings: {
                title: "Show me where I am, yo!"
            }
            }).addTo(map);
        L.control.browserPrint().addTo(map); //打印控件


        //添加图例控件
        var gridLayerLegend = L.control({ position: "bottomright" });
        gridLayerLegend.onAdd = function(map) {
            var div = L.DomUtil.create("div", "legend"),
                grades = [0,1000, 10000, 100000, 1000000];
            div.innerHTML+='<p style = "font-weight:bold; height:0px;">二氧化碳排放量(吨/年)</p>' + '<br>';
            for (var i = 0; i < grades.length; i++) {
                div.innerHTML +=
                    '<i style="background:' +
                    getColor(grades[i] + 1) +
                    '"></i> ' +
                    grades[i] +
                    (grades[i + 1] ? "&ndash;" + grades[i + 1] + "<br>" : "+");
            }
            return div;
            };
        gridLayerLegend.addTo(map);
    </script>
</body>

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350