需求
实现相邻省份组合边界线绘制
- 基础:全国各地的geojson文件、中国边境线geojson文件
- 多相邻省份组合需要用到
turf
这个插件,核心实现 - 同组需要遍历好每一份数据(geojson的省份可能包含岛屿,边界线绘制不包含岛屿,因为是不相干的,无法连接)
填充对应的背景
- 绘制底图层级需要对
ExtrudeGeometry
计算包围盒,不然背景无法正确安置 - 指定每个组或者全局的图片背景的话,如果图片是复杂图片,需要正确安放(大小,角度)(本文章内不考虑这个需求,实际实现用的复杂渐变)
- 指定每个组或者全局的背景为实色(本文章内不考虑这个需求,过于简单,自行AI)
- 指定每个组或者全局的背景为复杂渐变,
核心实现是通过canvas控制大小角度,以及颜色渐变
的处理
实现(实现仅描述对应逻辑的实现,目前不会有完整实现案例)
- 以函数中return
layer可用的meshGroup为准
,在layer中使用
省份组描述
- 每个组内包含
adcode
数组,包含相邻省份的adcode(geojson内) - map是如果要实现每个组的不同地图背景,就开放这个参数,到时候遍历内使用
jsx
// 省份组(目前只实现南方)(相邻省份为一组)
export const provinceGroups = {
south: [
{
adcodes: [210000, 220000, 230000],
// map: provinceGroupBg,
},
{
adcodes: [110000, 120000],
},
{
adcodes: [
530000,
510000,
810000,
820000,
330000,
360000,
450000,
320000,
440000,
410000,
420000,
370000,
310000,
520000,
500000,
340000,
350000,
430000,
],
// map: provinceGroupBg,
},
{
adcodes: [710000],
},
{
adcodes: [460000],
},
],
land: [
{
adcodes: [710000],
},
{
adcodes: [460000],
},
],
};
geojson数据结构描述
数据量过大,自行检索获取
省份组json
json
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"adcode": 110000,
"name": "北京",
"center": [116.405285, 39.904989],
"centroid": [116.41995, 40.18994],
"childrenNum": 16,
"level": "province",
"parent": { "adcode": 100000 },
"subFeatureIndex": 0,
"acroutes": [100000]
},
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[117.348611, 40.581141],
[117.389879, 40.561593],
[117.429915, 40.576141],
[117.412669, 40.605226],
[117.467487, 40.649738],
[117.467487, 40.649738],
]
]
]
}
},
{
...more features
}
]
}
边境线的json跟省份的差不多,只是在coordinates
中的内部数组里包含了所有的点位(组合后的可以理解为)
插件相关
node
npm i @turf/turf @turf/union
实现基础3D地图
- meshGroup:
jsx
const mapGroup = new THREE.Group();
- 3D地图绘制主函数
jsx
import * as THREE from 'three';
/**
* 绘制 3D 地图
* @param {Object} topFaceMaterial 地图表面颜色材质
* @param {'all' | 'south'} type 全部地区、南方地区(自定义地区)
* @param {File} basicMap 全局的地图背景图片
* @param {Boolean} isAnimateSide 是否开启边界(有一定高度后的边界面)的动画
* @param {Boolean} isPrivinceMap 是否为渲染部分地区的map(type为准)
* @param {Boolean} isProvinceStorke 是否为渲染部分地区的map(type为准)自定义省份的边界线
* @returns {Object} mapGroup 3D地图
*/
/*
基础层级描述:
config.defaultOptions.depth(基准层级高度) + 0.001 底图背景 (可选)
+ 0.2 省市区分界线
+ 0.4 省名字
+ 0.42 边境滚动线 ( 可选
*/
/*
// used:
await onInit3DMap({
isAnimateSide: true,
isStorke: false,
// basicMap: map3dBasicMap,
isPrivinceMap: true,
isProvinceStorke: true,
type: mapType,
});
*/
export async function onInit3DMap(params = {}) {
const {
isStorke = true,
basicMap,
topFaceMaterial = new THREE.MeshBasicMaterial({
color: new THREE.Color('rgba(3,43,56,0.2)'),
transparent: true,
opacity: 1,
}),
isAnimateSide,
type = 'all',
isPrivinceMap = false,
isProvinceStorke = false,
} = params;
// 创建边界的材质
let sideMaterial = new THREE.MeshBasicMaterial({
color: 0x07152b,
transparent: true,
opacity: 1,
});
if (isAnimateSide) {
// 创建开启动画的材质
sideMaterial = await createSideMaterial();
}
// 拿到所有组flat之后的Feature集合(详见下方函数描述)
const res = await getProvinceGroupPoint(type);
//整个3D地图的组,所有的层级都在这里
const mapGroup = new THREE.Group();
mapGroup.position.copy(new THREE.Vector3(0, 0, 0.06));
const extrudeSettings = {
depth: config.defaultOptions.depth // 基础层级,自行设定,
bevelEnabled: true,
bevelSegments: 1,
bevelThickness: 0.1,
};
res.forEach((feature, groupIndex) => {
let { name, center = [], adcode } = feature.properties;
//相当于每一个省的地图集合
const group = new THREE.Group();
group.name = 'meshGroup' + groupIndex;
group.userData = {
index: groupIndex,
name,
center,
centroid: feature.properties.centroid || feature.properties.center,
adcode,
childrenNum: feature.properties.childrenNum,
};
// 省份的边境线组
let lineGroup = new THREE.Group();
lineGroup.name = 'lineGroup' + groupIndex;
lineGroup.userData.index = groupIndex;
lineGroup.userData.adcode = adcode;
// 表面材质以及side的材质组合
let materials = [topFaceMaterial.clone(), sideMaterial];
(['内蒙古'].includes(feature.properties.name)
? [feature.geometry.coordinates]
: feature.geometry.coordinates
).forEach((multiPolygon) => {
multiPolygon.forEach((polygon) => {
const shape = new THREE.Shape();
for (let i = 0; i < polygon.length; i++) {
if (!polygon[i][0] || !polygon[i][1]) {
return false;
}
// 根据geojson的经纬度计算投影点位映射返回的x,y点位
const [x, y] = geoProjection({
center: config.defaultOptions.pointCenter,
args: polygon[i],
});
if (i === 0) {
shape.moveTo(x, -y);
}
shape.lineTo(x, -y);
}
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
// 点位连线绘制完成之后创建3D网格对象
const mesh = new THREE.Mesh(geometry, materials);
mesh.userData.depth = config.defaultOptions.depth;
mesh.userData.name = name;
mesh.userData.adcode = adcode;
group.add(mesh);
});
// 省边界线绘制
const points = [];
let line = null;
// 只拿第一个组的点位绘制,像海南有群岛的情况会有多个组,只拿第一个也就是最大的点位组绘制
multiPolygon[0].forEach((item) => {
const [x, y] = geoProjection({ center: config.defaultOptions.pointCenter, args: item });
points.push(new THREE.Vector3(x, -y, 0));
const lineMaterial = new THREE.LineBasicMaterial({ color: 0x6cf9f9 });
line = createLine(points, lineMaterial);
});
lineGroup.add(line);
});
// 边界线的组设定高度,添加到地图组
lineGroup.position.set(0, 0, config.defaultOptions.depth + 0.2);
group.add(lineGroup);
// 基础板块地图绘制完毕
mapGroup.add(group);
})
}
这时候绘制出来的就是这么个东西:

箭头处是关于createSideMaterial
函数开启之后的纵深影响位置
现在关于地图的合成,核心逻辑还是通过featrue的组合将省份依次拼接的,但是如果要绘制相邻省份组合之后的边界线,亦或者是给组合的这一大块地图设置一个背景色,图片的话,仅靠拼接就不太够了,这时候就需要用到turf
这个插件来协助了:
实现相邻省份组合边界线绘制以及国境线绘制
边界线绘制的话,一般会使用new THREE.LineBasicMaterial
方法。但是该方法并不支持设置宽度,也就是说宽度恒定为1
那么如何拓宽呢? 最终的实现方案还是需要依赖THREE.Mesh
来用细长的矩形伪装成线来实现自定义宽度:
onInit3DMap
函数内追加如下内容:
JSX
// 开启国境线绘制
if (isStorke) {
const meshs = await createStorke(type);
mapGroup.add(...meshs);
}
// 开启省份边界线绘制
if (isProvinceStorke) {
const meshs = await createStorke(type, 'single');
mapGroup.add(...meshs);
}
核心实现(这里的全国边境线绘制使用了图片+动画,做出流线型效果):
createStorke
:
JS
/**
*
* @param {'all' | 'south' | '...待追加'} type 全国或者是指定的Group组
* @param {'total' | 'single'} stokeType 线类型 total是全国边境线动画+图片,single为省份边界线(加粗)
* @returns Mesh[]
*/
async function createStorke(type = 'all', stokeType = 'total') {
let material;
// 全局的话
if (stokeType === 'total') {
const texture = await config.onLoaderTexture(require('@/Fudge/assets/pathLine2.png'));
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(1, 1);
// 开启动画
texture.tween1 = new TWEEN.Tween({ y: 0 })
.to({ y: config.defaultOptions.depth }, 10000)
.onUpdate((params) => {
let { y } = params;
texture.offset.x += 0.003;
})
.repeat(Infinity)
.yoyo();
texture.tween1.start();
// 材质创建
material = new THREE.MeshBasicMaterial({
color: 0x92e5ff,
map: texture,
alphaMap: texture,
fog: false,
transparent: true,
opacity: 1,
blending: THREE.AdditiveBlending,
});
} else if (stokeType === 'single') {
// 创建基础材质,纯色的
material = new THREE.MeshBasicMaterial({ color: '#80c5f1' });
}
// 点位组记录Ars
let pathPointGroups = [];
if (type === 'all') {
// 直接拿全国边境线geojson
const res = getProvinceStorkeData();
let pathPoint = [];
res.features.forEach((path) => {
path.geometry.coordinates.forEach((cord) => {
cord[0].forEach((item) => {
let [x, y] = geoProjection({ center: config.defaultOptions.pointCenter, args: item });
pathPoint.push(new THREE.Vector3(x, -y, 0));
});
});
});
pathPointGroups = [pathPoint];
} else {
if (stokeType === 'single') {
// 拿每一个省份的边界线信息
const groupsJsonData = getProvinceGroupPoint(type);
groupsJsonData.forEach((feature) => {
const pathPoint = [];
feature.geometry.coordinates[0].forEach((cord) => {
cord.forEach((item) => {
let [x, y] = geoProjection({ center: config.defaultOptions.pointCenter, args: item });
pathPoint.push(new THREE.Vector3(x, -y, 0));
});
});
pathPointGroups.push(pathPoint);
});
} else {
// 获取到合并后的边境线组,会有多个
const [_, __, points] = getOtherProvincesShape(type);
pathPointGroups = points;
}
}
const meshs = [];
// 遍历组生成每一份mesh的线信息
pathPointGroups.map((p) => {
if (p) {
const curve = new THREE.CatmullRomCurve3(p);
const tubeGeometry = new THREE.TubeGeometry(curve, 256 * 10, 0.15, 4, false);
const mesh = new THREE.Mesh(tubeGeometry, material);
mesh.position.set(0, 0, config.defaultOptions.depth + 0.42);
meshs.push(mesh);
}
});
return meshs;
}
出来就是这样子的:

