Merge pull request #210 from antvis/text_render

Text render
This commit is contained in:
@thinkinggis 2020-02-14 16:20:38 +08:00 committed by GitHub
commit f20c553ea7
22 changed files with 199 additions and 85 deletions

View File

@ -1,4 +1,5 @@
// @see https://babeljs.io/docs/en/next/config-files#project-wide-configuration
const path = require('path');
module.exports = api => {
api.cache(() => process.env.NODE_ENV);
@ -52,7 +53,8 @@ module.exports = api => {
modules: (isCDNBundle || isESModule) ? false : 'auto',
targets: {
chrome: 58,
ie: 11
ie: 10,
browsers: [ 'ie >= 11' ]
}
}
],
@ -115,7 +117,8 @@ module.exports = api => {
// isCDNBundle ? 'inline-webgl-constants' : {},
],
ignore: [
'node_modules',
// 'node_modules/d3-array',
// /node_modules\/(?![d3*])/,
...!isTest ? [
'**/*.test.tsx',
'**/*.test.ts',

View File

@ -9,7 +9,14 @@ import babel from 'rollup-plugin-babel';
import glsl from './rollup-plugin-glsl';
import postcss from 'rollup-plugin-postcss';
import url from 'postcss-url';
const { BUILD, MINIFY } = process.env;
const minified = MINIFY === 'true';
const production = BUILD === 'production';
const outputFile = !production
? 'packages/l7/dist/l7-dev.js'
: minified
? 'packages/l7/dist/l7.js'
: 'packages/l7/dist/l7-dev.js';
function resolveFile(filePath) {
return path.join(__dirname, '..', filePath);
}
@ -18,7 +25,7 @@ module.exports = [
{
input: resolveFile('build/bundle.ts'),
output: {
file: resolveFile('packages/l7/dist/l7.js'),
file: resolveFile(outputFile),
format: 'umd',
name: 'L7',
globals: {
@ -28,7 +35,7 @@ module.exports = [
external: [
'mapbox-gl'
],
treeshake: true,
treeshake: minified,
plugins: [
alias(
{
@ -81,7 +88,8 @@ module.exports = [
babel({
extensions: [ '.js', '.ts' ]
}),
terser(),
// terser(),
minified ? terser() : false,
analyze({
summaryOnly: true,
limit: 20

View File

@ -14,7 +14,7 @@
"message": "chore: publish"
}
},
"version": "2.0.19",
"version": "2.0.21",
"npmClient": "yarn",
"useWorkspaces": true,
"publishConfig": {

View File

@ -147,7 +147,8 @@
"coveralls": "jest --coverage && cat ./tests/coverage/lcov.info | coveralls",
"tsc": "tsc",
"watch": "yarn clean && lerna exec --parallel -- cross-env BABEL_ENV=cjs babel --watch src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --delete-dir-on-start --no-comments",
"bundle": "cross-env BABEL_ENV=bundle node_modules/.bin/rollup -c ./build/rollup.config.js",
"bundle": "cross-env BABEL_ENV=bundle node_modules/.bin/rollup -c ./build/rollup.config.js --environment BUILD:production,MINIFY:true ",
"bundle-dev": "cross-env BABEL_ENV=bundle node_modules/.bin/rollup -c ./build/rollup.config.js --environment 'BUILD:production,MINIFY:false'",
"bundle:watch": "cross-env BABEL_ENV=bundle node_modules/.bin/rollup -c ./build/rollup.config.js --watch",
"glsl-minify": "node_modules/.bin/glsl-minifier -i ./build/example.frag -o ./build/example.min.frag",
"clean": "lerna run clean"

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-component",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -24,8 +24,8 @@
"author": "lzxue",
"license": "ISC",
"dependencies": {
"@antv/l7-core": "^2.0.19",
"@antv/l7-utils": "^2.0.19",
"@antv/l7-core": "^2.0.21",
"@antv/l7-utils": "^2.0.21",
"@babel/runtime": "^7.7.7",
"eventemitter3": "^4.0.0",
"inversify": "^5.0.1",

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-core",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -22,7 +22,7 @@
"author": "xiaoiver",
"license": "ISC",
"dependencies": {
"@antv/l7-utils": "^2.0.19",
"@antv/l7-utils": "^2.0.21",
"@babel/runtime": "^7.7.7",
"@mapbox/tiny-sdf": "^1.1.1",
"ajv": "^6.10.2",

View File

@ -69,11 +69,9 @@ export default class LayerService implements ILayerService {
}
public renderLayers() {
// TODO脏检查只渲染发生改变的 Layer
if (this.alreadyInRendering) {
return;
}
//
this.alreadyInRendering = true;
this.clear();
this.updateRenderOrder();

View File

@ -125,7 +125,6 @@ export default class StyleAttributeService implements IStyleAttributeService {
const { elements, sizePerElement } = this.featureLayout;
// 截取待更新的 feature 范围
const featuresToUpdate = elements.slice(startFeatureIdx, endFeatureIdx);
// [n, n] 中断更新
if (!featuresToUpdate.length) {
return;
@ -184,6 +183,11 @@ export default class StyleAttributeService implements IStyleAttributeService {
};
elements: IElements;
} {
// 每次创建的初始化化 LayerOut
this.featureLayout = {
sizePerElement: 0,
elements: [],
};
if (triangulation) {
this.triangulation = triangulation;
}
@ -196,7 +200,6 @@ export default class StyleAttributeService implements IStyleAttributeService {
const indices: number[] = [];
const normals: number[] = [];
let size = 3;
features.forEach((feature, featureIdx) => {
// 逐 feature 进行三角化
const {

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7",
"version": "2.0.19",
"version": "2.0.21",
"description": "A Large-scale WebGL-powered Geospatial Data Visualization",
"main": "lib/index.js",
"module": "es/index.js",
@ -24,11 +24,11 @@
"author": "antv",
"license": "MIT",
"dependencies": {
"@antv/l7-component": "^2.0.19",
"@antv/l7-core": "^2.0.19",
"@antv/l7-layers": "^2.0.19",
"@antv/l7-maps": "^2.0.19",
"@antv/l7-scene": "^2.0.19",
"@antv/l7-component": "^2.0.21",
"@antv/l7-core": "^2.0.21",
"@antv/l7-layers": "^2.0.21",
"@antv/l7-maps": "^2.0.21",
"@antv/l7-scene": "^2.0.21",
"@babel/runtime": "^7.7.7"
},
"gitHead": "f2bd3c6473df79d815467b1677c6f985cf68800e",

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-layers",
"version": "2.0.19",
"version": "2.0.21",
"description": "L7's collection of built-in layers",
"main": "lib/index.js",
"module": "es/index.js",
@ -22,9 +22,9 @@
"author": "xiaoiver",
"license": "ISC",
"dependencies": {
"@antv/l7-core": "^2.0.19",
"@antv/l7-source": "^2.0.19",
"@antv/l7-utils": "^2.0.19",
"@antv/l7-core": "^2.0.21",
"@antv/l7-source": "^2.0.21",
"@antv/l7-utils": "^2.0.21",
"@babel/runtime": "^7.7.7",
"@mapbox/martini": "^0.1.0",
"@turf/meta": "^6.0.2",

View File

@ -360,7 +360,9 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
updateOptions?: Partial<IStyleAttributeUpdateOptions>,
) {
this.updateStyleAttribute('filter', field, values, updateOptions);
this.dataState.dataMappingNeedUpdate = true;
// if (this.inited) {
// this.layerModelNeedUpdate = true;
// }
return this;
}
@ -811,6 +813,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
protected renderModels() {
if (this.layerModelNeedUpdate) {
this.models = this.layerModel.buildModels();
this.hooks.beforeRender.call();
this.layerModelNeedUpdate = false;
}
this.models.forEach((model) => {

View File

@ -27,11 +27,16 @@ export default class DataMappingPlugin implements ILayerPlugin {
}: { styleAttributeService: IStyleAttributeService },
) {
layer.hooks.init.tap('DataMappingPlugin', () => {
// 初始化重新生成 map
this.generateMaping(layer, { styleAttributeService });
});
layer.hooks.beforeRenderData.tap('DataMappingPlugin', (flag) => {
if (flag || layer.dataState.dataMappingNeedUpdate) {
if (
flag ||
layer.dataState.dataMappingNeedUpdate ||
layer.layerModelNeedUpdate
) {
layer.dataState.dataMappingNeedUpdate = false;
this.generateMaping(layer, { styleAttributeService });
return true;
@ -42,16 +47,37 @@ export default class DataMappingPlugin implements ILayerPlugin {
// remapping before render
layer.hooks.beforeRender.tap('DataMappingPlugin', () => {
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
const filter = styleAttributeService.getLayerStyleAttribute('filter');
const { dataArray } = layer.getSource().data;
const attributesToRemapping = attributes.filter(
(attribute) => attribute.needRemapping,
(attribute) => attribute.needRemapping, // 如果filter变化
);
let filterData = dataArray;
// 数据过滤完 再执行数据映射
if (filter?.needRemapping && filter?.scale) {
filterData = dataArray.filter((record: IParseDataItem) => {
return this.applyAttributeMapping(filter, record)[0];
});
}
if (attributesToRemapping.length) {
layer.setEncodedData(this.mapping(attributesToRemapping, dataArray));
// 过滤数据
if (filter?.needRemapping) {
layer.setEncodedData(this.mapping(attributes, filterData));
} else {
layer.setEncodedData(
this.mapping(
attributesToRemapping,
filterData,
layer.getEncodedData(),
),
);
}
this.logger.debug('remapping finished');
}
});
}
private generateMaping(
layer: ILayer,
{
@ -62,17 +88,16 @@ export default class DataMappingPlugin implements ILayerPlugin {
const filter = styleAttributeService.getLayerStyleAttribute('filter');
const { dataArray } = layer.getSource().data;
let filterData = dataArray;
// 数据过滤完 执行数据映射
// 数据过滤完 执行数据映射
if (filter?.scale) {
filterData = dataArray.filter((record: IParseDataItem) => {
return this.applyAttributeMapping(filter, record)[0];
});
}
// TODO: FIXME
if (!filterData) {
return;
}
// if (!filterData) {
// return;
// }
// mapping with source data
layer.setEncodedData(this.mapping(attributes, filterData));
}
@ -80,11 +105,14 @@ export default class DataMappingPlugin implements ILayerPlugin {
private mapping(
attributes: IStyleAttribute[],
data: IParseDataItem[],
predata?: IEncodeFeature[],
): IEncodeFeature[] {
return data.map((record: IParseDataItem) => {
return data.map((record: IParseDataItem, i) => {
const preRecord = predata ? predata[i] : {};
const encodeRecord: IEncodeFeature = {
id: record._id,
coordinates: record.coordinates,
...preRecord,
};
attributes
.filter((attribute) => attribute.scale !== undefined)

View File

@ -19,6 +19,7 @@ export default class LayerModelPlugin implements ILayerPlugin {
layer.prepareBuildModel();
// 初始化 Model
layer.buildModels();
layer.layerModelNeedUpdate = false;
}
return false;
});

View File

@ -37,10 +37,12 @@ export default class UpdateStyleAttributePlugin implements ILayerPlugin {
});
layer.hooks.beforeRender.tap('UpdateStyleAttributePlugin', () => {
if (layer.layerModelNeedUpdate) {
return;
}
this.updateStyleAtrribute(layer, { styleAttributeService });
});
}
private updateStyleAtrribute(
layer: ILayer,
{
@ -48,10 +50,16 @@ export default class UpdateStyleAttributePlugin implements ILayerPlugin {
}: { styleAttributeService: IStyleAttributeService },
) {
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
const filter = styleAttributeService.getLayerStyleAttribute('filter');
if (filter && filter.needRegenerateVertices) {
layer.layerModelNeedUpdate = true;
attributes.forEach((attr) => (attr.needRegenerateVertices = false));
return;
}
attributes
.filter((attribute) => attribute.needRegenerateVertices)
.forEach((attribute) => {
// 精确更新某个/某些 feature(s),需要传入 featureIdx
// 精确更新某个/某些 feature(s),需要传入 featureIdx d
styleAttributeService.updateAttributeByFeatureRange(
attribute.name,
layer.getEncodedData(), // 获取经过 mapping 最新的数据

View File

@ -9,7 +9,7 @@ import {
IModelUniform,
ITexture2D,
} from '@antv/l7-core';
import { rgb2arr } from '@antv/l7-utils';
import { boundsContains, padBounds, rgb2arr } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import CollisionIndex from '../../utils/collision-index';
import { calculteCentroid } from '../../utils/geo';
@ -84,18 +84,33 @@ export default class TextModel extends BaseModel {
private glyphInfo: IEncodeFeature[];
private currentZoom: number = -1;
private extent: [[number, number], [number, number]];
private textureHeight: number = 0;
private preTextStyle: Partial<IPointTextLayerStyleOptions> = {};
private glyphInfoMap: {
[key: string]: {
shaping: any;
glyphQuads: IGlyphQuad[];
centroid: number[];
};
} = {};
public getUninforms(): IModelUniform {
const {
fontWeight = 800,
fontFamily = 'sans-serif',
opacity = 1.0,
stroke = '#fff',
strokeWidth = 0,
strokeOpacity = 1,
textAnchor = 'center',
textAllowOverlap = true,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
this.updateTexture();
const { canvas } = this.fontService;
if (canvas.height !== this.textureHeight) {
this.updateTexture();
}
this.preTextStyle = {
textAnchor,
textAllowOverlap,
};
return {
u_opacity: opacity,
u_stroke_opacity: strokeOpacity,
@ -109,6 +124,14 @@ export default class TextModel extends BaseModel {
public buildModels(): IModel[] {
this.extent = this.textExtent();
const {
textAnchor = 'center',
textAllowOverlap = true,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
this.preTextStyle = {
textAnchor,
textAllowOverlap,
};
this.initGlyph();
this.updateTexture();
this.filterGlyphs();
@ -127,15 +150,15 @@ export default class TextModel extends BaseModel {
const {
textAllowOverlap = false,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
// textAllowOverlap 发生改变
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)) {
const flag = boundsContains(this.extent, extent);
// 文本不能压盖则进行过滤
if (
(!textAllowOverlap && (Math.abs(this.currentZoom - zoom) > 1 || !flag)) ||
textAllowOverlap !== this.preTextStyle.textAllowOverlap
) {
this.filterGlyphs();
this.layer.models = [
this.layer.buildLayerModel({
@ -227,12 +250,7 @@ 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],
];
return padBounds(bounds, 0.5);
}
/**
*
@ -240,7 +258,7 @@ export default class TextModel extends BaseModel {
private initTextFont() {
const {
fontWeight = '800',
fontFamily,
fontFamily = 'sans-serif',
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const data = this.layer.getEncodedData();
const characterSet: string[] = [];
@ -264,6 +282,7 @@ export default class TextModel extends BaseModel {
*
*/
private generateGlyphLayout() {
// TODO:更新文字布局
const { mapping } = this.fontService;
const {
spacing = 2,
@ -272,7 +291,7 @@ export default class TextModel extends BaseModel {
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
const data = this.layer.getEncodedData();
this.glyphInfo = data.map((feature: IEncodeFeature) => {
const { shape = '', coordinates } = feature;
const { shape = '', coordinates, id } = feature;
const shaping = shapeText(
shape.toString(),
mapping,
@ -286,6 +305,13 @@ export default class TextModel extends BaseModel {
feature.shaping = shaping;
feature.glyphQuads = glyphQuads;
feature.centroid = calculteCentroid(coordinates);
if (id) {
this.glyphInfoMap[id] = {
shaping,
glyphQuads,
centroid: calculteCentroid(coordinates),
};
}
return feature;
});
}
@ -298,6 +324,7 @@ export default class TextModel extends BaseModel {
textAllowOverlap = false,
} = this.layer.getLayerConfig() as IPointTextLayerStyleOptions;
if (textAllowOverlap) {
this.layer.setEncodedData(this.glyphInfo);
return;
}
this.currentZoom = this.mapService.getZoom();
@ -343,10 +370,26 @@ export default class TextModel extends BaseModel {
private updateTexture() {
const { createTexture2D } = this.rendererService;
const { canvas } = this.fontService;
this.textureHeight = canvas.height;
this.texture = createTexture2D({
data: canvas,
width: canvas.width,
height: canvas.height,
});
}
private rebuildModel() {
// 避让 anchor,等属性变化时需要重新构建model
this.filterGlyphs();
return [
this.layer.buildLayerModel({
moduleName: 'pointText',
vertexShader: textVert,
fragmentShader: textFrag,
triangulation: TextTriangulation,
depth: { enable: false },
blend: this.getBlend(),
}),
];
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-maps",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -23,8 +23,8 @@
"author": "xiaoiver",
"license": "ISC",
"dependencies": {
"@antv/l7-core": "^2.0.19",
"@antv/l7-utils": "^2.0.19",
"@antv/l7-core": "^2.0.21",
"@antv/l7-utils": "^2.0.21",
"@babel/runtime": "^7.7.7",
"gl-matrix": "^3.1.0",
"inversify": "^5.0.1",

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-react",
"version": "2.0.18",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -24,10 +24,10 @@
"author": "lzxue",
"license": "ISC",
"dependencies": {
"@antv/l7": "^2.0.18",
"@antv/l7-maps": "^2.0.18",
"react": "^16.8.6",
"@babel/runtime": "^7.7.7"
"@antv/l7": "^2.0.21",
"@antv/l7-maps": "^2.0.21",
"@babel/runtime": "^7.7.7",
"react": "^16.8.6"
},
"gitHead": "f2bd3c6473df79d815467b1677c6f985cf68800e",
"publishConfig": {

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-renderer",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -25,7 +25,7 @@
"gl": "^4.4.0"
},
"dependencies": {
"@antv/l7-core": "^2.0.19",
"@antv/l7-core": "^2.0.21",
"@babel/runtime": "^7.7.7",
"inversify": "^5.0.1",
"lodash": "^4.17.15",

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-scene",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -22,11 +22,11 @@
"author": "xiaoiver",
"license": "ISC",
"dependencies": {
"@antv/l7-component": "^2.0.19",
"@antv/l7-core": "^2.0.19",
"@antv/l7-maps": "^2.0.19",
"@antv/l7-renderer": "^2.0.19",
"@antv/l7-utils": "^2.0.19",
"@antv/l7-component": "^2.0.21",
"@antv/l7-core": "^2.0.21",
"@antv/l7-maps": "^2.0.21",
"@antv/l7-renderer": "^2.0.21",
"@antv/l7-utils": "^2.0.21",
"@babel/runtime": "^7.7.7",
"inversify": "^5.0.1",
"mapbox-gl": "^1.2.1",

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-source",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",
@ -24,8 +24,8 @@
"author": "lzxue",
"license": "ISC",
"dependencies": {
"@antv/l7-core": "^2.0.19",
"@antv/l7-utils": "^2.0.19",
"@antv/l7-core": "^2.0.21",
"@antv/l7-utils": "^2.0.21",
"@babel/runtime": "^7.7.7",
"@mapbox/geojson-rewind": "^0.4.0",
"@turf/helpers": "^6.1.4",

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7-utils",
"version": "2.0.19",
"version": "2.0.21",
"description": "",
"main": "lib/index.js",
"module": "es/index.js",

View File

@ -1,7 +1,7 @@
import { PointLayer, Scene } from '@antv/l7';
import { GaodeMap, Mapbox } from '@antv/l7-maps';
import * as React from 'react';
import * as dat from 'dat.gui';
import * as React from 'react';
// @ts-ignore
import data from '../data/data.json';
export default class TextLayerDemo extends React.Component {
@ -40,8 +40,12 @@ export default class TextLayerDemo extends React.Component {
},
})
.shape('m', 'text')
// .shape('circle')
.size(12)
.color('#fff')
.filter('t', (t) => {
return t > 14;
})
.color('red')
.style({
textAllowOverlap: true,
// fontWeight: 200,
@ -54,10 +58,6 @@ export default class TextLayerDemo extends React.Component {
// strokeOpacity: 1.0,
});
scene.addLayer(pointLayer);
pointLayer.on('click', (e) => {
console.log(e);
});
this.scene = scene;
const gui = new dat.GUI();
@ -65,7 +65,9 @@ export default class TextLayerDemo extends React.Component {
const styleOptions = {
textAnchor: 'center',
strokeWidth: 1,
textAllowOverlap: false,
opacity: 1,
color: '#ffffff',
};
const rasterFolder = gui.addFolder('文本可视化');
rasterFolder
@ -90,8 +92,17 @@ export default class TextLayerDemo extends React.Component {
rasterFolder
.add(styleOptions, 'strokeWidth', 0, 10)
.onChange((strokeWidth: number) => {
pointLayer.filter('t', (t: number) => {
return t > strokeWidth;
});
// pointLayer.setData(pointsData.list.slice(0, strokeWidth));
scene.render();
});
rasterFolder
.add(styleOptions, 'textAllowOverlap', 0, 10)
.onChange((textAllowOverlap: boolean) => {
pointLayer.style({
strokeWidth,
textAllowOverlap,
});
scene.render();
});
@ -102,7 +113,14 @@ export default class TextLayerDemo extends React.Component {
opacity,
});
scene.render();
setTimeout(() => {
scene.render();
}, 10);
});
rasterFolder.addColor(styleOptions, 'color').onChange((color: string) => {
pointLayer.color(color);
scene.render();
});
// });
}