Merge pull request #119 from antvis/source

Source
This commit is contained in:
@thinkinggis 2019-12-12 20:52:30 +08:00 committed by GitHub
commit cf7bd082fe
40 changed files with 811 additions and 456 deletions

1
.gitignore vendored
View File

@ -78,3 +78,4 @@ git_log.sh
node_modules/ node_modules/
packages/l7/package_bak.json packages/l7/package_bak.json
stories/Test

View File

@ -25,6 +25,14 @@ shape 支持
``` ```
## source
点数据类型,根据经纬点绘制图形,目前支持三种数据结构
- [GeoJOSN]('../source/geojson/#point')
- [CSV]()
- [JSON](../source/json/#点数据)
**图片标注** **图片标注**
通过 `Scene.addImage()` 可以添加图片资源, 通过 `Scene.addImage()` 可以添加图片资源,

53
docs/api/source/csv.en.md Normal file
View File

@ -0,0 +1,53 @@
---
title: CSV
order: 3
---
L7 支持 CSV 以逗号分隔的 CSV 数据加载。
CSV 是文本数据结构,很难表达复杂的地理数据结构,因此 CSV 仅支持两种数据结构
- 点数据 需要指定经度,纬度坐标
- 线段,弧线数据 需要指定 起始点的 经度,纬度坐标
## parser
- type string 必选 json
- x string 点数据表示 经度
- y string 点数据表示 纬度
- x1 string 经度
- x2 string 纬度
### 点数据通过 CSV 加载
```javascript
layer.source(data, {
parser: {
type: 'csv',
x: 'lng',
y: 'lat',
},
});
```
[CSV 数据 demo 示例](../../../examples/point/bubble#scatter)
### 线段弧线数据通过 CSV 加载
```javascript
layer.source(
data,
{
parser:{
type:'csv',
x:'lng1',
y:'lat1' ,
x1:'lng1',
y1:'lat2' ,
}
}
})
```
[CSV 线段数据 demo 示例](../../../examples/gallery/basic#arcCircle)

53
docs/api/source/csv.zh.md Normal file
View File

@ -0,0 +1,53 @@
---
title: CSV
order: 3
---
L7 支持 CSV 以逗号分隔的 CSV 数据加载。
CSV 是文本数据结构,很难表达复杂的地理数据结构,因此 CSV 仅支持两种数据结构
- 点数据 需要指定经度,纬度坐标
- 线段,弧线数据 需要指定 起始点的 经度,纬度坐标
## parser
- type string 必选 json
- x string 点数据表示 经度
- y string 点数据表示 纬度
- x1 string 经度
- x2 string 纬度
### 点数据通过 CSV 加载
```javascript
layer.source(data, {
parser: {
type: 'csv',
x: 'lng',
y: 'lat',
},
});
```
[CSV 数据 demo 示例](../../../examples/point/bubble#scatter)
### 线段弧线数据通过 CSV 加载
```javascript
layer.source(
data,
{
parser:{
type:'csv',
x:'lng1',
y:'lat1' ,
x1:'lng1',
y1:'lat2' ,
}
}
})
```
[CSV 线段数据 demo 示例](../../../examples/gallery/basic#arcCircle)

View File

@ -0,0 +1,25 @@
---
title: Image
order: 4
---
Image 数据主要用于在地图根据经纬度范围添加图图片,不如一幅纸制地图扫描版你要放在地图显示。
## parser
- type: image
- extent: 图像的经纬度范围 [minlng, minlat,maxLng, maxLat]
根据图片的经纬度范围,将图片添加到地图上。
```javascript
layer.source(
'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
{
parser: {
type: 'image',
extent: [121.168, 30.2828, 121.384, 30.4219],
},
},
);
```

View File

@ -0,0 +1,25 @@
---
title: Image
order: 4
---
Image 数据主要用于在地图根据经纬度范围添加图图片,不如一幅纸制地图扫描版你要放在地图显示。
## parser
- type: image
- extent: 图像的经纬度范围 [minlng, minlat,maxLng, maxLat]
根据图片的经纬度范围,将图片添加到地图上。
```javascript
layer.source(
'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
{
parser: {
type: 'image',
extent: [121.168, 30.2828, 121.384, 30.4219],
},
},
);
```

View File

@ -1,6 +1,6 @@
--- ---
title: JSON title: JSON
order: 1 order: 2
--- ---
GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。 GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。

View File

@ -1,6 +1,6 @@
--- ---
title: JSON title: JSON
order: 1 order: 2
--- ---
GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。 GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。
@ -39,6 +39,8 @@ layer.source(data, {
}); });
``` ```
[JOSN 数据 demo 示例](../../../examples/gallery/basic)
### 通用解析方式 ### 通用解析方式
可也解析任意复杂的点,线面 可也解析任意复杂的点,线面

View File

@ -40,100 +40,25 @@ layer.source(data);
#### JSON #### JSON
[JSON 数据格式解析](../json) [JSON 数据格式解析](./json)
#### csv #### csv
点,线数据配置项同 json 数据类型 [CSV 数据格式解析](./csv)
```javascript 栅格数据类型
layer.source(data, {
parser: {
type: 'csv',
x: 'lng1',
y: 'lat1',
x1: 'lng1',
y1: 'lat2',
},
});
```
**栅格数据类型 **
#### image #### image
根据图片的经纬度范围,将图片添加到地图上。  配置项 [Image 数据格式解析](./image)
- type: image
- extent: 图像的经纬度范围 []
```javascript
layer.source(
'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
{
parser: {
type: 'image',
extent: [121.168, 30.2828, 121.384, 30.4219],
},
},
);
```
#### raster
栅格数据类型,主要表示遥感数据类型 data 栅格数据的二维矩阵数据 parser 配置项
- type  raster
- width  数据宽度二维矩阵 columns
- height 数据高度
- min 数据最大值
- max 数据最小值
- extent 经纬度范围
```javascript
source(values, {
parser: {
type: 'raster',
width: n,
height: m,
min: 0,
max: 8000,
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
},
});
```
### transforms ### transforms
目前支持三种数据处理方法 mapgridhexagon transform 配置项 目前支持两种热力图使用的数据处理方法 gridhexagon transform 配置项
- type 数据处理类型 - type 数据处理类型
- tansform cfg  数据处理配置项 - tansform cfg  数据处理配置项
#### map
数据处理,支持自定义 callback 函数
- 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;
},
},
],
});
```
#### grid #### grid
生成方格网布局,根据数据字段统计,主要在网格热力图中使用 生成方格网布局,根据数据字段统计,主要在网格热力图中使用
@ -163,4 +88,4 @@ layer.source(data, {
- type: 'hexagon', - type: 'hexagon',
- size: 网格半径 - size: 网格半径
- field: 数据统计字段 - field: 数据统计字段
- method:聚合方法   count,max,min,sum,mean5 个统计维度 - method:聚合方法   count,max,min,sum,mean 5 个统计维度

View File

@ -7,6 +7,13 @@ order: 0
source 地理数据处理模块主要包含数据解析parser),和数据处理(transform); source 地理数据处理模块主要包含数据解析parser),和数据处理(transform);
- data
- option
- cluster **boolean** 是否聚合
- clusterOption 聚合配置项
- parser 数据解析配置
- transforms 数据处理配置
### parser ### parser
不同数据类型处理成统一数据格式。矢量数据包括 GeoJON, CSVJson 等不同数据格式,栅格数据,包括 RasterImage 数据。将来还会支持瓦片格式数据。 不同数据类型处理成统一数据格式。矢量数据包括 GeoJON, CSVJson 等不同数据格式,栅格数据,包括 RasterImage 数据。将来还会支持瓦片格式数据。
@ -23,6 +30,14 @@ source 地理数据处理模块主要包含数据解析parser),和数据
## API ## API
### cluster 可选 可以只设置 cluster
### clusterOption 可选
- radius 聚合半径 **number** default 40
- minZoom: 最小聚合缩放等级 **number** default 0
- maxZoom: 最大聚合缩放等级 **number** default 16
### parser ### parser
**配置项** **配置项**
@ -40,100 +55,25 @@ layer.source(data);
#### JSON #### JSON
[JSON 数据格式解析](../json) [JSON 数据格式解析](./json)
#### csv #### csv
点,线数据配置项同 json 数据类型 [CSV 数据格式解析](./csv)
```javascript 栅格数据类型
layer.source(data, {
parser: {
type: 'csv',
x: 'lng1',
y: 'lat1',
x1: 'lng1',
y1: 'lat2',
},
});
```
**栅格数据类型 **
#### image #### image
根据图片的经纬度范围,将图片添加到地图上。  配置项 [Image 数据格式解析](./image)
- type: image
- extent: 图像的经纬度范围 []
```javascript
layer.source(
'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
{
parser: {
type: 'image',
extent: [121.168, 30.2828, 121.384, 30.4219],
},
},
);
```
#### raster
栅格数据类型,主要表示遥感数据类型 data 栅格数据的二维矩阵数据 parser 配置项
- type  raster
- width  数据宽度二维矩阵 columns
- height 数据高度
- min 数据最大值
- max 数据最小值
- extent 经纬度范围
```javascript
source(values, {
parser: {
type: 'raster',
width: n,
height: m,
min: 0,
max: 8000,
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
},
});
```
### transforms ### transforms
目前支持三种数据处理方法 mapgridhexagon transform 配置项 目前支持两种热力图使用的数据处理方法 gridhexagon transform 配置项
- type 数据处理类型 - type 数据处理类型
- tansform cfg  数据处理配置项 - tansform cfg  数据处理配置项
#### map
数据处理,支持自定义 callback 函数
- 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;
},
},
],
});
```
#### grid #### grid
生成方格网布局,根据数据字段统计,主要在网格热力图中使用 生成方格网布局,根据数据字段统计,主要在网格热力图中使用

