diff --git a/demos/01_point_circle.html b/demos/01_point_circle.html index f63a295ae3..88efe97d33 100644 --- a/demos/01_point_circle.html +++ b/demos/01_point_circle.html @@ -66,7 +66,7 @@ .color('point_count',["#002466","#105CB3","#2894E0","#CFF6FF","#FFF5B8","#FFAB5C","#F27049","#730D1C"]) .style({ stroke: 'rgb(255,255,255)', - strokeWidth: 1, + strokeWidth: 2, opacity: 1 }) .render(); @@ -76,7 +76,7 @@ }) .source(circleLayer.layerSource) .shape('point_count', 'text') - .active(true) + .active(false) .filter('point_count',(p)=>{ return p > 50 }) @@ -84,10 +84,10 @@ .color('#fff') .style({ stroke: '#999', - strokeWidth: 0, + strokeWidth: 1, opacity: 1.0 }) - .render(); + .render(); console.log(layerText); }); diff --git a/demos/06_text.html b/demos/06_text.html index 79f636e663..3fd2bc4f61 100644 --- a/demos/06_text.html +++ b/demos/06_text.html @@ -29,8 +29,7 @@ }); window.scene = scene; scene.on('loaded', () => { - $.get('./data/provincePoint.geojson', data => { - // data.features = data.features.slice(0,1); + $.get('./data/provincePoint.json', data => { scene.PointLayer({ zIndex: 2 }) diff --git a/demos/assets/color-hash.js b/demos/assets/color-hash.js new file mode 100644 index 0000000000..ce0dca5ef7 --- /dev/null +++ b/demos/assets/color-hash.js @@ -0,0 +1,173 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ColorHash = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o MAX_SAFE_INTEGER) { + hash = parseInt(hash / seed2); + } + hash = hash * seed + str.charCodeAt(i); + } + return hash; + }; + + module.exports = BKDRHash; + + },{}],2:[function(require,module,exports){ + var BKDRHash = require('./bkdr-hash'); + + /** + * Convert RGB Array to HEX + * + * @param {Array} RGBArray - [R, G, B] + * @returns {String} 6 digits hex starting with # + */ + var RGB2HEX = function(RGBArray) { + var hex = '#'; + RGBArray.forEach(function(value) { + if (value < 16) { + hex += 0; + } + hex += value.toString(16); + }); + return hex; + }; + + /** + * Convert HSL to RGB + * + * @see {@link http://zh.wikipedia.org/wiki/HSL和HSV色彩空间} for further information. + * @param {Number} H Hue ∈ [0, 360) + * @param {Number} S Saturation ∈ [0, 1] + * @param {Number} L Lightness ∈ [0, 1] + * @returns {Array} R, G, B ∈ [0, 255] + */ + var HSL2RGB = function(H, S, L) { + H /= 360; + + var q = L < 0.5 ? L * (1 + S) : L + S - L * S; + var p = 2 * L - q; + + return [H + 1/3, H, H - 1/3].map(function(color) { + if(color < 0) { + color++; + } + if(color > 1) { + color--; + } + if(color < 1/6) { + color = p + (q - p) * 6 * color; + } else if(color < 0.5) { + color = q; + } else if(color < 2/3) { + color = p + (q - p) * 6 * (2/3 - color); + } else { + color = p; + } + return Math.round(color * 255); + }); + }; + + function isArray(o) { + return Object.prototype.toString.call(o) === '[object Array]'; + } + + /** + * Color Hash Class + * + * @class + */ + var ColorHash = function(options) { + options = options || {}; + + var LS = [options.lightness, options.saturation].map(function(param) { + param = param || [0.35, 0.5, 0.65]; // note that 3 is a prime + return isArray(param) ? param.concat() : [param]; + }); + + this.L = LS[0]; + this.S = LS[1]; + + if (typeof options.hue === 'number') { + options.hue = {min: options.hue, max: options.hue}; + } + if (typeof options.hue === 'object' && !isArray(options.hue)) { + options.hue = [options.hue]; + } + if (typeof options.hue === 'undefined') { + options.hue = []; + } + this.hueRanges = options.hue.map(function (range) { + return { + min: typeof range.min === 'undefined' ? 0 : range.min, + max: typeof range.max === 'undefined' ? 360: range.max + }; + }); + + this.hash = options.hash || BKDRHash; + }; + + /** + * Returns the hash in [h, s, l]. + * Note that H ∈ [0, 360); S ∈ [0, 1]; L ∈ [0, 1]; + * + * @param {String} str string to hash + * @returns {Array} [h, s, l] + */ + ColorHash.prototype.hsl = function(str) { + var H, S, L; + var hash = this.hash(str); + + if (this.hueRanges.length) { + var range = this.hueRanges[hash % this.hueRanges.length]; + var hueResolution = 727; // note that 727 is a prime + H = ((hash / this.hueRanges.length) % hueResolution) * (range.max - range.min) / hueResolution + range.min; + } else { + H = hash % 359; // note that 359 is a prime + } + hash = parseInt(hash / 360); + S = this.S[hash % this.S.length]; + hash = parseInt(hash / this.S.length); + L = this.L[hash % this.L.length]; + + return [H, S, L]; + }; + + /** + * Returns the hash in [r, g, b]. + * Note that R, G, B ∈ [0, 255] + * + * @param {String} str string to hash + * @returns {Array} [r, g, b] + */ + ColorHash.prototype.rgb = function(str) { + var hsl = this.hsl(str); + return HSL2RGB.apply(this, hsl); + }; + + /** + * Returns the hash in hex + * + * @param {String} str string to hash + * @returns {String} hex with # + */ + ColorHash.prototype.hex = function(str) { + var rgb = this.rgb(str); + return RGB2HEX(rgb); + }; + + module.exports = ColorHash; + + },{"./bkdr-hash":1}]},{},[2])(2) + }); \ No newline at end of file diff --git a/demos/taxi.html b/demos/taxi.html index d8ea264443..dc2edc621e 100644 --- a/demos/taxi.html +++ b/demos/taxi.html @@ -39,7 +39,7 @@ //.color('#F08D41') .color('#ff893a') .animate({enable:true}) - .render(); + //.render(); }); $.get('https://gw.alipayobjects.com/os/rmsportal/vmvAxgsEwbpoSWbSYvix.json', data => { buildLayer = scene.PolygonLayer({ diff --git a/demos/tile.html b/demos/tile.html index b3ac0aaae2..cd97a2c849 100644 --- a/demos/tile.html +++ b/demos/tile.html @@ -47,6 +47,7 @@ } ) .source(city) + .active(false) .color('pm2_5_24h',["#FFF5B8","#FFDC7D","#FFAB5C","#F27049","#D42F31","#730D1C"]) .shape('fill') .style({ diff --git a/demos/vectorTile.html b/demos/vectorTile.html new file mode 100644 index 0000000000..720922f3aa --- /dev/null +++ b/demos/vectorTile.html @@ -0,0 +1,83 @@ + + + + + + + + + + + hexagon demo + + + + +
+ + + + + + + + diff --git a/demos/vectorTilepolygon.html b/demos/vectorTilepolygon.html new file mode 100644 index 0000000000..ce4df2bf67 --- /dev/null +++ b/demos/vectorTilepolygon.html @@ -0,0 +1,81 @@ + + + + + + + + + + + hexagon demo + + + + +
+ + + + + + + + + diff --git a/demos/vectorheatMap.html b/demos/vectorheatMap.html new file mode 100644 index 0000000000..4dd387940d --- /dev/null +++ b/demos/vectorheatMap.html @@ -0,0 +1,80 @@ + + + + + + + + + + + hexagon demo + + + + +
+ + + + + + + + diff --git a/package.json b/package.json index 5e4095f1c6..723a1e1cf1 100755 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/core/engine/index.js b/src/core/engine/index.js index 9f04690f43..5a379e0778 100644 --- a/src/core/engine/index.js +++ b/src/core/engine/index.js @@ -10,11 +10,11 @@ export default class Engine extends EventEmitter { this._scene = Scene; this._camera = new Camera(container).camera; this._renderer = new Renderer(container).renderer; - this._world = world; + this._world = world;// 地图场景实例 // for MapBox this.world = new THREE.Group(); this._scene.add(this.world); - this._picking = Picking(this._world, this._renderer, this._camera, this._scene); + this._picking = Picking(this._world, this._renderer, this._camera); this.clock = new THREE.Clock(); this.composerLayers = []; } @@ -24,11 +24,15 @@ export default class Engine extends EventEmitter { }); } update() { + this._renderer.clear(); this._renderer.render(this._scene, this._camera); this._initPostProcessing(); } destroy() { - + } + // 渲染第三方Scene对象 + renderScene(scene) { + this._renderer.render(scene, this._camera); } run() { this.update(); diff --git a/src/core/engine/picking/picking.js b/src/core/engine/picking/picking.js index 3d61d86427..a332d1ea51 100755 --- a/src/core/engine/picking/picking.js +++ b/src/core/engine/picking/picking.js @@ -3,13 +3,10 @@ import * as THREE from '../../three'; let nextId = 1; class Picking { - constructor(world, renderer, camera, scene) { + constructor(world, renderer, camera) { this._world = world; this._renderer = renderer; this._camera = camera; - this._raycaster = new THREE.Raycaster(); - this.scene = scene; - this._raycaster.linePrecision = 10; this._pickingScene = PickingScene; this.world = new THREE.Group(); this._pickingScene.add(this.world); @@ -49,12 +46,11 @@ class Picking { this._height = size.height; this._pickingTexture.setSize(this._width, this._height); this._pixelBuffer = new Uint8Array(4 * this._width * this._height); - this._needUpdate = true; } _update(point) { - const texture = this._pickingTexture; + // this._pickingTexture this._renderer.render(this._pickingScene, this._camera, this._pickingTexture); this.pixelBuffer = new Uint8Array(4); this._renderer.readRenderTargetPixels(texture, point.x, this._height - point.y, 1, 1, this.pixelBuffer); @@ -76,9 +72,9 @@ class Picking { }); } - _updateRender() { - this._renderer.render(this._pickingScene, this._camera, this._pickingTexture); - } + // _updateRender() { + // this._renderer.render(this._pickingScene, this._camera, this._pickingTexture); + // } _pick(point, normalisedPoint, layerId) { this._update(point); @@ -125,8 +121,6 @@ class Picking { this._world._container.removeEventListener(event[0], event[1], false); }); - this._world.off('move', this._onWorldMove); - if (this._pickingScene.children) { // Remove everything else in the layer let child; diff --git a/src/core/engine/renderer.js b/src/core/engine/renderer.js index fd4d64a6d5..4607a8fe3b 100644 --- a/src/core/engine/renderer.js +++ b/src/core/engine/renderer.js @@ -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; diff --git a/src/core/layer.js b/src/core/layer.js index 9523aa21eb..c8714d232d 100644 --- a/src/core/layer.js +++ b/src/core/layer.js @@ -11,6 +11,7 @@ import pickingFragmentShader from '../core/engine/picking/picking_frag.glsl'; import { getInteraction } from '../interaction/index'; import Attr from '../attr/index'; import diff from '../util/diff'; +import { updateObjecteUniform } from '../util/object3d-util'; import Util from '../util'; import Global from '../global'; let id = 1; @@ -97,8 +98,10 @@ export default class Layer extends Base { this._visibleWithZoom(); object.onBeforeRender = () => { // 每次渲染前改变状态 const zoom = this.scene.getZoom(); - object.material.setUniformsValue('u_time', this.scene._engine.clock.getElapsedTime()); - object.material.setUniformsValue('u_zoom', zoom); + updateObjecteUniform(this._object3D, { + u_time: this.scene._engine.clock.getElapsedTime(), + u_zoom: zoom + }); this.preRender(); }; @@ -309,7 +312,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'); @@ -370,17 +373,20 @@ export default class Layer extends Base { setActive(id, color) { this._activeIds = id; - this.layerMesh.material.setUniformsValue('u_activeId', id); if (!Array.isArray(color)) { color = ColorUtil.color2RGBA(color); } - this.layerMesh.material.setUniformsValue('u_activeColor', color); + updateObjecteUniform(this._object3D, { + u_activeColor: color, + u_activeId: id + }); + this.scene._engine.update(); } _addActiveFeature(e) { const { featureId } = e; this._activeIds = featureId; - this.layerMesh.material.setUniformsValue('u_activeId', featureId); + updateObjecteUniform(this._object3D, { u_activeId: featureId }); } @@ -404,7 +410,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(); @@ -487,15 +493,15 @@ export default class Layer extends Base { for (const key in option) { newOption['u_' + key] = option[key]; } - this.layerMesh.material.updateUninform(newOption); - + updateObjecteUniform(this._object3D, 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 +534,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 +555,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; @@ -603,10 +611,9 @@ export default class Layer extends Base { pickmaterial.fragmentShader = pickingFragmentShader; const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial); pickingMesh.name = this.layerId; - pickmaterial.setDefinesvalue(this.type, true); pickingMesh.onBeforeRender = () => { const zoom = this.scene.getZoom(); - pickingMesh.material.setUniformsValue('u_zoom', zoom); + updateObjecteUniform(pickingMesh, { u_zoom: zoom }); }; this._pickingMesh.add(pickingMesh); @@ -618,9 +625,10 @@ export default class Layer extends Base { type = 'mouseleave'; } this._activeIds = featureId; - const feature = this.layerSource.getSelectFeature(featureId); + // TODO 瓦片图层获取选中数据信息 const lnglat = this.scene.containerToLngLat(point2d); - const style = this.layerData[featureId - 1]; + const { feature, style } = this.getSelectFeature(featureId, lnglat); + // const style = this.layerData[featureId - 1]; const target = { featureId, feature, @@ -629,12 +637,20 @@ export default class Layer extends Base { type, lnglat: { lng: lnglat.lng, lat: lnglat.lat } }; - if (featureId >= 0 || this._activeIds !== null) { // 拾取到元素,或者离开元素 + if (featureId >= 0 || this._activeIds >= 0) { // 拾取到元素,或者离开元素 this.emit(type, target); } }); } + getSelectFeature(featureId) { + const feature = this.layerSource.getSelectFeature(featureId); + const style = this.layerData[featureId - 1]; + return { + feature, + style + }; + } /** * 用于过滤数据 * @param {*} object 更新颜色和数据过滤 @@ -668,6 +684,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'); @@ -734,13 +751,16 @@ export default class Layer extends Base { } interactions[type] = interaction; } + styleCfg() { + + } /** * 重置高亮要素 */ _resetStyle() { this._activeIds = null; - this.layerMesh.material.setUniformsValue('u_activeId', 0); + updateObjecteUniform(this._object3D, { u_activeId: 0 }); } /** * 销毁Layer对象 @@ -751,7 +771,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 +797,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; } diff --git a/src/core/scene.js b/src/core/scene.js index 0789516806..50823980fd 100644 --- a/src/core/scene.js +++ b/src/core/scene.js @@ -26,6 +26,7 @@ export default class Scene extends Base { _initEngine(mapContainer) { this._engine = new Engine(mapContainer, this); 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() { diff --git a/src/core/source.js b/src/core/source.js index 765636ea5d..8b8fd97a92 100644 --- a/src/core/source.js +++ b/src/core/source.js @@ -56,7 +56,9 @@ export default class Source extends Base { const { type = 'geojson' } = parser; const data = this.get('data'); this.originData = getParser(type)(data, parser); - this.data = clone(this.originData); + this.data = { + dataArray: clone(this.originData.dataArray) + }; this.data.extent = extent(this.data.dataArray); } /** @@ -150,4 +152,8 @@ export default class Source extends Base { } return featureIndex; } + destroy() { + this.data = null; + this.originData = null; + } } diff --git a/src/geom/buffer/point/normalBuffer.js b/src/geom/buffer/point/normalBuffer.js index 755e7697a3..696cbb6290 100644 --- a/src/geom/buffer/point/normalBuffer.js +++ b/src/geom/buffer/point/normalBuffer.js @@ -13,7 +13,5 @@ export default function NormalBuffer(layerData) { attributes.sizes.push(size); }); - - return attributes; } diff --git a/src/geom/material/heatmapMateial.js b/src/geom/material/heatmapMaterial.js similarity index 100% rename from src/geom/material/heatmapMateial.js rename to src/geom/material/heatmapMaterial.js diff --git a/src/geom/material/pointLineMaterial.js b/src/geom/material/pointLineMaterial.js index ca6242e0f2..c84c5911f5 100644 --- a/src/geom/material/pointLineMaterial.js +++ b/src/geom/material/pointLineMaterial.js @@ -8,7 +8,7 @@ export default class PointLineMaterial extends Material { u_stroke: { value: [ 1.0, 1.0, 1.0, 1.0 ] }, u_strokeWidth: { value: 1.0 }, u_zoom: { value: 10 }, - u_activeId: { value: -1 }, + u_activeId: { value: 0 }, u_activeColor: { value: [ 1.0, 0, 0, 1.0 ] } } diff --git a/src/geom/material/tile/maskMaterial.js b/src/geom/material/tile/maskMaterial.js new file mode 100644 index 0000000000..36ddd9e098 --- /dev/null +++ b/src/geom/material/tile/maskMaterial.js @@ -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; + } +} diff --git a/src/geom/shader/index.js b/src/geom/shader/index.js index 23e47bf8df..e6ff0c5c79 100644 --- a/src/geom/shader/index.js +++ b/src/geom/shader/index.js @@ -41,6 +41,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'; @@ -60,5 +65,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 }); } diff --git a/src/geom/shader/meshline_vert.glsl b/src/geom/shader/meshline_vert.glsl index 2a5a87aefe..2bf13b88ea 100644 --- a/src/geom/shader/meshline_vert.glsl +++ b/src/geom/shader/meshline_vert.glsl @@ -6,7 +6,7 @@ attribute float a_dash_array; uniform float u_zoom; uniform float u_time : 0; -uniform float u_activeId : 1; +uniform float u_activeId : 0; uniform vec4 u_activeColor : [ 1.0, 0, 0, 1.0 ]; varying float v_time; diff --git a/src/geom/shader/point_frag.glsl b/src/geom/shader/point_frag.glsl index f14cbd46c2..30ff5a6623 100644 --- a/src/geom/shader/point_frag.glsl +++ b/src/geom/shader/point_frag.glsl @@ -20,11 +20,11 @@ const vec3 halo = vec3( 1.0 ); void main() { // 纹理坐标 #ifdef TEXCOORD_0 - vec2 pos = v_uv + gl_PointCoord / 512.0 * 64.0; - pos.y = 1.0 - pos.y; - vec4 textureColor = texture2D(u_texture, pos); - gl_FragColor =textureColor; - return; + vec2 pos = v_uv + gl_PointCoord / 512.0 * 64.0; + pos.y = 1.0 - pos.y; + vec4 textureColor = texture2D(u_texture, pos); + gl_FragColor =textureColor; + return; #endif if(v_color.a == 0.) discard; diff --git a/src/geom/shader/point_meshLine_vert.glsl b/src/geom/shader/point_meshLine_vert.glsl index d4773d23e6..023a75f17d 100644 --- a/src/geom/shader/point_meshLine_vert.glsl +++ b/src/geom/shader/point_meshLine_vert.glsl @@ -21,10 +21,12 @@ void main() { #endif v_color = u_stroke; v_color.a *= u_strokeOpacity; + + v_pickingId = pickingId; if(v_pickingId == u_activeId) { v_color = u_activeColor; } - v_pickingId = pickingId; + //vec3 pointPos = newposition.xyz + vec3(normal * u_strokeWidth * pow(2.0,20.0-u_zoom) / 2.0 * a_miter); vec3 pointPos = newposition.xyz + vec3(normal * u_strokeWidth * scale / 2.0 * a_miter); gl_Position = matModelViewProjection * vec4(pointPos, 1.0); diff --git a/src/geom/shader/point_vert.glsl b/src/geom/shader/point_vert.glsl index d394e556fc..874376fe9a 100644 --- a/src/geom/shader/point_vert.glsl +++ b/src/geom/shader/point_vert.glsl @@ -16,7 +16,7 @@ void main() { mat4 matModelViewProjection = projectionMatrix * modelViewMatrix; v_color = a_color; v_color.a *= u_opacity; - if(pickingId == u_activeId) { + if(pickingId == u_activeId) { v_color = u_activeColor; } gl_Position = matModelViewProjection * vec4(position, 1.0); diff --git a/src/geom/shader/tile/mask_quard_frag.glsl b/src/geom/shader/tile/mask_quard_frag.glsl new file mode 100644 index 0000000000..6b4ceede8f --- /dev/null +++ b/src/geom/shader/tile/mask_quard_frag.glsl @@ -0,0 +1,4 @@ + precision highp float; + void main() { + gl_FragColor = vec4(1.0); +} \ No newline at end of file diff --git a/src/geom/shader/tile/mask_quard_vert.glsl b/src/geom/shader/tile/mask_quard_vert.glsl new file mode 100644 index 0000000000..b8988414c9 --- /dev/null +++ b/src/geom/shader/tile/mask_quard_vert.glsl @@ -0,0 +1,6 @@ +precision highp float; +void main(){ + mat4 matModelViewProjection=projectionMatrix*modelViewMatrix; + gl_Position = matModelViewProjection * vec4(position, 1.0); + +} \ No newline at end of file diff --git a/src/geom/shape/line.js b/src/geom/shape/line.js index c3849a8f8d..5ac514067b 100644 --- a/src/geom/shape/line.js +++ b/src/geom/shape/line.js @@ -65,8 +65,11 @@ export function defaultLine(geo, index) { return { positions, indexes: indexArray }; } // mesh line + export function Line(path, props, positionsIndex, lengthPerDashSegment = 200) { - if (path.length === 1) path = path[0];// 面坐标转线坐标 + if (Array.isArray(path[0][0])) { + path = path[0]; // 面坐标转线坐标 + } const positions = []; const pickingIds = []; const normal = []; @@ -78,12 +81,13 @@ export function Line(path, props, positionsIndex, lengthPerDashSegment = 200) { let attrDistance = []; const sizes = []; - const { size, color, id } = props; + let { size, color, id } = props; + !Array.isArray(size) && (size = [ size ]); attrPos.forEach((point, pointIndex) => { colors.push(...color); pickingIds.push(id); sizes.push(size[0]); - point[2] = size[1]; + point[2] = size[1] || 0; positions.push(...point); if (pointIndex === 0 || pointIndex === 1) { @@ -109,7 +113,6 @@ export function Line(path, props, positionsIndex, lengthPerDashSegment = 200) { attrDistance = attrDistance.map(d => { return d / totalLength; }); - return { positions, normal, diff --git a/src/global.js b/src/global.js index f33666f102..b6f5e5341e 100644 --- a/src/global.js +++ b/src/global.js @@ -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, diff --git a/src/interaction/hash.js b/src/interaction/hash.js index 04b1bcb86d..abe5cc48a9 100644 --- a/src/interaction/hash.js +++ b/src/interaction/hash.js @@ -13,7 +13,7 @@ export default class Hash extends Interaction { this._updateHash(); } reset() { - this.layer._resetStyle(); + // this.layer._resetStyle(); } _getHashString() { const center = this.layer.getCenter(), diff --git a/src/layer/heatmapLayer.js b/src/layer/heatmapLayer.js index e47b5656fd..b75c270c1f 100644 --- a/src/layer/heatmapLayer.js +++ b/src/layer/heatmapLayer.js @@ -1,9 +1,5 @@ import Layer from '../core/layer'; -import gridBuffer from '../geom/buffer/heatmap/grid'; -import DrawGrid from './render/heatmap/gird'; -import DrawHexagon from './render/heatmap/hexagon'; -import { drawHeatmap } from './render/heatmap/heatmap'; -import hexagonBuffer from '../geom/buffer/heatmap/hexagon'; +import { getRender } from './render/'; export default class HeatMapLayer extends Layer { shape(type) { @@ -12,43 +8,6 @@ export default class HeatMapLayer extends Layer { } draw() { this.type = 'heatmap'; - switch (this.shapeType) { - case 'grid' : - this._drawGrid(); - break; - case 'hexagon' : - this._drawHexagon(); - break; - default: - drawHeatmap(this); - } + this.add(getRender('heatmap', this.shapeType || 'heatmap')(this.layerData, this, this.layerSource)); } - _drawHexagon() { - const style = this.get('styleOptions'); - const activeOption = this.get('activedOptions'); - const { radius } = this.layerSource.data; - this._buffer = new hexagonBuffer(this.layerData); - const config = { - ...style, - radius, - activeColor: activeOption.fill - }; - const Mesh = new DrawHexagon(this._buffer, config); - this.add(Mesh); - } - _drawGrid() { - const style = this.get('styleOptions'); - const activeOption = this.get('activedOptions'); - const { xOffset, yOffset } = this.layerSource.data; - this._buffer = new gridBuffer(this.layerData); - const config = { - ...style, - xOffset, - yOffset, - activeColor: activeOption.fill - }; - const girdMesh = new DrawGrid(this._buffer, config); - this.add(girdMesh); - } - } diff --git a/src/layer/index.js b/src/layer/index.js index 8b33d8534e..04c572c984 100644 --- a/src/layer/index.js +++ b/src/layer/index.js @@ -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 }; diff --git a/src/layer/lineLayer.js b/src/layer/lineLayer.js index 4a2046686e..489c1ac999 100644 --- a/src/layer/lineLayer.js +++ b/src/layer/lineLayer.js @@ -1,7 +1,5 @@ import Layer from '../core/layer'; -import DrawLine from './render/line/drawMeshLine'; -import DrawArc from './render/line/drawArc'; -import { LineBuffer } from '../geom/buffer/index'; +import { getRender } from './render/'; export default class LineLayer extends Layer { shape(type) { this.shapeType = type; @@ -19,27 +17,7 @@ export default class LineLayer extends Layer { } } draw() { - this.type = 'polyline'; - const layerData = this.layerData; - const style = this.get('styleOptions'); - const animateOptions = this.get('animateOptions'); - const activeOption = this.get('activedOptions'); - const layerCfg = { - zoom: this.scene.getZoom(), - style, - animateOptions, - activeOption - }; - const buffer = (this._buffer = new LineBuffer({ - layerData, - shapeType: this.shapeType, - style - })); - const { attributes } = buffer; - if (this.shapeType === 'arc') { - DrawArc(attributes, layerCfg, this); - } else { - DrawLine(attributes, layerCfg, this); - } + this.type = 'line'; + this.add(getRender('line', this.shapeType || 'line')(this.layerData, this, this.layerSource)); } } diff --git a/src/layer/pointLayer.js b/src/layer/pointLayer.js index 1e6efc7ae3..07d915c127 100644 --- a/src/layer/pointLayer.js +++ b/src/layer/pointLayer.js @@ -1,10 +1,6 @@ import Layer from '../core/layer'; -import * as drawPoint from '../layer/render/point'; -import TextBuffer from '../geom/buffer/point/text'; -import DrawText from './render/point/drawText'; import Global from '../global'; -// import PointBuffer from '../geom/buffer/point'; -import * as PointBuffer from '../geom/buffer/point/index'; +import { getRender } from './render/'; const { pointShape } = Global; /** * point shape 2d circle, traingle text,image @@ -16,67 +12,9 @@ const { pointShape } = Global; export default class PointLayer extends Layer { draw() { this.type = 'point'; - const { stroke, fill } = this.get('styleOptions'); - const style = this.get('styleOptions'); - const activeOption = this.get('activedOptions'); - const config = { - ...style, - activeColor: activeOption.fill - }; - const pointShapeType = this._getShape(); - this.shapeType = pointShapeType; - switch (pointShapeType) { - case 'fill': { // 填充图形 - if (fill !== 'none') { - // 是否填充 - const attributes = PointBuffer.FillBuffer(this.layerData, style); - const meshfill = drawPoint.DrawFill(attributes, config); - this.add(meshfill); - } - if (stroke !== 'none') { - // 是否绘制边界 - const lineAttribute = PointBuffer.StrokeBuffer(this.layerData, style); - const meshStroke = drawPoint.DrawStroke(lineAttribute, config); - this.add(meshStroke, 'line'); - } - break; - } - case 'image': { // 绘制图片标注 - const imageAttribute = PointBuffer.ImageBuffer(this.layerData, { - imagePos: this.scene.image.imagePos - }); - const imageMesh = drawPoint.DrawImage(imageAttribute, { - ...style, - texture: this.scene.image.texture - }); - this.add(imageMesh); - break; - } - case 'normal': { // 原生点 - const normalAttribute = PointBuffer.NormalBuffer(this.layerData, style); - const normalPointMesh = drawPoint.DrawNormal(normalAttribute, config); - this.add(normalPointMesh); - break; - } - case 'text': { // 原生点 - const { width, height } = this.scene.getSize(); - const textCfg = { - ...style, - width, - height, - activeColor: activeOption.fill - }; - const buffer = new TextBuffer( - this.layerData, - this.scene.fontAtlasManager - ); - const mesh = new DrawText(buffer, textCfg); - this.add(mesh); - break; - } - default: - return null; - } + this.shapeType = this._getShape(); + const mesh = getRender(this.type, this.shapeType)(this.layerData, this, this.layerSource); + this.add(mesh); } _getShape() { diff --git a/src/layer/polygonLayer.js b/src/layer/polygonLayer.js index e25c052038..32779aa2c2 100644 --- a/src/layer/polygonLayer.js +++ b/src/layer/polygonLayer.js @@ -1,6 +1,5 @@ import Layer from '../core/layer'; -import * as drawPolygon from './render/polygon'; -import PolygonBuffer from '../geom/buffer/polygon'; +import { getRender } from './render/'; export default class PolygonLayer extends Layer { shape(type) { this.shape = type; @@ -9,33 +8,13 @@ export default class PolygonLayer extends Layer { draw() { this.init(); this.type = 'polygon'; - this._buffer = new PolygonBuffer({ - shape: this.shape, - layerData: this.layerData - }); - this.add(this._getLayerRender()); + const animateOptions = this.get('animateOptions'); + if (animateOptions.enable) { + this.shape = 'animate'; + } + this.add(getRender(this.type, this.shape)(this.layerData, this)); } update() { this.updateFilter(this.layerMesh); } - _getLayerRender() { - const animateOptions = this.get('animateOptions'); - const { attributes } = this._buffer; - const style = this.get('styleOptions'); - const activeOption = this.get('activedOptions'); - const config = { - ...style, - activeColor: activeOption.fill - }; - if (this.shape === 'line') { - return drawPolygon.DrawLine(attributes, style); - } else if (animateOptions.enable) { - const { near, far } = this.map.getCameraState(); - this.scene.startAnimate(); - return drawPolygon.DrawAnimate(attributes, { ...style, near, far }); - } - return drawPolygon.DrawFill(attributes, config); - - } - } diff --git a/src/layer/render/factory.js b/src/layer/render/factory.js new file mode 100644 index 0000000000..9f6d53b333 --- /dev/null +++ b/src/layer/render/factory.js @@ -0,0 +1,12 @@ +export const Render_MAP = {}; +export const getRender = (layerType, shapeType) => { + return Render_MAP[layerType.toLowerCase()] && Render_MAP[layerType.toLowerCase()][shapeType.toLowerCase()]; +}; +export const registerRender = (layerType, shapeType, render) => { + if (getRender(layerType, shapeType)) { + throw new Error(`Render shapeType '${shapeType}' existed.`); + } + // 存储到 map 中 + if (!Render_MAP[layerType.toLowerCase()]) Render_MAP[layerType.toLowerCase()] = {}; + Render_MAP[layerType.toLowerCase()][shapeType.toLowerCase()] = render; +}; diff --git a/src/layer/render/heatmap/gird.js b/src/layer/render/heatmap/gird.js index aa83cedc92..04b31b40bf 100644 --- a/src/layer/render/heatmap/gird.js +++ b/src/layer/render/heatmap/gird.js @@ -1,7 +1,11 @@ import * as THREE from '../../../core/three'; +import gridBuffer from '../../../geom/buffer/heatmap/grid'; import GridMaterial from '../../../geom/material/grid'; -export default function DrawGrid(attributes, style) { - const { opacity, xOffset, yOffset, coverage, activeColor } = style; +export default function DrawGrid(layerdata, layer, source) { + const { opacity, coverage } = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const { xOffset, yOffset } = source.data; + const attributes = new gridBuffer(layerdata); const geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miter, 2)); @@ -12,7 +16,7 @@ export default function DrawGrid(attributes, style) { u_xOffset: xOffset, u_yOffset: yOffset, u_coverage: coverage, - u_activeColor: activeColor + u_activeColor: activeOption.fill }, { SHAPE: false }); diff --git a/src/layer/render/heatmap/heatmap.js b/src/layer/render/heatmap/heatmap.js index 887f41f954..5ce24e1f68 100644 --- a/src/layer/render/heatmap/heatmap.js +++ b/src/layer/render/heatmap/heatmap.js @@ -1,24 +1,23 @@ 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'; import EffectComposer from '../../../core/engine/effect-composer'; import * as THREE from '../../../core/three'; -export function drawHeatmap(layer) { +export default function DrawHeatmap(layerdata, layer) { const colors = layer.get('styleOptions').rampColors; layer.rampColors = createColorRamp(colors); - const heatmap = new heatmapPass(layer); + const heatmap = new heatmapPass(layerdata, layer); const copy = new copyPass(layer); copy.renderToScreen = true; const composer = new EffectComposer(layer.scene._engine._renderer, layer.scene._container); composer.addPass(heatmap); composer.addPass(copy); - layer.add(composer); layer.scene._engine.update(); layer._updateStyle = style => { if (style.rampColors) { @@ -31,13 +30,14 @@ export function drawHeatmap(layer) { heatmap.scene.children[0].material.updateUninform(newOption); copy.scene.children[0].material.updateUninform(newOption); }; + return composer; } -function heatmapPass(layer) { +function heatmapPass(layerdata, layer) { const scene = new THREE.Scene(); const style = layer.get('styleOptions'); - const data = layer.layerData; + const data = layerdata; const camera = layer.scene._engine._camera; // get attributes data const buffer = new HeatmapBuffer({ diff --git a/src/layer/render/heatmap/hexagon.js b/src/layer/render/heatmap/hexagon.js index 639deaa645..8c2c42ee14 100644 --- a/src/layer/render/heatmap/hexagon.js +++ b/src/layer/render/heatmap/hexagon.js @@ -1,7 +1,12 @@ import * as THREE from '../../../core/three'; +import hexagonBuffer from '../../../geom/buffer/heatmap/hexagon'; import GridMaterial from '../../../geom/material/hexagon'; -export default function DrawHexagon(attributes, style) { - const { opacity, radius, angle, coverage, activeColor } = style; +export default function DrawHexagon(layerdata, layer, source) { + const style = layer.get('styleOptions'); + const { fill } = layer.get('activedOptions'); + const { radius } = source.data; + const attributes = new hexagonBuffer(layerdata); + const { opacity, angle, coverage } = style; const geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('miter', new THREE.Float32BufferAttribute(attributes.miter, 2)); @@ -12,7 +17,7 @@ export default function DrawHexagon(attributes, style) { u_radius: radius, u_angle: angle / 180 * Math.PI, u_coverage: coverage, - u_activeColor: activeColor + u_activeColor: fill }, { SHAPE: false }); diff --git a/src/layer/render/index.js b/src/layer/render/index.js new file mode 100644 index 0000000000..16a2d48273 --- /dev/null +++ b/src/layer/render/index.js @@ -0,0 +1,48 @@ +import { registerRender, getRender } from './factory'; +// polygon +import DrawFill from './polygon/drawFill'; +import DrawLine from './polygon/drawLine'; +import DrawAnimate from './polygon/drawAnimate'; + +registerRender('polygon', 'fill', DrawFill); +registerRender('polygon', 'extrude', DrawFill); +registerRender('polygon', 'line', DrawLine); +registerRender('polygon', 'animate', DrawAnimate); + +// line +import DrawMeshLine from './line/drawMeshLine'; +import DrawArcLine from './line/drawArc'; + +registerRender('line', 'line', DrawMeshLine); +registerRender('line', 'arc', DrawArcLine); + +// point +import DrawPointFill from './point/drawFill'; +import DrawPointImage from './point/drawImage'; +import DrawPointNormal from './point/drawNormal'; +import DrawPointStroke from './point/drawStroke'; +import DrawPointText from './point/drawText'; + +registerRender('point', 'fill', DrawPointFill); +registerRender('point', 'image', DrawPointImage); +registerRender('point', 'normal', DrawPointNormal); +registerRender('point', 'stroke', DrawPointStroke); +registerRender('point', 'text', DrawPointText); + +// heatmap + +import DrawGrid from './heatmap/gird'; +import DrawHeatmap from './heatmap/heatmap'; +import DrawHexagon from './heatmap/hexagon'; + +registerRender('heatmap', 'grid', DrawGrid); +registerRender('heatmap', 'heatmap', DrawHeatmap); +registerRender('heatmap', 'hexagon', DrawHexagon); + +// image + +import DrawImage from './image/drawImage'; + +registerRender('image', 'image', DrawImage); + +export { getRender }; diff --git a/src/layer/render/line/drawArc.js b/src/layer/render/line/drawArc.js index 5324e0c821..a043a0b2a6 100644 --- a/src/layer/render/line/drawArc.js +++ b/src/layer/render/line/drawArc.js @@ -1,8 +1,14 @@ import * as THREE from '../../../core/three'; +import { LineBuffer } from '../../../geom/buffer/index'; import { ArcLineMaterial } from '../../../geom/material/lineMaterial'; -export default function DrawArcLine(attributes, cfg, layer) { - const { style, activeOption } = cfg; - const { opacity, zoom } = style; +export default function DrawArcLine(layerdata, layer) { + const style = this.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const { attributes } = new LineBuffer({ + layerdata, + shapeType: 'arc', + style + }); const geometry = new THREE.BufferGeometry(); geometry.setIndex(attributes.indexArray); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.positions, 3)); @@ -10,12 +16,12 @@ export default function DrawArcLine(attributes, cfg, layer) { geometry.addAttribute('a_instance', new THREE.Float32BufferAttribute(attributes.instances, 4)); geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.sizes, 1)); const lineMaterial = new ArcLineMaterial({ - u_opacity: opacity, - u_zoom: zoom, + u_opacity: style.opacity, + u_zoom: layer.scene.getZoom(), activeColor: activeOption.fill }, { SHAPE: false }); const arcMesh = new THREE.Mesh(geometry, lineMaterial); - layer.add(arcMesh); + return arcMesh; } diff --git a/src/layer/render/line/drawMeshLine.js b/src/layer/render/line/drawMeshLine.js index 5709c5ae77..1b955faef7 100644 --- a/src/layer/render/line/drawMeshLine.js +++ b/src/layer/render/line/drawMeshLine.js @@ -1,7 +1,16 @@ import * as THREE from '../../../core/three'; +import { LineBuffer } from '../../../geom/buffer/index'; import { MeshLineMaterial } from '../../../geom/material/lineMaterial'; -export default function DrawLine(attributes, cfg, layer) { - const { style, animateOptions, activeOption, zoom } = cfg; +export default function DrawLine(layerData, layer) { + + const style = layer.get('styleOptions'); + const animateOptions = layer.get('animateOptions'); + const activeOption = layer.get('activedOptions'); + const { attributes } = new LineBuffer({ + layerData, + shapeType: 'line', + style + }); const geometry = new THREE.BufferGeometry(); geometry.setIndex(attributes.indexArray); geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); @@ -15,7 +24,7 @@ export default function DrawLine(attributes, cfg, layer) { const lineMaterial = new MeshLineMaterial({ u_opacity: style.opacity, - u_zoom: zoom, + u_zoom: layer.scene.getZoom(), u_time: 0, u_dash_offset: style.dashOffset, u_dash_ratio: style.dashRatio, @@ -44,5 +53,5 @@ export default function DrawLine(attributes, cfg, layer) { }); lineMaterial.setDefinesvalue('ANIMATE', true); } - layer.add(lineMesh); + return lineMesh; } diff --git a/src/layer/render/point/drawFill.js b/src/layer/render/point/drawFill.js index 008d47dd35..a6ec26dc49 100644 --- a/src/layer/render/point/drawFill.js +++ b/src/layer/render/point/drawFill.js @@ -6,9 +6,13 @@ * @desc [description] 绘制点图层的面状填充,圆,三角形,六边形 */ import * as THREE from '../../../core/three'; +import * as PointBuffer from '../../../geom/buffer/point/index'; +import DrawStroke from './drawStroke'; import PolygonMaterial from '../../../geom/material/polygonMaterial'; -export default function DrawFill(attributes, style) { - const { opacity, activeColor } = style; +export default function DrawFill(layerData, layer) { + const style = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const attributes = PointBuffer.FillBuffer(layerData, style); const geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); @@ -28,14 +32,20 @@ export default function DrawFill(attributes, style) { // instancedGeometry.addAttribute('a_size', new THREE.InstancedBufferAttribute(new Float32Array(attributes.a_size), 3)); const material = new PolygonMaterial({ - u_opacity: opacity, - u_activeColor: activeColor + u_opacity: style.opacity, + u_activeColor: activeOption.fill, + u_zoom: layer.scene.getZoom() }, { SHAPE: true }); material.setDefinesvalue('SHAPE', true); material.depthTest = false; const fillMesh = new THREE.Mesh(geometry, material); + if (style.stroke !== 'none') { + // 是否绘制边界 + const meshStroke = DrawStroke(layerData, layer); + fillMesh.add(meshStroke); + } // const fillMesh = new THREE.Mesh(instancedGeometry, material); return fillMesh; diff --git a/src/layer/render/point/drawImage.js b/src/layer/render/point/drawImage.js index f6fc926aac..832db247b4 100644 --- a/src/layer/render/point/drawImage.js +++ b/src/layer/render/point/drawImage.js @@ -1,9 +1,15 @@ import * as THREE from '../../../core/three'; +import * as PointBuffer from '../../../geom/buffer/point/index'; import PointMaterial from '../../../geom/material/pointMaterial'; -export default function DrawImage(attributes, style) { +export default function DrawImage(layerData, layer) { const geometry = new THREE.BufferGeometry(); - const { strokeWidth, stroke, opacity, texture } = style; + const style = layer.get('styleOptions'); + const { strokeWidth, stroke, opacity } = style; + const texture = layer.scene.image.texture; + const attributes = PointBuffer.ImageBuffer(layerData, { + imagePos: layer.scene.image.imagePos + }); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); diff --git a/src/layer/render/point/drawNormal.js b/src/layer/render/point/drawNormal.js index 077c80d6f1..c851e85341 100644 --- a/src/layer/render/point/drawNormal.js +++ b/src/layer/render/point/drawNormal.js @@ -2,17 +2,21 @@ * 原生点绘制 */ import * as THREE from '../../../core/three'; +import * as PointBuffer from '../../../geom/buffer/point/index'; import PointMaterial from '../../../geom/material/pointMaterial'; -export default function DrawNormal(attributes, style) { +export default function DrawNormal(layerData, layer) { const geometry = new THREE.BufferGeometry(); - const { opacity, activeColor } = style; + const style = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const { opacity } = style; + const attributes = PointBuffer.NormalBuffer(layerData, style); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.sizes, 1)); const material = new PointMaterial({ u_opacity: opacity, - u_activeColor: activeColor + u_activeColor: activeOption.fill }, { SHAPE: false, TEXCOORD_0: false diff --git a/src/layer/render/point/drawStroke.js b/src/layer/render/point/drawStroke.js index 8b5c780e48..da4ce645aa 100644 --- a/src/layer/render/point/drawStroke.js +++ b/src/layer/render/point/drawStroke.js @@ -7,9 +7,13 @@ */ import PointLineMaterial from '../../../geom/material/pointLineMaterial'; +import * as PointBuffer from '../../../geom/buffer/point/index'; import * as THREE from '../../../core/three'; -export default function DrawStroke(attributes, style) { +export default function DrawStroke(layerData, layer) { + const style = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); const { strokeWidth, stroke, strokeOpacity } = style; + const attributes = PointBuffer.StrokeBuffer(layerData, style); const geometry = new THREE.BufferGeometry(); geometry.setIndex(attributes.indexArray); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.positions, 3)); @@ -22,7 +26,8 @@ export default function DrawStroke(attributes, style) { const material = new PointLineMaterial({ u_strokeOpacity: strokeOpacity, u_stroke: stroke, - u_strokeWidth: strokeWidth + u_strokeWidth: strokeWidth, + u_activeColor: activeOption.fill }); const strokeMesh = new THREE.Mesh(geometry, material); return strokeMesh; diff --git a/src/layer/render/point/drawText.js b/src/layer/render/point/drawText.js index 32fa9a28d5..95d1f8949d 100644 --- a/src/layer/render/point/drawText.js +++ b/src/layer/render/point/drawText.js @@ -1,7 +1,15 @@ import * as THREE from '../../../core/three'; import TextMaterial from '../../../geom/material/textMaterial'; +import TextBuffer from '../../../geom/buffer/point/text'; -export default function DrawText(attributes, style) { +export default function DrawText(layerData, layer) { + const style = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const { width, height } = layer.scene.getSize(); + const attributes = new TextBuffer( + layerData, + layer.scene.fontAtlasManager + ); const geometry = new THREE.BufferGeometry(); geometry.addAttribute( 'position', @@ -27,9 +35,9 @@ export default function DrawText(attributes, style) { 'pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1) ); - const { strokeWidth, width, stroke, height, opacity, activeColor } = style; + const { strokeWidth, stroke, opacity } = style; const material = new TextMaterial({ - name: this.layerId, + name: layer.layerId, u_texture: attributes.texture, u_strokeWidth: strokeWidth, u_stroke: stroke, @@ -41,7 +49,7 @@ export default function DrawText(attributes, style) { u_buffer: 0.75, u_opacity: opacity, u_glSize: [ width, height ], - u_activeColor: activeColor + u_activeColor: activeOption.fill }); const mesh = new THREE.Mesh(geometry, material); return mesh; diff --git a/src/layer/render/polygon/drawAnimate.js b/src/layer/render/polygon/drawAnimate.js index 1e7bb408fa..b8eed2ab34 100644 --- a/src/layer/render/polygon/drawAnimate.js +++ b/src/layer/render/polygon/drawAnimate.js @@ -1,7 +1,15 @@ import * as THREE from '../../../core/three'; +import PolygonBuffer from '../../../geom/buffer/polygon'; import PolygonMaterial from '../../../geom/material/polygonMaterial'; -export default function DrawAnimate(attributes, style) { - const { opacity, baseColor, brightColor, windowColor, near, far } = style; +export default function DrawAnimate(layerData, layer) { + const style = layer.get('styleOptions'); + const { near, far } = layer.map.getCameraState(); + layer.scene.startAnimate(); + const { attributes } = new PolygonBuffer({ + shape: 'extrude', + layerData + }); + const { opacity, baseColor, brightColor, windowColor } = style; const geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); diff --git a/src/layer/render/polygon/drawFill.js b/src/layer/render/polygon/drawFill.js index d9981cbdbb..11ae385a2e 100644 --- a/src/layer/render/polygon/drawFill.js +++ b/src/layer/render/polygon/drawFill.js @@ -1,20 +1,24 @@ import * as THREE from '../../../core/three'; +import PolygonBuffer from '../../../geom/buffer/polygon'; import PolygonMaterial from '../../../geom/material/polygonMaterial'; -// import TileMaterial from '../../../geom/material/tile/polygon'; -export default function DrawPolygonFill(attributes, style) { - const { opacity, activeColor } = style; +export default function DrawPolygonFill(layerData, layer) { + const style = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const config = { + ...style, + activeColor: activeOption.fill + }; + const { opacity, activeColor } = config; + const { attributes } = new PolygonBuffer({ + shape: layer.shape, + layerData + }); const geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); geometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normals, 3)); - // const material = new PolygonMaterial({ - // u_opacity: opacity, - // u_activeColor: activeColor - // }, { - // SHAPE: false - // }); const material = new PolygonMaterial({ u_opacity: opacity, u_activeColor: activeColor diff --git a/src/layer/render/polygon/drawLine.js b/src/layer/render/polygon/drawLine.js index 8755a4f573..d14d8dc3e6 100644 --- a/src/layer/render/polygon/drawLine.js +++ b/src/layer/render/polygon/drawLine.js @@ -1,7 +1,18 @@ import * as THREE from '../../../core/three'; +import PolygonBuffer from '../../../geom/buffer/polygon'; import { LineMaterial } from '../../../geom/material/lineMaterial'; -export default function DrawPolygonLine(attributes, style) { - const { opacity } = style; +export default function DrawPolygonLine(layerData, layer) { + const style = layer.get('styleOptions'); + const activeOption = layer.get('activedOptions'); + const config = { + ...style, + activeColor: activeOption.fill + }; + const { opacity } = config; + const { attributes } = new PolygonBuffer({ + shape: layer.shape, + layerData + }); const geometry = new THREE.BufferGeometry(); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); diff --git a/src/layer/render/polygon/index.js b/src/layer/render/polygon/index.js index afde7904a6..65a4365702 100644 --- a/src/layer/render/polygon/index.js +++ b/src/layer/render/polygon/index.js @@ -1,3 +1,8 @@ + export { default as DrawAnimate } from './drawAnimate'; export { default as DrawFill } from './drawFill'; export { default as DrawLine } from './drawLine'; + +export function polygonMesh() { + +} diff --git a/src/layer/tile/VectorTileLayer.js b/src/layer/tile/VectorTileLayer.js new file mode 100644 index 0000000000..73258a6e40 --- /dev/null +++ b/src/layer/tile/VectorTileLayer.js @@ -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); + } +} diff --git a/src/layer/tile/imageTile.js b/src/layer/tile/imageTile.js index d30e336385..1a16b19c7d 100644 --- a/src/layer/tile/imageTile.js +++ b/src/layer/tile/imageTile.js @@ -13,30 +13,34 @@ export default class ImageTile extends Tile { }, 0); } _requestTile() { - const urlParams = { - x: this._tile[0], - y: this._tile[1], - z: this._tile[2] - }; + const image = this._createDebugMesh(); + this._createMesh(image); + this.emit('tileLoaded'); + // return; + // const urlParams = { + // x: this._tile[0], + // y: this._tile[1], + // z: this._tile[2] + // }; - const url = this._getTileURL(urlParams); - const image = document.createElement('img'); + // const url = this._getTileURL(urlParams); + // const image = document.createElement('img'); - image.addEventListener('load', () => { + // image.addEventListener('load', () => { + // this._isLoaded = true; + // this._createMesh(image); + // this._ready = true; + // }, false); - this._createMesh(image); - this._ready = true; - }, false); + // // image.addEventListener('progress', event => {}, false); + // // image.addEventListener('error', event => {}, false); - // image.addEventListener('progress', event => {}, false); - // image.addEventListener('error', event => {}, false); + // image.crossOrigin = ''; - image.crossOrigin = ''; + // // Load image + // image.src = url; - // Load image - image.src = url; - - this._image = image; + // this._image = image; } _getBufferData(images) { const NW = this._tileBounds.getTopLeft(); @@ -58,8 +62,20 @@ 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; + } + _createDebugMesh() { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + canvas.width = 256; + canvas.height = 256; + context.font = 'Bold 20px Helvetica Neue, Verdana, Arial'; + context.fillStyle = '#ff0000'; + context.fillText(this._tile.join('/'), 20, 20); + context.rect(0, 0, 256, 256); + context.stroke(); + return canvas; } _abortRequest() { if (!this._image) { @@ -68,7 +84,9 @@ export default class ImageTile extends Tile { this._image.src = ''; } - + getSelectFeature() { + return {}; + } destroy() { // Cancel any pending requests this._abortRequest(); diff --git a/src/layer/tile/tile.js b/src/layer/tile/tile.js index 261800150c..dc504e13b7 100644 --- a/src/layer/tile/tile.js +++ b/src/layer/tile/tile.js @@ -1,10 +1,28 @@ import * as THREE from '../../core/three'; +import Base from '../../core/base'; +import { destoryObject } from '../../util/object3d-util'; +import Controller from '../../core/controller/index'; +import Util from '../../util'; +import Global from '../../global'; +import Attr from '../../attr/index'; import { toLngLatBounds, toBounds } from '@antv/geo-coord'; const r2d = 180 / Math.PI; const tileURLRegex = /\{([zxy])\}/g; - -export default class Tile { +function parseFields(field) { + if (Util.isArray(field)) { + return field; + } + if (Util.isString(field)) { + return field.split('*'); + } + return [ field ]; +} +export default class Tile extends Base { constructor(key, url, layer) { + super({ + scales: {}, + attrs: {} + }); this.layer = layer; this._tile = key.split('_').map(v => v * 1); this._path = url; @@ -15,10 +33,134 @@ export default class Tile { this._center = this._tileBounds.getCenter(); this._centerLnglat = this._tileLnglatBounds.getCenter(); - this.Object3D = new THREE.Object3D(); - this.requestTileAsync(); + this._object3D = new THREE.Object3D(); + this._object3D.onBeforeRender = () => { + }; + this._isLoaded = false; + this.requestTileAsync(data => this._init(data)); + } + _init(data) { + this._initControllers(); + this._creatSource(data); + this._initTileAttrs(); + this._mapping(); + this._createMesh(); + } + _initControllers() { + const scales = this.layer.get('scaleOptions'); + const scaleController = new Controller.Scale({ + defs: { + ...scales + } + }); + this.set('scaleController', scaleController); + } + _createScale(field) { + // TODO scale更新 + const scales = this.get('scales'); + let scale = scales[field]; + if (!scale) { + scale = this.createScale(field); + scales[field] = scale; + } + return scale; + } + createScale(field) { + const data = this.source.data.dataArray; + const scales = this.get('scales'); + let scale = scales[field]; + const scaleController = this.get('scaleController'); + if (!scale) { + scale = scaleController.createScale(field, data); + scales[field] = scale; + } + return scale; + } + // 获取属性映射的值 + _getAttrValues(attr, record) { + const scales = attr.scales; + const params = []; + for (let i = 0; i < scales.length; i++) { + const scale = scales[i]; + const field = scale.field; + if (scale.type === 'identity') { + params.push(scale.value); + } else { + params.push(record[field]); + } + } + const indexZoom = params.indexOf('zoom'); + indexZoom !== -1 ? params[indexZoom] = attr.zoom : null; + const values = attr.mapping(...params); + return values; + } + _mapping() { + const attrs = this.get('attrs'); + const mappedData = []; + // const data = this.layerSource.propertiesData; + const data = this.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); + }); + } + this.layerData = mappedData; + } + _initTileAttrs() { + const attrOptions = this.layer.get('attrOptions'); + for (const type in attrOptions) { + if (attrOptions.hasOwnProperty(type)) { + this._updateTileAttr(type); + } + } + } + _updateTileAttr(type) { + const self = this; + const attrs = this.get('attrs'); + const attrOptions = this.layer.get('attrOptions'); + const option = attrOptions[type]; + option.neadUpdate = true; + const className = Util.upperFirst(type); + const fields = parseFields(option.field); + const scales = []; + for (let i = 0; i < fields.length; i++) { + const field = fields[i]; + const scale = self._createScale(field); + + if (type === 'color' && Util.isNil(option.values)) { // 设置 color 的默认色值 + option.values = Global.colors; + } + scales.push(scale); + } + option.scales = scales; + const attr = new Attr[className](option); + attrs[type] = attr; } _createMesh() {} _getTileURL(urlParams) { @@ -34,12 +176,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,31 +202,30 @@ 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))); } - destroy() { - if (this._object3D && this._object3D.children) { - let child; - for (let i = 0; i < this._object3D.children.length; i++) { - child = this._object3D.children[i]; - if (!child) { - continue; - } - this.remove(child); - if (child.geometry) { - // child.geometry.dispose(); - child.geometry = null; - } - if (child.material) { - if (child.material.map) { - child.material.map.dispose(); - child.material.map = null; - } + _retainParent(x, y, z, minZoom = 5) { + const x2 = Math.floor(x / 2), + y2 = Math.floor(y / 2), + z2 = z - 1, + coords2 = [ +x2, +y2, +z2 ]; + const tile = this._tiles[coords2]; // 计算保留 + if (tile && tile.active) { + tile.retain = true; + return true; - child.material.dispose(); - child.material = null; - } - child = null; - } + } else if (tile && tile.loaded) { + tile.retain = true; + } + + if (z2 > minZoom) { + return this._retainParent(x2, y2, z2, minZoom); } - this._object3D = null; + + return false; + } + _preRender() { + } + destroy() { + super.destroy(); + destoryObject(this._object3D); } } diff --git a/src/layer/tile/tileLayer.js b/src/layer/tile/tileLayer.js index a3bc44afc9..6cdcd01bc4 100644 --- a/src/layer/tile/tileLayer.js +++ b/src/layer/tile/tileLayer.js @@ -1,126 +1,325 @@ import Layer from '../../core/layer'; +import source from '../../core/source'; import * as THREE from '../../core/three'; +import Global from '../../global'; +const { pointShape } = Global; import TileCache from './tileCache'; -import { throttle } from '@antv/util'; -import { toLngLat } from '@antv/geo-coord'; +import pickingFragmentShader from '../../core/engine/picking/picking_frag.glsl'; +import { throttle, deepMix } from '@antv/util'; +import { toLngLat, Bounds, Point } from '@antv/geo-coord'; +import { wrapNum } from '@antv/geo-coord/lib/util/index'; import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857'; export default class TileLayer extends Layer { constructor(scene, cfg) { - super(scene, cfg); - this._tileCache = new TileCache(50, this._destroyTile); + super(scene, { + ...cfg, + keepBuffer: 2 + }); + this._tileCache = new TileCache(100, this._destroyTile); this._crs = epsg3857; this._tiles = new THREE.Object3D(); + this._pickTiles = new THREE.Object3D(); + this._pickTiles.name = this.layerId; + this.scene._engine._picking.add(this._pickTiles); + this._tiles.frustumCulled = false; this._tileKeys = []; - this.tileList = []; - - + this.tileList = {}; } - source(url) { + shape(field, values) { + const layerType = this.get('layerType'); + if (layerType === 'point') { + return super.shape(field, values); + } + this.shape = field; + return this; + } + source(url, cfg = {}) { this.url = url; + this.sourceCfg = cfg; + this.sourceCfg.mapType = this.scene.mapType; return this; } + tileSource(data, cfg) { + if (data instanceof source) { + return data; + } + const tileSourceCfg = { + data, + zoom: this.scene.getZoom() + }; + deepMix(tileSourceCfg, this.sourceCfg, cfg); + return new source(tileSourceCfg); + } render() { + // this._initControllers(); this._initMapEvent(); + // this._initAttrs(); + this._initInteraction(); this.draw(); + return this; } draw() { this._object3D.add(this._tiles); this._calculateLOD(); } + drawTile() { } + zoomchange(ev) { super.zoomchange(ev); throttle(this._calculateLOD, 200); this._calculateLOD(); } + dragend(ev) { super.dragend(ev); this._calculateLOD(); } _calculateLOD() { - const viewPort = this.scene.getBounds().toBounds(); - const SE = viewPort.getSouthEast(); - const NW = viewPort.getNorthWest(); + /** + * 加载完成 active + * 需要显示 current + * 是否保留 retain + */ + this.updateTileList = []; const zoom = Math.round(this.scene.getZoom()) - 1; - const tileCount = Math.pow(2, zoom); const center = this.scene.getCenter(); - const NWPoint = this._crs.lngLatToPoint(toLngLat(NW.lng, NW.lat), zoom); - const SEPoint = this._crs.lngLatToPoint(toLngLat(SE.lng, SE.lat), zoom); const centerPoint = this._crs.lngLatToPoint(toLngLat(center.lng, center.lat), zoom); const centerXY = centerPoint.divideBy(256).round(); - const minXY = NWPoint.divideBy(256).round(); - const maxXY = SEPoint.divideBy(256).round(); - // 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; - 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++) { - this._updateTileList(updateTileList, i, j, zoom); + const pixelBounds = this._getPixelBounds(); + const tileRange = this._pxBoundsToTileRange(pixelBounds); + const margin = this.get('keepBuffer'); + this.noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([ margin, -margin ]), + tileRange.getTopRight().add([ margin, -margin ])); + if (!(isFinite(tileRange.min.x) && + isFinite(tileRange.min.y) && + isFinite(tileRange.max.x) && + isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } + for (let j = tileRange.min.y; j <= tileRange.max.y; j++) { + for (let i = tileRange.min.x; i <= tileRange.max.x; i++) { + const coords = [ i, j, zoom ]; + const tile = this.tileList[coords.join('_')]; + if (tile) { + tile.current = true; + } else { + this.tileList[coords.join('_')] = { + current: true, + coords + }; + this.updateTileList.push(coords); } } - for (let i = maxXY.x; i < tileCount; i++) { - for (let j = Math.min(0, minXY.y - halfy); j < Math.max(maxXY.y + halfy, tileCount); j++) { - this._updateTileList(updateTileList, i, j, zoom); - } - } - } - for (let i = Math.max(0, minXY.x - halfx); i < Math.min(maxXY.x + halfx, tileCount); i++) { - for (let j = Math.max(0, minXY.y - halfy); j < Math.min(maxXY.y + halfy, tileCount); j++) { - this._updateTileList(updateTileList, i, j, zoom); - } } - // 过滤掉已经存在的 - // tileList = tileList.filter(tile => { - // }) - updateTileList = updateTileList.sort((a, b) => { - const tile1 = a.split('_'); - const tile2 = b.split('_'); + this.updateTileList.sort((a, b) => { + const tile1 = a; + const tile2 = b; const d1 = Math.pow((tile1[0] * 1 - centerXY.x), 2) + Math.pow((tile1[1] * 1 - centerXY.y), 2); const d2 = Math.pow((tile2[0] * 1 - centerXY.x), 2) + Math.pow((tile2[1] * 1 - centerXY.y), 2); return d1 - d2; }); - updateTileList.forEach(key => { - this._requestTile(key, this); + this._pruneTiles(); + // 更新瓦片数据 + this.updateTileList.forEach(coords => { + const key = coords.join('_'); + if (this.tileList[key].current) { + this._requestTile(key, this); + } }); - this._removeOutTiles(); } - _updateTileList(updateTileList, x, y, z) { + _getShape(layerData) { + let shape = null; + if (!layerData[0].hasOwnProperty('shape')) { + return 'normal'; + } + for (let i = 0; i < layerData.length; i++) { + shape = layerData[i].shape; + if (shape !== undefined) { + break; + } + } + if ( + pointShape['2d'].indexOf(shape) !== -1 || + pointShape['3d'].indexOf(shape) !== -1 + ) { + return 'fill'; + } else if (this.scene.image.imagesIds.indexOf(shape) !== -1) { + return 'image'; + } + return 'text'; + } + _updateTileList(x, y, z) { const key = [ x, y, z ].join('_'); - this.tileList.push(key); - if (this._tileKeys.indexOf(key) === -1) { - updateTileList.push(key); + const tile = this.tileList[key]; + if (tile) { + tile.current = true; + } else { + this.tileList[key] = { + current: true, + active: false, + coords: key.split('_') + }; + this.updateTileList.push(key); } } _requestTile(key, layer) { + const t = this.tileList[key]; + if (!t) { + return; + } 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', () => { + t.active = true; + const mesh = tile.getMesh(); + mesh.name = key; + this._tileCache.setTile(tile, key); + this._tileKeys.push(key); + if (mesh.type === 'composer') { + this.scene._engine.composerLayers.push(mesh); + this.scene._engine.update(); + this._pruneTiles(); + return; + } + if (mesh.children.length !== 0) { + this._tiles.add(tile.getMesh()); + this._addPickTile(tile.getMesh()); + } + this.scene._engine.update(); + this._pruneTiles(); + }); + } else { + if (tile.getMesh().type === 'composer') { + this.scene._engine.composerLayers.push(tile.getMesh()); + this.scene._engine.update(); + this._pruneTiles(); + return; + } + this._tiles.add(tile.getMesh()); + t.active = true; + this._addPickTile(tile.getMesh()); this._tileKeys.push(key); - // this.scene._engine.update(); + this.scene._engine.update(); + this._pruneTiles(); + } + } + _addPickTile(meshobj) { + const mesh = meshobj.children[0]; + const pickmaterial = mesh.material.clone(); + pickmaterial.fragmentShader = pickingFragmentShader; + const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial); + pickingMesh.name = this.layerId; + pickingMesh.onBeforeRender = () => { + const zoom = this.scene.getZoom(); + pickingMesh.material.setUniformsValue('u_zoom', zoom); + }; + this._pickTiles.add(pickingMesh); + + } + // 根据距离优先级查找 + getSelectFeature(id, lnglat) { + const zoom = Math.round(this.scene.getZoom()) - 1; + const tilePoint = this._crs.lngLatToPoint(toLngLat(lnglat.lng, lnglat.lat), zoom); + const tileXY = tilePoint.divideBy(256).round(); + const key = [ tileXY.x, tileXY.y, zoom ].join('_'); + const tile = this._tileCache.getTile(key); + const feature = tile ? tile.getSelectFeature(id) : null; + return { feature }; + } + _pruneTiles() { + let tile; + const zoom = Math.round(this.scene.getZoom()) - 1; + for (const key in this.tileList) { + const c = this.tileList[key].coords; + if (c[2] !== zoom || !this.noPruneRange.contains(new Point(c[0], c[1]))) { + this.tileList[key].current = false; + } + } + + for (const key in this.tileList) { + tile = this.tileList[key]; + tile.retain = tile.current; + } + for (const key in this.tileList) { + tile = this.tileList[key]; + if (tile.current && !tile.active) { + const [ x, y, z ] = key.split('_').map(v => v * 1); + if (!this._retainParent(x, y, z, z - 5)) { + this._retainChildren(x, y, z, z + 2); + } + } + + } + this._removeOutTiles(); + } + _retainParent(x, y, z, minZoom) { + const x2 = Math.floor(x / 2); + const y2 = Math.floor(y / 2); + const z2 = z - 1; + const tile = this.tileList[[ x2, y2, z2 ].join('_')]; + if (tile && tile.active) { + tile.retain = true; + return true; + } else if (tile && tile.loaded) { + tile.retain = true; + } + if (z2 > minZoom) { + return this._retainParent(x2, y2, z2, minZoom); + } + + return false; + + } + _retainChildren(x, y, z, maxZoom) { + for (let i = 2 * x; i < 2 * x + 2; i++) { + for (let j = 2 * y; j < 2 * y + 2; j++) { + const key = [ i, j, z + 1 ].join('_'); + const tile = this.tileList[key]; + if (tile && tile.active) { + tile.retain = true; + continue; + } else if (tile && tile.loaded) { + tile.retain = true; + } + + if (z + 1 < maxZoom) { + this._retainChildren(i, j, z + 1, maxZoom); + } + } } - this._tiles.add(tile.getMesh()); - this._tileKeys.push(key); } // 移除视野外的tile _removeOutTiles() { - for (let i = this._tiles.children.length - 1; i >= 0; i--) { - const tile = this._tiles.children[i]; - const key = tile.name; - if (this.tileList.indexOf(key) === -1) { - this._tiles.remove(tile); + for (const key in this.tileList) { + if (!this.tileList[key].retain) { + const tileObj = this._tileCache.getTile(key); + if (tileObj) { + tileObj._abortRequest(); + this._tiles.remove(tileObj.getMesh()); + } + if (tileObj && tileObj.getMesh().type === 'composer') { + this.scene._engine.composerLayers = this.scene._engine.composerLayers.filter(obj => { + return obj.name !== tileObj.getMesh().name; + }); + } + delete this.tileList[key]; } - this._tileKeys = [].concat(this.tileList); } + if (this._tiles.children.length > Object.keys(this.tileList).length) { + this._tiles.children.forEach(tile => { + const key = tile.name; + if (!this.tileList[key]) { + this._tiles.remove(tile); + } + }); + } // 移除 空的geom + this.scene._engine.update(); } + + _removeTiles() { if (!this._tiles || !this._tiles.children) { return; @@ -130,9 +329,51 @@ export default class TileLayer extends Layer { this._tiles.remove(this._tiles.children[i]); } } + _getPixelBounds() { + const viewPort = this.scene.getBounds().toBounds(); + const NE = viewPort.getNorthEast(); + const SW = viewPort.getSouthWest(); + const zoom = Math.round(this.scene.getZoom()) - 1; + const center = this.scene.getCenter(); + const NEPoint = this._crs.lngLatToPoint(toLngLat(NE.lng, NE.lat), zoom); + const SWPoint = this._crs.lngLatToPoint(toLngLat(SW.lng, SW.lat), zoom); + const centerPoint = this._crs.lngLatToPoint(toLngLat(center.lng, center.lat), zoom); + const topHeight = centerPoint.y - NEPoint.y; + const bottomHeight = SWPoint.y - centerPoint.y; + // 跨日界线的情况 + let leftWidth; + let rightWidth; + if (center.lng - NE.lng > 0 || center.lng - SW.lng < 0) { + const width = Math.pow(2, zoom) * 256 / 360 * (180 - NE.lng) + Math.pow(2, zoom) * 256 / 360 * (SW.lng + 180); + if (center.lng - NE.lng > 0) { // 日界线在右侧 + leftWidth = Math.pow(2, zoom) * 256 / 360 * (center.lng - NE.lng); + rightWidth = width - leftWidth; + } else { + rightWidth = Math.pow(2, zoom) * 256 / 360 * (SW.lng - center.lng); + leftWidth = width - rightWidth; + } + } else { // 不跨日界线 + leftWidth = Math.pow(2, zoom) * 256 / 360 * (center.lng - SW.lng); + rightWidth = Math.pow(2, zoom) * 256 / 360 * (NE.lng - center.lng); + } + const pixelBounds = new Bounds(centerPoint.subtract(leftWidth, topHeight), centerPoint.add(rightWidth, bottomHeight)); + return pixelBounds; + } + _pxBoundsToTileRange(pixelBounds) { + return new Bounds( + pixelBounds.min.divideBy(256).floor(), + pixelBounds.max.divideBy(256).ceil().subtract([ 1, 1 ]) + ); + } + _wrapCoords(coords) { + const wrapX = [ 0, Math.pow(2, coords[2]) ]; + const newX = wrapNum(coords[0], wrapX); + return [ newX, coords[1], coords[2] ]; + } _destroyTile(tile) { tile.destroy(); + tile = null; } - desttroy() { + destroy() { } } diff --git a/src/layer/tile/vectorTile.js b/src/layer/tile/vectorTile.js new file mode 100644 index 0000000000..b87fac4998 --- /dev/null +++ b/src/layer/tile/vectorTile.js @@ -0,0 +1,132 @@ +import Tile from './tile'; +import { getArrayBuffer } from '../../util/ajax'; +import { destoryObject, updateObjecteUniform } from '../../util/object3d-util'; +import * as THREE from '../../core/three'; +import MaskMaterial from '../../geom/material/tile/maskMaterial'; +import { getRender } from '../render/index'; +export default class VectorTile extends Tile { + requestTileAsync(done) { + // Making this asynchronous really speeds up the LOD framerate + setTimeout(() => { + if (!this._mesh) { + // this._mesh = this._createMesh(); + this._requestTile(done); + } + }, 0); + } + _requestTile(done) { + 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; + done(data.data); + }); + } + _creatSource(data) { + this.source = this.layer.tileSource(data, { + parser: { + tile: this._tile + } + }); + } + _createMesh() { + if (this.layer.get('layerType') === 'point') { + this.layer.shape = this.layer._getShape(this.layerData); + } + this.mesh = getRender(this.layer.get('layerType'), this.layer.shape)(this.layerData, this.layer); + if (this.mesh.type !== 'composer') { // 热力图的情况 + 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); + } else { + this._object3D = this.mesh; + } + + this.emit('tileLoaded'); + return this._object3D; + } + _renderMask(renderer) { + const zoom = this.layer.scene.getZoom(); + updateObjecteUniform(this.mesh, { + u_time: this.layer.scene._engine.clock.getElapsedTime(), + u_zoom: zoom + }); + if (this.layer.get('layerType') === 'point') { // 点图层目前不需要mask + return; + } + 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(); + } + getSelectFeature(id) { + const featureIndex = this.source.originData.featureKeys[id]; + if (featureIndex) { + return this.source.originData.dataArray[featureIndex]; + } + return null; + } + destroy() { + super.destroy(); + destoryObject(this.maskScene); + this._object3D = null; + this.maskScene = null; + this.layerData = null; + this.source.destroy(); + this.source = null; + } +} diff --git a/src/source/index.js b/src/source/index.js index 1d7fe62ba4..b1d1819c64 100644 --- a/src/source/index.js +++ b/src/source/index.js @@ -5,6 +5,7 @@ import image from './parser/image'; import csv from './parser/csv'; import json from './parser/json'; import raster from './parser/raster'; +import mvt from './parser/mvt'; import { registerTransform, registerParser } from './factory'; import { aggregatorToGrid } from './transform/grid'; @@ -16,6 +17,7 @@ registerParser('image', image); registerParser('csv', csv); registerParser('json', json); registerParser('raster', raster); +registerParser('mvt', mvt); // 注册transform registerTransform('grid', aggregatorToGrid); diff --git a/src/source/parser/geojson.js b/src/source/parser/geojson.js index 0e7d4de3f1..79e49bd68d 100644 --- a/src/source/parser/geojson.js +++ b/src/source/parser/geojson.js @@ -1,23 +1,35 @@ import * as turfMeta from '@turf/meta'; import { getCoords } from '@turf/invariant'; - -export default function geoJSON(data) { +import { BKDRHash } from '../../util/bkdr-hash'; +export default function geoJSON(data, cfg) { const resultData = []; + const featureKeys = {}; data.features = data.features.filter(item => { return item != null && item.geometry && item.geometry.type && item.geometry.coordinates && item.geometry.coordinates.length > 0; }); - // 数据为空时处理 turfMeta.flattenEach(data, (currentFeature, featureIndex) => { // 多个polygon 拆成一个 const coord = getCoords(currentFeature); + let id = featureIndex + 1; + if (cfg.idField && currentFeature.properties[cfg.idField]) { + const value = currentFeature.properties[cfg.idField]; + // id = value; + id = BKDRHash(value) % 1000019; + // if (featureKeys[id] && featureIndex !== featureKeys[id]) { + // // TODO 哈希冲突解决方法 + // console.log('哈希冲突', value); + // } + } + featureKeys[id] = featureIndex; const dataItem = { ...currentFeature.properties, coordinates: coord, - _id: featureIndex + 1 + _id: id }; resultData.push(dataItem); }); return { - dataArray: resultData + dataArray: resultData, + featureKeys }; } diff --git a/src/source/parser/mvt.js b/src/source/parser/mvt.js new file mode 100644 index 0000000000..7e5165488b --- /dev/null +++ b/src/source/parser/mvt.js @@ -0,0 +1,19 @@ +import PBF from 'pbf'; +import * as VectorParser from '@mapbox/vector-tile'; +import geojson from './geojson'; +export default function mvt(data, cfg) { + const tile = new VectorParser.VectorTile(new PBF(data)); + // CHN_Cities_L CHN_Cities CHN_L + const layerName = cfg.sourceLayer; + const features = []; + const vectorLayer = tile.layers[layerName]; + for (let i = 0; i < vectorLayer.length; i++) { + const feature = vectorLayer.feature(i); + features.push(feature.toGeoJSON(cfg.tile[0], cfg.tile[1], cfg.tile[2])); + } + const geodata = { + type: 'FeatureCollection', + features + }; + return geojson(geodata, cfg); +} diff --git a/src/util/bkdr-hash.js b/src/util/bkdr-hash.js new file mode 100644 index 0000000000..12848786a1 --- /dev/null +++ b/src/util/bkdr-hash.js @@ -0,0 +1,16 @@ +export function BKDRHash(str) { + const seed = 131; + const seed2 = 137; + let hash = 0; + // make hash more sensitive for short string like 'a', 'b', 'c' + str += 'x'; + // Note: Number.MAX_SAFE_INTEGER equals 9007199254740991 + const MAX_SAFE_INTEGER = parseInt(9007199254740991 / seed2); + for (let i = 0; i < str.length; i++) { + if (hash > MAX_SAFE_INTEGER) { + hash = parseInt(hash / seed2); + } + hash = hash * seed + str.charCodeAt(i); + } + return hash; +} diff --git a/src/util/lru-cache.js b/src/util/lru-cache.js index ab7a78339b..d44c2626dc 100644 --- a/src/util/lru-cache.js +++ b/src/util/lru-cache.js @@ -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); } } diff --git a/src/util/object3d-util.js b/src/util/object3d-util.js new file mode 100644 index 0000000000..17100fccb3 --- /dev/null +++ b/src/util/object3d-util.js @@ -0,0 +1,58 @@ +import pickingFragmentShader from '../core/engine/picking/picking_frag.glsl'; +import * as THREE from '../core/three'; +export function destoryObject(obj) { + if (!obj) { + return; + } + if (obj.children) { + for (let i = 0; i < obj.children.length; i++) { + const child = obj.children[i]; + destoryObject(child); + } + } + if (obj.geometry) { + obj.geometry.dispose(); + obj.geometry = null; + } + if (obj.material) { + if (obj.material.map) { + obj.material.map.dispose(); + obj.material.map = null; + } + + obj.material.dispose(); + obj.material = null; + } +} +export function updateObjecteUniform(obj, newOption) { + if (!obj) { + return; + } + if (obj.children) { + for (let i = 0; i < obj.children.length; i++) { + const child = obj.children[i]; + updateObjecteUniform(child, newOption); + } + } + if (obj.material) { + obj.material.updateUninform(newOption); + } +} +export function getPickObject(obj, newbj) { + if (!obj) { + return; + } + if (obj.isMesh) { + const pickmaterial = obj.material.clone(); + pickmaterial.fragmentShader = pickingFragmentShader; + const pickMesh = new THREE[obj.type](obj.geometry, pickmaterial); + newbj.add(pickMesh); + } + if (obj.children) { + const newObj = new THREE.Object3D(); + for (let i = 0; i < obj.children.length; i++) { + const child = obj.children[i]; + newObj.add(getPickObject(child, newbj)); + } + } +}