improvement: markerLayer 新增聚合方法 sum| mean| max | min

This commit is contained in:
thinkinggis 2020-01-27 11:27:29 +08:00
parent 2b44abb43e
commit f3bdab91fa
10 changed files with 123 additions and 44 deletions

View File

@ -1,5 +1,5 @@
---
title: Marker Layer
title: Marker 图层
order: 3
---
@ -33,11 +33,20 @@ scene.addMarkerLayer(markerLayer);
#### option
- cluster 是部分聚合 `boolean` 默认 `false`
- cluster 聚合 `boolean` 默认 `false`
- clusterOption 聚合配置
- cluster 是部分聚合
- element `function`
- field `string` 聚合统计字段
- method `sum| max| min| mean`
- element `function` 通过回调函数设置聚合 Marker 的样式,返回 dom 元素
参数feature
point_count 默认 聚合元素个数
clusterData `Array` 聚合节点的原始数据
point_sum 聚合求和 根据 field 和 method 计算
point_max 聚合最大值 根据 field 和 method 计算
point_min 聚合最小值 根据 field 和 method 计算
point_mean 聚合平均值 根据 field 和 method 计算
后续会增加更多配置项目
@ -47,7 +56,7 @@ scene.addMarkerLayer(markerLayer);
参数
- marker `Marker` 需要添加的 Marker
- marker `IMarker` 需要添加的 Marker
添加 Marker
@ -58,6 +67,21 @@ const marker = new Marker().setLnglat(); // 添加进Marker必须设置经纬度
markerLayer.addMarker(marker);
```
为 Marker 添加属性信息,
如果聚合参数设置统计配置项 `field| method`需要为 Marker 添加属性信息
通过 Marker 的 extData[配置项](./marker#option)设置 Marker 属性信息
```javascript
const marker = new Marker({
extData: nodes.features[i].properties,
}).setLnglat({
lng: coordinates[0],
lat: coordinates[1],
});
```
#### removeMarker
从 MarkerLayer 移除 Marker

View File

@ -36,7 +36,17 @@ scene.addMarkerLayer(markerLayer);
- cluster 聚合 `boolean` 默认 `false`
- clusterOption 聚合配置
- field `string` 聚合统计字段
- method `sum| max| min| mean`
- element `function` 通过回调函数设置聚合 Marker 的样式,返回 dom 元素
参数feature
point_count 默认 聚合元素个数
clusterData `Array` 聚合节点的原始数据
point_sum 聚合求和 根据 field 和 method 计算
point_max 聚合最大值 根据 field 和 method 计算
point_min 聚合最小值 根据 field 和 method 计算
point_mean 聚合平均值 根据 field 和 method 计算
后续会增加更多配置项目
@ -57,6 +67,21 @@ const marker = new Marker().setLnglat(); // 添加进Marker必须设置经纬度
markerLayer.addMarker(marker);
```
为 Marker 添加属性信息,
如果聚合参数设置统计配置项 `field| method`需要为 Marker 添加属性信息
通过 Marker 的 extData[配置项](./marker#option)设置 Marker 属性信息
```javascript
const marker = new Marker({
extData: nodes.features[i].properties,
}).setLnglat({
lng: coordinates[0],
lat: coordinates[1],
});
```
#### removeMarker
从 MarkerLayer 移除 Marker

View File

@ -1,8 +1,8 @@
import { IMapService, IMarker, TYPES } from '@antv/l7-core';
import { bindAll, DOM } from '@antv/l7-utils';
import { bindAll, DOM, Satistics } from '@antv/l7-utils';
import { EventEmitter } from 'eventemitter3';
import { Container } from 'inversify';
import { isFunction } from 'lodash';
import { merge } from 'lodash';
import Supercluster from 'supercluster';
import Marker from './marker';
type CallBack = (...args: any[]) => any;
@ -10,6 +10,8 @@ interface IMarkerStyleOption {
element: CallBack;
style: { [key: string]: any } | CallBack;
className: string;
field?: string;
method?: 'sum' | 'max' | 'min' | 'mean';
radius: number;
maxZoom: number;
minZoom: number;
@ -18,7 +20,7 @@ interface IMarkerStyleOption {
interface IMarkerLayerOption {
cluster: boolean;
clusterOption: IMarkerStyleOption;
clusterOption: Partial<IMarkerStyleOption>;
}
interface IPointFeature {
@ -40,10 +42,7 @@ export default class MarkerLayer extends EventEmitter {
constructor(option?: Partial<IMarkerLayerOption>) {
super();
this.markerLayerOption = {
...this.getDefault(),
...option,
};
this.markerLayerOption = merge(this.getDefault(), option);
bindAll(['update'], this);
this.zoom = this.markerLayerOption.clusterOption?.zoom || -99;
}
@ -151,6 +150,24 @@ export default class MarkerLayer extends EventEmitter {
});
this.clusterMarkers = [];
clusterPoint.forEach((feature) => {
const { field, method } = this.markerLayerOption.clusterOption;
// 处理聚合数据
if (feature.properties && feature.properties?.cluster_id) {
const clusterData = this.getLeaves(feature.properties?.cluster_id);
feature.properties.clusterData = clusterData;
if (field && method) {
const columnData = clusterData?.map((item) => {
const data = {
[field]: item.properties[field],
};
return data;
});
const column = Satistics.getColumn(columnData as any, field);
const stat = Satistics.getSatByColumn(method, column);
const fieldName = 'point_' + method;
feature.properties[fieldName] = stat;
}
}
const marker =
feature.properties && feature.properties.hasOwnProperty('point_count')
? this.clusterMarker(feature)
@ -160,7 +177,16 @@ export default class MarkerLayer extends EventEmitter {
marker.addTo(this.scene);
});
}
private getLeaves(
clusterId: number,
limit: number = Infinity,
offset: number = 0,
) {
if (!clusterId) {
return null;
}
return this.clusterIndex.getLeaves(clusterId, limit, offset);
}
private clusterMarker(feature: any) {
const clusterOption = this.markerLayerOption.clusterOption;

View File

@ -1,9 +1,8 @@
/**
*
*/
import { IParserCfg, IParserData, ISourceCFG, ITransform } from '@antv/l7-core';
import { aProjectFlat, metersToLngLat } from '@antv/l7-utils';
import { statMap } from './statistics';
import { IParserData, ITransform } from '@antv/l7-core';
import { Satistics } from '@antv/l7-utils';
interface IGridHash {
[key: string]: any;
@ -90,8 +89,8 @@ function _getGridLayerDataFromGridHash(
[key: string]: any;
} = {};
if (option.field && option.method) {
const columns = getColumn(gridHash[key].points, option.field);
item[option.method] = statMap[option.method](columns);
const columns = Satistics.getColumn(gridHash[key].points, option.field);
item[option.method] = Satistics.statMap[option.method](columns);
}
Object.assign(item, {
_id: i + 1,
@ -99,6 +98,7 @@ function _getGridLayerDataFromGridHash(
-180 + gridOffset.xOffset * lonIdx,
-90 + gridOffset.yOffset * latIdx,
],
rawData: gridHash[key].points,
count: gridHash[key].count,
});
// @ts-ignore
@ -106,8 +106,3 @@ function _getGridLayerDataFromGridHash(
return accu;
}, []);
}
function getColumn(data: any[], columnName: string) {
return data.map((item) => {
return item[columnName] * 1;
});
}

View File

@ -9,7 +9,7 @@ import {
ITransform,
} from '@antv/l7-core';
import { aProjectFlat, metersToLngLat } from '@antv/l7-utils';
import { statMap } from './statistics';
import { statMap } from '../../../utils/src/statistics';
interface IGridHash {
[key: string]: any;

View File

@ -1,14 +1,12 @@
import { aProjectFlat, metersToLngLat } from '@antv/l7-utils';
import { aProjectFlat, Satistics } from '@antv/l7-utils';
import { hexbin } from 'd3-hexbin';
const R_EARTH = 6378000;
import {
IParseDataItem,
IParserCfg,
IParserData,
ISourceCFG,
ITransform,
} from '@antv/l7-core';
import { statMap } from './statistics';
interface IHexBinItem<T> extends Array<T> {
x: number;
y: number;
@ -39,12 +37,13 @@ export function pointToHexbin(data: IParserData, option: ITransform) {
const result: IParserData = {
dataArray: hexbinBins.map((hex: IHexBinItem<IRawData>, index: number) => {
if (option.field && method) {
const columns = getColumn(hex, option.field);
hex[method] = statMap[method](columns);
const columns = Satistics.getColumn(hex, option.field);
hex[method] = Satistics.statMap[method](columns);
}
return {
[option.method]: hex[method],
count: hex.length,
rawData: hex,
coordinates: [hex.x, hex.y],
_id: index + 1,
};
@ -56,8 +55,3 @@ export function pointToHexbin(data: IParserData, option: ITransform) {
};
return result;
}
function getColumn(data: IHexBinItem<IRawData>, columnName: string) {
return data.map((item: IRawData) => {
return item[columnName] * 1;
});
}

View File

@ -5,4 +5,5 @@ export * from './geo';
export * from './lru_cache';
export * from './event';
export * from './color';
export { DOM };
import * as Satistics from './statistics';
export { DOM, Satistics };

View File

@ -1,3 +1,6 @@
interface IItemData {
[key: string]: any;
}
function max(x: number[]) {
if (x.length === 0) {
throw new Error('max requires at least one data point');
@ -75,3 +78,12 @@ export const statMap: { [key: string]: any } = {
mean,
sum,
};
export function getColumn(data: IItemData[], columnName: string) {
return data.map((item: IItemData) => {
return item[columnName] * 1;
});
}
export function getSatByColumn(type: string, column: number[]) {
return statMap[type](column);
}

View File

@ -1,6 +1,6 @@
// @ts-ignore
import { Marker, MarkerLayer, PolygonLayer, Scene } from '@antv/l7';
import { Mapbox, GaodeMap } from '@antv/l7-maps';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import * as React from 'react';
export default class ClusterMarkerLayer extends React.Component {
private scene: Scene;
@ -34,6 +34,10 @@ export default class ClusterMarkerLayer extends React.Component {
const nodes = await response.json();
const markerLayer = new MarkerLayer({
cluster: true,
clusterOption: {
field: 'mag',
method: 'sum',
},
});
const scene = new Scene({
id: 'map',
@ -47,7 +51,9 @@ export default class ClusterMarkerLayer extends React.Component {
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < nodes.features.length; i++) {
const { coordinates } = nodes.features[i].geometry;
const marker = new Marker().setLnglat({
const marker = new Marker({
extData: nodes.features[i].properties,
}).setLnglat({
lng: coordinates[0],
lat: coordinates[1],
});

View File

@ -28,10 +28,7 @@ export default class HeatMapLayerDemo extends React.Component {
const layer = new HeatmapLayer();
layer
.source({
type: 'FeatureCollection',
features: [],
})
.source(data)
.shape('heatmap')
.size('mag', [0, 1.0]) // weight映射通道
.style({
@ -51,9 +48,8 @@ export default class HeatMapLayerDemo extends React.Component {
},
});
scene.addLayer(layer);
layer.setData({
type: 'FeatureCollection',
features: data.features.slice(0, 100),
scene.on('loaded', () => {
console.log('scene loaded');
});
this.scene = scene;
}