View File

@ -15,8 +15,11 @@ Current version: ![L7 2.0版本号](https://badgen.net/npm/v/@antv/l7/beta)
Include the L7 JS JavaScript <head> of your HTML file. Include the L7 JS JavaScript <head> of your HTML file.
:warning: 如果需要引用第三方地图API请确保在先引入第三方API然后引入L7
```html ```html
<head> <head>
<! --引入第三方地图JSAPI-->
<script src='https://gw.alipayobjects.com/os/antv/pkg/_antv.l7-2.0.0-beta.19/dist/l7.js'> <script src='https://gw.alipayobjects.com/os/antv/pkg/_antv.l7-2.0.0-beta.19/dist/l7.js'>
</script> </script>
</head> </head>
@ -41,6 +44,7 @@ npm install --save @antv/l7-maps;
``` ```
### 初始化地图 ### 初始化地图
#### 使用 高德 底图 #### 使用 高德 底图
@ -80,3 +84,76 @@ const scene = new Scene({
``` ```
### React中使用
React 组件待开发期待和大家共建l7-react 目前可以暂时以 Submodule 方式使用
```
import { Scene, PolygonLayer } from '@antv/l7';
import { AMap } from '@antv/l7-maps';
import * as React from 'react';
export default class AMapExample extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/d2e0e930-fd44-4fca-8872-c1037b0fee7b.json',
);
const scene = new Scene({
id: 'map',
map: new AMap({
center: [110.19382669582967, 50.258134],
pitch: 0,
style: 'dark',
zoom: 3,
token: 'pg.xxx', // 高德或者 Mapbox 的 token
}),
});
const layer = new PolygonLayer({});
layer
.source(await response.json())
.size('name', [0, 10000, 50000, 30000, 100000])
.color('name', [
'#2E8AE6',
'#69D1AB',
'#DAF291',
'#FFD591',
'#FF7A45',
'#CF1D49',
])
.shape('fill')
.style({
opacity: 0.8,
});
scene.addLayer(layer);
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}
```
⚠️组件 Unmount 时需要通过 scene.destroy() 手动销毁场景。
更多React使用 [示例查看](https://github.com/antvis/L7/tree/master/stories)
### Vue 欢迎补充

View File

@ -44,67 +44,71 @@ const scene = new Scene({
zoom: 3.802 zoom: 3.802
}) })
}); });
Promise.all([ addChart();
fetch( scene.render();
'https://gw.alipayobjects.com/os/basement_prod/5b772136-a1f4-4fc5-9a80-9f9974b4b182.json' function addChart() {
).then(d => d.json()), Promise.all([
fetch( fetch(
'https://gw.alipayobjects.com/os/basement_prod/f3c467a4-9ae0-4f08-bb5f-11f9c869b2cb.json' 'https://gw.alipayobjects.com/os/basement_prod/5b772136-a1f4-4fc5-9a80-9f9974b4b182.json'
).then(d => d.json()) ).then(d => d.json()),
]).then(function onLoad([ center, population ]) { fetch(
const popobj = {}; 'https://gw.alipayobjects.com/os/basement_prod/f3c467a4-9ae0-4f08-bb5f-11f9c869b2cb.json'
population.forEach(element => { ).then(d => d.json())
popobj[element.Code] = ]).then(function onLoad([ center, population ]) {
element['Population, female (% of total) (% of total)']; const popobj = {};
}); population.forEach(element => {
// 数据绑定 popobj[element.Code] =
element['Population, female (% of total) (% of total)'];
});
// 数据绑定
center.features = center.features.map(fe => { center.features = center.features.map(fe => {
fe.properties.female = popobj[fe.properties.id] * 1 || 0; fe.properties.female = popobj[fe.properties.id] * 1 || 0;
return fe; return fe;
}); });
center.features.forEach(point => { center.features.forEach(point => {
const el = document.createElement('div'); const el = document.createElement('div');
const coord = point.geometry.coordinates; const coord = point.geometry.coordinates;
const v = point.properties.female * 1; const v = point.properties.female * 1;
if (v < 1 || (v > 46 && v < 54)) { if (v < 1 || (v > 46 && v < 54)) {
return; return;
}
const size = 60;
const data = [
{
type: '男性',
value: 100.0 - v.toFixed(2)
},
{
type: '女性',
value: v.toFixed(2) * 1
} }
]; const size = 60;
const chart = new G2.Chart({ const data = [
container: el, {
width: size, type: '男性',
height: size, value: 100.0 - v.toFixed(2)
render: 'svg', },
padding: 0 {
type: '女性',
value: v.toFixed(2) * 1
}
];
const chart = new G2.Chart({
container: el,
width: size,
height: size,
render: 'svg',
padding: 0
});
chart.source(data);
chart.legend(false);
chart.tooltip(false);
chart.coord('theta', {
radius: 0.9,
innerRadius: 0.6
});
chart
.intervalStack()
.position('value')
.color('type', [ '#5CCEA1', '#5B8FF9' ])
.opacity(1);
chart.render();
const marker = new Marker({ element: el }).setLnglat({
lng: coord[0],
lat: coord[1]
});
scene.addMarker(marker);
}); });
chart.source(data);
chart.legend(false);
chart.tooltip(false);
chart.coord('theta', {
radius: 0.9,
innerRadius: 0.6
});
chart
.intervalStack()
.position('value')
.color('type', [ '#5CCEA1', '#5B8FF9' ])
.opacity(1);
chart.render();
const marker = new Marker({ element: el }).setLnglat({
lng: coord[0],
lat: coord[1]
});
scene.addMarker(marker);
}); });
}); }