颜色,图片均可自定义。
填充相邻省份组合块的背景
onInit3DMap
函数内追加如下内容:
JSX
if (isPrivinceMap) {
const meshs = await createBasicMap({ mapUrl: false, hasGlobal: false, otherShapeType: type });
mapGroup.add(...meshs);
}
createBasicMap
函数核心实现:
JSX
/**
*
* @param {boolean} hasGlobal 是否为大陆设置背景底图
* @param {'south'} otherShapeType 指定绘制区域的类型
* @returns mesh[]
*/
async function createBasicMap(params = {}) {
const { hasGlobal = true, otherShapeType } = params;
let shapes = [],
maps = [];
if (hasGlobal) {
const shape = new THREE.Shape();
const res = getProvinceStorkeData();
res.features.forEach((path) => {
path.geometry.coordinates.forEach((cord) => {
cord[0].forEach((item, index) => {
let [x, y] = geoProjection({ center: config.defaultOptions.pointCenter, args: item });
if (index === 0) {
shape.moveTo(x, -y);
}
shape.lineTo(x, -y);
});
});
});
// 添加海南,台湾岛屿(边界限跟大陆划不在一块儿)
const [otherShapes] = getOtherProvincesShape('land');
shapes = [...shapes, shape, ...otherShapes];
}
if (otherShapeType) {
const [otherShapes, otherMaps] = getOtherProvincesShape(otherShapeType);
shapes = otherShapes;
maps = otherMaps;
}
let meshs = [];
for (let i = 0; i <= shapes.length - 1; i++) {
const s = shapes[i];
const extrudeSettings = getBasicMapGeometrySettings(s);
const geometry = new THREE.ExtrudeGeometry(s, extrudeSettings);
let texture;
// 这里的图片就是从provinceGroups对象内提供的,可选
if (maps[i]) {
texture = await config.onLoaderTexture(maps[i]);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(0.3, 0.7); // 缩小尺寸,自行设置
} else {
// 渐变色纹理
const canvas = getCanvasColorBg();
texture = new THREE.CanvasTexture(canvas);
}
// 声明纹理为 sRGB 颜色空间 重要!
texture.encoding = THREE.sRGBEncoding;
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.DoubleSide,
transparent: true,
opacity: 1,
});
const m = new THREE.Mesh(geometry, material);
m.renderOrder = 999;
m.position.z = config.defaultOptions.depth + 0.001;
meshs.push(m);
}
return meshs;
}
实现之后的效果见下图(为了展示效果把先前的实现先藏起来):

