feat(layer): add setSelect setActive 方法 & refactor color util

This commit is contained in:
thinkinggis 2019-12-18 00:33:25 +08:00
parent 4da5798d7c
commit c9ffc3b7c0
24 changed files with 234 additions and 88 deletions

View File

@ -165,7 +165,10 @@ export default class Popup extends EventEmitter implements IPopup {
};
}
private onClickClose() {
private onClickClose(e: Event) {
if (e.stopPropagation) {
e.stopPropagation();
}
this.remove();
}
@ -193,6 +196,9 @@ export default class Popup extends EventEmitter implements IPopup {
this.container.addEventListener('mousedown', (e) => {
e.stopPropagation();
});
this.container.addEventListener('click', (e) => {
e.stopPropagation();
});
}
if (maxWidth && this.container.style.maxWidth !== maxWidth) {
this.container.style.maxWidth = maxWidth;

View File

@ -17,7 +17,9 @@ export default class PopupService implements IPopupService {
}
public addPopup(popup: IPopup) {
if (this.popup) {
this.popup.remove();
}
popup.addTo(this.scene);
this.popup = popup;
}

View File

@ -56,7 +56,9 @@ const defaultLayerConfig: Partial<ILayerConfig> = {
active: false,
activeColor: 'red',
enableHighlight: false,
enableSelect: false,
highlightColor: 'red',
selectColor: 'blue',
enableTAA: false,
jitterScale: 1,
enableLighting: false,

View File

@ -1,6 +1,16 @@
import { ILngLat } from '../map/IMapService';
export enum InteractionEvent {
Hover = 'hover',
Click = 'click',
Select = 'select',
Active = 'active',
}
export interface IInteractionTarget {
x: number;
y: number;
lngLat: ILngLat;
type: string;
featureId?: number;
}
export interface IInteractionService {
@ -8,7 +18,9 @@ export interface IInteractionService {
destroy(): void;
on(
eventName: InteractionEvent,
callback: (params: { x: number; y: number; type: string }) => void,
callback: (params: IInteractionTarget) => void,
): void;
triggerHover({ x, y, type }: { x: number; y: number; type?: string }): void;
triggerHover({ x, y, type }: Partial<IInteractionTarget>): void;
triggerSelect(id: number): void;
triggerActive(id: number): void;
}

View File

@ -36,6 +36,13 @@ export default class InteractionService extends EventEmitter
public triggerHover({ x, y }: { x: number; y: number }) {
this.emit(InteractionEvent.Hover, { x, y });
}
public triggerSelect(id: number): void {
this.emit(InteractionEvent.Select, { featureId: id });
}
public triggerActive(id: number): void {
this.emit(InteractionEvent.Active, { featureId: id });
}
private addEventListenerOnMap() {
const $containter = this.mapService.getMapContainer();
@ -81,6 +88,7 @@ export default class InteractionService extends EventEmitter
x -= left;
y -= top;
}
this.emit(InteractionEvent.Hover, { x, y, type });
const lngLat = this.mapService.containerToLngLat([x, y]);
this.emit(InteractionEvent.Hover, { x, y, lngLat, type });
};
}

View File

@ -73,6 +73,8 @@ export interface ILayer {
beforePickingEncode: SyncHook<void>;
afterPickingEncode: SyncHook<void>;
beforeHighlight: SyncHook<[number[]]>;
beforeSelect: SyncHook<[number[]]>;
afterSelect: SyncHook<void>;
afterHighlight: SyncHook<void>;
beforeDestroy: SyncHook<void>;
afterDestroy: SyncHook<void>;
@ -86,6 +88,8 @@ export interface ILayer {
getLayerConfig(): Partial<ILayerConfig & ISceneConfig>;
getContainer(): Container;
setContainer(container: Container): void;
setCurrentPickId(id: number | null): void;
getCurrentPickId(): number | null;
buildLayerModel(
options: ILayerModelInitializationOptions &
Partial<IModelInitializationOptions>,
@ -198,10 +202,13 @@ export interface ILayerConfig {
*
*/
enableHighlight: boolean;
enableSelect: boolean;
/**
*
*/
highlightColor: string | number[];
selectColor: string | number[];
active: boolean;
activeColor: string | number[];
/**

View File

@ -1,5 +1,9 @@
import { inject, injectable } from 'inversify';
import { IRendererService, IShaderModuleService } from '../../../index';
import {
IMapService,
IRendererService,
IShaderModuleService,
} from '../../../index';
import { TYPES } from '../../../types';
import { ICameraService } from '../../camera/ICameraService';
import { IInteractionService } from '../../interaction/IInteractionService';
@ -17,6 +21,7 @@ export default class BaseNormalPass<InitializationOptions = {}>
protected rendererService: IRendererService;
protected cameraService: ICameraService;
protected mapService: IMapService;
protected interactionService: IInteractionService;
protected layerService: ILayerService;
@ -38,6 +43,7 @@ export default class BaseNormalPass<InitializationOptions = {}>
this.cameraService = layer
.getContainer()
.get<ICameraService>(TYPES.ICameraService);
this.mapService = layer.getContainer().get<IMapService>(TYPES.IMapService);
this.interactionService = layer
.getContainer()
.get<IInteractionService>(TYPES.IInteractionService);

View File

@ -1,20 +1,18 @@
import { decodePickingColor, encodePickingColor } from '@antv/l7-utils';
import { inject, injectable } from 'inversify';
import { TYPES } from '../../../types';
import { InteractionEvent } from '../../interaction/IInteractionService';
import {
IInteractionTarget,
InteractionEvent,
} from '../../interaction/IInteractionService';
import { ILayer } from '../../layer/ILayerService';
import { ILogService } from '../../log/ILogService';
import { ILngLat } from '../../map/IMapService';
import { gl } from '../gl';
import { IFramebuffer } from '../IFramebuffer';
import { PassType } from '../IMultiPassRenderer';
import BaseNormalPass from './BaseNormalPass';
function decodePickingColor(color: Uint8Array): number {
const [i1, i2, i3] = color;
// 1 was added to seperate from no selection
const index = i1 + i2 * 256 + i3 * 65536 - 1;
return index;
}
/**
* color-based PixelPickingPass
* @see https://github.com/antvis/L7/blob/next/dev-docs/PixelPickingEngine.md
@ -66,6 +64,14 @@ export default class PixelPickingPass<
// 监听 hover 事件
this.interactionService.on(InteractionEvent.Hover, this.pickFromPickingFBO);
this.interactionService.on(
InteractionEvent.Select,
this.selectFeatureHander.bind(this),
);
this.interactionService.on(
InteractionEvent.Active,
this.highlightFeatureHander.bind(this),
);
}
public render(layer: ILayer) {
@ -112,15 +118,7 @@ export default class PixelPickingPass<
*
* TODO
*/
private pickFromPickingFBO = ({
x,
y,
type,
}: {
x: number;
y: number;
type: string;
}) => {
private pickFromPickingFBO = ({ x, y, lngLat, type }: IInteractionTarget) => {
if (!this.layer.isVisible()) {
return;
}
@ -130,7 +128,7 @@ export default class PixelPickingPass<
useFramebuffer,
} = this.rendererService;
const { width, height } = getViewportSize();
const { enableHighlight } = this.layer.getLayerConfig();
const { enableHighlight, enableSelect } = this.layer.getLayerConfig();
const xInDevicePixel = x * window.devicePixelRatio;
const yInDevicePixel = y * window.devicePixelRatio;
@ -142,7 +140,6 @@ export default class PixelPickingPass<
) {
return;
}
let pickedColors: Uint8Array | undefined;
useFramebuffer(this.pickingFBO, () => {
// avoid realloc
@ -166,54 +163,58 @@ export default class PixelPickingPass<
const rawFeature = this.layer
.getSource()
.getFeatureById(pickedFeatureIdx);
const target = {
x,
y,
type,
lngLat,
featureId: pickedFeatureIdx,
feature: rawFeature,
};
if (!rawFeature) {
// this.logger.error(
// '未找到颜色编码解码后的原始 feature请检查 fragment shader 中末尾是否添加了 `gl_FragColor = filterColor(gl_FragColor);`',
// );
} else {
// trigger onHover/Click callback on layer
this.triggerHoverOnLayer({ x, y, type, feature: rawFeature });
this.layer.setCurrentPickId(pickedFeatureIdx);
this.triggerHoverOnLayer(target);
}
} else {
const target = {
x,
y,
lngLat,
type: this.layer.getCurrentPickId() === null ? 'unpick' : 'mouseout',
featureId: null,
feature: null,
};
this.triggerHoverOnLayer(target);
this.layer.setCurrentPickId(null);
}
});
if (enableHighlight) {
this.highlightPickedFeature(pickedColors);
}
if (
enableSelect &&
type === 'click' &&
pickedColors?.toString() !== [0, 0, 0, 0].toString()
) {
this.selectFeature(pickedColors);
}
};
private triggerHoverOnLayer({
x,
y,
type,
feature,
}: {
private triggerHoverOnLayer(target: {
x: number;
y: number;
type: string;
lngLat: ILngLat;
feature: unknown;
featureId: number | null;
}) {
const { onHover, onClick } = this.layer.getLayerConfig();
// if (onHover) {
// onHover({
// x,
// y,
// feature,
// });
// }
// if (onClick) {
// onClick({
// x,
// y,
// feature,
// });
// }
this.layer.emit(type, {
x,
y,
feature,
});
this.layer.emit(target.type, target);
}
/**
@ -232,7 +233,6 @@ export default class PixelPickingPass<
private highlightPickedFeature(pickedColors: Uint8Array | undefined) {
const [r, g, b] = pickedColors;
const { clear, useFramebuffer } = this.rendererService;
// 先输出到 PostProcessor
const readFBO = this.layer.multiPassRenderer
.getPostProcessor()
@ -257,4 +257,43 @@ export default class PixelPickingPass<
});
this.layer.multiPassRenderer.getPostProcessor().render(this.layer);
}
private selectFeature(pickedColors: Uint8Array | undefined) {
const [r, g, b] = pickedColors;
const { clear, useFramebuffer } = this.rendererService;
// 先输出到 PostProcessor
const readFBO = this.layer.multiPassRenderer
.getPostProcessor()
.getReadFBO();
this.layer.hooks.beforeRender.call();
useFramebuffer(readFBO, () => {
clear({
color: [0, 0, 0, 0],
depth: 1,
stencil: 0,
framebuffer: readFBO,
});
// TODO: highlight pass 需要 multipass
const originRenderFlag = this.layer.multiPassRenderer.getRenderFlag();
this.layer.multiPassRenderer.setRenderFlag(false);
this.layer.hooks.beforeSelect.call([r, g, b]);
this.layer.render();
this.layer.hooks.afterSelect.call();
this.layer.hooks.afterRender.call();
this.layer.multiPassRenderer.setRenderFlag(originRenderFlag);
});
this.layer.multiPassRenderer.getPostProcessor().render(this.layer);
}
private selectFeatureHander({ featureId }: Partial<IInteractionTarget>) {
const pickedColors = encodePickingColor(featureId as number);
this.selectFeature(new Uint8Array(pickedColors));
}
private highlightFeatureHander({ featureId }: Partial<IInteractionTarget>) {
const pickedColors = encodePickingColor(featureId as number);
this.highlightPickedFeature(new Uint8Array(pickedColors));
}
}

View File

@ -81,6 +81,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
afterPickingEncode: new SyncHook<void>(),
beforeHighlight: new SyncHook<[number[]]>(['pickedColor']),
afterHighlight: new SyncHook<void>(),
beforeSelect: new SyncHook<[number[]]>(['pickedColor']),
afterSelect: new SyncHook<void>(),
beforeDestroy: new SyncHook<void>(),
afterDestroy: new SyncHook<void>(),
};
@ -142,6 +144,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
private configSchema: object;
private currentPickId: number | null = null;
private rawConfig: Partial<ILayerConfig & ChildLayerStyleOptions>;
private needUpdateConfig: Partial<ILayerConfig & ChildLayerStyleOptions>;
@ -159,7 +163,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
private scaleOptions: IScaleOptions = {};
constructor(config: Partial<ILayerConfig & ChildLayerStyleOptions>) {
constructor(config: Partial<ILayerConfig & ChildLayerStyleOptions> = {}) {
super();
this.rawConfig = config;
}
@ -469,10 +473,22 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
? options.color
: this.getLayerConfig().highlightColor,
});
this.interactionService.triggerActive(id);
}
}
public select(option: IActiveOption | false): ILayer {
const activeOption: Partial<ILayerConfig> = {};
activeOption.enableSelect = isObject(option) ? true : option;
if (isObject(option)) {
activeOption.enableSelect = true;
if (option.color) {
activeOption.selectColor = option.color;
}
} else {
activeOption.enableHighlight = !!option;
}
this.updateLayerConfig(activeOption);
return this;
}
@ -480,7 +496,23 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
id: number | { x: number; y: number },
options?: IActiveOption,
): void {
throw new Error('Method not implemented.');
if (isObject(id)) {
const { x = 0, y = 0 } = id;
this.updateLayerConfig({
selectColor: isObject(options)
? options.color
: this.getLayerConfig().selectColor,
});
this.pick({ x, y });
} else {
this.updateLayerConfig({
pickedFeatureID: id,
selectColor: isObject(options)
? options.color
: this.getLayerConfig().selectColor,
});
this.interactionService.triggerSelect(id);
}
}
public show(): ILayer {
this.updateLayerConfig({
@ -502,6 +534,13 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
return this;
}
public setCurrentPickId(id: number) {
this.currentPickId = id;
}
public getCurrentPickId(): number | null {
return this.currentPickId;
}
public isVisible(): boolean {
const zoom = this.mapService.getZoom();

View File

@ -7,10 +7,10 @@ import {
IModelUniform,
ITexture2D,
} from '@antv/l7-core';
import { generateColorRamp, IColorRamp } from '@antv/l7-utils';
import { mat4 } from 'gl-matrix';
import BaseModel from '../../core/BaseModel';
import { HeatmapTriangulation } from '../../core/triangulation';
import { generateColorRamp, IColorRamp } from '../../utils/color';
import heatmap3DFrag from '../shaders/heatmap_3d_frag.glsl';
import heatmap3DVert from '../shaders/heatmap_3d_vert.glsl';
import heatmapColorFrag from '../shaders/heatmap_frag.glsl';

View File

@ -9,8 +9,8 @@ import {
IStyleAttributeService,
TYPES,
} from '@antv/l7-core';
import { rgb2arr } from '@antv/l7-utils';
import { inject, injectable } from 'inversify';
import { rgb2arr } from '../utils/color';
@injectable()
export default class DataMappingPlugin implements ILayerPlugin {

View File

@ -23,7 +23,7 @@ export default class DataSourcePlugin implements ILayerPlugin {
const source = layer.getSource();
const cluster = source.cluster;
const { zoom = 0, maxZoom = 16 } = source.clusterOptions;
const newZoom = this.mapService.getZoom();
const newZoom = this.mapService.getZoom() - 1;
if (cluster && Math.abs(zoom - newZoom) > 1 && maxZoom > zoom) {
source.updateClusterData(Math.floor(newZoom));
return true;

View File

@ -1,7 +1,7 @@
import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
import Source from '@antv/l7-source';
import { encodePickingColor, rgb2arr } from '@antv/l7-utils';
import { injectable } from 'inversify';
import { encodePickingColor, rgb2arr } from '../utils/color';
@injectable()
export default class LayerStylePlugin implements ILayerPlugin {
public apply(layer: ILayer) {

View File

@ -7,8 +7,8 @@ import {
IRendererService,
IStyleAttributeService,
} from '@antv/l7-core';
import { encodePickingColor, rgb2arr } from '@antv/l7-utils';
import { injectable } from 'inversify';
import { encodePickingColor, rgb2arr } from '../utils/color';
const PickingStage = {
NONE: 0.0,
@ -95,6 +95,24 @@ export default class PixelPickingPlugin implements ILayerPlugin {
);
},
);
layer.hooks.beforeSelect.tap(
'PixelPickingPlugin',
(pickedColor: number[]) => {
const { selectColor } = layer.getLayerConfig();
const highlightColorInArray =
typeof selectColor === 'string'
? rgb2arr(selectColor)
: selectColor || [1, 0, 0, 1];
layer.models.forEach((model) =>
model.addUniforms({
u_PickingStage: PickingStage.HIGHLIGHT,
u_PickingColor: pickedColor,
u_HighlightColor: highlightColorInArray.map((c) => c * 255),
}),
);
},
);
// }
}
}

View File

@ -7,9 +7,9 @@ import {
IModel,
IModelUniform,
} from '@antv/l7-core';
import { rgb2arr } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { PointFillTriangulation } from '../../core/triangulation';
import { rgb2arr } from '../../utils/color';
import pointFillFrag from '../shaders/fill_frag.glsl';
import pointFillVert from '../shaders/fill_vert.glsl';
interface IPointLayerStyleOptions {

View File

@ -6,8 +6,8 @@ import {
IModelUniform,
} from '@antv/l7-core';
import { rgb2arr } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { rgb2arr } from '../../utils/color';
import normalFrag from '../shaders/normal_frag.glsl';
import normalVert from '../shaders/normal_vert.glsl';

View File

@ -11,8 +11,8 @@ import {
lazyInject,
TYPES,
} from '@antv/l7-core';
import { generateColorRamp, IColorRamp } from '@antv/l7-utils';
import BaseLayer from '../core/BaseLayer';
import { generateColorRamp, IColorRamp } from '../utils/color';
import { RasterTriangulation } from './buffers/triangulation';
import rasterFrag from './shaders/raster_frag.glsl';
import rasterVert from './shaders/raster_vert.glsl';

View File

@ -1,7 +1,7 @@
import { AttributeType, gl, IEncodeFeature, ITexture2D } from '@antv/l7-core';
import { generateColorRamp, IColorRamp } from '@antv/l7-utils';
import BaseLayer from '../core/BaseLayer';
import { RasterImageTriangulation } from '../core/triangulation';
import { generateColorRamp, IColorRamp } from '../utils/color';
import rasterImageFrag from './shaders/raster_2d_frag.glsl';
import rasterImageVert from './shaders/raster_2d_vert.glsl';
interface IRasterLayerStyleOptions {

View File

@ -15,6 +15,13 @@ export function rgb2arr(str: string) {
return arr;
}
export function decodePickingColor(color: Uint8Array): number {
const [i1, i2, i3] = color;
// 1 was added to seperate from no selection
const index = i1 + i2 * 256 + i3 * 65536 - 1;
return index;
}
export function encodePickingColor(
featureIdx: number,
): [number, number, number] {

View File

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

View File

@ -19,7 +19,7 @@ export default class Point3D extends React.Component {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
map: new Mapbox({
center: [120.19382669582967, 30.258134],
pitch: 0,
style: 'dark',
@ -36,13 +36,18 @@ export default class Point3D extends React.Component {
type: 'quantile',
})
.size('point_count', [5, 10, 15, 20, 25])
.active(true)
.color('red')
.color('yellow')
.style({
opacity: 0.3,
opacity: 0.5,
strokeWidth: 1,
});
scene.addLayer(pointLayer);
pointLayer.on('mousemove', (e) => {
const id = e.featureId;
console.log(id);
pointLayer.setActive(id);
});
this.scene = scene;
});
}

View File

@ -21,21 +21,14 @@ export default class Point3D extends React.Component {
zoom: 1,
}),
});
const pointLayer = new PointLayer({
enablePicking: false,
enableHighlight: false,
enableTAA: false,
onHover: (pickedFeature: any) => {
// tslint:disable-next-line:no-console
console.log('Scene4', pickedFeature.feature.name);
},
});
const pointLayer = new PointLayer();
pointLayer
.source(data, {
cluster: true,
})
.color('red')
.shape('cylinder')
.active({ color: 'blue' })
.size([15, 10]);
scene.addLayer(pointLayer);
scene.render();

View File

@ -41,6 +41,7 @@ export default class Polygon3D extends React.Component {
.source(await response.json())
.shape('extrude')
.size('h20', [100, 120, 160, 200, 260, 500])
.active({ color: 'blue' })
.color('h20', [
'#816CAD',
'#A67FB5',

View File

@ -1,5 +1,5 @@
// @ts-ignore
import { PolygonLayer, Scene } from '@antv/l7';
import { PolygonLayer, Popup, Scene } from '@antv/l7';
import { Mapbox } from '@antv/l7-maps';
import * as dat from 'dat.gui';
import * as React from 'react';
@ -24,15 +24,7 @@ export default class Tooltip extends React.Component {
zoom: 3,
}),
});
const layer = new PolygonLayer({
enablePicking: true,
enableHighlight: false,
onHover: (pickedFeature) => {
// tslint:disable-next-line:no-console
console.log(pickedFeature);
},
});
const layer = new PolygonLayer();
layer
.source(await response.json())
.size('name', [0, 10000, 50000, 30000, 100000])
@ -49,6 +41,14 @@ export default class Tooltip extends React.Component {
opacity: 0.8,
});
scene.addLayer(layer);
layer.on('click', (e) => {
const popup = new Popup({
offsets: [0, 0],
})
.setLnglat(e.lngLat)
.setText(e.feature.properties.name);
scene.addPopup(popup);
});
this.scene = scene;
}