View File

@ -11,7 +11,7 @@ export default class MarkerService implements IMarkerService {
private markers: IMarker[] = []; private markers: IMarker[] = [];
private unAddMarkers: IMarker[] = []; private unAddMarkers: IMarker[] = [];
public addMarker(marker: IMarker): void { public addMarker(marker: IMarker): void {
if (!this.mapsService.map && this.mapsService.getMarkerContainer()) { if (this.mapsService.map && this.mapsService.getMarkerContainer()) {
this.markers.push(marker); this.markers.push(marker);
marker.addTo(this.scene); marker.addTo(this.scene);
} else { } else {

View File

@ -47,9 +47,13 @@ const defaultLayerConfig: Partial<ILayerConfig> = {
minZoom: 0, minZoom: 0,
maxZoom: 20, maxZoom: 20,
visible: true, visible: true,
autoFit: false,
zIndex: 0, zIndex: 0,
enableMultiPassRenderer: false, pickedFeatureID: -1,
enablePicking: false, enableMultiPassRenderer: true,
enablePicking: true,
active: false,
activeColor: 'red',
enableHighlight: false, enableHighlight: false,
highlightColor: 'red', highlightColor: 'red',
enableTAA: false, enableTAA: false,

View File

@ -8,7 +8,7 @@ export interface IInteractionService {
destroy(): void; destroy(): void;
on( on(
eventName: InteractionEvent, eventName: InteractionEvent,
callback: (params: { x: number; y: number }) => void, callback: (params: { x: number; y: number; type: string }) => void,
): void; ): void;
triggerHover({ x, y }: { x: number; y: number }): void; triggerHover({ x, y, type }: { x: number; y: number; type?: string }): void;
} }

View File

@ -5,7 +5,6 @@ import { TYPES } from '../../types';
import { ILogService } from '../log/ILogService'; import { ILogService } from '../log/ILogService';
import { IMapService } from '../map/IMapService'; import { IMapService } from '../map/IMapService';
import { IInteractionService, InteractionEvent } from './IInteractionService'; import { IInteractionService, InteractionEvent } from './IInteractionService';
/** /**
* L7 canvas WebGL Context * L7 canvas WebGL Context
* L7 canvas * L7 canvas
@ -49,8 +48,13 @@ export default class InteractionService extends EventEmitter
// hammertime.on('panmove', this.onPanmove); // hammertime.on('panmove', this.onPanmove);
// hammertime.on('panend', this.onPanend); // hammertime.on('panend', this.onPanend);
// hammertime.on('pinch', this.onPinch); // hammertime.on('pinch', this.onPinch);
$containter.addEventListener('mousemove', this.onHover); $containter.addEventListener('mousemove', this.onHover);
$containter.addEventListener('click', this.onHover);
$containter.addEventListener('mousedown', this.onHover);
$containter.addEventListener('mouseup', this.onHover);
$containter.addEventListener('dblclick', this.onHover);
$containter.addEventListener('contextmenu', this.onHover);
this.hammertime = hammertime; this.hammertime = hammertime;
// TODO: 根据场景注册事件到 L7 canvas 上 // TODO: 根据场景注册事件到 L7 canvas 上
@ -62,16 +66,21 @@ export default class InteractionService extends EventEmitter
const $containter = this.mapService.getMapContainer(); const $containter = this.mapService.getMapContainer();
if ($containter) { if ($containter) {
$containter.removeEventListener('mousemove', this.onHover); $containter.removeEventListener('mousemove', this.onHover);
$containter.removeEventListener('click', this.onHover);
$containter.removeEventListener('mousedown', this.onHover);
$containter.removeEventListener('mouseup', this.onHover);
$containter.removeEventListener('dblclick', this.onHover);
$containter.removeEventListener('contextmenu', this.onHover);
} }
} }
private onHover = ({ x, y }: MouseEvent) => { private onHover = ({ x, y, type }: MouseEvent) => {
const $containter = this.mapService.getMapContainer(); const $containter = this.mapService.getMapContainer();
if ($containter) { if ($containter) {
const { top, left } = $containter.getBoundingClientRect(); const { top, left } = $containter.getBoundingClientRect();
x -= left; x -= left;
y -= top; y -= top;
} }
this.emit(InteractionEvent.Hover, { x, y }); this.emit(InteractionEvent.Hover, { x, y, type });
}; };
} }

View File

@ -22,7 +22,13 @@ import {
StyleAttributeOption, StyleAttributeOption,
Triangulation, Triangulation,
} from './IStyleAttributeService'; } from './IStyleAttributeService';
export interface IDataState {
dataSourceNeedUpdate: boolean;
dataMappingNeedUpdate: boolean;
filterNeedUpdate: boolean;
featureScaleNeedUpdate: boolean;
StyleAttrNeedUpdate: boolean;
}
export interface ILayerModelInitializationOptions { export interface ILayerModelInitializationOptions {
moduleName: string; moduleName: string;
vertexShader: string; vertexShader: string;
@ -44,6 +50,10 @@ export interface IPickedFeature {
lnglat?: { lng: number; lat: number }; lnglat?: { lng: number; lat: number };
feature?: unknown; feature?: unknown;
} }
// 交互样式
export interface IActiveOption {
color: string | number[];
}
export interface ILayer { export interface ILayer {
id: string; // 一个场景中同一类型 Layer 可能存在多个 id: string; // 一个场景中同一类型 Layer 可能存在多个
@ -52,9 +62,11 @@ export interface ILayer {
zIndex: number; zIndex: number;
plugins: ILayerPlugin[]; plugins: ILayerPlugin[];
layerModelNeedUpdate: boolean; layerModelNeedUpdate: boolean;
dataPluginsState: { [key: string]: boolean }; dataState: IDataState; // 数据流状态
pickedFeatureID: number;
hooks: { hooks: {
init: SyncBailHook<void, boolean | void>; init: SyncBailHook<void, boolean | void>;
afterInit: SyncBailHook<void, boolean | void>;
beforeRenderData: SyncWaterfallHook<boolean | void>; beforeRenderData: SyncWaterfallHook<boolean | void>;
beforeRender: SyncBailHook<void, boolean | void>; beforeRender: SyncBailHook<void, boolean | void>;
afterRender: SyncHook<void>; afterRender: SyncHook<void>;
@ -87,7 +99,16 @@ export interface ILayer {
animate(option: IAnimateOption): ILayer; animate(option: IAnimateOption): ILayer;
// pattern(field: string, value: StyleAttributeOption): ILayer; // pattern(field: string, value: StyleAttributeOption): ILayer;
filter(field: string, value: StyleAttributeOption): ILayer; filter(field: string, value: StyleAttributeOption): ILayer;
// active(option: ActiveOption): ILayer; active(option: IActiveOption | boolean): ILayer;
setActive(
id: number | { x: number; y: number },
option?: IActiveOption,
): void;
select(option: IActiveOption | false): ILayer;
setSelect(
id: number | { x: number; y: number },
option?: IActiveOption,
): void;
style(options: unknown): ILayer; style(options: unknown): ILayer;
hide(): ILayer; hide(): ILayer;
show(): ILayer; show(): ILayer;
@ -100,22 +121,24 @@ export interface ILayer {
destroy(): void; destroy(): void;
source(data: any, option?: ISourceCFG): ILayer; source(data: any, option?: ISourceCFG): ILayer;
setData(data: any, option?: ISourceCFG): ILayer; setData(data: any, option?: ISourceCFG): ILayer;
fitBounds(): ILayer;
/** /**
* *
* @param plugin * @param plugin
*/ */
addPlugin(plugin: ILayerPlugin): ILayer; addPlugin(plugin: ILayerPlugin): ILayer;
getSource(): ISource; getSource(): ISource;
isSourceNeedUpdate(): boolean;
setSource(source: ISource): void; setSource(source: ISource): void;
setEncodedData(encodedData: IEncodeFeature[]): void; setEncodedData(encodedData: IEncodeFeature[]): void;
getEncodedData(): IEncodeFeature[]; getEncodedData(): IEncodeFeature[];
getScaleOptions(): IScaleOptions; getScaleOptions(): IScaleOptions;
/** /**
* *
*/ */
on(type: string, hander: (...args: any[]) => void): void; on(type: string, hander: (...args: any[]) => void): void;
off(type: string, hander: (...args: any[]) => void): void; off(type: string, hander: (...args: any[]) => void): void;
emit(type: string, hander: unknown): void;
once(type: string, hander: (...args: any[]) => void): void; once(type: string, hander: (...args: any[]) => void): void;
/** /**
* JSON Schema * JSON Schema
@ -126,6 +149,8 @@ export interface ILayer {
* 使 * 使
*/ */
pick(query: { x: number; y: number }): void; pick(query: { x: number; y: number }): void;
updateLayerConfig(configToUpdate: Partial<ILayerConfig | unknown>): void;
} }
/** /**
@ -160,6 +185,8 @@ export interface ILayerConfig {
maxZoom: number; maxZoom: number;
visible: boolean; visible: boolean;
zIndex: number; zIndex: number;
autoFit: boolean;
pickedFeatureID: number;
enableMultiPassRenderer: boolean; enableMultiPassRenderer: boolean;
passes: Array<string | [string, { [key: string]: unknown }]>; passes: Array<string | [string, { [key: string]: unknown }]>;
@ -175,6 +202,8 @@ export interface ILayerConfig {
* *
*/ */
highlightColor: string | number[]; highlightColor: string | number[];
active: boolean;
activeColor: string | number[];
/** /**
* TAA * TAA
*/ */

View File

@ -112,7 +112,18 @@ export default class PixelPickingPass<
* *
* TODO * TODO
*/ */
private pickFromPickingFBO = ({ x, y }: { x: number; y: number }) => { private pickFromPickingFBO = ({
x,
y,
type,
}: {
x: number;
y: number;
type: string;
}) => {
if (!this.layer.isVisible()) {
return;
}
const { const {
getViewportSize, getViewportSize,
readPixels, readPixels,
@ -152,9 +163,9 @@ export default class PixelPickingPass<
) { ) {
this.logger.debug('picked'); this.logger.debug('picked');
const pickedFeatureIdx = decodePickingColor(pickedColors); const pickedFeatureIdx = decodePickingColor(pickedColors);
const rawFeature = this.layer.getSource()?.data?.dataArray[ const rawFeature = this.layer
pickedFeatureIdx .getSource()
]; .getFeatureById(pickedFeatureIdx);
if (!rawFeature) { if (!rawFeature) {
// this.logger.error( // this.logger.error(
@ -162,7 +173,7 @@ export default class PixelPickingPass<
// ); // );
} else { } else {
// trigger onHover/Click callback on layer // trigger onHover/Click callback on layer
this.triggerHoverOnLayer({ x, y, feature: rawFeature }); this.triggerHoverOnLayer({ x, y, type, feature: rawFeature });
} }
} }
}); });
@ -175,27 +186,34 @@ export default class PixelPickingPass<
private triggerHoverOnLayer({ private triggerHoverOnLayer({
x, x,
y, y,
type,
feature, feature,
}: { }: {
x: number; x: number;
y: number; y: number;
type: string;
feature: unknown; feature: unknown;
}) { }) {
const { onHover, onClick } = this.layer.getLayerConfig(); const { onHover, onClick } = this.layer.getLayerConfig();
if (onHover) { // if (onHover) {
onHover({ // onHover({
x, // x,
y, // y,
feature, // feature,
}); // });
} // }
if (onClick) { // if (onClick) {
onClick({ // onClick({
x, // x,
y, // y,
feature, // feature,
}); // });
} // }
this.layer.emit(type, {
x,
y,
feature,
});
} }
/** /**

View File

@ -61,6 +61,8 @@ export interface ISource {
data: IParserData; data: IParserData;
cluster: boolean; cluster: boolean;
clusterOptions: Partial<IClusterOptions>; clusterOptions: Partial<IClusterOptions>;
updateClusterData(zoom: number): void;
getFeatureById(id: number): unknown;
} }
export interface IRasterCfg { export interface IRasterCfg {
extent: [number, number, number, number]; extent: [number, number, number, number];

View File

@ -5,6 +5,7 @@
"main": "lib/index.js", "main": "lib/index.js",
"module": "es/index.js", "module": "es/index.js",
"types": "es/index.d.ts", "types": "es/index.d.ts",
"unpkg": "dist/l7.js",
"sideEffects": true, "sideEffects": true,
"files": [ "files": [
"dist", "dist",

View File

@ -1,6 +1,8 @@
import { import {
gl, gl,
IActiveOption,
IAnimateOption, IAnimateOption,
IDataState,
IEncodeFeature, IEncodeFeature,
IFontService, IFontService,
IGlobalConfigService, IGlobalConfigService,
@ -44,12 +46,10 @@ import mergeJsonSchemas from 'merge-json-schemas';
import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable'; import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable';
import { normalizePasses } from '../plugins/MultiPassRendererPlugin'; import { normalizePasses } from '../plugins/MultiPassRendererPlugin';
import baseLayerSchema from './schema'; import baseLayerSchema from './schema';
/** /**
* layer id * layer id
*/ */
let layerIdCounter = 0; let layerIdCounter = 0;
const MapEventTypes = ['zoomchange', 'dragend', 'camerachange', 'resize'];
export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
implements ILayer { implements ILayer {
@ -61,15 +61,19 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
public maxZoom: number; public maxZoom: number;
public inited: boolean = false; public inited: boolean = false;
public layerModelNeedUpdate: boolean = false; public layerModelNeedUpdate: boolean = false;
public dataPluginsState: { [key: string]: boolean } = { public pickedFeatureID: number = -1;
DataSource: false,
DataMapping: false, public dataState: IDataState = {
FeatureScale: false, dataSourceNeedUpdate: false,
StyleAttr: false, dataMappingNeedUpdate: false,
filterNeedUpdate: false,
featureScaleNeedUpdate: false,
StyleAttrNeedUpdate: false,
}; };
// 生命周期钩子 // 生命周期钩子
public hooks = { public hooks = {
init: new SyncBailHook<void, boolean | void>(), init: new SyncBailHook<void, boolean | void>(),
afterInit: new SyncBailHook<void, boolean | void>(),
beforeRender: new SyncBailHook<void, boolean | void>(), beforeRender: new SyncBailHook<void, boolean | void>(),
beforeRenderData: new SyncWaterfallHook<void | boolean>(['data']), beforeRenderData: new SyncWaterfallHook<void | boolean>(['data']),
afterRender: new SyncHook<void>(), afterRender: new SyncHook<void>(),
@ -140,6 +144,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
private rawConfig: Partial<ILayerConfig & ChildLayerStyleOptions>; private rawConfig: Partial<ILayerConfig & ChildLayerStyleOptions>;
private needUpdateConfig: Partial<ILayerConfig & ChildLayerStyleOptions>;
/** /**
* *
*/ */
@ -165,11 +171,20 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
public updateLayerConfig( public updateLayerConfig(
configToUpdate: Partial<ILayerConfig | ChildLayerStyleOptions>, configToUpdate: Partial<ILayerConfig | ChildLayerStyleOptions>,
) { ) {
const sceneId = this.container.get<string>(TYPES.SceneID); if (!this.inited) {
this.configService.setLayerConfig(sceneId, this.id, { this.needUpdateConfig = {
...this.configService.getLayerConfig(this.id), ...this.needUpdateConfig,
...configToUpdate, ...configToUpdate,
}); };
} else {
const sceneId = this.container.get<string>(TYPES.SceneID);
this.configService.setLayerConfig(sceneId, this.id, {
...this.configService.getLayerConfig(this.id),
...this.needUpdateConfig,
...configToUpdate,
});
this.needUpdateConfig = {};
}
} }
/** /**
@ -273,10 +288,11 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
// 触发 init 生命周期插件 // 触发 init 生命周期插件
this.hooks.init.call(); this.hooks.init.call();
this.inited = true;
this.hooks.afterInit.call();
this.buildModels(); this.buildModels();
this.inited = true;
// 触发初始化完成事件; // 触发初始化完成事件;
this.emit('inited'); this.emit('inited');
return this; return this;
@ -311,6 +327,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
}); });
return this; return this;
} }
// 对mapping后的数据过滤scale保持不变
public filter( public filter(
field: StyleAttributeField, field: StyleAttributeField,
values?: StyleAttributeOption, values?: StyleAttributeOption,
@ -322,6 +339,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
attributeValues: values, attributeValues: values,
updateOptions, updateOptions,
}); });
this.dataState.dataMappingNeedUpdate = true;
return this; return this;
} }
@ -371,30 +389,6 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
this.buildModels(); this.buildModels();
return this; 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 { public style(options: object & Partial<ILayerConfig>): ILayer {
const { passes, ...rest } = options; const { passes, ...rest } = options;
@ -442,11 +436,51 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
return this; return this;
} }
public active(options: IActiveOption) {
this.updateLayerConfig({
enableHighlight: isObject(options) ? true : options,
highlightColor: isObject(options)
? options.color
: this.getLayerConfig().highlightColor,
});
return this;
}
public setActive(
id: number | { x: number; y: number },
options?: IActiveOption,
): void {
if (isObject(id)) {
const { x = 0, y = 0 } = id;
this.updateLayerConfig({
highlightColor: isObject(options)
? options.color
: this.getLayerConfig().highlightColor,
});
this.pick({ x, y });
} else {
this.updateLayerConfig({
pickedFeatureID: id,
highlightColor: isObject(options)
? options.color
: this.getLayerConfig().highlightColor,
});
}
}
public select(option: IActiveOption | false): ILayer {
return this;
}
public setSelect(
id: number | { x: number; y: number },
options?: IActiveOption,
): void {
throw new Error('Method not implemented.');
}
public show(): ILayer { public show(): ILayer {
this.updateLayerConfig({ this.updateLayerConfig({
visible: true, visible: true,
}); });
this.layerService.renderLayers();
return this; return this;
} }
@ -454,7 +488,6 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
this.updateLayerConfig({ this.updateLayerConfig({
visible: false, visible: false,
}); });
this.layerService.renderLayers();
return this; return this;
} }
@ -491,13 +524,20 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
/** /**
* zoom to layer Bounds * zoom to layer Bounds
*/ */
public fitBounds(): void { public fitBounds(): ILayer {
if (!this.inited) {
this.updateLayerConfig({
autoFit: true,
});
return this;
}
const source = this.getSource(); const source = this.getSource();
const extent = source.extent; const extent = source.extent;
this.mapService.fitBounds([ this.mapService.fitBounds([
[extent[0], extent[1]], [extent[0], extent[1]],
[extent[2], extent[3]], [extent[2], extent[3]],
]); ]);
return this;
} }
public destroy() { public destroy() {
@ -537,12 +577,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
const bounds = this.mapService.getBounds(); const bounds = this.mapService.getBounds();
const zoom = this.mapService.getZoom(); const zoom = this.mapService.getZoom();
if (this.layerSource.cluster) { if (this.layerSource.cluster) {
this.layerSource.updateClusterData(zoom, [ this.layerSource.updateClusterData(zoom);
bounds[0][0],
bounds[0][1],
bounds[1][0],
bounds[1][1],
]);
} }
} }
public getSource() { public getSource() {
@ -643,11 +678,6 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
}; };
} }
private registerMapEvent() {
MapEventTypes.forEach((type) => {
this.mapService.on(type, this.layerMapHander.bind(this, type));
});
}
private layerMapHander(type: string, data: any) { private layerMapHander(type: string, data: any) {
this.emit(type, data); this.emit(type, data);
} }

View File

@ -45,29 +45,29 @@ export default class HexagonModel extends BaseModel {
} }
protected registerBuiltinAttributes() { protected registerBuiltinAttributes() {
// point layer size; // point layer size;
this.styleAttributeService.registerStyleAttribute({ // this.styleAttributeService.registerStyleAttribute({
name: 'size', // name: 'size',
type: AttributeType.Attribute, // type: AttributeType.Attribute,
descriptor: { // descriptor: {
name: 'a_Size', // name: 'a_Size',
buffer: { // buffer: {
// give the WebGL driver a hint that this buffer may change // // give the WebGL driver a hint that this buffer may change
usage: gl.DYNAMIC_DRAW, // usage: gl.DYNAMIC_DRAW,
data: [], // data: [],
type: gl.FLOAT, // type: gl.FLOAT,
}, // },
size: 1, // size: 1,
update: ( // update: (
feature: IEncodeFeature, // feature: IEncodeFeature,
featureIdx: number, // featureIdx: number,
vertex: number[], // vertex: number[],
attributeIdx: number, // attributeIdx: number,
) => { // ) => {
const { size } = feature; // const { size } = feature;
return Array.isArray(size) ? [size[0]] : [size as number]; // return Array.isArray(size) ? [size[0]] : [size as number];
}, // },
}, // },
}); // });
// point layer size; // point layer size;
this.styleAttributeService.registerStyleAttribute({ this.styleAttributeService.registerStyleAttribute({

View File

@ -13,6 +13,7 @@ import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin
import DataMappingPlugin from './plugins/DataMappingPlugin'; import DataMappingPlugin from './plugins/DataMappingPlugin';
import DataSourcePlugin from './plugins/DataSourcePlugin'; import DataSourcePlugin from './plugins/DataSourcePlugin';
import FeatureScalePlugin from './plugins/FeatureScalePlugin'; import FeatureScalePlugin from './plugins/FeatureScalePlugin';
import LayerStylePlugin from './plugins/LayerStylePlugin';
import LightingPlugin from './plugins/LightingPlugin'; import LightingPlugin from './plugins/LightingPlugin';
import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin'; import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin';
import PixelPickingPlugin from './plugins/PixelPickingPlugin'; import PixelPickingPlugin from './plugins/PixelPickingPlugin';
@ -56,6 +57,15 @@ container
.bind<ILayerPlugin>(TYPES.ILayerPlugin) .bind<ILayerPlugin>(TYPES.ILayerPlugin)
.to(DataMappingPlugin) .to(DataMappingPlugin)
.inRequestScope(); .inRequestScope();
/**
* active, show, hide
*/
container
.bind<ILayerPlugin>(TYPES.ILayerPlugin)
.to(LayerStylePlugin)
.inRequestScope();
/** /**
* *
*/ */

View File

@ -27,13 +27,13 @@ export default class DataMappingPlugin implements ILayerPlugin {
}: { styleAttributeService: IStyleAttributeService }, }: { styleAttributeService: IStyleAttributeService },
) { ) {
layer.hooks.init.tap('DataMappingPlugin', () => { layer.hooks.init.tap('DataMappingPlugin', () => {
this.doMaping(layer, { styleAttributeService }); this.generateMaping(layer, { styleAttributeService });
}); });
layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => { layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => {
if (flag) { if (flag || layer.dataState.dataMappingNeedUpdate) {
layer.dataPluginsState.DataMapping = false; layer.dataState.dataMappingNeedUpdate = false;
this.doMaping(layer, { styleAttributeService }); this.generateMaping(layer, { styleAttributeService });
return true; return true;
} }
return false; return false;
@ -52,7 +52,7 @@ export default class DataMappingPlugin implements ILayerPlugin {
} }
}); });
} }
private doMaping( private generateMaping(
layer: ILayer, layer: ILayer,
{ {
styleAttributeService, styleAttributeService,
@ -62,6 +62,7 @@ export default class DataMappingPlugin implements ILayerPlugin {
const filter = styleAttributeService.getLayerStyleAttribute('filter'); const filter = styleAttributeService.getLayerStyleAttribute('filter');
const { dataArray } = layer.getSource().data; const { dataArray } = layer.getSource().data;
let filterData = dataArray; let filterData = dataArray;
// 数据过滤完 在执行数据映射
if (filter?.scale) { if (filter?.scale) {
filterData = dataArray.filter((record: IParseDataItem) => { filterData = dataArray.filter((record: IParseDataItem) => {
return this.applyAttributeMapping(filter, record)[0]; return this.applyAttributeMapping(filter, record)[0];

View File

@ -1,10 +1,12 @@
import { ILayer, ILayerPlugin } from '@antv/l7-core'; import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
import Source from '@antv/l7-source'; import Source from '@antv/l7-source';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
@injectable() @injectable()
export default class DataSourcePlugin implements ILayerPlugin { export default class DataSourcePlugin implements ILayerPlugin {
protected mapService: IMapService;
public apply(layer: ILayer) { public apply(layer: ILayer) {
this.mapService = layer.getContainer().get<IMapService>(TYPES.IMapService);
layer.hooks.init.tap('DataSourcePlugin', () => { layer.hooks.init.tap('DataSourcePlugin', () => {
const { data, options } = layer.sourceOption; const { data, options } = layer.sourceOption;
layer.setSource(new Source(data, options)); layer.setSource(new Source(data, options));
@ -12,7 +14,12 @@ export default class DataSourcePlugin implements ILayerPlugin {
// 检测数据是不否需要更新 // 检测数据是不否需要更新
layer.hooks.beforeRenderData.tap('DataSourcePlugin', (flag) => { layer.hooks.beforeRenderData.tap('DataSourcePlugin', (flag) => {
if (layer.isSourceNeedUpdate()) { const source = layer.getSource();
const cluster = source.cluster;
const { zoom = 0, maxZoom = 16 } = source.clusterOptions;
const newZoom = this.mapService.getZoom();
if (cluster && Math.abs(zoom - newZoom) > 1 && maxZoom > zoom) {
source.updateClusterData(Math.floor(newZoom) + 1);
return true; return true;
} }
return false; return false;

View File

@ -0,0 +1,30 @@
import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
import Source from '@antv/l7-source';
import { injectable } from 'inversify';
import { encodePickingColor, rgb2arr } from '../utils/color';
@injectable()
export default class LayerStylePlugin implements ILayerPlugin {
public apply(layer: ILayer) {
layer.hooks.afterInit.tap('LayerStylePlugin', () => {
layer.updateLayerConfig({});
const { autoFit } = layer.getLayerConfig();
if (autoFit) {
layer.fitBounds();
}
});
layer.hooks.beforeRender.tap('LayerStylePlugin', () => {
const {
highlightColor = 'red',
pickedFeatureID = -1,
} = layer.getLayerConfig();
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: 2.0,
u_PickingColor: encodePickingColor(pickedFeatureID),
u_HighlightColor: rgb2arr(highlightColor as string),
}),
);
});
}
}

View File

@ -8,15 +8,7 @@ import {
IStyleAttributeService, IStyleAttributeService,
} from '@antv/l7-core'; } from '@antv/l7-core';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { rgb2arr } from '../utils/color'; import { encodePickingColor, rgb2arr } from '../utils/color';
function encodePickingColor(featureIdx: number): [number, number, number] {
return [
(featureIdx + 1) & 255,
((featureIdx + 1) >> 8) & 255,
(((featureIdx + 1) >> 8) >> 8) & 255,
];
}
const PickingStage = { const PickingStage = {
NONE: 0.0, NONE: 0.0,
@ -50,9 +42,13 @@ export default class PixelPickingPlugin implements ILayerPlugin {
}, },
size: 3, size: 3,
// TODO: 固定 feature range 范围内的 pickingColor 都是固定的,可以生成 cache // TODO: 固定 feature range 范围内的 pickingColor 都是固定的,可以生成 cache
update: (feature: IEncodeFeature, featureIdx: number) => update: (feature: IEncodeFeature, featureIdx: number) => {
// 只有开启拾取才需要 encode // 只有开启拾取才需要 encode
enablePicking ? encodePickingColor(featureIdx) : [0, 0, 0], const { id } = feature;
return enablePicking && layer.isVisible()
? encodePickingColor(id as number)
: [0, 0, 0];
},
}, },
}); });
}); });
@ -60,7 +56,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
// if (layer.multiPassRenderer) { // if (layer.multiPassRenderer) {
layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => { layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => {
const { enablePicking } = layer.getLayerConfig(); const { enablePicking } = layer.getLayerConfig();
if (enablePicking) { if (enablePicking && layer.isVisible()) {
layer.models.forEach((model) => layer.models.forEach((model) =>
model.addUniforms({ model.addUniforms({
u_PickingStage: PickingStage.ENCODE, u_PickingStage: PickingStage.ENCODE,
@ -71,7 +67,7 @@ export default class PixelPickingPlugin implements ILayerPlugin {
layer.hooks.afterPickingEncode.tap('PixelPickingPlugin', () => { layer.hooks.afterPickingEncode.tap('PixelPickingPlugin', () => {
const { enablePicking } = layer.getLayerConfig(); const { enablePicking } = layer.getLayerConfig();
if (enablePicking) { if (enablePicking && layer.isVisible()) {
layer.models.forEach((model) => layer.models.forEach((model) =>
model.addUniforms({ model.addUniforms({
u_PickingStage: PickingStage.NONE, u_PickingStage: PickingStage.NONE,

View File

@ -15,6 +15,16 @@ export function rgb2arr(str: string) {
return arr; return arr;
} }
export function encodePickingColor(
featureIdx: number,
): [number, number, number] {
return [
(featureIdx + 1) & 255,
((featureIdx + 1) >> 8) & 255,
(((featureIdx + 1) >> 8) >> 8) & 255,
];
}
export function generateColorRamp(colorRamp: IColorRamp): ImageData { export function generateColorRamp(colorRamp: IColorRamp): ImageData {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;

View File

@ -20,6 +20,6 @@ describe('source constructor', () => {
field: 'mag', field: 'mag',
}, },
}); });
source.updateClusterData(2, [10, 0, 130, 75]); source.updateClusterData(2);
}); });
}); });

View File

@ -6,8 +6,11 @@ import json from './parser/json';
import raster from './parser/raster'; import raster from './parser/raster';
import Source from './source'; import Source from './source';
import { cluster } from './transform/cluster'; import { cluster } from './transform/cluster';
import { filter } from './transform/filter';
import { aggregatorToGrid } from './transform/grid'; import { aggregatorToGrid } from './transform/grid';
import { pointToHexbin } from './transform/hexagon'; import { pointToHexbin } from './transform/hexagon';
import { join } from './transform/join';
import { map } from './transform/map';
export default Source; export default Source;
registerParser('geojson', geojson); registerParser('geojson', geojson);
registerParser('image', image); registerParser('image', image);
@ -15,6 +18,9 @@ registerParser('csv', csv);
registerParser('json', json); registerParser('json', json);
registerParser('raster', raster); registerParser('raster', raster);
registerTransform('cluster', cluster); registerTransform('cluster', cluster);
registerTransform('filter', filter);
registerTransform('join', join);
registerTransform('map', map);
registerTransform('grid', aggregatorToGrid); registerTransform('grid', aggregatorToGrid);
registerTransform('hexagon', pointToHexbin); registerTransform('hexagon', pointToHexbin);
export { export {

View File

@ -1,9 +1,12 @@
import { import {
IClusterOptions, IClusterOptions,
IMapService,
IParserCfg, IParserCfg,
IParserData, IParserData,
ISourceCFG, ISourceCFG,
ITransform, ITransform,
lazyInject,
TYPES,
} from '@antv/l7-core'; } from '@antv/l7-core';
import { extent } from '@antv/l7-utils'; import { extent } from '@antv/l7-utils';
import { import {
@ -14,13 +17,13 @@ import {
Properties, Properties,
} from '@turf/helpers'; } from '@turf/helpers';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { Container } from 'inversify';
import { cloneDeep, isFunction, isString } from 'lodash'; import { cloneDeep, isFunction, isString } from 'lodash';
import Supercluster from 'supercluster'; import Supercluster from 'supercluster';
import { SyncHook } from 'tapable'; import { SyncHook } from 'tapable';
import { getParser, getTransform } from './'; import { getParser, getTransform } from './';
import { statMap } from './utils/statistics'; import { statMap } from './utils/statistics';
import { getColumn } from './utils/util'; import { getColumn } from './utils/util';
export default class Source extends EventEmitter { export default class Source extends EventEmitter {
public data: IParserData; public data: IParserData;
@ -82,13 +85,9 @@ export default class Source extends EventEmitter {
this.init(); this.init();
} }
public updateClusterData( public updateClusterData(zoom: number): void {
zoom: number,
bbox: [number, number, number, number],
): void {
const { method = 'sum', field } = this.clusterOptions; const { method = 'sum', field } = this.clusterOptions;
let data = this.clusterIndex.getClusters(bbox, zoom); let data = this.clusterIndex.getClusters(this.extent, zoom);
this.clusterOptions.bbox = bbox;
this.clusterOptions.zoom = zoom; this.clusterOptions.zoom = zoom;
data.forEach((p) => { data.forEach((p) => {
if (!p.id) { if (!p.id) {
@ -122,6 +121,16 @@ export default class Source extends EventEmitter {
}); });
this.executeTrans(); this.executeTrans();
} }
public getFeatureById(id: number): unknown {
const { type = 'geojson' } = this.parser;
if (type === 'geojson') {
return id < this.rawData.features.length
? this.rawData.features[id]
: 'null';
} else {
return id < this.data.dataArray.length ? this.data.dataArray[id] : 'null';
}
}
private excuteParser(): void { private excuteParser(): void {
const parser = this.parser; const parser = this.parser;

View File

@ -0,0 +1,8 @@
import { IParserData } from '@antv/l7-core';
export function filter(data: IParserData, options: { [key: string]: any }) {
const { callback } = options;
if (callback) {
data.dataArray = data.dataArray.filter(callback);
}
return data;
}

View File

@ -0,0 +1,28 @@
import { IParseDataItem, IParserData } from '@antv/l7-core';
interface IJoinOption {
field: 'string';
data: any[];
}
/**
*
* @param data
* @param options
*/
export function join(geoData: IParserData, options: { [key: string]: any }) {
const { field, data } = options;
const dataObj: { [key: string]: any } = {};
data.forEach((element: { [key: string]: any }) => {
dataObj[element.field] = element;
});
geoData.dataArray = data.dataArray.map((item: IParseDataItem) => {
const joinName = item[field];
return {
...dataObj[joinName],
...item,
};
});
return data;
}

View File

@ -0,0 +1,9 @@
import { IParserData } from '@antv/l7-core';
type CallBack = (...args: any[]) => any;
export function map(data: IParserData, options: { [key: string]: any }) {
const { callback } = options;
if (callback) {
data.dataArray = data.dataArray.map(callback);
}
return data;
}

View File

@ -1,5 +1,6 @@
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import * as React from 'react'; import * as React from 'react';
import Chart from './components/chart';
import Marker from './components/Marker'; import Marker from './components/Marker';
import Popup from './components/Popup'; import Popup from './components/Popup';
import Scale from './components/Scale'; import Scale from './components/Scale';
@ -9,4 +10,5 @@ storiesOf('UI 组件', module)
.add('Zoom', () => <Zoom />) .add('Zoom', () => <Zoom />)
.add('Scale', () => <Scale />) .add('Scale', () => <Scale />)
.add('Marker', () => <Marker />) .add('Marker', () => <Marker />)
.add('Chart', () => <Chart />)
.add('Popup', () => <Popup />); .add('Popup', () => <Popup />);

View File

@ -0,0 +1,108 @@
// @ts-ignore
import * as G2 from '@antv/g2';
import { Marker, Scene } from '@antv/l7';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import * as React from 'react';
export default class ChartComponent extends React.Component {
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
pitch: 0,
style: 'dark',
center: [52.21496184144132, 24.121126851768906],
zoom: 3.802,
}),
});
addChart();
scene.render();
function addChart() {
Promise.all([
fetch(
'https://gw.alipayobjects.com/os/basement_prod/5b772136-a1f4-4fc5-9a80-9f9974b4b182.json',
).then((d) => d.json()),
fetch(
'https://gw.alipayobjects.com/os/basement_prod/f3c467a4-9ae0-4f08-bb5f-11f9c869b2cb.json',
).then((d) => d.json()),
]).then(function onLoad([center, population]) {
const popobj: { [key: string]: any } = {};
population.forEach((element: any) => {
popobj[element.Code] =
element['Population, female (% of total) (% of total)'];
});
// 数据绑定
center.features = center.features.map((fe: any) => {
fe.properties.female = popobj[fe.properties.id] * 1 || 0;
return fe;
});
center.features.forEach((point: any) => {
const el = document.createElement('div');
const coord = point.geometry.coordinates;
const v = (point.properties.female * 1) as number;
if (v < 1 || (v > 46 && v < 54)) {
return;
}
const size = 60;
const data = [
{
type: '男性',
value: 100.0 - Number(v.toFixed(2)),
},
{
type: '女性',
value: v.toFixed(2),
},
];
const chart = new G2.Chart({
container: el,
width: size,
height: size,
// render: 'svg',
padding: 0,
});
chart.source(data);
chart.legend(false);
chart.tooltip(false);
chart.coord('theta', {
radius: 0.9,
innerRadius: 0.6,
});
chart
.intervalStack()
.position('value')
.color('type', ['#5CCEA1', '#5B8FF9'])
.opacity(1);
chart.render();
const marker = new Marker({ element: el }).setLnglat({
lng: coord[0],
lat: coord[1],
});
scene.addMarker(marker);
});
});
}
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -5,7 +5,6 @@ import ArcLineDemo from './components/Arcline';
import Column from './components/column'; import Column from './components/column';
import DataUpdate from './components/data_update'; import DataUpdate from './components/data_update';
import HeatMapDemo from './components/HeatMap'; import HeatMapDemo from './components/HeatMap';
import GridHeatMap from './components/HeatmapGrid';
import LineLayer from './components/Line'; import LineLayer from './components/Line';
import PointDemo from './components/Point'; import PointDemo from './components/Point';
import Point3D from './components/Point3D'; import Point3D from './components/Point3D';
@ -25,7 +24,6 @@ storiesOf('图层', module)
.add('线图层', () => <LineLayer />) .add('线图层', () => <LineLayer />)
.add('3D弧线', () => <ArcLineDemo />) .add('3D弧线', () => <ArcLineDemo />)
.add('2D弧线', () => <Arc2DLineDemo />) .add('2D弧线', () => <Arc2DLineDemo />)
.add('网格热力图', () => <GridHeatMap />)
.add('热力图', () => <HeatMapDemo />) .add('热力图', () => <HeatMapDemo />)
.add('栅格', () => <RasterLayerDemo />) .add('栅格', () => <RasterLayerDemo />)
.add('图片', () => <ImageLayerDemo />); .add('图片', () => <ImageLayerDemo />);

View File

@ -1,85 +0,0 @@
import { HeatmapLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as React from 'react';
export default class GridHeatMap extends React.Component {
// @ts-ignore
private scene: Scene;
public componentWillUnmount() {
this.scene.destroy();
}
public async componentDidMount() {
const response = await fetch(
'https://gw.alipayobjects.com/os/basement_prod/7359a5e9-3c5e-453f-b207-bc892fb23b84.csv',
);
const data = await response.text();
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
pitch: 0,
center: [110.097892, 33.853662],
zoom: 4.056,
}),
});
const layer = new HeatmapLayer({
enablePicking: true,
enableHighlight: true,
})
.source(data, {
parser: {
type: 'csv',
x: 'lng',
y: 'lat',
},
transforms: [
{
type: 'grid',
size: 10000,
field: 'v',
method: 'sum',
},
],
})
.size('count', (value) => {
return value * 0;
})
.shape('square')
.style({
coverage: 1,
angle: 0,
})
.color(
'count',
[
'#FF3417',
'#FF7412',
'#FFB02A',
'#FFE754',
'#46F3FF',
'#02BEFF',
'#1A7AFF',
'#0A1FB2',
].reverse(),
);
scene.addLayer(layer);
this.scene = scene;
}
public render() {
return (
<div
id="map"
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
}}
/>
);
}
}

View File

@ -36,9 +36,6 @@ export default class Point3D extends React.Component {
.scale('point_count', { .scale('point_count', {
type: 'quantile', type: 'quantile',
}) })
.filter('point_count', (point_count: number) => {
return point_count > 1;
})
.size('point_count', [5, 10, 15, 20, 25]) .size('point_count', [5, 10, 15, 20, 25])
.color('red') .color('red')
.style({ .style({
@ -46,7 +43,6 @@ export default class Point3D extends React.Component {
strokeWidth: 1, strokeWidth: 1,
}); });
scene.addLayer(pointLayer); scene.addLayer(pointLayer);
console.log(pointLayer);
} }
public render() { public render() {

View File

@ -38,13 +38,18 @@ export default class AdvancedAPI extends React.Component {
highlightColor: [0, 0, 1, 1], highlightColor: [0, 0, 1, 1],
onHover: (pickedFeature) => { onHover: (pickedFeature) => {
// tslint:disable-next-line:no-console // tslint:disable-next-line:no-console
console.log(pickedFeature); },
onClick: (pickedFeature) => {
// tslint:disable-next-line:no-console
}, },
}); });
layer layer
.source(await response.json()) .source(await response.json())
.size('name', [0, 10000, 50000, 30000, 100000]) .size('name', [0, 10000, 50000, 30000, 100000])
.active({
color: 'red',
})
.color('name', [ .color('name', [
'#2E8AE6', '#2E8AE6',
'#69D1AB', '#69D1AB',
@ -58,6 +63,9 @@ export default class AdvancedAPI extends React.Component {
opacity: 0.8, opacity: 0.8,
}); });
scene.addLayer(layer); scene.addLayer(layer);
layer.on('click', (e) => {
console.log(e);
});
this.scene = scene; this.scene = scene;
@ -67,9 +75,10 @@ export default class AdvancedAPI extends React.Component {
const styleOptions = { const styleOptions = {
enablePicking: true, enablePicking: true,
enableHighlight: true, enableHighlight: true,
highlightColor: [0, 0, 255], highlightColor: [1, 0, 0],
pickingX: window.innerWidth / 2, pickingX: window.innerWidth / 2,
pickingY: window.innerHeight / 2, pickingY: window.innerHeight / 2,
visible: true,
}; };
const pointFolder = gui.addFolder('非鼠标 hover 交互'); const pointFolder = gui.addFolder('非鼠标 hover 交互');
pointFolder pointFolder
@ -80,23 +89,30 @@ export default class AdvancedAPI extends React.Component {
}); });
scene.render(); scene.render();
}); });
pointFolder.add(styleOptions, 'visible').onChange((visible: boolean) => {
layer.style({
visible,
});
scene.render();
});
pointFolder pointFolder
.add(styleOptions, 'pickingX', 0, window.innerWidth) .add(styleOptions, 'pickingX', 0, window.innerWidth)
.onChange((pickingX: number) => { .onChange((pickingX: number) => {
layer.pick({ x: pickingX, y: styleOptions.pickingY }); layer.setActive({ x: pickingX, y: styleOptions.pickingY });
}); });
pointFolder pointFolder
.add(styleOptions, 'pickingY', 0, window.innerHeight) .add(styleOptions, 'pickingY', 0, window.innerHeight)
.onChange((pickingY: number) => { .onChange((pickingY: number) => {
layer.pick({ x: styleOptions.pickingX, y: pickingY }); layer.setActive({ x: styleOptions.pickingX, y: pickingY });
}); });
pointFolder pointFolder
.addColor(styleOptions, 'highlightColor') .addColor(styleOptions, 'highlightColor')
.onChange((highlightColor: number[]) => { .onChange((highlightColor: number[]) => {
const [r, g, b] = highlightColor.map((c) => c / 255); const [r, g, b] = highlightColor.map((c) => c / 255);
layer.style({ layer.setActive(
highlightColor: [r, g, b, 1], { x: styleOptions.pickingX, y: styleOptions.pickingY },
}); { color: [r, g, b, 1] },
);
scene.render(); scene.render();
}); });
pointFolder.open(); pointFolder.open();