阅读视图

发现新文章,点击刷新页面。

rio-tiler在windows中安装使用

之前用rio-tiler,参考rio-viz做了个可以针对多个影像同时提供WTMS服务的小应用,前段时间需要在一台windows服务器上部署,遇到一些问题,记录下来。

rio-tiler依赖于rasterio,rasterio依赖于gdal,在win上安装都有点费劲。

直接使用pip安装rio-tiler会在安装gdal及rasterio时报错,卡住。

最开始的时候在Unofficial Windows Binaries for Python Extension Packages中找到了相对应版本的GDAL和rasterio,安装完成后依然有些问题,如rasterio引入时会报pyproj的CRS定义找不到的错误。

最开始使用conda的默认频道安装,在rio-tiler=2.0.0rc3版本中,依赖的rasterio≥1.1.7,而默认频道中没有该版本。使用conda-forge频道,发现版本比较全。

首先,我是用的miniconda3,体积更小,安装过程也没什么坑,新建环境,激活环境。

conda create -n rasterio python=3.7
conda activate rasterio
conda search rasterio -c conda-forge
Loading channels: done
# Name                       Version           Build  Channel
rasterio                      0.36.0  py27h43d01bd_1  pkgs/main
rasterio                      0.36.0  py35h49e1f75_1  pkgs/main
rasterio                      0.36.0  py36hb8ea33a_1  pkgs/main
rasterio                      1.0.13  py36h6bd7d87_0  pkgs/main
rasterio                      1.0.13  py37h6bd7d87_0  pkgs/main
rasterio                      1.0.18  py36h6bd7d87_0  pkgs/main
rasterio                      1.0.18  py37h6bd7d87_0  pkgs/main
rasterio                      1.0.21  py36h6bd7d87_0  pkgs/main
rasterio                      1.0.21  py37h6bd7d87_0  pkgs/main
rasterio                      1.0.23  py36hbf02ebe_0  conda-forge
rasterio                      1.0.24  py36h163c445_0  conda-forge
rasterio                      1.0.24  py37h163c445_0  conda-forge
rasterio                      1.0.25  py36h163c445_0  conda-forge
rasterio                      1.0.25  py37h163c445_0  conda-forge
rasterio                      1.0.25  py37he350917_1  conda-forge
rasterio                      1.0.26  py36h163c445_1  conda-forge
rasterio                      1.0.26  py37h163c445_1  conda-forge
rasterio                      1.0.27  py36h163c445_0  conda-forge
rasterio                      1.0.27  py37h163c445_0  conda-forge
rasterio                      1.0.28  py36h163c445_0  conda-forge
rasterio                      1.0.28  py36h163c445_1  conda-forge
rasterio                      1.0.28  py36h2617b1b_2  conda-forge
rasterio                      1.0.28  py37h163c445_0  conda-forge
rasterio                      1.0.28  py37h163c445_1  conda-forge
rasterio                      1.0.28  py37h2617b1b_2  conda-forge
rasterio                       1.1.0  py36h039b02d_0  pkgs/main
rasterio                       1.1.0  py36h2617b1b_0  conda-forge
rasterio                       1.1.0  py37h039b02d_0  pkgs/main
rasterio                       1.1.0  py37h2617b1b_0  conda-forge
rasterio                       1.1.1  py36h163c445_0  conda-forge
rasterio                       1.1.1  py36h2617b1b_0  conda-forge
rasterio                       1.1.1  py37h163c445_0  conda-forge
rasterio                       1.1.1  py37h2617b1b_0  conda-forge
rasterio                       1.1.1  py38h163c445_0  conda-forge
rasterio                       1.1.1  py38h2617b1b_0  conda-forge
rasterio                       1.1.2  py36h163c445_0  conda-forge
rasterio                       1.1.2  py37h163c445_0  conda-forge
rasterio                       1.1.2  py37h2617b1b_0  conda-forge
rasterio                       1.1.2  py38h163c445_0  conda-forge
rasterio                       1.1.3  py36h163c445_0  conda-forge
rasterio                       1.1.3  py36h2617b1b_0  conda-forge
rasterio                       1.1.3  py37h163c445_0  conda-forge
rasterio                       1.1.3  py37h2617b1b_0  conda-forge
rasterio                       1.1.3  py38h163c445_0  conda-forge
rasterio                       1.1.3  py38h2617b1b_0  conda-forge
rasterio                       1.1.4  py36h2409764_0  conda-forge
rasterio                       1.1.4  py36ha22ed69_0  conda-forge
rasterio                       1.1.4  py37h02db82b_0  conda-forge
rasterio                       1.1.4  py37h91b820b_0  conda-forge
rasterio                       1.1.4  py38h151dc71_0  conda-forge
rasterio                       1.1.4  py38hef609b1_0  conda-forge
rasterio                       1.1.5  py36h2409764_0  conda-forge
rasterio                       1.1.5  py36ha22ed69_0  conda-forge
rasterio                       1.1.5  py36ha22ed69_1  conda-forge
rasterio                       1.1.5  py37h02db82b_0  conda-forge
rasterio                       1.1.5  py37h02db82b_1  conda-forge
rasterio                       1.1.5  py37h91b820b_0  conda-forge
rasterio                       1.1.5  py38h151dc71_0  conda-forge
rasterio                       1.1.5  py38h151dc71_1  conda-forge
rasterio                       1.1.5  py38hef609b1_0  conda-forge
rasterio                       1.1.6  py36hc1acebe_0  conda-forge
rasterio                       1.1.6  py36hc1acebe_1  conda-forge
rasterio                       1.1.6  py37hce843d0_0  conda-forge
rasterio                       1.1.6  py37hce843d0_1  conda-forge
rasterio                       1.1.6  py38hf2e4ed7_0  conda-forge
rasterio                       1.1.6  py38hf2e4ed7_1  conda-forge
rasterio                       1.1.7  py36hc1acebe_0  conda-forge
rasterio                       1.1.7  py37hce843d0_0  conda-forge
rasterio                       1.1.7  py37hce843d0_1  conda-forge
rasterio                       1.1.7  py38hf2e4ed7_0  conda-forge
rasterio                       1.1.7  py38hf2e4ed7_1  conda-forge
rasterio                       1.1.7  py39h11aa1b2_1  conda-forge
rasterio                       1.1.8  py37hc4b0cd6_0  conda-forge
rasterio                       1.1.8  py38h5653988_0  conda-forge
rasterio                       1.1.8  py39hfec4536_0  conda-forge

