diff --git a/demos/06_text.html b/demos/06_text.html index 3fd2bc4f61..45708e187f 100644 --- a/demos/06_text.html +++ b/demos/06_text.html @@ -29,7 +29,7 @@ }); window.scene = scene; scene.on('loaded', () => { - $.get('./data/provincePoint.json', data => { + $.get('https://gw.alipayobjects.com/os/basement_prod/abcfe339-b8bc-46ce-8ff4-c96185b6235f.json', data => { scene.PointLayer({ zIndex: 2 }) diff --git a/demos/08_arc_line.html b/demos/08_arc_line.html index 8a130d82e9..553b9a0d69 100644 --- a/demos/08_arc_line.html +++ b/demos/08_arc_line.html @@ -51,7 +51,7 @@ }) //.animate({enable:true}) .render(); - console.log(layer); + /** scene.LineLayer({ zIndex: 2 diff --git a/demos/workerdemo.html b/demos/workerdemo.html new file mode 100644 index 0000000000..d17462f3d6 --- /dev/null +++ b/demos/workerdemo.html @@ -0,0 +1,67 @@ + + + + + + + + + + + hexagon demo + + + + +
+ + + + + + + + diff --git a/src/core/layer.js b/src/core/layer.js index b1c8cfcac2..ad705463f3 100644 --- a/src/core/layer.js +++ b/src/core/layer.js @@ -69,6 +69,7 @@ export default class Layer extends Base { this._object3D.renderOrder = this.get('zIndex') || 0; this._mapEventHandlers = []; const layerId = this._getUniqueId(); + this.set('layerId', layerId); this.layerId = layerId; this._activeIds = null; const world = scene._engine.world; @@ -127,7 +128,19 @@ export default class Layer extends Base { this.set('visible', visible); this._object3D.visible = this.get('visible'); } + // 兼容瓦片source,非瓦片source + source(data, cfg = {}) { + // 根据Source类型判断,是不是瓦片图层 + if (this.scene.getTileSource(data)) { + this.set('layerType', 'tile'); + this.set('sourceOption', { + id: data, + ...cfg + }); + return this; + } + if (data instanceof source) { this.layerSource = data; return this; @@ -136,9 +149,6 @@ export default class Layer extends Base { cfg.mapType = this.scene.mapType; cfg.zoom = this.scene.getZoom(); this.layerSource = new source(cfg); - // this.scene.workerPool.runTask(cfg).then(data => { - // console.log(data); - // }); return this; } color(field, values) { @@ -281,7 +291,7 @@ export default class Layer extends Base { options[attrName] = attrCfg; } _createAttrOption(attrName, field, cfg, defaultValues) { - + const attrCfg = {}; attrCfg.field = field; if (cfg) { @@ -305,6 +315,10 @@ export default class Layer extends Base { } render() { + if (this.get('layerType') === 'tile') { + this.scene.style.update(this._attrs); + return this; + } this.init(); this.scene._engine.update(); return this; diff --git a/src/core/scene.js b/src/core/scene.js index 86d3afca35..efc46c4976 100644 --- a/src/core/scene.js +++ b/src/core/scene.js @@ -3,12 +3,13 @@ import { LAYER_MAP } from '../layer'; import Base from './base'; import LoadImage from './image'; import FontAtlasManager from '../geom/buffer/point/text/font-manager'; -import WorkerPool from '../worker/worker_pool'; // import { MapProvider } from '../map/AMap'; import { getMap } from '../map/index'; import Global from '../global'; import { getInteraction } from '../interaction/index'; import { compileBuiltinModules } from '../geom/shader'; +import Style from './style'; +import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857'; export default class Scene extends Base { getDefaultCfg() { return Global.scene; @@ -16,6 +17,7 @@ export default class Scene extends Base { constructor(cfg) { super(cfg); this._initMap(); + this.crs = epsg3857; // this._initAttribution(); // 暂时取消,后面作为组件去加载 this.addImage(); this.fontAtlasManager = new FontAtlasManager(); @@ -27,7 +29,6 @@ export default class Scene extends Base { this._engine = new Engine(mapContainer, this); // this.registerMapEvent(); this._engine.run(); - this.workerPool = new WorkerPool(); compileBuiltinModules(); } // 为pickup场景添加 object 对象 @@ -54,6 +55,7 @@ export default class Scene extends Base { const interaction = new Ctor({ layer: this }); interaction._onHashChange(); } + this.style = new Style(this, {}); this.emit('loaded'); }); @@ -68,6 +70,13 @@ export default class Scene extends Base { } } + // 添加 Tile Source + addTileSource(id, Sourcecfg) { + this.style.addSource(id, Sourcecfg); + } + getTileSource(id) { + return this.style.getSource(id); + } on(type, hander) { if (this.map) { this.map.on(type, hander); } super.on(type, hander); diff --git a/src/core/style.js b/src/core/style.js new file mode 100644 index 0000000000..2dbbf858de --- /dev/null +++ b/src/core/style.js @@ -0,0 +1,131 @@ +import Base from '../core/base'; +import WorkerPool from '../worker/worker_pool'; +import { toLngLat, Bounds } from '@antv/geo-coord'; +import SourceCache from '../source/source_cache'; +import WorkerController from '../worker/worker_controller'; +// 统一管理所有的Source +// 统一管理地图样式 +export default class Style extends Base { + constructor(scene, cfg) { + super(cfg); + this.scene = scene; + this._sourceCaches = {}; + this.WorkerPool = new WorkerPool(); + this._tileMap = {}; + this.WorkerController = new WorkerController(this.WorkerPool, this); + this.layerStyles = {}; + this.addMapEvent(); + } + addSource(id, sourceCfg) { + if (this._sourceCaches[id] !== undefined) { + throw new Error('SourceID 已存在'); + } + this._sourceCaches[id] = new SourceCache(this.scene, sourceCfg); + } + getSource(id) { + return this._sourceCaches[id]; + } + // 设置 + addTileStyle(layerCfg) { + const layerid = layerCfg.layerId; + this.layerStyles[layerid] = layerCfg; + this._layerStyleGroupBySourceID(); + this.WorkerController.broadcast('setLayers', this.layerStyles); + // TODO 更新 style + + } + removeTileStyle(id) { + delete this.layerStyles[id]; + this._layerStyleGroupBySourceID(); + + } + _layerStyleGroupBySourceID() { + const sourceStyles = []; + // 支持VectorLayer + for (const layerId in this.layerStyles) { + const sourceID = this.layerStyles[layerId].sourceOption.id; + const sourcelayer = this.layerStyles[layerId].sourceOption.parser.sourceLayer; + if (!sourceStyles[sourceID]) sourceStyles[sourceID] = {}; + if (!sourceStyles[sourceID][sourcelayer]) sourceStyles[sourceID][sourcelayer] = []; + sourceStyles[sourceID][sourcelayer].push(this.layerStyles[layerId]); + } + this.sourceStyles = sourceStyles; + } + update(parameters) { + this.addTileStyle(parameters); + for (const key in this._sourceCaches) { + this._sourceCaches[key].update(this.sourceStyles[key]); + } + + } + addMapEvent() { + this.mapEventHander = () => { + for (const key in this._sourceCaches) { + this._sourceCaches[key].update(this.sourceStyles[key]); + } + }; + this.scene.on('zoomchange', this.mapEventHander); + this.scene.on('dragend', this.mapEventHander); + } + clearMapEvent() { + this.scene.off('zoomchange', this.mapEventHander); + this.scene.off('dragend', this.mapEventHander); + } + // 计算视野内的瓦片坐标 + _calculateTileIDs() { + this.updateTileList = []; + 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, this.tileZoom ]; + this.tileList.push(coords); + this._tileMap[coords.join('_')] = coords; + } + } + } + _getPixelBounds() { + const viewPort = this.scene.getBounds().toBounds(); + const NE = viewPort.getNorthEast(); + const SW = viewPort.getSouthWest(); + const zoom = this.tileZoom; + const center = this.scene.getCenter(); + const NEPoint = this.scene.crs.lngLatToPoint(toLngLat(NE.lng, NE.lat), zoom); + const SWPoint = this.scene.crs.lngLatToPoint(toLngLat(SW.lng, SW.lat), zoom); + const centerPoint = this.scene.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 ]) + ); + } + +} diff --git a/src/layer/tile/tile.js b/src/layer/tile/tile.js index 846b85ea78..6311e8698f 100644 --- a/src/layer/tile/tile.js +++ b/src/layer/tile/tile.js @@ -47,10 +47,10 @@ export default class Tile extends Base { } requestTileAsync(done) { // 获取数据 - this.layer.workerTileSource.loadTile({ - tile: this._tile, - url: this.layer.tileSource.getRequestUrl(this._tile[0], this._tile[1], this._tile[2]) - }); + // this.layer.workerTileSource.loadTile({ + // tile: this._tile, + // url: this.layer.tileSource.getRequestUrl(this._tile[0], this._tile[1], this._tile[2]) + // }); const data = this.layer.tileSource.getTileData(this._tile[0], this._tile[1], this._tile[2]); if (data.loaded) { done(data.data); diff --git a/src/layer/tile/tile_layer.js b/src/layer/tile/tile_layer.js index 821dd5b2e2..8779fc63dd 100644 --- a/src/layer/tile/tile_layer.js +++ b/src/layer/tile/tile_layer.js @@ -2,7 +2,6 @@ import Layer from '../../core/layer'; import Util from '../../util'; import diff from '../../util/diff'; import TileSource from '../../source/tile_source'; -import TileWorkerSource from '../../source/tile_worker_source'; import * as THREE from '../../core/three'; import Controller from '../../core/controller/index'; import Global from '../../global'; @@ -27,9 +26,6 @@ export default class TileLayer extends Layer { this.tileList = {}; this.type = this.get('layerType'); this.workerPool = this.scene.workerPool; - this.workerTileSource = new TileWorkerSource({ - workerPool: this.scene.workerPool - }); } shape(field, values) { const layerType = this.get('layerType'); diff --git a/src/source/source_cache.js b/src/source/source_cache.js new file mode 100644 index 0000000000..593e126745 --- /dev/null +++ b/src/source/source_cache.js @@ -0,0 +1,216 @@ +import Base from '../core/base'; +import TileDataCache from '../source/tile_data_cache'; +import VectorTileSource from './vector_tile_source'; +import { toLngLat, Bounds } from '@antv/geo-coord'; +// 统一管理 source 添加,管理,更新 +export default class SouceCache extends Base { + constructor(scene, cfg) { + super({ + cacheLimit: 50, + minZoom: 0, + maxZoom: 18, + keepBuffer: 2, + ...cfg + }); + this._tileMap = {};// 视野内瓦片坐标序列 + this._tileList = {}; // 正在使用的瓦片坐标,记录瓦片的使用状态 + this.scene = scene; + // TODO 销毁函数 + this._tileCache = new TileDataCache(this.get('cacheLimit'), () => { }); + this._source = new VectorTileSource(cfg, this.scene.style.WorkerController); + } + + /** + * 移除视野外的瓦片,计算新增的瓦片数据 + * @param {*}tileMap 瓦片列表 + */ + + update(layercfg) { + if (!layercfg && this.layercfg) return; + this._layercfg = layercfg; + this._calculateTileIDs(); + this.updateList = this._getNewTiles(this._tileMap);// 计算新增瓦片 + this._pruneTiles(); + for (let i = 0; i < this.updateList.length; i++) { + // 瓦片相关参数 + const tileId = this.updateList[i]; + this._source.loadTile(tileId, res => { + this._tileList[tileId].active = true; + }); + } + } + // 计算视野内的瓦片坐标 + _calculateTileIDs() { + this._tileMap = {}; + const zoom = Math.floor(this.scene.getZoom()) - 1; + const minSourceZoom = this.get('minZoom'); + const maxSourceZoom = this.get('maxZoom'); + this.tileZoom = zoom > maxSourceZoom ? maxSourceZoom : 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, this.tileZoom ]; + this._tileMap[coords.join('_')] = coords; + } + } + const currentZoom = this.scene.getZoom(); + if (currentZoom < minSourceZoom) { + this._removeTiles(); + // 小于source最小范围不在处理 + return; + } + } + _getPixelBounds() { + const viewPort = this.scene.getBounds().toBounds(); + const NE = viewPort.getNorthEast(); + const SW = viewPort.getSouthWest(); + const zoom = this.tileZoom; + const center = this.scene.getCenter(); + const NEPoint = this.scene.crs.lngLatToPoint(toLngLat(NE.lng, NE.lat), zoom); + const SWPoint = this.scene.crs.lngLatToPoint(toLngLat(SW.lng, SW.lat), zoom); + const centerPoint = this.scene.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 ]) + ); + } + + _loadTile(tile, callback) { + return this._source.loadTile(tile, callback); + + } + reload() { + + } + + _reloadTile() { + + } + _removeTile() { + + } + clearTiles() { + + } + _getNewTiles(tileMap) { + const center = this.scene.getCenter(); + const centerPoint = this.scene.crs.lngLatToPoint(toLngLat(center.lng, center.lat), this.tileZoom); + const centerXY = centerPoint.divideBy(256).floor(); + const newTileList = []; + for (const tile in tileMap) { + if (!this._tileList[tile]) { + this._tileList[tile] = { + current: true, + active: false, + coords: tile.split('_') + }; + newTileList.push(tile); + } else { + this._tileList[tile].current = true; + } + } + for (const tile in this._tileList) { // 更新tilelist状态 + this._tileList[tile].current = !!this._tileMap[tile]; + this._tileList[tile].retain = !!this._tileMap[tile]; + } + newTileList.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; + }); + return newTileList; + } + _pruneTiles() { + + for (const key in this._tileList) { + const 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; + tile.current = 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; + tile.current = true; + continue; + } else if (tile && tile.loaded) { + tile.retain = true; + tile.current = true; + } + + if (z + 1 < maxZoom) { + this._retainChildren(i, j, z + 1, maxZoom); + } + } + } + } + _removeOutTiles() { + // 移除视野外的tile + for (const key in this._tileList) { + !this._tileList[key].retain && delete this._tileList[key]; + // 移除对应的数据 + } + } + +} diff --git a/src/source/tile_worker_source.js b/src/source/tile_worker_source.js deleted file mode 100644 index dc314d1e17..0000000000 --- a/src/source/tile_worker_source.js +++ /dev/null @@ -1,17 +0,0 @@ -import Base from '../core/base'; - -export default class TileWorkerSource extends Base { - constructor(cfg) { - super(cfg); - this.workerPool = this.get('workerPool'); - this.type = 'tile'; - } - loadTile({ tile, url }) { - this.get('sourceCfg').parser.tile = tile; - return this.workerPool.runTask({ - url, - attrs: this.get('attrs'), - sourceCfg: this.get('sourceCfg') - }); - } -} diff --git a/src/source/vector_tile_source.js b/src/source/vector_tile_source.js new file mode 100644 index 0000000000..1c124cf8d8 --- /dev/null +++ b/src/source/vector_tile_source.js @@ -0,0 +1,26 @@ +import Base from '../core/base'; + +export default class VectorTileSource extends Base{ + constructor(cfg, workerController) { + super({ + type: 'vector', + ...cfg + }); + this.workerController = workerController; + } + loadTile(tile, callback) { + const params = { + id: tile, + }; + this.workerController.send('loadTile', params, done.bind(this)); + function done(err,data) { + callback(); + } + } + abortTile(tile) { + this.workerController.send('abortTile', { uid: tile.uid, type: this.type, source: this.id }, undefined, tile.workerID); + } + unloadTile(tile) { + this.workerController.send('removeTile', { uid: tile.uid, type: this.type, source: this.id }, undefined, tile.workerID); + } +} diff --git a/src/source/vector_tile_worker_source.js b/src/source/vector_tile_worker_source.js new file mode 100644 index 0000000000..d96487b150 --- /dev/null +++ b/src/source/vector_tile_worker_source.js @@ -0,0 +1,61 @@ +import Base from '../core/base'; +import { getArrayBuffer } from '../util/ajax'; +const tileURLRegex = /\{([zxy])\}/g; +import PBF from 'pbf'; +import * as VectorParser from '@mapbox/vector-tile'; +import WorkerTile from '../worker/workerTile'; +// import WorkerTile from '../worker/workerTile'; +export default class VectorTileSource extends Base { + constructor(cfg, workerController) { + super({ + type: 'vector', + ...cfg + }); + this.workerController = workerController; + } + loadVectorTile(params, callback) { + const request = getArrayBuffer(params.request, (err, data) => { + if (err) { + callback(err); + } else if (data) { + callback(null, { + vectorTile: new VectorParser.VectorTile(new PBF(data)), + rawData: data + }); + } + }); + return () => { + request.cancel(); + callback(); + }; + } + loadTile(params, callback) { + console.log(params); + const workerTile = new WorkerTile(params); + workerTile.abort = this.loadVectorData(params, (err, response) => { + + }) + + + } + abortTile() { + + } + unloadTile() { + + } + hasTransition() { + + } + _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.urlTemplate.replace(tileURLRegex, function(value, key) { + return urlParams[key]; + }); + } +} diff --git a/src/worker/actor.js b/src/worker/actor.js new file mode 100644 index 0000000000..e39a9eccef --- /dev/null +++ b/src/worker/actor.js @@ -0,0 +1,63 @@ +import { serialize } from './worker_transform'; +function bindAll(fns, context) { + fns.forEach(fn => { + if (!context[fn]) { return; } + context[fn] = context[fn].bind(context); + }); +} + +export default class Actor { + constructor(target, parent, mapId) { + this.target = target; + this.parent = parent; + this.mapId = mapId; + this.callbacks = {}; + this.callbackID = 0; + bindAll(['receive'], this); + this.target.addEventListener('message', this.receive, false); + + } + send(type, data, callback, targetMapId) { + const id = callback ? `${this.mapId}_${this.callbackID++}` : null; + if (callback) this.callbacks[id] = callback; + const buffers = []; + this.target.postMessage({ + targetMapId, + sourceMapId: this.mapId, + type, + id: String(id), + data + }, buffers); + if (callback) { + return { + cancel: () => this.target.postMessage({ + targetMapId, + sourceMapId: this.mapId, + type: '', + id: String(id) + }) + }; + } + } + receive(message) { + const data = message.data; + const id = data.id; + if (Object.keys(this.callbacks).length === 0) { + this.target.postMessage({ // worker向主线程发送结果数据 + sourceMapId: this.mapId, + type: '', + id: String(id), + data: 'callback' + }); + } + if (typeof data.id !== 'undefined' && this.parent[data.type]) { + console.log(data.type); + } + // TODO worker 处理数据 创建worker source 根据类型调用响应的方法 + if (data.type === '' || data.type === '') { + this.callbacks[id](id); + delete this.callbacks[id]; // 回调执行 + + } + } +} diff --git a/src/worker/worker.js b/src/worker/worker.js index 1a5f69cfe3..b5918345d9 100644 --- a/src/worker/worker.js +++ b/src/worker/worker.js @@ -1,45 +1,60 @@ -import Source from '../core/source'; -// import Controller from '../core/controller/index'; -import { getArrayBuffer } from '../util/ajax'; +import VectorTileWorkerSource from '../source/vector_tile_worker_source'; +import Actor from './actor'; + + +// 统一管理workerSource 实例化 export default class Worker { constructor(self) { this.self = self; - this.self.addEventListener('message', cfg => { - this.loadTile(cfg.data); - }); + this.actor = new Actor(self, this); + this.workerSourceTypes = { + vector: VectorTileWorkerSource + }; + this.workerSources = {}; + this.self.registerWorkerSource = (name, WorkerSource) => { + if (this.workerSourceTypes[name]) { + throw new Error(`Worker source with name "${name}" already registered.`); + } + this.workerSourceTypes[name] = WorkerSource; + }; } loadTile(cfg) { - // const tileSource = new TileSource(cfg.data, cfg.cfg); - getArrayBuffer({ url: cfg.url }, (err, data) => { - if (err) { - this.self.postMessage(null); - return; - } - const tileData = this._generateSource(cfg, data.data); - console.log(tileData); - const uInt8Array = new Uint8Array(1024 * 1024 * 32); // 32MB - for (let i = 0; i < uInt8Array.length; ++i) { - uInt8Array[i] = i; - } - console.time('postmessage'); - const b = function() { - return 'update'; - }; - this.self.postMessage({ a: - uInt8Array.buffer, - update: b - }, [ uInt8Array.buffer, b ]); - console.timeEnd('postmessage'); - }); } - _generateSource(cfg, data) { - const tileData = new Source({ - ...cfg.sourceCfg, - data - }); - return tileData; + setLayers(mapId, layercfgs,callback) { + + } + updateLayers(id, params, callback) { + + } + /** + * 获取workerSource + * @param {string} mapId WorkerPool Id + * @param {string} type 瓦片类型 目前支持Vector + * @param {string} source souce ID + * @return {*} WorkerSource + */ + getWorkerSource(mapId, type, source) { + if (!this.workerSources[mapId]) { + this.workerSources[mapId] = {}; + } + if (!this.workerSources[mapId][type]) { + this.workerSources[mapId][type] = {}; + } + + if (!this.workerSources[mapId][type][source]) { + // use a wrapped actor so that we can attach a target mapId param + // to any messages invoked by the WorkerSource + const actor = { + send: (type, data, callback) => { + this.actor.send(type, data, callback, mapId); + } + }; + + this.workerSources[mapId][type][source] = new this.workerSourceTypes[type](actor, this.getLayerIndex(mapId)); + } + return this.workerSources[mapId][type][source]; } } self.worker = new Worker(self); diff --git a/src/worker/workerTile.js b/src/worker/workerTile.js new file mode 100644 index 0000000000..f24473251f --- /dev/null +++ b/src/worker/workerTile.js @@ -0,0 +1,17 @@ +export default class WorkerTile { + constructor(tile) { + this.id = tile.id; + } + parse(data, layerstyle, actor, callback) { + this.status = 'parsing'; + this.data = data; + const buckets = {}; + // 根据source + for (const sourcelayer in layerstyle) { + for (let i = 0; i < layerstyle[sourcelayer].length; i++) { + + } + } + this.status = 'done'; + } +} diff --git a/src/worker/worker_controller.js b/src/worker/worker_controller.js new file mode 100644 index 0000000000..f18f01b67a --- /dev/null +++ b/src/worker/worker_controller.js @@ -0,0 +1,69 @@ + +import Actor from './actor'; +let id = 1; +function asyncAll( + array, + fn, + callback +) { + if (!array.length) { return callback(null, []); } + let remaining = array.length; + const results = new Array(array.length); + let error = null; + array.forEach((item, i) => { + fn(item, (err, result) => { + if (err) error = err; + results[i] = ((result)); + if (--remaining === 0) callback(error, results); + }); + }); +} + +export default class WorkerController { + constructor(workerPool, parent) { + this.workerPool = workerPool; + this.actors = []; + this.currentActor = 0; + this.id = id++; + const workers = this.workerPool.acquire(this.id); + + for (let i = 0; i < workers.length; i++) { + const worker = workers[i]; + const actor = new WorkerController.Actor(worker, parent, this.id); + actor.name = `Worker ${i}`; + this.actors.push(actor); + } + } + + /** + * Broadcast a message to all Workers. + */ + + broadcast(type, data, cb) { + cb = cb || function() { }; + asyncAll(this.actors, (actor, done) => { + actor.send(type, data, done); + }, cb); + } + + + send(type, data, callback, targetID) { + console.log('消息发送', data); + if (typeof targetID !== 'number' || isNaN(targetID)) { + // Use round robin to send requests to web workers. + targetID = this.currentActor = (this.currentActor + 1) % this.actors.length; + } + this.actors[targetID].send(type, data, callback, targetID); + return targetID; + } + + remove() { + this.actors.forEach(actor => { actor.remove(); }); + this.actors = []; + this.workerPool.release(this.id); + } + + +} +WorkerController.Actor = Actor; + diff --git a/src/worker/worker_pool.1.js b/src/worker/worker_pool.1.js new file mode 100644 index 0000000000..b051f02749 --- /dev/null +++ b/src/worker/worker_pool.1.js @@ -0,0 +1,54 @@ +import WebWorker from './web_worker'; +export default class WorkerPool { + constructor(workerCount) { + this.workerCount = workerCount || Math.max(Math.floor(window.navigator.hardwareConcurrency / 2), 1); + this.workers = []; // worker线程池 + this.workerQueue = []; // 任务队列 + this._initWorker(); // 初始化线程池 + } + _initWorker() { + while (this.workers.length < this.workerCount) { + this.workers.push(new WebWorker()); + } + } + runTask(payload) { + return new Promise((resolve, reject) => { + if (this.workers.length > 0) { + const worker = this.workers.shift(); // 从线程池取出一个worker + worker.postMessage(payload); // 向线程发送数据 + const workerCallback = e => { + resolve(e.data); // 成功则返回数据 + // 移除事件监听 + worker.removeEventListener('message', workerCallback); + // 重新放回线程池 + this.workers.push(worker); + // 如果任务队列的数据还有则从任务队列继续取数据执行任务 + if (this.workerQueue.length > 0) { + const queueData = this.workerQueue.shift(); + this.runTask(queueData.payload).then(data => { + queueData.resolve(data); + }); + } + }; + // 监听worker事件 + worker.addEventListener('message', workerCallback); + worker.addEventListener('error', e => { + reject('filename:' + e.filename + '\nmessage:' + e.message + '\nlineno:' + e.lineno); + }); + } else { + // 如果线程池都被占用,则将数据丢入任务队列,并保存对应的resolve和reject + this.workerQueue.push({ + payload, + resolve, + reject + }); + } + }); + } + release() { + this.workers.forEach(worker => { + worker.terminate(); + }); + } +} + diff --git a/src/worker/worker_pool.js b/src/worker/worker_pool.js index b051f02749..078947cef7 100644 --- a/src/worker/worker_pool.js +++ b/src/worker/worker_pool.js @@ -1,54 +1,38 @@ import WebWorker from './web_worker'; + +/** + * Constructs a worker pool. + * @private + */ export default class WorkerPool { - constructor(workerCount) { - this.workerCount = workerCount || Math.max(Math.floor(window.navigator.hardwareConcurrency / 2), 1); - this.workers = []; // worker线程池 - this.workerQueue = []; // 任务队列 - this._initWorker(); // 初始化线程池 - } - _initWorker() { - while (this.workers.length < this.workerCount) { - this.workers.push(new WebWorker()); - } + + constructor() { + this.active = {}; } - runTask(payload) { - return new Promise((resolve, reject) => { - if (this.workers.length > 0) { - const worker = this.workers.shift(); // 从线程池取出一个worker - worker.postMessage(payload); // 向线程发送数据 - const workerCallback = e => { - resolve(e.data); // 成功则返回数据 - // 移除事件监听 - worker.removeEventListener('message', workerCallback); - // 重新放回线程池 - this.workers.push(worker); - // 如果任务队列的数据还有则从任务队列继续取数据执行任务 - if (this.workerQueue.length > 0) { - const queueData = this.workerQueue.shift(); - this.runTask(queueData.payload).then(data => { - queueData.resolve(data); - }); - } - }; - // 监听worker事件 - worker.addEventListener('message', workerCallback); - worker.addEventListener('error', e => { - reject('filename:' + e.filename + '\nmessage:' + e.message + '\nlineno:' + e.lineno); - }); - } else { - // 如果线程池都被占用,则将数据丢入任务队列,并保存对应的resolve和reject - this.workerQueue.push({ - payload, - resolve, - reject - }); + + acquire(mapId) { + if (!this.workers) { + // Lazily look up the value of mapboxgl.workerCount so that + // client code has had a chance to set it. + this.workers = []; + while (this.workers.length < WorkerPool.workerCount) { + this.workers.push(new WebWorker()); } - }); + } + + this.active[mapId] = true; + return this.workers.slice(); } - release() { - this.workers.forEach(worker => { - worker.terminate(); - }); + + release(mapId) { + delete this.active[mapId]; + if (Object.keys(this.active).length === 0) { + this.workers.forEach(w => { + w.terminate(); + }); + this.workers = null; + } } } +WorkerPool.workerCount = Math.max(Math.floor(window.navigator.hardwareConcurrency / 2), 1); diff --git a/src/worker/worker_transform.js b/src/worker/worker_transform.js new file mode 100644 index 0000000000..7fbfc26bb8 --- /dev/null +++ b/src/worker/worker_transform.js @@ -0,0 +1,6 @@ +export function serialize() { +} + +export function deserialize() { + +}