feat(tilelayer): add mask

This commit is contained in:
thinkinggis 2019-05-14 17:10:19 +08:00
parent b39f6c297a
commit e018661b7a
24 changed files with 378 additions and 42 deletions

53
demos/vectorTile.html Normal file
View File

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="geometry" content="diagram">
<link rel="stylesheet" href="./assets/common.css">
<link rel="stylesheet" href="./assets/info.css">
<title>hexagon demo</title>
<style>
body {margin: 0;}
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script src="./assets/dat.gui.min.js"></script>
<script src="../build/L7.js"></script>
<script>
const scene = new L7.Scene({
id: 'map',
mapStyle: 'light', // 样式URL
center: [120.05859375,30.29701788337204 ],
pitch: 0,
hash:true,
zoom: 11,
});
window.scene = scene;
scene.on('loaded', () => {
const layer = scene.VectorTileLayer({
zIndex:0
})
//.source('https://pre-lbs-show.taobao.com/gettile?x={x}&y={y}&z={z}&pipeId=pipe_vt_test')
// https://mvt.amap.com/district/CHN2/8/203/105/4096?key=
.source('http://localhost:5000/test.mbtile/{z}/{x}/{y}.pbf')
.shape('line')
.color('red')
.render();
console.log(layer);
});
</script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "@antv/l7",
"version": "1.1.10",
"version": "1.1.11",
"description": "Large-scale WebGL-powered Geospatial Data Visualization",
"main": "build/l7.js",
"browser": "build/l7.js",

View File

@ -24,11 +24,14 @@ export default class Engine extends EventEmitter {
});
}
update() {
this._renderer.clear();
this._renderer.render(this._scene, this._camera);
this._initPostProcessing();
}
destroy() {
}
renderScene(scene) {
this._renderer.render(scene, this._camera);
}
run() {
this.update();

View File

@ -9,7 +9,8 @@ export default class Renderer {
initRender() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
alpha: true,
autoClear: false
});
this.renderer.setClearColor(0xff0000, 0.0);
this.pixelRatio = window.devicePixelRatio;

View File

