From 594d9c821cb641ecdaba7ecc243096dd58dd8823 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Tue, 10 Dec 2019 20:11:26 +0800 Subject: [PATCH 1/4] docs: add source docs, fix mapbox marker --- docs/api/layer/pointlayer.zh.md | 8 ++ docs/api/source/csv.en.md | 53 ++++++++ docs/api/source/csv.zh.md | 53 ++++++++ docs/api/source/image.en.md | 25 ++++ docs/api/source/image.zh.md | 25 ++++ docs/api/source/json.en.md | 2 +- docs/api/source/json.zh.md | 4 +- docs/api/source/source.en.md | 87 +----------- docs/api/source/source.zh.md | 100 +++----------- docs/tutorial/quickstart.zh.md | 77 +++++++++++ examples/point/chart/demo/ring.js | 124 +++++++++--------- .../src/services/component/MarkerService.ts | 2 +- .../core/src/services/layer/ILayerService.ts | 1 - .../src/services/source/ISourceService.ts | 1 + packages/layers/src/core/BaseLayer.ts | 31 +---- .../layers/src/plugins/DataSourcePlugin.ts | 11 +- packages/source/__tests__/source.spec.ts | 2 +- packages/source/src/source.ts | 13 +- stories/Components/Components.stories.tsx | 2 + stories/Components/components/chart.tsx | 108 +++++++++++++++ stories/Layers/components/Point.tsx | 4 - 21 files changed, 464 insertions(+), 269 deletions(-) create mode 100644 docs/api/source/csv.en.md create mode 100644 docs/api/source/csv.zh.md create mode 100644 docs/api/source/image.en.md create mode 100644 docs/api/source/image.zh.md create mode 100644 stories/Components/components/chart.tsx diff --git a/docs/api/layer/pointlayer.zh.md b/docs/api/layer/pointlayer.zh.md index 5804e506b9..5dc8c8f83a 100644 --- a/docs/api/layer/pointlayer.zh.md +++ b/docs/api/layer/pointlayer.zh.md @@ -25,6 +25,14 @@ shape 支持 ``` +## source + +点数据类型,根据经纬点绘制图形,目前支持三种数据结构 + +- [GeoJOSN]('../source/geojson/#point') +- [CSV]() +- [JSON](../source/json/#点数据) + **图片标注** 通过 `Scene.addImage()` 可以添加图片资源, diff --git a/docs/api/source/csv.en.md b/docs/api/source/csv.en.md new file mode 100644 index 0000000000..eed20c031c --- /dev/null +++ b/docs/api/source/csv.en.md @@ -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) diff --git a/docs/api/source/csv.zh.md b/docs/api/source/csv.zh.md new file mode 100644 index 0000000000..eed20c031c --- /dev/null +++ b/docs/api/source/csv.zh.md @@ -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) diff --git a/docs/api/source/image.en.md b/docs/api/source/image.en.md new file mode 100644 index 0000000000..153208b5b2 --- /dev/null +++ b/docs/api/source/image.en.md @@ -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], + }, + }, +); +``` diff --git a/docs/api/source/image.zh.md b/docs/api/source/image.zh.md new file mode 100644 index 0000000000..153208b5b2 --- /dev/null +++ b/docs/api/source/image.zh.md @@ -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], + }, + }, +); +``` diff --git a/docs/api/source/json.en.md b/docs/api/source/json.en.md index 2a112c3517..6d93a25a89 100644 --- a/docs/api/source/json.en.md +++ b/docs/api/source/json.en.md @@ -1,6 +1,6 @@ --- title: JSON -order: 1 +order: 2 --- GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。 diff --git a/docs/api/source/json.zh.md b/docs/api/source/json.zh.md index 2a112c3517..5ded5808b8 100644 --- a/docs/api/source/json.zh.md +++ b/docs/api/source/json.zh.md @@ -1,6 +1,6 @@ --- title: JSON -order: 1 +order: 2 --- GeoJSON 虽然是通用的的地理数据格式,在具体使用场景中,数据服务人员可能并不熟悉 GeoJON,或者没有生成 GeoJON 的工具, 因此 L7 对数据定义了 Parser 的概念,你的数据可以是任何格式,使用指定数据对应的地理信息字段即可。 @@ -39,6 +39,8 @@ layer.source(data, { }); ``` +[JOSN 数据 demo 示例](../../../examples/gallery/basic) + ### 通用解析方式 可也解析任意复杂的点,线面 diff --git a/docs/api/source/source.en.md b/docs/api/source/source.en.md index 40289647b4..23d82db659 100644 --- a/docs/api/source/source.en.md +++ b/docs/api/source/source.en.md @@ -40,100 +40,25 @@ layer.source(data); #### JSON -[JSON 数据格式解析](../json) +[JSON 数据格式解析](./json) #### csv -点,线数据配置项同 json 数据类型 +[CSV 数据格式解析](./csv) -```javascript -layer.source(data, { - parser: { - type: 'csv', - x: 'lng1', - y: 'lat1', - x1: 'lng1', - y1: 'lat2', - }, -}); -``` - -**栅格数据类型 ** +栅格数据类型 #### 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], - }, -}); -``` +[Image 数据格式解析](./image) ### transforms -目前支持三种数据处理方法 map,grid,hexagon transform 配置项 +目前支持两种热力图使用的数据处理方法 grid,hexagon transform 配置项 - type 数据处理类型 - 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 生成方格网布局,根据数据字段统计,主要在网格热力图中使用 @@ -163,4 +88,4 @@ layer.source(data, { - type: 'hexagon', - size: 网格半径 - field: 数据统计字段 -- method:聚合方法   count,max,min,sum,mean5 个统计维度 +- method:聚合方法   count,max,min,sum,mean 5 个统计维度 diff --git a/docs/api/source/source.zh.md b/docs/api/source/source.zh.md index 89bef2766b..45b0f47246 100644 --- a/docs/api/source/source.zh.md +++ b/docs/api/source/source.zh.md @@ -7,6 +7,13 @@ order: 0 source 地理数据处理模块,主要包含数据解析(parser),和数据处理(transform); +- data +- option + - cluster **boolean** 是否聚合 + - clusterOption 聚合配置项 + - parser 数据解析配置 + - transforms 数据处理配置 + ### parser 不同数据类型处理成统一数据格式。矢量数据包括 GeoJON, CSV,Json 等不同数据格式,栅格数据,包括 Raster,Image 数据。将来还会支持瓦片格式数据。 @@ -23,6 +30,14 @@ source 地理数据处理模块,主要包含数据解析(parser),和数据 ## API +### cluster 可选 可以只设置 cluster + +### clusterOption 可选 + +- radius 聚合半径 **number** default 40 +- minZoom: 最小聚合缩放等级 **number** default 0 +- maxZoom: 最大聚合缩放等级 **number** default 16 + ### parser **配置项** @@ -40,100 +55,25 @@ layer.source(data); #### JSON -[JSON 数据格式解析](../json) +[JSON 数据格式解析](./json) #### csv -点,线数据配置项同 json 数据类型 +[CSV 数据格式解析](./csv) -```javascript -layer.source(data, { - parser: { - type: 'csv', - x: 'lng1', - y: 'lat1', - x1: 'lng1', - y1: 'lat2', - }, -}); -``` - -**栅格数据类型 ** +栅格数据类型 #### 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], - }, -}); -``` +[Image 数据格式解析](./image) ### transforms -目前支持三种数据处理方法 map,grid,hexagon transform 配置项 +目前支持两种热力图使用的数据处理方法 grid,hexagon transform 配置项 - type 数据处理类型 - 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 生成方格网布局,根据数据字段统计,主要在网格热力图中使用 diff --git a/docs/tutorial/quickstart.zh.md b/docs/tutorial/quickstart.zh.md index 7eaead8843..f8edaff8e0 100644 --- a/docs/tutorial/quickstart.zh.md +++ b/docs/tutorial/quickstart.zh.md @@ -15,8 +15,11 @@ Current version: ![L7 2.0版本号](https://badgen.net/npm/v/@antv/l7/beta) Include the L7 JS JavaScript of your HTML file. +:warning: 如果需要引用第三方地图API,请确保在先引入第三方API,然后引入L7 + ```html + @@ -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 ( +
+ ); + } +} +``` + +⚠️组件 Unmount 时需要通过 scene.destroy() 手动销毁场景。 + +更多React使用 [示例查看](https://github.com/antvis/L7/tree/master/stories) + +### Vue 欢迎补充 \ No newline at end of file diff --git a/examples/point/chart/demo/ring.js b/examples/point/chart/demo/ring.js index 47541248ca..81b520e40b 100644 --- a/examples/point/chart/demo/ring.js +++ b/examples/point/chart/demo/ring.js @@ -44,67 +44,71 @@ const scene = new Scene({ zoom: 3.802 }) }); -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 = {}; - population.forEach(element => { - popobj[element.Code] = - element['Population, female (% of total) (% of total)']; - }); - // 数据绑定 +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 = {}; + population.forEach(element => { + popobj[element.Code] = + element['Population, female (% of total) (% of total)']; + }); + // 数据绑定 - center.features = center.features.map(fe => { - fe.properties.female = popobj[fe.properties.id] * 1 || 0; - return fe; - }); - center.features.forEach(point => { - const el = document.createElement('div'); - const coord = point.geometry.coordinates; - const v = point.properties.female * 1; - if (v < 1 || (v > 46 && v < 54)) { - return; - } - const size = 60; - const data = [ - { - type: '男性', - value: 100.0 - v.toFixed(2) - }, - { - type: '女性', - value: v.toFixed(2) * 1 + center.features = center.features.map(fe => { + fe.properties.female = popobj[fe.properties.id] * 1 || 0; + return fe; + }); + center.features.forEach(point => { + const el = document.createElement('div'); + const coord = point.geometry.coordinates; + const v = point.properties.female * 1; + if (v < 1 || (v > 46 && v < 54)) { + return; } - ]; - const chart = new G2.Chart({ - container: el, - width: size, - height: size, - render: 'svg', - padding: 0 + const size = 60; + const data = [ + { + type: '男性', + value: 100.0 - v.toFixed(2) + }, + { + 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); }); -}); +} diff --git a/packages/core/src/services/component/MarkerService.ts b/packages/core/src/services/component/MarkerService.ts index e742bef00c..dd30a4b276 100644 --- a/packages/core/src/services/component/MarkerService.ts +++ b/packages/core/src/services/component/MarkerService.ts @@ -11,7 +11,7 @@ export default class MarkerService implements IMarkerService { private markers: IMarker[] = []; private unAddMarkers: IMarker[] = []; public addMarker(marker: IMarker): void { - if (!this.mapsService.map && this.mapsService.getMarkerContainer()) { + if (this.mapsService.map && this.mapsService.getMarkerContainer()) { this.markers.push(marker); marker.addTo(this.scene); } else { diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index 79d362db17..e864586575 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -106,7 +106,6 @@ export interface ILayer { */ addPlugin(plugin: ILayerPlugin): ILayer; getSource(): ISource; - isSourceNeedUpdate(): boolean; setSource(source: ISource): void; setEncodedData(encodedData: IEncodeFeature[]): void; getEncodedData(): IEncodeFeature[]; diff --git a/packages/core/src/services/source/ISourceService.ts b/packages/core/src/services/source/ISourceService.ts index 7d547c2316..2e03b0ac31 100644 --- a/packages/core/src/services/source/ISourceService.ts +++ b/packages/core/src/services/source/ISourceService.ts @@ -61,6 +61,7 @@ export interface ISource { data: IParserData; cluster: boolean; clusterOptions: Partial; + updateClusterData(zoom: number): void; } export interface IRasterCfg { extent: [number, number, number, number]; diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 3874894474..ae4463bc56 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -371,30 +371,6 @@ export default class BaseLayer extends EventEmitter 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): ILayer { const { passes, ...rest } = options; @@ -537,12 +513,7 @@ export default class BaseLayer extends EventEmitter 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], - ]); + this.layerSource.updateClusterData(zoom); } } public getSource() { diff --git a/packages/layers/src/plugins/DataSourcePlugin.ts b/packages/layers/src/plugins/DataSourcePlugin.ts index 4db5efe3ba..e99b486125 100644 --- a/packages/layers/src/plugins/DataSourcePlugin.ts +++ b/packages/layers/src/plugins/DataSourcePlugin.ts @@ -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 { injectable } from 'inversify'; @injectable() export default class DataSourcePlugin implements ILayerPlugin { + protected mapService: IMapService; public apply(layer: ILayer) { + this.mapService = layer.getContainer().get(TYPES.IMapService); layer.hooks.init.tap('DataSourcePlugin', () => { const { data, options } = layer.sourceOption; layer.setSource(new Source(data, options)); @@ -12,7 +14,12 @@ export default class DataSourcePlugin implements ILayerPlugin { // 检测数据是不否需要更新 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 false; diff --git a/packages/source/__tests__/source.spec.ts b/packages/source/__tests__/source.spec.ts index 047c150766..baec235c98 100644 --- a/packages/source/__tests__/source.spec.ts +++ b/packages/source/__tests__/source.spec.ts @@ -20,6 +20,6 @@ describe('source constructor', () => { field: 'mag', }, }); - source.updateClusterData(2, [10, 0, 130, 75]); + source.updateClusterData(2); }); }); diff --git a/packages/source/src/source.ts b/packages/source/src/source.ts index 09dfd27f90..86a31b9350 100644 --- a/packages/source/src/source.ts +++ b/packages/source/src/source.ts @@ -1,9 +1,12 @@ import { IClusterOptions, + IMapService, IParserCfg, IParserData, ISourceCFG, ITransform, + lazyInject, + TYPES, } from '@antv/l7-core'; import { extent } from '@antv/l7-utils'; import { @@ -14,13 +17,13 @@ import { Properties, } from '@turf/helpers'; import { EventEmitter } from 'eventemitter3'; +import { Container } from 'inversify'; 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; @@ -82,13 +85,9 @@ export default class Source extends EventEmitter { this.init(); } - public updateClusterData( - zoom: number, - bbox: [number, number, number, number], - ): void { + public updateClusterData(zoom: number): void { const { method = 'sum', field } = this.clusterOptions; - let data = this.clusterIndex.getClusters(bbox, zoom); - this.clusterOptions.bbox = bbox; + let data = this.clusterIndex.getClusters(this.extent, zoom); this.clusterOptions.zoom = zoom; data.forEach((p) => { if (!p.id) { diff --git a/stories/Components/Components.stories.tsx b/stories/Components/Components.stories.tsx index 405bf6cbba..0c7bb0b105 100644 --- a/stories/Components/Components.stories.tsx +++ b/stories/Components/Components.stories.tsx @@ -1,5 +1,6 @@ import { storiesOf } from '@storybook/react'; import * as React from 'react'; +import Chart from './components/chart'; import Marker from './components/Marker'; import Popup from './components/Popup'; import Scale from './components/Scale'; @@ -9,4 +10,5 @@ storiesOf('UI 组件', module) .add('Zoom', () => ) .add('Scale', () => ) .add('Marker', () => ) + .add('Chart', () => ) .add('Popup', () => ); diff --git a/stories/Components/components/chart.tsx b/stories/Components/components/chart.tsx new file mode 100644 index 0000000000..095643fe4c --- /dev/null +++ b/stories/Components/components/chart.tsx @@ -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 ( +
+ ); + } +} diff --git a/stories/Layers/components/Point.tsx b/stories/Layers/components/Point.tsx index 5586e9087d..10ce3b4e0f 100644 --- a/stories/Layers/components/Point.tsx +++ b/stories/Layers/components/Point.tsx @@ -36,9 +36,6 @@ export default class Point3D extends React.Component { .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({ @@ -46,7 +43,6 @@ export default class Point3D extends React.Component { strokeWidth: 1, }); scene.addLayer(pointLayer); - console.log(pointLayer); } public render() { From 87176fb0bcb5e286ec96bf59f0e2b4fd0e024523 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Thu, 12 Dec 2019 00:35:15 +0800 Subject: [PATCH 2/4] feat(source render): source transfrom, layer event --- .gitignore | 1 + .../core/src/services/config/ConfigService.ts | 8 +- .../interaction/IInteractionService.ts | 4 +- .../interaction/InteractionService.ts | 17 ++- .../core/src/services/layer/ILayerService.ts | 36 +++++- .../renderer/passes/PixelPickingPass.ts | 28 ++++- .../src/services/source/ISourceService.ts | 1 + packages/layers/src/core/BaseLayer.ts | 103 ++++++++++++++---- packages/layers/src/heatmap/models/hexagon.ts | 46 ++++---- packages/layers/src/index.ts | 10 ++ .../layers/src/plugins/DataMappingPlugin.ts | 11 +- .../layers/src/plugins/LayerStylePlugin.ts | 30 +++++ .../layers/src/plugins/PixelPickingPlugin.ts | 22 ++-- packages/layers/src/utils/color.ts | 10 ++ packages/source/src/index.ts | 4 + packages/source/src/source.ts | 10 ++ packages/source/src/transform/filter.ts | 8 ++ packages/source/src/transform/map.ts | 9 ++ stories/Layers/Layers.stories.tsx | 2 - stories/Layers/components/HeatmapGrid.tsx | 85 --------------- stories/Picking/components/AdvancedAPI.tsx | 30 +++-- 21 files changed, 302 insertions(+), 173 deletions(-) create mode 100644 packages/layers/src/plugins/LayerStylePlugin.ts create mode 100644 packages/source/src/transform/filter.ts create mode 100644 packages/source/src/transform/map.ts delete mode 100644 stories/Layers/components/HeatmapGrid.tsx diff --git a/.gitignore b/.gitignore index 0b84735259..115b6ecf1f 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ git_log.sh node_modules/ packages/l7/package_bak.json +stories/Test diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index 0e3a7ff13b..0b76770525 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -47,9 +47,13 @@ const defaultLayerConfig: Partial = { minZoom: 0, maxZoom: 20, visible: true, + autoFit: false, zIndex: 0, - enableMultiPassRenderer: false, - enablePicking: false, + pickedFeatureID: -1, + enableMultiPassRenderer: true, + enablePicking: true, + active: false, + activeColor: 'red', enableHighlight: false, highlightColor: 'red', enableTAA: false, diff --git a/packages/core/src/services/interaction/IInteractionService.ts b/packages/core/src/services/interaction/IInteractionService.ts index df77a1d265..ed351c574e 100644 --- a/packages/core/src/services/interaction/IInteractionService.ts +++ b/packages/core/src/services/interaction/IInteractionService.ts @@ -8,7 +8,7 @@ export interface IInteractionService { destroy(): void; on( eventName: InteractionEvent, - callback: (params: { x: number; y: number }) => void, + callback: (params: { x: number; y: number; type: string }) => void, ): void; - triggerHover({ x, y }: { x: number; y: number }): void; + triggerHover({ x, y, type }: { x: number; y: number; type?: string }): void; } diff --git a/packages/core/src/services/interaction/InteractionService.ts b/packages/core/src/services/interaction/InteractionService.ts index 65d1bb58a5..add63327e4 100644 --- a/packages/core/src/services/interaction/InteractionService.ts +++ b/packages/core/src/services/interaction/InteractionService.ts @@ -5,7 +5,6 @@ import { TYPES } from '../../types'; import { ILogService } from '../log/ILogService'; import { IMapService } from '../map/IMapService'; import { IInteractionService, InteractionEvent } from './IInteractionService'; - /** * 由于目前 L7 与地图结合的方案为双 canvas 而非共享 WebGL Context,事件监听注册在地图底图上。 * 除此之外,后续如果支持非地图场景,事件监听就需要注册在 L7 canvas 上。 @@ -49,8 +48,13 @@ export default class InteractionService extends EventEmitter // hammertime.on('panmove', this.onPanmove); // hammertime.on('panend', this.onPanend); // hammertime.on('pinch', this.onPinch); - $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; // TODO: 根据场景注册事件到 L7 canvas 上 @@ -62,16 +66,21 @@ export default class InteractionService extends EventEmitter const $containter = this.mapService.getMapContainer(); if ($containter) { $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(); if ($containter) { const { top, left } = $containter.getBoundingClientRect(); x -= left; y -= top; } - this.emit(InteractionEvent.Hover, { x, y }); + this.emit(InteractionEvent.Hover, { x, y, type }); }; } diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index e864586575..c3164a439a 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -22,7 +22,13 @@ import { StyleAttributeOption, Triangulation, } from './IStyleAttributeService'; - +export interface IDataState { + dataSourceNeedUpdate: boolean; + dataMappingNeedUpdate: boolean; + filterNeedUpdate: boolean; + featureScaleNeedUpdate: boolean; + StyleAttrNeedUpdate: boolean; +} export interface ILayerModelInitializationOptions { moduleName: string; vertexShader: string; @@ -44,6 +50,10 @@ export interface IPickedFeature { lnglat?: { lng: number; lat: number }; feature?: unknown; } +// 交互样式 +export interface IActiveOption { + color: string | number[]; +} export interface ILayer { id: string; // 一个场景中同一类型 Layer 可能存在多个 @@ -52,9 +62,11 @@ export interface ILayer { zIndex: number; plugins: ILayerPlugin[]; layerModelNeedUpdate: boolean; - dataPluginsState: { [key: string]: boolean }; + dataState: IDataState; // 数据流状态 + pickedFeatureID: number; hooks: { init: SyncBailHook; + afterInit: SyncBailHook; beforeRenderData: SyncWaterfallHook; beforeRender: SyncBailHook; afterRender: SyncHook; @@ -87,7 +99,16 @@ export interface ILayer { animate(option: IAnimateOption): ILayer; // pattern(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; hide(): ILayer; show(): ILayer; @@ -100,6 +121,7 @@ export interface ILayer { destroy(): void; source(data: any, option?: ISourceCFG): ILayer; setData(data: any, option?: ISourceCFG): ILayer; + fitBounds(): ILayer; /** * 向当前图层注册插件 * @param plugin 插件实例 @@ -110,11 +132,13 @@ export interface ILayer { setEncodedData(encodedData: IEncodeFeature[]): void; getEncodedData(): IEncodeFeature[]; getScaleOptions(): IScaleOptions; + /** * 事件 */ on(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; /** * JSON Schema 用于校验配置项 @@ -125,6 +149,8 @@ export interface ILayer { * 直接调用拾取方法,在非鼠标交互场景中使用 */ pick(query: { x: number; y: number }): void; + + updateLayerConfig(configToUpdate: Partial): void; } /** @@ -159,6 +185,8 @@ export interface ILayerConfig { maxZoom: number; visible: boolean; zIndex: number; + autoFit: boolean; + pickedFeatureID: number; enableMultiPassRenderer: boolean; passes: Array; @@ -174,6 +202,8 @@ export interface ILayerConfig { * 高亮颜色 */ highlightColor: string | number[]; + active: boolean; + activeColor: string | number[]; /** * 开启 TAA */ diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index cad35e8eb8..4c1785cc76 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -112,7 +112,18 @@ export default class PixelPickingPass< * 拾取视口指定坐标属于的要素 * 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 { getViewportSize, readPixels, @@ -152,9 +163,9 @@ export default class PixelPickingPass< ) { this.logger.debug('picked'); const pickedFeatureIdx = decodePickingColor(pickedColors); - const rawFeature = this.layer.getSource()?.data?.dataArray[ - pickedFeatureIdx - ]; + const rawFeature = this.layer + .getSource() + .getFeatureById(pickedFeatureIdx); if (!rawFeature) { // this.logger.error( @@ -162,7 +173,7 @@ export default class PixelPickingPass< // ); } else { // trigger onHover/Click callback on layer - this.triggerHoverOnLayer({ x, y, feature: rawFeature }); + this.triggerHoverOnLayer({ x, y, type, feature: rawFeature }); } } }); @@ -175,10 +186,12 @@ export default class PixelPickingPass< private triggerHoverOnLayer({ x, y, + type, feature, }: { x: number; y: number; + type: string; feature: unknown; }) { const { onHover, onClick } = this.layer.getLayerConfig(); @@ -196,6 +209,11 @@ export default class PixelPickingPass< feature, }); } + this.layer.emit(type, { + x, + y, + feature, + }); } /** diff --git a/packages/core/src/services/source/ISourceService.ts b/packages/core/src/services/source/ISourceService.ts index 2e03b0ac31..71d3ebbce9 100644 --- a/packages/core/src/services/source/ISourceService.ts +++ b/packages/core/src/services/source/ISourceService.ts @@ -62,6 +62,7 @@ export interface ISource { cluster: boolean; clusterOptions: Partial; updateClusterData(zoom: number): void; + getFeatureById(id: number): unknown; } export interface IRasterCfg { extent: [number, number, number, number]; diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index ae4463bc56..d70a1f20be 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -1,6 +1,8 @@ import { gl, + IActiveOption, IAnimateOption, + IDataState, IEncodeFeature, IFontService, IGlobalConfigService, @@ -44,12 +46,10 @@ import mergeJsonSchemas from 'merge-json-schemas'; import { SyncBailHook, SyncHook, SyncWaterfallHook } from 'tapable'; import { normalizePasses } from '../plugins/MultiPassRendererPlugin'; import baseLayerSchema from './schema'; - /** * 分配 layer id */ let layerIdCounter = 0; -const MapEventTypes = ['zoomchange', 'dragend', 'camerachange', 'resize']; export default class BaseLayer extends EventEmitter implements ILayer { @@ -61,15 +61,19 @@ export default class BaseLayer extends EventEmitter 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 pickedFeatureID: number = -1; + + public dataState: IDataState = { + dataSourceNeedUpdate: false, + dataMappingNeedUpdate: false, + filterNeedUpdate: false, + featureScaleNeedUpdate: false, + StyleAttrNeedUpdate: false, }; // 生命周期钩子 public hooks = { init: new SyncBailHook(), + afterInit: new SyncBailHook(), beforeRender: new SyncBailHook(), beforeRenderData: new SyncWaterfallHook(['data']), afterRender: new SyncHook(), @@ -140,6 +144,8 @@ export default class BaseLayer extends EventEmitter private rawConfig: Partial; + private needUpdateConfig: Partial; + /** * 待更新样式属性,在初始化阶段完成注册 */ @@ -165,11 +171,20 @@ export default class BaseLayer extends EventEmitter public updateLayerConfig( configToUpdate: Partial, ) { - const sceneId = this.container.get(TYPES.SceneID); - this.configService.setLayerConfig(sceneId, this.id, { - ...this.configService.getLayerConfig(this.id), - ...configToUpdate, - }); + if (!this.inited) { + this.needUpdateConfig = { + ...this.needUpdateConfig, + ...configToUpdate, + }; + } else { + const sceneId = this.container.get(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 extends EventEmitter // 触发 init 生命周期插件 this.hooks.init.call(); + this.inited = true; + + this.hooks.afterInit.call(); this.buildModels(); - - this.inited = true; // 触发初始化完成事件; this.emit('inited'); return this; @@ -311,6 +327,7 @@ export default class BaseLayer extends EventEmitter }); return this; } + // 对mapping后的数据过滤,scale保持不变 public filter( field: StyleAttributeField, values?: StyleAttributeOption, @@ -322,6 +339,7 @@ export default class BaseLayer extends EventEmitter attributeValues: values, updateOptions, }); + this.dataState.dataMappingNeedUpdate = true; return this; } @@ -418,11 +436,51 @@ export default class BaseLayer extends EventEmitter 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 { this.updateLayerConfig({ visible: true, }); - this.layerService.renderLayers(); return this; } @@ -430,7 +488,6 @@ export default class BaseLayer extends EventEmitter this.updateLayerConfig({ visible: false, }); - this.layerService.renderLayers(); return this; } @@ -467,13 +524,20 @@ export default class BaseLayer extends EventEmitter /** * zoom to layer Bounds */ - public fitBounds(): void { + public fitBounds(): ILayer { + if (!this.inited) { + this.updateLayerConfig({ + autoFit: true, + }); + return this; + } const source = this.getSource(); const extent = source.extent; this.mapService.fitBounds([ [extent[0], extent[1]], [extent[2], extent[3]], ]); + return this; } public destroy() { @@ -614,11 +678,6 @@ export default class BaseLayer extends EventEmitter }; } - private registerMapEvent() { - MapEventTypes.forEach((type) => { - this.mapService.on(type, this.layerMapHander.bind(this, type)); - }); - } private layerMapHander(type: string, data: any) { this.emit(type, data); } diff --git a/packages/layers/src/heatmap/models/hexagon.ts b/packages/layers/src/heatmap/models/hexagon.ts index 16df1f4102..11c9a6f473 100644 --- a/packages/layers/src/heatmap/models/hexagon.ts +++ b/packages/layers/src/heatmap/models/hexagon.ts @@ -45,29 +45,29 @@ export default class HexagonModel extends BaseModel { } protected registerBuiltinAttributes() { // point layer size; - this.styleAttributeService.registerStyleAttribute({ - name: 'size', - type: AttributeType.Attribute, - descriptor: { - name: 'a_Size', - 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, - vertex: number[], - attributeIdx: number, - ) => { - const { size } = feature; - return Array.isArray(size) ? [size[0]] : [size as number]; - }, - }, - }); + // this.styleAttributeService.registerStyleAttribute({ + // name: 'size', + // type: AttributeType.Attribute, + // descriptor: { + // name: 'a_Size', + // 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, + // vertex: number[], + // attributeIdx: number, + // ) => { + // const { size } = feature; + // return Array.isArray(size) ? [size[0]] : [size as number]; + // }, + // }, + // }); // point layer size; this.styleAttributeService.registerStyleAttribute({ diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index b1672cafaa..408d77b348 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -13,6 +13,7 @@ import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin import DataMappingPlugin from './plugins/DataMappingPlugin'; import DataSourcePlugin from './plugins/DataSourcePlugin'; import FeatureScalePlugin from './plugins/FeatureScalePlugin'; +import LayerStylePlugin from './plugins/LayerStylePlugin'; import LightingPlugin from './plugins/LightingPlugin'; import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin'; import PixelPickingPlugin from './plugins/PixelPickingPlugin'; @@ -56,6 +57,15 @@ container .bind(TYPES.ILayerPlugin) .to(DataMappingPlugin) .inRequestScope(); + +/** + * 更新地图样式配置项 如active, show, hide + */ +container + .bind(TYPES.ILayerPlugin) + .to(LayerStylePlugin) + .inRequestScope(); + /** * 负责属性更新 */ diff --git a/packages/layers/src/plugins/DataMappingPlugin.ts b/packages/layers/src/plugins/DataMappingPlugin.ts index 6e12dd1ac2..bd7a897893 100644 --- a/packages/layers/src/plugins/DataMappingPlugin.ts +++ b/packages/layers/src/plugins/DataMappingPlugin.ts @@ -27,13 +27,13 @@ export default class DataMappingPlugin implements ILayerPlugin { }: { styleAttributeService: IStyleAttributeService }, ) { layer.hooks.init.tap('DataMappingPlugin', () => { - this.doMaping(layer, { styleAttributeService }); + this.generateMaping(layer, { styleAttributeService }); }); layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => { - if (flag) { - layer.dataPluginsState.DataMapping = false; - this.doMaping(layer, { styleAttributeService }); + if (flag || layer.dataState.dataMappingNeedUpdate) { + layer.dataState.dataMappingNeedUpdate = false; + this.generateMaping(layer, { styleAttributeService }); return true; } return false; @@ -52,7 +52,7 @@ export default class DataMappingPlugin implements ILayerPlugin { } }); } - private doMaping( + private generateMaping( layer: ILayer, { styleAttributeService, @@ -62,6 +62,7 @@ export default class DataMappingPlugin implements ILayerPlugin { 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]; diff --git a/packages/layers/src/plugins/LayerStylePlugin.ts b/packages/layers/src/plugins/LayerStylePlugin.ts new file mode 100644 index 0000000000..c6d3915604 --- /dev/null +++ b/packages/layers/src/plugins/LayerStylePlugin.ts @@ -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), + }), + ); + }); + } +} diff --git a/packages/layers/src/plugins/PixelPickingPlugin.ts b/packages/layers/src/plugins/PixelPickingPlugin.ts index 782043f322..3afd3f0387 100644 --- a/packages/layers/src/plugins/PixelPickingPlugin.ts +++ b/packages/layers/src/plugins/PixelPickingPlugin.ts @@ -8,15 +8,7 @@ import { IStyleAttributeService, } from '@antv/l7-core'; import { injectable } from 'inversify'; -import { rgb2arr } from '../utils/color'; - -function encodePickingColor(featureIdx: number): [number, number, number] { - return [ - (featureIdx + 1) & 255, - ((featureIdx + 1) >> 8) & 255, - (((featureIdx + 1) >> 8) >> 8) & 255, - ]; -} +import { encodePickingColor, rgb2arr } from '../utils/color'; const PickingStage = { NONE: 0.0, @@ -50,9 +42,13 @@ export default class PixelPickingPlugin implements ILayerPlugin { }, size: 3, // TODO: 固定 feature range 范围内的 pickingColor 都是固定的,可以生成 cache - update: (feature: IEncodeFeature, featureIdx: number) => + update: (feature: IEncodeFeature, featureIdx: number) => { // 只有开启拾取才需要 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) { layer.hooks.beforePickingEncode.tap('PixelPickingPlugin', () => { const { enablePicking } = layer.getLayerConfig(); - if (enablePicking) { + if (enablePicking && layer.isVisible()) { layer.models.forEach((model) => model.addUniforms({ u_PickingStage: PickingStage.ENCODE, @@ -71,7 +67,7 @@ export default class PixelPickingPlugin implements ILayerPlugin { layer.hooks.afterPickingEncode.tap('PixelPickingPlugin', () => { const { enablePicking } = layer.getLayerConfig(); - if (enablePicking) { + if (enablePicking && layer.isVisible()) { layer.models.forEach((model) => model.addUniforms({ u_PickingStage: PickingStage.NONE, diff --git a/packages/layers/src/utils/color.ts b/packages/layers/src/utils/color.ts index 20cbcff16e..f366874639 100644 --- a/packages/layers/src/utils/color.ts +++ b/packages/layers/src/utils/color.ts @@ -15,6 +15,16 @@ export function rgb2arr(str: string) { 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 { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d') as CanvasRenderingContext2D; diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index b4f693a4ca..cf2cbc9ae2 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -6,8 +6,10 @@ import json from './parser/json'; import raster from './parser/raster'; import Source from './source'; import { cluster } from './transform/cluster'; +import { filter } from './transform/filter'; import { aggregatorToGrid } from './transform/grid'; import { pointToHexbin } from './transform/hexagon'; +import { map } from './transform/map'; export default Source; registerParser('geojson', geojson); registerParser('image', image); @@ -15,6 +17,8 @@ registerParser('csv', csv); registerParser('json', json); registerParser('raster', raster); registerTransform('cluster', cluster); +registerTransform('filter', filter); +registerTransform('map', map); registerTransform('grid', aggregatorToGrid); registerTransform('hexagon', pointToHexbin); export { diff --git a/packages/source/src/source.ts b/packages/source/src/source.ts index 86a31b9350..fc7210ce77 100644 --- a/packages/source/src/source.ts +++ b/packages/source/src/source.ts @@ -121,6 +121,16 @@ export default class Source extends EventEmitter { }); 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 { const parser = this.parser; diff --git a/packages/source/src/transform/filter.ts b/packages/source/src/transform/filter.ts new file mode 100644 index 0000000000..9a0b50a127 --- /dev/null +++ b/packages/source/src/transform/filter.ts @@ -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; +} diff --git a/packages/source/src/transform/map.ts b/packages/source/src/transform/map.ts new file mode 100644 index 0000000000..2aedb29814 --- /dev/null +++ b/packages/source/src/transform/map.ts @@ -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; +} diff --git a/stories/Layers/Layers.stories.tsx b/stories/Layers/Layers.stories.tsx index c8af5a793c..3800e9eae3 100644 --- a/stories/Layers/Layers.stories.tsx +++ b/stories/Layers/Layers.stories.tsx @@ -5,7 +5,6 @@ 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'; import PointDemo from './components/Point'; import Point3D from './components/Point3D'; @@ -25,7 +24,6 @@ storiesOf('图层', module) .add('线图层', () => ) .add('3D弧线', () => ) .add('2D弧线', () => ) - .add('网格热力图', () => ) .add('热力图', () => ) .add('栅格', () => ) .add('图片', () => ); diff --git a/stories/Layers/components/HeatmapGrid.tsx b/stories/Layers/components/HeatmapGrid.tsx deleted file mode 100644 index 82a48d4c43..0000000000 --- a/stories/Layers/components/HeatmapGrid.tsx +++ /dev/null @@ -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 ( -
- ); - } -} diff --git a/stories/Picking/components/AdvancedAPI.tsx b/stories/Picking/components/AdvancedAPI.tsx index 5c63622f46..5dfbe984c0 100644 --- a/stories/Picking/components/AdvancedAPI.tsx +++ b/stories/Picking/components/AdvancedAPI.tsx @@ -38,13 +38,18 @@ export default class AdvancedAPI extends React.Component { highlightColor: [0, 0, 1, 1], onHover: (pickedFeature) => { // tslint:disable-next-line:no-console - console.log(pickedFeature); + }, + onClick: (pickedFeature) => { + // tslint:disable-next-line:no-console }, }); layer .source(await response.json()) .size('name', [0, 10000, 50000, 30000, 100000]) + .active({ + color: 'red', + }) .color('name', [ '#2E8AE6', '#69D1AB', @@ -58,6 +63,9 @@ export default class AdvancedAPI extends React.Component { opacity: 0.8, }); scene.addLayer(layer); + layer.on('click', (e) => { + console.log(e); + }); this.scene = scene; @@ -67,9 +75,10 @@ export default class AdvancedAPI extends React.Component { const styleOptions = { enablePicking: true, enableHighlight: true, - highlightColor: [0, 0, 255], + highlightColor: [1, 0, 0], pickingX: window.innerWidth / 2, pickingY: window.innerHeight / 2, + visible: true, }; const pointFolder = gui.addFolder('非鼠标 hover 交互'); pointFolder @@ -80,23 +89,30 @@ export default class AdvancedAPI extends React.Component { }); scene.render(); }); + pointFolder.add(styleOptions, 'visible').onChange((visible: boolean) => { + layer.style({ + visible, + }); + scene.render(); + }); pointFolder .add(styleOptions, 'pickingX', 0, window.innerWidth) .onChange((pickingX: number) => { - layer.pick({ x: pickingX, y: styleOptions.pickingY }); + layer.setActive({ x: pickingX, y: styleOptions.pickingY }); }); pointFolder .add(styleOptions, 'pickingY', 0, window.innerHeight) .onChange((pickingY: number) => { - layer.pick({ x: styleOptions.pickingX, y: pickingY }); + layer.setActive({ x: styleOptions.pickingX, y: pickingY }); }); pointFolder .addColor(styleOptions, 'highlightColor') .onChange((highlightColor: number[]) => { const [r, g, b] = highlightColor.map((c) => c / 255); - layer.style({ - highlightColor: [r, g, b, 1], - }); + layer.setActive( + { x: styleOptions.pickingX, y: styleOptions.pickingY }, + { color: [r, g, b, 1] }, + ); scene.render(); }); pointFolder.open(); From 1ea4af809de978e106b2958dffb143a9ca7f3657 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Thu, 12 Dec 2019 19:42:17 +0800 Subject: [PATCH 3/4] feat(source): add join transfroms --- .../renderer/passes/PixelPickingPass.ts | 28 +++++++++---------- packages/l7/package.json | 1 + packages/source/src/index.ts | 1 + packages/source/src/transform/join.ts | 28 +++++++++++++++++++ 4 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 packages/source/src/transform/join.ts diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index 4c1785cc76..a9c50ee8d7 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -195,20 +195,20 @@ export default class PixelPickingPass< feature: unknown; }) { const { onHover, onClick } = this.layer.getLayerConfig(); - if (onHover) { - onHover({ - x, - y, - feature, - }); - } - if (onClick) { - onClick({ - x, - y, - feature, - }); - } + // if (onHover) { + // onHover({ + // x, + // y, + // feature, + // }); + // } + // if (onClick) { + // onClick({ + // x, + // y, + // feature, + // }); + // } this.layer.emit(type, { x, y, diff --git a/packages/l7/package.json b/packages/l7/package.json index 19812ad383..7f9876aa65 100644 --- a/packages/l7/package.json +++ b/packages/l7/package.json @@ -5,6 +5,7 @@ "main": "lib/index.js", "module": "es/index.js", "types": "es/index.d.ts", + "unpkg": "dist/l7.js", "sideEffects": true, "files": [ "dist", diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index cf2cbc9ae2..2c36644448 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -9,6 +9,7 @@ import { cluster } from './transform/cluster'; import { filter } from './transform/filter'; import { aggregatorToGrid } from './transform/grid'; import { pointToHexbin } from './transform/hexagon'; +import { join } from './transform/join'; import { map } from './transform/map'; export default Source; registerParser('geojson', geojson); diff --git a/packages/source/src/transform/join.ts b/packages/source/src/transform/join.ts new file mode 100644 index 0000000000..b6dcb9ffd5 --- /dev/null +++ b/packages/source/src/transform/join.ts @@ -0,0 +1,28 @@ +import { IParseDataItem, IParserData } from '@antv/l7-core'; + +interface IJoinOption { + field: 'string'; + data: any[]; +} + +/** + * + * @param data + * @param options + */ +export function filter(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; +} From ba214864c8b7acb72e693c10aa3a9b92fe4e6db8 Mon Sep 17 00:00:00 2001 From: thinkinggis Date: Thu, 12 Dec 2019 19:42:17 +0800 Subject: [PATCH 4/4] feat(source): add join transfroms --- .../renderer/passes/PixelPickingPass.ts | 28 +++++++++---------- packages/l7/package.json | 1 + packages/source/src/index.ts | 2 ++ packages/source/src/transform/join.ts | 28 +++++++++++++++++++ 4 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 packages/source/src/transform/join.ts diff --git a/packages/core/src/services/renderer/passes/PixelPickingPass.ts b/packages/core/src/services/renderer/passes/PixelPickingPass.ts index 4c1785cc76..a9c50ee8d7 100644 --- a/packages/core/src/services/renderer/passes/PixelPickingPass.ts +++ b/packages/core/src/services/renderer/passes/PixelPickingPass.ts @@ -195,20 +195,20 @@ export default class PixelPickingPass< feature: unknown; }) { const { onHover, onClick } = this.layer.getLayerConfig(); - if (onHover) { - onHover({ - x, - y, - feature, - }); - } - if (onClick) { - onClick({ - x, - y, - feature, - }); - } + // if (onHover) { + // onHover({ + // x, + // y, + // feature, + // }); + // } + // if (onClick) { + // onClick({ + // x, + // y, + // feature, + // }); + // } this.layer.emit(type, { x, y, diff --git a/packages/l7/package.json b/packages/l7/package.json index 19812ad383..7f9876aa65 100644 --- a/packages/l7/package.json +++ b/packages/l7/package.json @@ -5,6 +5,7 @@ "main": "lib/index.js", "module": "es/index.js", "types": "es/index.d.ts", + "unpkg": "dist/l7.js", "sideEffects": true, "files": [ "dist", diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index cf2cbc9ae2..4d8e363280 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -9,6 +9,7 @@ import { cluster } from './transform/cluster'; import { filter } from './transform/filter'; import { aggregatorToGrid } from './transform/grid'; import { pointToHexbin } from './transform/hexagon'; +import { join } from './transform/join'; import { map } from './transform/map'; export default Source; registerParser('geojson', geojson); @@ -18,6 +19,7 @@ registerParser('json', json); registerParser('raster', raster); registerTransform('cluster', cluster); registerTransform('filter', filter); +registerTransform('join', join); registerTransform('map', map); registerTransform('grid', aggregatorToGrid); registerTransform('hexagon', pointToHexbin); diff --git a/packages/source/src/transform/join.ts b/packages/source/src/transform/join.ts new file mode 100644 index 0000000000..023b1e74c4 --- /dev/null +++ b/packages/source/src/transform/join.ts @@ -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; +}