mirror of https://gitee.com/antv-l7/antv-l7
feat(worker): worker Source
This commit is contained in:
parent
53f726872a
commit
d7d66a26f6
|
@ -29,7 +29,7 @@ const scene = new L7.Scene({
|
|||
});
|
||||
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
|
||||
})
|
||||
|
|
|
@ -51,7 +51,7 @@ scene.on('loaded', () => {
|
|||
})
|
||||
//.animate({enable:true})
|
||||
.render();
|
||||
console.log(layer);
|
||||
|
||||
/**
|
||||
scene.LineLayer({
|
||||
zIndex: 2
|
||||
|
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 ])
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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];
|
||||
// 移除对应的数据
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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')
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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]; // 回调执行
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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(); // 初始化线程池
|
||||
|
||||
constructor() {
|
||||
this.active = {};
|
||||
}
|
||||
_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
|
||||
});
|
||||
|
||||
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);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export function serialize() {
|
||||
}
|
||||
|
||||
export function deserialize() {
|
||||
|
||||
}
|
Loading…
Reference in New Issue