@ -309,7 +309,7 @@ export default class Layer extends Base {
}
createScale(field) {
const data = this.layerSource.data.dataArray;
const data = this.layerSource ? this.layerSource.data.dataArray : null;
const scales = this.get('scales');
let scale = scales[field];
const scaleController = this.get('scaleController');
@ -404,7 +404,7 @@ export default class Layer extends Base {
const nextAttrs = this.get('attrOptions');
const preStyle = this.get('preStyleOption');
const nextStyle = this.get('styleOptions');
if (preAttrs === undefined && preStyle === undefined) {
if (preAttrs === undefined && preStyle === undefined) { // 首次渲染
this._mapping();
this._setPreOption();
this._scaleByZoom();
@ -490,12 +490,13 @@ export default class Layer extends Base {
this.layerMesh.material.updateUninform(newOption);
}
_mapping() {
_mapping(source) {
const self = this;
const attrs = self.get('attrs');
const mappedData = [];
// const data = this.layerSource.propertiesData;
const data = this.layerSource.data.dataArray;
let data;
source ? data = source.data.dataArray : data = this.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
@ -528,14 +529,16 @@ export default class Layer extends Base {
});
}
this.layerData = mappedData;
return mappedData;
}
// 更新地图映射
_updateMaping() {
_updateMaping(source, layer) {
const self = this;
const attrs = self.get('attrs');
const data = this.layerSource.data.dataArray;
const data = source ? source.data.dataArray : this.layerSource.data.dataArray;
const layerData = layer || this.layerData;
for (let i = 0; i < data.length; i++) {
const record = data[i];
for (const attrName in attrs) {
@ -547,10 +550,10 @@ export default class Layer extends Base {
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
this.layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
this.layerData[i][names[0]] = values.length === 1 ? values[0] : values;
layerData[i][names[0]] = values.length === 1 ? values[0] : values;
}
attr.neadUpdate = true;
@ -668,6 +671,7 @@ export default class Layer extends Base {
pickAttr.needsUpdate = true;
}
_visibleWithZoom() {
if (this._object3D === null) return;
const zoom = this.scene.getZoom();
const minZoom = this.get('minZoom');
const maxZoom = this.get('maxZoom');
@ -751,7 +755,6 @@ export default class Layer extends Base {
this.clearMapEvent();
if (this._object3D.type === 'composer') {
this.remove(this._object3D);
return;
}
if (this._object3D && this._object3D.children) {
@ -778,8 +781,19 @@ export default class Layer extends Base {
child = null;
}
}
this.layerMesh.geometry = null;
this.layerMesh.material.dispose();
this.layerMesh.material = null;
if (this._pickingMesh) {
this._pickingMesh.children[0].geometry = null;
this._pickingMesh.children[0].material.dispose();
this._pickingMesh.children[0].material = null;
}
this._buffer = null;
this._object3D = null;
this.scene._engine._scene.remove(this._object3D);
this.layerData.length = 0;
this.layerSource = null;
this.scene._engine._picking.remove(this._pickingMesh);
this.destroyed = true;
}

View File

@ -25,7 +25,8 @@ export default class Scene extends Base {
_initEngine(mapContainer) {
this._engine = new Engine(mapContainer, this);
this.registerMapEvent();
// this.registerMapEvent();
this._engine.run();
// this.workerPool = new WorkerPool();
compileBuiltinModules();
}
@ -73,7 +74,6 @@ export default class Scene extends Base {
}
off(type, hander) {
if (this.map) { this.map.off(type, hander); }
super.off(type, hander);
}
addImage() {

View File

@ -87,6 +87,7 @@ export default class LineBuffer extends BufferBase {
layerData.forEach(item => {
const props = item;
const positionCount = positions.length / 3;
// TODO 处理多个线段的情况
const attr = lineShape.Line(item.coordinates, props, positionCount, (lineType !== 'soild'));
positions.push(...attr.positions);
normal.push(...attr.normal);

View File

@ -0,0 +1,24 @@
import Material from './../material';
import { getModule } from '../../../util/shaderModule';
export default class MaskMaterial extends Material {
getDefaultParameters() {
return {
uniforms: {
},
defines: {
}
};
}
constructor(_uniforms, _defines, parameters) {
super(parameters);
const { uniforms, defines } = this.getDefaultParameters();
const { vs, fs } = getModule('mask_quard');
this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms));
this.type = 'MaskMaterial';
this.defines = Object.assign(defines, _defines);
this.vertexShader = vs;
this.fragmentShader = fs;
this.transparent = true;
}
}

View File

@ -45,6 +45,11 @@ import raster_frag from '../shader/raster_frag.glsl';
import tile_polygon_vert from '../shader/tile/polygon_vert.glsl';
import tile_polygon_frag from '../shader/tile/polygon_frag.glsl';
// mask
import mask_quard_vert from '../shader/tile/mask_quard_vert.glsl';
import mask_quard_frag from '../shader/tile/mask_quard_frag.glsl';
import common from './common.glsl';
import { registerModule } from '../../util/shaderModule';
import pick_color from './shaderChunks/pick_color.glsl';
@ -65,5 +70,6 @@ export function compileBuiltinModules() {
registerModule('image', { vs: image_vert, fs: image_frag });
registerModule('raster', { vs: raster_vert, fs: raster_frag });
registerModule('tilepolygon', { vs: tile_polygon_vert, fs: tile_polygon_frag });
registerModule('mask_quard', { vs: mask_quard_vert, fs: mask_quard_frag });
}

View File

@ -0,0 +1,4 @@
precision highp float;
void main() {
gl_FragColor = vec4(1.0);
}

View File

@ -0,0 +1,6 @@
precision highp float;
void main(){
mat4 matModelViewProjection=projectionMatrix*modelViewMatrix;
gl_Position = matModelViewProjection * vec4(position, 1.0);
}

View File

@ -65,7 +65,10 @@ export function defaultLine(geo, index) {
}
// mesh line
export function Line(path, props, positionsIndex) {
if (path.length === 1) path = path[0];// 面坐标转线坐标
// 区分path是面还是线
if (Array.isArray(path[0][0])) {
path = path[0]; // 面坐标转线坐标
}
const positions = [];
const pickingIds = [];
const normal = [];
@ -77,7 +80,8 @@ export function Line(path, props, positionsIndex) {
const sizes = [];
let c = 0;
let index = positionsIndex;
const { size, color, id } = props;
let { size, color, id } = props;
!Array.isArray(size) && (size = [ size ]);
path.forEach((point, pointIndex, list) => {
const i = index;
colors.push(...color);

View File

@ -5,7 +5,7 @@
// const Global = {};
const FONT_FAMILY = '"-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto,"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",SimSun, "sans-serif"';
const Global = {
version: '1.0.0',
version: '1.11.1',
scene: {
mapType: 'AMAP',
zoom: 5,

View File

@ -7,6 +7,7 @@ import RasterLayer from './rasterLayer';
import HeatmapLayer from './heatmapLayer';
import TileLayer from './tile/tileLayer';
import ImageTileLayer from './tile/imageTileLayer';
import VectorTileLayer from './tile/VectorTileLayer';
registerLayer('PolygonLayer', PolygonLayer);
registerLayer('PointLayer', PointLayer);
@ -16,7 +17,8 @@ registerLayer('RasterLayer', RasterLayer);
registerLayer('HeatmapLayer', HeatmapLayer);
registerLayer('TileLayer', TileLayer);
registerLayer('ImageTileLayer', ImageTileLayer);
registerLayer('VectorTileLayer', VectorTileLayer);
export { LAYER_MAP } from './factory';
export { LAYER_MAP, getLayer } from './factory';
export { registerLayer };

View File

@ -39,7 +39,7 @@ export default class LineLayer extends Layer {
if (this.shapeType === 'arc') {
DrawArc(attributes, layerCfg, this);
} else {
DrawLine(attributes, layerCfg, this);
this.add(DrawLine(attributes, layerCfg, this));
}
}
}

View File

@ -1,6 +1,6 @@
import HeatmapBuffer from '../../../geom/buffer/heatmap/heatmap';
import { createColorRamp } from '../../../geom/buffer/heatmap/heatmap';
import { HeatmapIntensityMaterial, HeatmapColorizeMaterial } from '../../../geom/material/heatmapMateial';
import { HeatmapIntensityMaterial, HeatmapColorizeMaterial } from '../../../geom/material/heatmapMaterial';
// import Renderpass from '../../../core/engine/renderpass.bak';
import RenderPass from '../../../core/engine/render-pass';
import ShaderPass from '../../../core/engine/shader-pass';

View File

@ -40,5 +40,5 @@ export default function DrawLine(attributes, cfg, layer) {
});
lineMaterial.setDefinesvalue('ANIMATE', true);
}
layer.add(lineMesh);
return lineMesh;
}

View File

@ -0,0 +1,7 @@
import TileLayer from './tileLayer';
import VectorTile from './vectorTile';
export default class VectorTileLayer extends TileLayer {
_createTile(key, layer) {
return new VectorTile(key, this.url, layer);
}
}

View File

@ -23,7 +23,7 @@ export default class ImageTile extends Tile {
const image = document.createElement('img');
image.addEventListener('load', () => {
this._isLoaded = true;
this._createMesh(image);
this._ready = true;
}, false);
@ -58,8 +58,8 @@ export default class ImageTile extends Tile {
buffer.attributes.texture = buffer.texture;
const style = this.layer.get('styleOptions');
const mesh = DrawImage(buffer.attributes, style);
this.Object3D.add(mesh);
return this.Object3D;
this._object3D.add(mesh);
return this._object3D;
}
_abortRequest() {
if (!this._image) {

View File

@ -1,10 +1,12 @@
import * as THREE from '../../core/three';
import EventEmitter from 'wolfy87-eventemitter';
import { toLngLatBounds, toBounds } from '@antv/geo-coord';
const r2d = 180 / Math.PI;
const tileURLRegex = /\{([zxy])\}/g;
export default class Tile {
export default class Tile extends EventEmitter {
constructor(key, url, layer) {
super();
this.layer = layer;
this._tile = key.split('_').map(v => v * 1);
this._path = url;
@ -15,7 +17,10 @@ export default class Tile {
this._center = this._tileBounds.getCenter();
this._centerLnglat = this._tileLnglatBounds.getCenter();
this.Object3D = new THREE.Object3D();
this._object3D = new THREE.Object3D();
this._object3D.onBeforeRender = () => {
};
this._isLoaded = false;
this.requestTileAsync();
@ -34,12 +39,12 @@ export default class Tile {
}
// 经纬度范围转瓦片范围
_tileBounds(lnglatBound) {
const ne = this.layer.scene.project([ lnglatBound.getNorthWest().lng, lnglatBound.getNorthEast().lat ]);
const sw = this.layer.scene.project([ lnglatBound.getSouthEast().lng, lnglatBound.getSouthWest().lat ]);
const ne = this.layer.scene.project([ lnglatBound.getNorthEast().lng, lnglatBound.getNorthEast().lat ]);
const sw = this.layer.scene.project([ lnglatBound.getSouthWest().lng, lnglatBound.getSouthWest().lat ]);
return toBounds(sw, ne);
}
getMesh() {
return this.Object3D;
return this._object3D;
}
@ -60,6 +65,8 @@ export default class Tile {
const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
}
_preRender() {
}
destroy() {
if (this._object3D && this._object3D.children) {
let child;

View File

@ -1,5 +1,7 @@
import Layer from '../../core/layer';
import source from '../../core/source';
import * as THREE from '../../core/three';
import Util from '../../util';
import TileCache from './tileCache';
import { throttle } from '@antv/util';
import { toLngLat } from '@antv/geo-coord';
@ -12,16 +14,28 @@ export default class TileLayer extends Layer {
this._tiles = new THREE.Object3D();
this._tileKeys = [];
this.tileList = [];
}
source(url) {
source(url, cfg = {}) {
this.url = url;
this.sourceCfg = cfg;
return this;
}
tileSource(data) {
super.source(data, this.sourceCfg);
if (data instanceof source) {
return data;
}
this.sourceCfg.data = data;
this.sourceCfg.mapType = this.scene.mapType;
this.sourceCfg.zoom = this.scene.getZoom();
return new source(this.sourceCfg);
}
render() {
this._initControllers();
this._initMapEvent();
this._initAttrs();
this.draw();
return this;
}
draw() {
this._object3D.add(this._tiles);
@ -29,6 +43,44 @@ export default class TileLayer extends Layer {
}
drawTile() {
}
_mapping(source) {
const attrs = this.get('attrs');
const mappedData = [];
// const data = this.layerSource.propertiesData;
const data = source.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
newRecord.id = data[i]._id;
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
const attr = attrs[k];
const names = attr.names;
const values = this._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
newRecord[name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
newRecord[names[0]] = values.length === 1 ? values[0] : values;
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
}
// 通过透明度过滤数据
if (attrs.hasOwnProperty('filter')) {
mappedData.forEach(item => {
item.filter === false && (item.color[3] = 0);
});
}
return mappedData;
}
zoomchange(ev) {
super.zoomchange(ev);
@ -56,8 +108,9 @@ export default class TileLayer extends Layer {
// console.log(NW.lng, NW.lat, SE.lng, SE.lat, NWPonint, SEPonint);
let updateTileList = [];
this.tileList = [];
const halfx = Math.floor((maxXY.x - minXY.x) / 2) + 1;
const halfy = Math.floor((maxXY.y - minXY.y) / 2) + 1;
const halfx = 1;
const halfy = 1;
if (!(centerPoint.x > NWPoint.x && centerPoint.x < SEPoint.x)) { // 地图循环的问题
for (let i = 0; i < minXY.x; i++) {
for (let j = Math.min(0, minXY.y - halfy); j < Math.max(maxXY.y + halfy, tileCount); j++) {
@ -90,10 +143,11 @@ export default class TileLayer extends Layer {
});
this._removeOutTiles();
}
_updateTileList(updateTileList, x, y, z) {
const key = [ x, y, z ].join('_');
this.tileList.push(key);
if (this._tileKeys.indexOf(key) === -1) {
if (this._tileKeys.indexOf(key) === -1 && updateTileList.indexOf(key) === -1) {
updateTileList.push(key);
}
}
@ -101,14 +155,21 @@ export default class TileLayer extends Layer {
let tile = this._tileCache.getTile(key);
if (!tile) {
tile = this._createTile(key, layer);
const mesh = tile.getMesh();
mesh.name = key;
this._tileCache.setTile(tile, key);
tile.on('tileLoaded', () => {
if (this.tileList.indexOf(key) === -1) {
return;
}
const mesh = tile.getMesh();
mesh.name = key;
this._tileCache.setTile(tile, key);
this._tileKeys.push(key);
mesh.children.length !== 0 && this._tiles.add(tile.getMesh());
this.scene._engine.update();
});
} else {
this._tiles.add(tile.getMesh());
this._tileKeys.push(key);
// this.scene._engine.update();
}
this._tiles.add(tile.getMesh());
this._tileKeys.push(key);
}
// 移除视野外的tile
_removeOutTiles() {
@ -116,6 +177,8 @@ export default class TileLayer extends Layer {
const tile = this._tiles.children[i];
const key = tile.name;
if (this.tileList.indexOf(key) === -1) {
const tileObj = this._tileCache.getTile(key);
tileObj && tileObj._abortRequest();
this._tiles.remove(tile);
}
this._tileKeys = [].concat(this.tileList);

View File

@ -0,0 +1,141 @@
import Tile from './tile';
import { getArrayBuffer } from '../../util/ajax';
import PBF from 'pbf';
import * as VectorParser from '@mapbox/vector-tile';
import * as THREE from '../../core/three';
import MaskMaterial from '../../geom/material/tile/maskMaterial';
import { LineBuffer } from '../../geom/buffer/index';
import DrawLine from '../../layer/render/line/drawMeshLine';
export default class VectorTile extends Tile {
requestTileAsync() {
// Making this asynchronous really speeds up the LOD framerate
setTimeout(() => {
if (!this._mesh) {
// this._mesh = this._createMesh();
this._requestTile();
}
}, 0);
}
_requestTile() {
const urlParams = {
x: this._tile[0],
y: this._tile[1],
z: this._tile[2]
};
const url = this._getTileURL(urlParams);
this.xhrRequest = getArrayBuffer({ url }, (err, data) => {
if (err) {
this._noData = true;
return;
}
this._isLoaded = true;
this._parserData(data.data);
});
}
_creatSource(data) {
this.source = this.layer.tileSource(data);
}
_parserData(data) {
const tile = new VectorParser.VectorTile(new PBF(data));
// CHN_Cities_L CHN_Cities CHN_L
const layerName = 'county4326';
const features = [];
const vectorLayer = tile.layers[layerName];
for (let i = 0; i < vectorLayer.length; i++) {
const feature = vectorLayer.feature(i);
features.push(feature.toGeoJSON(this._tile[0], this._tile[1], this._tile[2]));
}
const geodata = {
type: 'FeatureCollection',
features
};
this._creatSource(geodata);
this._createMesh();
}
_createMesh() {
this.layerData = this.layer._mapping(this.source);
const style = this.layer.get('styleOptions');
const buffer = new LineBuffer({
layerData: this.layerData,
style,
shapeType: 'line'
});
const animateOptions = this.layer.get('animateOptions');
const activeOption = this.layer.get('activedOptions');
const layerCfg = {
zoom: this.layer.scene.getZoom(),
style,
animateOptions,
activeOption
};
this.mesh = new DrawLine(buffer.attributes, layerCfg, this.layer);
this.mesh.onBeforeRender = renderer => {
this._renderMask(renderer);
};
this.mesh.onAfterRender = renderer => {
const context = renderer.context;
context.disable(context.STENCIL_TEST);
};
this._object3D.add(this.mesh);
this.emit('tileLoaded');
return this._object3D;
}
_renderMask(renderer) {
const maskScene = new THREE.Scene();
this.maskScene = maskScene;
const tileMesh = this._tileMaskMesh();
maskScene.add(tileMesh);
const context = renderer.context;
renderer.autoClear = false;
renderer.clearDepth();
context.enable(context.STENCIL_TEST);
context.stencilOp(context.REPLACE, context.REPLACE, context.REPLACE);
context.stencilFunc(context.ALWAYS, 1, 0xffffffff);
context.clearStencil(0);
context.clear(context.STENCIL_BUFFER_BIT);
context.colorMask(false, false, false, false);
// config the stencil buffer to collect data for testing
this.layer.scene._engine.renderScene(maskScene);
context.colorMask(true, true, true, true);
context.depthMask(true);
renderer.clearDepth();
// only render where stencil is set to 1
context.stencilFunc(context.EQUAL, 1, 0xffffffff); // draw if == 1
context.stencilOp(context.KEEP, context.KEEP, context.KEEP);
}
_tileMaskMesh() {
const tilebound = this._tileBounds;
const bl = [ tilebound.getBottomLeft().x, tilebound.getBottomLeft().y, 0 ];
const br = [ tilebound.getBottomRight().x, tilebound.getBottomRight().y, 0 ];
const tl = [ tilebound.getTopLeft().x, tilebound.getTopLeft().y, 0 ];
const tr = [ tilebound.getTopRight().x, tilebound.getTopRight().y, 0 ];
const positions = [ ...bl, ...tr, ...br, ...bl, ...tl, ...tr ];
const geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
const maskMaterial = new MaskMaterial();
const maskMesh = new THREE.Mesh(geometry, maskMaterial);
return maskMesh;
}
_abortRequest() {
if (!this.xhrRequest) {
return;
}
this.xhrRequest.abort();
}
destroy() {
this.mesh.destroy();
// if (this.maskScene) {
// this.maskScene.children[0].geometry = null;
// this.maskScene.children[0].material.dispose();
// this.maskScene.children[0].material = null;
// }
}
}

View File

@ -48,10 +48,10 @@ export default class LRUCache {
delete(key) {
const value = this._cache[key];
this.destroy(value);
if (value) {
this._deleteCache(key);
this._deleteOrder(key);
this.destroy(value);
}
}