之后直接指定版本安装

conda install rasterio=1.1.8

使用conda安装rasterio,顺便也解决了gdal的问题,而且安装过程中也没再遇到其他的问题。

到这一步,一般情况下可能会遇到rio-color装不上的问题,原因是有c++依赖,需要

error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools

这时候,按照提示,使用visual-cpp-build-tools将相关依赖装上即可(勾选以下两项)。

  • MSVC v142 – VS 2019 C++ x64/x86 生成工具
  • Windows 10 SDK (10.0.18362.0)

我在安装好依赖后,还遇到一个问题,大致是"rc.exe"找不到这个执行程序,经过搜索,发现在以下三个目录下都有“rc.exe”,我选择将x64的文件夹(系统为x64)加到了path环境变量里,问题解决。

C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\arm64\
C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x64\
C:\Program Files (x86)\Windows Kits\10\bin\10.0.18362.0\x86\

至此,不出意外的话,使用以下命令就能将rio-tiler装好了。

pip install rio-tiler --pre

图层控制几点问题 Openlayers[2]

openlayers图层控制其实挺易用,使用图层visibility的getter和setter就能实现。但如果考虑组件的绑定,树状控制和同个图层不同属性的控制,问题就麻烦了。

图层与控制器的绑定

首先,怎么实现图层和组件控制器的键值绑定就很棘手,图层数量不多的情况还可以手动的添加图层,一一的将图层和开关绑定。但图层多起来显得就没那么优雅了,维护起来也相当麻烦。

我的解决方法是这样,首先对图层没有固定的id的问题,在加载图层时,为其添加name属性:

let testLayer = new VectorLayer({
    visible: true,
    name: 'test-layer',
    source: testSource
})

有了唯一name之后,可以遍历地图中的图层,找到期望的图层,今而对其操作。

map.getLayers().forEach((layer) => {
    let layerName = layer.values_.name;
		if (layerName !== undefined && layerName === "test-layer" {
        console.log(layer)
	}
});

这样只需将自定的图层名称与控制器绑定,就可以使用控制器的状态来同步图层的显示状态了。

树状控制器

树状控制器不像单一的控制,有父节点和子节点两种,父节点控制整个图层组的显示状态,子节点控制其单个图层,但也受父节点的控制。

一开始的时候我的想法是在id中设置标志符,区分子节点或是父节点,后来一想其实是没必要的,因为对于独立的几个图层来说,只要控制好子节点的状态,组件会自动更新父节点的状态。

例如,在我使用的antd组件中,每次check事件都会产生一个已选项目的id集合,这个集合也就标志着当前地图应该显示的图层,这时候我们只要把集合中包含的,显示状态为隐藏的图层激活显示,将集合中未包含的,显示状态为激活显示的图层隐藏即可,结合之前图层的遍历方法。

function setVisible(keyset) {
//			keyset = ['layer1','layer2',...]
        map.getLayers().forEach(function (layer) {
            let layerName = layer.values_.name;
//			把集合中包含的,显示状态为隐藏的图层激活显示
            if (layerName !== undefined && keyset.includes(layerName)) {
                if (!layer.getVisible()) {
                    layer.setVisible(true);
                }
//			将集合中未包含的,显示状态为激活显示的图层隐藏
            } else if (layerName !== undefined) {
                if (layer.getVisible()) {
                    layer.setVisible(false);
                }
            }
        });
    }

同一图层不同属性要素的控制

对于矢量图层

采用的是更新style的方式,和在前文Openlayers[1]中提到的方法,给未设置显示的要素设置透明的symbol。

以字段 name 为例 ,names为需要显示要素的对应属性值的集合。

export function createStyleDisplay(names) {
    return function (feature) {
        const type = feature.get('name');
        const display = names.includes(type);
        if (display) {
            return new Style({
                stroke: new Stroke({
                    color: '#ffcc33',
                    width: 0
                }),
                image: new Icon({
                    //color: [113, 140, 0],
                    size: 0,
                    crossOrigin: 'anonymous',
                    //  对应类型的个性图标
                    src: `/label/${type}.png`
                })
            })
        } else {
  			//... 透明样式
        }
    }
}

对于栅格图层

我使用的是GeoServer作为服务端,对于栅格瓦片图层,可以采用更新请求参数的方法。

使用cql_filter ,GeoServer有官方说明[link]:对于图层控制的需要,可使用以下语法:

test_field IN ('type1','type2',...,'typeN')

对于一个当前显示的类型集合,应用图层改变示例如下:

export function doCQL(src, field_name, keys) {
    let ql = `${field_name} IN (`
    for (let k in keys) {
        ql += `'keys[k]', `
    }
    ql = ql.substr(0, ql.length - 2)
    ql += ')'
    let pms = src.getParams()
    pms.CQL_FILTER = ql
    src.updateParams(pms)
}

后话

在图层属性中添加name字段其实是无奈之举,没有getter,setter,只是强行从图层对象中获取。但没找到适应以下情况的方法:既可以将图层与唯一ID绑定,又可以通过ID直接获取图层,从而对其操作。

当前对图层操作中,需要当前地图的图层集合来选定某图层,并不是很高效。或许可以建一个hash表,key -> layer,有机会会在下个项目中尝试一下。希望大家如果有更好的解决方法的话,可以与在留言报分享😘。

Terrocotta :一个轻量的瓦片服务

Introduction

最近一直想找一个轻量的影像瓦片的服务端,上周一直在看@Vincent Sarago 基于其自己一套工具 rio-tiler , lambda-proxy 的的瓦片服务的provider,前前后后看了衍生的几个项目,包括lambda-tilerlandsat-tilerrio-viz等等,经过简单的测试,感觉前两个工具均需要借助lambda才能发挥正常的性能,第三个应用框架用的tornado,考虑了并发问题,但是个单机应用,“移植”起来工程量挺大的,自己试了试放弃了。在单节点的情况下,请求阻塞问题非常严重,自己试着换了几个应用和服务端的组合,都没太大的改善。另外在单节点情况下,这种每个请求都要重新访问一次数据的方式并不经济。

简单的应用不行,在COG详情页看到了,Geotrellis项目,框架用scala实现的,在projects里发现了一个和需求很相近的实验项目,clone下来运行,并不能成功,好像是应用入口有变化,失败了,自己懒的上手改(不知道怎么改),就想着去quick start 里找个小例子,跑个tiles服务应该挺容易的(呸),scala在国内的新手使用体验是真的难,甚至比golang还难,构建工具sbt从maven中心仓库拉文件,乌龟似的启动速度,自己找了那寥寥无几的几篇更换国内源的方法,中间一度想吐🤮,最后换了华为云的源终于能接受了,sbt.build的诡异语法,硬着头皮坚持到io影像,最新版本的api根本跟docs大不一样了,自己照着api东改西改,又被魔鬼般的implict参数坑了十几分钟后:

$ rm -rf repos/geotrellis-test ~/.sbt
$ brew rmtree sbt

溜了溜了。

Terracotta

Github的feed真是个好东西,替我推荐了好多有用的玩意,Terracotta也是(太难打了,就叫他陶罐吧)。官方描述如下:

Terracotta is a pure Python tile server that runs as a WSGI app on a dedicated webserver or as a serverless app on AWS Lambda. It is built on a modern Python 3.6 stack, powered by awesome open-source software such as Flask, Zappa, and Rasterio.

提供传统部署和Lambda两种方式,轻量,pure python,都挺符合我的口味,“技术栈”也相对新。

陶罐与同样基于函数计算的lambda-tiler相比,不管是从结构来讲,或是理解起来,都是后者更简单。后者的整个流程非常直接,基于COG的portion请求特性和GDAL的VFS(Virtual File Systems),不管你的数据在哪,多大,只要告诉我它数据的本地地址或者HTTP地址,它就可以实时的拉取切片。在lambda的环境下,这种方式在性能上不会有太大问题。但对于在国内使用、部署有两个问题。

  • AWS在国内严重水土不服,给国内使用Lambda造成障碍,Aliyun等国内厂商也有函数计算的服务,但还不太成熟,移植proxy等成本也很高。
  • 一些open access 的数据比如Landsat 8Sentinel-2都托管在S3对象存储上,使用Lambda切片很大程度依赖在AWS各部件上快速访问,但如果在国内提供服务在访问速度上会受很大的影响。

当然,陶罐也是推荐部署在Lambda函数上的,的确,这种方式非常适合动态切片服务,但比Lambda-tiler,它加了一个易用、可靠的头文件的“缓存机制”。

在使用rio-tiler想实现一个可以快速部署在单机上、支持少用户,低请求的动态切片服务时,就曾经想在内存中对同源的数据的头文件缓存下来,因为每一张瓦片都要请求一次源数据获取头文件,在单机环境来看是很浪费的,当时自己的想法有建一个dict,根据数据源地址存储头文件或者建一个sqlite数据库来存储,试了建个dict的方式,但效果并不明显。

而陶罐在业务流程设计上就强制加入了这一点,这使得他在新增数据时会有一个预处理的过程,这比起直接处理有一个延后,但正所谓磨刀不误砍柴工,不得不说,这比起传统的预切片可要快出不少。

除此之外,对数据cog化,头文件注入等流程,陶罐都有很好的api支持。

Quick Start

试用非常简单,先切换到使用的环境,然后

$ pip install -U pip
$ pip install terracotta

查看一下版本

$ terracotta --version
$ terracotta, version 0.5.3.dev20+gd3e3da1

进入存放tif的目标文件夹,以cog格式对影像进行优化。

$ terracotta optimize-rasters *.tif -o optimized/

然后将希望serve的影像根据模式匹配存进sqlite数据库文件。

这里想吐槽一下这个功能,开始的时候我以为是一般的正则匹配,搞半天发现是{}的简单匹配,还不能不使用匹配,醉醉哒。

$ terracotta ingest optimized/LB8_{date}_{band}.tif -o test.sqlite

注入数据库完成后,启动服务

$ terracotta serve -d test.sqlite

服务默认在:5000启动,还提供了Web UI,需要另行启动,开另一个session:

$ terracotta connect localhost:5000

这样Web UI也就启动了。这样可以在提示的地址中访问到了。

Deployment

没看lambda的部署方式,因为大致和lambda-tiler方式差不多,因为国内aws访问半身不遂,移植到阿里云,腾讯云的serverless的成本又太高了,所以才放弃了这种方式。

传统的部署方式如下:

我是在centos的云主机上部署的,和docs里的大同小异。

首先新建环境,安装软件和依赖。

$ conda create --name gunicorn
$ source activate gunicorn
$ pip install cython
$ git clone https://github.com/DHI-GRAS/terracotta.git
$ cd /path/to/terracotta
$ pip install -e .
$ pip install gunicorn

准备数据,例子假设影像文件存放在/mnt/data/rasters/

$ terracotta optimize-rasters /mnt/data/rasters/*.tif -o /mnt/data/optimized-rasters
$ terracotta ingest /mnt/data/optimized-rasters/{name}.tif -o /mnt/data/terracotta.sqlite

新建服务,这里自己踩了两个坑,官方例子使用的是nginx反向代理到sock的方式,自己试了多个方法,没成功,也不想深入了解了。

server {
    listen 80;
    server_name VM_IP;

    location / {
        include proxy_params;
        proxy_pass http://unix:/mnt/data/terracotta.sock;
    }
}

另一个是,应用入口里的入口 版本更新过,service里的和上下文的不一样,修改之后如下

[Unit]
Description=Gunicorn instance to serve Terracotta
After=network.target

[Service]
User=root
WorkingDirectory=/mnt/data
Environment="PATH=/root/.conda/envs/gunicorn/bin"
Environment="TC_DRIVER_PATH=/mnt/data/terracotta.sqlite"
ExecStart=/root/.conda/envs/gunicorn/bin/gunicorn \
            --workers 3 --bind 0.0.0.0:5000  -m 007 terracotta.server.app:app

[Install]
WantedBy=multi-user.target

另外一个地方,使用"0.0.0.0",使外网可以访问。

官方解释如下:

  • Absolute path to Gunicorn executable
  • Number of workers to spawn (2 * cores + 1 is recommended)
  • Binding to a unix socket file terracotta.sock in the working directory
  • Dotted path to the WSGI entry point, which consists of the path to the python module containing the main Flask app and the app object: terracotta.server.app:app

服务里需要指定Gunicorn的执行路径,设置workers数量,绑定socket file,指定应用入口。

设置开机启动,启动服务。

$ sudo systemctl start terracotta
$ sudo systemctl enable terracotta
$ sudo systemctl restart terracotta

这样就能看到服务的表述了。

$ curl localhost:5000/swagger.json

当然,也可以用terracotta自带的client来看一下效果:

$ terracotta connect localhost:5000

Workflow

对与头文件存储方式的选择,sqlite自然是更方便,但mysql的灵活性和稳定性更高了,在线数据可以实现远程注入。

这里碰到点问题,driver的create方法新建失败,自己没看出问题在哪,就从driver里找出表定义,手动新建所需表。

from typing import Tuple

import terracotta as tc
import pymysql


# driver = tc.get_driver("mysql://root:password@ip-address:3306/tilesbox'")
key_names = ('type', 'date', 'band')
keys_desc = {'type': 'type', 'date': 'data\'s date', 'band': 'raster band'}

_MAX_PRIMARY_KEY_LENGTH = 767 // 4  # Max key length for MySQL is at least 767B
_METADATA_COLUMNS: Tuple[Tuple[str, ...], ...] = (
    ('bounds_north', 'REAL'),
    ('bounds_east', 'REAL'),
    ('bounds_south', 'REAL'),
    ('bounds_west', 'REAL'),
    ('convex_hull', 'LONGTEXT'),
    ('valid_percentage', 'REAL'),
    ('min', 'REAL'),
    ('max', 'REAL'),
    ('mean', 'REAL'),
    ('stdev', 'REAL'),
    ('percentiles', 'BLOB'),
    ('metadata', 'LONGTEXT')
)
_CHARSET: str = 'utf8mb4'
key_size = _MAX_PRIMARY_KEY_LENGTH // len(key_names)
key_type = f'VARCHAR({key_size})'

with pymysql.connect(host='ip-address', user='root',
                     password='password', port=3306,
                     binary_prefix=True, charset='utf8mb4', db='tilesbox') as cursor:
    cursor.execute(f'CREATE TABLE terracotta (version VARCHAR(255)) '
                   f'CHARACTER SET {_CHARSET}')

    cursor.execute('INSERT INTO terracotta VALUES (%s)', [str('0.5.2')])

    cursor.execute(f'CREATE TABLE key_names (key_name {key_type}, '
                   f'description VARCHAR(8000)) CHARACTER SET {_CHARSET}')
    key_rows = [(key, keys_desc[key]) for key in key_names]
    cursor.executemany('INSERT INTO key_names VALUES (%s, %s)', key_rows)

    key_string = ', '.join([f'{key} {key_type}' for key in key_names])
    cursor.execute(f'CREATE TABLE datasets ({key_string}, filepath VARCHAR(8000), '
                   f'PRIMARY KEY({", ".join(key_names)})) CHARACTER SET {_CHARSET}')

    column_string = ', '.join(f'{col} {col_type}' for col, col_type
                              in _METADATA_COLUMNS)
    cursor.execute(f'CREATE TABLE metadata ({key_string}, {column_string}, '
                   f'PRIMARY KEY ({", ".join(key_names)})) CHARACTER SET {_CHARSET}')

瓦罐的头文件存储共需要四个表。

Table Describe
terracotta 存储瓦罐版本信息
metadata 存储数据头文件
Key_names key类型及描述
Datasets 数据地址及(key)属性信息

服务启动修改如下:

[Unit]
Description=Gunicorn instance to serve Terracotta
After=network.target

[Service]
User=root
WorkingDirectory=/mnt/data
Environment="PATH=/root/.conda/envs/gunicorn/bin"
Environment="TC_DRIVER_PATH=root:password@ip-address:3306/tilesbox"
Environment="TC_DRIVER_PROVIDER=mysql"

ExecStart=/root/.conda/envs/gunicorn/bin/gunicorn \
            --workers 3 --bind 0.0.0.0:5000  -m 007 terracotta.server.app:app

[Install]
WantedBy=multi-user.target

对于注入本地文件,可参照如下方法:

import os
import terracotta as tc
from terracotta.scripts import optimize_rasters, click_types
import pathlib

driver = tc.get_driver("/path/to/data/google/tc.sqlite")
print(driver.get_datasets())

local = "/path/to/data/google/Origin.tiff"
outdir = "/path/to/data/google/cog"
filename = os.path.basename(os.path.splitext(local)[0])
seq = [[pathlib.Path(local)]]
path = pathlib.Path(outdir)
# 调用click方法
optimize_rasters.optimize_rasters.callback(raster_files=seq, output_folder=path, overwrite=True)

outfile = outdir + os.sep + filename + ".tif"

driver.insert(filepath=outfile, keys={'nomask': 'yes'})

print(driver.get_datasets())

运行如下

Optimizing rasters:   0%|          | [00:00<?, file=Origin.tiff]

Reading:   0%|          | 0/992
Reading:  12%|█▎        | 124/992
Reading:  21%|██▏       | 211/992
Reading:  29%|██▉       | 292/992
Reading:  37%|███▋      | 370/992
Reading:  46%|████▌     | 452/992
Reading:  54%|█████▍    | 534/992
Reading:  62%|██████▏   | 612/992
Reading:  70%|██████▉   | 693/992
Reading:  78%|███████▊  | 771/992
Reading:  87%|████████▋ | 867/992

Creating overviews:   0%|          | 0/1

Compressing:   0%|          | 0/1
Optimizing rasters: 100%|██████████| [00:06<00:00, file=Origin.tiff]
{('nomask',): '/path/to/data/google/nomask.tif', ('yes',): '/path/to/data/google/cog/Origin.tif'}

Process finished with exit code 0

稍加修改就可以传入input 文件名 和 output的文件夹名,就能实现影像优化、注入的工作流。

Reference

矢量瓦片高亮选中 Openlayers[1]

自己做的小Demo中有这样一个小需求:通过数据库检索,获取指定属性的要素,然后高亮显示。

如果采用WFS常规方式加载,不存在问题,遍历layerfeature source即可,不考虑效率,逻辑是没有问题的,但有个需求是图层feature非常多(因为是全球范围海岸线生成的缓冲区),所以地图加载的过程中使用了矢量瓦片的形式,矢量瓦片类型的source没有getFeatures()方法,给需要高亮显示的要素进行遍历造成了麻烦。

图层的静态样式使用openlayers最新例子的方式设置:

//颜色表
const colorTable = {
  "No": "rgba(200, 54, 54, 0)",
  "type1": "#FF0000",
  "type2": "#E69800",
  ...
  "": "#00FFFFFF",
};
export function createRiskLevelStyle(feature) {

  const riskLevel = feature.get('props_X');
  let selected = !!selection[feature.getId()];
  return new Style({
    fill: new Fill({
      //color: colorTable[riskLevel],
      color: selected ? 'rgba(200,20,20,0.8)' : colorTable[riskLevel],
      //color:
    }),
    stroke: new Stroke({
      color: '#FFFFFF',
      width: 0.1,
    })
  })
}

其中selected用于鼠标点击的高亮显示,逻辑大概是点击后将以featureid作为键值存储,标识该要素被选中。

自然的在考虑这个需求的时候,我的首先想法是遍历featureCollection找到相应的要素对应的Id,存进selection全局变量中。但因为矢量瓦片的source没有getFeatures()方法所以这个想法就破产了。之后甚至想再新建一个普通的WFS层用来遍历数据,但数据量实在太大了,一次加载要50几M,这种方式也就彻底破产了。

之后,考虑到既然加载的时候样式可以用这种形式的styleFunc,在检索的时候,给图层赋新的Func会不会有效呢,性能又如何?于是对styleFun微调后如下:

export function createRiskLevelStyleSearch(names) {
    return function (feature) {
        const riskLevel = feature.get('props_X')
        let properties = feature.getProperties()
        let zh = properties['label']
        let en = properties['name']
        let searched = false
        if (zh === undefined || en === undefined) {
            searched = false
        } else {
            names.forEach((v) => {
                if (en === v.key) searched = true
            })
        }
        return new Style({
            fill: new Fill({
                //color: colorTable[riskLevel],
                color: searched ? 'rgba(200,20,20,0.8)' : colorTable[riskLevel]
                //color:
            }),
            stroke: new Stroke({
                color: '#FFFFFF',
                width: 0.1
            })
        })
    }
}

参数names是一个国家名的数组,itemkey对应需要检索的值。

这种方法在这个数据量下,效果还可以,如下图:

❌