mirror of https://gitee.com/antv-l7/antv-l7
Merge pull request #617 from antvis/feat_meta_viewport
Feat meta viewport
This commit is contained in:
commit
935f5ce1a7
|
@ -80,4 +80,5 @@ L7 是由蚂蚁金服 AntV 数据可视化团队推出的基于 WebGL 的开源
|
|||
|
||||
L7 相关技术问题,需求反馈,我们会及时响应
|
||||
|
||||
![地理空间可视化L7支持群](https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*ePmsRbK4lZgAAAAAAAAAAABkARQnAQ)
|
||||
群号:30460926
|
||||
![地理空间可视化L7支持群](https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*W7IFQr6QSZYAAAAAAAAAAAAAARQnAQ)
|
||||
|
|
|
@ -46,6 +46,7 @@ export * from './services/component/IMarkerService';
|
|||
export * from './services/component/IPopupService';
|
||||
export * from './services/log/ILogService';
|
||||
export * from './services/interaction/IInteractionService';
|
||||
export * from './services/interaction/IPickingService';
|
||||
|
||||
/** 全部渲染服务接口 */
|
||||
export * from './services/renderer/IAttribute';
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
import { ILayer } from '../layer/ILayerService';
|
||||
export interface IPickingService {
|
||||
init(id: string): void;
|
||||
pickBox(layer: ILayer, box: [number, number, number, number]): any[];
|
||||
boxPickLayer(
|
||||
layer: ILayer,
|
||||
box: [number, number, number, number],
|
||||
cb: (...args: any[]) => void,
|
||||
): Promise<any>;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { decodePickingColor, encodePickingColor } from '@antv/l7-utils';
|
||||
import { decodePickingColor, DOM, encodePickingColor } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import {
|
||||
IMapService,
|
||||
|
@ -52,8 +52,8 @@ export default class PickingService implements IPickingService {
|
|||
width,
|
||||
height,
|
||||
} = (getContainer() as HTMLElement).getBoundingClientRect();
|
||||
width *= window.devicePixelRatio;
|
||||
height *= window.devicePixelRatio;
|
||||
width *= DOM.DPR;
|
||||
height *= DOM.DPR;
|
||||
this.pickBufferScale =
|
||||
this.configService.getSceneConfig(id).pickBufferScale || 1;
|
||||
// 创建 picking framebuffer,后续实时 resize
|
||||
|
@ -72,6 +72,75 @@ export default class PickingService implements IPickingService {
|
|||
this.pickingAllLayer.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
public async boxPickLayer(
|
||||
layer: ILayer,
|
||||
box: [number, number, number, number],
|
||||
cb: (...args: any[]) => void
|
||||
): Promise<any> {
|
||||
const { useFramebuffer, clear, getContainer } = this.rendererService;
|
||||
this.resizePickingFBO();
|
||||
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();
|
||||
const features = this.pickBox(layer, box);
|
||||
cb(features);
|
||||
});
|
||||
}
|
||||
|
||||
public pickBox(layer: ILayer, box: [number, number, number, number]): any[] {
|
||||
const [xMin, yMin, xMax, yMax] = box.map((v) => {
|
||||
const tmpV = v < 0 ? 0 : v;
|
||||
return Math.floor((tmpV * DOM.DPR) / this.pickBufferScale);
|
||||
});
|
||||
const { getViewportSize, readPixels, getContainer } = this.rendererService;
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
} = (getContainer() as HTMLElement).getBoundingClientRect();
|
||||
width *= DOM.DPR;
|
||||
height *= DOM.DPR;
|
||||
if (
|
||||
xMin > ((width - 1) * DOM.DPR) / this.pickBufferScale ||
|
||||
xMax < 0 ||
|
||||
yMin > ((height - 1) * DOM.DPR) / this.pickBufferScale ||
|
||||
yMax < 0
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
let pickedColors: Uint8Array | undefined;
|
||||
const w = Math.min(width / this.pickBufferScale, xMax) - xMin;
|
||||
const h = Math.min(height / this.pickBufferScale, yMax) - yMin;
|
||||
pickedColors = readPixels({
|
||||
x: xMin,
|
||||
// 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴
|
||||
y: Math.floor(height / this.pickBufferScale - (yMax + 1)),
|
||||
width: w,
|
||||
height: h,
|
||||
data: new Uint8Array(w * h * 4),
|
||||
framebuffer: this.pickingFBO,
|
||||
});
|
||||
|
||||
const features = [];
|
||||
const featuresIdMap: { [key: string]: boolean } = {};
|
||||
for (let i = 0; i < pickedColors.length / 4; i = i + 1) {
|
||||
const color = pickedColors.slice(i * 4, i * 4 + 4);
|
||||
const pickedFeatureIdx = decodePickingColor(color);
|
||||
if (pickedFeatureIdx !== -1 && !featuresIdMap[pickedFeatureIdx]) {
|
||||
const rawFeature = layer.getSource().getFeatureById(pickedFeatureIdx);
|
||||
features.push(rawFeature);
|
||||
featuresIdMap[pickedFeatureIdx] = true;
|
||||
}
|
||||
}
|
||||
return features;
|
||||
}
|
||||
private async pickingAllLayer(target: IInteractionTarget) {
|
||||
if (this.alreadyInPicking || this.layerService.alreadyInRendering) {
|
||||
return;
|
||||
|
@ -81,19 +150,15 @@ export default class PickingService implements IPickingService {
|
|||
this.layerService.renderLayers();
|
||||
this.alreadyInPicking = false;
|
||||
}
|
||||
private async pickingLayers(target: IInteractionTarget) {
|
||||
const {
|
||||
getViewportSize,
|
||||
useFramebuffer,
|
||||
clear,
|
||||
getContainer,
|
||||
} = this.rendererService;
|
||||
|
||||
private resizePickingFBO() {
|
||||
const { getContainer } = this.rendererService;
|
||||
let {
|
||||
width,
|
||||
height,
|
||||
} = (getContainer() as HTMLElement).getBoundingClientRect();
|
||||
width *= window.devicePixelRatio;
|
||||
height *= window.devicePixelRatio;
|
||||
width *= DOM.DPR;
|
||||
height *= DOM.DPR;
|
||||
if (this.width !== width || this.height !== height) {
|
||||
this.pickingFBO.resize({
|
||||
width: Math.round(width / this.pickBufferScale),
|
||||
|
@ -102,6 +167,16 @@ export default class PickingService implements IPickingService {
|
|||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
}
|
||||
private async pickingLayers(target: IInteractionTarget) {
|
||||
const {
|
||||
getViewportSize,
|
||||
useFramebuffer,
|
||||
clear,
|
||||
getContainer,
|
||||
} = this.rendererService;
|
||||
|
||||
this.resizePickingFBO();
|
||||
|
||||
useFramebuffer(this.pickingFBO, () => {
|
||||
const layers = this.layerService.getLayers();
|
||||
|
@ -123,6 +198,7 @@ export default class PickingService implements IPickingService {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
private pickFromPickingFBO = (
|
||||
layer: ILayer,
|
||||
{ x, y, lngLat, type, target }: IInteractionTarget,
|
||||
|
@ -133,16 +209,16 @@ export default class PickingService implements IPickingService {
|
|||
width,
|
||||
height,
|
||||
} = (getContainer() as HTMLElement).getBoundingClientRect();
|
||||
width *= window.devicePixelRatio;
|
||||
height *= window.devicePixelRatio;
|
||||
width *= DOM.DPR;
|
||||
height *= DOM.DPR;
|
||||
const { enableHighlight, enableSelect } = layer.getLayerConfig();
|
||||
|
||||
const xInDevicePixel = x * window.devicePixelRatio;
|
||||
const yInDevicePixel = y * window.devicePixelRatio;
|
||||
const xInDevicePixel = x * DOM.DPR;
|
||||
const yInDevicePixel = y * DOM.DPR;
|
||||
if (
|
||||
xInDevicePixel > width - 1 * window.devicePixelRatio ||
|
||||
xInDevicePixel > width - 1 * DOM.DPR ||
|
||||
xInDevicePixel < 0 ||
|
||||
yInDevicePixel > height - 1 * window.devicePixelRatio ||
|
||||
yInDevicePixel > height - 1 * DOM.DPR ||
|
||||
yInDevicePixel < 0
|
||||
) {
|
||||
return false;
|
||||
|
@ -151,9 +227,7 @@ export default class PickingService implements IPickingService {
|
|||
pickedColors = readPixels({
|
||||
x: Math.floor(xInDevicePixel / this.pickBufferScale),
|
||||
// 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴
|
||||
y: Math.floor(
|
||||
(height - (y + 1) * window.devicePixelRatio) / this.pickBufferScale,
|
||||
),
|
||||
y: Math.floor((height - (y + 1) * DOM.DPR) / this.pickBufferScale),
|
||||
width: 1,
|
||||
height: 1,
|
||||
data: new Uint8Array(1 * 1 * 4),
|
||||
|
|
|
@ -192,6 +192,10 @@ export interface ILayer {
|
|||
* 直接调用拾取方法,在非鼠标交互场景中使用
|
||||
*/
|
||||
pick(query: { x: number; y: number }): void;
|
||||
boxSelect(
|
||||
box: [number, number, number, number],
|
||||
cb: (...args: any[]) => void,
|
||||
): void;
|
||||
|
||||
updateLayerConfig(configToUpdate: Partial<ILayerConfig | unknown>): void;
|
||||
setAnimateStartTime(): void;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { decodePickingColor, encodePickingColor } from '@antv/l7-utils';
|
||||
import { decodePickingColor, DOM, encodePickingColor } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { TYPES } from '../../../types';
|
||||
import {
|
||||
|
@ -141,8 +141,8 @@ export default class PixelPickingPass<
|
|||
const { width, height } = getViewportSize();
|
||||
const { enableHighlight, enableSelect } = this.layer.getLayerConfig();
|
||||
|
||||
const xInDevicePixel = x * window.devicePixelRatio;
|
||||
const yInDevicePixel = y * window.devicePixelRatio;
|
||||
const xInDevicePixel = x * DOM.DPR;
|
||||
const yInDevicePixel = y * DOM.DPR;
|
||||
if (
|
||||
xInDevicePixel > width ||
|
||||
xInDevicePixel < 0 ||
|
||||
|
@ -157,7 +157,7 @@ export default class PixelPickingPass<
|
|||
pickedColors = readPixels({
|
||||
x: Math.round(xInDevicePixel),
|
||||
// 视口坐标系原点在左上,而 WebGL 在左下,需要翻转 Y 轴
|
||||
y: Math.round(height - (y + 1) * window.devicePixelRatio),
|
||||
y: Math.round(height - (y + 1) * DOM.DPR),
|
||||
width: 1,
|
||||
height: 1,
|
||||
data: new Uint8Array(1 * 1 * 4),
|
||||
|
|
|
@ -318,7 +318,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
}
|
||||
};
|
||||
private initContainer() {
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
const pixelRatio = DOM.DPR;
|
||||
const w = this.$container?.clientWidth || 400;
|
||||
const h = this.$container?.clientHeight || 300;
|
||||
const canvas = this.canvas;
|
||||
|
@ -337,7 +337,7 @@ export default class Scene extends EventEmitter implements ISceneService {
|
|||
}
|
||||
|
||||
private setCanvas() {
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
const pixelRatio = DOM.DPR;
|
||||
const w = this.$container?.clientWidth || 400;
|
||||
const h = this.$container?.clientHeight || 300;
|
||||
const canvas = this.canvas;
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
IModelInitializationOptions,
|
||||
IMultiPassRenderer,
|
||||
IPass,
|
||||
IPickingService,
|
||||
IPostProcessingPass,
|
||||
IRendererService,
|
||||
IScale,
|
||||
|
@ -129,6 +130,8 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
protected fontService: IFontService;
|
||||
|
||||
protected pickingService: IPickingService;
|
||||
|
||||
protected rendererService: IRendererService;
|
||||
|
||||
protected layerService: ILayerService;
|
||||
|
@ -258,6 +261,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
this.interactionService = this.container.get<IInteractionService>(
|
||||
TYPES.IInteractionService,
|
||||
);
|
||||
|
||||
this.pickingService = this.container.get<IPickingService>(
|
||||
TYPES.IPickingService,
|
||||
);
|
||||
this.mapService = this.container.get<IMapService>(TYPES.IMapService);
|
||||
this.cameraService = this.container.get<ICameraService>(
|
||||
TYPES.ICameraService,
|
||||
|
@ -815,6 +822,13 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
this.interactionService.triggerHover({ x, y });
|
||||
}
|
||||
|
||||
public boxSelect(
|
||||
box: [number, number, number, number],
|
||||
cb: (...args: any[]) => void,
|
||||
) {
|
||||
this.pickingService.boxPickLayer(this, box, cb);
|
||||
}
|
||||
|
||||
public buildLayerModel(
|
||||
options: ILayerModelInitializationOptions &
|
||||
Partial<IModelInitializationOptions>,
|
||||
|
|
|
@ -95,6 +95,7 @@ export function polygonTriangulation(feature: IEncodeFeature) {
|
|||
const { coordinates } = feature;
|
||||
const flattengeo = earcut.flatten(coordinates as number[][][]);
|
||||
const { vertices, dimensions, holes } = flattengeo;
|
||||
|
||||
return {
|
||||
indices: earcut(vertices, holes, dimensions),
|
||||
vertices,
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
IRendererService,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { inject, injectable } from 'inversify';
|
||||
|
||||
/**
|
||||
|
@ -53,7 +54,7 @@ export default class ShaderUniformPlugin implements ILayerPlugin {
|
|||
[CoordinateUniform.PixelsPerMeter]: this.coordinateSystemService.getPixelsPerMeter(),
|
||||
// 其他参数,例如视口大小、DPR 等
|
||||
u_ViewportSize: [width, height],
|
||||
u_DevicePixelRatio: window.devicePixelRatio,
|
||||
u_DevicePixelRatio: DOM.DPR,
|
||||
u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
|
||||
u_PickingBuffer: layer.getLayerConfig().pickingBuffer || 0,
|
||||
}),
|
||||
|
|
|
@ -359,7 +359,7 @@ export class Map extends Camera {
|
|||
}
|
||||
|
||||
private resizeCanvas(width: number, height: number) {
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const pixelRatio = DOM.DPR || 1;
|
||||
this.canvas.width = pixelRatio * width;
|
||||
this.canvas.height = pixelRatio * height;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
Properties,
|
||||
} from '@turf/helpers';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
import { cloneDeep, isFunction, isString } from 'lodash';
|
||||
import { cloneDeep, isFunction, isString, merge } from 'lodash';
|
||||
// @ts-ignore
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
import Supercluster from 'supercluster/dist/supercluster';
|
||||
|
@ -37,6 +37,7 @@ export default class Source extends EventEmitter {
|
|||
public hooks = {
|
||||
init: new SyncHook(),
|
||||
};
|
||||
|
||||
public parser: IParserCfg = { type: 'geojson' };
|
||||
public transforms: ITransform[] = [];
|
||||
public cluster: boolean = false;
|
||||
|
@ -51,6 +52,7 @@ export default class Source extends EventEmitter {
|
|||
// 原始数据
|
||||
private originData: any;
|
||||
private rawData: any;
|
||||
private cfg: any = {};
|
||||
|
||||
private clusterIndex: Supercluster;
|
||||
|
||||
|
@ -58,6 +60,7 @@ export default class Source extends EventEmitter {
|
|||
super();
|
||||
// this.rawData = cloneDeep(data);
|
||||
this.originData = data;
|
||||
|
||||
this.initCfg(cfg);
|
||||
|
||||
this.hooks.init.tap('parser', () => {
|
||||
|
@ -75,7 +78,7 @@ export default class Source extends EventEmitter {
|
|||
public setData(data: any, options?: ISourceCFG) {
|
||||
this.rawData = data;
|
||||
this.originData = data;
|
||||
this.initCfg(options);
|
||||
this.initCfg(this.cfg);
|
||||
this.init();
|
||||
this.emit('update');
|
||||
}
|
||||
|
@ -160,7 +163,9 @@ export default class Source extends EventEmitter {
|
|||
this.data = null;
|
||||
}
|
||||
|
||||
private initCfg(cfg?: ISourceCFG) {
|
||||
private initCfg(option?: ISourceCFG) {
|
||||
this.cfg = merge(this.cfg, option);
|
||||
const cfg = this.cfg;
|
||||
if (cfg) {
|
||||
if (cfg.parser) {
|
||||
this.parser = cfg.parser;
|
||||
|
|
|
@ -152,3 +152,15 @@ export function printCanvas(canvas: HTMLCanvasElement) {
|
|||
// tslint:disable-next-line:no-console
|
||||
console.log('%c\n', css.join(''));
|
||||
}
|
||||
|
||||
export function getViewPortScale() {
|
||||
const meta = document.querySelector('meta[name="viewport"]');
|
||||
const contentItems = (meta as any).content?.split(',');
|
||||
const scale = contentItems.find((item: string) => {
|
||||
const [key, value] = item.split('=');
|
||||
return key === 'initial-scale';
|
||||
});
|
||||
return scale ? scale.split('=')[1] * 1 : 1;
|
||||
}
|
||||
|
||||
export const DPR = getViewPortScale() < 1 ? 1 : window.devicePixelRatio;
|
||||
|
|
|
@ -14,7 +14,7 @@ export default class Point3D extends React.Component {
|
|||
public async componentDidMount() {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
pickBufferScale: 3.0,
|
||||
pickBufferScale: 1.0,
|
||||
map: new GaodeMap({
|
||||
style: 'light',
|
||||
center: [-121.24357, 37.58264],
|
||||
|
@ -59,23 +59,35 @@ export default class Point3D extends React.Component {
|
|||
stroke: '#fff',
|
||||
});
|
||||
|
||||
scene.addLayer(pointLayer);
|
||||
this.scene = scene;
|
||||
setTimeout(() => {
|
||||
console.log('updatedata');
|
||||
pointLayer.setData(
|
||||
{
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
const textLayer = new PointLayer({})
|
||||
.source(data, {
|
||||
parser: {
|
||||
type: 'csv',
|
||||
x: 'Longitude',
|
||||
y: 'Latitude',
|
||||
},
|
||||
{
|
||||
parser: {
|
||||
type: 'geojson',
|
||||
},
|
||||
})
|
||||
.shape('EventID', 'text')
|
||||
.size(8)
|
||||
|
||||
.color('red')
|
||||
.style({
|
||||
opacity: 1,
|
||||
strokeWidth: 0,
|
||||
stroke: '#fff',
|
||||
});
|
||||
|
||||
scene.addLayer(pointLayer);
|
||||
scene.addLayer(textLayer);
|
||||
pointLayer.on('click', (e) => {
|
||||
const res = pointLayer.boxSelect(
|
||||
[e.x - 10, e.y - 10, e.x + 10, e.y + 10],
|
||||
(fe) => {
|
||||
console.log(fe);
|
||||
},
|
||||
);
|
||||
console.log(pointLayer);
|
||||
}, 3000);
|
||||
});
|
||||
this.scene = scene;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@
|
|||
"no-implicit-dependencies": false
|
||||
},
|
||||
"linterOptions": {
|
||||
"exclude": ["**/*.d.ts", "**/data/*.ts", "**/*.{test,story}.ts{,x}", "node_modules/**/*.d.ts"]
|
||||
"exclude": ["packages/l7/node_modules/**","**/*.d.ts", "**/data/*.ts", "**/*.{test,story}.ts{,x}", "node_modules/**/*.d.ts"]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue