mirror of https://gitee.com/antv-l7/antv-l7
commit
6f1df93435
|
@ -73,7 +73,6 @@ es/
|
|||
public
|
||||
.cache
|
||||
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
git_log.sh
|
||||
node_modules/
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import '@storybook/addon-actions/register';
|
||||
// import '@storybook/addon-actions/register';
|
||||
import '@storybook/addon-notes/register';
|
||||
import '@storybook/addon-storysource/register';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// tslint:disable-next-line:no-submodule-imports
|
||||
import '!style-loader!css-loader!sass-loader!./iframe.scss';
|
||||
import '@storybook/addon-console';
|
||||
// import '@storybook/addon-console';
|
||||
import { addParameters, configure } from '@storybook/react';
|
||||
import { create } from '@storybook/theming';
|
||||
|
||||
|
@ -15,8 +15,8 @@ addParameters({
|
|||
enableShortcuts: true,
|
||||
theme: create({
|
||||
base: 'light',
|
||||
brandTitle: 'L7 POC for new architecture',
|
||||
brandUrl: 'https://github.com/xiaoiver/L7-POC',
|
||||
brandTitle: 'L7 for new architecture',
|
||||
brandUrl: 'https://github.com/antvis/L7',
|
||||
gridCellSize: 12,
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
module.exports = ({ config }) => {
|
||||
|
||||
config.module.rules.push({
|
||||
test: /\.glsl$/,
|
||||
loader: 'raw-loader'
|
||||
});
|
||||
// config.module.rules.push({
|
||||
// test: /\.glsl$/,
|
||||
// loader: 'raw-loader'
|
||||
// });
|
||||
|
||||
// config.module.rules.push({
|
||||
// test: /\.worker\.(js|ts)$/,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: geojson
|
||||
title: GeoJSON
|
||||
order: 1
|
||||
---
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
title: geojson
|
||||
title: GeoJSON
|
||||
order: 1
|
||||
---
|
||||
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
---
|
||||
title: JSON
|
||||
order: 1
|
||||
---
|
||||
|
||||
GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。
|
||||
|
||||
## JSON
|
||||
|
||||
⚠️ json 不是标准的地理数据结构,因此在使用时务必要设置 Parser
|
||||
|
||||
json 数据解析使用对应 JSON parser
|
||||
|
||||
## parser
|
||||
|
||||
支持两种解析方式
|
||||
|
||||
### 简易解析方式
|
||||
|
||||
该方式只支持解析的点数据,或者只有两个点的线段,或者弧线数据
|
||||
|
||||
- type `string` 必选 `json`
|
||||
- x `string` 点数据表示 经度
|
||||
- y `string` 点数据表示 纬度
|
||||
- x1 `string` 经度
|
||||
- x2 `string` 纬度
|
||||
|
||||
如果数据是点数据,只需要设置 x,y 字段即可
|
||||
|
||||
如果是线段,弧线数据,需要知道起始点坐标既,x,y,x1,y1
|
||||
|
||||
```javascript
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 通用解析方式
|
||||
|
||||
可也解析任意复杂的点,线面
|
||||
|
||||
- type `string` 必选 `json`
|
||||
- coordinates `array` 必选,主要用于表达比较复杂的格式,等同于 geojson coordinates 属性
|
||||
|
||||
```javascript
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'coord',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 点数据
|
||||
|
||||
#### 简易解析
|
||||
|
||||
- type json
|
||||
- x: 经度字段
|
||||
- y: 纬度字段
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
lng: 112.345,
|
||||
lat: 30.455,
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
lng: 114.345,
|
||||
lat: 31.455,
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### 通用解析
|
||||
|
||||
[ 点 coodinates 数据格式](./geojson##point)
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
coord: [112.345, 30.455],
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
coord: [114.345, 32.455],
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'coord',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 线数据
|
||||
|
||||
#### 简易解析
|
||||
|
||||
- type: json
|
||||
- x `string` 经度
|
||||
- y `string` 纬度
|
||||
- x1 `string` 经度
|
||||
- x2 `string` 纬度
|
||||
|
||||
简易解析只支持两个点组成的线段,主要再绘制弧线的时候比较常用,只需指定线段的起始点坐标
|
||||
|
||||
```javascript
|
||||
const data = [{
|
||||
lng1:112.345,
|
||||
lat1:30.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
lng1:114.345,
|
||||
lat1:31.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
}
|
||||
];
|
||||
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
x:'lng1',
|
||||
y:'lat1' ,
|
||||
x1:'lng1',
|
||||
y1:'lat2' ,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 通用解析
|
||||
|
||||
绘制线段、弧线也支持使用 coordinates 组织数据
|
||||
|
||||
coordinates 包含两个坐标,
|
||||
第一个坐标 对应 x, y
|
||||
第二个坐标 对应 x1, y1
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
"id": "1",
|
||||
"coord": [
|
||||
[
|
||||
101.953125,
|
||||
50.51342652633956
|
||||
],
|
||||
[
|
||||
119.17968749999999,
|
||||
33.137551192346145
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
coordinates: "coord",
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
如果需要使用绘制轨迹数据,需要通过 coodinates 指定线的点序列。
|
||||
|
||||
coordinate 格式 geojson 的 coordinate 字段 支持 LineString, MultiLineString
|
||||
|
||||
[ 线 coodinates 数据格式](./geojson#linesring)
|
||||
|
||||
```javascript
|
||||
const data = {
|
||||
name: 'path1',
|
||||
path: [
|
||||
[58.00781249999999, 32.84267363195431],
|
||||
[85.78125, 25.16517336866393],
|
||||
[101.953125, 41.77131167976407],
|
||||
[114.9609375, 39.639537564366684],
|
||||
[117.42187500000001, 28.613459424004414],
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
使用时通过 coordinates 指定
|
||||
|
||||
```javascript
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
coordinates:'path'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 面数据
|
||||
|
||||
面数据 coordinates 字段比较复杂不支持简易的解析方式
|
||||
|
||||
#### 通用解析
|
||||
|
||||
需要指定 coordinates 字段, 格式同 GeoJSON 的 coordinates 字段
|
||||
|
||||
[面 coodinates 数据格式](./geojson/#polygon)
|
||||
|
||||
**注意面数据 coord 是三层数据结构**
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
type: 'Polygon',
|
||||
geometryCoord: [
|
||||
[
|
||||
[115.1806640625, 30.637912028341123],
|
||||
[114.9609375, 29.152161283318915],
|
||||
[117.79541015625001, 27.430289738862594],
|
||||
[118.740234375, 29.420460341013133],
|
||||
[117.46582031249999, 31.50362930577303],
|
||||
[115.1806640625, 30.637912028341123],
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'geometryCoord',
|
||||
},
|
||||
});
|
||||
```
|
|
@ -0,0 +1,264 @@
|
|||
---
|
||||
title: JSON
|
||||
order: 1
|
||||
---
|
||||
|
||||
GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。
|
||||
|
||||
## JSON
|
||||
|
||||
⚠️ json 不是标准的地理数据结构,因此在使用时务必要设置 Parser
|
||||
|
||||
json 数据解析使用对应 JSON parser
|
||||
|
||||
## parser
|
||||
|
||||
支持两种解析方式
|
||||
|
||||
### 简易解析方式
|
||||
|
||||
该方式只支持解析的点数据,或者只有两个点的线段,或者弧线数据
|
||||
|
||||
- type `string` 必选 `json`
|
||||
- x `string` 点数据表示 经度
|
||||
- y `string` 点数据表示 纬度
|
||||
- x1 `string` 经度
|
||||
- x2 `string` 纬度
|
||||
|
||||
如果数据是点数据,只需要设置 x,y 字段即可
|
||||
|
||||
如果是线段,弧线数据,需要知道起始点坐标既,x,y,x1,y1
|
||||
|
||||
```javascript
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 通用解析方式
|
||||
|
||||
可也解析任意复杂的点,线面
|
||||
|
||||
- type `string` 必选 `json`
|
||||
- coordinates `array` 必选,主要用于表达比较复杂的格式,等同于 geojson coordinates 属性
|
||||
|
||||
```javascript
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'coord',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 点数据
|
||||
|
||||
#### 简易解析
|
||||
|
||||
- type json
|
||||
- x: 经度字段
|
||||
- y: 纬度字段
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
lng: 112.345,
|
||||
lat: 30.455,
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
lng: 114.345,
|
||||
lat: 31.455,
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
#### 通用解析
|
||||
|
||||
[ 点 coodinates 数据格式](./geojson##point)
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
coord: [112.345, 30.455],
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
coord: [114.345, 32.455],
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'coord',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 线数据
|
||||
|
||||
#### 简易解析
|
||||
|
||||
- type: json
|
||||
- x `string` 经度
|
||||
- y `string` 纬度
|
||||
- x1 `string` 经度
|
||||
- x2 `string` 纬度
|
||||
|
||||
简易解析只支持两个点组成的线段,主要再绘制弧线的时候比较常用,只需指定线段的起始点坐标
|
||||
|
||||
```javascript
|
||||
const data = [{
|
||||
lng1:112.345,
|
||||
lat1:30.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
lng1:114.345,
|
||||
lat1:31.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
}
|
||||
];
|
||||
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
x:'lng1',
|
||||
y:'lat1' ,
|
||||
x1:'lng1',
|
||||
y1:'lat2' ,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 通用解析
|
||||
|
||||
绘制线段、弧线也支持使用 coordinates 组织数据
|
||||
|
||||
coordinates 包含两个坐标,
|
||||
第一个坐标 对应 x, y
|
||||
第二个坐标 对应 x1, y1
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
"id": "1",
|
||||
"coord": [
|
||||
[
|
||||
101.953125,
|
||||
50.51342652633956
|
||||
],
|
||||
[
|
||||
119.17968749999999,
|
||||
33.137551192346145
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
coordinates: "coord",
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
如果需要使用绘制轨迹数据,需要通过 coodinates 指定线的点序列。
|
||||
|
||||
coordinate 格式 geojson 的 coordinate 字段 支持 LineString, MultiLineString
|
||||
|
||||
[ 线 coodinates 数据格式](./geojson#linesring)
|
||||
|
||||
```javascript
|
||||
const data = {
|
||||
name: 'path1',
|
||||
path: [
|
||||
[58.00781249999999, 32.84267363195431],
|
||||
[85.78125, 25.16517336866393],
|
||||
[101.953125, 41.77131167976407],
|
||||
[114.9609375, 39.639537564366684],
|
||||
[117.42187500000001, 28.613459424004414],
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
使用时通过 coordinates 指定
|
||||
|
||||
```javascript
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
coordinates:'path'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 面数据
|
||||
|
||||
面数据 coordinates 字段比较复杂不支持简易的解析方式
|
||||
|
||||
#### 通用解析
|
||||
|
||||
需要指定 coordinates 字段, 格式同 GeoJSON 的 coordinates 字段
|
||||
|
||||
[面 coodinates 数据格式](./geojson/#polygon)
|
||||
|
||||
**注意面数据 coord 是三层数据结构**
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
type: 'Polygon',
|
||||
geometryCoord: [
|
||||
[
|
||||
[115.1806640625, 30.637912028341123],
|
||||
[114.9609375, 29.152161283318915],
|
||||
[117.79541015625001, 27.430289738862594],
|
||||
[118.740234375, 29.420460341013133],
|
||||
[117.46582031249999, 31.50362930577303],
|
||||
[115.1806640625, 30.637912028341123],
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'geometryCoord',
|
||||
},
|
||||
});
|
||||
```
|
|
@ -3,142 +3,44 @@ title: Source
|
|||
order: 0
|
||||
---
|
||||
|
||||
### 概述
|
||||
## 概述
|
||||
|
||||
source 地理数据处理模块,主要包含数据解析(parser),和数据处理(transform);
|
||||
|
||||
**parser:**
|
||||
### parser
|
||||
|
||||
不同数据类型处理成统一数据格式。矢量数据包括 GeoJON, CSV,Json 等不同数据格式,栅格数据,包括 Raster,Image 数据。将来还会支持瓦片格式数据。
|
||||
|
||||
**transform**
|
||||
|
||||
数据转换,数据统计,网格布局,数据聚合等数据操作。
|
||||
|
||||
## API
|
||||
|
||||
### parser
|
||||
|
||||
空间数据分矢量数据和栅格数据两大类
|
||||
|
||||
- 矢量数据 支持 csv,geojson,json 三种数据类型
|
||||
|
||||
- 栅格数据 支持 image,Raster
|
||||
|
||||
### transform
|
||||
|
||||
数据转换,数据统计,网格布局,数据聚合等数据操作。
|
||||
|
||||
## API
|
||||
|
||||
### parser
|
||||
|
||||
**配置项**
|
||||
|
||||
- type: `csv|json|geojson|image|raster`
|
||||
- 其他可选配置项,具体和数据格式相关
|
||||
|
||||
#### geojson
|
||||
|
||||
[geojson](https://www.yuque.com/antv/l7/dm2zll) 数据为默认数据格式,可以
|
||||
|
||||
不需要设置 parser 参数
|
||||
[geojson](https://www.yuque.com/antv/l7/dm2zll) 数据为默认数据格式,可以 不设置 parser 参数
|
||||
|
||||
```javascript
|
||||
layer.source(data);
|
||||
```
|
||||
|
||||
#### json
|
||||
#### JSON
|
||||
|
||||
json 不是标准的地理数据结构,因此需要设置对应的经纬度字段
|
||||
|
||||
**点数据**
|
||||
|
||||
x: 经度字段
|
||||
|
||||
y: 纬度字段
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
lng: 112.345,
|
||||
lat: 30.455,
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
lng: 114.345,
|
||||
lat: 31.455,
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**线段数据**
|
||||
|
||||
type: json
|
||||
|
||||
这里的直线表示有两个点组成的线段,主要绘制弧线的时候比较常用,只需指定线段的起始点坐标
|
||||
|
||||
x:经度字段 起点经度
|
||||
y:纬度字段 起点纬度
|
||||
x1:经度字段 终点经度
|
||||
y1:纬度字段 终点纬度
|
||||
|
||||
```javascript
|
||||
const data = [{
|
||||
lng1:112.345,
|
||||
lat1:30.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
},{
|
||||
lng1:114.345,
|
||||
lat1:31.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
}
|
||||
];
|
||||
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
x:'lng1',
|
||||
y:'lat1' ,
|
||||
x1:'lng1',
|
||||
y1:'lat2' ,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**面数据**
|
||||
|
||||
需要指定 coordinates 字段, coordinates 据格式
|
||||
|
||||
**注意面数据 coord 是三层数据结构**
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
type: 'Polygon',
|
||||
geometryCoord: [
|
||||
[
|
||||
[115.1806640625, 30.637912028341123],
|
||||
[114.9609375, 29.152161283318915],
|
||||
[117.79541015625001, 27.430289738862594],
|
||||
[118.740234375, 29.420460341013133],
|
||||
[117.46582031249999, 31.50362930577303],
|
||||
[115.1806640625, 30.637912028341123],
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'geometryCoord',
|
||||
},
|
||||
});
|
||||
```
|
||||
[JSON 数据格式解析](../json)
|
||||
|
||||
#### csv
|
||||
|
||||
|
@ -156,7 +58,7 @@ layer.source(data, {
|
|||
});
|
||||
```
|
||||
|
||||
**栅格数据类型\*\***
|
||||
**栅格数据类型 **
|
||||
|
||||
#### image
|
||||
|
||||
|
@ -215,20 +117,21 @@ source(values, {
|
|||
- callback:function 回调函数
|
||||
|
||||
```javascript
|
||||
layer.source(data, {
|
||||
transforms:[
|
||||
{
|
||||
type: 'map',
|
||||
callback:function(item){
|
||||
const [x, y] = item.coordinates;
|
||||
item.lat = item.lat*1;
|
||||
item.lng = item.lng*1;
|
||||
item.v = item.v *1;
|
||||
item.coordinates = [x*1,y*1];
|
||||
return item;
|
||||
}
|
||||
|
||||
layer.source(data, {
|
||||
transforms: [
|
||||
{
|
||||
type: 'map',
|
||||
callback: function(item) {
|
||||
const [x, y] = item.coordinates;
|
||||
item.lat = item.lat * 1;
|
||||
item.lng = item.lng * 1;
|
||||
item.v = item.v * 1;
|
||||
item.coordinates = [x * 1, y * 1];
|
||||
return item;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
#### grid
|
||||
|
@ -241,16 +144,16 @@ source(values, {
|
|||
- method:聚合方法 count,max,min,sum,mean5 个统计维度
|
||||
|
||||
```javascript
|
||||
layer.source(data, {
|
||||
transforms:[
|
||||
{
|
||||
type: 'grid',
|
||||
size: 15000,
|
||||
field:'v',
|
||||
method:'sum'
|
||||
}
|
||||
]
|
||||
}
|
||||
layer.source(data, {
|
||||
transforms: [
|
||||
{
|
||||
type: 'grid',
|
||||
size: 15000,
|
||||
field: 'v',
|
||||
method: 'sum',
|
||||
},
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
#### hexagon
|
||||
|
@ -261,7 +164,3 @@ source(values, {
|
|||
- size: 网格半径
|
||||
- field: 数据统计字段
|
||||
- method:聚合方法 count,max,min,sum,mean5 个统计维度
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
|
|
|
@ -3,143 +3,44 @@ title: Source
|
|||
order: 0
|
||||
---
|
||||
|
||||
### 概述
|
||||
## 概述
|
||||
|
||||
source 地理数据处理模块,主要包含数据解析(parser),和数据处理(transform);
|
||||
|
||||
**parser:**
|
||||
### parser
|
||||
|
||||
不同数据类型处理成统一数据格式。矢量数据包括 GeoJON, CSV,Json 等不同数据格式,栅格数据,包括 Raster,Image 数据。将来还会支持瓦片格式数据。
|
||||
|
||||
**transform:**
|
||||
|
||||
数据转换,数据统计,网格布局,数据聚合等数据操作。
|
||||
|
||||
## API
|
||||
|
||||
### parser
|
||||
|
||||
空间数据分矢量数据和栅格数据两大类
|
||||
|
||||
- 矢量数据 支持 csv,geojson,json 三种数据类型
|
||||
|
||||
- 栅格数据 支持 image,Raster
|
||||
|
||||
### transform
|
||||
|
||||
数据转换,数据统计,网格布局,数据聚合等数据操作。
|
||||
|
||||
## API
|
||||
|
||||
### parser
|
||||
|
||||
**配置项**
|
||||
|
||||
- type: `csv|json|geojson|image|raster`
|
||||
- 其他可选配置项,具体和数据格式相关
|
||||
|
||||
#### geojson
|
||||
|
||||
[geojson](https://www.yuque.com/antv/l7/dm2zll) 数据为默认数据格式,可以
|
||||
|
||||
不需要设置 parser 参数
|
||||
[geojson](https://www.yuque.com/antv/l7/dm2zll) 数据为默认数据格式,可以 不设置 parser 参数
|
||||
|
||||
```javascript
|
||||
layer.source(data);
|
||||
```
|
||||
|
||||
#### json
|
||||
#### JSON
|
||||
|
||||
json 不是标准的地理数据结构,因此需要设置对应的经纬度字段
|
||||
|
||||
**点数据**
|
||||
|
||||
x: 经度字段
|
||||
|
||||
y: 纬度字段
|
||||
|
||||
```javascript
|
||||
const data = [
|
||||
{
|
||||
lng: 112.345,
|
||||
lat: 30.455,
|
||||
value: 10,
|
||||
},
|
||||
{
|
||||
lng: 114.345,
|
||||
lat: 31.455,
|
||||
value: 10,
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**线段数据**
|
||||
|
||||
type: json
|
||||
|
||||
这里的直线表示有两个点组成的线段,主要绘制弧线的时候比较常用,只需指定线段的起始点坐标
|
||||
|
||||
x:经度字段 起点经度
|
||||
y:纬度字段 起点纬度
|
||||
x1:经度字段 终点经度
|
||||
y1:纬度字段 终点纬度
|
||||
|
||||
```javascript
|
||||
const data = [{
|
||||
lng1:112.345,
|
||||
lat1:30.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
},
|
||||
{
|
||||
lng1:114.345,
|
||||
lat1:31.455,
|
||||
lng2:112.345,
|
||||
lat2:30.455,
|
||||
value: 10
|
||||
}
|
||||
];
|
||||
|
||||
layer.source(
|
||||
data,
|
||||
{
|
||||
parser:{
|
||||
type:'json',
|
||||
x:'lng1',
|
||||
y:'lat1' ,
|
||||
x1:'lng1',
|
||||
y1:'lat2' ,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**面数据**
|
||||
|
||||
需要指定 coordinates 字段, coordinates 据格式
|
||||
|
||||
**注意面数据 coord 是三层数据结构**
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
type: 'Polygon',
|
||||
geometryCoord: [
|
||||
[
|
||||
[115.1806640625, 30.637912028341123],
|
||||
[114.9609375, 29.152161283318915],
|
||||
[117.79541015625001, 27.430289738862594],
|
||||
[118.740234375, 29.420460341013133],
|
||||
[117.46582031249999, 31.50362930577303],
|
||||
[115.1806640625, 30.637912028341123],
|
||||
],
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
layer.source(data, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
coordinates: 'geometryCoord',
|
||||
},
|
||||
});
|
||||
```
|
||||
[JSON 数据格式解析](../json)
|
||||
|
||||
#### csv
|
||||
|
||||
|
@ -157,7 +58,7 @@ layer.source(data, {
|
|||
});
|
||||
```
|
||||
|
||||
**栅格数据类型\*\***
|
||||
**栅格数据类型 **
|
||||
|
||||
#### image
|
||||
|
||||
|
@ -262,8 +163,4 @@ layer.source(data, {
|
|||
- type: 'hexagon',
|
||||
- size: 网格半径
|
||||
- field: 数据统计字段
|
||||
- method:聚合方法 count,max,min,sum,mean5 个统计维度
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
- method:聚合方法 count,max,min,sum,mean 5 个统计维度
|
||||
|
|
|
@ -24,4 +24,7 @@ const scene = new Scene({
|
|||
```
|
||||
如果你只是需要做个中国地图,世界地图这样填充图,建议你采用这样的模式
|
||||
|
||||
离线,无token使用 ![mapbox demo](https://codesandbox.io/embed/frosty-architecture-tv6uv?fontsize=14&hidenavigation=1&theme=dark)
|
||||
|
||||
离线,无token使用
|
||||
|
||||
<iframe width="100%" height="400" src="//jsfiddle.net/lzxue/a76og89k/embedded/html,result/light/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
|
||||
|
|
|
@ -24,4 +24,6 @@ const scene = new Scene({
|
|||
```
|
||||
如果你只是需要做个中国地图,世界地图这样填充图,建议你采用这样的模式
|
||||
|
||||
离线,无token使用 ![mapbox demo](https://codesandbox.io/embed/frosty-architecture-tv6uv?fontsize=14&hidenavigation=1&theme=dark)
|
||||
离线,无token使用
|
||||
|
||||
<iframe width="100%" height="400" src="//jsfiddle.net/lzxue/a76og89k/embedded/html,result/light/" allowfullscreen="allowfullscreen" allowpaymentrequest frameborder="0"></iframe>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { Scene, PointLayer } from '@antv/l7';
|
||||
import { GaodeMap } from '@antv/l7-maps';
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'light',
|
||||
pitch: 0,
|
||||
center: [ 120.19382669582967, 30.258134 ],
|
||||
zoom: 5
|
||||
})
|
||||
});
|
||||
|
||||
const radius = 0.1;
|
||||
|
||||
function pointOnCircle(angle) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: [{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [ 120.19382669582967 + Math.cos(angle) * radius, 30.258134 + Math.sin(angle) * radius ]
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
const layer = new PointLayer()
|
||||
.source(pointOnCircle(0))
|
||||
.shape('circle')
|
||||
.size(15) // default 1
|
||||
.color('#2F54EB')
|
||||
.style({
|
||||
stroke: 'rgb(255,255,255)',
|
||||
strokeWidth: 2,
|
||||
opacity: 1
|
||||
});
|
||||
scene.addLayer(layer);
|
||||
function animateMarker(timestamp) {
|
||||
layer.setData(pointOnCircle(timestamp / 1000));
|
||||
requestAnimationFrame(animateMarker);
|
||||
}
|
||||
layer.on('inited', () => {
|
||||
animateMarker(0);
|
||||
});
|
||||
|
|
@ -6,8 +6,11 @@
|
|||
"demos": [
|
||||
{
|
||||
"filename": "line.js",
|
||||
"title": "json数据"",
|
||||
"screenshot": ""
|
||||
"title": "json数据"
|
||||
},
|
||||
{
|
||||
"filename": "data_update.js",
|
||||
"title": "数据更新"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"clean-webpack-plugin": "^0.1.19",
|
||||
"commitizen": "^4.0.3",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"core-js": "3",
|
||||
"core-js": "^3.4.7",
|
||||
"coveralls": "^3.0.7",
|
||||
"cross-env": "^6.0.3",
|
||||
"css-loader": "^3.2.0",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"@antv/l7-utils": "^2.0.0-beta.16",
|
||||
"@mapbox/tiny-sdf": "^1.1.1",
|
||||
"ajv": "^6.10.2",
|
||||
"element-resize-event": "^3.0.3",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"gl-matrix": "^3.1.0",
|
||||
"hammerjs": "^2.0.8",
|
||||
|
@ -41,6 +42,7 @@
|
|||
"@types/gl-matrix": "^2.4.5",
|
||||
"@types/hammerjs": "^2.0.36",
|
||||
"@types/lodash": "^4.14.138",
|
||||
"@types/element-resize-event": "^2.0.0",
|
||||
"@types/viewport-mercator-project": "^6.1.0"
|
||||
},
|
||||
"gitHead": "00d23ef70d9ec76eec26833fc50ac18fe584cf26",
|
||||
|
|
|
@ -48,7 +48,7 @@ const defaultLayerConfig: Partial<ILayerConfig> = {
|
|||
maxZoom: 20,
|
||||
visible: true,
|
||||
zIndex: 0,
|
||||
enableMultiPassRenderer: true,
|
||||
enableMultiPassRenderer: false,
|
||||
enablePicking: false,
|
||||
enableHighlight: false,
|
||||
highlightColor: 'red',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Container } from 'inversify';
|
||||
import { SyncBailHook, SyncHook } from 'tapable';
|
||||
import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable';
|
||||
import Clock from '../../utils/clock';
|
||||
import { ISceneConfig } from '../config/IConfigService';
|
||||
import { IMapService } from '../map/IMapService';
|
||||
|
@ -51,8 +51,11 @@ export interface ILayer {
|
|||
inited: boolean; // 是否初始化完成
|
||||
zIndex: number;
|
||||
plugins: ILayerPlugin[];
|
||||
layerModelNeedUpdate: boolean;
|
||||
dataPluginsState: { [key: string]: boolean };
|
||||
hooks: {
|
||||
init: SyncBailHook<void, boolean | void>;
|
||||
beforeRenderData: SyncWaterfallHook<boolean | void>;
|
||||
beforeRender: SyncBailHook<void, boolean | void>;
|
||||
afterRender: SyncHook<void>;
|
||||
beforePickingEncode: SyncHook<void>;
|
||||
|
@ -76,13 +79,14 @@ export interface ILayer {
|
|||
Partial<IModelInitializationOptions>,
|
||||
): IModel;
|
||||
init(): ILayer;
|
||||
scale(field: string | IScaleOptions, cfg: IScale): ILayer;
|
||||
size(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
color(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
shape(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
label(field: StyleAttrField, value?: StyleAttributeOption): ILayer;
|
||||
animate(option: IAnimateOption): ILayer;
|
||||
// pattern(field: string, value: StyleAttributeOption): ILayer;
|
||||
// filter(field: string, value: StyleAttributeOption): ILayer;
|
||||
filter(field: string, value: StyleAttributeOption): ILayer;
|
||||
// active(option: ActiveOption): ILayer;
|
||||
style(options: unknown): ILayer;
|
||||
hide(): ILayer;
|
||||
|
@ -95,12 +99,14 @@ export interface ILayer {
|
|||
render(): ILayer;
|
||||
destroy(): void;
|
||||
source(data: any, option?: ISourceCFG): ILayer;
|
||||
setData(data: any, option?: ISourceCFG): ILayer;
|
||||
/**
|
||||
* 向当前图层注册插件
|
||||
* @param plugin 插件实例
|
||||
*/
|
||||
addPlugin(plugin: ILayerPlugin): ILayer;
|
||||
getSource(): ISource;
|
||||
isSourceNeedUpdate(): boolean;
|
||||
setSource(source: ISource): void;
|
||||
setEncodedData(encodedData: IEncodeFeature[]): void;
|
||||
getEncodedData(): IEncodeFeature[];
|
||||
|
|
|
@ -25,9 +25,18 @@ export enum ScaleTypes {
|
|||
THRESHOLD = 'threshold',
|
||||
CAT = 'cat',
|
||||
}
|
||||
|
||||
export type ScaleTypeName =
|
||||
| 'linear'
|
||||
| 'power'
|
||||
| 'log'
|
||||
| 'identity'
|
||||
| 'time'
|
||||
| 'quantile'
|
||||
| 'quantize'
|
||||
| 'threshold'
|
||||
| 'cat';
|
||||
export interface IScale {
|
||||
type: ScaleTypes;
|
||||
type: ScaleTypeName;
|
||||
ticks?: any[];
|
||||
nice?: boolean;
|
||||
format?: () => any;
|
||||
|
@ -40,7 +49,7 @@ export enum StyleScaleType {
|
|||
}
|
||||
export interface IScaleOption {
|
||||
field?: string;
|
||||
type: ScaleTypes;
|
||||
type: ScaleTypeName;
|
||||
ticks?: any[];
|
||||
nice?: boolean;
|
||||
format?: () => any;
|
||||
|
@ -137,6 +146,7 @@ export interface IStyleAttribute extends IStyleAttributeInitializationOptions {
|
|||
vertexAttribute: IAttribute;
|
||||
mapping?(...params: unknown[]): unknown[];
|
||||
setProps(props: Partial<IStyleAttributeInitializationOptions>): void;
|
||||
resetDescriptor(): void;
|
||||
}
|
||||
|
||||
export type Triangulation = (
|
||||
|
@ -157,6 +167,12 @@ export interface IStyleAttributeService {
|
|||
// layerName: string,
|
||||
// options: ILayerStyleOptions,
|
||||
// ): void;
|
||||
attributesAndIndices: {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
};
|
||||
elements: IElements;
|
||||
};
|
||||
registerStyleAttribute(
|
||||
options: Partial<IStyleAttributeInitializationOptions>,
|
||||
): IStyleAttribute;
|
||||
|
@ -169,7 +185,7 @@ export interface IStyleAttributeService {
|
|||
getLayerStyleAttribute(attributeName: string): IStyleAttribute | undefined;
|
||||
createAttributesAndIndices(
|
||||
encodedFeatures: IEncodeFeature[],
|
||||
triangulation: Triangulation,
|
||||
triangulation?: Triangulation,
|
||||
): {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
|
|
|
@ -59,6 +59,7 @@ export default class LayerService implements ILayerService {
|
|||
.filter((layer) => layer.isVisible())
|
||||
.forEach((layer) => {
|
||||
// trigger hooks
|
||||
layer.hooks.beforeRenderData.call(true);
|
||||
layer.hooks.beforeRender.call();
|
||||
layer.render();
|
||||
layer.hooks.afterRender.call();
|
||||
|
|
|
@ -68,6 +68,12 @@ export default class StyleAttribute implements IStyleAttribute {
|
|||
return this.defaultCallback(params);
|
||||
}
|
||||
|
||||
public resetDescriptor() {
|
||||
if (this.descriptor) {
|
||||
this.descriptor.buffer.data = [];
|
||||
}
|
||||
}
|
||||
|
||||
private defaultCallback = (params: unknown[]): unknown[] => {
|
||||
// 没有 params 的情况,是指没有指定 fields,直接返回配置的 values 常量
|
||||
if (params.length === 0) {
|
||||
|
|
|
@ -30,11 +30,19 @@ let counter = 0;
|
|||
*/
|
||||
@injectable()
|
||||
export default class StyleAttributeService implements IStyleAttributeService {
|
||||
public attributesAndIndices: {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
};
|
||||
elements: IElements;
|
||||
};
|
||||
@inject(TYPES.IRendererService)
|
||||
private readonly rendererService: IRendererService;
|
||||
|
||||
private attributes: IStyleAttribute[] = [];
|
||||
|
||||
private triangulation: Triangulation;
|
||||
|
||||
private c = counter++;
|
||||
|
||||
private featureLayout: {
|
||||
|
@ -49,7 +57,6 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
sizePerElement: 0,
|
||||
elements: [],
|
||||
};
|
||||
|
||||
public registerStyleAttribute(
|
||||
options: Partial<IStyleAttributeInitializationOptions>,
|
||||
) {
|
||||
|
@ -170,14 +177,20 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
|
||||
public createAttributesAndIndices(
|
||||
features: IEncodeFeature[],
|
||||
triangulation: Triangulation,
|
||||
triangulation?: Triangulation,
|
||||
): {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
};
|
||||
elements: IElements;
|
||||
} {
|
||||
const descriptors = this.attributes.map((attr) => attr.descriptor);
|
||||
if (triangulation) {
|
||||
this.triangulation = triangulation;
|
||||
}
|
||||
const descriptors = this.attributes.map((attr) => {
|
||||
attr.resetDescriptor();
|
||||
return attr.descriptor;
|
||||
});
|
||||
let verticesNum = 0;
|
||||
const vertices: number[] = [];
|
||||
const indices: number[] = [];
|
||||
|
@ -191,7 +204,7 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
vertices: verticesForCurrentFeature,
|
||||
normals: normalsForCurrentFeature,
|
||||
size: vertexSize,
|
||||
} = triangulation(feature);
|
||||
} = this.triangulation(feature);
|
||||
indices.push(...indicesForCurrentFeature.map((i) => i + verticesNum));
|
||||
vertices.push(...verticesForCurrentFeature);
|
||||
if (normalsForCurrentFeature) {
|
||||
|
@ -211,7 +224,6 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
});
|
||||
|
||||
verticesNum += verticesNumForCurrentFeature;
|
||||
|
||||
// 根据 position 顶点生成其他顶点数据
|
||||
for (
|
||||
let vertexIdx = 0;
|
||||
|
@ -273,13 +285,12 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
type: gl.UNSIGNED_INT,
|
||||
count: indices.length,
|
||||
});
|
||||
|
||||
return {
|
||||
this.attributesAndIndices = {
|
||||
attributes,
|
||||
elements,
|
||||
};
|
||||
return this.attributesAndIndices;
|
||||
}
|
||||
|
||||
public clearAllAttributes() {
|
||||
// 销毁关联的 vertex attribute buffer objects
|
||||
this.attributes.forEach((attribute) => {
|
||||
|
|
|
@ -2,8 +2,8 @@ import { injectable } from 'inversify';
|
|||
import { Log } from 'probe.gl';
|
||||
import { ILogService } from './ILogService';
|
||||
|
||||
const Logger = new Log({ id: 'L7' }).enable();
|
||||
// 只输出 debug 级别以上的日志信息
|
||||
const Logger = new Log({ id: 'L7' }).enable(true);
|
||||
// // 只输出 debug 级别以上的日志信息
|
||||
Logger.priority = 4;
|
||||
|
||||
@injectable()
|
||||
|
|
|
@ -34,7 +34,6 @@ export interface IElements {
|
|||
// 原 Buffer 替换位置,单位为 byte
|
||||
offset: number;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* gl.deleteBuffer
|
||||
*/
|
||||
|
|
|
@ -220,6 +220,7 @@ export interface IModelDrawOptions {
|
|||
attributes?: {
|
||||
[key: string]: IAttribute;
|
||||
};
|
||||
elements?: IElements;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { DOM } from '@antv/l7-utils';
|
||||
import elementResizeEvent, { unbind } from 'element-resize-event';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { AsyncParallelHook } from 'tapable';
|
||||
|
@ -149,7 +151,11 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.$container = $container;
|
||||
if ($container) {
|
||||
await this.rendererService.init($container);
|
||||
window.addEventListener('resize', this.handleWindowResized, false);
|
||||
elementResizeEvent(
|
||||
this.$container as HTMLDivElement,
|
||||
this.handleWindowResized,
|
||||
);
|
||||
// window.addEventListener('resize', this.handleWindowResized, false);
|
||||
} else {
|
||||
this.logger.error('容器 id 不存在');
|
||||
}
|
||||
|
@ -211,11 +217,13 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
this.removeAllListeners();
|
||||
this.rendererService.destroy();
|
||||
this.map.destroy();
|
||||
window.removeEventListener('resize', this.handleWindowResized, false);
|
||||
unbind(this.$container as HTMLDivElement, this.handleWindowResized);
|
||||
// window.removeEventListener('resize', this.handleWindowResized, false);
|
||||
}
|
||||
|
||||
private handleWindowResized = () => {
|
||||
this.emit('resize');
|
||||
// @ts-check
|
||||
if (this.$container) {
|
||||
// recalculate the viewport's size and call gl.viewport
|
||||
// @see https://github.com/regl-project/regl/blob/master/lib/webgl.js#L24-L38
|
||||
|
@ -227,12 +235,15 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
w = bounds.right - bounds.left;
|
||||
h = bounds.bottom - bounds.top;
|
||||
}
|
||||
|
||||
this.rendererService.viewport({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: pixelRatio * w,
|
||||
height: pixelRatio * h,
|
||||
});
|
||||
// 触发 Map, canvas
|
||||
DOM.triggerResize();
|
||||
// repaint layers
|
||||
this.render();
|
||||
}
|
||||
|
|
|
@ -16,9 +16,21 @@ export interface ITransform {
|
|||
}
|
||||
|
||||
export interface ISourceCFG {
|
||||
cluster?: boolean;
|
||||
clusterOptions?: Partial<IClusterOptions>;
|
||||
parser?: IParserCfg;
|
||||
transforms?: ITransform[];
|
||||
}
|
||||
export interface IClusterOptions {
|
||||
enable: false;
|
||||
radius: number;
|
||||
maxZoom: number;
|
||||
minZoom: number;
|
||||
zoom: number;
|
||||
bbox: [number, number, number, number];
|
||||
field: string;
|
||||
method: 'max' | 'sum' | 'min' | 'mean' | 'count' | CallBack;
|
||||
}
|
||||
export interface IDictionary<TValue> {
|
||||
[key: string]: TValue;
|
||||
}
|
||||
|
@ -47,6 +59,8 @@ export type IJsonData = IJsonItem[];
|
|||
|
||||
export interface ISource {
|
||||
data: IParserData;
|
||||
cluster: boolean;
|
||||
clusterOptions: Partial<IClusterOptions>;
|
||||
}
|
||||
export interface IRasterCfg {
|
||||
extent: [number, number, number, number];
|
||||
|
|
|
@ -28,17 +28,20 @@ import {
|
|||
IStyleAttributeService,
|
||||
IStyleAttributeUpdateOptions,
|
||||
lazyInject,
|
||||
ScaleTypeName,
|
||||
ScaleTypes,
|
||||
StyleAttributeField,
|
||||
StyleAttributeOption,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import Source from '@antv/l7-source';
|
||||
import { bindAll } from '@antv/l7-utils';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { Container } from 'inversify';
|
||||
import { isFunction, isObject } from 'lodash';
|
||||
// @ts-ignore
|
||||
import mergeJsonSchemas from 'merge-json-schemas';
|
||||
import { SyncBailHook, SyncHook } from 'tapable';
|
||||
import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable';
|
||||
import { normalizePasses } from '../plugins/MultiPassRendererPlugin';
|
||||
import baseLayerSchema from './schema';
|
||||
|
||||
|
@ -46,6 +49,7 @@ import baseLayerSchema from './schema';
|
|||
* 分配 layer id
|
||||
*/
|
||||
let layerIdCounter = 0;
|
||||
const MapEventTypes = ['zoomchange', 'dragend', 'camerachange', 'resize'];
|
||||
|
||||
export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
||||
implements ILayer {
|
||||
|
@ -56,11 +60,18 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
public minZoom: number;
|
||||
public maxZoom: number;
|
||||
public inited: boolean = false;
|
||||
|
||||
public layerModelNeedUpdate: boolean = false;
|
||||
public dataPluginsState: { [key: string]: boolean } = {
|
||||
DataSource: false,
|
||||
DataMapping: false,
|
||||
FeatureScale: false,
|
||||
StyleAttr: false,
|
||||
};
|
||||
// 生命周期钩子
|
||||
public hooks = {
|
||||
init: new SyncBailHook<void, boolean | void>(),
|
||||
beforeRender: new SyncBailHook<void, boolean | void>(),
|
||||
beforeRenderData: new SyncWaterfallHook<void | boolean>(['data']),
|
||||
afterRender: new SyncHook<void>(),
|
||||
beforePickingEncode: new SyncHook<void>(),
|
||||
afterPickingEncode: new SyncHook<void>(),
|
||||
|
@ -262,6 +273,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
// 触发 init 生命周期插件
|
||||
this.hooks.init.call();
|
||||
|
||||
this.buildModels();
|
||||
|
||||
this.inited = true;
|
||||
|
@ -299,6 +311,19 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
});
|
||||
return this;
|
||||
}
|
||||
public filter(
|
||||
field: StyleAttributeField,
|
||||
values?: StyleAttributeOption,
|
||||
updateOptions?: Partial<IStyleAttributeUpdateOptions>,
|
||||
) {
|
||||
this.pendingStyleAttributes.push({
|
||||
attributeName: 'filter',
|
||||
attributeField: field,
|
||||
attributeValues: values,
|
||||
updateOptions,
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public shape(
|
||||
field: StyleAttributeField,
|
||||
|
@ -338,6 +363,38 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
public setData(data: any, options?: ISourceCFG) {
|
||||
this.sourceOption.data = data;
|
||||
this.sourceOption.options = options;
|
||||
this.hooks.init.call();
|
||||
this.buildModels();
|
||||
return this;
|
||||
}
|
||||
|
||||
public isSourceNeedUpdate() {
|
||||
const cluster = this.layerSource.cluster;
|
||||
if (cluster) {
|
||||
const { zoom = 0, bbox = [0, 0, 0, 0] } = this.layerSource.clusterOptions;
|
||||
const newZoom = this.mapService.getZoom();
|
||||
const bounds = this.mapService.getBounds();
|
||||
const newBbox: [number, number, number, number] = [
|
||||
bounds[0][0],
|
||||
bounds[0][1],
|
||||
bounds[1][0],
|
||||
bounds[1][1],
|
||||
];
|
||||
// ||
|
||||
// bbox[0] !== newBbox[0] ||
|
||||
// bbox[2] !== newBbox[2]
|
||||
if (Math.abs(zoom - newZoom) > 1) {
|
||||
this.layerSource.updateClusterData(Math.floor(newZoom), newBbox);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public style(options: object & Partial<ILayerConfig>): ILayer {
|
||||
const { passes, ...rest } = options;
|
||||
|
||||
|
@ -365,7 +422,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
}
|
||||
return this;
|
||||
}
|
||||
public scale(field: string | IScaleOptions, cfg: IScale) {
|
||||
public scale(field: ScaleTypeName | IScaleOptions, cfg: IScale) {
|
||||
if (isObject(field)) {
|
||||
this.scaleOptions = {
|
||||
...this.scaleOptions,
|
||||
|
@ -453,9 +510,16 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
this.hooks.afterDestroy.call();
|
||||
|
||||
this.removeAllListeners();
|
||||
|
||||
// 解绑图层容器中的服务
|
||||
// this.container.unbind(TYPES.IStyleAttributeService);
|
||||
}
|
||||
public clear() {
|
||||
this.styleAttributeService.clearAllAttributes();
|
||||
// 销毁所有 model
|
||||
this.models.forEach((model) => model.destroy());
|
||||
}
|
||||
|
||||
public isDirty() {
|
||||
return !!(
|
||||
|
@ -470,6 +534,16 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
public setSource(source: Source) {
|
||||
this.layerSource = source;
|
||||
const bounds = this.mapService.getBounds();
|
||||
const zoom = this.mapService.getZoom();
|
||||
if (this.layerSource.cluster) {
|
||||
this.layerSource.updateClusterData(zoom, [
|
||||
bounds[0][0],
|
||||
bounds[0][1],
|
||||
bounds[1][0],
|
||||
bounds[1][1],
|
||||
]);
|
||||
}
|
||||
}
|
||||
public getSource() {
|
||||
return this.layerSource;
|
||||
|
@ -519,7 +593,6 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
});
|
||||
const { vs, fs, uniforms } = this.shaderModuleService.getModule(moduleName);
|
||||
const { createModel } = this.rendererService;
|
||||
|
||||
const {
|
||||
attributes,
|
||||
elements,
|
||||
|
@ -569,4 +642,13 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
callback: isFunction(valuesOrCallback) ? valuesOrCallback : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private registerMapEvent() {
|
||||
MapEventTypes.forEach((type) => {
|
||||
this.mapService.on(type, this.layerMapHander.bind(this, type));
|
||||
});
|
||||
}
|
||||
private layerMapHander(type: string, data: any) {
|
||||
this.emit(type, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {
|
||||
IAttribute,
|
||||
ICameraService,
|
||||
IElements,
|
||||
IFontService,
|
||||
IGlobalConfigService,
|
||||
IIconService,
|
||||
|
@ -12,10 +14,13 @@ import {
|
|||
IShaderModuleService,
|
||||
IStyleAttributeService,
|
||||
lazyInject,
|
||||
Triangulation,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
|
||||
export default class BaseModel implements ILayerModel {
|
||||
public triangulation: Triangulation;
|
||||
|
||||
protected layer: ILayer;
|
||||
|
||||
@lazyInject(TYPES.IGlobalConfigService)
|
||||
|
@ -57,7 +62,14 @@ export default class BaseModel implements ILayerModel {
|
|||
public buildModels(): IModel[] {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public getAttribute(): {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
};
|
||||
elements: IElements;
|
||||
} {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public render() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
|
|
@ -27,16 +27,16 @@ export default class DataMappingPlugin implements ILayerPlugin {
|
|||
}: { styleAttributeService: IStyleAttributeService },
|
||||
) {
|
||||
layer.hooks.init.tap('DataMappingPlugin', () => {
|
||||
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
|
||||
const { dataArray } = layer.getSource().data;
|
||||
this.doMaping(layer, { styleAttributeService });
|
||||
});
|
||||
|
||||
// TODO: FIXME
|
||||
if (!dataArray) {
|
||||
return;
|
||||
layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => {
|
||||
if (flag) {
|
||||
layer.dataPluginsState.DataMapping = false;
|
||||
this.doMaping(layer, { styleAttributeService });
|
||||
return true;
|
||||
}
|
||||
|
||||
// mapping with source data
|
||||
layer.setEncodedData(this.mapping(attributes, dataArray));
|
||||
return false;
|
||||
});
|
||||
|
||||
// remapping before render
|
||||
|
@ -52,6 +52,29 @@ export default class DataMappingPlugin implements ILayerPlugin {
|
|||
}
|
||||
});
|
||||
}
|
||||
private doMaping(
|
||||
layer: ILayer,
|
||||
{
|
||||
styleAttributeService,
|
||||
}: { styleAttributeService: IStyleAttributeService },
|
||||
) {
|
||||
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
|
||||
const filter = styleAttributeService.getLayerStyleAttribute('filter');
|
||||
const { dataArray } = layer.getSource().data;
|
||||
let filterData = dataArray;
|
||||
if (filter?.scale) {
|
||||
filterData = dataArray.filter((record: IParseDataItem) => {
|
||||
return this.applyAttributeMapping(filter, record)[0];
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: FIXME
|
||||
if (!filterData) {
|
||||
return;
|
||||
}
|
||||
// mapping with source data
|
||||
layer.setEncodedData(this.mapping(attributes, filterData));
|
||||
}
|
||||
|
||||
private mapping(
|
||||
attributes: IStyleAttribute[],
|
||||
|
@ -62,21 +85,22 @@ export default class DataMappingPlugin implements ILayerPlugin {
|
|||
id: record._id,
|
||||
coordinates: record.coordinates,
|
||||
};
|
||||
// TODO 数据过滤
|
||||
attributes.forEach((attribute: IStyleAttribute) => {
|
||||
let values = this.applyAttributeMapping(attribute, record);
|
||||
attribute.needRemapping = false;
|
||||
attributes
|
||||
// .filter((attribute) => attribute.name !== 'filter')
|
||||
.forEach((attribute: IStyleAttribute) => {
|
||||
let values = this.applyAttributeMapping(attribute, record);
|
||||
attribute.needRemapping = false;
|
||||
|
||||
// TODO: 支持每个属性配置 postprocess
|
||||
if (attribute.name === 'color') {
|
||||
values = values.map((c: unknown) => {
|
||||
return rgb2arr(c as string);
|
||||
});
|
||||
}
|
||||
// @ts-ignore
|
||||
encodeRecord[attribute.name] =
|
||||
Array.isArray(values) && values.length === 1 ? values[0] : values;
|
||||
});
|
||||
// TODO: 支持每个属性配置 postprocess
|
||||
if (attribute.name === 'color') {
|
||||
values = values.map((c: unknown) => {
|
||||
return rgb2arr(c as string);
|
||||
});
|
||||
}
|
||||
// @ts-ignore
|
||||
encodeRecord[attribute.name] =
|
||||
Array.isArray(values) && values.length === 1 ? values[0] : values;
|
||||
});
|
||||
return encodeRecord;
|
||||
}) as IEncodeFeature[];
|
||||
}
|
||||
|
|
|
@ -9,5 +9,13 @@ export default class DataSourcePlugin implements ILayerPlugin {
|
|||
const { data, options } = layer.sourceOption;
|
||||
layer.setSource(new Source(data, options));
|
||||
});
|
||||
|
||||
// 检测数据是不否需要更新
|
||||
layer.hooks.beforeRenderData.tap('DataSourcePlugin', (flag) => {
|
||||
if (layer.isSourceNeedUpdate()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
IStyleAttribute,
|
||||
IStyleAttributeService,
|
||||
IStyleScale,
|
||||
ScaleTypeName,
|
||||
ScaleTypes,
|
||||
StyleScaleType,
|
||||
TYPES,
|
||||
|
@ -63,6 +64,18 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
this.caculateScalesForAttributes(attributes || [], dataArray);
|
||||
});
|
||||
|
||||
// 检测数据是不否需要更新
|
||||
layer.hooks.beforeRenderData.tap('FeatureScalePlugin', (flag) => {
|
||||
if (flag) {
|
||||
this.scaleOptions = layer.getScaleOptions();
|
||||
const attributes = styleAttributeService.getLayerStyleAttributes();
|
||||
const { dataArray } = layer.getSource().data;
|
||||
this.caculateScalesForAttributes(attributes || [], dataArray);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
layer.hooks.beforeRender.tap('FeatureScalePlugin', () => {
|
||||
this.scaleOptions = layer.getScaleOptions();
|
||||
const attributes = styleAttributeService.getLayerStyleAttributes();
|
||||
|
@ -223,7 +236,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
}
|
||||
|
||||
private createDefaultScaleConfig(
|
||||
type: ScaleTypes,
|
||||
type: ScaleTypeName,
|
||||
field: string,
|
||||
data?: IParseDataItem[],
|
||||
) {
|
||||
|
|
|
@ -54,6 +54,24 @@ export default class RegisterStyleAttributePlugin implements ILayerPlugin {
|
|||
},
|
||||
});
|
||||
|
||||
styleAttributeService.registerStyleAttribute({
|
||||
name: 'filter',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'filter',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 1,
|
||||
update: (feature: IEncodeFeature, featureIdx: number) => {
|
||||
const { filter } = feature;
|
||||
return filter ? [1] : [0];
|
||||
},
|
||||
},
|
||||
});
|
||||
styleAttributeService.registerStyleAttribute({
|
||||
name: 'color',
|
||||
type: AttributeType.Attribute,
|
||||
|
|
|
@ -31,6 +31,7 @@ export default class ShaderUniformPlugin implements ILayerPlugin {
|
|||
public apply(layer: ILayer) {
|
||||
layer.hooks.beforeRender.tap('ShaderUniformPlugin', () => {
|
||||
// 重新计算坐标系参数
|
||||
|
||||
this.coordinateSystemService.refresh();
|
||||
|
||||
const { width, height } = this.rendererService.getViewportSize();
|
||||
|
|
|
@ -21,23 +21,47 @@ export default class UpdateStyleAttributePlugin implements ILayerPlugin {
|
|||
styleAttributeService,
|
||||
}: { styleAttributeService: IStyleAttributeService },
|
||||
) {
|
||||
layer.hooks.init.tap('UpdateStyleAttributePlugin', () => {
|
||||
this.updateStyleAtrribute(layer, { styleAttributeService });
|
||||
});
|
||||
|
||||
layer.hooks.beforeRenderData.tap('styleAttributeService', (flag) => {
|
||||
if (flag) {
|
||||
// styleAttributeService.createAttributesAndIndices(
|
||||
// layer.getEncodedData(),
|
||||
// );
|
||||
layer.layerModelNeedUpdate = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
layer.hooks.beforeRender.tap('UpdateStyleAttributePlugin', () => {
|
||||
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
|
||||
attributes
|
||||
.filter((attribute) => attribute.needRegenerateVertices)
|
||||
.forEach((attribute) => {
|
||||
// 精确更新某个/某些 feature(s),需要传入 featureIdx
|
||||
styleAttributeService.updateAttributeByFeatureRange(
|
||||
attribute.name,
|
||||
layer.getEncodedData(), // 获取经过 mapping 最新的数据
|
||||
attribute.featureRange.startIndex,
|
||||
attribute.featureRange.endIndex,
|
||||
);
|
||||
attribute.needRegenerateVertices = false;
|
||||
this.logger.debug(
|
||||
`regenerate vertex attributes: ${attribute.name} finished`,
|
||||
);
|
||||
});
|
||||
this.updateStyleAtrribute(layer, { styleAttributeService });
|
||||
});
|
||||
}
|
||||
|
||||
private updateStyleAtrribute(
|
||||
layer: ILayer,
|
||||
{
|
||||
styleAttributeService,
|
||||
}: { styleAttributeService: IStyleAttributeService },
|
||||
) {
|
||||
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
|
||||
attributes
|
||||
.filter((attribute) => attribute.needRegenerateVertices)
|
||||
.forEach((attribute) => {
|
||||
// 精确更新某个/某些 feature(s),需要传入 featureIdx
|
||||
styleAttributeService.updateAttributeByFeatureRange(
|
||||
attribute.name,
|
||||
layer.getEncodedData(), // 获取经过 mapping 最新的数据
|
||||
attribute.featureRange.startIndex,
|
||||
attribute.featureRange.endIndex,
|
||||
);
|
||||
attribute.needRegenerateVertices = false;
|
||||
this.logger.debug(
|
||||
`regenerate vertex attributes: ${attribute.name} finished`,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,11 +20,15 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
|
|||
};
|
||||
}
|
||||
protected renderModels() {
|
||||
this.models.forEach((model) =>
|
||||
if (this.layerModelNeedUpdate) {
|
||||
this.models = this.layerModel.buildModels();
|
||||
this.layerModelNeedUpdate = false;
|
||||
}
|
||||
this.models.forEach((model) => {
|
||||
model.draw({
|
||||
uniforms: this.layerModel.getUninforms(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -59,4 +63,9 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
|
|||
return 'text';
|
||||
}
|
||||
}
|
||||
|
||||
private updateData() {
|
||||
// const bounds = this.mapService.getBounds();
|
||||
// console.log(bounds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
AttributeType,
|
||||
gl,
|
||||
IAttribute,
|
||||
IElements,
|
||||
IEncodeFeature,
|
||||
IModel,
|
||||
IModelUniform,
|
||||
|
@ -29,6 +31,17 @@ export default class FillModel extends BaseModel {
|
|||
};
|
||||
}
|
||||
|
||||
public getAttribute(): {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
};
|
||||
elements: IElements;
|
||||
} {
|
||||
return this.styleAttributeService.createAttributesAndIndices(
|
||||
this.layer.getEncodedData(),
|
||||
PointFillTriangulation,
|
||||
);
|
||||
}
|
||||
public buildModels(): IModel[] {
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
|
|
|
@ -130,10 +130,16 @@ export default class AMapService
|
|||
const amapBound = this.map.getBounds().toBounds();
|
||||
const NE = amapBound.getNorthEast();
|
||||
const SW = amapBound.getSouthWest();
|
||||
const center = this.getCenter();
|
||||
const maxlng =
|
||||
center.lng > NE.getLng() || center.lng < SW.getLng()
|
||||
? 180 - NE.getLng()
|
||||
: NE.getLng();
|
||||
const minlng = center.lng < SW.getLng() ? SW.getLng() - 180 : SW.getLng();
|
||||
// 兼容 Mapbox,统一返回西南、东北
|
||||
return [
|
||||
[SW.getLng(), SW.getLat()],
|
||||
[NE.getLng(), NE.getLat()],
|
||||
[minlng, SW.getLat()],
|
||||
[maxlng, NE.getLat()],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@ export const MapTheme: {
|
|||
normal: 'mapbox://styles/mapbox/streets-v11',
|
||||
blank: {
|
||||
version: 8,
|
||||
sprite: 'https://lzxue.github.io/font-glyphs/sprite/sprite',
|
||||
glyphs:
|
||||
'https://gw.alipayobjects.com/os/antvdemo/assets/mapbox/glyphs/{fontstack}/{range}.pbf',
|
||||
sources: {},
|
||||
layers: [
|
||||
{
|
||||
|
|
|
@ -52,15 +52,15 @@ export default class ReglRendererService implements IRendererService {
|
|||
// TODO: use extensions
|
||||
extensions: [
|
||||
'OES_element_index_uint',
|
||||
'EXT_shader_texture_lod', // IBL
|
||||
// 'EXT_shader_texture_lod', // IBL 兼容性问题
|
||||
'OES_standard_derivatives', // wireframe
|
||||
'OES_texture_float', // shadow map
|
||||
// 'OES_texture_float', // shadow map 兼容性问题
|
||||
'WEBGL_depth_texture',
|
||||
'angle_instanced_arrays',
|
||||
'EXT_texture_filter_anisotropic', // VSM shadow map
|
||||
],
|
||||
optionalExtensions: ['oes_texture_float_linear'],
|
||||
// profile: true,
|
||||
// optionalExtensions: ['oes_texture_float_linear'],
|
||||
profile: true,
|
||||
onDone: (err: Error | null, r?: regl.Regl | undefined): void => {
|
||||
if (err || !r) {
|
||||
reject(err);
|
||||
|
@ -131,6 +131,13 @@ export default class ReglRendererService implements IRendererService {
|
|||
}) => {
|
||||
// use WebGL context directly
|
||||
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#unsafe-escape-hatch
|
||||
const renderCanvas = this.$container?.getElementsByTagName('canvas')[0];
|
||||
if (renderCanvas) {
|
||||
renderCanvas.width = width;
|
||||
renderCanvas.height = height;
|
||||
renderCanvas.style.width = width / 2 + 'px';
|
||||
renderCanvas.style.height = height / 2 + 'px';
|
||||
}
|
||||
this.gl._gl.viewport(x, y, width, height);
|
||||
this.gl._refresh();
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
|||
import Source from '../src/source';
|
||||
import Point from './data/point';
|
||||
import Polygon from './data/polygon';
|
||||
|
||||
describe('source constructor', () => {
|
||||
|
@ -11,4 +12,14 @@ describe('source constructor', () => {
|
|||
30.60807236997211,
|
||||
]);
|
||||
});
|
||||
it('source.cluster', () => {
|
||||
const source = new Source(Point, {
|
||||
cluster: true,
|
||||
clusterOptions: {
|
||||
method: 'sum',
|
||||
field: 'mag',
|
||||
},
|
||||
});
|
||||
source.updateClusterData(2, [10, 0, 130, 75]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,26 @@
|
|||
import { IParserCfg, IParserData, ISourceCFG, ITransform } from '@antv/l7-core';
|
||||
import {
|
||||
IClusterOptions,
|
||||
IParserCfg,
|
||||
IParserData,
|
||||
ISourceCFG,
|
||||
ITransform,
|
||||
} from '@antv/l7-core';
|
||||
import { extent } from '@antv/l7-utils';
|
||||
import { BBox, FeatureCollection, Geometries, Properties } from '@turf/helpers';
|
||||
import {
|
||||
BBox,
|
||||
Feature,
|
||||
FeatureCollection,
|
||||
Geometries,
|
||||
Properties,
|
||||
} from '@turf/helpers';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { cloneDeep, isFunction, isString } from 'lodash';
|
||||
import Supercluster from 'supercluster';
|
||||
import { SyncHook } from 'tapable';
|
||||
import { getParser, getTransform } from './';
|
||||
import { statMap } from './utils/statistics';
|
||||
import { getColumn } from './utils/util';
|
||||
|
||||
export default class Source extends EventEmitter {
|
||||
public data: IParserData;
|
||||
|
||||
|
@ -18,12 +34,24 @@ export default class Source extends EventEmitter {
|
|||
};
|
||||
public parser: IParserCfg = { type: 'geojson' };
|
||||
public transforms: ITransform[] = [];
|
||||
public cluster: boolean = false;
|
||||
public clusterOptions: Partial<IClusterOptions> = {
|
||||
enable: false,
|
||||
radius: 40,
|
||||
maxZoom: 20,
|
||||
zoom: -99,
|
||||
method: 'count',
|
||||
};
|
||||
|
||||
// 原始数据
|
||||
private originData: any;
|
||||
private rawData: any;
|
||||
|
||||
private clusterIndex: Supercluster;
|
||||
|
||||
constructor(data: any, cfg?: ISourceCFG) {
|
||||
super();
|
||||
this.data = cloneDeep(data);
|
||||
this.rawData = cloneDeep(data);
|
||||
this.originData = data;
|
||||
if (cfg) {
|
||||
if (cfg.parser) {
|
||||
|
@ -32,15 +60,69 @@ export default class Source extends EventEmitter {
|
|||
if (cfg.transforms) {
|
||||
this.transforms = cfg.transforms;
|
||||
}
|
||||
this.cluster = cfg.cluster || false;
|
||||
if (cfg.clusterOptions) {
|
||||
this.cluster = true;
|
||||
this.clusterOptions = {
|
||||
...this.clusterOptions,
|
||||
...cfg.clusterOptions,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.hooks.init.tap('parser', () => {
|
||||
this.excuteParser();
|
||||
});
|
||||
this.hooks.init.tap('cluster', () => {
|
||||
this.initCluster();
|
||||
});
|
||||
this.hooks.init.tap('transform', () => {
|
||||
this.executeTrans();
|
||||
});
|
||||
this.init();
|
||||
}
|
||||
|
||||
public updateClusterData(
|
||||
zoom: number,
|
||||
bbox: [number, number, number, number],
|
||||
): void {
|
||||
const { method = 'sum', field } = this.clusterOptions;
|
||||
let data = this.clusterIndex.getClusters(bbox, zoom);
|
||||
this.clusterOptions.bbox = bbox;
|
||||
this.clusterOptions.zoom = zoom;
|
||||
data.forEach((p) => {
|
||||
if (!p.id) {
|
||||
p.properties.point_count = 1;
|
||||
}
|
||||
});
|
||||
if (field || isFunction(method)) {
|
||||
data = data.map((item) => {
|
||||
const id = item.id as number;
|
||||
if (id) {
|
||||
const points = this.clusterIndex.getLeaves(id, Infinity);
|
||||
const properties = points.map((d) => d.properties);
|
||||
let statNum;
|
||||
if (isString(method) && field) {
|
||||
const column = getColumn(properties, field);
|
||||
statNum = statMap[method](column);
|
||||
}
|
||||
if (isFunction(method)) {
|
||||
statNum = method(properties);
|
||||
}
|
||||
item.properties.stat = statNum;
|
||||
} else {
|
||||
item.properties.point_count = 1;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
this.data = getParser('geojson')({
|
||||
type: 'FeatureCollection',
|
||||
features: data,
|
||||
});
|
||||
this.executeTrans();
|
||||
}
|
||||
|
||||
private excuteParser(): void {
|
||||
const parser = this.parser;
|
||||
const type: string = parser.type || 'geojson';
|
||||
|
@ -60,6 +142,20 @@ export default class Source extends EventEmitter {
|
|||
Object.assign(this.data, data);
|
||||
});
|
||||
}
|
||||
|
||||
private initCluster() {
|
||||
if (!this.cluster) {
|
||||
return;
|
||||
}
|
||||
const { radius, minZoom = 0, maxZoom } = this.clusterOptions;
|
||||
this.clusterIndex = new Supercluster({
|
||||
radius,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
});
|
||||
this.clusterIndex.load(this.rawData.features);
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.hooks.init.call(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
function max(x: number[]) {
|
||||
if (x.length === 0) {
|
||||
throw new Error('max requires at least one data point');
|
||||
}
|
||||
|
||||
let value = x[0];
|
||||
for (let i = 1; i < x.length; i++) {
|
||||
// On the first iteration of this loop, max is
|
||||
// undefined and is thus made the maximum element in the array
|
||||
if (x[i] > value) {
|
||||
value = x[i];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function min(x: number[]) {
|
||||
if (x.length === 0) {
|
||||
throw new Error('min requires at least one data point');
|
||||
}
|
||||
|
||||
let value = x[0];
|
||||
for (let i = 1; i < x.length; i++) {
|
||||
// On the first iteration of this loop, min is
|
||||
// undefined and is thus made the minimum element in the array
|
||||
if (x[i] < value) {
|
||||
value = x[i];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function sum(x: number[]) {
|
||||
// If the array is empty, we needn't bother computing its sum
|
||||
if (x.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initializing the sum as the first number in the array
|
||||
let sumNum = x[0];
|
||||
|
||||
// Keeping track of the floating-point error correction
|
||||
let correction = 0;
|
||||
|
||||
let transition;
|
||||
|
||||
for (let i = 1; i < x.length; i++) {
|
||||
transition = sumNum + x[i] * 1;
|
||||
|
||||
// Here we need to update the correction in a different fashion
|
||||
// if the new absolute value is greater than the absolute sum
|
||||
if (Math.abs(sumNum) >= Math.abs(x[i])) {
|
||||
correction += sumNum - transition + x[i];
|
||||
} else {
|
||||
correction += x[i] - transition + sumNum;
|
||||
}
|
||||
|
||||
sumNum = transition;
|
||||
}
|
||||
|
||||
// Returning the corrected sum
|
||||
return sumNum + correction * 1;
|
||||
}
|
||||
function mean(x: number[]) {
|
||||
if (x.length === 0) {
|
||||
throw new Error('mean requires at least one data point');
|
||||
}
|
||||
return sum(x) / x.length;
|
||||
}
|
||||
|
||||
export { sum, max, min, mean };
|
||||
export const statMap: { [key: string]: any } = {
|
||||
min,
|
||||
max,
|
||||
mean,
|
||||
sum,
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
interface IDataItem {
|
||||
[key: string]: any;
|
||||
}
|
||||
export function getColumn(data: IDataItem[], columnName: string) {
|
||||
return data.map((item: IDataItem) => {
|
||||
return item[columnName] * 1;
|
||||
});
|
||||
}
|
|
@ -128,3 +128,17 @@ export function setTransform(el: ELType, value: string) {
|
|||
// @ts-ignore
|
||||
el.style[transformProp] = value;
|
||||
}
|
||||
|
||||
export function triggerResize() {
|
||||
if (typeof Event === 'function') {
|
||||
// modern browsers
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
} else {
|
||||
// for IE and other old browsers
|
||||
// causes deprecation warning on modern browsers
|
||||
const evt = window.document.createEvent('UIEvents');
|
||||
// @ts-ignore
|
||||
evt.initUIEvent('resize', true, false, window, 0);
|
||||
window.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
|||
import Arc2DLineDemo from './components/Arc2DLine';
|
||||
import ArcLineDemo from './components/Arcline';
|
||||
import Column from './components/column';
|
||||
import DataUpdate from './components/data_update';
|
||||
import HeatMapDemo from './components/HeatMap';
|
||||
import GridHeatMap from './components/HeatmapGrid';
|
||||
import LineLayer from './components/Line';
|
||||
|
@ -16,6 +17,7 @@ import RasterLayerDemo from './components/RasterLayer';
|
|||
// @ts-ignore
|
||||
storiesOf('图层', module)
|
||||
.add('点图层', () => <PointDemo />)
|
||||
.add('数据更新', () => <DataUpdate />)
|
||||
.add('3D点', () => <Point3D />)
|
||||
.add('Column', () => <Column />)
|
||||
.add('图片标注', () => <PointImage />)
|
||||
|
|
|
@ -16,34 +16,37 @@ export default class Point3D extends React.Component {
|
|||
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
|
||||
);
|
||||
const pointsData = await response.json();
|
||||
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
center: [120.19382669582967, 30.258134],
|
||||
pitch: 0,
|
||||
style: 'light',
|
||||
style: 'dark',
|
||||
zoom: 3,
|
||||
}),
|
||||
// map: new Mapbox({
|
||||
// center: [120.19382669582967, 30.258134],
|
||||
// pitch: 0,
|
||||
// style: 'mapbox://styles/mapbox/streets-v9',
|
||||
// zoom: 1,
|
||||
// }),
|
||||
});
|
||||
this.scene = scene;
|
||||
|
||||
const pointLayer = new PointLayer({})
|
||||
.source(pointsData)
|
||||
.shape('circle')
|
||||
.size('mag', [1, 25])
|
||||
.color('mag', (mag) => {
|
||||
return mag > 4.5 ? '#5B8FF9' : '#5CCEA1';
|
||||
.source(pointsData, {
|
||||
cluster: true,
|
||||
})
|
||||
.shape('circle')
|
||||
.scale('point_count', {
|
||||
type: 'quantile',
|
||||
})
|
||||
.filter('point_count', (point_count: number) => {
|
||||
return point_count > 1;
|
||||
})
|
||||
.size('point_count', [5, 10, 15, 20, 25])
|
||||
.color('red')
|
||||
.style({
|
||||
opacity: 0.3,
|
||||
strokeWidth: 1,
|
||||
});
|
||||
scene.addLayer(pointLayer);
|
||||
this.scene = scene;
|
||||
console.log(pointLayer);
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
|
|
@ -22,16 +22,18 @@ export default class Point3D extends React.Component {
|
|||
}),
|
||||
});
|
||||
const pointLayer = new PointLayer({
|
||||
enablePicking: true,
|
||||
enableHighlight: true,
|
||||
enableTAA: true,
|
||||
enablePicking: false,
|
||||
enableHighlight: false,
|
||||
enableTAA: false,
|
||||
onHover: (pickedFeature: any) => {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.log('Scene4', pickedFeature.feature.name);
|
||||
},
|
||||
});
|
||||
pointLayer
|
||||
.source(data)
|
||||
.source(data, {
|
||||
cluster: true,
|
||||
})
|
||||
.color('red')
|
||||
.shape('cylinder')
|
||||
.size([15, 10]);
|
||||
|
@ -39,7 +41,6 @@ export default class Point3D extends React.Component {
|
|||
scene.render();
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import { PointLayer, Scene } from '@antv/l7';
|
||||
import { GaodeMap, Mapbox } from '@antv/l7-maps';
|
||||
import * as React from 'react';
|
||||
// @ts-ignore
|
||||
export default class DataUpdate extends React.Component {
|
||||
// @ts-ignore
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new GaodeMap({
|
||||
style: 'light',
|
||||
pitch: 0,
|
||||
center: [120.19382669582967, 30.258134],
|
||||
zoom: 11,
|
||||
}),
|
||||
});
|
||||
this.scene = scene;
|
||||
const radius = 0.1;
|
||||
|
||||
function pointOnCircle(angle: number) {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
features: [
|
||||
{
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [
|
||||
120.19382669582967 + Math.cos(angle) * radius,
|
||||
30.258134 + Math.sin(angle) * radius,
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
const layer = new PointLayer({})
|
||||
.source(pointOnCircle(0))
|
||||
.shape('circle')
|
||||
.size(15) // default 1
|
||||
.color('#2F54EB')
|
||||
.style({
|
||||
strokeColor: '#fff',
|
||||
strokeWidth: 2,
|
||||
opacity: 1,
|
||||
});
|
||||
scene.addLayer(layer);
|
||||
function animateMarker(timestamp: number) {
|
||||
layer.setData(pointOnCircle(timestamp / 1000));
|
||||
|
||||
scene.render();
|
||||
|
||||
// setTimeout(animateMarker, 100);
|
||||
requestAnimationFrame(animateMarker);
|
||||
}
|
||||
layer.on('inited', () => {
|
||||
animateMarker(0);
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -28,5 +28,6 @@
|
|||
"useCache": false
|
||||
},
|
||||
"include": ["packages"],
|
||||
"exclude": ["node_modules", "packages/**/dist"],
|
||||
}
|
||||
"exclude": ["node_modules", "packages/**/dist"]
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
|
||||
{
|
||||
"extends": ["./tslint.json"],
|
||||
"rules": {
|
||||
"no-implicit-dependencies": true
|
||||
},
|
||||
"linterOptions": {
|
||||
"exclude": ["**/*.d.ts", "**/*.{test,story}.ts{,x}"]
|
||||
"exclude": ["**/*.d.ts", "**/data/*.ts", "**/*.{test,story}.ts{,x}"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
"no-implicit-dependencies": [false, "dev"]
|
||||
},
|
||||
"linterOptions": {
|
||||
"exclude": ["**/*.d.ts"]
|
||||
"exclude": ["**/*.d.ts", "**/data/*.ts"]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue