Merge pull request #136 from antvis/example

新增文本渲染支持
This commit is contained in:
@thinkinggis 2019-12-30 19:15:07 +08:00 committed by GitHub
commit a2d8894922
52 changed files with 6667 additions and 6919 deletions

View File

@ -1,8 +1,8 @@
import path from 'path';
import alias from '@rollup/plugin-alias';
import json from '@rollup/plugin-json';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'sharp';
import commonjs from '@rollup/plugin-commons';
import { terser } from 'rollup-plugin-terser';
import analyze from 'rollup-plugin-analyzer';
import babel from 'rollup-plugin-babel';

View File

@ -6,33 +6,33 @@
"demos": [
{
"filename": "column_dark.js",
"title": "",
"title": "3D柱图深色",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*7COqR4wCc6QAAAAAAAAAAABkARQnAQ"
},
{
"filename": "arcCircle.js",
"title": "",
"title": "弧线地图",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*6Qm_QY69sBMAAAAAAAAAAABkARQnAQ"
},
{
"filename": "bus_dark.js",
"title": "",
"title": "公交线路深色",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*j-P8RaJMEvAAAAAAAAAAAABkARQnAQ"
},
{
"filename": "light.js",
"title": "",
"title": "蜂窝图3D",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*SLcGSbvZoEwAAAAAAAAAAABkARQnAQ"
},
{
"filename": "point.js",
"title": "",
"title": "海量点",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*ypZCT6pqv84AAAAAAAAAAABkARQnAQ"
}
,
{
"filename": "normal.js",
"title": "",
"title": "亮度图",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*xr8BQouXGvoAAAAAAAAAAABkARQnAQ"
}
]

View File

@ -1,6 +1,4 @@
---
title: Gallery
title: Featured
order: 0
redirect_from:
- /en/examples
---

View File

@ -1,6 +1,4 @@
---
title: Gallery
title: 官方精品库
order: 0
redirect_from:
- /zh/examples
---

View File

@ -0,0 +1,7 @@
---
title: Gallery
order: -1
icon: other
redirect_from:
- /en/examples
---

View File

@ -0,0 +1,7 @@
---
title: 所有图表
order: -1
icon: other
redirect_from:
- /zh/examples
---

View File

@ -1,42 +0,0 @@
import { Scene, LineLayer } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
const scene = new Scene({
id: 'map',
map: new GaodeMap({
pitch: 0,
style: 'light',
center: [ 104.117492, 36.492696 ],
zoom: 3.89
})
});
fetch(
'https://gw.alipayobjects.com/os/basement_prod/9f6afbcd-3aec-4a26-bd4a-2276d3439e0d.json'
)
.then(res => res.json())
.then(data => {
const layer = new LineLayer({})
.source(data)
.scale('value', {
type: 'quantile'
})
.size('value', [ 0.5, 1, 1.5, 2 ])
.shape('line')
.color(
'value',
[
'#0A3663',
'#1558AC',
'#3771D9',
'#4D89E5',
'#64A5D3',
'#72BED6',
'#83CED6',
'#A6E1E0',
'#B8EFE2',
'#D7F9F0'
].reverse()
);
scene.addLayer(layer);
});

View File

@ -22,8 +22,8 @@
}, {
"filename": "scatter.js",
"title": "定点图",
"screenshot":"https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*LnlmQ7sFWigAAAAAAAAAAABkARQnAQ"
"title": "气泡图动画",
"screenshot":"https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*L-wETZt7WHwAAAAAAAAAAABkARQnAQ"
}

View File

@ -4,15 +4,15 @@ import { GaodeMap } from '@antv/l7-maps';
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'light',
center: [ -121.24357, 37.58264 ],
pitch: 0,
zoom: 6.45
style: 'dark',
center: [ 112, 23.69 ],
zoom: 2.5
})
});
fetch(
'https://gw.alipayobjects.com/os/basement_prod/6c4bb5f2-850b-419d-afc4-e46032fc9f94.csv'
'https://gw.alipayobjects.com/os/basement_prod/9078fd36-ce8d-4ee2-91bc-605db8315fdf.csv'
)
.then(res => res.text())
.then(data => {
@ -25,22 +25,12 @@ fetch(
}
})
.shape('circle')
.size(4)
.color('Magnitude', [
'#0A3663',
'#1558AC',
'#3771D9',
'#4D89E5',
'#64A5D3',
'#72BED6',
'#83CED6',
'#A6E1E0',
'#B8EFE2',
'#D7F9F0'
])
.active(true)
.animate(true)
.size(40)
.color('#ffa842')
.style({
opacity: 0.5,
strokeWidth: 0
opacity: 1
});
scene.addLayer(pointLayer);

View File

@ -11,7 +11,7 @@
},
{
"filename": "locate.js",
"title": "顶点符号",
"title": "精确符号",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*DgoWS7-XKdUAAAAAAAAAAABkARQnAQ"
},
{

View File

@ -25,6 +25,10 @@ function pointOnCircle(angle) {
}]
};
}
scene.on('loaded', () => {
// animateMarker(0);
});
const layer = new PointLayer()
.source(pointOnCircle(0))
.shape('circle')
@ -36,11 +40,7 @@ const layer = new PointLayer()
opacity: 1
});
scene.addLayer(layer);
function animateMarker(timestamp) {
layer.setData(pointOnCircle(timestamp / 1000));
requestAnimationFrame(animateMarker);
}
layer.on('inited', () => {
animateMarker(0);
});
// function animateMarker(timestamp) {
// layer.setData(pointOnCircle(timestamp / 1000));
// requestAnimationFrame(animateMarker);
// }

View File

@ -1,14 +1,56 @@
import { Scene } from '@antv/l7';
import { Scene, PointLayer } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
const map = new AMap.Map('container', {
resizeEnable: true, // 是否监控地图容器尺寸变化
zoom: 11, // 初始化地图层级
center: [ 116.397428, 39.90923 ] // 初始化地图中心点
});
new Scene({
id: 'map',
map: new GaodeMap({
mapInstance: map
})
});
window.onLoad = function() {
const map = new AMap.Map('map', {
viewMode: '3D',
pitch: 0,
mapStyle: 'amap://styles/darkblue',
center: [ 121.435159, 31.256971 ],
zoom: 14.89,
minZoom: 10
});
const scene = new Scene({
id: 'map',
map: new GaodeMap({
mapInstance: map
})
});
fetch(
'https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json'
)
.then(res => res.json())
.then(data => {
const pointLayer = new PointLayer()
.source(data, {
parser: {
type: 'json',
x: 'longitude',
y: 'latitude'
}
})
.shape('name', [
'circle',
'triangle',
'square',
'pentagon',
'hexagon',
'octogon',
'hexagram',
'rhombus',
'vesica'
])
.size('unit_price', [ 10, 25 ])
.color('name', [ '#5B8FF9', '#5CCEA1', '#5D7092', '#F6BD16', '#E86452' ])
.style({
opacity: 0.3,
strokeWidth: 2
});
scene.addLayer(pointLayer);
});
};
const url = 'https://webapi.amap.com/maps?v=1.4.15&key=15cd8a57710d40c9b7c0e3cc120f1200&callback=onLoad';
const jsapi = document.createElement('script');
jsapi.charset = 'utf-8';
jsapi.src = url;
document.head.appendChild(jsapi);

View File

@ -13,6 +13,11 @@
"filename": "mapbox.js",
"title": "MapBox底图",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*_SIYR50bbcoAAAAAAAAAAABkARQnAQ"
},
{
"filename": "amapInstance.js",
"title": "通过高德地图实例化",
"screenshot": "https://gw.alipayobjects.com/mdn/antv_site/afts/img/A*_SIYR50bbcoAAAAAAAAAAABkARQnAQ"
}
]
}

View File

@ -33,10 +33,8 @@ module.exports = {
title: {
zh: '图表演示',
en: 'Examples'
},
redirect: 'gallery/basic'
}
}
// target: '_blank',
],
docs: [
{
@ -141,8 +139,8 @@ module.exports = {
slug: 'gallery',
icon: 'gallery',
title: {
zh: 'Gallery',
en: 'Gallery'
zh: '官方精品库',
en: 'Featured'
}
},
{

View File

@ -23,12 +23,6 @@
"@commitlint/config-conventional": "^8.1.0",
"@rollup/plugin-alias": "^2.2.0",
"@rollup/plugin-json": "^4.0.0",
"@storybook/addon-actions": "^5.1.9",
"@storybook/addon-console": "^1.2.1",
"@storybook/addon-info": "^5.1.9",
"@storybook/addon-knobs": "^5.1.9",
"@storybook/addon-notes": "^5.1.9",
"@storybook/addon-storysource": "^5.1.11",
"@storybook/react": "^5.1.9",
"@types/dat.gui": "^0.7.1",
"@types/enzyme": "^3.1.14",
@ -94,8 +88,6 @@
"rollup": "^1.27.14",
"rollup-plugin-analyzer": "^3.2.2",
"rollup-plugin-babel": "^4.3.3",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-postcss": "^2.0.3",
"rollup-plugin-terser": "^5.1.2",
"rollup-pluginutils": "^2.8.2",
@ -173,6 +165,9 @@
"path": "cz-conventional-changelog"
}
},
"resolutions": {
"../core-js": "3"
},
"tnpm": {
"mode": "yarn"
},

View File

@ -26,15 +26,10 @@
"dependencies": {
"@antv/l7-core": "^2.0.0-beta.25",
"@antv/l7-utils": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"@turf/distance": "^6.0.1",
"core-js": "3",
"eventemitter3": "^4.0.0",
"inversify": "^5.0.1",
"inversify-inject-decorators": "^3.1.0",
"inversify-logging": "^0.2.1",
"load-styles": "^2.0.0",
"regenerator-runtime": "^0.13.3"
"load-styles": "^2.0.0"
},
"gitHead": "00d23ef70d9ec76eec26833fc50ac18fe584cf26",
"publishConfig": {

View File

@ -23,7 +23,6 @@
"license": "ISC",
"dependencies": {
"@antv/l7-utils": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"@mapbox/tiny-sdf": "^1.1.1",
"ajv": "^6.10.2",
"core-js": "3",

View File

@ -12,7 +12,7 @@ import {
} from './IFontService';
export const DEFAULT_CHAR_SET = getDefaultCharacterSet();
export const DEFAULT_FONT_FAMILY = 'sans-serif';
export const DEFAULT_FONT_WEIGHT = 'normal';
export const DEFAULT_FONT_WEIGHT = '800';
export const DEFAULT_FONT_SIZE = 24;
export const DEFAULT_BUFFER = 3;
export const DEFAULT_CUTOFF = 0.25;

View File

@ -47,7 +47,7 @@ const defaultLayerConfig: Partial<ILayerConfig> = {
],
shape3d: ['cylinder', 'triangleColumn', 'hexagonColumn', 'squareColumn'],
minZoom: 0,
maxZoom: 20,
maxZoom: 24,
visible: true,
autoFit: false,
zIndex: 0,

View File

@ -55,6 +55,7 @@ export interface ILayerModel {
getDefaultStyle(): unknown;
getAnimateUniforms(): IModelUniform;
buildModels(): IModel[];
needUpdate(): boolean;
}
export interface IModelUniform {
[key: string]: IUniform;

View File

@ -161,7 +161,9 @@ export default class Scene extends EventEmitter implements ISceneService {
this.$container as HTMLDivElement,
this.handleWindowResized,
);
// window.addEventListener('resize', this.handleWindowResized, false);
window
.matchMedia('screen and (-webkit-min-device-pixel-ratio: 1.5)')
.addListener(this.handleWindowResized);
} else {
this.logger.error('容器 id 不存在');
}
@ -227,7 +229,9 @@ export default class Scene extends EventEmitter implements ISceneService {
this.rendererService.destroy();
this.map.destroy();
unbind(this.$container as HTMLDivElement, this.handleWindowResized);
// window.removeEventListener('resize', this.handleWindowResized, false);
window
.matchMedia('screen and (min-resolution: 2dppx)')
.removeListener(this.handleWindowResized);
}
private handleWindowResized = () => {

View File

@ -28,9 +28,7 @@
"@antv/l7-core": "^2.0.0-beta.25",
"@antv/l7-layers": "^2.0.0-beta.25",
"@antv/l7-maps": "^2.0.0-beta.25",
"@antv/l7-scene": "^2.0.0-beta.25",
"core-js": "3",
"regenerator-runtime": "^0.13.3"
"@antv/l7-scene": "^2.0.0-beta.25"
},
"gitHead": "00d23ef70d9ec76eec26833fc50ac18fe584cf26",
"publishConfig": {

View File

@ -25,7 +25,6 @@
"@antv/l7-core": "^2.0.0-beta.25",
"@antv/l7-source": "^2.0.0-beta.25",
"@antv/l7-utils": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"@mapbox/martini": "^0.1.0",
"@turf/meta": "^6.0.2",
"@types/d3-color": "^1.2.2",
@ -40,7 +39,6 @@
"inversify": "^5.0.1",
"lodash": "^4.17.15",
"merge-json-schemas": "1.0.0",
"polyline-miter-util": "^1.0.1",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.3",
"tapable": "^2.0.0-beta.8"

View File

@ -16,16 +16,6 @@ export default class CityBuildingLayer extends BaseLayer {
},
};
}
protected renderModels() {
this.models.forEach((model) =>
model.draw({
uniforms: this.layerModel.getUninforms(),
}),
);
return this;
}
protected buildModels() {
this.layerModel = new CityBuildModel(this);
this.models = this.layerModel.buildModels();

View File

@ -165,7 +165,9 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
private scaleOptions: IScaleOptions = {};
private AnimateStartTime: number;
private animateStartTime: number;
private aniamateStatus: boolean = false;
constructor(config: Partial<ILayerConfig & ChildLayerStyleOptions> = {}) {
super();
@ -309,6 +311,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
const { animateOption } = this.getLayerConfig();
if (animateOption?.enable) {
this.layerService.startAnimate();
this.aniamateStatus = true;
}
this.buildModels();
// 触发初始化完成事件;
@ -730,10 +733,21 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
return this.layerService.clock.getDelta();
}
public setAnimateStartTime() {
this.AnimateStartTime = this.layerService.clock.getElapsedTime();
this.animateStartTime = this.layerService.clock.getElapsedTime();
}
public stopAnimate() {
if (this.aniamateStatus) {
this.layerService.stopAnimate();
this.aniamateStatus = false;
this.updateLayerConfig({
animateOption: {
enable: false,
},
});
}
}
public getLayerAnimateTime(): number {
return this.layerService.clock.getElapsedTime() - this.AnimateStartTime;
return this.layerService.clock.getElapsedTime() - this.animateStartTime;
}
protected getConfigSchema() {
@ -745,7 +759,16 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
}
protected renderModels() {
throw new Error('Method not implemented.');
if (this.layerModelNeedUpdate) {
this.models = this.layerModel.buildModels();
this.layerModelNeedUpdate = false;
}
this.models.forEach((model) => {
model.draw({
uniforms: this.layerModel.getUninforms(),
});
});
return this;
}
protected getModelType(): unknown {

View File

@ -77,6 +77,9 @@ export default class BaseModel<ChildLayerStyleOptions = {}>
return {};
}
public needUpdate(): boolean {
return false;
}
public buildModels(): IModel[] {
throw new Error('Method not implemented.');
}

View File

@ -2,6 +2,7 @@ import { IEncodeFeature } from '@antv/l7-core';
import { aProjectFlat, lngLatToMeters } from '@antv/l7-utils';
import earcut from 'earcut';
import { vec3 } from 'gl-matrix';
import { calculteCentroid } from '../utils/geo';
import getNormals from '../utils/polylineNormal';
import extrudePolygon, {
extrude_PolygonNormal,
@ -24,7 +25,7 @@ const GeometryCache: IGeometryCache = {};
* @param feature feature
*/
export function PointFillTriangulation(feature: IEncodeFeature) {
const coordinates = feature.coordinates as number[];
const coordinates = calculteCentroid(feature.coordinates);
return {
vertices: [...coordinates, ...coordinates, ...coordinates, ...coordinates],
indices: [0, 1, 2, 2, 3, 0],
@ -46,7 +47,6 @@ export function PointExtrudeTriangulation(feature: IEncodeFeature) {
vertices: positions,
indices: index,
normals,
// normals: Array.from(computeVertexNormals(positions, index, 3, false)),
size: 5,
};
}
@ -56,7 +56,7 @@ export function PointExtrudeTriangulation(feature: IEncodeFeature) {
* @param feature feature
*/
export function PointImageTriangulation(feature: IEncodeFeature) {
const coordinates = feature.coordinates as number[];
const coordinates = calculteCentroid(feature.coordinates);
return {
vertices: [...coordinates],
indices: [0],

View File

@ -21,8 +21,12 @@ export default class HeatMapLayer extends BaseLayer<IHeatMapLayerStyleOptions> {
protected renderModels() {
const shape = this.getModelType();
if (shape === 'heatmap') {
this.layerModel.render();
return;
this.layerModel.render(); // 独立的渲染流程
return this;
}
if (this.layerModelNeedUpdate) {
this.models = this.layerModel.buildModels();
this.layerModelNeedUpdate = false;
}
this.models.forEach((model) =>
model.draw({

View File

@ -20,8 +20,8 @@ import MultiPassRendererPlugin from './plugins/MultiPassRendererPlugin';
import PixelPickingPlugin from './plugins/PixelPickingPlugin';
import RegisterStyleAttributePlugin from './plugins/RegisterStyleAttributePlugin';
import ShaderUniformPlugin from './plugins/ShaderUniformPlugin';
import UpdateModelPlugin from './plugins/UpdateModelPlugin';
import UpdateStyleAttributePlugin from './plugins/UpdateStyleAttributePlugin';
/**
*
* @see /dev-docs/ConfigSchemaValidation.md
@ -74,6 +74,15 @@ container
.bind<ILayerPlugin>(TYPES.ILayerPlugin)
.to(UpdateStyleAttributePlugin)
.inRequestScope();
/**
* Model更新
*/
container
.bind<ILayerPlugin>(TYPES.ILayerPlugin)
.to(UpdateModelPlugin)
.inRequestScope();
/**
* Multi Pass 线
*/

View File

@ -5,8 +5,6 @@ import LineModels, { LineModelType } from './models';
export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
public type: string = 'LineLayer';
private animateStartTime: number = 0;
protected getConfigSchema() {
return {
properties: {
@ -28,14 +26,6 @@ export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
};
return defaultConfig[type];
}
protected renderModels() {
this.models.forEach((model) =>
model.draw({
uniforms: this.layerModel.getUninforms(),
}),
);
return this;
}
protected buildModels() {
const shape = this.getModelType();
@ -49,12 +39,4 @@ export default class LineLayer extends BaseLayer<ILineLayerStyleOptions> {
const shape = shapeAttribute?.scale?.field as LineModelType;
return shape || 'line';
}
// 拆分的动画插件
private initAnimate() {
const { enable } = this.animateOptions;
if (enable) {
this.layerService.startAnimate();
this.animateStartTime = this.layerService.clock.getElapsedTime();
}
}
}

View File

@ -20,6 +20,9 @@ export default class LayerAnimateStylePlugin implements ILayerPlugin {
private readonly rendererService: IRendererService;
public apply(layer: ILayer) {
// layer.hooks.beforeRender.tap('LayerAnimateStylePlugin', () => {
// })
layer.hooks.beforeRender.tap('LayerAnimateStylePlugin', () => {
// 重新计算坐标系参数
layer.models.forEach((model: IModel) => {

View File

@ -2,6 +2,9 @@ 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';
/**
*
*/
@injectable()
export default class LayerStylePlugin implements ILayerPlugin {
public apply(layer: ILayer) {

View File

@ -0,0 +1,16 @@
import { ILayer, ILayerPlugin, IMapService, TYPES } from '@antv/l7-core';
import { injectable } from 'inversify';
/**
* Model
*/
@injectable()
export default class UpdateModelPlugin implements ILayerPlugin {
public apply(layer: ILayer) {
layer.hooks.beforeRender.tap('UpdateModelPlugin', () => {
// 处理文本更新
if (layer.layerModel) {
layer.layerModel.needUpdate();
}
});
}
}

View File

@ -28,23 +28,12 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
fill: {},
extrude: {},
image: {},
text: {},
text: {
blend: 'normal',
},
};
return defaultConfig[type];
}
protected renderModels() {
if (this.layerModelNeedUpdate) {
this.models = this.layerModel.buildModels();
this.layerModelNeedUpdate = false;
}
this.models.forEach((model) => {
model.draw({
uniforms: this.layerModel.getUninforms(),
});
});
return this;
}
protected buildModels() {
const modelType = this.getModelType();
this.layerModel = new PointModels[modelType](this);
@ -79,9 +68,4 @@ export default class PointLayer extends BaseLayer<IPointLayerStyleOptions> {
return 'text';
}
}
private updateData() {
// const bounds = this.mapService.getBounds();
// console.log(bounds);
}
}

View File

@ -1,6 +1,7 @@
import { AttributeType, gl, IEncodeFeature, IModel } from '@antv/l7-core';
import BaseModel from '../../core/BaseModel';
import { PointExtrudeTriangulation } from '../../core/triangulation';
import { calculteCentroid } from '../../utils/geo';
import pointExtrudeFrag from '../shaders/extrude_frag.glsl';
import pointExtrudeVert from '../shaders/extrude_vert.glsl';
interface IPointLayerStyleOptions {
@ -21,15 +22,7 @@ export default class ExtrudeModel extends BaseModel {
vertexShader: pointExtrudeVert,
fragmentShader: pointExtrudeFrag,
triangulation: PointExtrudeTriangulation,
blend: {
enable: true,
func: {
srcRGB: gl.SRC_ALPHA,
srcAlpha: 1,
dstRGB: gl.ONE_MINUS_SRC_ALPHA,
dstAlpha: 1,
},
},
blend: this.getBlend(),
}),
];
}
@ -108,7 +101,7 @@ export default class ExtrudeModel extends BaseModel {
},
size: 3,
update: (feature: IEncodeFeature, featureIdx: number) => {
const coordinates = feature.coordinates as number[];
const coordinates = calculteCentroid(feature.coordinates);
return [coordinates[0], coordinates[1], 0];
},
},

View File

@ -42,15 +42,7 @@ export default class ImageModel extends BaseModel {
triangulation: PointImageTriangulation,
primitive: gl.POINTS,
depth: { enable: false },
blend: {
enable: true,
func: {
srcRGB: gl.SRC_ALPHA,
srcAlpha: 1,
dstRGB: gl.ONE_MINUS_SRC_ALPHA,
dstAlpha: 1,
},
},
blend: this.getBlend(),
}),
];
}

View File

@ -3,6 +3,7 @@ import {
BlendType,
gl,
IEncodeFeature,
ILayer,
ILayerConfig,
IModel,
IModelUniform,
@ -10,7 +11,8 @@ import {
} from '@antv/l7-core';
import { rgb2arr } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { PointFillTriangulation } from '../../core/triangulation';
import CollisionIndex from '../../utils/collision-index';
import { calculteCentroid } from '../../utils/geo';
import {
getGlyphQuads,
IGlyphQuad,
@ -32,14 +34,12 @@ interface IPointTextLayerStyleOptions {
textAllowOverlap: boolean;
}
export function TextTriangulation(feature: IEncodeFeature) {
const coordinates = feature.coordinates as number[];
const centroid = feature.centroid as number[]; // 计算中心点
const { glyphQuads } = feature;
const vertices: number[] = [];
const indices: number[] = [];
const coord =
coordinates.length === 2
? [coordinates[0], coordinates[1], 0]
: coordinates;
centroid.length === 2 ? [centroid[0], centroid[1], 0] : centroid;
glyphQuads.forEach((quad: IGlyphQuad, index: number) => {
vertices.push(
...coord,
@ -81,14 +81,18 @@ export function TextTriangulation(feature: IEncodeFeature) {
export default class TextModel extends BaseModel {
private texture: ITexture2D;
private glyphInfo: IEncodeFeature[];
private currentZoom: number = -1;
private extent: [[number, number], [number, number]];
public getUninforms(): IModelUniform {
const {
fontWeight = 'normal',
fontWeight = 800,
fontFamily,
stroke,
strokeWidth,
stroke = '#fff',
strokeWidth = 0,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const { canvas, fontAtlas, mapping } = this.fontService;
const { canvas } = this.fontService;
return {
u_opacity: 1.0,
u_sdf_map: this.texture,
@ -100,10 +104,9 @@ export default class TextModel extends BaseModel {
}
public buildModels(): IModel[] {
this.initTextFont();
this.generateGlyphLayout();
this.registerBuiltinAttributes();
this.initGlyph();
this.updateTexture();
this.filterGlyphs();
return [
this.layer.buildLayerModel({
moduleName: 'pointText',
@ -115,9 +118,36 @@ export default class TextModel extends BaseModel {
}),
];
}
public needUpdate() {
const {
textAllowOverlap = false,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const zoom = this.mapService.getZoom();
const extent = this.mapService.getBounds();
const flag =
extent[0][0] < this.extent[0][0] ||
extent[0][1] < this.extent[0][1] ||
extent[1][0] > this.extent[1][0] ||
extent[1][1] < this.extent[1][1];
if (!textAllowOverlap && (Math.abs(this.currentZoom - zoom) > 1 || flag)) {
this.filterGlyphs();
this.layer.models = [
this.layer.buildLayerModel({
moduleName: 'pointText',
vertexShader: textVert,
fragmentShader: textFrag,
triangulation: TextTriangulation,
depth: { enable: false },
blend: this.getBlend(),
}),
];
return true;
}
return false;
}
protected registerBuiltinAttributes() {
const viewProjection = this.cameraService.getViewProjectionMatrix();
this.styleAttributeService.registerStyleAttribute({
name: 'textOffsets',
type: AttributeType.Attribute,
@ -190,9 +220,21 @@ export default class TextModel extends BaseModel {
},
});
}
private textExtent(): [[number, number], [number, number]] {
const bounds = this.mapService.getBounds();
const step =
Math.min(bounds[1][0] - bounds[0][0], bounds[1][1] - bounds[1][0]) / 2;
return [
[bounds[0][0] - step, bounds[0][1] - step],
[bounds[1][0] + step, bounds[1][1] + step],
];
}
/**
*
*/
private initTextFont() {
const {
fontWeight = 'normal',
fontWeight = '800',
fontFamily,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const data = this.layer.getEncodedData();
@ -213,20 +255,19 @@ export default class TextModel extends BaseModel {
fontFamily,
});
}
/**
*
*/
private generateGlyphLayout() {
const { canvas, fontAtlas, mapping } = this.fontService;
const { mapping } = this.fontService;
const {
spacing = 2,
textAnchor = 'center',
textOffset,
padding = [4, 4],
textAllowOverlap,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const data = this.layer.getEncodedData();
data.forEach((feature: IEncodeFeature) => {
const { coordinates, shape = '' } = feature;
const size = feature.size as number;
const fontScale = size / 24;
this.glyphInfo = data.map((feature: IEncodeFeature) => {
const { shape = '', coordinates } = feature;
const shaping = shapeText(
shape.toString(),
mapping,
@ -239,19 +280,61 @@ export default class TextModel extends BaseModel {
const glyphQuads = getGlyphQuads(shaping, textOffset, false);
feature.shaping = shaping;
feature.glyphQuads = glyphQuads;
feature.centroid = calculteCentroid(coordinates);
return feature;
});
}
private drawGlyph() {
/**
*
*/
private filterGlyphs() {
const {
spacing = 2,
textAnchor = 'center',
textOffset = [0, 0],
padding = [4, 4],
textAllowOverlap,
textAllowOverlap = false,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const viewProjection = this.cameraService.getViewProjectionMatrix();
if (textAllowOverlap) {
return;
}
this.currentZoom = this.mapService.getZoom();
this.extent = this.textExtent();
const { width, height } = this.rendererService.getViewportSize();
const collisionIndex = new CollisionIndex(width, height);
const filterData = this.glyphInfo.filter((feature: IEncodeFeature) => {
const { shaping, id = 0 } = feature;
const centroid = feature.centroid as [number, number];
const size = feature.size as number;
const fontScale: number = size / 24;
const pixels = this.mapService.lngLatToContainer(centroid);
const { box } = collisionIndex.placeCollisionBox({
x1: shaping.left * fontScale - padding[0],
x2: shaping.right * fontScale + padding[0],
y1: shaping.top * fontScale - padding[1],
y2: shaping.bottom * fontScale + padding[1],
anchorPointX: pixels.x,
anchorPointY: pixels.y,
});
if (box && box.length) {
// TODOfeatureIndex
collisionIndex.insertCollisionBox(box, id);
return true;
} else {
return false;
}
});
this.layer.setEncodedData(filterData);
}
/**
*
*/
private initGlyph() {
// 1.生成文字纹理
this.initTextFont();
// 2.生成文字布局
this.generateGlyphLayout();
}
/**
*
*/
private updateTexture() {
const { createTexture2D } = this.rendererService;
const { canvas } = this.fontService;

View File

@ -27,7 +27,7 @@ void main() {
vec4 projected_position = project_common_position_to_clipspace(vec4(project_pos.xyz, 1.0));
gl_Position = vec4(projected_position.xy / projected_position.w
+ a_textOffsets * fontScale / u_ViewportSize * 2., 0.0, 1.0);
+ a_textOffsets * fontScale / u_ViewportSize * 2. * u_DevicePixelRatio, 0.0, 1.0);
v_gamma_scale = gl_Position.w;

View File

@ -1,5 +1,6 @@
import { IEncodeFeature } from '@antv/l7-core';
import BaseLayer from '../core/BaseLayer';
import { PointType } from '../point/models/';
import PolygonModels, { PolygonModelType } from './models/';
interface IPolygonLayerStyleOptions {
@ -20,16 +21,6 @@ export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
},
};
}
protected renderModels() {
this.models.forEach((model) =>
model.draw({
uniforms: this.layerModel.getUninforms(),
}),
);
return this;
}
protected buildModels() {
const shape = this.getModelType();
this.layerModel = new PolygonModels[shape](this);
@ -41,6 +32,42 @@ export default class PolygonLayer extends BaseLayer<IPolygonLayerStyleOptions> {
'shape',
);
const shape = shapeAttribute?.scale?.field as PolygonModelType;
return shape || 'fill';
if (shape === 'fill') {
return 'fill';
} else if (shape === 'extrude') {
return 'extrude';
} else if (shape === 'line') {
return 'line';
} else {
return this.getPointModelType();
}
}
protected getPointModelType(): PolygonModelType {
// pointlayer
// 2D、 3d、 shape、image、text、normal、
const layerData = this.getEncodedData();
const { shape2d, shape3d } = this.getLayerConfig();
const iconMap = this.iconService.getIconMap();
const item = layerData.find((fe: IEncodeFeature) => {
return fe.hasOwnProperty('shape');
});
if (!item) {
return 'fill';
} else {
const shape = item.shape;
if (shape === 'dot') {
return 'point_normal';
}
if (shape2d?.indexOf(shape as string) !== -1) {
return 'point_fill';
}
if (shape3d?.indexOf(shape as string) !== -1) {
return 'point_extrude';
}
if (iconMap.hasOwnProperty(shape as string)) {
return 'point_image';
}
return 'text';
}
}
}

View File

@ -1,13 +1,32 @@
import LineModel from '../../line/models/line';
import PointExtrudeModel from '../../point/models/extrude';
import PointFillModel from '../../point/models/fill';
import IMageModel from '../../point/models/image';
import NormalModel from '../../point/models/normal';
import TextModel from '../../point/models/text';
import ExtrudeModel from './extrude';
import FillModel from './fill';
export type PolygonModelType = 'fill' | 'extrude' | 'line';
export type PolygonModelType =
| 'fill'
| 'extrude'
| 'line'
| 'point_fill'
| 'point_image'
| 'point_normal'
| 'point_extrude'
| 'text';
const PolygonModels: { [key in PolygonModelType]: any } = {
fill: FillModel,
line: LineModel,
extrude: ExtrudeModel,
};
text: TextModel,
point_fill: PointFillModel,
point_image: IMageModel,
point_normal: NormalModel,
point_extrude: PointExtrudeModel,
// point_fill: PointModels.fill,
};
export default PolygonModels;

View File

@ -9,10 +9,6 @@ export interface ICollisionBox {
// @mapbox/grid-index 并没有类似 hitTest 的单纯获取碰撞检测结果的方法query 将导致计算大量多余的包围盒结果,因此使用改良版
import { mat4, vec4 } from 'gl-matrix';
import GridIndex from './grid-index';
// 为 viewport 加上 buffer避免边缘处的文本无法显示
const viewportPadding = 100;
/**
*
* @see https://zhuanlan.zhihu.com/p/74373214
@ -21,6 +17,7 @@ export default class CollisionIndex {
private width: number;
private height: number;
private grid: GridIndex;
private viewportPadding: number = 100;
private screenRightBoundary: number;
private screenBottomBoundary: number;
private gridRightBoundary: number;
@ -28,30 +25,35 @@ export default class CollisionIndex {
constructor(width: number, height: number) {
this.width = width;
this.height = height;
this.viewportPadding = Math.max(width, height);
// 创建网格索引
this.grid = new GridIndex(
width + 2 * viewportPadding,
height + 2 * viewportPadding,
width + this.viewportPadding,
height + this.viewportPadding,
25,
);
this.screenRightBoundary = width + viewportPadding;
this.screenBottomBoundary = height + viewportPadding;
this.gridRightBoundary = width + 2 * viewportPadding;
this.gridBottomBoundary = height + 2 * viewportPadding;
this.screenRightBoundary = width + this.viewportPadding;
this.screenBottomBoundary = height + this.viewportPadding;
this.gridRightBoundary = width + 2 * this.viewportPadding;
this.gridBottomBoundary = height + 2 * this.viewportPadding;
}
public placeCollisionBox(collisionBox: ICollisionBox, mvpMatrix: mat4) {
const projectedPoint = this.project(
mvpMatrix,
collisionBox.anchorPointX,
collisionBox.anchorPointY,
);
public placeCollisionBox(collisionBox: ICollisionBox) {
// const projectedPoint = this.project(
// mvpMatrix,
// collisionBox.anchorPointX,
// collisionBox.anchorPointY,
// );
const tlX = collisionBox.x1 + projectedPoint.x;
const tlY = collisionBox.y1 + projectedPoint.y;
const brX = collisionBox.x2 + projectedPoint.x;
const brY = collisionBox.y2 + projectedPoint.y;
const tlX =
collisionBox.x1 + collisionBox.anchorPointX + this.viewportPadding;
const tlY =
collisionBox.y1 + collisionBox.anchorPointY + this.viewportPadding;
const brX =
collisionBox.x2 + collisionBox.anchorPointX + this.viewportPadding;
const brY =
collisionBox.y2 + collisionBox.anchorPointY + this.viewportPadding;
if (
!this.isInsideGrid(tlX, tlY, brX, brY) ||
@ -79,14 +81,16 @@ export default class CollisionIndex {
* @param {number} y P20 Y
* @return {Point} projectedPoint
*/
public project(mvpMatrix: mat4, x: number, y: number) {
public project(mvpMatrix: number[], x: number, y: number) {
const point = vec4.fromValues(x, y, 0, 1);
const out = vec4.create();
vec4.transformMat4(out, point, mvpMatrix);
// @ts-ignore
const mat = mat4.fromValues(...mvpMatrix);
vec4.transformMat4(out, point, mat);
// GL 坐标系[-1, 1] -> viewport 坐标系[width, height]
return {
x: ((out[0] / out[3] + 1) / 2) * this.width + viewportPadding,
y: ((-out[1] / out[3] + 1) / 2) * this.height + viewportPadding,
x: ((out[0] / out[3] + 1) / 2) * this.width + this.viewportPadding,
y: ((-out[1] / out[3] + 1) / 2) * this.height + this.viewportPadding,
};
}

View File

@ -0,0 +1,27 @@
type Position = number[];
import { isNumber } from 'lodash';
export function calculteCentroid(
coord: Position | Position[] | Position[][],
): Position {
// let pos = coord as Position;
if (isNumber(coord[0])) {
return coord as Position;
} else if (isNumber(coord[0][0])) {
throw new Error('当前数据不支持标注');
} else if (isNumber(coord[0][0][0])) {
const coords = coord as Position[][];
let xSum = 0;
let ySum = 0;
let len = 0;
coords.forEach((coor: Position[]) => {
coor.forEach((pos) => {
xSum += pos[0];
ySum += pos[1];
len++;
});
});
return [xSum / len, ySum / len, 0];
} else {
throw new Error('当前数据不支持标注');
}
}

View File

@ -8,7 +8,7 @@ type CallBack = (...args: any[]) => any;
* @see https://zhuanlan.zhihu.com/p/74373214
*/
class GridIndex {
private boxCells: number[][];
private boxCells: number[][] = [];
private xCellCount: number;
private yCellCount: number;
private boxKeys: string[];

View File

@ -237,7 +237,7 @@ export function getGlyphQuads(
textOffset: [number, number] = [0, 0],
alongLine: boolean,
): IGlyphQuad[] {
const { positionedGlyphs } = shaping;
const { positionedGlyphs = [] } = shaping;
const quads: IGlyphQuad[] = [];
for (const positionedGlyph of positionedGlyphs) {

View File

@ -25,7 +25,6 @@
"dependencies": {
"@antv/l7-core": "^2.0.0-beta.25",
"@antv/l7-utils": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"core-js": "3",
"gl-matrix": "^3.1.0",
"inversify": "^5.0.1",

View File

@ -26,10 +26,8 @@
},
"dependencies": {
"@antv/l7-core": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"core-js": "3",
"inversify": "^5.0.1",
"inversify-logging": "^0.2.1",
"lodash": "^4.17.15",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.3",

View File

@ -27,13 +27,10 @@
"@antv/l7-maps": "^2.0.0-beta.25",
"@antv/l7-renderer": "^2.0.0-beta.25",
"@antv/l7-utils": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"core-js": "3",
"inversify": "^5.0.1",
"inversify-inject-decorators": "^3.1.0",
"mapbox-gl": "^1.2.1",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.3"
"reflect-metadata": "^0.1.13"
},
"gitHead": "00d23ef70d9ec76eec26833fc50ac18fe584cf26",
"publishConfig": {

View File

@ -26,7 +26,6 @@
"dependencies": {
"@antv/l7-core": "^2.0.0-beta.25",
"@antv/l7-utils": "^2.0.0-beta.25",
"@babel/runtime": "^7.7.7",
"@mapbox/geojson-rewind": "^0.4.0",
"@turf/helpers": "^6.1.4",
"@turf/invariant": "^6.1.2",
@ -35,22 +34,15 @@
"d3-dsv": "^1.1.1",
"d3-hexbin": "^0.2.2",
"eventemitter3": "^4.0.0",
"gl-matrix": "^3.1.0",
"inversify": "^5.0.1",
"inversify-inject-decorators": "^3.1.0",
"inversify-logging": "^0.2.1",
"lodash": "^4.17.15",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.3",
"supercluster": "^6.0.2",
"tapable": "^2.0.0-beta.8"
},
"devDependencies": {
"@types/d3-dsv": "^1.0.36",
"@types/d3-hexbin": "^0.2.3",
"@types/gl-matrix": "^2.4.5",
"@types/lodash": "^4.14.138",
"@types/viewport-mercator-project": "^6.1.0"
"@types/lodash": "^4.14.138"
},
"gitHead": "00d23ef70d9ec76eec26833fc50ac18fe584cf26",
"publishConfig": {

View File

@ -22,22 +22,8 @@
"author": "lzxue",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.7.7",
"@turf/helpers": "^6.1.4",
"core-js": "3",
"eventemitter3": "^4.0.0",
"gl-matrix": "^3.1.0",
"inversify": "^5.0.1",
"inversify-inject-decorators": "^3.1.0",
"inversify-logging": "^0.2.1",
"lodash": "^4.17.15",
"reflect-metadata": "^0.1.13",
"regenerator-runtime": "^0.13.3",
"tapable": "^2.0.0-beta.8"
},
"devDependencies": {
"@types/gl-matrix": "^2.4.5",
"@types/lodash": "^4.14.138"
"core-js": "3"
},
"gitHead": "00d23ef70d9ec76eec26833fc50ac18fe584cf26",
"publishConfig": {

View File

@ -19,7 +19,7 @@ export default class ZoomComponent extends React.Component {
const scene = new Scene({
id: 'map',
map: new Mapbox({
style: 'mapbox://styles/mapbox/streets-v9',
style: 'dark',
center: [110.19382669582967, 30.258134],
pitch: 0,
zoom: 3,
@ -39,9 +39,10 @@ export default class ZoomComponent extends React.Component {
'#FF7A45',
'#CF1D49',
])
.shape('fill')
.shape('name', 'text')
.size(10)
.style({
opacity: 0.3,
opacity: 1.0,
});
scene.addLayer(layer);
const zoomControl = new Zoom({

View File

@ -44,7 +44,7 @@ export default class TextLayerDemo extends React.Component {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
map: new Mapbox({
center: [120.19382669582967, 30.258134],
pitch: 0,
style: 'dark',
@ -61,17 +61,17 @@ export default class TextLayerDemo extends React.Component {
},
})
.shape('m', 'text')
.size(24)
.size(12)
.color('#fff')
.style({
fontWeight: 800,
textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
spacing: 2, // 字符间距
padding: [4, 4], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
strokeColor: 'white', // 描边颜色
strokeWidth: 4, // 描边宽度
strokeOpacity: 1.0,
// fontWeight: 200,
// textAnchor: 'center', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
// textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
// spacing: 2, // 字符间距
// padding: [1, 1], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
// stroke: 'red', // 描边颜色
// strokeWidth: 2, // 描边宽度
// strokeOpacity: 1.0,
});
scene.addLayer(pointLayer);

12828
yarn.lock

File diff suppressed because it is too large Load Diff