Merge branch 'mvt' into 'master'

Mvt

合并矢量瓦片

See merge request !34
This commit is contained in:
thinkinggis 2019-06-24 18:08:42 +08:00
commit 75cc96e32a
37 changed files with 922 additions and 520 deletions

View File

@ -76,7 +76,7 @@ scene.on('loaded', () => {
circleLayer.setData(pointOnCircle(timestamp / 1000)); circleLayer.setData(pointOnCircle(timestamp / 1000));
requestAnimationFrame(animateMarker); requestAnimationFrame(animateMarker);
} }
animateMarker(0); //animateMarker(0);
/** /**
const layerText = scene.PointLayer({ const layerText = scene.PointLayer({

View File

@ -35,15 +35,15 @@ const scene = new L7.Scene({
window.scene = scene; window.scene = scene;
scene.on('loaded', () => { scene.on('loaded', () => {
scene.ImageTileLayer({ scene.ImageTileLayer({
zIndex:0 zIndex:4
}) })
.source('http://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}') .source('http://t1.tianditu.com/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=174705aebfe31b79b3587279e211cb9a')
.render(); .render();
$.getJSON('https://gw.alipayobjects.com/os/rmsportal/JToMOWvicvJOISZFCkEI.json', city => { $.getJSON('https://gw.alipayobjects.com/os/rmsportal/JToMOWvicvJOISZFCkEI.json', city => {
const citylayer = scene.PolygonLayer( const citylayer = scene.PolygonLayer(
{ {
zIndex:4 zIndex:0
} }
) )
.source(city) .source(city)

View File

@ -34,6 +34,7 @@ const scene = new L7.Scene({
}); });
window.scene = scene; window.scene = scene;
scene.on('loaded', () => { scene.on('loaded', () => {
const layer = scene.VectorTileLayer({ const layer = scene.VectorTileLayer({
zIndex:0, zIndex:0,
layerType:'point' layerType:'point'
@ -46,25 +47,28 @@ scene.on('loaded', () => {
parser:{ parser:{
type: 'mvt', type: 'mvt',
sourceLayer:'layer', sourceLayer:'layer',
//idField:'OBJECTID', // idField:'adcode',
maxZoom: 17, maxZoom: 14,
minZoom: 13,
} }
}) })
.scale({ .scale({
total:{ total:{
min:0, min:0,
max:1000000, max:100000,
type:'log' type:'log'
} }
}) })
// cylinder .shape('normal')
.shape('hexagon') .size(1)
.size(2)
.active({fill:'red'}) .active({fill:'red'})
.color('total', ['#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd'].reverse()) .color('total', ['#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd'].reverse())
//.color('#0D408C') //.color('#0D408C')
.style({ .style({
opacity:1.0 stroke: 'rgba(255,255,255,0.8)',
strokeWidth: 1,
strokeOpacity:0.6,
opacity: 1
}) })
.render( .render(
); );
@ -73,10 +77,6 @@ scene.on('loaded', () => {
}) })
console.log(layer); console.log(layer);
}); });
//OBJECTID',(id)=>{
// const index = id % 8;
//return ['#9e0142','#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd','#5e4fa2'][index];
//}
</script> </script>
</body> </body>
</html> </html>

View File

@ -30,51 +30,66 @@ const scene = new L7.Scene({
center: [116.5909,39.9225 ], center: [116.5909,39.9225 ],
pitch: 0, pitch: 0,
hash:true, hash:true,
zoom: 14, zoom: 4,
}); });
window.scene = scene; window.scene = scene;
var colorHash = new ColorHash(); var colorHash = new ColorHash();
scene.on('loaded', () => { scene.on('loaded', () => {
scene.ImageTileLayer({
zIndex:4
})
.source('http://t1.tianditu.com/DataServer?T=cva_w&X={x}&Y={y}&L={z}&tk=174705aebfe31b79b3587279e211cb9a')
.render();
const provinceSource = new L7.TileSource('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/china/province/{z}/{x}/{y}.pbf',{
parser:{
type: 'mvt',
sourceLayer:'layer',
idField:'code',
maxZoom: 5,
}
})
//
$.getJSON('https://gw.alipayobjects.com/os/basement_prod/b402ae15-c1ab-499b-834c-708e7c1a13be.json', city => {
const citylayer = scene.PolygonLayer()
.source(city)
.color('total', ['#ffffe5','#fff7bc','#fee391','#fec44f','#fe9929','#ec7014','#cc4c02','#993404','#662506'])
.shape('line')
.size(2)
.active(true)
.style({
opacity: 1.0
})
.render();
});
const layer = scene.VectorTileLayer({ const layer = scene.VectorTileLayer({
zIndex:0, zIndex:0,
layerType:'polygon' layerType:'polygon'
}) })
//.source('https://pre-lbs-show.taobao.com/gettile?x={x}&y={y}&z={z}&pipeId=pipe_vt_test') .source(provinceSource)
.scale({
// http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/point/{z}/{x}/{y}.pbf total:{
// https://mvt.amap.com/district/CHN2/8/203/105/4096?key= type:'linear',
.source('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/china/{z}/{x}/{y}.pbf',{ min:0,
parser:{ max:5000000
type: 'mvt',
sourceLayer:'layer',
idField:'adcode',
maxZoom: 17,
} }
}) })
//.scale()
// cylinder
.shape('fill') .shape('fill')
.size(2) .size(2)
.active({fill:'red'}) .active(false)
.color('name',name => { .color('total', ['#ffffe5','#fff7bc','#fee391','#fec44f','#fe9929','#ec7014','#cc4c02','#993404','#662506'])
//var colorHash = new ColorHash();
return colorHash.hex(name)
})
.style({ .style({
opacity:1.0 opacity:1.0
}) })
.render( .render();
);
layer.on('mousemove',(feature)=>{
console.log(feature);
})
console.log(layer);
}); });
//OBJECTID',(id)=>{
// const index = id % 8;
//return ['#9e0142','#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd','#5e4fa2'][index];
//}
</script> </script>
</body> </body>
</html> </html>

View File

@ -25,55 +25,49 @@
const scene = new L7.Scene({ const scene = new L7.Scene({
id: 'map', id: 'map',
mapStyle: 'dark', // 样式URL mapStyle: 'light', // 样式URL
center: [116.5909,39.9225 ], center: [116.5909,39.9225 ],
pitch: 0, pitch: 0,
hash:true, hash:true,
zoom: 14, zoom: 16,
}); });
window.scene = scene; window.scene = scene;
scene.on('loaded', () => { scene.on('loaded', () => {
const layer = scene.VectorTileLayer({ const layer = scene.VectorTileLayer({
zIndex:0, zIndex:0,
layerType:'heatmap' layerType:'point'
}) })
.source('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/point2/{z}/{x}/{y}.pbf',{ .source('http://alipay-rmsdeploy-image.cn-hangzhou.alipay.aliyun-inc.com/thinkgis/tile/china/all_point/{z}/{x}/{y}.pbf',{
parser:{ parser:{
type: 'mvt', type: 'mvt',
sourceLayer:'layer', sourceLayer:'layer',
maxZoom: 17, maxZoom:14,
} }
}) })
.scale({ .scale({
total:{ bc_grade:{
min:0, type:'cat',
max:1000000, values:[1, 2 ,3, 4]
type:'log' },
open_mode:{
type:'cat',
values:['线上','线下','自助']
} }
}) })
.shape('heatmap') .active(false)
.size('total',[ 0, 1]) // 'circle', 'triangle', 'square', 'pentagon', 'hexagon', 'octogon', 'hexagram', 'rhombus', 'vesica'
.shape('open_mode', ['circle','hexagon','hexagram'])
.size('bc_grade', [2,15])
.color('bc_grade', ['#ffffcc','#d9f0a3','#addd8e','#78c679','#31a354','#006837'])
.style({ .style({
intensity: 10, stroke: '#fff',
radius: 10, strokeWidth: 1.0,
opacity:1, strokeOpacity:1.,
rampColors: {
colors: [ '#ffda45ff', '#fde725ff', '#ffc934ff', '#ffb824ff', '#ffb824ff', '#fa8100ff' ],
positions: [ 0, 0.2, 0.4, 0.6, 0.9, 1.0 ]
}
}) })
.render( .render(
); );
layer.on('mousemove',(feature)=>{
console.log(feature);
})
console.log(layer);
}); });
//OBJECTID',(id)=>{
// const index = id % 8;
//return ['#9e0142','#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd','#5e4fa2'][index];
//}
</script> </script>
</body> </body>
</html> </html>

View File

@ -0,0 +1,57 @@
import Util from '../../util';
import { updateObjecteUniform } from '../../util/object3d-util';
export default class BufferController {
constructor(cfg) {
// defs 列定义
Util.assign(this, cfg);
if (!this.mesh) this.mesh = this.layer;
}
_updateColorAttributes() {
const filterData = this.mesh.layerData;
const colorKey = {};
for (let e = 0; e < filterData.length; e++) {
const item = filterData[e];
colorKey[item.id] = item.color;
}
this.layer._activeIds = null; // 清空选中元素xwxw
const colorAttr = this.mesh.mesh.geometry.attributes.a_color;
const pickAttr = this.mesh.mesh.geometry.attributes.pickingId;
pickAttr.array.forEach((id, index) => {
let newId = Math.abs(id);
let item = null;
let color = null;
if (this.mesh.layerSource.data.featureKeys) { // hash数据映射
newId = this.mesh.layerSource.data.featureKeys[newId].index;
item = filterData[newId];
color = colorKey[item.id];
} else {
item = filterData[newId - 1];
color = colorKey[newId];
}
if (item.hasOwnProperty('filter') && item.filter === false) {
colorAttr.array[index * 4 + 0] = 0;
colorAttr.array[index * 4 + 1] = 0;
colorAttr.array[index * 4 + 2] = 0;
colorAttr.array[index * 4 + 3] = 0;
pickAttr.array[index] = -id; // 通过Id数据过滤 id<0 不显示
} else {
colorAttr.array[index * 4 + 0] = color[0];
colorAttr.array[index * 4 + 1] = color[1];
colorAttr.array[index * 4 + 2] = color[2];
colorAttr.array[index * 4 + 3] = color[3];
pickAttr.array[index] = id;
}
});
colorAttr.needsUpdate = true;
pickAttr.needsUpdate = true;
}
_updateStyle(option) {
const newOption = { };
for (const key in option) {
newOption['u_' + key] = option[key];
}
updateObjecteUniform(this.mesh._object3D, newOption);
}
}

View File

@ -0,0 +1,34 @@
import Util from '../../util';
export default class EventContoller {
constructor(cfg) {
Util.assign(this, cfg);
}
_init() {
this.layer.scene.on('pick-' + this.layer.layerId, e => {
let { featureId, point2d, type } = e;
if (featureId < 0 && this._activeIds !== null) {
type = 'mouseleave';
}
this._activeIds = featureId;
// TODO 瓦片图层获取选中数据信息
const lnglat = this.layer.scene.containerToLngLat(point2d);
const { feature, style } = this.layer.getSelectFeature(featureId, lnglat);
// const style = this.layerData[featureId - 1];
const target = {
featureId,
feature,
style,
pixel: point2d,
type,
lnglat: { lng: lnglat.lng, lat: lnglat.lat }
};
if (featureId >= 0 || this._activeIds >= 0) { // 拾取到元素,或者离开元素
this.layer.emit(type, target);
}
});
}
_initMapEvent() {
}
}

View File

@ -1,4 +1,14 @@
import Scale from './scale'; import Scale from './scale';
import Mapping from './mapping';
import Picking from './pick';
import Interaction from './interaction';
import Event from './event';
import Buffer from './buffer';
export default { export default {
Scale Scale,
Mapping,
Picking,
Interaction,
Event,
Buffer
}; };

View File

@ -0,0 +1,39 @@
import Util from '../../util';
import { getInteraction } from '../../interaction/index';
export default class InteractionController {
constructor(cfg) {
// defs 列定义
Util.assign(this, cfg);
}
// interaction 方法
clearAllInteractions() {
const interactions = this.layer.get('interactions');
Util.each(interactions, (interaction, key) => {
interaction.destory();
delete interactions[key];
});
return this;
}
clearInteraction(type) {
const interactions = this.layer.get('interactions');
if (interactions[type]) {
interactions[type].destory();
delete interactions[type];
}
return this;
}
addInteraction(type, cfg = {}) {
cfg.layer = this.layer;
const Ctor = getInteraction(type);
const interaction = new Ctor(cfg);
this._setInteraction(type, interaction);
return this;
}
_setInteraction(type, interaction) {
const interactions = this.layer.get('interactions');
if (interactions[type]) {
interactions[type].destory();
}
interactions[type] = interaction;
}
}

View File

@ -0,0 +1,188 @@
import Util from '../../util';
import Global from '../../global';
import ScaleController from './scale';
import Attr from '../../attr/index';
export default class Mapping {
/** mapping
* 初始化mapping
* @param {*} cfg 配置
* @param {*} cfg.layer layer对象
* @param {*} cfg.mesh mesh对象
*/
constructor(cfg) {
Util.assign(this, cfg);
if (!this.mesh) this.mesh = this.layer;
this._init();
}
_init() {
this._initControllers();
this._initTileAttrs();
this._mapping();
}
update() {
this.mesh.set('scales', {});
this._initTileAttrs();
this._updateMaping();
}
_initControllers() {
const scalesOption = this.layer.get('scaleOptions');
const scaleController = new ScaleController({
defs: {
...scalesOption
}
});
this.mesh.set('scaleController', scaleController);
}
_createScale(field) {
const scales = this.mesh.get('scales');
this._initControllers(); // scale更新
let scale = scales[field];
if (!scale) {
scale = this.createScale(field);
scales[field] = scale;
}
return scale;
}
createScale(field) {
const data = this.mesh.layerSource.data.dataArray;
const scales = this.mesh.get('scales');
let scale = scales[field];
const scaleController = this.mesh.get('scaleController');
if (!scale) {
scale = scaleController.createScale(field, data);
scales[field] = scale;
}
return scale;
}
// 获取属性映射的值
_getAttrValues(attr, record) {
const scales = attr.scales;
const params = [];
for (let i = 0; i < scales.length; i++) {
const scale = scales[i];
const field = scale.field;
if (scale.type === 'identity') {
params.push(scale.value);
} else {
params.push(record[field]);
}
}
const indexZoom = params.indexOf('zoom');
indexZoom !== -1 ? params[indexZoom] = attr.zoom : null;
const values = attr.mapping(...params);
return values;
}
_mapping() {
const attrs = this.mesh.get('attrs');
const mappedData = [];
const data = this.mesh.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
newRecord.id = data[i]._id;
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
const attr = attrs[k];
const names = attr.names;
const values = this._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
newRecord[name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
newRecord[names[0]] = values.length === 1 ? values[0] : values;
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
}
// 通过透明度过滤数据
if (attrs.hasOwnProperty('filter')) {
mappedData.forEach(item => {
if (item.filter === false) {
(item.color[3] = 0);
item.id = -item.id;
}
});
}
this.mesh.layerData = mappedData;
}
/**
* 更新数据maping
* @param {*} layerSource 数据源
* @param {*} layer map
*/
_updateMaping() {
const attrs = this.mesh.get('attrs');
const data = this.mesh.layerSource.data.dataArray;
const layerData = this.mesh.layerData;
for (let i = 0; i < data.length; i++) {
const record = data[i];
for (const attrName in attrs) {
if (attrs.hasOwnProperty(attrName) && attrs[attrName].neadUpdate) {
const attr = attrs[attrName];
const names = attr.names;
const values = this._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
layerData[i][names[0]] = values.length === 1 ? values[0] : values;
}
attr.neadUpdate = true;
}
}
}
}
_initTileAttrs() {
const attrOptions = this.layer.get('attrOptions');
for (const type in attrOptions) {
if (attrOptions.hasOwnProperty(type)) {
this._updateTileAttr(type);
}
}
}
_updateTileAttr(type) {
const self = this;
const attrs = this.mesh.get('attrs');
const attrOptions = this.layer.get('attrOptions');
const option = attrOptions[type];
option.neadUpdate = true;
const className = Util.upperFirst(type);
const fields = this._parseFields(option.field);
const scales = [];
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const scale = self._createScale(field);
if (type === 'color' && Util.isNil(option.values)) { // 设置 color 的默认色值
option.values = Global.colors;
}
scales.push(scale);
}
option.scales = scales;
const attr = new Attr[className](option);
attrs[type] = attr;
}
_parseFields(field) {
if (Util.isArray(field)) {
return field;
}
if (Util.isString(field)) {
return field.split('*');
}
return [ field ];
}
}

