diff --git a/dev-demos/features/tile/geojson-vt.md b/dev-demos/features/tile/geojson-vt.md
new file mode 100644
index 0000000000..fc6139abaa
--- /dev/null
+++ b/dev-demos/features/tile/geojson-vt.md
@@ -0,0 +1,2 @@
+### geojson - vt
+
\ No newline at end of file
diff --git a/dev-demos/features/tile/geojson-vt.tsx b/dev-demos/features/tile/geojson-vt.tsx
new file mode 100644
index 0000000000..57d13f0936
--- /dev/null
+++ b/dev-demos/features/tile/geojson-vt.tsx
@@ -0,0 +1,72 @@
+// @ts-ignore
+import { Scene, Source, PolygonLayer } from '@antv/l7';
+// @ts-ignore
+import { Mapbox } from '@antv/l7-maps';
+import React, { useEffect } from 'react';
+
+export default () => {
+ useEffect(() => {
+ const scene = new Scene({
+ id: 'map',
+ stencil: true,
+ map: new Mapbox({
+ center: [121.268, 30.3628],
+ pitch: 0,
+ style: 'blank',
+ zoom: 4,
+ }),
+ });
+
+ fetch(
+ 'https://gw.alipayobjects.com/os/bmw-prod/2b7aae6e-5f40-437f-8047-100e9a0d2808.json',
+ )
+ .then((d) => d.json())
+ .then((data) => {
+ const source = new Source(data, {
+ parser: {
+ type: 'geojsonvt',
+ tileSize: 256,
+ zoomOffset: 0,
+ maxZoom: 9,
+ extent: [-180, -85.051129, 179, 85.051129],
+ },
+ });
+
+ // const line = new LineLayer({
+ // featureId: 'COLOR',
+ // sourceLayer: 'testName', // woods hillshade contour ecoregions ecoregions2 city
+ // }).source(source)
+ // .color('COLOR')
+ // .size(2)
+ // scene.addLayer(line);
+
+ const polygon = new PolygonLayer({
+ featureId: 'COLOR',
+ // sourceLayer: 'testName', // woods hillshade contour ecoregions ecoregions2 city
+ })
+ .source(source)
+ .color('COLOR')
+ .active(true);
+ scene.addLayer(polygon);
+
+ // const point = new PointLayer({
+ // featureId: 'COLOR',
+ // sourceLayer: 'testName', // woods hillshade contour ecoregions ecoregions2 city
+ // })
+ // .source(source)
+ // // .color('COLOR')
+ // .color('#f00')
+ // .size(20)
+ // scene.addLayer(point);
+ });
+ }, []);
+ return (
+
+ );
+};
diff --git a/dev-demos/features/tile/vectortile.tsx b/dev-demos/features/tile/vectortile.tsx
index 20d2172e2d..44ee82e5da 100644
--- a/dev-demos/features/tile/vectortile.tsx
+++ b/dev-demos/features/tile/vectortile.tsx
@@ -4,57 +4,6 @@ import { Scene, LineLayer } from '@antv/l7';
import { Mapbox } from '@antv/l7-maps';
import React, { useEffect } from 'react';
-const list = [
- {
- value: -28.0,
- color1: 'orange',
- province_adcode: '630000',
- province_adName: '青海省',
- province: '青海省',
- nnh: 2,
- },
- {
- value: 29.0,
- color1: 'orange',
- province_adcode: '640000',
- province_adName: '宁夏回族自治区',
- province: '宁夏回族自治区',
- nnh: 3,
- },
- {
- value: 60.0,
- color1: 'orange',
- province_adcode: '650000',
- province_adName: '新疆维吾尔自治区',
- province: '新疆维吾尔自治区',
- nnh: 4,
- },
- {
- value: -31.0,
- color1: 'orange',
- province_adcode: '710000',
- province_adName: '台湾省',
- province: '台湾省',
- nnh: 4,
- },
- {
- value: 80.0,
- color1: 'orange',
- province_adcode: '810000',
- province_adName: '香港特别行政区',
- province: '香港特别行政区',
- nnh: 4,
- },
- {
- value: -33.0,
- color1: 'orange',
- province_adcode: '820000',
- province_adName: '澳门特别行政区',
- province: '澳门特别行政区',
- nnh: 4,
- },
-];
-
export default () => {
useEffect(() => {
const scene = new Scene({
@@ -83,18 +32,10 @@ export default () => {
maxZoom: 9,
extent: [-180, -85.051129, 179, 85.051129],
},
- transforms: [
- {
- type: 'join',
- sourceField: 'nnh',
- targetField: 'NNH', // data 对应字段名 绑定到的地理数据
- data: list,
- },
- ],
},
)
.shape('simple')
- // .shape('line')
+
.color('COLOR')
.size(2)
.select(true);
diff --git a/packages/core/src/services/source/ISourceService.ts b/packages/core/src/services/source/ISourceService.ts
index 3fa6894591..d27a23df60 100644
--- a/packages/core/src/services/source/ISourceService.ts
+++ b/packages/core/src/services/source/ISourceService.ts
@@ -73,6 +73,7 @@ export interface ISource {
updateClusterData(zoom: number): void;
getFeatureById(id: number): unknown;
getFeatureId(field: string, value: any): number | undefined;
+ getParserType(): string;
getClusters(zoom: number): any;
getClustersLeaves(id: number): any;
updateFeaturePropertiesById(
diff --git a/packages/layers/src/line/index.ts b/packages/layers/src/line/index.ts
index 0f9eb7994b..ec2098619f 100644
--- a/packages/layers/src/line/index.ts
+++ b/packages/layers/src/line/index.ts
@@ -1,6 +1,7 @@
import BaseLayer from '../core/BaseLayer';
import { ILineLayerStyleOptions } from '../core/interface';
import LineModels, { LineModelType } from './models';
+import { isVectorTile } from '../tile/utils';
export default class LineLayer extends BaseLayer {
public type: string = 'LineLayer';
@@ -49,9 +50,11 @@ export default class LineLayer extends BaseLayer {
if (this.layerType) {
return this.layerType as LineModelType;
}
- if (this.layerSource.parser.type === 'mvt') {
+ const parserType = this.layerSource.getParserType();
+ if (isVectorTile(parserType)) {
return 'vectorline';
}
+
const shapeAttribute = this.styleAttributeService.getLayerStyleAttribute(
'shape',
);
diff --git a/packages/layers/src/point/index.ts b/packages/layers/src/point/index.ts
index 7492b53e69..4e33ea0548 100644
--- a/packages/layers/src/point/index.ts
+++ b/packages/layers/src/point/index.ts
@@ -2,6 +2,7 @@ import { IEncodeFeature } from '@antv/l7-core';
import BaseLayer from '../core/BaseLayer';
import { IPointLayerStyleOptions } from '../core/interface';
import PointModels, { PointType } from './models/index';
+import { isVectorTile } from '../tile/utils';
export default class PointLayer extends BaseLayer {
public type: string = 'PointLayer';
@@ -92,7 +93,8 @@ export default class PointLayer extends BaseLayer {
'earthFill',
'earthExtrude',
];
- if (this.layerSource.parser.type === 'mvt') {
+ const parserType = this.layerSource.getParserType();
+ if (isVectorTile(parserType)) {
return 'vectorpoint';
}
diff --git a/packages/layers/src/polygon/index.ts b/packages/layers/src/polygon/index.ts
index 089d0f5509..3afdb4eed0 100644
--- a/packages/layers/src/polygon/index.ts
+++ b/packages/layers/src/polygon/index.ts
@@ -2,6 +2,7 @@ import { IEncodeFeature } from '@antv/l7-core';
import BaseLayer from '../core/BaseLayer';
import { IPolygonLayerStyleOptions } from '../core/interface';
import PolygonModels, { PolygonModelType } from './models/';
+import { isVectorTile } from '../tile/utils';
export default class PolygonLayer extends BaseLayer {
public type: string = 'PolygonLayer';
@@ -29,9 +30,11 @@ export default class PolygonLayer extends BaseLayer {
}
protected getModelType(): PolygonModelType {
- if (this.layerSource.parser.type === 'mvt') {
+ const parserType = this.layerSource.getParserType();
+ if (isVectorTile(parserType)) {
return 'vectorpolygon';
}
+
const shapeAttribute = this.styleAttributeService.getLayerStyleAttribute(
'shape',
);
diff --git a/packages/layers/src/raster/index.ts b/packages/layers/src/raster/index.ts
index 754eec0c51..95bfa02ede 100644
--- a/packages/layers/src/raster/index.ts
+++ b/packages/layers/src/raster/index.ts
@@ -37,7 +37,8 @@ export default class RaterLayer extends BaseLayer {
protected getModelType(): RasterModelType {
// 根据 source 的类型判断 model type
- switch (this.layerSource.parser.type) {
+ const parserType = this.layerSource.getParserType();
+ switch (parserType) {
case 'raster':
return 'raster';
case 'rasterTile':
diff --git a/packages/layers/src/tile/manager/tileLayerManager.ts b/packages/layers/src/tile/manager/tileLayerManager.ts
index 6796ce9de4..89f5a68130 100644
--- a/packages/layers/src/tile/manager/tileLayerManager.ts
+++ b/packages/layers/src/tile/manager/tileLayerManager.ts
@@ -175,6 +175,7 @@ export class TileLayerManager implements ITileLayerManager {
);
const source = this.parent.getSource();
const { coords } = source?.data?.tilesetOptions || {};
+ const parentParserType = source.getParserType();
const layerShape = getLayerShape(this.parent.type, this.parent);
@@ -189,7 +190,7 @@ export class TileLayerManager implements ITileLayerManager {
shape: layerShape,
zIndex,
opacity,
- sourceLayer,
+ sourceLayer: parentParserType === 'geojsonvt' ? 'geojsonvt' : sourceLayer,
coords,
featureId,
color: colorValue,
diff --git a/packages/layers/src/tile/utils.ts b/packages/layers/src/tile/utils.ts
index b1b1754abc..c207dbf47f 100644
--- a/packages/layers/src/tile/utils.ts
+++ b/packages/layers/src/tile/utils.ts
@@ -6,6 +6,13 @@ import {
} from '@antv/l7-core';
import { DOM, Tile } from '@antv/l7-utils';
import { Container } from 'inversify';
+
+export const tileVectorParser = ['mvt', 'geojsonvt'];
+
+export function isVectorTile(parserType: string) {
+ return tileVectorParser.indexOf(parserType) > 0;
+}
+
export function registerLayers(parentLayer: ILayer, layers: ILayer[]) {
layers.map((layer) => {
const container = createLayerContainer(
diff --git a/packages/source/package.json b/packages/source/package.json
index 591477b31b..0e2e6e10c4 100644
--- a/packages/source/package.json
+++ b/packages/source/package.json
@@ -30,6 +30,7 @@
"@babel/runtime": "^7.7.7",
"@mapbox/geojson-rewind": "^0.5.2",
"@mapbox/vector-tile": "^1.3.1",
+ "geojson-vt": "^3.2.1",
"@turf/helpers": "^6.1.4",
"@turf/invariant": "^6.1.2",
"@turf/meta": "^6.0.2",
@@ -43,6 +44,7 @@
"supercluster": "^7.0.0"
},
"devDependencies": {
+ "@types/geojson-vt": "3.2.0",
"@types/d3-dsv": "^1.0.36",
"@types/d3-hexbin": "^0.2.3",
"@types/lodash": "^4.14.138",
diff --git a/packages/source/src/index.ts b/packages/source/src/index.ts
index 2d7063b771..2ddaf700a8 100644
--- a/packages/source/src/index.ts
+++ b/packages/source/src/index.ts
@@ -4,6 +4,7 @@ import geojson from './parser/geojson';
import image from './parser/image';
import json, { defaultData, defaultParser, defaultSource } from './parser/json';
import mapboxVectorTile from './parser/mvt';
+import geojsonVTTile from './parser/geojsonvt';
import raster from './parser/raster';
import rasterTile from './parser/raster-tile';
import Source from './source';
@@ -16,6 +17,7 @@ import { map } from './transform/map';
registerParser('rasterTile', rasterTile);
registerParser('mvt', mapboxVectorTile);
+registerParser('geojsonvt', geojsonVTTile);
registerParser('geojson', geojson);
registerParser('image', image);
registerParser('csv', csv);
diff --git a/packages/source/src/interface.ts b/packages/source/src/interface.ts
index 426f9129b6..b339829779 100644
--- a/packages/source/src/interface.ts
+++ b/packages/source/src/interface.ts
@@ -27,7 +27,20 @@ export enum RasterTileType {
IMAGE = 'image',
ARRAYBUFFER = 'arraybuffer',
}
-export interface IRasterTileParserCFG {
+
+export interface IGeojsonvtOptions {
+ maxZoom: number; // max zoom to preserve detail on
+ indexMaxZoom: number; // max zoom in the tile index
+ indexMaxPoints: number; // max number of points per tile in the tile index
+ tolerance: number; // simplification tolerance (higher means simpler)
+ extent: number; // tile extent
+ buffer: number; // tile buffer on each side
+ lineMetrics: boolean; // whether to calculate line metrics
+ promoteId: null; // name of a feature property to be promoted to feature.id
+ generateId: boolean; // whether to generate feature ids. Cannot be used with promoteId
+ debug: number; // logging level (0, 1 or 2)
+}
+export interface ITileParserCFG {
tileSize?: number;
minZoom?: number;
maxZoom?: number;
@@ -42,6 +55,8 @@ export interface IRasterTileParserCFG {
// 指定栅格瓦片的类型
dataType?: RasterTileType;
+ geojsonvtOptions?: IGeojsonvtOptions;
+
format?: any;
}
diff --git a/packages/source/src/parser/geojsonvt.ts b/packages/source/src/parser/geojsonvt.ts
new file mode 100644
index 0000000000..f77eed68d8
--- /dev/null
+++ b/packages/source/src/parser/geojsonvt.ts
@@ -0,0 +1,259 @@
+import { Tile, TileLoadParams, TilesetManagerOptions } from '@antv/l7-utils';
+import {
+ Feature,
+ FeatureCollection,
+ Geometries,
+ Properties,
+} from '@turf/helpers';
+import geojsonvt from 'geojson-vt';
+import { VectorTileLayer } from '@mapbox/vector-tile';
+import { IParserData, ITileParserCFG, IGeojsonvtOptions } from '../interface';
+
+const DEFAULT_CONFIG: Partial = {
+ tileSize: 256,
+ minZoom: 0,
+ maxZoom: Infinity,
+ zoomOffset: 0,
+};
+
+function signedArea(ring: any[]) {
+ let sum = 0;
+ for (let i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
+ p1 = ring[i];
+ p2 = ring[j];
+ sum += (p2.x - p1.x) * (p1.y + p2.y);
+ }
+ return sum;
+}
+
+function classifyRings(rings: any[]) {
+ const len = rings.length;
+
+ if (len <= 1) {
+ return [rings];
+ }
+
+ const polygons: any = [];
+ let polygon: any;
+ let ccw;
+
+ for (let i = 0; i < len; i++) {
+ const area = signedArea(rings[i]);
+ if (area === 0) {
+ continue;
+ }
+
+ if (ccw === undefined) {
+ ccw = area < 0;
+ }
+
+ if (ccw === area < 0) {
+ if (polygon) {
+ polygons.push(polygon);
+ }
+ polygon = [rings[i]];
+ } else {
+ polygon.push(rings[i]);
+ }
+ }
+ if (polygon) {
+ polygons.push(polygon);
+ }
+
+ return polygons;
+}
+
+const VectorTileFeatureTypes = ['Unknown', 'Point', 'LineString', 'Polygon'];
+
+function GetGeoJSON(
+ extent: number,
+ x: number,
+ y: number,
+ z: number,
+ vectorTileFeature: any,
+) {
+ let coords = vectorTileFeature.geometry as any;
+ const currenType = vectorTileFeature.type;
+
+ const currentProperties = vectorTileFeature.tags;
+ const currentId = vectorTileFeature.id;
+
+ const size = extent * Math.pow(2, z);
+ const x0 = extent * x;
+ const y0 = extent * y;
+
+ let type = VectorTileFeatureTypes[currenType];
+ let i;
+ let j;
+
+ function project(line: any[]) {
+ for (let j = 0; j < line.length; j++) {
+ const p = line[j];
+ if (p[3]) {
+ // 避免重复计算
+ break;
+ }
+ const y2 = 180 - ((p[1] + y0) * 360) / size;
+ const lng = ((p[0] + x0) * 360) / size - 180;
+ const lat =
+ (360 / Math.PI) * Math.atan(Math.exp((y2 * Math.PI) / 180)) - 90;
+ line[j] = [lng, lat, 0, 1];
+ }
+ }
+
+ switch (currenType) {
+ case 1:
+ const points = [];
+ for (i = 0; i < coords.length; i++) {
+ points[i] = coords[i][0];
+ }
+ coords = points;
+ project(coords);
+ break;
+
+ case 2:
+ for (i = 0; i < coords.length; i++) {
+ project(coords[i]);
+ }
+ break;
+
+ case 3:
+ coords = classifyRings(coords);
+ for (i = 0; i < coords.length; i++) {
+ for (j = 0; j < coords[i].length; j++) {
+ project(coords[i][j]);
+ }
+ }
+ break;
+ }
+
+ if (coords.length === 1) {
+ coords = coords[0];
+ } else {
+ type = 'Multi' + type;
+ }
+
+ const result = {
+ type: 'Feature',
+ geometry: {
+ type,
+ coordinates: coords,
+ },
+ properties: currentProperties,
+ id: currentId,
+ tileOrigin: [0, 0],
+ coord: '',
+ };
+
+ return result;
+}
+
+export type MapboxVectorTile = {
+ layers: { [_: string]: VectorTileLayer & { features: Feature[] } };
+};
+
+const getVectorTile = async (
+ tile: Tile,
+ tileIndex: any,
+ tileParams: TileLoadParams,
+ extent: number,
+): Promise => {
+ return new Promise((resolve) => {
+ const tileData = tileIndex.getTile(tile.z, tile.x, tile.y);
+ // tileData
+ const features: any = [];
+ tileData.features.map((vectorTileFeature: any) => {
+ const feature = GetGeoJSON(
+ extent,
+ tileParams.x,
+ tileParams.y,
+ tileParams.z,
+ vectorTileFeature,
+ );
+ features.push(feature);
+ });
+
+ const vectorTile = {
+ layers: {
+ // Tip: fixed SourceLayer Name
+ geojsonvt: {
+ features,
+ } as VectorTileLayer & {
+ features: Feature[];
+ },
+ },
+ } as MapboxVectorTile;
+
+ resolve(vectorTile);
+ });
+};
+
+function getGeoJSONVTOptions(cfg?: ITileParserCFG) {
+ const defaultOptions = {
+ // geojson-vt default options
+ maxZoom: 14, // max zoom to preserve detail on
+ indexMaxZoom: 5, // max zoom in the tile index
+ indexMaxPoints: 100000, // max number of points per tile in the tile index
+ tolerance: 3, // simplification tolerance (higher means simpler)
+ extent: 4096, // tile extent
+ buffer: 64, // tile buffer on each side
+ lineMetrics: false, // whether to calculate line metrics
+ promoteId: null, // name of a feature property to be promoted to feature.id
+ generateId: true, // whether to generate feature ids. Cannot be used with promoteId
+ debug: 0, // logging level (0, 1 or 2)
+ };
+
+ if (cfg === undefined || typeof cfg.geojsonvtOptions === 'undefined') {
+ return defaultOptions;
+ } else {
+ cfg.geojsonvtOptions.maxZoom &&
+ (defaultOptions.maxZoom = cfg.geojsonvtOptions.maxZoom);
+ cfg.geojsonvtOptions.indexMaxZoom &&
+ (defaultOptions.indexMaxZoom = cfg.geojsonvtOptions.indexMaxZoom);
+ cfg.geojsonvtOptions.indexMaxPoints &&
+ (defaultOptions.indexMaxPoints = cfg.geojsonvtOptions.indexMaxPoints);
+ cfg.geojsonvtOptions.tolerance &&
+ (defaultOptions.tolerance = cfg.geojsonvtOptions.tolerance);
+ cfg.geojsonvtOptions.extent &&
+ (defaultOptions.extent = cfg.geojsonvtOptions.extent);
+ cfg.geojsonvtOptions.buffer &&
+ (defaultOptions.buffer = cfg.geojsonvtOptions.buffer);
+ cfg.geojsonvtOptions.lineMetrics &&
+ (defaultOptions.lineMetrics = cfg.geojsonvtOptions.lineMetrics);
+ cfg.geojsonvtOptions.promoteId &&
+ (defaultOptions.promoteId = cfg.geojsonvtOptions.promoteId);
+ cfg.geojsonvtOptions.generateId &&
+ (defaultOptions.generateId = cfg.geojsonvtOptions.generateId);
+ cfg.geojsonvtOptions.debug &&
+ (defaultOptions.debug = cfg.geojsonvtOptions.debug);
+ return defaultOptions;
+ }
+}
+
+export default function geojsonVTTile(
+ data: FeatureCollection,
+ cfg: ITileParserCFG,
+): IParserData {
+ const geojsonOptions = getGeoJSONVTOptions(cfg) as geojsonvt.Options &
+ IGeojsonvtOptions;
+
+ const extent = geojsonOptions.extent || 4096;
+ const tileIndex = geojsonvt(data, geojsonOptions);
+
+ const getTileData = (tileParams: TileLoadParams, tile: Tile) => {
+ return getVectorTile(tile, tileIndex, tileParams, extent);
+ };
+
+ const tilesetOptions = {
+ ...DEFAULT_CONFIG,
+ ...cfg,
+ getTileData,
+ };
+
+ return {
+ data,
+ dataArray: [],
+ tilesetOptions,
+ isTile: true,
+ };
+}
diff --git a/packages/source/src/parser/mvt.ts b/packages/source/src/parser/mvt.ts
index 6843abf528..33bbb936d1 100644
--- a/packages/source/src/parser/mvt.ts
+++ b/packages/source/src/parser/mvt.ts
@@ -12,7 +12,7 @@ import {
} from '@mapbox/vector-tile';
import { Feature } from '@turf/helpers';
import Protobuf from 'pbf';
-import { IParserData, IRasterTileParserCFG } from '../interface';
+import { IParserData, ITileParserCFG } from '../interface';
const DEFAULT_CONFIG: Partial = {
tileSize: 256,
@@ -214,7 +214,7 @@ const getVectorTile = async (
export default function mapboxVectorTile(
data: string | string[],
- cfg?: IRasterTileParserCFG,
+ cfg?: ITileParserCFG,
): IParserData {
const coord = cfg?.coord || 'lnglat'; // lnglat - offset
const getTileData = (tileParams: TileLoadParams, tile: Tile) =>
diff --git a/packages/source/src/parser/raster-tile.ts b/packages/source/src/parser/raster-tile.ts
index 18af7def24..13e9152850 100644
--- a/packages/source/src/parser/raster-tile.ts
+++ b/packages/source/src/parser/raster-tile.ts
@@ -1,9 +1,5 @@
import { Tile, TileLoadParams, TilesetManagerOptions } from '@antv/l7-utils';
-import {
- IParserData,
- IRasterTileParserCFG,
- RasterTileType,
-} from '../interface';
+import { IParserData, ITileParserCFG, RasterTileType } from '../interface';
import { defaultFormat, getTileBuffer, getTileImage } from '../utils/getTile';
const DEFAULT_CONFIG: Partial = {
@@ -15,7 +11,7 @@ const DEFAULT_CONFIG: Partial = {
export default function rasterTile(
data: string | string[],
- cfg?: IRasterTileParserCFG,
+ cfg?: ITileParserCFG,
): IParserData {
const tileDataType: RasterTileType = cfg?.dataType || RasterTileType.IMAGE;
const getTileData = (tileParams: TileLoadParams, tile: Tile) => {
diff --git a/packages/source/src/source.ts b/packages/source/src/source.ts
index 6635857839..d10e3958d2 100644
--- a/packages/source/src/source.ts
+++ b/packages/source/src/source.ts
@@ -2,7 +2,6 @@
import { SyncHook } from '@antv/async-hook';
import {
IClusterOptions,
- IMapService,
IParseDataItem,
IParserCfg,
IParserData,
@@ -57,7 +56,6 @@ export default class Source extends EventEmitter implements ISource {
// 瓦片数据管理器
public tileset: TilesetManager | undefined;
- private readonly mapService: IMapService;
// 是否有效范围
private invalidExtent: boolean = false;
@@ -87,6 +85,10 @@ export default class Source extends EventEmitter implements ISource {
return this.clusterIndex.getLeaves(id, Infinity);
}
+ public getParserType() {
+ return this.parser.type;
+ }
+
public updateClusterData(zoom: number): void {
const { method = 'sum', field } = this.clusterOptions;
let data = this.clusterIndex.getClusters(