refactor(feature picking): feature picking

This commit is contained in:
thinkinggis 2020-02-17 22:47:33 +08:00
parent 17f44bb2f7
commit 5936f9aa47
30 changed files with 566 additions and 102 deletions

View File

@ -104,6 +104,7 @@
"stylelint-config-styled-components": "^0.1.1",
"stylelint-processor-styled-components": "^1.3.2",
"svg-inline-loader": "^0.8.0",
"tapable": "^1.1.3",
"ts-jest": "^24.0.2",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",

View File

@ -17,6 +17,7 @@ import { IControlService } from './services/component/IControlService';
import { IGlobalConfigService } from './services/config/IConfigService';
import { ICoordinateSystemService } from './services/coordinate/ICoordinateSystemService';
import { IInteractionService } from './services/interaction/IInteractionService';
import { IPickingService } from './services/interaction/IPickingService';
import { ILayerService } from './services/layer/ILayerService';
import { IStyleAttributeService } from './services/layer/IStyleAttributeService';
import { ILogService } from './services/log/ILogService';
@ -33,6 +34,7 @@ import PopupService from './services/component/PopupService';
import GlobalConfigService from './services/config/ConfigService';
import CoordinateSystemService from './services/coordinate/CoordinateSystemService';
import InteractionService from './services/interaction/InteractionService';
import PickingService from './services/interaction/PickingService';
import LayerService from './services/layer/LayerService';
import StyleAttributeService from './services/layer/StyleAttributeService';
import LogService from './services/log/LogService';
@ -180,6 +182,10 @@ export function createSceneContainer() {
.bind<IInteractionService>(TYPES.IInteractionService)
.to(InteractionService)
.inSingletonScope();
sceneContainer
.bind<IPickingService>(TYPES.IPickingService)
.to(PickingService)
.inSingletonScope();
sceneContainer
.bind<IControlService>(TYPES.IControlService)
.to(ControlService)

View File

@ -55,7 +55,7 @@ const defaultLayerConfig: Partial<ILayerConfig> = {
zIndex: 0,
blend: 'normal',
pickedFeatureID: -1,
enableMultiPassRenderer: true,
enableMultiPassRenderer: false,
enablePicking: true,
active: false,
activeColor: '#2f54eb',

View File

@ -0,0 +1,3 @@
export interface IPickingService {
init(): void;
}

View File

@ -0,0 +1,247 @@
import { decodePickingColor, encodePickingColor } from '@antv/l7-utils';
import { inject, injectable } from 'inversify';
import {
IMapService,
IRendererService,
IShaderModuleService,
} from '../../index';
import { TYPES } from '../../types';
import { ICameraService } from '../camera/ICameraService';
import {
IInteractionService,
IInteractionTarget,
InteractionEvent,
} from '../interaction/IInteractionService';
import { ILayer, ILayerService } from '../layer/ILayerService';
import { ILngLat } from '../map/IMapService';
import { gl } from '../renderer/gl';
import { IFramebuffer } from '../renderer/IFramebuffer';
import { IPickingService } from './IPickingService';
@injectable()
export default class PickingService implements IPickingService {
@inject(TYPES.IRendererService)
private rendererService: IRendererService;
@inject(TYPES.IInteractionService)
private interactionService: IInteractionService;
@inject(TYPES.ILayerService)
private layerService: ILayerService;
private pickingFBO: IFramebuffer;
private width: number = 0;
private height: number = 0;
private alreadyInPicking: boolean = false;
public init() {
const {
createTexture2D,
createFramebuffer,
getViewportSize,
} = this.rendererService;
const { width, height } = getViewportSize();
// 创建 picking framebuffer后续实时 resize
this.pickingFBO = createFramebuffer({
color: createTexture2D({
width,
height,
wrapS: gl.CLAMP_TO_EDGE,
wrapT: gl.CLAMP_TO_EDGE,
}),
});
// 监听 hover 事件
this.interactionService.on(
InteractionEvent.Hover,
this.pickingAllLayer.bind(this),
);
}
private async pickingAllLayer(target: IInteractionTarget) {
if (this.alreadyInPicking || this.layerService.alreadyInRendering) {
return;
}
this.alreadyInPicking = true;
const layers = this.layerService.getLayers();
layers
.filter((layer) => layer.needPick())
.reverse()
.forEach(async (layer) => {
await this.pickingLayer(layer, target); // 可以实现是否向下触发
});
this.layerService.renderLayers();
this.alreadyInPicking = false;
}
private async pickingLayer(layer: ILayer, target: IInteractionTarget) {
const { getViewportSize, useFramebuffer, clear } = this.rendererService;
const { width, height } = getViewportSize();
if (this.width !== width || this.height !== height) {
this.pickingFBO.resize({ width, height });
this.width = width;
this.height = height;
}
useFramebuffer(this.pickingFBO, () => {
clear({
framebuffer: this.pickingFBO,
color: [0, 0, 0, 0],
stencil: 0,
depth: 1,
});
layer.hooks.beforePickingEncode.call();
layer.renderModels();
layer.hooks.afterPickingEncode.call();
this.pickFromPickingFBO(layer, target);
});
}
private pickFromPickingFBO = (
layer: ILayer,
{ x, y, lngLat, type }: IInteractionTarget,
) => {
const {
getViewportSize,
readPixels,
useFramebuffer,
} = this.rendererService;
const { width, height } = getViewportSize();
const { enableHighlight, enableSelect } = layer.getLayerConfig();
const xInDevicePixel = x * window.devicePixelRatio;
const yInDevicePixel = y * window.devicePixelRatio;
if (
xInDevicePixel > width ||
xInDevicePixel < 0 ||
yInDevicePixel > height ||
yInDevicePixel < 0
) {
return;
}
let pickedColors: Uint8Array | undefined;
pickedColors = readPixels({
x: Math.round(xInDevicePixel),
// 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴
y: Math.round(height - (y + 1) * window.devicePixelRatio),
width: 1,
height: 1,
data: new Uint8Array(1 * 1 * 4),
framebuffer: this.pickingFBO,
});
if (
pickedColors[0] !== 0 ||
pickedColors[1] !== 0 ||
pickedColors[2] !== 0
) {
const pickedFeatureIdx = decodePickingColor(pickedColors);
const rawFeature = 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
layer.setCurrentPickId(pickedFeatureIdx);
this.triggerHoverOnLayer(layer, target);
}
} else {
const target = {
x,
y,
lngLat,
type: layer.getCurrentPickId() === null ? 'un' + type : 'mouseout',
featureId: null,
feature: null,
};
this.triggerHoverOnLayer(layer, {
...target,
type: 'unpick',
});
this.triggerHoverOnLayer(layer, target);
layer.setCurrentPickId(null);
}
if (enableHighlight) {
this.highlightPickedFeature(layer, pickedColors);
}
if (
enableSelect &&
type === 'click' &&
pickedColors?.toString() !== [0, 0, 0, 0].toString()
) {
this.selectFeature(layer, pickedColors);
}
};
private triggerHoverOnLayer(
layer: ILayer,
target: {
x: number;
y: number;
type: string;
lngLat: ILngLat;
feature: unknown;
featureId: number | null;
},
) {
layer.emit(target.type, target);
}
/**
* highlight feature buffer
* 1.
* 2. alpha
* shader
* @example
* this.layer.color('name', ['#000000'], {
* featureRange: {
* startIndex: pickedFeatureIdx,
* endIndex: pickedFeatureIdx + 1,
* },
* });
*/
private highlightPickedFeature(
layer: ILayer,
pickedColors: Uint8Array | undefined,
) {
const [r, g, b] = pickedColors;
layer.hooks.beforeHighlight.call([r, g, b]);
this.layerService.renderLayers();
}
private selectFeature(layer: ILayer, pickedColors: Uint8Array | undefined) {
const [r, g, b] = pickedColors;
layer.hooks.beforeSelect.call([r, g, b]);
this.layerService.renderLayers();
}
private selectFeatureHandle(
layer: ILayer,
{ featureId }: Partial<IInteractionTarget>,
) {
const pickedColors = encodePickingColor(featureId as number);
this.selectFeature(layer, new Uint8Array(pickedColors));
}
private highlightFeatureHandle(
layer: ILayer,
{ featureId }: Partial<IInteractionTarget>,
) {
const pickedColors = encodePickingColor(featureId as number);
this.highlightPickedFeature(layer, new Uint8Array(pickedColors));
}
}

View File

@ -111,6 +111,7 @@ export interface ILayer {
setCurrentPickId(id: number | null): void;
getCurrentPickId(): number | null;
prepareBuildModel(): void;
renderModels(): void;
buildModels(): void;
buildLayerModel(
options: ILayerModelInitializationOptions &
@ -263,6 +264,7 @@ export interface ILayerConfig {
*/
export interface ILayerService {
clock: Clock;
alreadyInRendering: boolean;
add(layer: ILayer): void;
initLayers(): void;
startAnimate(): void;

View File

@ -10,6 +10,8 @@ import { ILayerModel, ILayerService } from './ILayerService';
export default class LayerService implements ILayerService {
public clock = new Clock();
public alreadyInRendering: boolean = false;
private layers: ILayer[] = [];
private layerRenderID: number;
@ -18,8 +20,6 @@ export default class LayerService implements ILayerService {
private animateInstanceCount: number = 0;
private alreadyInRendering: boolean = false;
@inject(TYPES.IRendererService)
private readonly renderService: IRendererService;

View File

@ -3,7 +3,7 @@ import Probe, { Log } from 'probe.gl';
import { ILogService } from './ILogService';
const Logger = new Log({ id: 'L7' }).enable(true);
// // 只输出 debug 级别以上的日志信息
Logger.priority = 2;
Logger.priority = 5;
@injectable()
export default class LogService implements ILogService {

View File

@ -38,6 +38,10 @@ export default class MultiPassRenderer implements IMultiPassRenderer {
private layer: ILayer;
private renderFlag: boolean;
private width: number = 0;
private height: number = 0;
public setLayer(layer: ILayer) {
this.layer = layer;
}
@ -58,11 +62,16 @@ export default class MultiPassRenderer implements IMultiPassRenderer {
for (const pass of this.passes) {
await pass.render(this.layer);
}
await this.postProcessor.render(this.layer);
this.layer.renderModels();
// await this.postProcessor.render(this.layer);
}
public resize(width: number, height: number) {
this.postProcessor.resize(width, height);
if (this.width !== width || this.height !== height) {
this.postProcessor.resize(width, height);
this.width = width;
this.height = height;
}
}
public add<T>(pass: IPass<T>, config?: Partial<T>) {

View File

@ -60,7 +60,6 @@ export default class PixelPickingPass<
getViewportSize,
} = this.rendererService;
const { width, height } = getViewportSize();
// 创建 picking framebuffer后续实时 resize
this.pickingFBO = createFramebuffer({
color: createTexture2D({

View File

@ -14,4 +14,9 @@ export interface ISceneService {
destroy(): void;
}
// scene 事件
export const SceneEventList = ['loaded', 'maploaded', 'resize', 'destroy'];
export const SceneEventList: string[] = [
'loaded',
'maploaded',
'resize',
'destroy',
];

View File

@ -14,6 +14,7 @@ import { IPopupService } from '../component/IPopupService';
import { IGlobalConfigService, ISceneConfig } from '../config/IConfigService';
import { ICoordinateSystemService } from '../coordinate/ICoordinateSystemService';
import { IInteractionService } from '../interaction/IInteractionService';
import { IPickingService } from '../interaction/IPickingService';
import { ILayer, ILayerService } from '../layer/ILayerService';
import { ILogService } from '../log/ILogService';
import { IMapCamera, IMapService } from '../map/IMapService';
@ -64,6 +65,9 @@ export default class Scene extends EventEmitter implements ISceneService {
@inject(TYPES.IInteractionService)
private readonly interactionService: IInteractionService;
@inject(TYPES.IPickingService)
private readonly pickingService: IPickingService;
@inject(TYPES.IShaderModuleService)
private readonly shaderModuleService: IShaderModuleService;
@ -150,6 +154,7 @@ export default class Scene extends EventEmitter implements ISceneService {
this.popupService.initPopup();
// 地图初始化之后 才能初始化 container 上的交互
this.interactionService.init();
this.logger.debug(`map ${this.id} loaded`);
});
@ -174,6 +179,7 @@ export default class Scene extends EventEmitter implements ISceneService {
} else {
this.logger.error('容器 id 不存在');
}
this.pickingService.init();
this.logger.debug(`scene ${this.id} renderer loaded`);
});

View File

@ -17,6 +17,7 @@ const TYPES = {
IIconService: Symbol.for('IIconService'),
IFontService: Symbol.for('IFontService'),
IInteractionService: Symbol.for('IInteractionService'),
IPickingService: Symbol.for('IPickingService'),
IControlService: Symbol.for('IControlService'),
IStyleAttributeService: Symbol.for('IStyleAttributeService'),
ILayer: Symbol.for('ILayer'),

View File

@ -170,6 +170,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
private aniamateStatus: boolean = false;
// private pickingPassRender: IPass<'pixelPicking'>;
constructor(config: Partial<ILayerConfig & ChildLayerStyleOptions> = {}) {
super();
this.name = config.name || this.id;
@ -228,7 +230,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
// 设置配置项
const sceneId = this.container.get<string>(TYPES.SceneID);
// 初始化图层配置项
this.configService.setLayerConfig(sceneId, this.id, {});
const { enableMultiPassRenderer = false } = this.rawConfig;
this.configService.setLayerConfig(sceneId, this.id, {
enableMultiPassRenderer,
});
// 全局容器服务
@ -297,6 +302,9 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
// 触发 init 生命周期插件
this.hooks.init.call();
// this.pickingPassRender = this.normalPassFactory('pixelPicking');
// this.pickingPassRender.init(this);
this.hooks.afterInit.call();
// 触发初始化完成事件;
@ -465,11 +473,21 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
return this;
}
public render(): ILayer {
if (this.multiPassRenderer && this.multiPassRenderer.getRenderFlag()) {
this.multiPassRenderer.render();
} else {
this.renderModels();
}
// if (
// this.needPick() &&
// this.multiPassRenderer &&
// this.multiPassRenderer.getRenderFlag()
// ) {
// this.multiPassRenderer.render();
// } else if (this.needPick() && this.multiPassRenderer) {
// this.renderModels();
// } else {
// this.renderModels();
// }
this.renderModels();
// this.multiPassRenderer.render();
// this.renderModels();
return this;
}
@ -806,11 +824,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
throw new Error('Method not implemented.');
}
protected getConfigSchema() {
throw new Error('Method not implemented.');
}
protected renderModels() {
public renderModels() {
if (this.layerModelNeedUpdate) {
this.models = this.layerModel.buildModels();
this.hooks.beforeRender.call();
@ -824,6 +838,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
return this;
}
protected getConfigSchema() {
throw new Error('Method not implemented.');
}
protected getModelType(): unknown {
throw new Error('Method not implemented.');
}

View File

@ -12,19 +12,7 @@ export default class HeatMapLayer extends BaseLayer<IHeatMapLayerStyleOptions> {
this.layerModel = new HeatMapModels[shape](this);
this.models = this.layerModel.buildModels();
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
public renderModels() {
const shape = this.getModelType();
if (shape === 'heatmap') {
// if (this.layerModelNeedUpdate) {
@ -49,6 +37,18 @@ export default class HeatMapLayer extends BaseLayer<IHeatMapLayerStyleOptions> {
);
return this;
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected getModelType(): HeatMapModelType {
const shapeAttribute = this.styleAttributeService.getLayerStyleAttribute(
'shape',

View File

@ -76,11 +76,11 @@ export default class MultiPassRendererPlugin implements ILayerPlugin {
});
layer.hooks.beforeRender.tap('MultiPassRendererPlugin', () => {
if (this.enabled) {
// 渲染前根据 viewport 调整 FBO size
const { width, height } = rendererService.getViewportSize();
layer.multiPassRenderer.resize(width, height);
}
// if (this.enabled) {
// // 渲染前根据 viewport 调整 FBO size
// const { width, height } = rendererService.getViewportSize();
// layer.multiPassRenderer.resize(width, height);
// }
});
}
@ -103,26 +103,26 @@ export default class MultiPassRendererPlugin implements ILayerPlugin {
}
// use TAA pass if enabled instead of render pass
if (enableTAA) {
multiPassRenderer.add(normalPassFactory('taa'));
} else {
// render all layers in this pass
multiPassRenderer.add(normalPassFactory('render'));
}
// if (enableTAA) {
// multiPassRenderer.add(normalPassFactory('taa'));
// } else {
// // render all layers in this pass
// multiPassRenderer.add(normalPassFactory('render'));
// }
// post processing
normalizePasses(passes).forEach(
(pass: [string, { [key: string]: unknown }]) => {
const [passName, initializationOptions] = pass;
multiPassRenderer.add(
postProcessingPassFactory(passName),
initializationOptions,
);
},
);
// normalizePasses(passes).forEach(
// (pass: [string, { [key: string]: unknown }]) => {
// const [passName, initializationOptions] = pass;
// multiPassRenderer.add(
// postProcessingPassFactory(passName),
// initializationOptions,
// );
// },
// );
// 末尾为固定的 CopyPass
multiPassRenderer.add(postProcessingPassFactory('copy'));
// multiPassRenderer.add(postProcessingPassFactory('copy'));
return multiPassRenderer;
}

View File

@ -18,6 +18,7 @@ interface IPointLayerStyleOptions {
opacity: number;
strokeWidth: number;
stroke: string;
strokeOpacity: number;
}
export default class FillModel extends BaseModel {
public getUninforms(): IModelUniform {
@ -25,11 +26,13 @@ export default class FillModel extends BaseModel {
opacity = 1,
stroke = 'rgb(0,0,0,0)',
strokeWidth = 1,
strokeOpacity = 1,
} = this.layer.getLayerConfig() as IPointLayerStyleOptions;
return {
u_opacity: opacity,
u_stroke_width: strokeWidth,
u_stroke_color: rgb2arr(stroke),
u_stroke_opacity: strokeOpacity,
};
}
public getAnimateUniforms(): IModelUniform {

View File

@ -57,19 +57,7 @@ export default class RasterLayer extends BaseLayer<IRasterLayerStyleOptions> {
});
this.models = [this.buildRasterModel()];
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
public renderModels() {
const { opacity, heightRatio = 10 } = this.getLayerConfig();
const parserDataItem = this.getSource().data.dataArray[0];
const { coordinates, width, height, min, max } = parserDataItem;
@ -91,6 +79,18 @@ export default class RasterLayer extends BaseLayer<IRasterLayerStyleOptions> {
return this;
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
private buildRasterModel() {
const source = this.getSource();
const sourceFeature = source.data.dataArray[0];

View File

@ -57,20 +57,7 @@ export default class Raster2dLayer extends BaseLayer<IRasterLayerStyleOptions> {
}),
];
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
protected renderModels() {
public renderModels() {
const { opacity } = this.getLayerConfig();
const parserDataItem = this.getSource().data.dataArray[0];
const { min, max } = parserDataItem;
@ -91,6 +78,18 @@ export default class Raster2dLayer extends BaseLayer<IRasterLayerStyleOptions> {
return this;
}
protected getConfigSchema() {
return {
properties: {
opacity: {
type: 'number',
minimum: 0,
maximum: 1,
},
},
};
}
private registerBuiltinAttributes() {
// point layer size;
this.styleAttributeService.registerStyleAttribute({

View File

@ -240,6 +240,7 @@ export default class AMapService
if (mapInstance) {
this.map = mapInstance as AMap.Map & IAMapInstance;
this.$mapContainer = this.map.getContainer();
this.removeLogoControl();
setTimeout(() => {
this.map.on('camerachange', this.handleCameraChanged);
resolve();
@ -255,6 +256,7 @@ export default class AMapService
viewMode: '3D',
...rest,
});
this.removeLogoControl();
// 监听地图相机事件
map.on('camerachange', this.handleCameraChanged);
// @ts-ignore
@ -390,4 +392,12 @@ export default class AMapService
document.head.appendChild(script);
});
}
private removeLogoControl(): void {
// @ts-ignore
const logo = document.getElementsByClassName('amap-logo');
if (logo) {
logo[0].setAttribute('style', 'display: none !important');
}
}
}

View File

@ -0,0 +1,51 @@
import { IMapConfig, Scene } from '@antv/l7';
// tslint:disable-next-line:no-submodule-imports
import GaodeMap from '@antv/l7-maps/lib/amap';
import React, { createElement, createRef, useEffect, useState } from 'react';
import SceneContext from './SceneContext';
interface IMapSceneConig {
style?: React.CSSProperties;
className?: string;
map: IMapConfig;
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>;
}
const AMapScene = React.memo((props: IMapSceneConig) => {
const { style, className, map } = props;
const container = createRef();
const [scene, setScene] = useState();
useEffect(() => {
const sceneInstance = new Scene({
id: container.current as HTMLDivElement,
map: new GaodeMap(map),
});
sceneInstance.on('loaded', () => {
setScene(sceneInstance);
});
return () => {
sceneInstance.destroy();
};
}, []);
useEffect(() => {
if (!scene) {
return;
}
scene.setMapStyle(map.style);
}, [map.style]);
return (
<SceneContext.Provider value={scene}>
{createElement(
'div',
{
ref: container,
style,
className,
},
scene && props.children,
)}
</SceneContext.Provider>
);
});
export default AMapScene;

View File

@ -0,0 +1,39 @@
import { IControl, Logo, PositionType, Scale, Scene, Zoom } from '@antv/l7';
import React, { useEffect, useState } from 'react';
import { useSceneValue } from './SceneContext';
interface IControlProps {
type: 'scale' | 'zoom' | 'logo';
position: PositionType;
[key: string]: any;
}
export default React.memo(function MapControl(props: IControlProps) {
const scene = (useSceneValue() as unknown) as Scene;
const [, setControl] = useState();
const { type, position } = props;
useEffect(() => {
let ctr: IControl;
switch (type) {
case 'scale':
ctr = new Scale({
position,
});
break;
case 'zoom':
ctr = new Zoom({
position,
});
break;
case 'logo':
ctr = new Logo({
position,
});
}
setControl(ctr);
scene.addControl(ctr);
return () => {
scene.removeControl(ctr);
};
}, [type, position]);
return null;
});

View File

@ -12,8 +12,11 @@ export default React.memo(function Chart(props: ISourceProps) {
const { data, ...sourceOption } = source;
useEffect(() => {
// @ts-ignore
layer.source(data, sourceOption);
}, []);
if (!layer.inited) {
layer.source(data, sourceOption);
} else {
layer.setData(data, sourceOption);
}
}, [data, sourceOption]);
return null;
});

View File

@ -0,0 +1,51 @@
import { IMapConfig, Scene } from '@antv/l7';
// tslint:disable-next-line:no-submodule-imports
import Mapbox from '@antv/l7-maps/lib/mapbox';
import React, { createElement, createRef, useEffect, useState } from 'react';
import SceneContext from './SceneContext';
interface IMapSceneConig {
style?: React.CSSProperties;
className?: string;
map: IMapConfig;
children?: JSX.Element | JSX.Element[] | Array<JSX.Element | undefined>;
}
const MapboxScene = React.memo((props: IMapSceneConig) => {
const { style, className, map } = props;
const container = createRef();
const [scene, setScene] = useState();
useEffect(() => {
const sceneInstance = new Scene({
id: container.current as HTMLDivElement,
map: new Mapbox(map),
});
sceneInstance.on('loaded', () => {
setScene(sceneInstance);
});
return () => {
sceneInstance.destroy();
};
}, []);
useEffect(() => {
if (!scene) {
return;
}
scene.setMapStyle(map.style);
}, [map.style]);
return (
<SceneContext.Provider value={scene}>
{createElement(
'div',
{
ref: container,
style,
className,
},
scene && props.children,
)}
</SceneContext.Provider>
);
});
export default MapboxScene;

View File

@ -23,6 +23,12 @@ const MapScene = React.memo((props: IMapSceneConig) => {
sceneInstance.destroy();
};
}, []);
useEffect(() => {
if (!scene) {
return;
}
scene.setMapStyle(style);
}, [style]);
return (
<SceneContext.Provider value={scene}>

View File

@ -1,3 +1,6 @@
export * from './component/SceneContext';
export { default as Scene } from './component/Scene';
export { default as AMapScene } from './component/AMapScene';
export { default as MapboxScene } from './component/MapboxScene';
export * from './component/Layer';
export { default as Control } from './component/Control';

View File

@ -62,7 +62,7 @@ export default class ScaleComponent extends React.Component {
})
.size('point_count', [5, 10, 15, 20, 25])
.animate(false)
.select(true)
.active(true)
.color('yellow')
.style({
opacity: 0.5,

View File

@ -35,22 +35,24 @@ export default class PointImage extends React.Component {
'02',
'https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*o16fSIvcKdUAAAAAAAAAAABkARQnAQ',
);
const imageLayer = new PointLayer({})
.source(await response.json(), {
parser: {
type: 'json',
x: 'longitude',
y: 'latitude',
},
})
.shape('name', ['00', '01', '02'])
.active(true)
.size(30);
scene.addLayer(imageLayer);
imageLayer.on('click', (e) => {
console.log(e);
});
let i = 0;
const data = await response.json();
while (i < 50) {
const imageLayer = new PointLayer()
.source(data, {
parser: {
type: 'json',
x: 'longitude',
y: 'latitude',
},
})
.shape('circle')
.color('red')
.active(false)
.size(20);
scene.addLayer(imageLayer);
i++;
}
}
public render() {

View File

View File

@ -22411,8 +22411,8 @@ table@^5.0.0, table@^5.2.3:
tapable@^1.0.0, tapable@^1.1.3:
version "1.1.3"
resolved "https://registry.npm.taobao.org/tapable/download/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha1-ofzMBrWNth/XpF2i2kT186Pme6I=
resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
tapable@^2.0.0-beta.8:
version "2.0.0-beta.9"