可以看到是有五个组分别进行了颜色的设置,provinceGroups
对象对应
相关引入函数,方法
provinceGroups对象
主要用于整合相邻省份组的信息,map的信息也是在这里进行添加
JSX
// 省份组(目前只有南方)(相邻省份为一组)
export const provinceGroups = {
south: [
{
adcodes: [210000, 220000, 230000],
// map: provinceGroupBg,
},
{
adcodes: [110000, 120000],
},
{
adcodes: [
530000,
510000,
810000,
820000,
330000,
360000,
450000,
320000,
440000,
410000,
420000,
370000,
310000,
520000,
500000,
340000,
350000,
430000,
],
// map: provinceGroupBg,
},
{
adcodes: [710000],
},
{
adcodes: [460000],
},
],
land: [
{
adcodes: [710000],
},
{
adcodes: [460000],
},
],
};
geoProjection
JS
import { geoMercator } from 'd3-geo';
function geoProjection({ center = [108.55, 34.32], args }) {
return geoMercator().center(center).scale(120).translate([0, 0])(args);
}
createLine
js
export function createLine(points, lineMaterial) {
const geometry = new THREE.BufferGeometry();
geometry.setFromPoints(points);
let line = new THREE.LineLoop(geometry, lineMaterial);
line.renderOrder = 2;
line.name = 'mapLine';
return line;
}
createSideMaterial
jsx
async function createSideMaterial() {
const sideMap = await config.onLoaderTexture(require('@/assets/side.png'));
// 设置大小,方位
sideMap.wrapS = THREE.RepeatWrapping;
sideMap.wrapT = THREE.RepeatWrapping;
sideMap.repeat.set(1, 0.3);
sideMap.offset.y += 0.01;
let sideMaterial = new THREE.MeshStandardMaterial({
// color: 0x62c3d1,
color: 0xffffff,
map: sideMap,
fog: false,
transparent: true,
opacity: 1,
side: THREE.DoubleSide,
});
sideMaterial.name = 'bgSideMaterial';
// 动画逻辑:
sideMaterial.onBeforeCompile = (shader) => {
shader.uniforms = {
...shader.uniforms,
uColor1: { value: new THREE.Color(0x6cf9f9) },
uColor2: { value: new THREE.Color(0x6cf9f9) },
};
shader.vertexShader = shader.vertexShader.replace(
'void main() {',
`
attribute float alpha;
varying vec3 vPosition;
varying float vAlpha;
void main() {
vAlpha = alpha;
vPosition = position;
`,
);
shader.fragmentShader = shader.fragmentShader.replace(
'void main() {',
`
varying vec3 vPosition;
varying float vAlpha;
uniform vec3 uColor1;
uniform vec3 uColor2;
void main() {
`,
);
shader.fragmentShader = shader.fragmentShader.replace(
'#include <opaque_fragment>',
/* glsl */ `
#ifdef OPAQUE
diffuseColor.a = 1.0;
#endif
// https://github.com/mrdoob/three.js/pull/22425
#ifdef USE_TRANSMISSION
diffuseColor.a *= transmissionAlpha + 0.1;
#endif
vec3 gradient = mix(uColor1, uColor2, vPosition.z/1.2);
outgoingLight = outgoingLight*gradient;
gl_FragColor = vec4( outgoingLight, diffuseColor.a );
`,
);
};
sideMaterial.tween1 = new TWEEN.Tween({ y: 0 })
.to({ y: config.defaultOptions.depth }, 10000)
.onUpdate((params) => {
let { y } = params;
sideMaterial.map.offset.y = y;
})
.repeat(Infinity)
.yoyo();
sideMaterial.tween1.start();
return sideMaterial;
}
getProvinceGroupPoint
jsx
import { getProvinceData } from './utils';
/**
* @description 获取指定类型的省份(组)点位
* @param {provinceGroups的key} type
* @param {boolean} isGroup 是否以 组 级区分
* @returns Feature[]
*/
export const getProvinceGroupPoint = (type = 'all', isGroup = false) => {
// 获取到中国各个省份的geojson点位信息
const { features = [] } = getProvinceData();
if (type === 'all') return features;
const currentGroups = provinceGroups[type];
if (!currentGroups?.length) return [];
let groupJsonData = [];
if (isGroup) {
currentGroups.map((group, index) => {
const findFeatures = features.filter((f) => group.adcodes.includes(f.properties.adcode));
if (findFeatures) {
groupJsonData[index] = groupJsonData[index] || [];
groupJsonData[index].push(...findFeatures);
}
});
} else {
const adcodes = currentGroups.reduce((ls, cur) => [...ls, ...cur.adcodes], []);
groupJsonData = features.filter((item) => adcodes.includes(item?.properties?.adcode));
}
return groupJsonData;
};
函数返回的结构就是下图这样,本质上就是筛选:

getOtherProvincesShape
:
jsx
// 获取指定组的边界线
/**
*
* @param {'all' | 'south'} type 选择区域
* @returns [shapes:绘制好的二维图像组, maps:对应组的地图(若有), points:点位组,可用于绘制边界线, groupFeatures: 组内省份组合后的Feature点位组]
*/
export function getOtherProvincesShape(type) {
const featuresGroup = getProvinceGroupPoint(type, true);
const provinceDataGroup = provinceGroups[type];
const shapes = [],
maps = [],
points = [],
groupFeatures = [];
featuresGroup.map((features, index) => {
maps[index] = provinceDataGroup[index].map;
const shape = new THREE.Shape();
let cord;
// 只有一个的话就直接拿json的点位,多个就组合起来
if (features.length === 1) {
cord = features[0].geometry.coordinates?.[0]?.[0] || [];
} else {
const stokes = getProvinceMergeStoke(features);
cord = stokes?.geometry?.coordinates[0] || [];
/*
// 如果组内出现多岛屿情况,且无法分离(这时候点位成了MultiPolygon),那么就用这个逻辑,让其使用数组多的points,主动变成单组(Polygon)
if(cord[0].length > 2){
cord = cord[0]
}
*/
}
groupFeatures.push(cord);
cord.map((item, i) => {
const [x, y] = geoProjection({ center: config.defaultOptions.pointCenter, args: item });
// 单独省份不会划线(暂时)
if (features.length > 1) {
points[index] = points[index] || [];
points[index].push(new THREE.Vector3(x, -y, 0));
}
if (i === 0) {
shape.moveTo(x, -y);
}
shape.lineTo(x, -y);
});
shapes.push(shape);
});
return [shapes, maps, points, groupFeatures];
}
getProvinceMergeStoke
:
根据得到的features信息,组合成featureCollection
类型,然后进行合并:
jsx
/**
*
* @description 合并相邻省份的边境界(必须相邻,否则返回MultiPolygon无法绘制(现有逻辑))
* @param {} provinceGroup Feature[]
* @param {} adcodes number[]
* @description 参数二选一
* @returns point[]
*/
export const getProvinceMergeStoke = (provinceGroup, adcodes) => {
let groups = provinceGroup;
if (!provinceGroup && adcodes) {
groups = getProvinceData().filter((item) => adcodes.includes(item.properties.adcode));
}
if (groups.length === 0) return [];
const polygons = groups.map((it) => polygon(it.geometry.coordinates[0]));
const u = union(featureCollection(polygons));
return u;
};
getBasicMapGeometrySettings
(计算包围盒)
建立复杂三维图像的时候,必须要计算包围盒
拿到setting数据,否则无法展示
jsx
// 计算包围盒
export function getBasicMapGeometrySettings(shape) {
const boundingBox = new THREE.Box2().setFromPoints(shape.getPoints());
const min = boundingBox.min;
const max = boundingBox.max;
const customUVGenerator = {
generateTopUV: function (geometry, vertices, a, b, c) {
const ax = vertices[a * 3],
ay = vertices[a * 3 + 1];
const bx = vertices[b * 3],
by = vertices[b * 3 + 1];
const cx = vertices[c * 3],
cy = vertices[c * 3 + 1];
const ua = (ax - min.x) / (max.x - min.x);
const va = 1 - (ay - min.y) / (max.y - min.y);
const ub = (bx - min.x) / (max.x - min.x);
const vb = 1 - (by - min.y) / (max.y - min.y);
const uc = (cx - min.x) / (max.x - min.x);
const vc = 1 - (cy - min.y) / (max.y - min.y);
return [new THREE.Vector2(ua, va), new THREE.Vector2(ub, vb), new THREE.Vector2(uc, vc)];
},
generateSideUV: function () {
return [
new THREE.Vector2(0, 0),
new THREE.Vector2(1, 0),
new THREE.Vector2(1, 1),
new THREE.Vector2(0, 1),
];
},
generateSideWallUV: function (geometry, vertices, idxA, idxB, idxC, idxD) {
/*
参数说明:
- geometry: 几何体对象
- vertices: 顶点数组(平面坐标)
- idxA, idxB, idxC, idxD: 构成侧面四边形的四个顶点索引
四边形顶点顺序:
A ---- B
| |
D ---- C
*/
// 简单实现:将侧面展开为矩形纹理映射
return [
new THREE.Vector2(0, 0), // A
new THREE.Vector2(1, 0), // B
new THREE.Vector2(1, 1), // C
new THREE.Vector2(0, 1), // D
];
},
};
// 创建挤压几何体
const extrudeSettings = {
depth: 0.1,
bevelEnabled: false,
UVGenerator: customUVGenerator,
};
return extrudeSettings;
}
getCanvasColorBg
通过canvas得到渐变色背景用于填充
CSS
background: radial-gradient(44.21% 39.63% at 49.89% 55.39%, #0C3E4D 19.82%, #0DAFE1 100%);
下方对象canvasGradientParams
的参数是对上方css渐变处理的,可以对应替换
jsx
// canvas渐变背景参数
const canvasGradientParams = {
shape: 'ellipse', // 椭圆形(CSS 默认)
size: { w: 44.21, h: 39.63 },
position: { x: 49.89, y: 55.39 },
stops: [
{ color: '#0C3E4D', offset: 0.1982 }, // 19.82%
{ color: '#0D9AE1', offset: 1.0 }, // 100%
],
};
export const getCanvasColorBg = () => {
const canvas = document.createElement('canvas');
canvas.width = 2048; // 高分辨率避免锯齿
canvas.height = 2048;
const ctx = canvas.getContext('2d');
// 计算实际像素坐标
const centerX = (canvasGradientParams.position.x / 100) * canvas.width;
const centerY = (canvasGradientParams.position.y / 100) * canvas.height;
const radiusX = (canvasGradientParams.size.w / 100) * canvas.width;
const radiusY = (canvasGradientParams.size.h / 100) * canvas.height;
// 创建径向渐变
const gradient = ctx.createRadialGradient(
centerX,
centerY,
0, // 起始圆(中心点,半径0)
centerX,
centerY,
Math.max(radiusX, radiusY), // 结束圆(相同中心,最大半径)
);
// 添加色标
canvasGradientParams.stops.forEach((stop) => {
gradient.addColorStop(stop.offset, stop.color);
});
// 绘制渐变
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return canvas;
};
彩头:如何判断目标经纬度是否在组合块(或者是目标省份adcode)的范围内?
核心实现在于 booleanPointInPolygon
方法,判断是否在区域内
jsx
import { booleanPointInPolygon, point } from '@turf/turf';
// 获取指定区域内的人员(组合的区域)
export const getCurrentProvinceGroupPerson = (personLs, type = 'all') => {
if (type === 'all') return personLs;
let [_, __, ___, groupFeatures] = getOtherProvincesShape(type);
// 包装成features
groupFeatures = groupFeatures.map((points) => ({
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [[...points]],
},
}));
const filterPersonLs = personLs.filter((person) => {
return groupFeatures.some((feature) => {
const { longitude, latitude } = person;
const p = point([longitude, latitude]);
const isInside = booleanPointInPolygon(p, feature);
return isInside
});
});
return filterPersonLs
};
小结(吐槽)
正所谓学中干,干中学。three.js这玩意儿一个月之前还是一点都不了解,到现在也是站在前人的肩膀上,去试图了解一些,然后用刚学到的皮毛去做一些有趣的东西,真是让人感到兴(痛)奋(苦)啊,不出意外的话以后还要再长期接触three,只得希望未来产品可以手下留情一些吧hhh。