From 874221a4a99eaeef7338d0d1153d5272a64d6989 Mon Sep 17 00:00:00 2001 From: "@thinkinggis" Date: Mon, 7 Nov 2022 14:48:01 +0800 Subject: [PATCH] Feat tile (#1462) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: tilemanager warp * fix: tile mulipolygon id 问题 * chore: vector & tile 方法调整 * fix: add vector source * docs: 添加瓦片demo * fix: 删除 consolelog --- dev-demos/draw/demo/draw.tsx | 2 +- dev-demos/features/tile/raster/satellite.tsx | 3 +- dev-demos/tile/Sentinel-2.md | 5 + dev-demos/tile/citytile.md | 5 + dev-demos/tile/district/Sentinel-2.tsx | 45 ++++++ dev-demos/tile/district/chinamap.tsx | 1 - dev-demos/tile/district/citytile.tsx | 85 +++++++++++ dev-demos/tile/district/farmland.tsx | 139 ++++++++++++++++++ dev-demos/tile/district/geojson-vt.tsx | 1 - dev-demos/tile/district/rasterData.tsx | 13 +- dev-demos/tile/district/worldmap.tsx | 62 ++++---- dev-demos/tile/farmland.md | 5 + .../core/src/services/config/ConfigService.ts | 2 +- .../services/interaction/PickingService.ts | 64 +------- packages/layers/src/core/BaseLayer.ts | 2 +- packages/layers/src/polygon/models/fill.ts | 3 + .../src/tile/service/TileLayerService.ts | 44 +++++- .../src/tile/service/TilePickService.ts | 32 ++-- packages/layers/src/tile/tileFactory/Tile.ts | 41 +----- .../layers/src/tile/tileFactory/VectorTile.ts | 55 ++++--- packages/layers/src/tile/tileFactory/util.ts | 15 +- .../layers/src/tile/tileLayer/BaseLayer.ts | 25 +--- packages/source/src/index.ts | 1 + packages/source/src/parser/geojson.ts | 11 +- packages/source/src/parser/mvt.ts | 17 ++- packages/source/src/source/baseSource.ts | 12 ++ packages/source/src/source/index.ts | 2 + packages/source/src/source/interface.ts | 9 ++ packages/source/src/source/vector.ts | 60 ++++++++ packages/utils/src/tileset-manager/tile.ts | 1 + .../src/tileset-manager/tileset-manager.ts | 5 +- 31 files changed, 542 insertions(+), 225 deletions(-) create mode 100644 dev-demos/tile/Sentinel-2.md create mode 100644 dev-demos/tile/citytile.md create mode 100644 dev-demos/tile/district/Sentinel-2.tsx create mode 100644 dev-demos/tile/district/citytile.tsx create mode 100644 dev-demos/tile/district/farmland.tsx create mode 100644 dev-demos/tile/farmland.md create mode 100644 packages/source/src/source/baseSource.ts create mode 100644 packages/source/src/source/index.ts create mode 100644 packages/source/src/source/interface.ts create mode 100644 packages/source/src/source/vector.ts diff --git a/dev-demos/draw/demo/draw.tsx b/dev-demos/draw/demo/draw.tsx index 3bc43b623d..57762b2a4f 100644 --- a/dev-demos/draw/demo/draw.tsx +++ b/dev-demos/draw/demo/draw.tsx @@ -21,7 +21,7 @@ const Demo: React.FC = () => { scene.on('loaded', () => { const drawer = new DrawPolygon(scene, { areaOptions: {}, - liveUpdate: true, + // liveUpdate: true, }); setPolygonDrawer(drawer); console.log(drawer); diff --git a/dev-demos/features/tile/raster/satellite.tsx b/dev-demos/features/tile/raster/satellite.tsx index 082067afed..b7e0afe588 100644 --- a/dev-demos/features/tile/raster/satellite.tsx +++ b/dev-demos/features/tile/raster/satellite.tsx @@ -24,8 +24,7 @@ export default () => { parser: { type: 'rasterTile', tileSize: 256, - // zoomOffset: 0 - // zoomOffset: 1, + }, }, ); diff --git a/dev-demos/tile/Sentinel-2.md b/dev-demos/tile/Sentinel-2.md new file mode 100644 index 0000000000..db09296344 --- /dev/null +++ b/dev-demos/tile/Sentinel-2.md @@ -0,0 +1,5 @@ +--- +title: SenTinel 底图 +order: 2 +--- + \ No newline at end of file diff --git a/dev-demos/tile/citytile.md b/dev-demos/tile/citytile.md new file mode 100644 index 0000000000..a006b8c74f --- /dev/null +++ b/dev-demos/tile/citytile.md @@ -0,0 +1,5 @@ +--- +title: 北京瓦片 +order: 2 +--- + \ No newline at end of file diff --git a/dev-demos/tile/district/Sentinel-2.tsx b/dev-demos/tile/district/Sentinel-2.tsx new file mode 100644 index 0000000000..4afeaf989c --- /dev/null +++ b/dev-demos/tile/district/Sentinel-2.tsx @@ -0,0 +1,45 @@ +// @ts-ignore +import { Scene, RasterLayer, TileDebugLayer } from '@antv/l7'; +// @ts-ignore +import { Map } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; + +export default () => { + useEffect(() => { + const scene = new Scene({ + id: 'map', + stencil: true, + map: new Map({ + center: [113.270854, 23.141717], + zoom: 11, + }), + }); + + const url1 = + 'https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2020_3857_512/default/GoogleMapsCompatible_512/{z}/{y}/{x}.jpg'; + const layer1 = new RasterLayer({ + zIndex: 1, + }).source(url1, { + parser: { + type: 'rasterTile', + tileSize: 512, + updateStrategy: 'realtime', + }, + }); + + scene.on('loaded', () => { + scene.addLayer(layer1); + const debugerLayer = new TileDebugLayer(); + scene.addLayer(debugerLayer); + }); + }, []); + return ( +
+ ); +}; diff --git a/dev-demos/tile/district/chinamap.tsx b/dev-demos/tile/district/chinamap.tsx index ef2c7b6e2b..b47b5ff658 100644 --- a/dev-demos/tile/district/chinamap.tsx +++ b/dev-demos/tile/district/chinamap.tsx @@ -123,7 +123,6 @@ export default () => { scene.addLayer(line); scene.addLayer(line2); scene.addLayer(text); - scene.addLayer(debugerLayer); }); }, []); diff --git a/dev-demos/tile/district/citytile.tsx b/dev-demos/tile/district/citytile.tsx new file mode 100644 index 0000000000..616dad2431 --- /dev/null +++ b/dev-demos/tile/district/citytile.tsx @@ -0,0 +1,85 @@ +// @ts-ignore +import { Scene, Source, PolygonLayer, LineLayer } from '@antv/l7'; +// @ts-ignore +import { GaodeMapV2 } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; + +export default () => { + useEffect(() => { + const scene = new Scene({ + id: 'map', + stencil: true, + map: new GaodeMapV2({ + center: [116.39852, 39.918255], + zoom: 9, + }), + }); + + const source = new Source( + 'https://pre-gridwise.alibaba-inc.com/tile/test?z={z}&x={x}&y={y}', + { + parser: { + type: 'mvt', + tileSize: 256, + // minZoom: 9, + }, + }, + ); + + const layer = new PolygonLayer({ + featureId: 'space_id', + zIndex: 3, + mask: false, + sourceLayer: 'default', // woods hillshade contour ecoregions ecoregions2 city + }) + .source(source) + .shape('fill') + .scale('space_val', { + type: 'quantize', + domain: [0, 100], + }) + .color('space_val', [ + '#f2f0f7', + '#cbc9e2', + '#9e9ac8', + '#756bb1', + '#54278f', + ]) + .style({ + opacity: 0.8, + }); + + const layer2 = new LineLayer({ + featureId: 'space_id', + zIndex: 3, + mask: false, + sourceLayer: 'default', // woods hillshade contour ecoregions ecoregions2 city + }) + .source(source) + .shape('simple') + .size(0.8) + .color('#3E6Eff') + .style({ + opacity: 1, + }); + + scene.on('loaded', () => { + scene.addLayer(layer); + scene.addLayer(layer2); + + // const debugerLayer = new TileDebugLayer(); + // scene.addLayer(debugerLayer); + }); + }, []); + return ( +
+ ); +}; + +// diff --git a/dev-demos/tile/district/farmland.tsx b/dev-demos/tile/district/farmland.tsx new file mode 100644 index 0000000000..25011592f4 --- /dev/null +++ b/dev-demos/tile/district/farmland.tsx @@ -0,0 +1,139 @@ +// @ts-ignore +import { + Scene, + Source, + PolygonLayer, + TileDebugLayer, + RasterLayer, + PointLayer, +} from '@antv/l7'; +// @ts-ignore +import { Map } from '@antv/l7-maps'; +import React, { useEffect } from 'react'; + +export default () => { + useEffect(() => { + const scene = new Scene({ + id: 'map', + stencil: true, + map: new Map({ + center: [-95.7548387434569, 44.82687715672517], + zoom: 9, + }), + }); + + const url1 = + 'https://api.mapbox.com/v4/mapbox.satellite/{z}/{x}/{y}.webp?sku=101ifSAcKcVFs&access_token=pk.eyJ1IjoidW5mb2xkZWRpbmMiLCJhIjoiY2s5ZG90MjMzMDV6eDNkbnh2cDJvbHl4NyJ9.BT2LAvHi31vNNEplsgxucQ'; + const layer1 = new RasterLayer({ + zIndex: 1, + }).source(url1, { + parser: { + type: 'rasterTile', + tileSize: 256, + }, + }); + + const source = new Source( + 'https://cdn.unfolded.ai/indigo/hexify_v5/{z}/{x}/{y}.pbf', + { + parser: { + type: 'mvt', + tileSize: 256, + // minZoom: 9, + }, + }, + ); + // const source2 = new Source( + // 'https://cdn.unfolded.ai/indigo/hexify_v5/{z}/{x}/{y}.pbf', + // { + // parser: { + // type: 'mvt', + // tileSize: 256, + // maxZoom: 9, + // }, + // }, + // ); + const layer = new PolygonLayer({ + featureId: 'id', + zIndex: 3, + minZoom: 9, + sourceLayer: 'state_s10_27', // woods hillshade contour ecoregions ecoregions2 city + }) + .source(source) + .shape('line') + .color('#000') + .size(0.3) + .style({ + opacity: 1, + }); + + const layer2 = new PolygonLayer({ + featureId: 'id', + zIndex: 2, + minZoom: 9, + sourceLayer: 'state_s10_27', // woods hillshade contour ecoregions ecoregions2 city + }) + .source(source) + .shape('fill') + .scale('croptype', { + type: 'quantize', + domain: [0, 4], + }) + .color('croptype', [ + '#C1C9CC', + '#DFB02F', + '#7F8120', + '#DCD0A4', + '#AD5633', + ]) + .style({ + opacity: 1, + }); + + const layer3 = new PointLayer({ + featureId: 'id', + zIndex: 2, + maxZoom: 9, + mask: true, + sourceLayer: 'parcel_pointgeojsonl', // woods hillshade contour ecoregions ecoregions2 city + }) + .source(source) + .shape('hexagon') + .size(2) + .scale('croptype', { + type: 'quantize', + domain: [0, 4], + }) + .color('croptype', [ + '#C1C9CC', + '#DFB02F', + '#7F8120', + '#DCD0A4', + '#AD5633', + ]) + .style({ + opacity: 1, + }); + + scene.on('loaded', () => { + scene.addLayer(layer); + scene.addLayer(layer1); + scene.addLayer(layer2); + scene.addLayer(layer3); + + const debugerLayer = new TileDebugLayer(); + scene.addLayer(debugerLayer); + }); + }, []); + return ( +
+ ); +}; + +// https://pre-gridwise.alibaba-inc.com/tile/test?z=13&x=6746&y=3104 diff --git a/dev-demos/tile/district/geojson-vt.tsx b/dev-demos/tile/district/geojson-vt.tsx index 8974c01766..73e7620d71 100644 --- a/dev-demos/tile/district/geojson-vt.tsx +++ b/dev-demos/tile/district/geojson-vt.tsx @@ -22,7 +22,6 @@ export default () => { ) .then((d) => d.json()) .then((data) => { - console.log(data); const source = new Source(data, { parser: { type: 'geojsonvt', diff --git a/dev-demos/tile/district/rasterData.tsx b/dev-demos/tile/district/rasterData.tsx index 2035feec26..a82000e9d7 100644 --- a/dev-demos/tile/district/rasterData.tsx +++ b/dev-demos/tile/district/rasterData.tsx @@ -129,18 +129,7 @@ export default () => { // layer.on('click', (e) => { // console.log('layer click'); // console.log(e); - // }); - - setTimeout(() => { - layer.style({ - opacity: 0.6, - rampColors: { - colors: ['#f00', '#ff0'], - positions: [0, 1], - }, - }); - scene.render(); - }, 2000); + // }) }); }); diff --git a/dev-demos/tile/district/worldmap.tsx b/dev-demos/tile/district/worldmap.tsx index 5466c18995..d1fce0c7c0 100644 --- a/dev-demos/tile/district/worldmap.tsx +++ b/dev-demos/tile/district/worldmap.tsx @@ -3,26 +3,34 @@ import { Scene, Source, PolygonLayer, + LineLayer, TileDebugLayer, PointLayer, } from '@antv/l7'; // @ts-ignore -import { Map } from '@antv/l7-maps'; +import { GaodeMapV2 } from '@antv/l7-maps'; import React, { useEffect } from 'react'; import { data } from './data'; export default () => { useEffect(() => { const counts = [10000, 5000, 1000, 500, 100]; - const color = ['#41ae76', '#99d8c9', '#ccece6', '#e5f5f9', '#f7fcfd']; + const color = [ + '#e41a1c', + '#377eb8', + '#4daf4a', + '#984ea3', + '#ff7f00', + '#ffff33', + ]; const scene = new Scene({ id: 'map', stencil: true, - map: new Map({ - center: [120, 30], + map: new GaodeMapV2({ + center: [100, 30], // zoom: 12, minZoom: 0, - zoom: 3, + zoom: 2, }), }); @@ -61,7 +69,7 @@ export default () => { return c.name == namestr; }); if (!country) { - return '#fff'; + return '#ffff33'; } const qz = ((country.qz as unknown) as number) * 1; if (qz > counts[0]) { @@ -77,30 +85,32 @@ export default () => { } }); - // const line = new LineLayer({ - // sourceLayer: 'WLD_L', - // zIndex: 2, - // }) - // .source(source) - // .shape('line') - // .size(0.6) - // .color('type', (t) => { - // if (t === '0') { - // return 'red'; - // } - // if (t === '2') { - // return '#09f'; - // } - // return '#fc9272'; - // }); + const line = new LineLayer({ + sourceLayer: 'WLD_L', + zIndex: 2, + }) + .source(source) + .shape('line') + .size(0.6) + .color('type', (t) => { + if (t === '0') { + return 'red'; + } + if (t === '2') { + return '#09f'; + } + return '#fc9272'; + }); const text = new PointLayer({ sourceLayer: 'WLD', - // blend: 'normal', + blend: 'normal', zIndex: 10, }) .source(source) - .shape('id', 'text') + .shape('NAME_CHN', (NAME_CHN) => { + return unicode2Char(NAME_CHN); + }) .size(12) .color('#000'); @@ -114,8 +124,8 @@ export default () => { scene.addLayer(water_surface); scene.addLayer(text); - // scene.addLayer(line); - const debugerLayer = new TileDebugLayer({ zIndex: 1 }); + scene.addLayer(line); + const debugerLayer = new TileDebugLayer(); scene.addLayer(debugerLayer); }); }, []); diff --git a/dev-demos/tile/farmland.md b/dev-demos/tile/farmland.md new file mode 100644 index 0000000000..54090339fc --- /dev/null +++ b/dev-demos/tile/farmland.md @@ -0,0 +1,5 @@ +--- +title: 矢量 FarmLand +order: 2 +--- + \ No newline at end of file diff --git a/packages/core/src/services/config/ConfigService.ts b/packages/core/src/services/config/ConfigService.ts index 57dacc59b0..b8210106ae 100644 --- a/packages/core/src/services/config/ConfigService.ts +++ b/packages/core/src/services/config/ConfigService.ts @@ -68,7 +68,7 @@ const defaultLayerConfig: Partial = { active: false, activeColor: '#2f54eb', enableHighlight: false, - enableSelect: true, + enableSelect: false, highlightColor: '#2f54eb', activeMix: 0, selectColor: 'blue', diff --git a/packages/core/src/services/interaction/PickingService.ts b/packages/core/src/services/interaction/PickingService.ts index 28c08377d2..11231ff794 100644 --- a/packages/core/src/services/interaction/PickingService.ts +++ b/packages/core/src/services/interaction/PickingService.ts @@ -209,14 +209,8 @@ export default class PickingService implements IPickingService { data: new Uint8Array(1 * 1 * 4), framebuffer: this.pickingFBO, }); - this.pickedColors = pickedColors; - // let pickedColors = new Uint8Array(4) - // this.rendererService.getGLContext().readPixels( - // Math.floor(xInDevicePixel / this.pickBufferScale), - // Math.floor((height - (y + 1) * DOM.DPR) / this.pickBufferScale), - // 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pickedColors) - // console.log(pickedColors[0] == pixels[0] && pickedColors[1] == pixels[1] && pickedColors[2] == pixels[2]) + this.pickedColors = pickedColors; if ( pickedColors[0] !== 0 || @@ -224,7 +218,7 @@ export default class PickingService implements IPickingService { pickedColors[2] !== 0 ) { const pickedFeatureIdx = decodePickingColor(pickedColors); - + // 瓦片数据获取性能问题需要优化 const rawFeature = layer.layerPickService.getFeatureById(pickedFeatureIdx); if ( pickedFeatureIdx !== layer.getCurrentPickId() && @@ -362,6 +356,7 @@ export default class PickingService implements IPickingService { return layer.needPick(target.type)}) .reverse() .some((layer) => { + clear({ framebuffer: this.pickingFBO, color: [0, 0, 0, 0], @@ -369,34 +364,8 @@ export default class PickingService implements IPickingService { depth: 1, }); - // Tip: clear last picked tilelayer state - // this.pickedTileLayers.map((pickedTileLayer) => - // (pickedTileLayer.tileLayer as ITileLayer)?.clearPick(target.type), - // ); - - // Tip: 如果当前 layer 是瓦片图层,则走瓦片图层独立的拾取逻辑 - // if (layer.tileLayer && (layer.tileLayer as ITileLayer).pickLayers) { - // return (layer.tileLayer as ITileLayer).pickLayers(target); - // } - - // 将当前的 layer 绘制到 pickingFBO - // 普通图层和瓦片图层的 layerPickService 拥有不同的 pickRender 方法 layer.layerPickService.pickRender(target); - - // layer.hooks.beforePickingEncode.call(); - - // if (layer.masks.length > 0) { - // // 若存在 mask,则在 pick 阶段的绘制也启用 - // layer.masks.map(async (m: ILayer) => { - // m.hooks.beforeRender.call(); - // m.render(); - // m.hooks.afterRender.call(); - // }); - // } - // layer.renderModels(true); - // layer.hooks.afterPickingEncode.call(); const isPicked = this.pickFromPickingFBO(layer, target); - this.layerService.pickedLayerId = isPicked ? +layer.id : -1; return isPicked && !layer.getLayerConfig().enablePropagation; }); @@ -422,31 +391,4 @@ export default class PickingService implements IPickingService { } } - /** - * highlight 如果直接修改选中 feature 的 buffer,存在两个问题: - * 1. 鼠标移走时无法恢复 - * 2. 无法实现高亮颜色与原始原色的 alpha 混合 - * 因此高亮还是放在 shader 中做比较好 - * @example - * this.layer.color('name', ['#000000'], { - * featureRange: { - * startIndex: pickedFeatureIdx, - * endIndex: pickedFeatureIdx + 1, - * }, - * }); - */ - private highlightPickedFeature( - layer: ILayer, - pickedColors: Uint8Array | undefined, - ) { - // @ts-ignore - const [r, g, b] = pickedColors; - layer.hooks.beforeHighlight.call([r, g, b]); - } - - private selectFeature(layer: ILayer, pickedColors: Uint8Array | undefined) { - // @ts-ignore - const [r, g, b] = pickedColors; - layer.hooks.beforeSelect.call([r, g, b]); - } } diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 9d562e4d25..36e3e74e58 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -870,7 +870,7 @@ export default class BaseLayer minZoom = -Infinity, maxZoom = Infinity, } = this.getLayerConfig(); - return !!visible && zoom >= minZoom && zoom <= maxZoom; + return !!visible && zoom >= minZoom && zoom < maxZoom; } public setMultiPass( diff --git a/packages/layers/src/polygon/models/fill.ts b/packages/layers/src/polygon/models/fill.ts index 7e445a8cd2..62766c35c6 100644 --- a/packages/layers/src/polygon/models/fill.ts +++ b/packages/layers/src/polygon/models/fill.ts @@ -25,6 +25,7 @@ export default class FillModel extends BaseModel { dir: 'in', }, } = this.layer.getLayerConfig() as IPolygonLayerStyleOptions; + if (this.dataTextureTest && this.dataTextureNeedUpdate({ opacity })) { this.judgeStyleAttributes({ opacity }); const encodeData = this.layer.getEncodedData(); @@ -52,7 +53,9 @@ export default class FillModel extends BaseModel { width: 1, height: 1, }); + } + return { u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1] u_cellTypeLayout: this.getCellTypeLayout(), diff --git a/packages/layers/src/tile/service/TileLayerService.ts b/packages/layers/src/tile/service/TileLayerService.ts index f372a2b172..e2336d4286 100644 --- a/packages/layers/src/tile/service/TileLayerService.ts +++ b/packages/layers/src/tile/service/TileLayerService.ts @@ -53,14 +53,52 @@ export class TileLayerService { } updateTileVisible(sourceTile: SourceTile) { const tile = this.getTile(sourceTile.key); + // if(sourceTile.isVisible) { + // // 不可见 => 可见 兄弟节点加载完成 + // if(sourceTile.parent) { + // const flag = this.isChildrenLoaded(sourceTile.parent) + // tile?.updateVisible(flag); + // } else { + // tile?.updateVisible(true); + // } + + // } else { + // // 可见 => 不可见 兄弟节点加载完成 + // if(sourceTile.parent) { + // const flag = this.isChildrenLoaded(sourceTile.parent) + // tile?.updateVisible(!flag); + // } else { + // tile?.updateVisible(false); + // } + // } + tile?.updateVisible(sourceTile.isVisible); } - beforeRender() { - // TODO 统一处理状态更新 attribute style - + public isParentLoaded(sourceTile: SourceTile): boolean { + const parentTile = sourceTile.parent; + if(!parentTile) { + return true + } + const tile = this.getTile(parentTile?.key) + if(tile?.isLoaded) { // 递归父级 + return true + } + + return false + } + public isChildrenLoaded(sourceTile: SourceTile):boolean { + const childrenTile = sourceTile?.children; + if(childrenTile.length === 0) { + return true + } + return childrenTile.some((tile:SourceTile)=>{ + const tileLayer = this.getTile(tile?.key) + return tileLayer?.isLoaded === false + }) + } async render() { const layers = this.getRenderLayers(); layers.map(async layer => { diff --git a/packages/layers/src/tile/service/TilePickService.ts b/packages/layers/src/tile/service/TilePickService.ts index 1cee9505f2..fb261600ec 100644 --- a/packages/layers/src/tile/service/TilePickService.ts +++ b/packages/layers/src/tile/service/TilePickService.ts @@ -1,4 +1,5 @@ import { ILayerService, ITile, ITilePickService, IInteractionTarget } from '@antv/l7-core'; +import { decodePickingColor, encodePickingColor } from '@antv/l7-utils'; import { TileLayerService } from './TileLayerService'; import { TileSourceService } from './TileSourceService'; export interface ITilePickServiceOptions { @@ -24,15 +25,14 @@ export class TilePickService implements ITilePickService{ if (tile) { // TODO 多图层拾取 const pickLayer = tile.getMainLayer(); - if (pickLayer) { - pickLayer.layerPickService.pickRender(target) - } + pickLayer?.layerPickService.pickRender(target) + } } selectFeature(pickedColors: Uint8Array | undefined) { // @ts-ignore const [r, g, b] = pickedColors; - const id = this.clor2PickId(r, g, b); + const id = this.color2PickId(r, g, b); this.tilePickID.set(SELECT, id); this.updateHighLight(r, g, b, SELECT); } @@ -40,24 +40,23 @@ export class TilePickService implements ITilePickService{ highlightPickedFeature(pickedColors: Uint8Array | undefined) { // @ts-ignore const [r, g, b] = pickedColors; - const id = this.clor2PickId(r, g, b); + const id = this.color2PickId(r, g, b); this.tilePickID.set(ACTIVE, id); this.updateHighLight(r, g, b, ACTIVE); } updateHighLight(r: number, g: number, b: number, type: string){ this.tileLayerService.tiles.map((tile: ITile) => { - const layers = tile.getLayers(); - layers.forEach((layer) => { + const layer = tile.getMainLayer(); switch(type) { case SELECT: - layer.hooks.beforeSelect.call([r, g, b]); + layer?.hooks.beforeSelect.call([r, g, b]); break; case ACTIVE: - layer.hooks.beforeHighlight.call([r, g, b]); + layer?.hooks.beforeHighlight.call([r, g, b]); break; } - }); + }); } @@ -76,12 +75,12 @@ export class TilePickService implements ITilePickService{ } } - private clor2PickId (r: number, g: number, b: number){ - return r + '-' + g + '-' + b; + private color2PickId (r: number, g: number, b: number){ + return decodePickingColor(new Uint8Array([r,g,b])) } - private pickId2Color(str: string){ - return str.split('-').map(n => +n) + private pickId2Color(str: number){ + return encodePickingColor(str ) } /** 从瓦片中根据数据 */ @@ -99,6 +98,9 @@ export class TilePickService implements ITilePickService{ } // 将 feature 列表合并后返回 // 统一返回成 polygon 的格式 点、线、面可以通用 - return this.tileSourceService.getCombineFeature(features); + + // const data = this.tileSourceService.getCombineFeature(features); + + return [] } } diff --git a/packages/layers/src/tile/tileFactory/Tile.ts b/packages/layers/src/tile/tileFactory/Tile.ts index c4264405ff..9cd67df2e1 100644 --- a/packages/layers/src/tile/tileFactory/Tile.ts +++ b/packages/layers/src/tile/tileFactory/Tile.ts @@ -1,7 +1,7 @@ import { ILayer, createLayerContainer, ILngLat, ITile } from '@antv/l7-core'; import { SourceTile } from '@antv/l7-utils'; import { Container } from 'inversify'; -import { Feature, Properties } from '@turf/helpers'; + export default abstract class Tile implements ITile{ public x: number; public y: number; @@ -35,6 +35,7 @@ export default abstract class Tile implements ITile{ return lng >= minLng && lng <= maxLng && lat >= minLat && lat <= maxLat; } + protected async addMask(layer: ILayer, mask: ILayer) { const container = createLayerContainer( this.parent.sceneContainer as Container, @@ -75,45 +76,17 @@ export default abstract class Tile implements ITile{ return this.layers[0]; } - public getFeatures(sourceLayer: string | undefined){ - if(!sourceLayer || !this.sourceTile.data?.layers[sourceLayer]) { - return []; - } - - const vectorTile = this.sourceTile.data?.layers[sourceLayer]; - - if(Array.isArray(vectorTile.features)) { - // 数据不需要被解析 geojson-vt 类型 - return vectorTile.features; - } - - const { x, y, z } = this.sourceTile; - const features: Feature[] = []; - for( let i = 0; i < vectorTile.length; i++ ) { - const vectorTileFeature = vectorTile.feature(i); - const feature = vectorTileFeature.toGeoJSON(x, y, z); - features.push({ - ...feature, - properties: { - id: feature.id, - ...feature.properties, - }, - }) - } - return features; + public getFeatures(sourceLayer: string | undefined):any[] { + return [] } - + /** * 在一个 Tile 中可能存在一个相同 ID 的 feature * @param id * @returns */ - public getFeatureById(id: number) { - const layer = this.getMainLayer(); - if (!layer) { - return []; - } - return layer.getSource().data.dataArray.filter(d => d._id === id); + public getFeatureById(id: number):any[] { + return [] } public destroy() { diff --git a/packages/layers/src/tile/tileFactory/VectorTile.ts b/packages/layers/src/tile/tileFactory/VectorTile.ts index 62af76a9d0..13ff9fa5ba 100644 --- a/packages/layers/src/tile/tileFactory/VectorTile.ts +++ b/packages/layers/src/tile/tileFactory/VectorTile.ts @@ -1,17 +1,16 @@ import { ILayer, ILayerAttributesOption } from '@antv/l7-core'; import Tile from './Tile'; -import { getTileLayer, getMaskLayer } from './util'; - +import { getTileLayer, isNeedMask } from './util'; +import MaskLayer from '../../mask'; +import { VectorSource } from '@antv/l7-source' export default class VectorTile extends Tile { public async initTileLayer(): Promise { const attributes = this.parent.getLayerAttributeConfig(); const layerOptions = this.parent.getLayerConfig() + layerOptions.mask = isNeedMask(this.parent.type) ||layerOptions.mask; const vectorLayer = getTileLayer(this.parent.type); - const maskLayer = getMaskLayer(this.parent.type); - layerOptions.mask = !!maskLayer; - - + const sourceOptions = this.getSourceOption(); if(!sourceOptions){ this.isLoaded = true; @@ -21,7 +20,7 @@ export default class VectorTile extends Tile { sourceOptions.data, sourceOptions.options, ); - + // 初始化数据映射 Object.keys(attributes).forEach((type) => { @@ -29,8 +28,17 @@ export default class VectorTile extends Tile { // @ts-ignore layer[attr](attributes[attr]?.field, attributes[attr]?.values); }); - if (maskLayer) { - const mask = new maskLayer({layerType: "MaskLayer"}) + if(layerOptions.mask ) { + await this.addTileMask(layer) + } + + await this.addLayer(layer); + this.setLayerMinMaxZoom(layer); + this.isLoaded = true; + } + // Todo 校验数据有效性 + protected async addTileMask(layer: ILayer) { + const mask = new MaskLayer({layerType: "MaskLayer"}) .source({ type: 'FeatureCollection', features: [ @@ -39,19 +47,10 @@ export default class VectorTile extends Tile { }, { parser: { type: 'geojson', + featureId: 'id' } }) - // .style({ - // opacity: 1 - // }); await this.addMask(layer, mask) - } - await this.addLayer(layer); - this.setLayerMinMaxZoom(layer); - this.isLoaded = true; - } - // Todo 校验数据有效性 - protected beforeInit() { } protected getSourceOption() { @@ -86,4 +85,22 @@ export default class VectorTile extends Tile { } } + public getFeatures(sourceLayer: string){ + const source = this.sourceTile.data as VectorSource; + return source.getTileData(sourceLayer); + } + + /** + * 在一个 Tile 中可能存在一个相同 ID 的 feature + * @param id + * @returns + */ + public getFeatureById(id: number) { + const layer = this.getMainLayer(); + if (!layer) { + return []; + } + return layer.getSource().data.dataArray.filter(d => d._id === id); + } + } diff --git a/packages/layers/src/tile/tileFactory/util.ts b/packages/layers/src/tile/tileFactory/util.ts index c1b44431d4..68394a9528 100644 --- a/packages/layers/src/tile/tileFactory/util.ts +++ b/packages/layers/src/tile/tileFactory/util.ts @@ -2,7 +2,7 @@ import PointLayer from '../../point/index'; import LineLayer from '../../line'; import PolygonLayer from '../../polygon'; -import MaskLayer from '../../mask'; + export function getTileLayer(type: string) { if(type === 'PolygonLayer') { @@ -18,15 +18,6 @@ export function getTileLayer(type: string) { } -export function getMaskLayer(type: string){ - switch(type) { - case 'PolygonLayer': - case 'LineLayer': - return MaskLayer; - case 'PointLayer': - case 'RasterLayer': - return undefined; - default: - return undefined; - } +export function isNeedMask(type: string) { + return ['PolygonLayer','LineLayer'].indexOf(type) !== -1 } \ No newline at end of file diff --git a/packages/layers/src/tile/tileLayer/BaseLayer.ts b/packages/layers/src/tile/tileLayer/BaseLayer.ts index 212aeaec2f..dde298028c 100644 --- a/packages/layers/src/tile/tileLayer/BaseLayer.ts +++ b/packages/layers/src/tile/tileLayer/BaseLayer.ts @@ -181,10 +181,12 @@ export default class BaseTileLayer { if (!this.tilesetManager) { return; } + const minZoom = this.parent.getMinZoom(); + const maxZoom = this.parent.getMaxZoom() await Promise.all(this.tilesetManager.tiles .filter((tile: SourceTile) => tile.isLoaded) // 过滤未加载完成的 .filter((tile: SourceTile) => tile.isVisibleChange) // 过滤未发生变化的 - .filter((tile: SourceTile) => this.isTileReady(tile)) // 过滤未发生变化的 + .filter((tile: SourceTile) => tile.z>= minZoom && tile.z < maxZoom) .map(async (tile: SourceTile) => { if (!this.tileLayerService.hasTile(tile.key)) { const tileInstance = getTileFactory(this.parent); @@ -193,11 +195,11 @@ export default class BaseTileLayer { this.tilePickService.setPickState(); if(tileLayer.getLayers().length!==0) { this.tileLayerService.addTile(tileLayer); + this.tileLayerService.updateTileVisible(tile); this.layerService.reRender() } - this.tileLayerService.addTile(tileLayer); - this.layerService.reRender() - } else { + } else {// 已加载瓦片 + this.tileLayerService.updateTileVisible(tile); this.tilePickService.setPickState(); this.layerService.reRender() @@ -210,21 +212,6 @@ export default class BaseTileLayer { } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public isTileReady(tile: SourceTile) { - - // if (tile.data?.layers && this.sourceLayer) { - // // vector - // const vectorTileLayer = tile.data.layers[this.sourceLayer]; - // const features = vectorTileLayer?.features; - // if (!(Array.isArray(features) && features.length > 0)) { - // return false; - // } - // } - - return true; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars public setPickState(layers: ILayer[]) {} diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts index 0f0f04c86e..cc170436d7 100644 --- a/packages/source/src/index.ts +++ b/packages/source/src/index.ts @@ -16,6 +16,7 @@ import { aggregatorToGrid } from './transform/grid'; import { pointToHexbin } from './transform/hexagon'; import { join } from './transform/join'; import { map } from './transform/map'; +export * from './source/index'; registerParser('rasterTile', rasterTile); registerParser('mvt', mapboxVectorTile); diff --git a/packages/source/src/parser/geojson.ts b/packages/source/src/parser/geojson.ts index 91d55a429f..5be3e82e30 100644 --- a/packages/source/src/parser/geojson.ts +++ b/packages/source/src/parser/geojson.ts @@ -36,16 +36,13 @@ function getFeatureID(feature: Feature, key?: string) { if (key === undefined) { return null; } - if (key === 'id' && feature.id) { - // 标准 mapbox vector feature - return feature.id; - } // @ts-ignore - if (feature[key]) { + if (feature.properties[key]) { // 单独指定要素 // @ts-ignore - return feature[key]; + return feature.properties[key]; } + if (feature.properties && feature.properties[key]) { // 根据 properties 要素的属性进行编码 return djb2hash(feature.properties[key] + '') % 1000019; @@ -59,7 +56,6 @@ export default function geoJSON( ): IParserData { const resultData: IParseDataItem[] = []; const featureKeys: IFeatureKey = {}; - if (!data.features) { data.features = []; return { @@ -95,6 +91,7 @@ export default function geoJSON( if (featureId === null) { featureId = featureIndex; } + const sortedID = featureId; const coord = getCoords(currentFeature); diff --git a/packages/source/src/parser/mvt.ts b/packages/source/src/parser/mvt.ts index b3bd2be27d..912631afad 100644 --- a/packages/source/src/parser/mvt.ts +++ b/packages/source/src/parser/mvt.ts @@ -7,10 +7,10 @@ import { TileLoadParams, TilesetManagerOptions, } from '@antv/l7-utils'; -import { VectorTile, VectorTileLayer } from '@mapbox/vector-tile'; +import { VectorTileLayer } from '@mapbox/vector-tile'; import { Feature } from '@turf/helpers'; -import Protobuf from 'pbf'; import { IParserData } from '../interface'; +import VectorSource from '../source/vector'; const DEFAULT_CONFIG: Partial = { tileSize: 256, @@ -29,7 +29,7 @@ const getVectorTile = async ( tileParams: TileLoadParams, tile: SourceTile, requestParameters?: Partial, -): Promise => { +): Promise => { const tileUrl = getURLFromTemplate(url, tileParams); return new Promise((resolve) => { const xhr = getArrayBuffer( @@ -39,12 +39,13 @@ const getVectorTile = async ( }, (err, data) => { if (err || !data) { - resolve({ layers: {} }); + resolve(undefined); } else { - const vectorTile = new VectorTile( - new Protobuf(data), - ) as MapboxVectorTile; - resolve(vectorTile); + const vectorSource = new VectorSource(data, tile.x, tile.y, tile.z); + // const vectorTile = new VectorTile( + // new Protobuf(data), + // ) as MapboxVectorTile; + resolve(vectorSource); } }, ); diff --git a/packages/source/src/source/baseSource.ts b/packages/source/src/source/baseSource.ts new file mode 100644 index 0000000000..7ff7b3f78c --- /dev/null +++ b/packages/source/src/source/baseSource.ts @@ -0,0 +1,12 @@ +import { ITileSource } from './interface'; +export default abstract class BaseSource implements ITileSource { + protected x: number; + protected y: number; + protected z: number; + constructor(data: any, x: number, y: number, z: number) { + this.x = x; + this.y = y; + this.z = z; + } + public abstract getTileData(layer: string): any; +} diff --git a/packages/source/src/source/index.ts b/packages/source/src/source/index.ts new file mode 100644 index 0000000000..e5974f4823 --- /dev/null +++ b/packages/source/src/source/index.ts @@ -0,0 +1,2 @@ +export * from './interface'; +export { default as VectorSource } from './vector'; diff --git a/packages/source/src/source/interface.ts b/packages/source/src/source/interface.ts new file mode 100644 index 0000000000..7a9eb63058 --- /dev/null +++ b/packages/source/src/source/interface.ts @@ -0,0 +1,9 @@ +import { VectorTileLayer } from '@mapbox/vector-tile'; +import { Feature } from '@turf/helpers'; +export interface ITileSource { + getTileData(layer: string): any; +} + +export type MapboxVectorTile = { + layers: { [_: string]: VectorTileLayer & { features: Feature[] } }; +}; diff --git a/packages/source/src/source/vector.ts b/packages/source/src/source/vector.ts new file mode 100644 index 0000000000..8343d4bf20 --- /dev/null +++ b/packages/source/src/source/vector.ts @@ -0,0 +1,60 @@ +import { VectorTile } from '@mapbox/vector-tile'; +import { Feature, Properties } from '@turf/helpers'; +import Protobuf from 'pbf'; +import { ITileSource, MapboxVectorTile } from './interface'; +export default class VectorSource implements ITileSource { + private vectorTile: VectorTile; + private vectorLayerCache: { + [key: string]: Array>; + } = {}; + private x: number; + private y: number; + private z: number; + + constructor(data: ArrayBuffer, x: number, y: number, z: number) { + this.x = x; + this.y = y; + this.z = z; + this.vectorTile = new VectorTile(new Protobuf(data)) as MapboxVectorTile; + } + + public getTileData(sourceLayer: string) { + if (!sourceLayer || !this.vectorTile.layers[sourceLayer]) { + return []; + } + // 优先走缓存 + if (this.vectorLayerCache[sourceLayer]) { + return this.vectorLayerCache[sourceLayer]; + } + + const vectorTile = this.vectorTile.layers[sourceLayer]; + + // @ts-ignore + if (Array.isArray(vectorTile.features)) { + // 数据不需要被解析 geojson-vt 类型 + // @ts-ignore + this.vectorLayerCache[sourceLayer] = vectorTile.features; + // @ts-ignore + return vectorTile.features; + } + + const features: Array> = []; + for (let i = 0; i < vectorTile.length; i++) { + const vectorTileFeature = vectorTile.feature(i); + const feature = vectorTileFeature.toGeoJSON(this.x, this.y, this.z); + + features.push({ + ...feature, + properties: { + id: feature.id, + ...feature.properties, + }, + }); + } + this.vectorLayerCache[sourceLayer] = features; + return features; + } + public getFeatureById() { + throw new Error('Method not implemented.'); + } +} diff --git a/packages/utils/src/tileset-manager/tile.ts b/packages/utils/src/tileset-manager/tile.ts index 3663a5c662..1bcd9c2461 100644 --- a/packages/utils/src/tileset-manager/tile.ts +++ b/packages/utils/src/tileset-manager/tile.ts @@ -105,6 +105,7 @@ export class SourceTile extends EventEmitter { const polygon = bboxPolygon(this.bounds as TileBounds, { properties: { key: this.key, + id: this.key, bbox: this.bounds, center, meta: ` diff --git a/packages/utils/src/tileset-manager/tileset-manager.ts b/packages/utils/src/tileset-manager/tileset-manager.ts index 24e81b14a7..12941533e0 100644 --- a/packages/utils/src/tileset-manager/tileset-manager.ts +++ b/packages/utils/src/tileset-manager/tileset-manager.ts @@ -109,10 +109,11 @@ export class TilesetManager extends EventEmitter { verifyZoom, latLonBoundsBuffer, ).filter((tile) => { + // 处理数据 warp return ( - this.options.warp || (tile.x >= 0 && tile.x <= Math.pow(verifyZoom, 2)) + this.options.warp || (tile.x >= 0 && tile.x < Math.pow(2, verifyZoom)) ); - }); // TODO 数据循环 + }); this.currentTiles = tileIndices.map(({ x, y, z }) => { let tile = this.getTile(x, y, z); if (tile) {