diff --git a/demos/01_animatePoint.html b/demos/01_animatePoint.html new file mode 100644 index 0000000000..e1ee4ae1f6 --- /dev/null +++ b/demos/01_animatePoint.html @@ -0,0 +1,106 @@ + + + + + + + + + + point_circle + + + + +
+ + + + + + + + diff --git a/demos/01_point_circle.html b/demos/01_point_circle.html index fef2948b0d..a03bf2326e 100644 --- a/demos/01_point_circle.html +++ b/demos/01_point_circle.html @@ -24,7 +24,7 @@ + + + + + + + diff --git a/demos/assets/screenshots/01_animatePoint.png b/demos/assets/screenshots/01_animatePoint.png new file mode 100644 index 0000000000..2486fbebc5 Binary files /dev/null and b/demos/assets/screenshots/01_animatePoint.png differ diff --git a/demos/assets/screenshots/01_point_circle.png b/demos/assets/screenshots/01_point_circle.png index c049a40aad..829bcb7954 100644 Binary files a/demos/assets/screenshots/01_point_circle.png and b/demos/assets/screenshots/01_point_circle.png differ diff --git a/demos/assets/screenshots/01_point_column.png b/demos/assets/screenshots/01_point_column.png index 0d451be5b9..d643b3f43d 100644 Binary files a/demos/assets/screenshots/01_point_column.png and b/demos/assets/screenshots/01_point_column.png differ diff --git a/demos/assets/screenshots/01_point_distribute.png b/demos/assets/screenshots/01_point_distribute.png index f2345f8351..6ab860f52b 100644 Binary files a/demos/assets/screenshots/01_point_distribute.png and b/demos/assets/screenshots/01_point_distribute.png differ diff --git a/demos/assets/screenshots/01_point_image.png b/demos/assets/screenshots/01_point_image.png index d22999aecd..974b17a2c4 100644 Binary files a/demos/assets/screenshots/01_point_image.png and b/demos/assets/screenshots/01_point_image.png differ diff --git a/demos/assets/screenshots/02_animateline.png b/demos/assets/screenshots/02_animateline.png new file mode 100644 index 0000000000..dcce8a146f Binary files /dev/null and b/demos/assets/screenshots/02_animateline.png differ diff --git a/demos/assets/screenshots/02_contour.png b/demos/assets/screenshots/02_contour.png index 8832d56b21..014d9d3b15 100644 Binary files a/demos/assets/screenshots/02_contour.png and b/demos/assets/screenshots/02_contour.png differ diff --git a/demos/assets/screenshots/02_oneBletoneRoad.png b/demos/assets/screenshots/02_oneBletoneRoad.png index 9f9d920822..1efcc4c127 100644 Binary files a/demos/assets/screenshots/02_oneBletoneRoad.png and b/demos/assets/screenshots/02_oneBletoneRoad.png differ diff --git a/demos/assets/screenshots/03_1_extrude_polygon.png b/demos/assets/screenshots/03_1_extrude_polygon.png index c049a40aad..3bfd17c60b 100644 Binary files a/demos/assets/screenshots/03_1_extrude_polygon.png and b/demos/assets/screenshots/03_1_extrude_polygon.png differ diff --git a/demos/assets/screenshots/03_choropleths_polygon.png b/demos/assets/screenshots/03_choropleths_polygon.png index 64e3e8d621..fb36580103 100644 Binary files a/demos/assets/screenshots/03_choropleths_polygon.png and b/demos/assets/screenshots/03_choropleths_polygon.png differ diff --git a/demos/assets/screenshots/04_image.png b/demos/assets/screenshots/04_image.png index c049a40aad..a9dabd3b55 100644 Binary files a/demos/assets/screenshots/04_image.png and b/demos/assets/screenshots/04_image.png differ diff --git a/demos/assets/screenshots/05_raster_dem.png b/demos/assets/screenshots/05_raster_dem.png index c049a40aad..829bcb7954 100644 Binary files a/demos/assets/screenshots/05_raster_dem.png and b/demos/assets/screenshots/05_raster_dem.png differ diff --git a/demos/assets/screenshots/06_text.png b/demos/assets/screenshots/06_text.png index c049a40aad..e4a6de2833 100644 Binary files a/demos/assets/screenshots/06_text.png and b/demos/assets/screenshots/06_text.png differ diff --git a/demos/assets/screenshots/07_city.png b/demos/assets/screenshots/07_city.png index c049a40aad..023f8e252b 100644 Binary files a/demos/assets/screenshots/07_city.png and b/demos/assets/screenshots/07_city.png differ diff --git a/demos/assets/screenshots/08_point_shape.png b/demos/assets/screenshots/08_point_shape.png index 68525450f4..24873ac275 100644 Binary files a/demos/assets/screenshots/08_point_shape.png and b/demos/assets/screenshots/08_point_shape.png differ diff --git a/demos/assets/screenshots/grid.png b/demos/assets/screenshots/grid.png new file mode 100644 index 0000000000..6ab860f52b Binary files /dev/null and b/demos/assets/screenshots/grid.png differ diff --git a/demos/assets/screenshots/heatmap.png b/demos/assets/screenshots/heatmap.png new file mode 100644 index 0000000000..619506ae98 Binary files /dev/null and b/demos/assets/screenshots/heatmap.png differ diff --git a/demos/assets/screenshots/hexgon.png b/demos/assets/screenshots/hexgon.png new file mode 100644 index 0000000000..6ab860f52b Binary files /dev/null and b/demos/assets/screenshots/hexgon.png differ diff --git a/demos/assets/screenshots/index.png b/demos/assets/screenshots/index.png new file mode 100644 index 0000000000..939021031a Binary files /dev/null and b/demos/assets/screenshots/index.png differ diff --git a/demos/line.html b/demos/line.html index 7c38f6cf1d..8583dbfc0d 100644 --- a/demos/line.html +++ b/demos/line.html @@ -9,53 +9,81 @@ city demo -
-
- +
时间: 6时
+
+ +
+ - diff --git a/src/core/atlas/font-manager.js b/src/core/atlas/font-manager.js new file mode 100644 index 0000000000..b51ffe17e3 --- /dev/null +++ b/src/core/atlas/font-manager.js @@ -0,0 +1,227 @@ +import TinySDF from '@mapbox/tiny-sdf'; +import { buildMapping } from '../../util/font-util'; +import * as THREE from '../../core/three'; +import LRUCache from '../../util/lru-cache'; +export const DEFAULT_CHAR_SET = getDefaultCharacterSet(); +export const DEFAULT_FONT_FAMILY = 'sans-serif'; +export const DEFAULT_FONT_WEIGHT = 'normal'; +export const DEFAULT_FONT_SIZE = 24; +export const DEFAULT_BUFFER = 3; +export const DEFAULT_CUTOFF = 0.25; +export const DEFAULT_RADIUS = 8; +const MAX_CANVAS_WIDTH = 1024; +const BASELINE_SCALE = 0.9; +const HEIGHT_SCALE = 1.2; +const CACHE_LIMIT = 3; +const cache = new LRUCache(CACHE_LIMIT); + +const VALID_PROPS = [ + 'fontFamily', + 'fontWeight', + 'characterSet', + 'fontSize', + 'sdf', + 'buffer', + 'cutoff', + 'radius' +]; + +function getDefaultCharacterSet() { + const charSet = []; + for (let i = 32; i < 128; i++) { + charSet.push(String.fromCharCode(i)); + } + return charSet; +} + +function setTextStyle(ctx, fontFamily, fontSize, fontWeight) { + ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`; + ctx.fillStyle = '#000'; + ctx.textBaseline = 'baseline'; + ctx.textAlign = 'left'; +} +function getNewChars(key, characterSet) { + const cachedFontAtlas = cache.get(key); + if (!cachedFontAtlas) { + return characterSet; + } + + const newChars = []; + const cachedMapping = cachedFontAtlas.mapping; + let cachedCharSet = Object.keys(cachedMapping); + cachedCharSet = new Set(cachedCharSet); + + let charSet = characterSet; + if (charSet instanceof Array) { + charSet = new Set(charSet); + } + + charSet.forEach(char => { + if (!cachedCharSet.has(char)) { + newChars.push(char); + } + }); + + return newChars; +} + +function populateAlphaChannel(alphaChannel, imageData) { + // populate distance value from tinySDF to image alpha channel + for (let i = 0; i < alphaChannel.length; i++) { + imageData.data[4 * i + 3] = alphaChannel[i]; + } +} + +export default class FontAtlasManager { + constructor() { + + // font settings + this.props = { + fontFamily: DEFAULT_FONT_FAMILY, + fontWeight: DEFAULT_FONT_WEIGHT, + characterSet: DEFAULT_CHAR_SET, + fontSize: DEFAULT_FONT_SIZE, + buffer: DEFAULT_BUFFER, + // sdf only props + // https://github.com/mapbox/tiny-sdf + sdf: true, + cutoff: DEFAULT_CUTOFF, + radius: DEFAULT_RADIUS + }; + + // key is used for caching generated fontAtlas + this._key = null; + this._texture = new THREE.Texture(); + } + + get texture() { + return this._texture; + } + + get mapping() { + const data = cache.get(this._key); + return data && data.mapping; + } + + get scale() { + return HEIGHT_SCALE; + } + + get fontAtlas() { + return this._fontAtlas; + } + + setProps(props = {}) { + VALID_PROPS.forEach(prop => { + if (prop in props) { + this.props[prop] = props[prop]; + } + }); + + // update cache key + const oldKey = this._key; + this._key = this._getKey(); + + const charSet = getNewChars(this._key, this.props.characterSet); + const cachedFontAtlas = cache.get(this._key); + + // if a fontAtlas associated with the new settings is cached and + // there are no new chars + if (cachedFontAtlas && charSet.length === 0) { + // update texture with cached fontAtlas + if (this._key !== oldKey) { + this._updateTexture(cachedFontAtlas); + } + return; + } + + // update fontAtlas with new settings + const fontAtlas = this._generateFontAtlas(this._key, charSet, cachedFontAtlas); + this._fontAtlas = fontAtlas; + this._updateTexture(fontAtlas); + + // update cache + cache.set(this._key, fontAtlas); + } + + _updateTexture({ data: canvas }) { + this._texture = new THREE.CanvasTexture(canvas); + this._texture.wrapS = THREE.ClampToEdgeWrapping; + this._texture.wrapT = THREE.ClampToEdgeWrapping; + this._texture.minFilter = THREE.LinearFilter; + this._texture.flipY = false; + this._texture.needUpdate = true; + } + + _generateFontAtlas(key, characterSet, cachedFontAtlas) { + const { fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff } = this.props; + let canvas = cachedFontAtlas && cachedFontAtlas.data; + if (!canvas) { + canvas = document.createElement('canvas'); + canvas.width = MAX_CANVAS_WIDTH; + } + const ctx = canvas.getContext('2d'); + + setTextStyle(ctx, fontFamily, fontSize, fontWeight); + + // 1. build mapping + const { mapping, canvasHeight, xOffset, yOffset } = buildMapping( + Object.assign( + { + getFontWidth: char => ctx.measureText(char).width, + fontHeight: fontSize * HEIGHT_SCALE, + buffer, + characterSet, + maxCanvasWidth: MAX_CANVAS_WIDTH + }, + cachedFontAtlas && { + mapping: cachedFontAtlas.mapping, + xOffset: cachedFontAtlas.xOffset, + yOffset: cachedFontAtlas.yOffset + } + ) + ); + + // 2. update canvas + // copy old canvas data to new canvas only when height changed + if (canvas.height !== canvasHeight) { + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + canvas.height = canvasHeight; + ctx.putImageData(imageData, 0, 0); + } + setTextStyle(ctx, fontFamily, fontSize, fontWeight); + + // 3. layout characters + if (sdf) { + const tinySDF = new TinySDF(fontSize, buffer, radius, cutoff, fontFamily, fontWeight); + // used to store distance values from tinySDF + // tinySDF.size equals `fontSize + buffer * 2` + const imageData = ctx.getImageData(0, 0, tinySDF.size, tinySDF.size); + + for (const char of characterSet) { + populateAlphaChannel(tinySDF.draw(char), imageData); + ctx.putImageData(imageData, mapping[char].x - buffer, mapping[char].y - buffer); + } + } else { + for (const char of characterSet) { + ctx.fillText(char, mapping[char].x, mapping[char].y + fontSize * BASELINE_SCALE); + } + } + return { + xOffset, + yOffset, + mapping, + data: canvas, + width: canvas.width, + height: canvas.height + }; + } + + _getKey() { + const { fontFamily, fontWeight, fontSize, buffer, sdf, radius, cutoff } = this.props; + if (sdf) { + return `${fontFamily} ${fontWeight} ${fontSize} ${buffer} ${radius} ${cutoff}`; + } + return `${fontFamily} ${fontWeight} ${fontSize} ${buffer}`; + } +} diff --git a/src/core/atlas/icon-manager.js b/src/core/atlas/icon-manager.js new file mode 100644 index 0000000000..74d7b9653f --- /dev/null +++ b/src/core/atlas/icon-manager.js @@ -0,0 +1,52 @@ +import { buildIconMaping } from '../../util/font-util'; +import * as THREE from '../../../../core/three'; +const BUFFER = 3; +const MAX_CANVAS_WIDTH = 1024; +export default class IconManager { + constructor() { + this._getIcon = null; + this._mapping = {}; + this._autoPacking = false; + this.iconData = {}; + this._canvas = document.createElement('canvas'); + this._texture = new THREE.Texture(this._canvas); + this.ctx = this._canvas.getContext('2d'); + } + getTexture() { + return this._texture; + } + + _updateIconAtlas() { + this._canvas.width = MAX_CANVAS_WIDTH; + this._canvas.height = this._canvasHeigth; + for (const key in this.mapping) { + const icon = this.mapping[key]; + const { x, y, image } = icon; + this.ctx.drawImage(image, x, y, this.imageWidth, this.imageWidth); + } + this.texture.magFilter = THREE.LinearFilter; + this.texture.minFilter = THREE.LinearFilter; + this.texture.needsUpdate = true; + } + + addImage(id, opt) { + this._loadImage(opt).then(image => { + this.iconData.push({ id, image }); + const { mapping, canvasHeight } = buildIconMaping(this.iconData, BUFFER, MAX_CANVAS_WIDTH); + this._mapping = mapping; + this._canvasHeigth = canvasHeight; + }); + } + _loadImage(url) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = () => { + resolve(image); + }; + image.onerror = function() { + reject(new Error('Could not load image at ' + url)); + }; + image.src = url; + }); + } +} diff --git a/src/core/layer.js b/src/core/layer.js index 1403fee10e..507729b617 100644 --- a/src/core/layer.js +++ b/src/core/layer.js @@ -8,8 +8,9 @@ import ColorUtil from '../attr/color-util'; import Controller from './controller/index'; import source from './source'; import pickingFragmentShader from '../core/engine/picking/picking_frag.glsl'; -// import PickingMaterial from '../core/engine/picking/pickingMaterial'; +import { getInteraction } from '../interaction/index'; import Attr from '../attr/index'; +import diff from '../util/diff'; import Util from '../util'; import Global from '../global'; let id = 1; @@ -36,6 +37,7 @@ export default class Layer extends Base { attrOptions: { }, scaleOptions: {}, + preScaleOptions: null, scales: {}, attrs: {}, // 样式配置项 @@ -53,6 +55,7 @@ export default class Layer extends Base { activedOptions: { fill: [ 1.0, 0, 0, 1.0 ] }, + interactions: {}, animateOptions: { enable: false } @@ -66,6 +69,7 @@ export default class Layer extends Base { this._pickObject3D = new THREE.Object3D(); this._object3D.visible = this.get('visible'); this._object3D.renderOrder = this.get('zIndex') || 0; + this._mapEventHandlers = []; const layerId = this._getUniqueId(); this.layerId = layerId; this._activeIds = null; @@ -82,18 +86,15 @@ export default class Layer extends Base { * @param {*} type mesh类型是区别是填充还是边线 */ add(object, type = 'fill') { - // composer合图层绘制 + // composer合图层绘制 if (object.type === 'composer') { this._object3D = object; this.scene._engine.composerLayers.push(object); + setTimeout(() => this.scene._engine.update(), 500); return; } - type === 'fill' ? this.layerMesh = object : this.layerLineMesh = object; this._visibleWithZoom(); - this._zoomchangeHander = this._visibleWithZoom.bind(this); - this.scene.on('zoomchange', this._zoomchangeHander); - object.onBeforeRender = () => { // 每次渲染前改变状态 const zoom = this.scene.getZoom(); object.material.setUniformsValue('u_time', this.scene._engine.clock.getElapsedTime()); @@ -104,15 +105,10 @@ export default class Layer extends Base { object.onAfterRender = () => { // 每次渲染后改变状态 this.afterRender(); }; - // 更新 - if (this._needUpdateFilter) { // 动态更新数据过滤 - this._updateFilter(object); - } this._object3D.add(object); if (type === 'fill') { this._addPickMesh(object);// 不对边界线进行拾取 } - this.scene._engine.update(); setTimeout(() => this.scene._engine.update(), 500); } remove(object) { @@ -132,8 +128,13 @@ export default class Layer extends Base { this._object3D.visible = this.get('visible'); } source(data, cfg = {}) { + if (data instanceof source) { + this.layerSource = data; + return this; + } cfg.data = data; cfg.mapType = this.scene.mapType; + cfg.zoom = this.scene.getZoom(); this.layerSource = new source(cfg); // this.scene.workerPool.runTask({ // command: 'geojson', @@ -144,7 +145,6 @@ export default class Layer extends Base { return this; } color(field, values) { - this._needUpdateColor = true;// 标识颜色是否需要更新 this._createAttrOption('color', field, values, Global.colors); return this; } @@ -224,7 +224,6 @@ export default class Layer extends Base { } filter(field, values) { - this._needUpdateFilter = true; this._createAttrOption('filter', field, values, true); return this; } @@ -263,6 +262,10 @@ export default class Layer extends Base { this._visible(true); return this; } + setData(data, cfg) { + this.layerSource.setData(data, cfg); + this.repaint(); + } _createScale(field) { // TODO scale更新 const scales = this.get('scales'); @@ -316,25 +319,54 @@ export default class Layer extends Base { } return scale; } + render() { + this.init(); + this.scene._engine.update(); + return this; + } + // 重绘 度量, 映射,顶点构建 + repaint() { + this.set('scales', {}); + this._initControllers(); + this._initAttrs(); + this._mapping(); + this.redraw(); + } // 初始化图层 init() { this._initControllers(); this._initAttrs(); - this._scaleByZoom(); - this._mapping(); - - const activeHander = this._addActiveFeature.bind(this); - const resetHander = this._resetStyle.bind(this); + this._updateDraw(); + } + _initInteraction() { if (this.get('allowActive')) { - - this.on('mousemove', activeHander); - this.on('mouseleave', resetHander); - - } else { - this.off('mousemove', activeHander); - this.off('mouseleave', resetHander); + this.interaction('active'); } } + _initMapEvent() { + // zoomchange mapmove resize + const EVENT_TYPES = [ 'zoomchange', 'dragend' ]; + Util.each(EVENT_TYPES, type => { + const handler = Util.wrapBehavior(this, `${type}`); + this.map.on(`${type}`, handler); + this._mapEventHandlers.push({ type, handler }); + }); + } + clearMapEvent() { + const eventHandlers = this._mapEventHandlers; + Util.each(eventHandlers, eh => { + this.map.off(eh.type, eh.handler); + }); + } + zoomchange(ev) { + // 地图缩放等级变化 + this._visibleWithZoom(ev); + } + dragend() { + + } + resize() { + } setActive(id, color) { this._activeIds = id; @@ -353,6 +385,7 @@ export default class Layer extends Base { _initAttrs() { + // 对比 options变化判断如何更新 const attrOptions = this.get('attrOptions'); for (const type in attrOptions) { if (attrOptions.hasOwnProperty(type)) { @@ -360,6 +393,55 @@ export default class Layer extends Base { } } } + _setPreOption() { + const nextAttrs = this.get('attrOptions'); + const nextStyle = this.get('styleOptions'); + this.set('preAttrOptions', Util.clone(nextAttrs)); + this.set('preStyleOption', Util.clone(nextStyle)); + } + _updateDraw() { + const preAttrs = this.get('preAttrOptions'); + const nextAttrs = this.get('attrOptions'); + const preStyle = this.get('preStyleOption'); + const nextStyle = this.get('styleOptions'); + if (preAttrs === undefined && preStyle === undefined) { + this._mapping(); + this._setPreOption(); + this._scaleByZoom(); + this._initInteraction(); + this._initMapEvent(); + this.draw(); + return; + } + if (!Util.isEqual(preAttrs.color, nextAttrs.color)) { + this._updateAttributes(this.layerMesh); + } + // 更新数据过滤 filter + if (!Util.isEqual(preAttrs.filter, nextAttrs.filter)) { + // 更新color; + this._updateAttributes(this.layerMesh); + } + // 更新Size + if (!Util.isEqual(preAttrs.size, nextAttrs.size)) { + // 更新color; + this._updateSize(); + } + // 更新形状 + if (!Util.isEqual(preAttrs.shape, nextAttrs.shape)) { + // 更新color; + this._updateShape(); + } + if (!Util.isEqual(preStyle, nextStyle)) { + // 判断新增,修改,删除 + const newStyle = {}; + Util.each(diff(preStyle, nextStyle), ({ type, key, value }) => { + (type !== 'remove') && (newStyle[key] = value); + // newStyle[key] = type === 'remove' ? null : value; + }); + this._updateStyle(newStyle); + } + this._setPreOption(); + } _updateAttr(type) { const self = this; @@ -400,7 +482,14 @@ export default class Layer extends Base { } this.emit('sizeUpdated', this.zoomSizeCache[zoom]); } + _updateStyle(option) { + const newOption = { }; + for (const key in option) { + newOption['u_' + key] = option[key]; + } + this.layerMesh.material.updateUninform(newOption); + } _mapping() { const self = this; const attrs = self.get('attrs'); @@ -410,13 +499,12 @@ export default class Layer extends Base { 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]; - attr.needUpdate = false; const names = attr.names; - const values = self._getAttrValues(attr, record); if (names.length > 1) { // position 之类的生成多个字段的属性 for (let j = 0; j < values.length; j++) { @@ -433,6 +521,12 @@ export default class Layer extends Base { newRecord.coordinates = record.coordinates; mappedData.push(newRecord); } + // 通过透明度过滤数据 + if (attrs.hasOwnProperty('filter')) { + mappedData.forEach(item => { + item.filter === false && (item.color[3] = 0); + }); + } this.layerData = mappedData; } @@ -483,17 +577,6 @@ export default class Layer extends Base { const values = attr.mapping(...params); return values; } - - // temp - _getDataType(data) { - if (data.hasOwnProperty('type')) { - const type = data.type; - if (type === 'FeatureCollection') { - return 'geojson'; - } - } - return 'basic'; - } _scaleByZoom() { if (this._zoomScale) { this.map.on('zoomend', () => { @@ -502,11 +585,7 @@ export default class Layer extends Base { }); } } - // on(type, callback) { - // this._addPickingEvents(); - // super.on(type, callback); - // } getPickingId() { return this.scene._engine._picking.getNextId(); } @@ -519,19 +598,9 @@ export default class Layer extends Base { _addPickMesh(mesh) { this._pickingMesh = new THREE.Object3D(); this._pickingMesh.name = this.layerId; - // this._visibleWithZoom(); - // this.scene.on('zoomchange', () => { - // this._visibleWithZoom(); - // }); this.addToPicking(this._pickingMesh); const pickmaterial = mesh.material.clone(); - pickmaterial.fragmentShader = pickingFragmentShader; - // const pickmaterial = new PickingMaterial({ - // u_zoom: this.scene.getZoom(), - // vs: mesh.material. - // }); - const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial); pickingMesh.name = this.layerId; pickmaterial.setDefinesvalue(this.type, true); @@ -542,16 +611,13 @@ export default class Layer extends Base { this._pickingMesh.add(pickingMesh); } - _setPickingId() { - this._pickingId = this.getPickingId(); - } _initEvents() { this.scene.on('pick-' + this.layerId, e => { let { featureId, point2d, type } = e; if (featureId < 0 && this._activeIds !== null) { type = 'mouseleave'; - // featureId = this._activeIds; } + this._activeIds = featureId; const feature = this.layerSource.getSelectFeature(featureId); const lnglat = this.scene.containerToLngLat(point2d); const style = this.layerData[featureId - 1]; @@ -569,44 +635,11 @@ export default class Layer extends Base { }); } - /** - * 更新active操作 - * @param {*} featureStyleId 需要更新的要素Id - * @param {*} style 更新的要素样式 - */ - updateStyle(featureStyleId, style) { - if (this._activeIds) { - this._resetStyle(); - } - this._activeIds = featureStyleId; - const pickingId = this.layerMesh.geometry.attributes.pickingId.array; - const color = style.color; - const colorAttr = this.layerMesh.geometry.attributes.a_color; - const firstId = pickingId.indexOf(featureStyleId[0]); - for (let i = firstId; i < pickingId.length; i++) { - if (pickingId[i] === featureStyleId[0]) { - colorAttr.array[i * 4 + 0] = color[0]; - colorAttr.array[i * 4 + 1] = color[1]; - colorAttr.array[i * 4 + 2] = color[2]; - colorAttr.array[i * 4 + 3] = color[3]; - } else { - break; - } - } - colorAttr.needsUpdate = true; - return; - } - - _updateColor() { - - this._updateMaping(); - - } /** * 用于过滤数据 - * @param {*} object 需要过滤的mesh + * @param {*} object 更新颜色和数据过滤 */ - _updateFilter(object) { + _updateAttributes(object) { this._updateMaping(); const filterData = this.layerData; this._activeIds = null; // 清空选中元素 @@ -642,16 +675,64 @@ export default class Layer extends Base { let offset = 0; if (this.type === 'point') { offset = 5; + this.shapeType = 'text' && (offset = 10); + } else if (this.type === 'polyline') { offset = 2; } - this._object3D.position.z = offset * Math.pow(2, 20 - zoom); + this._object3D.position && (this._object3D.position.z = offset * Math.pow(2, 20 - zoom)); if (zoom < minZoom || zoom > maxZoom) { this._object3D.visible = false; } else if (this.get('visible')) { this._object3D.visible = true; } } + + // 重新构建mesh + redraw() { + this._object3D.children.forEach(child => { + this._object3D.remove(child); + }); + this.removeFromPicking(this._pickingMesh); + this.draw(); + } + // 更新mesh + updateDraw() { + + } + + // interaction 方法 + clearAllInteractions() { + const interactions = this.get('interactions'); + Util.each(interactions, (interaction, key) => { + interaction.destory(); + delete interactions[key]; + }); + return this; + } + clearInteraction(type) { + const interactions = this.get('interactions'); + if (interactions[type]) { + interactions[type].destory(); + delete interactions[type]; + } + return this; + } + interaction(type, cfg = {}) { + cfg.layer = this; + const Ctor = getInteraction(type); + const interaction = new Ctor(cfg); + this._setInteraction(type, interaction); + return this; + } + _setInteraction(type, interaction) { + const interactions = this.get('interactions'); + if (interactions[type]) { + interactions[type].destory(); + } + interactions[type] = interaction; + } + /** * 重置高亮要素 */ @@ -664,6 +745,8 @@ export default class Layer extends Base { */ destroy() { this.removeAllListeners(); + this.clearAllInteractions(); + this.clearMapEvent(); if (this._object3D.type === 'composer') { this.remove(this._object3D); @@ -696,7 +779,6 @@ export default class Layer extends Base { this._object3D = null; this.scene._engine._scene.remove(this._object3D); this.scene._engine._picking.remove(this._pickingMesh); - this.scene.off('zoomchange', this._zoomchangeHander); this.destroyed = true; } diff --git a/src/core/scene.js b/src/core/scene.js index 5d22c4e67d..1309cfd525 100644 --- a/src/core/scene.js +++ b/src/core/scene.js @@ -7,6 +7,7 @@ import FontAtlasManager from '../geom/buffer/point/text/font-manager'; // import { MapProvider } from '../map/AMap'; import { getMap } from '../map/index'; import Global from '../global'; +import { getInteraction } from '../interaction/index'; import { compileBuiltinModules } from '../geom/shader'; export default class Scene extends Base { getDefaultCfg() { @@ -46,6 +47,12 @@ export default class Scene extends Base { Map.asyncCamera(this._engine); this.initLayer(); this._registEvents(); + const hash = this.get('hash'); + if (hash) { + const Ctor = getInteraction('hash'); + const interaction = new Ctor({ layer: this }); + interaction._onHashChange(); + } this.emit('loaded'); }); @@ -69,14 +76,6 @@ export default class Scene extends Base { super.off(type, hander); } - _initAttribution() { - const message = 'AntV | L7 '; - const element = document.createElement('div'); - - element.innerHTML = message; - element.style.cssText += 'position: absolute; pointer-events:none;background: rgba(255, 255, 255, 0.7);font-size: 11px;z-index:100; padding:4px;bottom: 0;right:0px;'; - this._container.appendChild(element); - } addImage() { this.image = new LoadImage(); } diff --git a/src/core/source.js b/src/core/source.js index 305e535178..25841350ea 100644 --- a/src/core/source.js +++ b/src/core/source.js @@ -1,6 +1,8 @@ import Base from './base'; import { getTransform, getParser } from '../source'; +import { cluster, formatData } from '../source/transform/cluster'; import { extent, tranfrormCoord } from '../util/geo'; +import { clone } from '@antv/util'; import { getMap } from '../map/index'; export default class Source extends Base { getDefaultCfg() { @@ -21,18 +23,40 @@ export default class Source extends Base { const mapType = this.get('mapType'); this.projectFlat = getMap(mapType).project; // 数据解析 + this._init(); + + } + _init() { this._excuteParser(); + const isCluster = this.get('isCluster') || false; + isCluster && this._executeCluster(); // 数据转换 统计,聚合,分类 this._executeTrans(); // 坐标转换 this._projectCoords(); - } + setData(data, cfg = {}) { + Object.assign(this._attrs, cfg); + const transform = this.get('transforms'); + this._transforms = transform || []; + this.set('data', data); + this._init(); + } + // 数据更新 + updateTransfrom(cfg) { + const { transforms } = cfg; + this._transforms = transforms; + this.data = clone(this.originData); + this._executeTrans(); + this._projectCoords(); + } + _excuteParser() { const parser = this.get('parser'); const { type = 'geojson' } = parser; const data = this.get('data'); - this.data = getParser(type)(data, parser); + this.originData = getParser(type)(data, parser); + this.data = clone(this.originData); this.data.extent = extent(this.data.dataArray); } /** @@ -51,6 +75,24 @@ export default class Source extends Base { const data = getTransform(option.type)(this.data, option); Object.assign(this.data, data); } + _executeCluster() { + const clusterCfg = this.get('Cluster') || {}; + const zoom = this.get('zoom'); + clusterCfg.zoom = Math.floor(zoom); + this.set('cluster', clusterCfg); + const clusterData = cluster(this.data, clusterCfg); + this.data = clusterData.data; + this.pointIndex = clusterData.pointIndex; + } + updateCusterData(zoom, bbox) { + const clusterPoint = this.pointIndex.getClusters(bbox, zoom); + this.data.dataArray = formatData(clusterPoint); + const clusterCfg = this.get('Cluster') || {}; + clusterCfg.zoom = Math.floor(zoom); + clusterCfg.bbox = bbox; + this.set('cluster', clusterCfg); + this._projectCoords(); + } _projectCoords() { this.data.dataArray.forEach(data => { // data.coordinates = this._coordProject(data.coordinates); diff --git a/src/geom/base.js b/src/geom/base.js new file mode 100644 index 0000000000..dc04754965 --- /dev/null +++ b/src/geom/base.js @@ -0,0 +1,38 @@ + +export const GeomBase = { + color: 'updateDraw', + size: 'repaint', + filter: 'updateDraw', + layer: '', + pickable: true, + setLayer(layer) { + this.layer = layer; + this.style = layer.get('styleOption'); + }, + getShape(type) { + return type; + }, + draw() { + const shape = this.getShape(); + this.Mesh = shape.Mesh(); + }, + // 更新geometry buffer; + updateDraw() { + + }, + repaint() { + + } +}; +export const shapeBae = { + geometryBuffer() { + }, + + geometry() {}, + + material() {}, + + mesh() { + + } +}; diff --git a/src/geom/buffer/point/text.js b/src/geom/buffer/point/text.js index 2552bdf894..9b2f5f50b8 100644 --- a/src/geom/buffer/point/text.js +++ b/src/geom/buffer/point/text.js @@ -29,12 +29,11 @@ function drawGlyph(layerData, fontAtlasManager) { const size = element.size; const pos = element.coordinates; let text = element.shape || ''; + text = text.toString(); const pen = { x: (-text.length * size) / 2, y: 0 }; - text = text.toString(); - for (let i = 0; i < text.length; i++) { const metric = mapping[text[i]]; const { x, y, width, height } = metric; diff --git a/src/geom/geom.js b/src/geom/geom.js deleted file mode 100644 index 9684109ea2..0000000000 --- a/src/geom/geom.js +++ /dev/null @@ -1,62 +0,0 @@ -const geom = { - point: { - symbol: [ 'circle', 'hexagon', 'triangle', 'diamond' ], - native: { - buffer: '', - geometry: 'PointGeometry', - material: 'PointMaterial' - }, - line: { - buffer: 'PointBuffer', - geometry: 'PolygonLine', - material: 'MeshlineMaterial' - }, - fill: { - buffer: 'PointBuffer', - geometry: 'PolygonGeometry', - material: 'PolygonMaterial' - }, - extrude: { - buffer: 'PointBuffer', - geometry: 'PolygonGeometry', - material: 'PolygonMaterial' - }, - extrudeline: { - buffer: 'PointBuffer', - geometry: 'PolygonLine', - material: 'MeshlineMaterial' - }, - pointGrid: { - buffer: 'pointGrid', - geometry: 'PolygonLine', - material: 'MeshlineMaterial' - - } - }, - line: { - shape: [ 'native' ] - }, - polygon: { - line: { - buffer: 'polygonLineBuffer', - geometry: 'PolygonLine', - material: 'MeshlineMaterial' - }, - fill: { - buffer: 'PolygonBuffer', - geometry: 'PolygonGeometry', - material: 'PolygonMaterial' - }, - extrude: { - buffer: 'PolygonBuffer', - geometry: 'PolygonGeometry', - material: 'PolygonMaterial' - }, - extrudeline: { - buffer: 'polygonLineBuffer', - geometry: 'PolygonLine', - material: 'MeshlineMaterial' - } - } -}; -export default geom; diff --git a/src/geom/material/heatmapMateial.js b/src/geom/material/heatmapMateial.js index 315d19c7b9..d1ff1c26e0 100644 --- a/src/geom/material/heatmapMateial.js +++ b/src/geom/material/heatmapMateial.js @@ -1,35 +1,58 @@ import * as THREE from '../../core/three'; import Material from './material'; import { getModule } from '../../util/shaderModule'; +export class HeatmapColorizeMaterial extends Material { + getDefaultParameters() { + return { + uniforms: { + u_intensity: { value: 1.0 }, + u_texture: { value: null }, + u_rampColors: { value: 0 }, + u_opacity: { value: 1 } + }, + defines: { -export function HeatmapIntensityMaterial(opt) { - const { vs, fs } = getModule('heatmap_intensity'); - const material = new Material({ - uniforms: { - u_intensity: { value: opt.intensity }, - u_radius: { value: opt.radius }, - u_zoom: { value: opt.zoom } - }, - vertexShader: vs, - fragmentShader: fs, - transparent: true, - depthTest: false, - blending: THREE.AdditiveBlending - }); - return material; + } + }; + } + constructor(_uniforms, _defines = {}, parameters) { + super(parameters); + const { uniforms, defines } = this.getDefaultParameters(); + const { vs, fs } = getModule('heatmap_color'); + this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms)); + this.type = 'HeatmapColorizeMaterial'; + this.defines = Object.assign(defines, _defines); + this.vertexShader = vs; + this.fragmentShader = fs; + this.transparent = true; + } } -export function HeatmapColorizeMaterial(opt) { - const { vs, fs } = getModule('heatmap_color'); - const material = new Material({ - uniforms: { - u_texture: { value: opt.texture }, - u_colorRamp: { value: opt.colorRamp }, - u_opacity: { value: opt.opacity } - }, - vertexShader: vs, - fragmentShader: fs, - transparent: true - }); - return material; +export class HeatmapIntensityMaterial extends Material { + getDefaultParameters() { + return { + uniforms: { + u_intensity: { value: 10.0 }, + u_zoom: { value: 4 }, + u_radius: { value: 10 } + }, + defines: { + + } + }; + } + constructor(_uniforms, _defines = {}, parameters) { + super(parameters); + const { uniforms, defines } = this.getDefaultParameters(); + const { vs, fs } = getModule('heatmap_intensity'); + this.uniforms = Object.assign(uniforms, this.setUniform(_uniforms)); + this.type = 'heatmap_intensity'; + this.defines = Object.assign(defines, _defines); + this.vertexShader = vs; + this.blending = THREE.AdditiveBlending; + this.fragmentShader = fs; + this.depthTest = false; + this.transparent = true; + } } + diff --git a/src/geom/material/lineMaterial.js b/src/geom/material/lineMaterial.js index 2adf7377c4..91a1c4d204 100644 --- a/src/geom/material/lineMaterial.js +++ b/src/geom/material/lineMaterial.js @@ -55,7 +55,8 @@ export function MeshLineMaterial(options) { }, vertexShader: vs, fragmentShader: fs, - transparent: true + transparent: true, + blending: THREE.AdditiveBlending }); return material; } diff --git a/src/geom/material/material.js b/src/geom/material/material.js index 33e9d25fd0..d6b52304ce 100644 --- a/src/geom/material/material.js +++ b/src/geom/material/material.js @@ -19,7 +19,7 @@ export default class Material extends THREE.ShaderMaterial { } return uniforms; } - upDateUninform(option) { + updateUninform(option) { for (const key in option) { if (key.substr(0, 2) === 'u_') { this.setUniformsValue(key, option[key]); diff --git a/src/geom/shader/heatmap_colorize_frag.glsl b/src/geom/shader/heatmap_colorize_frag.glsl index 4cd75f67ed..4d5af730bc 100644 --- a/src/geom/shader/heatmap_colorize_frag.glsl +++ b/src/geom/shader/heatmap_colorize_frag.glsl @@ -1,11 +1,11 @@ uniform sampler2D u_texture; -uniform sampler2D u_colorRamp; +uniform sampler2D u_rampColors; uniform float u_opacity; varying vec2 v_uv; void main(){ float intensity = texture2D(u_texture,v_uv).r; - vec4 color = texture2D(u_colorRamp,vec2(0.5,1.0-intensity)); + vec4 color = texture2D(u_rampColors,vec2(0.5,1.0-intensity)); gl_FragColor = color; gl_FragColor.a = color.a * smoothstep(0.,0.05,intensity) * u_opacity; diff --git a/src/geom/shader/line_vert.glsl b/src/geom/shader/line_vert.glsl index e214d46a61..c54c2ac07f 100644 --- a/src/geom/shader/line_vert.glsl +++ b/src/geom/shader/line_vert.glsl @@ -16,7 +16,8 @@ void main() { } #ifdef ANIMATE vTime = 1.0- (mod(u_time*50.,3600.)- position.z) / 100.; + // vTime = 1.0- (28800. + mod(u_time* 10.,28800.)- position.z / 1000.) / 100.; #endif - gl_Position = matModelViewProjection * vec4(position.xy,0., 1.0); + gl_Position = matModelViewProjection * vec4(position.xy, 0., 1.0); worldId = id_toPickColor(pickingId); } \ No newline at end of file diff --git a/src/geom/shader/meshline_vert.glsl b/src/geom/shader/meshline_vert.glsl index dcb775d27f..76c5d8b73a 100644 --- a/src/geom/shader/meshline_vert.glsl +++ b/src/geom/shader/meshline_vert.glsl @@ -20,7 +20,7 @@ uniform float u_trailLength; void main() { mat4 matModelViewProjection = projectionMatrix * modelViewMatrix; - vec3 pointPos = position.xyz + vec3(normal * a_size * pow(2.0,20.0-u_zoom) / 2.0 * a_miter); + vec3 pointPos = vec3(position.xy,0.) + vec3(normal * a_size * pow(2.0,20.0-u_zoom) / 2.0 * a_miter); v_color = a_color; if(pickingId == u_activeId) { v_color = u_activeColor; @@ -30,8 +30,9 @@ void main() { float alpa =1.0 - fract( mod(1.0- a_distance,u_interval)* (1.0/u_interval) + u_time / u_duration); alpa = (alpa + u_trailLength -1.0) / u_trailLength; vTime = clamp(alpa,0.,1.); + // vTime = (28800. + mod(u_time* 1000.,28800.)- position.z) / 100.; #endif worldId = id_toPickColor(pickingId); -gl_Position = matModelViewProjection * vec4(pointPos, 1.0); + gl_Position = matModelViewProjection * vec4(pointPos.xy, 0., 1.0); } \ No newline at end of file diff --git a/src/geom/shader/point_meshLine_vert.glsl b/src/geom/shader/point_meshLine_vert.glsl index 3493808811..d4773d23e6 100644 --- a/src/geom/shader/point_meshLine_vert.glsl +++ b/src/geom/shader/point_meshLine_vert.glsl @@ -15,7 +15,7 @@ varying vec4 v_color; void main() { mat4 matModelViewProjection = projectionMatrix * modelViewMatrix; float scale = pow(2.0,(20.0 - u_zoom)); - vec3 newposition = position + (a_size + vec3(u_strokeWidth/2.,u_strokeWidth/2.,0)) * scale* a_shape; + vec3 newposition = position + (a_size + vec3(u_strokeWidth/2.,u_strokeWidth/2.,0)) * scale* a_shape + vec3(0., a_size.y * scale / 4., 0.);; #ifdef ANIMATE vTime = 1.0- (mod(u_time*50.,3600.)- position.z) / 100.; #endif diff --git a/src/geom/shader/polygon_vert.glsl b/src/geom/shader/polygon_vert.glsl index 36474bde79..2015bee5f5 100644 --- a/src/geom/shader/polygon_vert.glsl +++ b/src/geom/shader/polygon_vert.glsl @@ -21,7 +21,7 @@ void main() { vec3 newposition = position; // newposition.x -= 128.0; #ifdef SHAPE - newposition =position + a_size * scale* a_shape; + newposition =position + a_size * scale* a_shape + vec3(0., a_size.y * scale / 4., 0.); #endif v_texCoord = faceUv; if(normal == vec3(0.,0.,1.)){ diff --git a/src/geom/shader/text_frag.glsl b/src/geom/shader/text_frag.glsl index f29d97a435..7fe59c4257 100644 --- a/src/geom/shader/text_frag.glsl +++ b/src/geom/shader/text_frag.glsl @@ -7,9 +7,11 @@ uniform float u_buffer; uniform float u_gamma; uniform float u_opacity; varying vec2 v_texcoord; +varying float v_size; void main(){ float dist=texture2D(u_texture,vec2(v_texcoord.x,v_texcoord.y)).a; float alpha; + if(u_strokeWidth==0.){ alpha=smoothstep(u_buffer-u_gamma,u_buffer+u_gamma,dist); gl_FragColor=vec4(v_color.rgb,alpha*v_color.a); diff --git a/src/geom/shader/text_vert.glsl b/src/geom/shader/text_vert.glsl index 51ff8a0150..f4290df289 100644 --- a/src/geom/shader/text_vert.glsl +++ b/src/geom/shader/text_vert.glsl @@ -7,12 +7,14 @@ uniform vec2 u_textTextureSize;// 纹理大小 uniform vec2 u_glSize; varying vec2 v_texcoord; varying vec4 v_color; +varying float v_size; uniform float u_activeId; uniform vec4 u_activeColor; void main(){ mat4 matModelViewProjection=projectionMatrix*modelViewMatrix; vec4 cur_position=matModelViewProjection*vec4(position.xy,0,1); + v_size = 12. / u_glSize.x; gl_Position=cur_position/cur_position.w+vec4((a_txtOffsets+a_txtsize)/u_glSize*2.,0.,0.); v_color=vec4(a_color.rgb,a_color.a*u_opacity); if(pickingId==u_activeId){ diff --git a/src/geom/shape/line.js b/src/geom/shape/line.js index ee6ea123e2..f171c1f86c 100644 --- a/src/geom/shape/line.js +++ b/src/geom/shape/line.js @@ -1,60 +1,4 @@ -import { Vector3 } from '../../core/three'; import getNormal from 'polyline-normals'; -/** - * shape meshLine - * @param {array} geo 坐标点 - * @param {object} props 属性数据 - * @param {int} index 原始数据ndex - * @return {object} 顶点坐标,索引坐标 - */ -export function meshLine(geo, props, index) { - const dataLength = geo.length; - const width = props.size[0] * 50 || 100; - const dem = props.size[1] || 0; - const posArray = []; - const indexArray = []; - const points = []; - for (let i = 0; i < dataLength; i++) { - let previous = (i === 0) ? geo[0] : geo[i - 1]; - let next = (i === dataLength - 1) ? geo[dataLength - 1] : geo[i + 1]; - let current = geo[i]; - previous = [ previous[0], previous[1], 0 ]; - next = [ next[0], next[1], 0 ]; - current = [ current[0], current[1], 0 ]; - - let dir = null; - if (i === 0 || i === dataLength - 1) { - dir = new Vector3(1, 1, 1); - } else { - const dir1 = new Vector3(); - const dir2 = new Vector3(); - dir = new Vector3(); - dir1.subVectors(new Vector3(...current), new Vector3(...previous)).normalize(); - dir2.subVectors(new Vector3(...next), new Vector3(...current)).normalize(); - dir.addVectors(dir1, dir2).normalize(); - } - let normal = [ -dir.y, dir.x, 0 ]; - normal = [ normal[0] * width, normal[1] * width, 0 ]; - const n1 = [ normal[0], normal[1], 0 ]; - const n2 = [ -normal[0], -normal[1], 0 ]; - const p1 = new Vector3(); - const p2 = new Vector3(); - p1.addVectors(new Vector3(...current), new Vector3(...n1)); - p2.addVectors(new Vector3(...current), new Vector3(...n2)); - points.push([ p1.x, p1.y, dem ], [ p2.x, p2.y, dem ]); - }// end of for - for (let i = 0; i < points.length - 2; i += 2) { - const ct = i; - const cb = i + 1; - const nt = i + 2; - const nb = i + 3; - posArray.push(points[ct], points[cb], points[nt]); - posArray.push(points[nt], points[cb], points[nb]); - indexArray.push(index, index, index); - indexArray.push(index, index, index); - } - return { positions: posArray, indexes: indexArray }; -} /** * shape arc @@ -150,7 +94,7 @@ export function Line(path, props, positionsIndex) { indexArray[c++] = i + 2; indexArray[c++] = i + 3; } - point[2] = size[1]; + // point[2] = size[1]; positions.push(...point); positions.push(...point); diff --git a/src/geom/shape/path.js b/src/geom/shape/path.js index d4c2842a50..a693484d9c 100644 --- a/src/geom/shape/path.js +++ b/src/geom/shape/path.js @@ -33,7 +33,7 @@ export function polygonPath(pointCount) { const step = Math.PI * 2 / pointCount; const line = []; for (let i = 0; i < pointCount; i++) { - line.push(step * i); + line.push(step * i - Math.PI / 12); } const path = line.map(t => { const x = Math.sin(t + Math.PI / 4), diff --git a/src/global.js b/src/global.js index 83d6ab5fc5..f33666f102 100644 --- a/src/global.js +++ b/src/global.js @@ -3,6 +3,7 @@ * @author dxq613 */ // 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', scene: { @@ -11,24 +12,28 @@ const Global = { center: [ 107.622, 39.266 ], minZoom: 0, maxZoom: 22, - pitch: 0 + pitch: 0, + hash: false }, - trackable: true, animate: true, - snapArray: [ 0, 1, 2, 4, 5, 10 ], height: 0, activeColor: '#2f54eb', colors: [ 'rgb(103,0,31)', 'rgb(178,24,43)', 'rgb(214,96,77)', 'rgb(244,165,130)', 'rgb(253,219,199)', 'rgb(247,247,247)', 'rgb(209,229,240)', 'rgb(146,197,222)', 'rgb(67,147,195)', 'rgb(33,102,172)', 'rgb(5,48,97)' ], - // 指定固定 tick 数的逼近值 - snapCountArray: [ 0, 1, 1.2, 1.5, 1.6, 2, 2.2, 2.4, 2.5, 3, 4, 5, 6, 7.5, 8, 10 ], size: 10000, shape: 'circle', + snapArray: [ 0, 1, 2, 4, 5, 10 ], pointShape: { '2d': [ 'circle', 'square', 'hexagon', 'triangle' ], '3d': [ 'cylinder', 'triangleColumn', 'hexagonColumn', 'squareColumn' ] }, sdfHomeUrl: 'https://sdf.amap.com', - scales: { + scales: { }, + textStyle: { + fontSize: 12, + fill: '#ccc', + textBaseline: 'middle', + fontFamily: FONT_FAMILY, + textAlign: 'center' } }; diff --git a/src/index.js b/src/index.js index f791d2df22..399f8df9ec 100755 --- a/src/index.js +++ b/src/index.js @@ -2,10 +2,19 @@ // import Util from './util'; import Scene from './core/scene'; import Global from './global'; - +import Source from './core/source'; +import { registerParser, registerTransform } from './source'; +import { registerInteraction, getInteraction } from './interaction'; +import { registerLayer } from './layer'; const version = Global.version; export { version, - Scene + Scene, + Source, + registerParser, + registerTransform, + registerLayer, + registerInteraction, + getInteraction }; diff --git a/src/interaction/active.js b/src/interaction/active.js new file mode 100644 index 0000000000..d4e8807d45 --- /dev/null +++ b/src/interaction/active.js @@ -0,0 +1,16 @@ +import Interaction from './base'; +export default class Active extends Interaction { + constructor(cfg) { + super({ + processEvent: 'mousemove', + resetEvent: 'mouseleave', + ...cfg + }); + } + process(ev) { + this.layer._addActiveFeature(ev); + } + reset() { + this.layer._resetStyle(); + } +} diff --git a/src/interaction/base.js b/src/interaction/base.js new file mode 100644 index 0000000000..2103c1a5cd --- /dev/null +++ b/src/interaction/base.js @@ -0,0 +1,87 @@ +import * as _ from '@antv/util'; +const EVENT_TYPES = [ 'start', 'process', 'end', 'reset' ]; + +export default class Interaction { + constructor(cfg) { + const defaultCfg = this._getDefaultCfg(); + Object.assign(this, defaultCfg, cfg); + this._eventHandlers = []; + this._bindEvents(); + } + _getDefaultCfg() { + return { + startEvent: 'mousedown', + processEvent: 'mousemove', + endEvent: 'mouseup', + resetEvent: 'dblclick' + }; + } + _start(ev) { + this.preStart(ev); + this.start(ev); + this.afterStart(ev); + } + + preStart() {} + + start() {} + + afterStart() {} + + _process(ev) { + this.preProcess(ev); + this.process(ev); + this.afterProcess(ev); + } + + preProcess() {} + + process() { + } + + afterProcess() {} + + _end(ev) { + this.preEnd(ev); + this.end(ev); + this.afterEnd(ev); + } + preEnd() {} + + end() {} + + afterEnd() {} + + _reset() { + this.preReset(); + this.reset(); + this.afterReset(); + } + + preReset() {} + + reset() {} + + afterReset() {} + + _bindEvents() { + _.each(EVENT_TYPES, type => { + const eventName = this[`${type}Event`]; + const handler = _.wrapBehavior(this, `_${type}`); + this.layer.on(eventName, handler); + this._eventHandlers.push({ type: eventName, handler }); + }); + } + + _unbindEvents() { + const eventHandlers = this._eventHandlers; + _.each(eventHandlers, eh => { + this.layer.off(eh.type, eh.handler); + }); + } + + destory() { + this._unbindEvents(); + this._reset(); + } +} diff --git a/src/interaction/factory.js b/src/interaction/factory.js new file mode 100644 index 0000000000..46143b74d0 --- /dev/null +++ b/src/interaction/factory.js @@ -0,0 +1,14 @@ +export const INTERACTION_MAP = {}; + +export const getInteraction = type => { + return INTERACTION_MAP[type]; +}; + +export const registerInteraction = (type, ctor) => { + // 注册的时候,需要校验 type 重名,不区分大小写 + if (getInteraction(type)) { + throw new Error(`Interaction type '${type}' existed.`); + } + // 存储到 map 中 + INTERACTION_MAP[type] = ctor; +}; diff --git a/src/interaction/hash.js b/src/interaction/hash.js new file mode 100644 index 0000000000..b06188a6f7 --- /dev/null +++ b/src/interaction/hash.js @@ -0,0 +1,58 @@ +import Interaction from './base'; +import throttle from '@antv/util/src/throttle.js'; +export default class Hash extends Interaction { + constructor(cfg) { + super({ + endEvent: 'camerachange', + ...cfg + }); + window.addEventListener('hashchange', this._onHashChange.bind(this), false); + this._updateHash = throttle(this._updateHashUnthrottled.bind(this), 20 * 1000 / 100); + } + end() { + this._updateHash(); + } + reset() { + this.layer._resetStyle(); + } + _getHashString() { + const center = this.layer.getCenter(), + zoom = Math.round(this.layer.getZoom() * 100) / 100, + // derived from equation: 512px * 2^z / 360 / 10^d < 0.5px + precision = Math.ceil((zoom * Math.LN2 + Math.log(512 / 360 / 0.5)) / Math.LN10), + m = Math.pow(10, precision), + lng = Math.round(center.lng * m) / m, + lat = Math.round(center.lat * m) / m, + bearing = this.layer.getRotation(), + pitch = this.layer.getPitch(); + let hash = ''; + hash += `#${zoom}/${lat}/${lng}`; + if (bearing || pitch) hash += (`/${Math.round(bearing * 10) / 10}`); + if (pitch) hash += (`/${Math.round(pitch)}`); + return hash; + } + _onHashChange() { + const loc = window.location.hash.replace('#', '').split('/'); + if (loc.length >= 3) { + this.layer.setStatus({ + center: [ +loc[2], +loc[1] ], + zoom: +loc[0], + bearing: +(loc[3] || 0), + pitch: +(loc[4] || 0) + }); + return true; + } + return false; + } + _updateHashUnthrottled() { + const hash = this._getHashString(); + window.history.replaceState(window.history.state, '', hash); + } + destory() { + window.removeEventListener('hashchange', this._onHashChange, false); + this.layer.off('camerachange', this._updateHash); + clearTimeout(this._updateHash()); + + return this; + } +} diff --git a/src/interaction/index.js b/src/interaction/index.js new file mode 100644 index 0000000000..54beb99308 --- /dev/null +++ b/src/interaction/index.js @@ -0,0 +1,11 @@ +import Interaction from './base'; +import Active from './active'; +import Select from './select'; +import Hash from './hash'; +import { getInteraction, registerInteraction } from './factory'; + +registerInteraction('active', Active); +registerInteraction('select', Select); +registerInteraction('hash', Hash); + +export { Interaction, registerInteraction, getInteraction }; diff --git a/src/interaction/select.js b/src/interaction/select.js new file mode 100644 index 0000000000..2cf5e4d001 --- /dev/null +++ b/src/interaction/select.js @@ -0,0 +1,12 @@ +import Interaction from './base'; +export default class Select extends Interaction { + constructor(cfg) { + super({ + processEvent: 'click', + ...cfg + }); + } + process(ev) { + this.layer._addActiveFeature(ev); + } +} diff --git a/src/layer/heatmapLayer.js b/src/layer/heatmapLayer.js index ae8e34b1df..e47b5656fd 100644 --- a/src/layer/heatmapLayer.js +++ b/src/layer/heatmapLayer.js @@ -10,12 +10,7 @@ export default class HeatMapLayer extends Layer { this.shapeType = type; return this; } - render() { - this._prepareRender(); - return this; - } - _prepareRender() { - this.init(); + draw() { this.type = 'heatmap'; switch (this.shapeType) { case 'grid' : @@ -56,10 +51,4 @@ export default class HeatMapLayer extends Layer { this.add(girdMesh); } - // afterRender() { - // if (this.shapeType !== 'grid' && this.shapeType !== 'hexagon') { - // updateIntensityPass(this); - // } - // } - } diff --git a/src/layer/imageLayer.js b/src/layer/imageLayer.js index 92e9a60b59..95f4d49767 100644 --- a/src/layer/imageLayer.js +++ b/src/layer/imageLayer.js @@ -4,8 +4,7 @@ import ImageBuffer from '../geom/buffer/image'; // import ImageGeometry from '../geom/bufferGeometry/image'; import ImageMaterial from '../geom/material/imageMaterial'; export default class imageLayer extends Layer { - render() { - this.init(); + draw() { this.type = 'image'; const source = this.layerSource; const { opacity } = this.get('styleOptions'); diff --git a/src/layer/index.js b/src/layer/index.js index f7754776e2..02e72edceb 100644 --- a/src/layer/index.js +++ b/src/layer/index.js @@ -14,4 +14,5 @@ registerLayer('RasterLayer', RasterLayer); registerLayer('HeatmapLayer', HeatmapLayer); export { LAYER_MAP } from './factory'; +export { registerLayer }; diff --git a/src/layer/lineLayer.js b/src/layer/lineLayer.js index b31f8e96fa..4a2046686e 100644 --- a/src/layer/lineLayer.js +++ b/src/layer/lineLayer.js @@ -1,112 +1,45 @@ import Layer from '../core/layer'; -import * as THREE from '../core/three'; +import DrawLine from './render/line/drawMeshLine'; +import DrawArc from './render/line/drawArc'; import { LineBuffer } from '../geom/buffer/index'; -import { LineMaterial, ArcLineMaterial, MeshLineMaterial, DashLineMaterial } from '../geom/material/lineMaterial'; export default class LineLayer extends Layer { shape(type) { this.shapeType = type; return this; } - render() { - this.type = 'polyline'; - this.init(); - const layerData = this.layerData; - const style = this.get('styleOptions'); - const buffer = this._buffer = new LineBuffer({ - layerData, - shapeType: this.shapeType, - style - }); - const { opacity } = this.get('styleOptions'); - const animateOptions = this.get('animateOptions'); - const activeOption = this.get('activedOptions'); - const geometry = new THREE.BufferGeometry(); - const { attributes } = buffer; - - if (this.shapeType === 'arc') { - geometry.setIndex(attributes.indexArray); - geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); - geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.positions, 3)); - geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); - geometry.addAttribute('a_instance', new THREE.Float32BufferAttribute(attributes.instances, 4)); - geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.sizes, 1)); - const material = new ArcLineMaterial({ - u_opacity: opacity, - u_zoom: this.scene.getZoom(), - activeColor: activeOption.fill - }); - const mesh = new THREE.Mesh(geometry, material); - this.add(mesh); - } else if (this.shapeType === 'line') { - - geometry.setIndex(attributes.indexArray); - geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); - geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.positions, 3)); - geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); - geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.sizes, 1)); - geometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normal, 3)); - geometry.addAttribute('a_miter', new THREE.Float32BufferAttribute(attributes.miter, 1)); - geometry.addAttribute('a_distance', new THREE.Float32BufferAttribute(attributes.attrDistance, 1)); - const lineType = style.lineType; - let material; - - if (lineType !== 'dash') { - - material = new MeshLineMaterial({ - u_opacity: opacity, - u_zoom: this.scene.getZoom(), - activeColor: activeOption.fill - }); - - if (animateOptions.enable) { - - material.setDefinesvalue('ANIMATE', true); - this.scene.startAnimate(); - const { duration, interval, trailLength, repeat = Infinity } = animateOptions; - this.animateDuration = this.scene._engine.clock.getElapsedTime() + duration * repeat; - material.upDateUninform({ - u_duration: duration, - u_interval: interval, - u_trailLength: trailLength - }); - - - } - } else { - geometry.addAttribute('a_distance', new THREE.Float32BufferAttribute(attributes.attrDistance, 1)); - material = new DashLineMaterial({ - u_opacity: opacity, - u_zoom: this.scene.getZoom(), - activeColor: activeOption.fill - }); - } - const mesh = new THREE.Mesh(geometry, material); - this.add(mesh); - } else { // 直线 - geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); - geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); - geometry.addAttribute('a_color', new THREE.Float32BufferAttribute(attributes.colors, 4)); - const material = new LineMaterial({ - u_opacity: opacity, - u_time: 0, - activeColor: activeOption.fill - }); - if (animateOptions.enable) { - material.setDefinesvalue('ANIMATE', true); - this.scene.startAnimate(); - } - - const mesh = new THREE.LineSegments(geometry, material); - this.add(mesh); - } - return this; - } preRender() { - if (this.animateDuration > 0 && this.animateDuration < this.scene._engine.clock.getElapsedTime()) { + if ( + this.animateDuration > 0 && + this.animateDuration < this.scene._engine.clock.getElapsedTime() + ) { this.layerMesh.material.setDefinesvalue('ANIMATE', false); this.emit('animateEnd'); this.scene.stopAnimate(); this.animateDuration = Infinity; } } + 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); + } + } } diff --git a/src/layer/pointLayer.js b/src/layer/pointLayer.js index de3cbf3a57..1e6efc7ae3 100644 --- a/src/layer/pointLayer.js +++ b/src/layer/pointLayer.js @@ -14,21 +14,8 @@ const { pointShape } = Global; */ export default class PointLayer extends Layer { - render() { + draw() { this.type = 'point'; - this.init(); - if (!this._hasRender) { - this._prepareRender(this.shapeType); - this._hasRender = true; - } else { - this._initAttrs(); - this._needUpdateFilter || this._needUpdateColor - ? this._updateFilter() - : null; - } - return this; - } - _prepareRender() { const { stroke, fill } = this.get('styleOptions'); const style = this.get('styleOptions'); const activeOption = this.get('activedOptions'); @@ -37,6 +24,7 @@ export default class PointLayer extends Layer { activeColor: activeOption.fill }; const pointShapeType = this._getShape(); + this.shapeType = pointShapeType; switch (pointShapeType) { case 'fill': { // 填充图形 if (fill !== 'none') { @@ -112,4 +100,33 @@ export default class PointLayer extends Layer { } return 'text'; } + zoomchange(ev) { + super.zoomchange(ev); + this._updateData(); + } + dragend(ev) { + super.dragend(ev); + this._updateData(); + + } + _updateData() { + if (this.layerSource.get('isCluster')) { + const bounds = this.scene.getBounds().toBounds(); + const SW = bounds.getSouthWest(); + const NE = bounds.getNorthEast(); + const zoom = this.scene.getZoom(); + const step = Math.max(NE.lng - SW.lng, NE.lat - SW.lat) / 2; + const bbox = [ SW.lng, SW.lat, NE.lng, NE.lat ]; + // const bbox = [ SW.lng - step, SW.lat - step, NE.lng + step, NE.lat + step ]; + const cfg = this.layerSource.get('cluster'); + const preBox = cfg.bbox; + const preZoom = cfg.zoom; + if (!(preBox && preBox[0] < bbox[0] && preBox[1] < bbox[1] && preBox[2] > bbox[2] && preBox[3] < bbox[3] && // 当前范围在范围内 + (Math.abs(zoom - preZoom)) < 0.5)) { + const newbbox = [ SW.lng - step, SW.lat - step, NE.lng + step, NE.lat + step ]; + this.layerSource.updateCusterData(Math.floor(zoom - 1), newbbox); + this.repaint(); + } + } + } } diff --git a/src/layer/polygonLayer.js b/src/layer/polygonLayer.js index 17c95244ef..e25c052038 100644 --- a/src/layer/polygonLayer.js +++ b/src/layer/polygonLayer.js @@ -6,19 +6,7 @@ export default class PolygonLayer extends Layer { this.shape = type; return this; } - render() { - if (!this._hasRender) { // 首次渲染 - this._hasRender = true; - this._prepareRender(); - } else { - - this._initAttrs(); - (this._needUpdateFilter || this._needUpdateColor) ? this._updateFilter(this.layerMesh) : null; - // TODO update Style; - } - return this; - } - _prepareRender() { + draw() { this.init(); this.type = 'polygon'; this._buffer = new PolygonBuffer({ @@ -29,7 +17,6 @@ export default class PolygonLayer extends Layer { } update() { this.updateFilter(this.layerMesh); - // 动态更新相关属性 } _getLayerRender() { const animateOptions = this.get('animateOptions'); diff --git a/src/layer/rasterLayer.js b/src/layer/rasterLayer.js index f3816da695..40075e54c0 100644 --- a/src/layer/rasterLayer.js +++ b/src/layer/rasterLayer.js @@ -5,9 +5,8 @@ import { RasterBuffer } from '../geom/buffer/raster'; export default class RasterLayer extends Layer { - render() { + draw() { this.type = 'raster'; - this.init(); const source = this.layerSource; // 加载 完成事件 const styleOptions = this.get('styleOptions'); diff --git a/src/layer/render/heatmap/heatmap.js b/src/layer/render/heatmap/heatmap.js index b035f38942..c03d7f9b46 100644 --- a/src/layer/render/heatmap/heatmap.js +++ b/src/layer/render/heatmap/heatmap.js @@ -10,7 +10,8 @@ import * as THREE from '../../../core/three'; export function drawHeatmap(layer) { const colors = layer.get('styleOptions').rampColors; - layer.colorRamp = createColorRamp(colors); + + layer.rampColors = createColorRamp(colors); const heatmap = new heatmapPass(layer); const copy = new copyPass(layer); copy.renderToScreen = true; @@ -18,31 +19,42 @@ export function drawHeatmap(layer) { composer.addPass(heatmap); composer.addPass(copy); layer.add(composer); + layer.scene._engine.update(); + layer._updateStyle = style => { + if (style.rampColors) { + style.rampColors = createColorRamp(style.rampColors); + } + const newOption = { }; + for (const key in style) { + newOption['u_' + key] = style[key]; + } + heatmap.scene.children[0].material.updateUninform(newOption); + copy.scene.children[0].material.updateUninform(newOption); + }; } - function heatmapPass(layer) { const scene = new THREE.Scene(); const style = layer.get('styleOptions'); const data = layer.layerData; const camera = layer.scene._engine._camera; - // get attributes data + // get attributes data const buffer = new HeatmapBuffer({ data }); const attributes = buffer.attributes; - // create geometery + // create geometery const geometry = new THREE.BufferGeometry(); - // geometry.setIndex(attributes.indices); + // geometry.setIndex(attributes.indices); geometry.addAttribute('position', new THREE.Float32BufferAttribute(attributes.vertices, 3)); geometry.addAttribute('a_dir', new THREE.Float32BufferAttribute(attributes.dirs, 2)); geometry.addAttribute('a_weight', new THREE.Float32BufferAttribute(attributes.weights, 1)); const material = new HeatmapIntensityMaterial({ - intensity: style.intensity, - radius: style.radius, - zoom: layer.scene.getZoom() - }); + u_intensity: style.intensity, + u_radius: style.radius, + u_zoom: layer.scene.getZoom() + }, {}); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); scene.onBeforeRender = () => { // 每次渲染前改变状态 @@ -55,9 +67,9 @@ function heatmapPass(layer) { function copyPass(layer) { const style = layer.get('styleOptions'); const material = new HeatmapColorizeMaterial({ - colorRamp: layer.colorRamp, - opacity: style.opacity - }); + u_rampColors: layer.rampColors, + u_opacity: style.opacity + }, {}); const copyPass = new ShaderPass(material, 'u_texture'); return copyPass; } diff --git a/src/layer/render/line/drawArc.js b/src/layer/render/line/drawArc.js index 246b8b55aa..5324e0c821 100644 --- a/src/layer/render/line/drawArc.js +++ b/src/layer/render/line/drawArc.js @@ -1,6 +1,7 @@ import * as THREE from '../../../core/three'; import { ArcLineMaterial } from '../../../geom/material/lineMaterial'; -export default function DrawArcLine(attributes, style) { +export default function DrawArcLine(attributes, cfg, layer) { + const { style, activeOption } = cfg; const { opacity, zoom } = style; const geometry = new THREE.BufferGeometry(); geometry.setIndex(attributes.indexArray); @@ -10,10 +11,11 @@ export default function DrawArcLine(attributes, style) { geometry.addAttribute('a_size', new THREE.Float32BufferAttribute(attributes.sizes, 1)); const lineMaterial = new ArcLineMaterial({ u_opacity: opacity, - u_zoom: zoom + u_zoom: zoom, + activeColor: activeOption.fill }, { SHAPE: false }); const arcMesh = new THREE.Mesh(geometry, lineMaterial); - return arcMesh; + layer.add(arcMesh); } diff --git a/src/layer/render/line/drawLine.js b/src/layer/render/line/drawMeshLine.js similarity index 55% rename from src/layer/render/line/drawLine.js rename to src/layer/render/line/drawMeshLine.js index e0a6f7a228..37eaf2ad25 100644 --- a/src/layer/render/line/drawLine.js +++ b/src/layer/render/line/drawMeshLine.js @@ -1,7 +1,7 @@ import * as THREE from '../../../core/three'; import { MeshLineMaterial } from '../../../geom/material/lineMaterial'; -export default function DrawLine(attributes, style) { - const { opacity, zoom, animate, duration, interval, trailLength } = style; +export default function DrawLine(attributes, cfg, layer) { + const { style, animateOptions, activeOption, zoom } = cfg; const geometry = new THREE.BufferGeometry(); geometry.setIndex(attributes.indexArray); geometry.addAttribute('pickingId', new THREE.Float32BufferAttribute(attributes.pickingIds, 1)); @@ -11,17 +11,34 @@ export default function DrawLine(attributes, style) { geometry.addAttribute('normal', new THREE.Float32BufferAttribute(attributes.normal, 3)); geometry.addAttribute('a_miter', new THREE.Float32BufferAttribute(attributes.miter, 1)); geometry.addAttribute('a_distance', new THREE.Float32BufferAttribute(attributes.attrDistance, 1)); + const lineMaterial = new MeshLineMaterial({ - u_opacity: opacity, + u_opacity: style.opacity, u_zoom: zoom, - u_duration: duration, - u_interval: interval, - u_trailLength: trailLength, - u_time: 0 + u_time: 0, + activeColor: activeOption.fill }, { SHAPE: false, - ANIMATE: animate + ANIMATE: false }); - const arcMesh = new THREE.Mesh(geometry, lineMaterial); - return arcMesh; + + const lineMesh = new THREE.Mesh(geometry, lineMaterial); + if (animateOptions.enable) { + layer.scene.startAnimate(); + const { + duration = 2, + interval = 0.5, + trailLength = 0.5, + repeat = Infinity + } = animateOptions; + layer.animateDuration = + layer.scene._engine.clock.getElapsedTime() + duration * repeat; + lineMaterial.updateUninform({ + u_duration: duration, + u_interval: interval, + u_trailLength: trailLength + }); + lineMaterial.setDefinesvalue('ANIMATE', true); + } + layer.add(lineMesh); } diff --git a/src/layer/render/point/drawText.js b/src/layer/render/point/drawText.js index 0604938e61..32fa9a28d5 100644 --- a/src/layer/render/point/drawText.js +++ b/src/layer/render/point/drawText.js @@ -37,7 +37,7 @@ export default function DrawText(attributes, style) { attributes.fontAtlas.width, attributes.fontAtlas.height ], - u_gamma: 0.2, + u_gamma: (1.0 / 12.0) * (1.4142135623730951 / (2.0)), u_buffer: 0.75, u_opacity: opacity, u_glSize: [ width, height ], diff --git a/src/layer/render/polygon/drawAnimate.js b/src/layer/render/polygon/drawAnimate.js index e8a4883bb5..1e7bb408fa 100644 --- a/src/layer/render/polygon/drawAnimate.js +++ b/src/layer/render/polygon/drawAnimate.js @@ -25,7 +25,7 @@ export default function DrawAnimate(attributes, style) { } DrawAnimate.prototype.updateStyle = function(style) { - this.fillPolygonMesh.material.upDateUninform({ + this.fillPolygonMesh.material.updateUninform({ u_opacity: style.opacity, u_baseColor: style.baseColor, u_brightColor: style.brightColor, diff --git a/src/layer/tile/imageTile.js b/src/layer/tile/imageTile.js new file mode 100644 index 0000000000..c14a052e01 --- /dev/null +++ b/src/layer/tile/imageTile.js @@ -0,0 +1,137 @@ +// import * as THREE from '../../core/three'; +// import Tile from './tile'; +// export default class ImageTile extends Tile { +// constructor(layer, z, x, y) { + +// } +// 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); + +// const image = document.createElement('img'); + +// image.addEventListener('load', event => { +// const texture = new THREE.Texture(); + +// texture.image = image; +// texture.needsUpdate = true; + +// // Silky smooth images when tilted +// texture.magFilter = THREE.LinearFilter; +// texture.minFilter = THREE.LinearMipMapLinearFilter; + +// // TODO: Set this to renderer.getMaxAnisotropy() / 4 +// texture.anisotropy = 4; + +// texture.needsUpdate = true; + +// // Something went wrong and the tile or its material is missing +// // +// // Possibly removed by the cache before the image loaded +// if (!this._mesh || !this._mesh.children[0] || !this._mesh.children[0].material) { +// return; +// } + +// this._mesh.children[0].material.map = texture; +// this._mesh.children[0].material.needsUpdate = true; + +// this._texture = texture; +// this._ready = true; +// }, false); + +// // image.addEventListener('progress', event => {}, false); +// // image.addEventListener('error', event => {}, false); + +// image.crossOrigin = ''; + +// // Load image +// image.src = url; + +// this._image = image; +// } + +// _createMesh() { +// // Something went wrong and the tile +// // +// // Possibly removed by the cache before loaded +// if (!this._center) { +// return; +// } + +// const mesh = new THREE.Object3D(); +// const geom = new THREE.PlaneBufferGeometry(this._side, this._side, 1); + +// let material; +// if (!this._world._environment._skybox) { +// material = new THREE.MeshBasicMaterial({ +// depthWrite: false +// }); + +// // const material = new THREE.MeshPhongMaterial({ +// // depthWrite: false +// // }); +// } else { +// // Other MeshStandardMaterial settings +// // +// // material.envMapIntensity will change the amount of colour reflected(?) +// // from the environment map–can be greater than 1 for more intensity + +// material = new THREE.MeshStandardMaterial({ +// depthWrite: false +// }); +// material.roughness = 1; +// material.metalness = 0.1; +// material.envMap = this._world._environment._skybox.getRenderTarget(); +// } + +// const localMesh = new THREE.Mesh(geom, material); +// localMesh.rotation.x = -90 * Math.PI / 180; + +// localMesh.receiveShadow = true; + +// mesh.add(localMesh); +// mesh.renderOrder = 0.1; + +// mesh.position.x = this._center[0]; +// mesh.position.z = this._center[1]; + +// // const box = new BoxHelper(localMesh); +// // mesh.add(box); +// // +// // mesh.add(this._createDebugMesh()); + +// return mesh; +// } +// _abortRequest() { +// if (!this._image) { +// return; +// } + +// this._image.src = ''; +// } + +// destroy() { +// // Cancel any pending requests +// this._abortRequest(); + +// // Clear image reference +// this._image = null; + +// super.destroy(); +// } + +// } diff --git a/src/layer/tile/tile-cache.js b/src/layer/tile/tile-cache.js new file mode 100644 index 0000000000..cd42e03cd1 --- /dev/null +++ b/src/layer/tile/tile-cache.js @@ -0,0 +1,22 @@ +import LRUCache from '../../util/lru-cache'; +export default class TileCache { + constructor(limit = 50) { + this._cache = new LRUCache(limit); + } + + getTile(z, x, y) { + const key = this._generateKey(z, x, y); + return this._cache.get(key); + } + + setTile(tile, z, x, y) { + const key = this._generateKey(z, x, y); + this._cache.set(key, tile); + } + _generateKey(z, x, y) { + return [ z, x, y ].join('_'); + } + destory() { + this._cache.clear(); + } +} diff --git a/src/layer/tile/tile.js b/src/layer/tile/tile.js new file mode 100644 index 0000000000..8ca5d10b48 --- /dev/null +++ b/src/layer/tile/tile.js @@ -0,0 +1,59 @@ +// const r2d = 180 / Math.PI; +// const tileURLRegex = /\{([zxy])\}/g; +// export class Tile { +// constructor(layer, z, x, y) { +// this.layer = layer; +// this._tile = [ z, x, y ]; + +// } +// _createMesh() {} +// _createDebugMesh() {} + +// _getTileURL(urlParams) { +// if (!urlParams.s) { +// // Default to a random choice of a, b or c +// urlParams.s = String.fromCharCode(97 + Math.floor(Math.random() * 3)); +// } + +// tileURLRegex.lastIndex = 0; +// return this._path.replace(tileURLRegex, function(value, key) { +// // Replace with paramter, otherwise keep existing value +// return urlParams[key]; +// }); +// } + +// _tileBoundsFromWGS84(boundsWGS84) { +// const sw = this._layer._world.latLonToPoint(LatLon(boundsWGS84[1], boundsWGS84[0])); +// const ne = this._layer._world.latLonToPoint(LatLon(boundsWGS84[3], boundsWGS84[2])); + +// return [sw.x, sw.y, ne.x, ne.y]; +// } + +// // Get tile bounds in WGS84 coordinates +// _tileBoundsWGS84(tile) { +// const e = this._tile2lon(tile[0] + 1, tile[2]); +// const w = this._tile2lon(tile[0], tile[2]); +// const s = this._tile2lat(tile[1] + 1, tile[2]); +// const n = this._tile2lat(tile[1], tile[2]); +// return [ w, s, e, n ]; +// } + +// _tile2lon(x, z) { +// return x / Math.pow(2, z) * 360 - 180; +// } + +// _tile2lat(y, z) { +// const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z); +// return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); +// } + +// _boundsToCenter(bounds) { +// const x = bounds[0] + (bounds[2] - bounds[0]) / 2; +// const y = bounds[1] + (bounds[3] - bounds[1]) / 2; + +// return [ x, y ]; +// } +// destory() { + +// } +// } diff --git a/src/map/AMap.js b/src/map/AMap.js index 29374ad778..c4ba78e2a5 100644 --- a/src/map/AMap.js +++ b/src/map/AMap.js @@ -128,6 +128,9 @@ export default class GaodeMap extends Base { scene.setZoom = zoom => { return map.setZoom(zoom); }; + scene.getBounds = () => { + return map.getBounds(); + }; scene.setZoomAndCenter = (zoom, center) => { const lnglat = new AMap.LngLat(center[0], center[1]); return map.setZoomAndCenter(zoom, lnglat); @@ -138,6 +141,9 @@ export default class GaodeMap extends Base { scene.setRotation = rotation => { return map.setRotation(rotation); }; + scene.setStatus = status => { + return map.setStatus(status); + }; scene.zoomIn = () => { return map.zoomIn(); }; diff --git a/src/source/parser/geojson.js b/src/source/parser/geojson.js index 6385fa0d42..d157b4ffec 100644 --- a/src/source/parser/geojson.js +++ b/src/source/parser/geojson.js @@ -1,11 +1,10 @@ import * as turfMeta from '@turf/meta'; -import { default as cleanCoords } from '@turf/clean-coords'; import { getCoords } from '@turf/invariant'; export default function geoJSON(data) { const resultData = []; turfMeta.flattenEach(data, (currentFeature, featureIndex) => { // 多个polygon 拆成一个 - const coord = getCoords(cleanCoords(currentFeature)); + const coord = getCoords(currentFeature); const dataItem = { ...currentFeature.properties, coordinates: coord, diff --git a/src/source/transform/cluster.js b/src/source/transform/cluster.js index 645c7abcc0..5a88bedd53 100644 --- a/src/source/transform/cluster.js +++ b/src/source/transform/cluster.js @@ -1,6 +1,6 @@ import Supercluster from 'supercluster'; export function cluster(data, option) { - const { radius = 40, maxZoom = 16, minZoom = 0, field, zoom = 2 } = option; + const { radius = 80, maxZoom = 18, minZoom = 0, field, zoom = 2 } = option; if (data.pointIndex) { const clusterPoint = data.pointIndex.getClusters(data.extent, zoom); data.dataArray = formatData(clusterPoint); @@ -38,10 +38,9 @@ export function cluster(data, option) { }; }); data.dataArray = resultData; - data.pointIndex = pointIndex; - return data; + return { data, pointIndex }; } -function formatData(clusterPoint) { +export function formatData(clusterPoint) { return clusterPoint.map((point, index) => { return { coordinates: point.geometry.coordinates, diff --git a/src/util/diff.js b/src/util/diff.js new file mode 100644 index 0000000000..949b8fd08d --- /dev/null +++ b/src/util/diff.js @@ -0,0 +1,38 @@ +import * as _ from '@antv/util'; +export default function diff(a, b) { + // Throw is a or b are not objects. + if (!_.isPlainObject(a)) { + throw new Error('First parameter to diff() is not an object'); + } + if (!_.isPlainObject(b)) { + throw new Error('Second parameter to diff() is not an object'); + } + + const changes = []; + const keysA = _.keys(a); + const keysB = _.keys(b); + + // Find the items in A that are not in B. + _.each(_.difference(keysA, keysB), key => { + changes.push({ type: 'remove', key, value: a[key] }); + }); + + // Find the items in B that are not in A. + _.each(_.difference(keysB, keysA), key => { + changes.push({ type: 'add', key, value: b[key] }); + }); + + // Find the items that are in both, but have changed. + _.each(intersection(keysA, keysB), key => { + if (!_.isEqual(a[key], b[key])) { + changes.push({ type: 'update', key, value: b[key] }); + } + }); + + return changes; +} +function intersection(keysA, keysB) { + return keysA.filter(key => { + return keysB.indexOf(key) > -1; + }); +} diff --git a/src/util/font-util.js b/src/util/font-util.js index 1e0b96a98d..42c2387cb3 100644 --- a/src/util/font-util.js +++ b/src/util/font-util.js @@ -41,3 +41,73 @@ export function buildMapping({ canvasHeight: nextPowOfTwo(yOffset + (row + 1) * rowHeight) }; } + +function buildRowMapping(mapping, columns, yOffset) { + for (let i = 0; i < columns.length; i++) { + const { icon, xOffset } = columns[i]; + mapping[icon.id] = Object.assign({}, icon, { + x: xOffset, + y: yOffset, + image: icon.image + }); + } +} +export function resizeImage(ctx, imageData, width, height) { + const { naturalWidth, naturalHeight } = imageData; + if (width === naturalWidth && height === naturalHeight) { + return imageData; + } + + ctx.canvas.height = height; + ctx.canvas.width = width; + + ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); + + // image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight + ctx.drawImage(imageData, 0, 0, naturalWidth, naturalHeight, 0, 0, width, height); + + return ctx.canvas; +} + +export function buildIconMaping({ icons, buffer, maxCanvasWidth }) { + let xOffset = 0; + let yOffset = 0; + let rowHeight = 0; + let columns = []; + const mapping = {}; + for (let i = 0; i < icons.length; i++) { + const icon = icons[i]; + if (!mapping[icon.id]) { + const { height, width } = icon; + + // fill one row + if (xOffset + width + buffer > maxCanvasWidth) { + buildRowMapping(mapping, columns, yOffset); + + xOffset = 0; + yOffset = rowHeight + yOffset + buffer; + rowHeight = 0; + columns = []; + } + + columns.push({ + icon, + xOffset + }); + + xOffset = xOffset + width + buffer; + rowHeight = Math.max(rowHeight, height); + } + } + + if (columns.length > 0) { + buildRowMapping(mapping, columns, yOffset); + } + + const canvasHeight = nextPowOfTwo(rowHeight + yOffset + buffer); + + return { + mapping, + canvasHeight + }; +} diff --git a/src/util/index.js b/src/util/index.js deleted file mode 100644 index 5d3404b1b8..0000000000 --- a/src/util/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import CommonUtil from './common'; -import DomUtil from './dom'; - -const Util = {}; - -CommonUtil.merge(Util, CommonUtil, DomUtil, { - mixin(c, mixins) { - const Param = c.CFG ? 'CFG' : 'ATTRS'; - if (c && mixins) { - c._mixins = mixins; - c[Param] = c[Param] || {}; - const temp = {}; - Util.each(mixins, function(mixin) { - Util.augment(c, mixin); - const attrs = mixin[Param]; - if (attrs) { - Util.merge(temp, attrs); - } - }); - c[Param] = Util.merge(temp, c[Param]); - } - } -}); - -export default Util; diff --git a/src/util/lru-cache.js b/src/util/lru-cache.js new file mode 100644 index 0000000000..8b417e53e2 --- /dev/null +++ b/src/util/lru-cache.js @@ -0,0 +1,71 @@ +/** + * LRU Cache class with limit + * + * Update order for each get/set operation + * Delete oldest when reach given limit + */ + +export default class LRUCache { + constructor(limit = 5) { + this.limit = limit; + + this.clear(); + } + + clear() { + this._cache = {}; + // access/update order, first item is oldest, last item is newest + this._order = []; + } + + get(key) { + const value = this._cache[key]; + if (value) { + // update order + this._deleteOrder(key); + this._appendOrder(key); + } + return value; + } + + set(key, value) { + if (!this._cache[key]) { + // if reach limit, delete the oldest + if (Object.keys(this._cache).length === this.limit) { + this.delete(this._order[0]); + } + + this._cache[key] = value; + this._appendOrder(key); + } else { + // if found in cache, delete the old one, insert new one to the first of list + this.delete(key); + + this._cache[key] = value; + this._appendOrder(key); + } + } + + delete(key) { + const value = this._cache[key]; + if (value) { + this._deleteCache(key); + this._deleteOrder(key); + } + } + + _deleteCache(key) { + delete this._cache[key]; + } + + _deleteOrder(key) { + const index = this._order.findIndex(o => o === key); + if (index >= 0) { + this._order.splice(index, 1); + } + } + + _appendOrder(key) { + this._order.push(key); + } +} diff --git a/test/unit/shader-module/base-spec.js b/test/unit/shader-module/base-spec.js index 36410577d1..55ae9c0cba 100644 --- a/test/unit/shader-module/base-spec.js +++ b/test/unit/shader-module/base-spec.js @@ -22,8 +22,8 @@ describe('test shader module', function() { registerModule('common', commonModule); registerModule('module1', module1); it('should import a module correctly.', function() { - // expect(vs).eq('#define PI 3.14'); - // expect(fs.replace(/(\s+)|(\n)+|(\r\n)+/g, '')).eqls('#ifdefGL_FRAGMENT_PRECISION_HIGHprecisionhighpfloat;#elseprecisionmediumpfloat;#endif'); + // expect(vs).eq('#define PI 3.14'); + // expect(fs.replace(/(\s+)|(\n)+|(\r\n)+/g, '')).eqls('#ifdefGL_FRAGMENT_PRECISION_HIGHprecisionhighpfloat;#elseprecisionmediumpfloat;#endif'); }); }); diff --git a/test/unit/source/transfrom/cluster-spec.js b/test/unit/source/transfrom/cluster-spec.js index f46ed7c23a..bd37f2a145 100644 --- a/test/unit/source/transfrom/cluster-spec.js +++ b/test/unit/source/transfrom/cluster-spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { pointData } from '../../../asset/data/point'; import { cluster } from '../../../../src/source/transform/cluster'; -describe('hexagon Test', function() { +describe('cluster Test', function() { it('pointToCuster', function() { const dataArray = pointData.map(item => { @@ -17,7 +17,7 @@ describe('hexagon Test', function() { dataArray, extent: [ -180, -85, 180, 85 ] }; - const grid = cluster(data, { radius: 40, field: 'v', zoom: 13 }); - expect(grid.dataArray.length).eql(26); + const grid = cluster(data, { radius: 40, field: 'v', zoom: 13, bbox: [ -180, -85, 180, 85 ] }); + expect(grid.data.dataArray.length).eql(26); }); });