View File

@ -0,0 +1,50 @@
import Util from '../../util';
import * as THREE from '../three';
import pickingFragmentShader from '../engine/picking/picking_frag.glsl';
import { updateObjecteUniform, destoryObject } from '../../util/object3d-util';
export default class PickContoller {
constructor(cfg) {
Util.assign(this, cfg);
this.pickObject3D = new THREE.Object3D();
this.addToPicking(this.pickObject3D);
}
getPickingId() {
return this.layer.scene._engine._picking.getNextId();
}
addToPicking(object) {
object.name = this.layer.layerId;
this.layer.scene._engine._picking.add(object);
}
removePickingObject(object) {
this.layer.scene._engine._picking.remove(object);
}
removePickingMesh(mesh) {
this.pickObject3D.remove(mesh);
destoryObject(mesh);
}
removePickMeshByName(name) {
for (let i = 0; i < this.pickObject3D.children.length; i++) {
if (this.pickObject3D.children[i].name === name) {
this.removePickingMesh(this.pickObject3D.children[i]);
}
}
}
removeAllMesh() {
this.pickObject3D.children.forEach(element => {
this.pickObject3D.remove(element);
destoryObject(element);
});
}
addPickMesh(mesh) {
const pickmaterial = mesh.material.clone();
pickmaterial.fragmentShader = pickingFragmentShader;
const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial);
pickingMesh.name = mesh.name;
pickingMesh.onBeforeRender = () => {
const zoom = this.layer.scene.getZoom();
updateObjecteUniform(pickingMesh, { u_zoom: zoom });
};
this.pickObject3D.add(pickingMesh);
}
}

View File

@ -50,8 +50,7 @@ class Picking {
} }
_update(point) { _update(point) {
const texture = this._pickingTexture; const texture = this._pickingTexture;
// this._pickingTexture this._renderer.render(this._pickingScene, this._camera, texture);
this._renderer.render(this._pickingScene, this._camera, this._pickingTexture);
this.pixelBuffer = new Uint8Array(4); this.pixelBuffer = new Uint8Array(4);
this._renderer.readRenderTargetPixels(texture, point.x, this._height - point.y, 1, 1, this.pixelBuffer); this._renderer.readRenderTargetPixels(texture, point.x, this._height - point.y, 1, 1, this.pixelBuffer);
@ -62,8 +61,23 @@ class Picking {
index === id ? object.visible = true : object.visible = false; index === id ? object.visible = true : object.visible = false;
}); });
} }
_layerIsVisable(object) {
const layers = this._world.getLayers();
let isVisable = false;
for (let i = 0; i < layers.length; i++) {
const layer = layers[i];
if (object.name === layer.layerId) {
isVisable = layer.get('visible');
break;
}
}
return isVisable;
}
_pickAllObject(point, normalisedPoint) { _pickAllObject(point, normalisedPoint) {
this.world.children.forEach((object, index) => { this.world.children.forEach((object, index) => {
if (!this._layerIsVisable(object)) {
return;
}
this._filterObject(index); this._filterObject(index);
const item = this._pick(point, normalisedPoint, object.name); const item = this._pick(point, normalisedPoint, object.name);
item.type = point.type; item.type = point.type;

View File

@ -7,9 +7,6 @@ import * as THREE from './three';
import ColorUtil from '../attr/color-util'; import ColorUtil from '../attr/color-util';
import Controller from './controller/index'; import Controller from './controller/index';
import source from './source'; import source from './source';
import pickingFragmentShader from '../core/engine/picking/picking_frag.glsl';
import { getInteraction } from '../interaction/index';
import Attr from '../attr/index';
import diff from '../util/diff'; import diff from '../util/diff';
import { updateObjecteUniform } from '../util/object3d-util'; import { updateObjecteUniform } from '../util/object3d-util';
import Util from '../util'; import Util from '../util';
@ -24,7 +21,6 @@ function parseFields(field) {
} }
return [ field ]; return [ field ];
} }
export default class Layer extends Base { export default class Layer extends Base {
getDefaultCfg() { getDefaultCfg() {
return { return {
@ -110,7 +106,7 @@ export default class Layer extends Base {
}; };
this._object3D.add(object); this._object3D.add(object);
if (type === 'fill') { if (type === 'fill') {
this._addPickMesh(object);// 不对边界线进行拾取 this.get('pickingController').addPickMesh(object);
} }
setTimeout(() => this.scene._engine.update(), 500); setTimeout(() => this.scene._engine.update(), 500);
} }
@ -302,26 +298,14 @@ export default class Layer extends Base {
this._setAttrOptions(attrName, attrCfg); this._setAttrOptions(attrName, attrCfg);
} }
_initControllers() { _initControllers() {
const scales = this.get('scaleOptions'); const mappingCtr = new Controller.Mapping({ layer: this });
const scaleController = new Controller.Scale({ const pickCtr = new Controller.Picking({ layer: this });
defs: { const interactionCtr = new Controller.Interaction({ layer: this });
...scales this.set('mappingController', mappingCtr);
} this.set('pickingController', pickCtr);
}); this.set('interacionController', interactionCtr);
this.set('scaleController', scaleController);
} }
createScale(field) {
const data = this.layerSource ? this.layerSource.data.dataArray : null;
const scales = this.get('scales');
let scale = scales[field];
const scaleController = this.get('scaleController');
if (!scale) {
scale = scaleController.createScale(field, data);
scales[field] = scale;
}
return scale;
}
render() { render() {
this.init(); this.init();
this.scene._engine.update(); this.scene._engine.update();
@ -330,20 +314,21 @@ export default class Layer extends Base {
// 重绘 度量, 映射,顶点构建 // 重绘 度量, 映射,顶点构建
repaint() { repaint() {
this.set('scales', {}); this.set('scales', {});
this._initControllers(); const mappingCtr = new Controller.Mapping({ layer: this });
this._initAttrs(); this.set('mappingController', mappingCtr);
this._mapping(); // this._initAttrs();
// this._mapping();
this.redraw(); this.redraw();
} }
// 初始化图层 // 初始化图层
init() { init() {
this._initControllers(); this._initControllers();
this._initAttrs(); // this._initAttrs();
this._updateDraw(); this._updateDraw();
} }
_initInteraction() { _initInteraction() {
if (this.get('allowActive')) { if (this.get('allowActive')) {
this.interaction('active'); this.get('interacionController').addInteraction('active');
} }
} }
_initMapEvent() { _initMapEvent() {
@ -389,16 +374,6 @@ export default class Layer extends Base {
updateObjecteUniform(this._object3D, { u_activeId: featureId }); updateObjecteUniform(this._object3D, { u_activeId: featureId });
} }
_initAttrs() {
// 对比 options变化判断如何更新
const attrOptions = this.get('attrOptions');
for (const type in attrOptions) {
if (attrOptions.hasOwnProperty(type)) {
this._updateAttr(type);
}
}
}
_setPreOption() { _setPreOption() {
const nextAttrs = this.get('attrOptions'); const nextAttrs = this.get('attrOptions');
const nextStyle = this.get('styleOptions'); const nextStyle = this.get('styleOptions');
@ -411,7 +386,7 @@ export default class Layer extends Base {
const preStyle = this.get('preStyleOption'); const preStyle = this.get('preStyleOption');
const nextStyle = this.get('styleOptions'); const nextStyle = this.get('styleOptions');
if (preAttrs === undefined && preStyle === undefined) { // 首次渲染 if (preAttrs === undefined && preStyle === undefined) { // 首次渲染
this._mapping(); // this._mapping();
this._setPreOption(); this._setPreOption();
this._scaleByZoom(); this._scaleByZoom();
this._initInteraction(); this._initInteraction();
@ -449,28 +424,6 @@ export default class Layer extends Base {
this._setPreOption(); this._setPreOption();
} }
_updateAttr(type) {
const self = this;
const attrs = this.get('attrs');
const attrOptions = this.get('attrOptions');
const option = attrOptions[type];
option.neadUpdate = true;
const className = Util.upperFirst(type);
const fields = parseFields(option.field);
const scales = [];
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const scale = self._createScale(field);
if (type === 'color' && Util.isNil(option.values)) { // 设置 color 的默认色值
option.values = Global.colors;
}
scales.push(scale);
}
option.scales = scales;
const attr = new Attr[className](option);
attrs[type] = attr;
}
_updateSize(zoom) { _updateSize(zoom) {
const sizeOption = this.get('attrOptions').size; const sizeOption = this.get('attrOptions').size;
const fields = parseFields(sizeOption.field); const fields = parseFields(sizeOption.field);
@ -495,96 +448,6 @@ export default class Layer extends Base {
} }
updateObjecteUniform(this._object3D, newOption); updateObjecteUniform(this._object3D, newOption);
} }
_mapping(source) {
const self = this;
const attrs = self.get('attrs');
const mappedData = [];
// const data = this.layerSource.propertiesData;
let data;
source ? data = source.data.dataArray : data = this.layerSource.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
newRecord.id = data[i]._id;
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
const attr = attrs[k];
const names = attr.names;
const values = self._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
newRecord[name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
newRecord[names[0]] = values.length === 1 ? values[0] : values;
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
}
// 通过透明度过滤数据
if (attrs.hasOwnProperty('filter')) {
mappedData.forEach(item => {
item.filter === false && (item.color[3] = 0);
});
}
this.layerData = mappedData;
return mappedData;
}
// 更新地图映射
_updateMaping(source, layer) {
const self = this;
const attrs = self.get('attrs');
const data = source ? source.data.dataArray : this.layerSource.data.dataArray;
const layerData = layer || this.layerData;
for (let i = 0; i < data.length; i++) {
const record = data[i];
for (const attrName in attrs) {
if (attrs.hasOwnProperty(attrName) && attrs[attrName].neadUpdate) {
const attr = attrs[attrName];
const names = attr.names;
const values = self._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
layerData[i][name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
layerData[i][names[0]] = values.length === 1 ? values[0] : values;
}
attr.neadUpdate = true;
}
}
}
}
// 获取属性映射的值
_getAttrValues(attr, record) {
const scales = attr.scales;
const params = [];
for (let i = 0; i < scales.length; i++) {
const scale = scales[i];
const field = scale.field;
if (scale.type === 'identity') {
params.push(scale.value);
} else {
params.push(record[field]);
}
}
const indexZoom = params.indexOf('zoom');
indexZoom !== -1 ? params[indexZoom] = attr.zoom : null;
const values = attr.mapping(...params);
return values;
}
_scaleByZoom() { _scaleByZoom() {
if (this._zoomScale) { if (this._zoomScale) {
this.map.on('zoomend', () => { this.map.on('zoomend', () => {
@ -594,41 +457,18 @@ export default class Layer extends Base {
} }
} }
getPickingId() {
return this.scene._engine._picking.getNextId();
}
addToPicking(object) {
this.scene._engine._picking.add(object);
}
removeFromPicking(object) {
this.scene._engine._picking.remove(object);
}
_addPickMesh(mesh) {
this._pickingMesh = new THREE.Object3D();
this._pickingMesh.name = this.layerId;
this.addToPicking(this._pickingMesh);
const pickmaterial = mesh.material.clone();
pickmaterial.fragmentShader = pickingFragmentShader;
const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial);
pickingMesh.name = this.layerId;
pickingMesh.onBeforeRender = () => {
const zoom = this.scene.getZoom();
updateObjecteUniform(pickingMesh, { u_zoom: zoom });
};
this._pickingMesh.add(pickingMesh);
}
_initEvents() { _initEvents() {
this.scene.on('pick-' + this.layerId, e => { this.scene.on('pick-' + this.layerId, e => {
let { featureId, point2d, type } = e; let { featureId, point2d, type } = e;
if (featureId < 0 && this._activeIds !== null) {
type = 'mouseleave';
}
this._activeIds = featureId;
// TODO 瓦片图层获取选中数据信息 // TODO 瓦片图层获取选中数据信息
const lnglat = this.scene.containerToLngLat(point2d); const lnglat = this.scene.containerToLngLat(point2d);
const { feature, style } = this.getSelectFeature(featureId, lnglat); let feature = null;
// const style = this.layerData[featureId - 1]; let style = null;
if (featureId !== -999) {
const res = this.getSelectFeature(featureId, lnglat);
feature = res.feature;
style = res.style;
}
const target = { const target = {
featureId, featureId,
feature, feature,
@ -637,9 +477,14 @@ export default class Layer extends Base {
type, type,
lnglat: { lng: lnglat.lng, lat: lnglat.lat } lnglat: { lng: lnglat.lng, lat: lnglat.lat }
}; };
if (featureId >= 0 || this._activeIds >= 0) { // 拾取到元素,或者离开元素 if (featureId >= 0) { // 拾取到元素,或者离开元素
this.emit(type, target); this.emit(type, target);
} }
if (featureId < 0 && this._activeIds >= 0) {
type = 'mouseleave';
this.emit(type, target);
}
this._activeIds = featureId;
}); });
} }
@ -656,7 +501,7 @@ export default class Layer extends Base {
* @param {*} object 更新颜色和数据过滤 * @param {*} object 更新颜色和数据过滤
*/ */
_updateAttributes(object) { _updateAttributes(object) {
this._updateMaping(); this.get('mappingController').update();
const filterData = this.layerData; const filterData = this.layerData;
this._activeIds = null; // 清空选中元素 this._activeIds = null; // 清空选中元素
const colorAttr = object.geometry.attributes.a_color; const colorAttr = object.geometry.attributes.a_color;
@ -694,13 +539,13 @@ export default class Layer extends Base {
offset = 5; offset = 5;
this.shapeType = 'text' && (offset = 10); this.shapeType = 'text' && (offset = 10);
} else if (this.type === 'polyline') { } else if (this.type === 'polyline' || this.type === 'line') {
offset = 2; offset = 2;
} else if (this.type === 'polygon') { } else if (this.type === 'polygon') {
offset = 1; offset = 1;
} }
this._object3D.position && (this._object3D.position.z = offset * Math.pow(2, 20 - zoom)); this._object3D.position && (this._object3D.position.z = offset * Math.pow(2, 20 - zoom));
if (zoom < minZoom || zoom > maxZoom) { if (zoom < minZoom || zoom >= maxZoom) {
this._object3D.visible = false; this._object3D.visible = false;
} else if (this.get('visible')) { } else if (this.get('visible')) {
this._object3D.visible = true; this._object3D.visible = true;
@ -712,7 +557,7 @@ export default class Layer extends Base {
this._object3D.children.forEach(child => { this._object3D.children.forEach(child => {
this._object3D.remove(child); this._object3D.remove(child);
}); });
this.removeFromPicking(this._pickingMesh); this.get('pickingController').removeAllMesh();
this.draw(); this.draw();
} }
// 更新mesh // 更新mesh
@ -720,37 +565,6 @@ export default class Layer extends Base {
} }
// interaction 方法
clearAllInteractions() {
const interactions = this.get('interactions');
Util.each(interactions, (interaction, key) => {
interaction.destory();
delete interactions[key];
});
return this;
}
clearInteraction(type) {
const interactions = this.get('interactions');
if (interactions[type]) {
interactions[type].destory();
delete interactions[type];
}
return this;
}
interaction(type, cfg = {}) {
cfg.layer = this;
const Ctor = getInteraction(type);
const interaction = new Ctor(cfg);
this._setInteraction(type, interaction);
return this;
}
_setInteraction(type, interaction) {
const interactions = this.get('interactions');
if (interactions[type]) {
interactions[type].destory();
}
interactions[type] = interaction;
}
styleCfg() { styleCfg() {
} }

View File

@ -105,7 +105,9 @@ export default class Scene extends Base {
this._container.addEventListener(event, e => { this._container.addEventListener(event, e => {
// 要素拾取 // 要素拾取
e.pixel || (e.pixel = e.point); e.pixel || (e.pixel = e.point);
requestAnimationFrame(() => {
this._engine._picking.pickdata(e); this._engine._picking.pickdata(e);
});
}, false); }, false);
}); });
} }

View File

@ -20,7 +20,7 @@ export default class Source extends Base {
super(cfg); super(cfg);
const transform = this.get('transforms'); const transform = this.get('transforms');
this._transforms = transform || []; this._transforms = transform || [];
const mapType = this.get('mapType'); const mapType = this.get('mapType') || 'AMap';
this.projectFlat = getMap(mapType).project; this.projectFlat = getMap(mapType).project;
// 数据解析 // 数据解析
this._init(); this._init();
@ -56,11 +56,14 @@ export default class Source extends Base {
const { type = 'geojson' } = parser; const { type = 'geojson' } = parser;
const data = this.get('data'); const data = this.get('data');
this.originData = getParser(type)(data, parser); this.originData = getParser(type)(data, parser);
this.data = { // this.data = {
dataArray: clone(this.originData.dataArray) // dataArray: clone(this.originData.dataArray)
}; // }; // TODO 关闭数据备份
this.data = this.originData;
if (this.data !== null) {
this.data.extent = extent(this.data.dataArray); this.data.extent = extent(this.data.dataArray);
} }
}
/** /**
* 数据统计 * 数据统计
*/ */
@ -96,6 +99,9 @@ export default class Source extends Base {
this._projectCoords(); this._projectCoords();
} }
_projectCoords() { _projectCoords() {
if (this.data === null) {
return;
}
this.data.dataArray.forEach(data => { this.data.dataArray.forEach(data => {
// data.coordinates = this._coordProject(data.coordinates); // data.coordinates = this._coordProject(data.coordinates);
data.coordinates = tranfrormCoord(data.coordinates, this._coorConvert.bind(this)); data.coordinates = tranfrormCoord(data.coordinates, this._coorConvert.bind(this));

View File

@ -17,8 +17,8 @@ export function LineMaterial(options) {
}, },
vertexShader: vs, vertexShader: vs,
fragmentShader: fs, fragmentShader: fs,
transparent: true, transparent: true
blending: THREE.AdditiveBlending // blending: THREE.AdditiveBlending
}); });
return material; return material;
} }
@ -50,8 +50,8 @@ export function MeshLineMaterial(options, defines) {
defines, defines,
vertexShader: vs, vertexShader: vs,
fragmentShader: fs, fragmentShader: fs,
transparent: true, transparent: true
blending: THREE.AdditiveBlending // blending: THREE.AdditiveBlending
}); });
return material; return material;
} }

View File

@ -1,4 +1,4 @@
import * as THREE from '../../core/three';
import Material from './material'; import Material from './material';
import { getModule } from '../../util/shaderModule'; import { getModule } from '../../util/shaderModule';
export default class PointMaterial extends Material { export default class PointMaterial extends Material {
@ -26,7 +26,7 @@ export default class PointMaterial extends Material {
this.vertexShader = vs; this.vertexShader = vs;
this.fragmentShader = fs; this.fragmentShader = fs;
this.transparent = true; this.transparent = true;
if (!this.uniforms.shape) { this.blending = THREE.AdditiveBlending; } // if (!this.uniforms.shape) { this.blending = THREE.AdditiveBlending; }
if (this.uniforms.u_texture) { if (this.uniforms.u_texture) {
this.defines.TEXCOORD_0 = true; this.defines.TEXCOORD_0 = true;
} }

View File

@ -18,6 +18,6 @@ void main() {
vTime = 1.0- (mod(u_time*50.,3600.)- position.z) / 100.; vTime = 1.0- (mod(u_time*50.,3600.)- position.z) / 100.;
// vTime = 1.0- (28800. + mod(u_time* 10.,28800.)- position.z / 1000.) / 100.; // vTime = 1.0- (28800. + mod(u_time* 10.,28800.)- position.z / 1000.) / 100.;
#endif #endif
gl_Position = matModelViewProjection * vec4(position.xy, 0., 1.0); gl_Position = matModelViewProjection * vec4(position.xy, 10., 1.0);
worldId = id_toPickColor(pickingId); worldId = id_toPickColor(pickingId);
} }

View File

@ -33,6 +33,7 @@ void main() {
float extrude_scale = pow(2.0, 20.0 - u_zoom); float extrude_scale = pow(2.0, 20.0 - u_zoom);
vec3 offset = vec3(normal * a_size * extrude_scale / 2.0 * a_miter); vec3 offset = vec3(normal * a_size * extrude_scale / 2.0 * a_miter);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xy + offset.xy, 0., 1.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xy + offset.xy, 0., 1.0);
// gl_Position.z -=0.8 * gl_Position.w;
#ifdef ANIMATE #ifdef ANIMATE
float alpha =1.0 - fract( mod(1.0- a_distance,u_interval)* (1.0/u_interval) + u_time / u_duration); float alpha =1.0 - fract( mod(1.0- a_distance,u_interval)* (1.0/u_interval) + u_time / u_duration);

View File

@ -3,6 +3,7 @@
import Scene from './core/scene'; import Scene from './core/scene';
import Global from './global'; import Global from './global';
import Source from './core/source'; import Source from './core/source';
import TileSource from './source/tileSource';
import { registerParser, registerTransform } from './source'; import { registerParser, registerTransform } from './source';
import { registerInteraction, getInteraction } from './interaction'; import { registerInteraction, getInteraction } from './interaction';
import { registerLayer } from './layer'; import { registerLayer } from './layer';
@ -11,6 +12,7 @@ export {
version, version,
Scene, Scene,
Source, Source,
TileSource,
registerParser, registerParser,
registerTransform, registerTransform,
registerLayer, registerLayer,

View File

@ -41,11 +41,15 @@ export default class PointLayer extends Layer {
} }
zoomchange(ev) { zoomchange(ev) {
super.zoomchange(ev); super.zoomchange(ev);
requestAnimationFrame(() => {
this._updateData(); this._updateData();
});
} }
dragend(ev) { dragend(ev) {
super.dragend(ev); super.dragend(ev);
requestAnimationFrame(() => {
this._updateData(); this._updateData();
});
} }
_updateData() { _updateData() {

View File

@ -25,5 +25,8 @@ export default function drawCircle(layerData, layer) {
}); });
material.depthTest = false; material.depthTest = false;
const fillMesh = new THREE.Mesh(geometry, material); const fillMesh = new THREE.Mesh(geometry, material);
aPosition.length = 0;
aPackedData.length = 0;
index.length = 0;
return fillMesh; return fillMesh;
} }

View File

@ -29,6 +29,10 @@ export default function DrawPolygonFill(layerData, layer) {
LIGHTING: true LIGHTING: true
}); });
const fillPolygonMesh = new THREE.Mesh(geometry, material); const fillPolygonMesh = new THREE.Mesh(geometry, material);
delete attributes.vertices;
delete attributes.colors;
delete attributes.pickingIds;
delete attributes.normals;
return fillPolygonMesh; return fillPolygonMesh;
} }

