feat(worker): worker Source

This commit is contained in:
thinkinggis 2019-07-08 16:07:47 +08:00
parent bab462ac40
commit 3bbdb5ec88
19 changed files with 824 additions and 113 deletions

View File

@ -29,7 +29,7 @@ const scene = new L7.Scene({
}); });
window.scene = scene; window.scene = scene;
scene.on('loaded', () => { 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({ scene.PointLayer({
zIndex: 2 zIndex: 2
}) })

View File

@ -51,7 +51,7 @@ scene.on('loaded', () => {
}) })
//.animate({enable:true}) //.animate({enable:true})
.render(); .render();
console.log(layer);
/** /**
scene.LineLayer({ scene.LineLayer({
zIndex: 2 zIndex: 2

67
demos/workerdemo.html Normal file
View File

@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="geometry" content="diagram">
<link rel="stylesheet" href="./assets/common.css">
<link rel="stylesheet" href="./assets/info.css">
<title>hexagon demo</title>
<style>
body {margin: 0;}
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id="map"></div>
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
<script src="./assets/jquery-3.2.1.min.js"></script>
<script src="./assets/dat.gui.min.js"></script>
<script src="../build/L7.js"></script>
<script>
const scene = new L7.Scene({
id: 'map',
mapStyle: 'light', // 样式URL
center: [104.838088,34.075889 ],
pitch: 0,
hash:true,
zoom: 3,
});
scene.on('loaded', () => {
scene.addTileSource('test',{
url:'http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/county/{z}/{x}/{y}.pbf',
type:'mvt',
minZoom: 0,
maxZoom:9
})
const layer = scene.PolygonLayer({
zIndex:0,
})
.source('test',{
parser:{
type: 'mvt',
sourceLayer:'county',
idField:'id'
}
})
.shape('line')
.size(2)
.active(false)
.color('red')
.style({
opacity:1.0
})
.render();
});
</script>
</body>
</html>

View File

@ -69,6 +69,7 @@ export default class Layer extends Base {
this._object3D.renderOrder = this.get('zIndex') || 0; this._object3D.renderOrder = this.get('zIndex') || 0;
this._mapEventHandlers = []; this._mapEventHandlers = [];
const layerId = this._getUniqueId(); const layerId = this._getUniqueId();
this.set('layerId', layerId);
this.layerId = layerId; this.layerId = layerId;
this._activeIds = null; this._activeIds = null;
const world = scene._engine.world; const world = scene._engine.world;
@ -127,7 +128,19 @@ export default class Layer extends Base {
this.set('visible', visible); this.set('visible', visible);
this._object3D.visible = this.get('visible'); this._object3D.visible = this.get('visible');
} }
// 兼容瓦片source非瓦片source
source(data, cfg = {}) { 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) { if (data instanceof source) {
this.layerSource = data; this.layerSource = data;
return this; return this;
@ -136,9 +149,6 @@ export default class Layer extends Base {
cfg.mapType = this.scene.mapType; cfg.mapType = this.scene.mapType;
cfg.zoom = this.scene.getZoom(); cfg.zoom = this.scene.getZoom();
this.layerSource = new source(cfg); this.layerSource = new source(cfg);
// this.scene.workerPool.runTask(cfg).then(data => {
// console.log(data);
// });
return this; return this;
} }
color(field, values) { color(field, values) {
@ -305,6 +315,10 @@ export default class Layer extends Base {
} }
render() { render() {
if (this.get('layerType') === 'tile') {
this.scene.style.update(this._attrs);
return this;
}
this.init(); this.init();
this.scene._engine.update(); this.scene._engine.update();
return this; return this;

View File

@ -3,12 +3,13 @@ import { LAYER_MAP } from '../layer';
import Base from './base'; import Base from './base';
import LoadImage from './image'; import LoadImage from './image';
import FontAtlasManager from '../geom/buffer/point/text/font-manager'; import FontAtlasManager from '../geom/buffer/point/text/font-manager';
import WorkerPool from '../worker/worker_pool';
// import { MapProvider } from '../map/AMap'; // import { MapProvider } from '../map/AMap';
import { getMap } from '../map/index'; import { getMap } from '../map/index';
import Global from '../global'; import Global from '../global';
import { getInteraction } from '../interaction/index'; import { getInteraction } from '../interaction/index';
import { compileBuiltinModules } from '../geom/shader'; 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 { export default class Scene extends Base {
getDefaultCfg() { getDefaultCfg() {
return Global.scene; return Global.scene;
@ -16,6 +17,7 @@ export default class Scene extends Base {
constructor(cfg) { constructor(cfg) {
super(cfg); super(cfg);
this._initMap(); this._initMap();
this.crs = epsg3857;
// this._initAttribution(); // 暂时取消,后面作为组件去加载 // this._initAttribution(); // 暂时取消,后面作为组件去加载
this.addImage(); this.addImage();
this.fontAtlasManager = new FontAtlasManager(); this.fontAtlasManager = new FontAtlasManager();
@ -27,7 +29,6 @@ export default class Scene extends Base {
this._engine = new Engine(mapContainer, this); this._engine = new Engine(mapContainer, this);
// this.registerMapEvent(); // this.registerMapEvent();
this._engine.run(); this._engine.run();
this.workerPool = new WorkerPool();
compileBuiltinModules(); compileBuiltinModules();
} }
// 为pickup场景添加 object 对象 // 为pickup场景添加 object 对象
@ -54,6 +55,7 @@ export default class Scene extends Base {
const interaction = new Ctor({ layer: this }); const interaction = new Ctor({ layer: this });
interaction._onHashChange(); interaction._onHashChange();
} }
this.style = new Style(this, {});
this.emit('loaded'); 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) { on(type, hander) {
if (this.map) { this.map.on(type, hander); } if (this.map) { this.map.on(type, hander); }
super.on(type, hander); super.on(type, hander);

131
src/core/style.js Normal file
View File

@ -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 ])
);
}
}

View File

@ -47,10 +47,10 @@ export default class Tile extends Base {
} }
requestTileAsync(done) { requestTileAsync(done) {
// 获取数据 // 获取数据
this.layer.workerTileSource.loadTile({ // this.layer.workerTileSource.loadTile({
tile: this._tile, // tile: this._tile,
url: this.layer.tileSource.getRequestUrl(this._tile[0], this._tile[1], this._tile[2]) // 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]); const data = this.layer.tileSource.getTileData(this._tile[0], this._tile[1], this._tile[2]);
if (data.loaded) { if (data.loaded) {
done(data.data); done(data.data);

View File

@ -2,7 +2,6 @@ import Layer from '../../core/layer';
import Util from '../../util'; import Util from '../../util';
import diff from '../../util/diff'; import diff from '../../util/diff';
import TileSource from '../../source/tile_source'; import TileSource from '../../source/tile_source';
import TileWorkerSource from '../../source/tile_worker_source';
import * as THREE from '../../core/three'; import * as THREE from '../../core/three';
import Controller from '../../core/controller/index'; import Controller from '../../core/controller/index';
import Global from '../../global'; import Global from '../../global';
@ -27,9 +26,6 @@ export default class TileLayer extends Layer {
this.tileList = {}; this.tileList = {};
this.type = this.get('layerType'); this.type = this.get('layerType');
this.workerPool = this.scene.workerPool; this.workerPool = this.scene.workerPool;
this.workerTileSource = new TileWorkerSource({
workerPool: this.scene.workerPool
});
} }
shape(field, values) { shape(field, values) {
const layerType = this.get('layerType'); const layerType = this.get('layerType');

216
src/source/source_cache.js Normal file
View File

@ -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];
// 移除对应的数据
}
}
}

View File

@ -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')
});
}
}

View File

@ -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);
}
}

View File

@ -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];
});
}
}

63
src/worker/actor.js Normal file
View File

@ -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: '<cancel>',
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: '<response>',
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 === '<response>' || data.type === '<cancel>') {
this.callbacks[id](id);
delete this.callbacks[id]; // 回调执行
}
}
}

View File

@ -1,45 +1,60 @@
import Source from '../core/source'; import VectorTileWorkerSource from '../source/vector_tile_worker_source';
// import Controller from '../core/controller/index'; import Actor from './actor';
import { getArrayBuffer } from '../util/ajax';
// 统一管理workerSource 实例化
export default class Worker { export default class Worker {
constructor(self) { constructor(self) {
this.self = self; this.self = self;
this.self.addEventListener('message', cfg => { this.actor = new Actor(self, this);
this.loadTile(cfg.data); 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) { 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) { setLayers(mapId, layercfgs,callback) {
const tileData = new Source({
...cfg.sourceCfg, }
data updateLayers(id, params, callback) {
});
return tileData; }
/**
* 获取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); self.worker = new Worker(self);

17
src/worker/workerTile.js Normal file
View File

@ -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';
}
}

View File

@ -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;

View File

@ -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();
});
}
}

View File

@ -1,54 +1,38 @@
import WebWorker from './web_worker'; import WebWorker from './web_worker';
/**
* Constructs a worker pool.
* @private
*/
export default class WorkerPool { export default class WorkerPool {
constructor(workerCount) {
this.workerCount = workerCount || Math.max(Math.floor(window.navigator.hardwareConcurrency / 2), 1); constructor() {
this.workers = []; // worker线程池 this.active = {};
this.workerQueue = []; // 任务队列
this._initWorker(); // 初始化线程池
} }
_initWorker() {
while (this.workers.length < this.workerCount) { acquire(mapId) {
this.workers.push(new WebWorker()); if (!this.workers) {
} // Lazily look up the value of mapboxgl.workerCount so that
} // client code has had a chance to set it.
runTask(payload) { this.workers = [];
return new Promise((resolve, reject) => { while (this.workers.length < WorkerPool.workerCount) {
if (this.workers.length > 0) { this.workers.push(new WebWorker());
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
});
} }
}); }
this.active[mapId] = true;
return this.workers.slice();
} }
release() {
this.workers.forEach(worker => { release(mapId) {
worker.terminate(); 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);

View File

@ -0,0 +1,6 @@
export function serialize() {
}
export function deserialize() {
}