Merge pull request #617 from antvis/feat_meta_viewport

Feat meta viewport
This commit is contained in:
@thinkinggis 2021-01-12 20:05:00 +08:00 committed by GitHub
commit 935f5ce1a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 181 additions and 28328 deletions

View File

@ -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)

View File

@ -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';

View File

@ -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>;
}

View File

@ -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),

View File

@ -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;

View File

@ -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),

View File

@ -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;

View File

@ -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>,

View File

@ -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,

View File

@ -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,
}),

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;
});
});
}

View File

@ -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"]
}
}

28279
yarn.lock

File diff suppressed because it is too large Load Diff