View File

@ -16,7 +16,6 @@ export default class ImageTile extends Tile {
const image = this._createDebugMesh(); const image = this._createDebugMesh();
this._createMesh(image); this._createMesh(image);
this.emit('tileLoaded'); this.emit('tileLoaded');
// return;
// const urlParams = { // const urlParams = {
// x: this._tile[0], // x: this._tile[0],
// y: this._tile[1], // y: this._tile[1],
@ -29,12 +28,10 @@ export default class ImageTile extends Tile {
// image.addEventListener('load', () => { // image.addEventListener('load', () => {
// this._isLoaded = true; // this._isLoaded = true;
// this._createMesh(image); // this._createMesh(image);
// this.emit('tileLoaded');
// this._ready = true; // this._ready = true;
// }, false); // }, false);
// // image.addEventListener('progress', event => {}, false);
// // image.addEventListener('error', event => {}, false);
// image.crossOrigin = ''; // image.crossOrigin = '';
// // Load image // // Load image
@ -73,6 +70,7 @@ export default class ImageTile extends Tile {
context.font = 'Bold 20px Helvetica Neue, Verdana, Arial'; context.font = 'Bold 20px Helvetica Neue, Verdana, Arial';
context.fillStyle = '#ff0000'; context.fillStyle = '#ff0000';
context.fillText(this._tile.join('/'), 20, 20); context.fillText(this._tile.join('/'), 20, 20);
context.strokeStyle = 'red';
context.rect(0, 0, 256, 256); context.rect(0, 0, 256, 256);
context.stroke(); context.stroke();
return canvas; return canvas;
@ -83,6 +81,9 @@ export default class ImageTile extends Tile {
} }
this._image.src = ''; this._image.src = '';
}
updateColor() {
} }
getSelectFeature() { getSelectFeature() {
return {}; return {};

View File

@ -2,6 +2,10 @@ import TileLayer from './tileLayer';
import ImageTile from './imageTile'; import ImageTile from './imageTile';
export default class ImageTileLayer extends TileLayer { export default class ImageTileLayer extends TileLayer {
constructor(scene, cfg) {
super(scene, cfg);
this.type = 'image';
}
_createTile(key, layer) { _createTile(key, layer) {
return new ImageTile(key, this.url, layer); return new ImageTile(key, this.url, layer);
} }

View File

@ -2,21 +2,9 @@ import * as THREE from '../../core/three';
import Base from '../../core/base'; import Base from '../../core/base';
import { destoryObject } from '../../util/object3d-util'; import { destoryObject } from '../../util/object3d-util';
import Controller from '../../core/controller/index'; import Controller from '../../core/controller/index';
import Util from '../../util';
import Global from '../../global';
import Attr from '../../attr/index';
import { toLngLatBounds, toBounds } from '@antv/geo-coord'; import { toLngLatBounds, toBounds } from '@antv/geo-coord';
const r2d = 180 / Math.PI; const r2d = 180 / Math.PI;
const tileURLRegex = /\{([zxy])\}/g; const tileURLRegex = /\{([zxy])\}/g;
function parseFields(field) {
if (Util.isArray(field)) {
return field;
}
if (Util.isString(field)) {
return field.split('*');
}
return [ field ];
}
export default class Tile extends Base { export default class Tile extends Base {
constructor(key, url, layer) { constructor(key, url, layer) {
super({ super({
@ -33,134 +21,52 @@ export default class Tile extends Base {
this._center = this._tileBounds.getCenter(); this._center = this._tileBounds.getCenter();
this._centerLnglat = this._tileLnglatBounds.getCenter(); this._centerLnglat = this._tileLnglatBounds.getCenter();
this._object3D = new THREE.Object3D(); this._object3D = new THREE.Object3D({ name: key });
this._object3D.frustumCulled = false;
// this._object3D.name = key;
this._object3D.onBeforeRender = () => { this._object3D.onBeforeRender = () => {
}; };
this._isLoaded = false; this._isLoaded = false;
this.requestTileAsync(data => this._init(data)); this.requestTileAsync(data => this._init(data));
} }
_init(data) { _init(data) {
// this._creatSource(data); // 获取Source
this.layerSource = data;
if (this.layerSource.data === null) {
this.isValid = false;
return;
}
this.isValid = true;
this._initControllers(); this._initControllers();
this._creatSource(data);
this._initTileAttrs();
this._mapping();
this._createMesh(); this._createMesh();
} }
repaint() {
this._initControllers();
this._createMesh();
}
requestTileAsync(done) {
const data = this.layer.tileSource.getTileData(this._tile[0], this._tile[1], this._tile[2]);
if (data.loaded) {
done(data.data);
} else {
data.data.then(data => {
done(data);
});
}
}
_initControllers() { _initControllers() {
const scales = this.layer.get('scaleOptions'); const mappingCtr = new Controller.Mapping({
const scaleController = new Controller.Scale({ layer: this.layer,
defs: { mesh: this
...scales
}
}); });
this.set('scaleController', scaleController); const bufferCtr = new Controller.Buffer({
} layer: this.layer,
_createScale(field) { mesh: this
// TODO scale更新
const scales = this.get('scales');
let scale = scales[field];
if (!scale) {
scale = this.createScale(field);
scales[field] = scale;
}
return scale;
}
createScale(field) {
const data = this.source.data.dataArray;
const scales = this.get('scales');
let scale = scales[field];
const scaleController = this.get('scaleController');
if (!scale) {
scale = scaleController.createScale(field, data);
scales[field] = scale;
}
return scale;
}
// 获取属性映射的值
_getAttrValues(attr, record) {
const scales = attr.scales;
const params = [];
for (let i = 0; i < scales.length; i++) {
const scale = scales[i];
const field = scale.field;
if (scale.type === 'identity') {
params.push(scale.value);
} else {
params.push(record[field]);
}
}
const indexZoom = params.indexOf('zoom');
indexZoom !== -1 ? params[indexZoom] = attr.zoom : null;
const values = attr.mapping(...params);
return values;
}
_mapping() {
const attrs = this.get('attrs');
const mappedData = [];
// const data = this.layerSource.propertiesData;
const data = this.source.data.dataArray;
for (let i = 0; i < data.length; i++) {
const record = data[i];
const newRecord = {};
newRecord.id = data[i]._id;
for (const k in attrs) {
if (attrs.hasOwnProperty(k)) {
const attr = attrs[k];
const names = attr.names;
const values = this._getAttrValues(attr, record);
if (names.length > 1) { // position 之类的生成多个字段的属性
for (let j = 0; j < values.length; j++) {
const val = values[j];
const name = names[j];
newRecord[name] = (Util.isArray(val) && val.length === 1) ? val[0] : val; // 只有一个值时返回第一个属性值
}
} else {
newRecord[names[0]] = values.length === 1 ? values[0] : values;
}
}
}
newRecord.coordinates = record.coordinates;
mappedData.push(newRecord);
}
// 通过透明度过滤数据
if (attrs.hasOwnProperty('filter')) {
mappedData.forEach(item => {
item.filter === false && (item.color[3] = 0);
}); });
} this.set('mappingController', mappingCtr);
this.layerData = mappedData; this.set('bufferController', bufferCtr);
}
_initTileAttrs() {
const attrOptions = this.layer.get('attrOptions');
for (const type in attrOptions) {
if (attrOptions.hasOwnProperty(type)) {
this._updateTileAttr(type);
}
}
}
_updateTileAttr(type) {
const self = this;
const attrs = this.get('attrs');
const attrOptions = this.layer.get('attrOptions');
const option = attrOptions[type];
option.neadUpdate = true;
const className = Util.upperFirst(type);
const fields = parseFields(option.field);
const scales = [];
for (let i = 0; i < fields.length; i++) {
const field = fields[i];
const scale = self._createScale(field);
if (type === 'color' && Util.isNil(option.values)) { // 设置 color 的默认色值
option.values = Global.colors;
}
scales.push(scale);
}
option.scales = scales;
const attr = new Attr[className](option);
attrs[type] = attr;
} }
_createMesh() {} _createMesh() {}
_getTileURL(urlParams) { _getTileURL(urlParams) {
@ -222,6 +128,15 @@ export default class Tile extends Base {
return false; return false;
} }
updateColor() {
const bufferCtr = this.get('bufferController');
this.get('mappingController').update();
bufferCtr._updateColorAttributes(this.getMesh().children[0]);
}
updateStyle() {
const bufferCtr = this.get('bufferController');
bufferCtr._updateStyle(this.getMesh().children[0]);
}
_preRender() { _preRender() {
} }
destroy() { destroy() {

View File

@ -14,4 +14,10 @@ export default class TileCache {
destory() { destory() {
this._cache.clear(); this._cache.clear();
} }
setNeedUpdate() {
this._cache._order.forEach(key => {
const tile = this._cache.get(key);
tile.needUpdate = true;
});
}
} }

View File

@ -1,11 +1,12 @@
import Layer from '../../core/layer'; import Layer from '../../core/layer';
import source from '../../core/source'; import Util from '../../util';
import diff from '../../util/diff';
import TileSource from '../../source/tileSource';
import * as THREE from '../../core/three'; import * as THREE from '../../core/three';
import Controller from '../../core/controller/index';
import Global from '../../global'; import Global from '../../global';
const { pointShape } = Global; const { pointShape } = Global;
import TileCache from './tileCache'; import TileCache from './tileCache';
import pickingFragmentShader from '../../core/engine/picking/picking_frag.glsl';
import { throttle, deepMix } from '@antv/util';
import { toLngLat, Bounds, Point } from '@antv/geo-coord'; import { toLngLat, Bounds, Point } from '@antv/geo-coord';
import { wrapNum } from '@antv/geo-coord/lib/util/index'; import { wrapNum } from '@antv/geo-coord/lib/util/index';
import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857'; import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857';
@ -13,17 +14,17 @@ export default class TileLayer extends Layer {
constructor(scene, cfg) { constructor(scene, cfg) {
super(scene, { super(scene, {
...cfg, ...cfg,
minSourceZoom: 0,
maxSOurceZoom: 18,
keepBuffer: 2 keepBuffer: 2
}); });
this._tileCache = new TileCache(100, this._destroyTile); this._tileCache = new TileCache(50, this._destroyTile);
this._crs = epsg3857; this._crs = epsg3857;
this._tiles = new THREE.Object3D(); this._tiles = new THREE.Object3D();
this._pickTiles = new THREE.Object3D();
this._pickTiles.name = this.layerId;
this.scene._engine._picking.add(this._pickTiles);
this._tiles.frustumCulled = false; this._tiles.frustumCulled = false;
this._tileKeys = []; this._tileKeys = [];
this.tileList = {}; this.tileList = {};
this.type = this.get('layerType');
} }
shape(field, values) { shape(field, values) {
const layerType = this.get('layerType'); const layerType = this.get('layerType');
@ -33,62 +34,78 @@ export default class TileLayer extends Layer {
this.shape = field; this.shape = field;
return this; return this;
} }
source(url, cfg = {}) { source(data, cfg = {}) {
this.url = url; if (data instanceof TileSource) {
this.sourceCfg = cfg; data.set('mapType', this.scene.mapType);
this.sourceCfg.mapType = this.scene.mapType; this.tileSource = data;
} else {
cfg.mapType = this.scene.mapType;
this.tileSource = new TileSource(data, cfg);
}
return this; return this;
} }
tileSource(data, cfg) { _initControllers() {
if (data instanceof source) { const pickCtr = new Controller.Picking({ layer: this });
return data; const interactionCtr = new Controller.Interaction({ layer: this });
} this.set('pickingController', pickCtr);
const tileSourceCfg = { this.set('interacionController', interactionCtr);
data,
zoom: this.scene.getZoom()
};
deepMix(tileSourceCfg, this.sourceCfg, cfg);
return new source(tileSourceCfg);
} }
render() { render() {
// this._initControllers(); this._visibleWithZoom();
this._initMapEvent(); this._updateDraw();
// this._initAttrs(); this.scene._engine.update();
this._initInteraction();
this.draw();
return this; return this;
} }
draw() { draw() {
this._object3D.add(this._tiles); this._object3D.add(this._tiles);
this._calculateLOD(); this._calculateLOD();
} }
drawTile() { drawTile() {
} }
zoomchange(ev) { zoomchange(ev) {
super.zoomchange(ev); super.zoomchange(ev);
throttle(this._calculateLOD, 200); this._visibleWithZoom();
requestAnimationFrame(() => {
this._calculateLOD();
});
this._calculateLOD(); this._calculateLOD();
} }
dragend(ev) { dragend(ev) {
super.dragend(ev); super.dragend(ev);
requestAnimationFrame(() => {
this._calculateLOD(); this._calculateLOD();
});
} }
_calculateLOD() { _calculateLOD() {
/** /**
* 加载完成 active * 加载完成 active
* 需要显示 current * 需要显示 current
* 是否保留 retain * 是否保留 retain
*/ */
const zoom = Math.floor(this.scene.getZoom()) - 1;
const minZoom = this.get('minZoom');
const maxZoom = this.get('maxZoom');
const minSourceZoom = this.tileSource.get('minSourceZoom');
const maxSourceZoom = this.tileSource.get('maxSourceZoom');
const currentZoom = this.scene.getZoom();
this.tileZoom = zoom > maxSourceZoom ? maxSourceZoom : zoom;
if (currentZoom < minZoom || currentZoom >= maxZoom || currentZoom < minSourceZoom) {
this._removeTiles();
this._object3D.visible = false;
return;
} else if (this.get('visible')) {
this._object3D.visible = true;
}
this.show();
this.updateTileList = []; this.updateTileList = [];
const zoom = Math.round(this.scene.getZoom()) - 1;
const center = this.scene.getCenter(); const center = this.scene.getCenter();
const centerPoint = this._crs.lngLatToPoint(toLngLat(center.lng, center.lat), zoom); const centerPoint = this._crs.lngLatToPoint(toLngLat(center.lng, center.lat), this.tileZoom);
const centerXY = centerPoint.divideBy(256).round(); const centerXY = centerPoint.divideBy(256).floor();
const pixelBounds = this._getPixelBounds(); const pixelBounds = this._getPixelBounds();
const tileRange = this._pxBoundsToTileRange(pixelBounds); const tileRange = this._pxBoundsToTileRange(pixelBounds);
const margin = this.get('keepBuffer'); const margin = this.get('keepBuffer');
@ -100,7 +117,7 @@ export default class TileLayer extends Layer {
isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); } 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 j = tileRange.min.y; j <= tileRange.max.y; j++) {
for (let i = tileRange.min.x; i <= tileRange.max.x; i++) { for (let i = tileRange.min.x; i <= tileRange.max.x; i++) {
const coords = [ i, j, zoom ]; const coords = [ i, j, this.tileZoom ];
const tile = this.tileList[coords.join('_')]; const tile = this.tileList[coords.join('_')];
if (tile) { if (tile) {
tile.current = true; tile.current = true;
@ -140,10 +157,9 @@ export default class TileLayer extends Layer {
break; break;
} }
} }
if ( if (pointShape['2d'].indexOf(shape) !== -1) {
pointShape['2d'].indexOf(shape) !== -1 || return 'circle';
pointShape['3d'].indexOf(shape) !== -1 } else if (pointShape['3d'].indexOf(shape) !== -1) {
) {
return 'fill'; return 'fill';
} else if (this.scene.image.imagesIds.indexOf(shape) !== -1) { } else if (this.scene.image.imagesIds.indexOf(shape) !== -1) {
return 'image'; return 'image';
@ -198,6 +214,10 @@ export default class TileLayer extends Layer {
this._pruneTiles(); this._pruneTiles();
return; return;
} }
if (tile.needUpdate) {
tile.updateColor();
tile.needUpdate = false;
}
this._tiles.add(tile.getMesh()); this._tiles.add(tile.getMesh());
t.active = true; t.active = true;
this._addPickTile(tile.getMesh()); this._addPickTile(tile.getMesh());
@ -207,23 +227,20 @@ export default class TileLayer extends Layer {
} }
} }
_addPickTile(meshobj) { _addPickTile(meshobj) {
if (this.type === 'image') {
return;
}
const pickCtr = this.get('pickingController');
const mesh = meshobj.children[0]; const mesh = meshobj.children[0];
const pickmaterial = mesh.material.clone(); mesh.name = meshobj.name;
pickmaterial.fragmentShader = pickingFragmentShader; pickCtr.addPickMesh(mesh);
const pickingMesh = new THREE[mesh.type](mesh.geometry, pickmaterial);
pickingMesh.name = this.layerId;
pickingMesh.onBeforeRender = () => {
const zoom = this.scene.getZoom();
pickingMesh.material.setUniformsValue('u_zoom', zoom);
};
this._pickTiles.add(pickingMesh);
} }
// 根据距离优先级查找 // 根据距离优先级查找
getSelectFeature(id, lnglat) { getSelectFeature(id, lnglat) {
const zoom = Math.round(this.scene.getZoom()) - 1; const zoom = this.tileZoom;
const tilePoint = this._crs.lngLatToPoint(toLngLat(lnglat.lng, lnglat.lat), zoom); const tilePoint = this._crs.lngLatToPoint(toLngLat(lnglat.lng, lnglat.lat), zoom);
const tileXY = tilePoint.divideBy(256).round(); const tileXY = tilePoint.divideBy(256).floor();
const key = [ tileXY.x, tileXY.y, zoom ].join('_'); const key = [ tileXY.x, tileXY.y, zoom ].join('_');
const tile = this._tileCache.getTile(key); const tile = this._tileCache.getTile(key);
const feature = tile ? tile.getSelectFeature(id) : null; const feature = tile ? tile.getSelectFeature(id) : null;
@ -231,7 +248,7 @@ export default class TileLayer extends Layer {
} }
_pruneTiles() { _pruneTiles() {
let tile; let tile;
const zoom = Math.round(this.scene.getZoom()) - 1; const zoom = this.tileZoom;
for (const key in this.tileList) { for (const key in this.tileList) {
const c = this.tileList[key].coords; const c = this.tileList[key].coords;
if (c[2] !== zoom || !this.noPruneRange.contains(new Point(c[0], c[1]))) { if (c[2] !== zoom || !this.noPruneRange.contains(new Point(c[0], c[1]))) {
@ -298,6 +315,8 @@ export default class TileLayer extends Layer {
const tileObj = this._tileCache.getTile(key); const tileObj = this._tileCache.getTile(key);
if (tileObj) { if (tileObj) {
tileObj._abortRequest(); tileObj._abortRequest();
const pickCtr = this.get('pickingController');
pickCtr && pickCtr.removePickMeshByName(tileObj.getMesh().name);
this._tiles.remove(tileObj.getMesh()); this._tiles.remove(tileObj.getMesh());
} }
if (tileObj && tileObj.getMesh().type === 'composer') { if (tileObj && tileObj.getMesh().type === 'composer') {
@ -316,11 +335,13 @@ export default class TileLayer extends Layer {
} }
}); });
} // 移除 空的geom } // 移除 空的geom
this.scene._engine.update(); this.scene._engine.update();
} }
_removeTiles() { _removeTiles() {
this.hide();
if (!this._tiles || !this._tiles.children) { if (!this._tiles || !this._tiles.children) {
return; return;
} }
@ -328,12 +349,17 @@ export default class TileLayer extends Layer {
for (let i = this._tiles.children.length - 1; i >= 0; i--) { for (let i = this._tiles.children.length - 1; i >= 0; i--) {
this._tiles.remove(this._tiles.children[i]); this._tiles.remove(this._tiles.children[i]);
} }
this.tileList = [];
this._tileKeys = [];
this._tileCache.destory();
const pickCtr = this.get('pickingController');
pickCtr.removeAllMesh();
} }
_getPixelBounds() { _getPixelBounds() {
const viewPort = this.scene.getBounds().toBounds(); const viewPort = this.scene.getBounds().toBounds();
const NE = viewPort.getNorthEast(); const NE = viewPort.getNorthEast();
const SW = viewPort.getSouthWest(); const SW = viewPort.getSouthWest();
const zoom = Math.round(this.scene.getZoom()) - 1; const zoom = this.tileZoom;
const center = this.scene.getCenter(); const center = this.scene.getCenter();
const NEPoint = this._crs.lngLatToPoint(toLngLat(NE.lng, NE.lat), zoom); const NEPoint = this._crs.lngLatToPoint(toLngLat(NE.lng, NE.lat), zoom);
const SWPoint = this._crs.lngLatToPoint(toLngLat(SW.lng, SW.lat), zoom); const SWPoint = this._crs.lngLatToPoint(toLngLat(SW.lng, SW.lat), zoom);
@ -374,6 +400,59 @@ export default class TileLayer extends Layer {
tile.destroy(); tile.destroy();
tile = null; tile = null;
} }
_updateDraw() {
const preAttrs = this.get('preAttrOptions');
const nextAttrs = this.get('attrOptions');
const preStyle = this.get('preStyleOption');
const nextStyle = this.get('styleOptions');
if (preAttrs === undefined && preStyle === undefined) { // 首次渲染
this._setPreOption();
this._scaleByZoom();
this._initControllers();
this._initInteraction();
this._initMapEvent();
this.draw();
this._setPreOption();
return;
}
if (!this._tiles.children.length > 0 || !this._object3D.visible) {
return;
}
// 更新数据颜色 过滤 filter
if (!Util.isEqual(preAttrs.color, nextAttrs.color) || !Util.isEqual(preAttrs.filter, nextAttrs.filter)) {
this._tileCache.setNeedUpdate();
this._tiles.children.forEach(tile => {
const tileObj = this._tileCache.getTile(tile.name);
tileObj.updateColor();
tileObj.needUpdate = false;
this.scene._engine.update();
});
}
// 更新Size
if (!Util.isEqual(preAttrs.size, nextAttrs.size)) {
// this._tiles.children(tile => {
// this._tileCache.get(tile.name).updateSize();
// });
}
// 更新形状
if (!Util.isEqual(preAttrs.shape, nextAttrs.shape)) {
// this._tiles.children(tile => {
// this._tileCache.get(tile.name).updateShape();
// });
}
if (!Util.isEqual(preStyle, nextStyle)) {
// 判断新增,修改,删除
const newStyle = {};
Util.each(diff(preStyle, nextStyle), ({ type, key, value }) => {
(type !== 'remove') && (newStyle[key] = value);
// newStyle[key] = type === 'remove' ? null : value;
});
this._tiles.children(tile => {
this._tileCache.get(tile.name).updateStyle();
});
}
this._setPreOption();
}
destroy() { destroy() {
} }
} }

View File

@ -5,15 +5,15 @@ import * as THREE from '../../core/three';
import MaskMaterial from '../../geom/material/tile/maskMaterial'; import MaskMaterial from '../../geom/material/tile/maskMaterial';
import { getRender } from '../render/index'; import { getRender } from '../render/index';
export default class VectorTile extends Tile { export default class VectorTile extends Tile {
requestTileAsync(done) { // requestTileAsync(done) {
// Making this asynchronous really speeds up the LOD framerate // // Making this asynchronous really speeds up the LOD framerate
setTimeout(() => { // setTimeout(() => {
if (!this._mesh) { // if (!this._mesh) {
// this._mesh = this._createMesh(); // // this._mesh = this._createMesh();
this._requestTile(done); // this._requestTile(done);
} // }
}, 0); // }, 0);
} // }
_requestTile(done) { _requestTile(done) {
const urlParams = { const urlParams = {
x: this._tile[0], x: this._tile[0],
@ -32,31 +32,35 @@ export default class VectorTile extends Tile {
}); });
} }
_creatSource(data) { _creatSource(data) {
this.source = this.layer.tileSource(data, { if (!data) return null;
this.layerSource = this.layer.tileSource(data, {
parser: { parser: {
tile: this._tile tile: this._tile
} }
}); });
} }
_createMesh() { _createMesh() {
const layerData = this.layerData;
if (this.layer.get('layerType') === 'point') { if (this.layer.get('layerType') === 'point') {
this.layer.shape = this.layer._getShape(this.layerData); this.layer.shape = this.layer._getShape(layerData);
} }
this.mesh = getRender(this.layer.get('layerType'), this.layer.shape)(this.layerData, this.layer); this.mesh = getRender(this.layer.get('layerType'), this.layer.shape)(layerData, this.layer);
if (this.mesh.type !== 'composer') { // 热力图的情况 if (this.mesh.type !== 'composer') { // 热力图的情况
this.mesh.onBeforeRender = renderer => { this.mesh.onBeforeRender = renderer => {
this._renderMask(renderer); this._renderMask(renderer);
}; };
this.mesh.onAfterRender = renderer => { this.mesh.onAfterRender = renderer => {
const context = renderer.context; const context = renderer.context;
context.clear(context.STENCIL_BUFFER_BIT);
context.disable(context.STENCIL_TEST); context.disable(context.STENCIL_TEST);
}; };
this._object3D.add(this.mesh); this._object3D.add(this.mesh);
} else { } else { // 如果是热力图
this._object3D = this.mesh; this._object3D = this.mesh;
} }
setTimeout(() => {
this.emit('tileLoaded'); this.emit('tileLoaded');
}, 0);
return this._object3D; return this._object3D;
} }
_renderMask(renderer) { _renderMask(renderer) {
@ -85,7 +89,7 @@ export default class VectorTile extends Tile {
// config the stencil buffer to collect data for testing // config the stencil buffer to collect data for testing
this.layer.scene._engine.renderScene(maskScene); this.layer.scene._engine.renderScene(maskScene);
context.colorMask(true, true, true, true); context.colorMask(true, true, true, true);
context.depthMask(true); context.depthMask(false);
renderer.clearDepth(); renderer.clearDepth();
// only render where stencil is set to 1 // only render where stencil is set to 1
@ -114,9 +118,10 @@ export default class VectorTile extends Tile {
this.xhrRequest.abort(); this.xhrRequest.abort();
} }
getSelectFeature(id) { getSelectFeature(id) {
const featureIndex = this.source.originData.featureKeys[id]; const featurekey = this.layerSource.originData.featureKeys[id];
if (featureIndex) { if (featurekey && featurekey.index !== undefined) {
return this.source.originData.dataArray[featureIndex]; const featureIndex = featurekey.index;
return this.layerSource.originData.dataArray[featureIndex];
} }
return null; return null;
} }
@ -126,7 +131,5 @@ export default class VectorTile extends Tile {
this._object3D = null; this._object3D = null;
this.maskScene = null; this.maskScene = null;
this.layerData = null; this.layerData = null;
this.source.destroy();
this.source = null;
} }
} }

View File

@ -177,7 +177,8 @@ class Linear extends Base {
return 0; return 0;
} }
const percent = (value - min) / (max - min); const percent = Math.min(1, Math.max(0, (value - min) / (max - min))); // 数据控制到 0-1 范围
// const percent = (value - min) / (max - min);
const rangeMin = this.rangeMin(); const rangeMin = this.rangeMin();
const rangeMax = this.rangeMax(); const rangeMax = this.rangeMax();
return rangeMin + percent * (rangeMax - rangeMin); return rangeMin + percent * (rangeMax - rangeMin);

View File

@ -1,6 +1,6 @@
import * as turfMeta from '@turf/meta'; import * as turfMeta from '@turf/meta';
import { getCoords } from '@turf/invariant'; import { getCoords } from '@turf/invariant';
import { BKDRHash } from '../../util/bkdr-hash'; import { djb2hash } from '../../util/bkdr-hash';
export default function geoJSON(data, cfg) { export default function geoJSON(data, cfg) {
const resultData = []; const resultData = [];
const featureKeys = {}; const featureKeys = {};
@ -8,19 +8,18 @@ export default function geoJSON(data, cfg) {
return item != null && item.geometry && item.geometry.type && item.geometry.coordinates && item.geometry.coordinates.length > 0; return item != null && item.geometry && item.geometry.type && item.geometry.coordinates && item.geometry.coordinates.length > 0;
}); });
// 数据为空时处理 // 数据为空时处理
let i = 0;
turfMeta.flattenEach(data, (currentFeature, featureIndex) => { // 多个polygon 拆成一个 turfMeta.flattenEach(data, (currentFeature, featureIndex) => { // 多个polygon 拆成一个
const coord = getCoords(currentFeature); const coord = getCoords(currentFeature);
let id = featureIndex + 1; let id = featureIndex + 1;
if (cfg.idField && currentFeature.properties[cfg.idField]) { if (cfg.idField && currentFeature.properties[cfg.idField]) {
const value = currentFeature.properties[cfg.idField]; const value = currentFeature.properties[cfg.idField];
// id = value; id = djb2hash(value) % 1000019;
id = BKDRHash(value) % 1000019; featureKeys[id] = {
// if (featureKeys[id] && featureIndex !== featureKeys[id]) { index: i++,
// // TODO 哈希冲突解决方法 idField: value
// console.log('哈希冲突', value); };
// }
} }
featureKeys[id] = featureIndex;
const dataItem = { const dataItem = {
...currentFeature.properties, ...currentFeature.properties,
coordinates: coord, coordinates: coord,

View File

@ -3,17 +3,24 @@ import * as VectorParser from '@mapbox/vector-tile';
import geojson from './geojson'; import geojson from './geojson';
export default function mvt(data, cfg) { export default function mvt(data, cfg) {
const tile = new VectorParser.VectorTile(new PBF(data)); const tile = new VectorParser.VectorTile(new PBF(data));
// CHN_Cities_L CHN_Cities CHN_L
const layerName = cfg.sourceLayer; const layerName = cfg.sourceLayer;
const features = []; const features = [];
const vectorLayer = tile.layers[layerName]; const vectorLayer = tile.layers[layerName];
if (vectorLayer === undefined) {
return null;
}
for (let i = 0; i < vectorLayer.length; i++) { for (let i = 0; i < vectorLayer.length; i++) {
const feature = vectorLayer.feature(i); const feature = vectorLayer.feature(i);
features.push(feature.toGeoJSON(cfg.tile[0], cfg.tile[1], cfg.tile[2])); const geofeature = feature.toGeoJSON(cfg.tile[0], cfg.tile[1], cfg.tile[2]);
if (geofeature.geometry.type === 'Polygon' && geofeature.geometry.coordinates[0].length < 20) {
continue;
} }
features.push(geofeature);
}
// console.log(features);
const geodata = { const geodata = {
type: 'FeatureCollection', type: 'FeatureCollection',
features features
}; };
return geojson(geodata, cfg); return features.length === 0 ? null : geojson(geodata, cfg);
} }

View File

@ -0,0 +1,20 @@
import LRUCache from '../util/lru-cache';
export default class TileDataCache {
constructor(limit = 50, tileDestroy) {
this._cache = new LRUCache(limit, tileDestroy);
}
getTile(key) {
return this._cache.get(key);
}
setTile(tile, key) {
this._cache.set(key, tile);
}
removeTile(key) {
return this._cache.delete(key);
}
destory() {
this._cache.clear();
}
}

92
src/source/tileSource.js Normal file
View File

@ -0,0 +1,92 @@
import Source from '../core/source';
import { getArrayBuffer } from '../util/ajax';
import TileDataCache from './tileDataCache';
const tileURLRegex = /\{([zxy])\}/g;
export default class TileSource extends Source {
constructor(url, cfg) {
super(cfg);
this.cfg = cfg;
this.urlTemplate = url;
this._tileDataCache = new TileDataCache(50, this.tileDestroy);
this.type = 'tile';
}
getTileData(x, y, z) {
const key = [ x, y, z ].join('_');
let tileData = this._tileDataCache.getTile(key);
if (!tileData) {
const tiledataPromise = new Promise(resolve => {
if (tileData) {
setTimeout(() => {
resolve(tileData);
}, 0);
} else {
this._requestTileData(x, y, z, resolve);
}
});
tileData = {
loading: true,
data: tiledataPromise
};
this._tileDataCache.setTile(tileData, key);
return tileData;
}
return tileData;
}
_init() {
const parser = this.get('parser');
this.set('minSourceZoom', parser && parser.minZoom || 0);
this.set('maxSourceZoom', parser && parser.maxZoom || 18);
}
_generateSource(x, y, z, data) {
this.cfg.parser.tile = [ x, y, z ];
const tileData = new Source({
...this.cfg,
mapType: this.get('mapType'),
data,
tile: [ x, y, z ]
});
return tileData;
}
_requestTileData(x, y, z, done) {
const urlParams = { x, y, z };
const url = this._getTileURL(urlParams);
const key = [ x, y, z ].join('_');
this.xhrRequest = getArrayBuffer({ url }, (err, data) => {
if (err) {
this._noData = true;
this._tileDataCache.setTile({ loaded: true, data: { data: null } }, key);
return;
}
const tileData = this._generateSource(x, y, z, data.data);
this._tileDataCache.setTile({ loaded: true, data: tileData }, key);
done(tileData);
});
}
_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];
});
}
tileDestroy(tile) {
if (!tile || !tile.data || tile.loading || !tile.data.data.dataArray) {
return;
}
const tileData = tile.data;
tileData.destroy();
tileData.data.dataArray.length = 0;
tileData.data.featureKeys = null;
tileData.originData.dataArray.length = 0;
tileData.originData.featureKeys = null;
}
}

View File

@ -14,3 +14,16 @@ export function BKDRHash(str) {
} }
return hash; return hash;
} }
export function djb2hash(str) {
let hash = 5381,
i = str.length;
while (i) {
hash = (hash * 33) ^ str.charCodeAt(--i);
}
/* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
* integers. Since we want the results to be always positive, convert the
* signed int to an unsigned by doing an unsigned bitshift. */
return hash >>> 0;
}

View File

@ -9,10 +9,14 @@ export default class LRUCache {
constructor(limit = 50, destroy = () => {}) { constructor(limit = 50, destroy = () => {}) {
this.limit = limit; this.limit = limit;
this.destroy = destroy; this.destroy = destroy;
this._order = [];
this.clear(); this.clear();
} }
clear() { clear() {
this._order.forEach(key => {
this.delete(key);
});
this._cache = {}; this._cache = {};
// access/update order, first item is oldest, last item is newest // access/update order, first item is oldest, last item is newest
this._order = []; this._order = [];

View File

@ -88,7 +88,28 @@ export default function(points, closed, indexOffset) {
// get orientation // get orientation
let flip = (dot(tangent, _normal) < 0) ? -1 : 1; let flip = (dot(tangent, _normal) < 0) ? -1 : 1;
const bevel = miterLen > miterLimit; const bevel = Math.abs(miterLen) > miterLimit;
// 处理前后两条线段重合的情况这种情况不需要使用任何接头miter/bevel
// 理论上这种情况下 miterLen = Infinity本应通过 isFinite(miterLen) 判断,
// 但是 AMap 投影变换后丢失精度只能通过一个阈值1000判断。
if (Math.abs(miterLen) > 1000) {
normal(_normal, lineA);
extrusions(attrPos, out, cur, _normal, 1);
attrDistance.push(d, d);
attrIndex.push(
_lastFlip === 1 ? [ index + 1, index + 3, index + 2 ]
: [ index, index + 2, index + 3 ]
);
// 避免在 Material 中使用 THREE.DoubleSide
attrIndex.push([ index + 2, index + 3, index + 4 ]);
count += 2;
_lastFlip = -1;
continue;
}
if (bevel) { if (bevel) {
miterLen = miterLimit; miterLen = miterLimit;