mirror of https://gitee.com/antv-l7/antv-l7
Merge branch 'master' of https://github.com/antvis/L7
This commit is contained in:
commit
ddc00aebba
|
@ -197,5 +197,7 @@
|
|||
"tnpm": {
|
||||
"mode": "yarn"
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"@antv/geo-coord": "^1.0.8"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -101,6 +101,8 @@ export interface ILayer {
|
|||
layerModelNeedUpdate: boolean;
|
||||
styleNeedUpdate: boolean;
|
||||
layerModel: ILayerModel;
|
||||
layerChildren: ILayer[]; // 在图层中添加子图层
|
||||
sceneContainer: Container | undefined;
|
||||
dataState: IDataState; // 数据流状态
|
||||
pickedFeatureID: number | null;
|
||||
hooks: {
|
||||
|
@ -137,7 +139,7 @@ export interface ILayer {
|
|||
needPick(type: string): boolean;
|
||||
getLayerConfig(): Partial<ILayerConfig & ISceneConfig>;
|
||||
getContainer(): Container;
|
||||
setContainer(container: Container): void;
|
||||
setContainer(container: Container, sceneContainer: Container): void;
|
||||
setCurrentPickId(id: number | null): void;
|
||||
getCurrentPickId(): number | null;
|
||||
setCurrentSelectedId(id: number | null): void;
|
||||
|
@ -377,7 +379,7 @@ export interface ILayerService {
|
|||
getLayers(): ILayer[];
|
||||
getLayer(id: string): ILayer | undefined;
|
||||
getLayerByName(name: string): ILayer | undefined;
|
||||
remove(layer: ILayer): void;
|
||||
remove(layer: ILayer, parentLayer?: ILayer): void;
|
||||
removeAllLayers(): void;
|
||||
updateRenderOrder(): void;
|
||||
renderLayers(type?: string): void;
|
||||
|
|
|
@ -64,11 +64,20 @@ export default class LayerService implements ILayerService {
|
|||
return this.layers.find((layer) => layer.name === name);
|
||||
}
|
||||
|
||||
public remove(layer: ILayer): void {
|
||||
const layerIndex = this.layers.indexOf(layer);
|
||||
if (layerIndex > -1) {
|
||||
this.layers.splice(layerIndex, 1);
|
||||
public remove(layer: ILayer, parentLayer?: ILayer): void {
|
||||
// Tip: layer.layerChildren 当 layer 存在子图层的情况
|
||||
if (parentLayer) {
|
||||
const layerIndex = parentLayer.layerChildren.indexOf(layer);
|
||||
if (layerIndex > -1) {
|
||||
parentLayer.layerChildren.splice(layerIndex, 1);
|
||||
}
|
||||
} else {
|
||||
const layerIndex = this.layers.indexOf(layer);
|
||||
if (layerIndex > -1) {
|
||||
this.layers.splice(layerIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
layer.emit('remove', null);
|
||||
layer.destroy();
|
||||
this.renderLayers();
|
||||
|
@ -91,17 +100,29 @@ export default class LayerService implements ILayerService {
|
|||
this.alreadyInRendering = true;
|
||||
this.clear();
|
||||
this.updateRenderOrder();
|
||||
|
||||
this.layers
|
||||
.filter((layer) => layer.inited)
|
||||
.filter((layer) => layer.isVisible())
|
||||
.forEach((layer) => {
|
||||
// trigger hooks
|
||||
layer.hooks.beforeRenderData.call();
|
||||
layer.hooks.beforeRender.call();
|
||||
layer.render();
|
||||
layer.hooks.afterRender.call();
|
||||
// Tip: 渲染 layer 的子图层 默认 layerChildren 为空数组 表示没有子图层 目前只有 ImageTileLayer 有子图层
|
||||
renderLayerEvent(layer.layerChildren);
|
||||
renderLayerEvent([layer]);
|
||||
});
|
||||
this.alreadyInRendering = false;
|
||||
|
||||
function renderLayerEvent(layers: ILayer[]) {
|
||||
layers
|
||||
.filter((layer) => layer.inited)
|
||||
.filter((layer) => layer.isVisible())
|
||||
.forEach((layer) => {
|
||||
// trigger hooks
|
||||
layer.hooks.beforeRenderData.call();
|
||||
layer.hooks.beforeRender.call();
|
||||
layer.render();
|
||||
layer.hooks.afterRender.call();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public updateRenderOrder() {
|
||||
|
@ -111,7 +132,14 @@ export default class LayerService implements ILayerService {
|
|||
}
|
||||
|
||||
public destroy() {
|
||||
this.layers.forEach((layer) => layer.destroy());
|
||||
this.layers.forEach((layer) => {
|
||||
// Tip: layer.layerChildren 当 layer 存在子图层的情况
|
||||
if (layer.layerChildren) {
|
||||
layer.layerChildren.forEach((child) => child.destroy());
|
||||
layer.layerChildren = [];
|
||||
}
|
||||
layer.destroy();
|
||||
});
|
||||
this.layers = [];
|
||||
this.renderLayers();
|
||||
}
|
||||
|
|
|
@ -187,7 +187,7 @@ export interface IEarthService<RawMap = {}> {
|
|||
): void;
|
||||
}
|
||||
|
||||
export const MapServiceEvent = ['mapload'];
|
||||
export const MapServiceEvent = ['mapload', 'mapchange'];
|
||||
|
||||
/**
|
||||
* 地图初始化配置项
|
||||
|
|
|
@ -113,6 +113,11 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
|
||||
public layerModel: ILayerModel;
|
||||
|
||||
// TODO: 记录 sceneContainer 供创建子图层的时候使用 如 imageTileLayer
|
||||
public sceneContainer: Container | undefined;
|
||||
// TODO: 用于保存子图层对象
|
||||
public layerChildren: ILayer[] = [];
|
||||
|
||||
@lazyInject(TYPES.IGlobalConfigService)
|
||||
protected readonly configService: IGlobalConfigService;
|
||||
|
||||
|
@ -222,8 +227,9 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
* -> SceneContainer 1.*
|
||||
* -> LayerContainer 1.*
|
||||
*/
|
||||
public setContainer(container: Container) {
|
||||
public setContainer(container: Container, sceneContainer: Container) {
|
||||
this.container = container;
|
||||
this.sceneContainer = sceneContainer;
|
||||
}
|
||||
|
||||
public getContainer() {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import BaseLayer from '../core/BaseLayer';
|
||||
import ImageTileModels, { ImageTileModelType } from './models/index';
|
||||
interface IImageLayerStyleOptions {
|
||||
opacity: number;
|
||||
}
|
||||
export default class ImageTileLayer extends BaseLayer<IImageLayerStyleOptions> {
|
||||
public type: string = 'ImageTileLayer';
|
||||
public buildModels() {
|
||||
const modelType = this.getModelType();
|
||||
this.layerModel = new ImageTileModels[modelType](this);
|
||||
this.models = this.layerModel.initModels();
|
||||
}
|
||||
public rebuildModels() {
|
||||
this.models = this.layerModel.buildModels();
|
||||
}
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
opacity: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
maximum: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
protected getDefaultConfig() {
|
||||
const type = this.getModelType();
|
||||
const defaultConfig = {
|
||||
imageTile: {},
|
||||
};
|
||||
return defaultConfig[type];
|
||||
}
|
||||
|
||||
protected getModelType(): ImageTileModelType {
|
||||
return 'imageTile';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
import {
|
||||
AttributeType,
|
||||
gl,
|
||||
IEncodeFeature,
|
||||
ILayer,
|
||||
ILayerPlugin,
|
||||
IModel,
|
||||
IModelUniform,
|
||||
IRasterParserDataItem,
|
||||
IStyleAttributeService,
|
||||
ITexture2D,
|
||||
lazyInject,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import BaseModel from '../../core/BaseModel';
|
||||
import { RasterImageTriangulation } from '../../core/triangulation';
|
||||
import ImageTileFrag from './shaders/imagetile_frag.glsl';
|
||||
import ImageTileVert from './shaders/imagetile_vert.glsl';
|
||||
|
||||
import Tile from '../utils/Tile';
|
||||
|
||||
interface IImageLayerStyleOptions {
|
||||
resolution: string;
|
||||
maxSourceZoom: number;
|
||||
}
|
||||
|
||||
export default class ImageTileModel extends BaseModel {
|
||||
public tileLayer: any;
|
||||
public getUninforms(): IModelUniform {
|
||||
return {};
|
||||
}
|
||||
|
||||
// 临时的瓦片测试方法
|
||||
public tile() {
|
||||
const [WS, EN] = this.mapService.getBounds();
|
||||
const NE = { lng: EN[0], lat: EN[1] };
|
||||
const SW = { lng: WS[0], lat: WS[1] };
|
||||
this.tileLayer.calCurrentTiles({
|
||||
NE,
|
||||
SW,
|
||||
tileCenter: this.mapService.getCenter(),
|
||||
currentZoom: this.mapService.getZoom(),
|
||||
minSourceZoom: this.mapService.getMinZoom(),
|
||||
minZoom: this.mapService.getMinZoom(),
|
||||
maxZoom: this.mapService.getMaxZoom(),
|
||||
});
|
||||
}
|
||||
|
||||
public initModels() {
|
||||
// TODO: 瓦片组件默认在最下层
|
||||
this.layer.zIndex = -999;
|
||||
const {
|
||||
resolution = 'low',
|
||||
maxSourceZoom = 17,
|
||||
} = this.layer.getLayerConfig() as IImageLayerStyleOptions;
|
||||
const source = this.layer.getSource();
|
||||
// 当存在 url 的时候生效
|
||||
if (source.data.tileurl) {
|
||||
this.tileLayer = new Tile({
|
||||
url: source.data.tileurl,
|
||||
layerService: this.layerService,
|
||||
layer: this.layer,
|
||||
resolution,
|
||||
maxSourceZoom,
|
||||
// Tip: 当前为 default
|
||||
crstype: 'epsg3857',
|
||||
});
|
||||
|
||||
this.tile();
|
||||
let t = new Date().getTime();
|
||||
this.mapService.on('mapchange', () => {
|
||||
const newT = new Date().getTime();
|
||||
const cutT = newT - t;
|
||||
t = newT;
|
||||
if (cutT < 16) {
|
||||
return;
|
||||
}
|
||||
this.tile();
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
this.layer.buildLayerModel({
|
||||
moduleName: 'ImageTileLayer',
|
||||
vertexShader: ImageTileVert,
|
||||
fragmentShader: ImageTileFrag,
|
||||
triangulation: RasterImageTriangulation,
|
||||
primitive: gl.TRIANGLES,
|
||||
depth: { enable: false },
|
||||
blend: this.getBlend(),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public clearModels() {
|
||||
this.tileLayer.removeTiles();
|
||||
}
|
||||
|
||||
public buildModels() {
|
||||
return this.initModels();
|
||||
}
|
||||
|
||||
protected registerBuiltinAttributes() {
|
||||
// point layer size;
|
||||
this.styleAttributeService.registerStyleAttribute({
|
||||
name: 'uv',
|
||||
type: AttributeType.Attribute,
|
||||
descriptor: {
|
||||
name: 'a_Uv',
|
||||
buffer: {
|
||||
// give the WebGL driver a hint that this buffer may change
|
||||
usage: gl.DYNAMIC_DRAW,
|
||||
data: [],
|
||||
type: gl.FLOAT,
|
||||
},
|
||||
size: 2,
|
||||
update: (
|
||||
feature: IEncodeFeature,
|
||||
featureIdx: number,
|
||||
vertex: number[],
|
||||
attributeIdx: number,
|
||||
) => {
|
||||
return [vertex[3], vertex[4]];
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import ImageTileModel from './imagetile';
|
||||
export type ImageTileModelType = 'imageTile';
|
||||
|
||||
const ImageTileModels: { [key in ImageTileModelType]: any } = {
|
||||
imageTile: ImageTileModel,
|
||||
};
|
||||
|
||||
export default ImageTileModels;
|
|
@ -0,0 +1,4 @@
|
|||
precision mediump float;
|
||||
void main() {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
precision highp float;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
uniform mat4 u_Mvp;
|
||||
attribute vec3 a_Position;
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||
// gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
|
||||
if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
|
||||
gl_Position = u_Mvp * (vec4(project_pos.xy,0., 1.0));
|
||||
} else {
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
precision mediump float;
|
||||
uniform float u_opacity: 1.0;
|
||||
uniform sampler2D u_texture;
|
||||
varying vec2 v_texCoord;
|
||||
void main() {
|
||||
vec4 color = texture2D(u_texture,vec2(v_texCoord.x,v_texCoord.y));
|
||||
gl_FragColor = color;
|
||||
gl_FragColor.a *= u_opacity;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
precision highp float;
|
||||
uniform mat4 u_ModelMatrix;
|
||||
uniform mat4 u_Mvp;
|
||||
attribute vec3 a_Position;
|
||||
attribute vec2 a_Uv;
|
||||
varying vec2 v_texCoord;
|
||||
#pragma include "projection"
|
||||
void main() {
|
||||
v_texCoord = a_Uv;
|
||||
vec4 project_pos = project_position(vec4(a_Position, 1.0));
|
||||
// gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
|
||||
if(u_CoordinateSystem == COORDINATE_SYSTEM_P20_2) { // gaode2.x
|
||||
gl_Position = u_Mvp * (vec4(project_pos.xy,0., 1.0));
|
||||
} else {
|
||||
gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy,0., 1.0));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import { LngLatBounds, toBounds, toLngLatBounds } from '@antv/geo-coord';
|
||||
import { Container } from 'inversify';
|
||||
import ImageLayer from '../../image';
|
||||
|
||||
interface IUrlParams {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
s?: string;
|
||||
}
|
||||
|
||||
const r2d = 180 / Math.PI;
|
||||
const tileURLRegex = /\{([zxy])\}/g;
|
||||
|
||||
export default class ImageTile {
|
||||
public tile: number[]; // 当前图片瓦片的索引
|
||||
public name: string;
|
||||
public imageLayer: any;
|
||||
constructor(
|
||||
key: string,
|
||||
url: string,
|
||||
container: Container,
|
||||
sceneContainer: Container,
|
||||
) {
|
||||
this.name = key;
|
||||
this.tile = key.split('_').map((v) => Number(v));
|
||||
|
||||
const urlParams = {
|
||||
x: this.tile[0],
|
||||
y: this.tile[1],
|
||||
z: this.tile[2],
|
||||
};
|
||||
const imageSrc = this.getTileURL(urlParams, url);
|
||||
|
||||
const lnglatBounds = this.tileLnglatBounds(this.tile);
|
||||
const west = lnglatBounds.getWest();
|
||||
const south = lnglatBounds.getSouth();
|
||||
const east = lnglatBounds.getEast();
|
||||
const north = lnglatBounds.getNorth();
|
||||
|
||||
const imageLayer = new ImageLayer({});
|
||||
imageLayer.source(
|
||||
// 'https://gw.alipayobjects.com/zos/rmsportal/FnHFeFklTzKDdUESRNDv.jpg',
|
||||
imageSrc,
|
||||
{
|
||||
parser: {
|
||||
type: 'image',
|
||||
// extent: [121.168, 30.2828, 121.384, 30.4219],
|
||||
extent: [west, south, east, north],
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
imageLayer.setContainer(container, sceneContainer);
|
||||
imageLayer.init();
|
||||
|
||||
this.imageLayer = imageLayer;
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
this.imageLayer.clearModels();
|
||||
this.imageLayer.destroy();
|
||||
}
|
||||
|
||||
public getTileURL(urlParams: IUrlParams, path: string) {
|
||||
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 path.replace(tileURLRegex, (value, key: any) => {
|
||||
// @ts-ignore
|
||||
return urlParams[key];
|
||||
});
|
||||
}
|
||||
|
||||
// Get tile bounds in WGS84 coordinates
|
||||
public tileLnglatBounds(tile: number[]) {
|
||||
const e = this.tile2lng(tile[0] + 1, tile[2]);
|
||||
const w = this.tile2lng(tile[0], tile[2]);
|
||||
const s = this.tile2lat(tile[1] + 1, tile[2]);
|
||||
const n = this.tile2lat(tile[1], tile[2]);
|
||||
return toLngLatBounds([w, n], [e, s]);
|
||||
}
|
||||
|
||||
public tile2lng(x: number, z: number) {
|
||||
return (x / Math.pow(2, z)) * 360 - 180;
|
||||
}
|
||||
|
||||
public tile2lat(y: number, z: number) {
|
||||
const n = Math.PI - (2 * Math.PI * y) / Math.pow(2, z);
|
||||
return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
import { Bounds, GeoCoordinates, Point, toLngLat } from '@antv/geo-coord';
|
||||
import {
|
||||
createLayerContainer,
|
||||
ILayer,
|
||||
ILayerService,
|
||||
ILngLat,
|
||||
} from '@antv/l7-core';
|
||||
import { Container } from 'inversify';
|
||||
|
||||
import ImageTile from './ImageTile';
|
||||
import TileCache from './tileCache';
|
||||
|
||||
// Tip: 瓦片地图的存储上限
|
||||
const CacheLimit = 30;
|
||||
|
||||
export default class Tile {
|
||||
public tileList: any = {};
|
||||
public tileCache: any;
|
||||
|
||||
public updateTileList: any[];
|
||||
public tileZoom: number;
|
||||
public noPruneRange: any;
|
||||
public url: string;
|
||||
public resolution: number;
|
||||
public maxSourceZoom: number;
|
||||
public crstype: string;
|
||||
public currentCrs: any;
|
||||
|
||||
public layerService: ILayerService;
|
||||
public layer: ILayer;
|
||||
constructor(props: any) {
|
||||
this.layerService = props.layerService;
|
||||
this.layer = props.layer;
|
||||
this.url = props.url;
|
||||
this.resolution = props.resolution === 'low' ? -1 : 0;
|
||||
this.maxSourceZoom = props.maxSourceZoom;
|
||||
this.crstype = props.crstype;
|
||||
|
||||
this.currentCrs = new GeoCoordinates.default({
|
||||
start: { x: 0, y: 0 },
|
||||
end: { x: 0, y: 0 },
|
||||
projection: this.crstype,
|
||||
}).crs as any;
|
||||
|
||||
this.destroyTile = this.destroyTile.bind(this);
|
||||
this.tileCache = new TileCache(CacheLimit, this.destroyTile);
|
||||
|
||||
this.updateTileList = [];
|
||||
|
||||
this.removeTiles = this.removeTiles.bind(this);
|
||||
}
|
||||
|
||||
public calCurrentTiles(oprions: any) {
|
||||
const {
|
||||
NE,
|
||||
SW,
|
||||
tileCenter,
|
||||
currentZoom,
|
||||
minSourceZoom,
|
||||
minZoom,
|
||||
maxZoom,
|
||||
} = oprions;
|
||||
// TODO: 当前瓦片的层级要比地图底图的层级低
|
||||
if (currentZoom >= this.maxSourceZoom) {
|
||||
return;
|
||||
}
|
||||
const zoom = Math.floor(currentZoom) + this.resolution;
|
||||
|
||||
this.tileZoom = zoom > this.maxSourceZoom ? this.maxSourceZoom : zoom;
|
||||
|
||||
if (
|
||||
currentZoom < minZoom ||
|
||||
currentZoom >= maxZoom ||
|
||||
currentZoom < minSourceZoom
|
||||
) {
|
||||
this.removeTiles();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateTileList = [];
|
||||
|
||||
// 计算瓦片中心
|
||||
const centerPoint = this.currentCrs.lngLatToPoint(
|
||||
toLngLat(tileCenter.lng, tileCenter.lat),
|
||||
this.tileZoom,
|
||||
);
|
||||
const centerXY = centerPoint.divideBy(256).floor();
|
||||
|
||||
const pixelBounds = this.getPixelBounds(
|
||||
NE,
|
||||
SW,
|
||||
tileCenter,
|
||||
this.tileZoom,
|
||||
this.currentCrs,
|
||||
); // 计算像素范围
|
||||
const tileRange = this.pxBoundsToTileRange(pixelBounds); // 计算瓦片范围
|
||||
|
||||
const margin = 4;
|
||||
|
||||
this.noPruneRange = new Bounds(
|
||||
tileRange.getBottomLeft().subtract([margin, -margin]),
|
||||
tileRange.getTopRight().add([margin, -margin]),
|
||||
);
|
||||
|
||||
// T: isFinite(n: number) 用于检测 n 是否无穷大
|
||||
if (
|
||||
!(
|
||||
isFinite(tileRange.min.x) &&
|
||||
isFinite(tileRange.min.y) &&
|
||||
isFinite(tileRange.max.x) &&
|
||||
isFinite(tileRange.max.y)
|
||||
)
|
||||
) {
|
||||
throw new Error('Attempted to load an infinite number of tiles');
|
||||
}
|
||||
|
||||
// 根据视野判断新增的瓦片索引
|
||||
for (let j = tileRange.min.y; j <= tileRange.max.y; j++) {
|
||||
for (let i = tileRange.min.x; i <= tileRange.max.x; i++) {
|
||||
const coords = [i, j, this.tileZoom];
|
||||
const tile = this.tileList[coords.join('_')];
|
||||
if (tile) {
|
||||
tile.current = true;
|
||||
} else {
|
||||
this.tileList[coords.join('_')] = {
|
||||
current: true,
|
||||
coords,
|
||||
};
|
||||
this.updateTileList.push(coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 瓦片列表排序
|
||||
this.updateTileList.sort((a: any, b: any) => {
|
||||
const tile1 = a;
|
||||
const tile2 = b;
|
||||
const d1 =
|
||||
Math.pow(tile1[0] * 1 - centerXY.x, 2) +
|
||||
Math.pow(tile1[1] * 1 - centerXY.y, 2);
|
||||
const d2 =
|
||||
Math.pow(tile2[0] * 1 - centerXY.x, 2) +
|
||||
Math.pow(tile2[1] * 1 - centerXY.y, 2);
|
||||
return d1 - d2;
|
||||
});
|
||||
|
||||
this.pruneTiles();
|
||||
this.updateTileList.forEach((coords: any) => {
|
||||
const key = coords.join('_');
|
||||
if (this.tileList[key].current) {
|
||||
this.requestTile(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public pxBoundsToTileRange(pixelBounds: any) {
|
||||
return new Bounds(
|
||||
pixelBounds.min.divideBy(256).floor(),
|
||||
pixelBounds.max
|
||||
.divideBy(256)
|
||||
.ceil()
|
||||
.subtract([1, 1]),
|
||||
);
|
||||
}
|
||||
|
||||
public getPixelBounds(
|
||||
NE: ILngLat,
|
||||
SW: ILngLat,
|
||||
tileCenter: ILngLat,
|
||||
tileZoom: number,
|
||||
crs: any,
|
||||
) {
|
||||
const zoom = tileZoom;
|
||||
const NEPoint = crs.lngLatToPoint(toLngLat(NE.lng, NE.lat), zoom);
|
||||
const SWPoint = crs.lngLatToPoint(toLngLat(SW.lng, SW.lat), zoom);
|
||||
const centerPoint = crs.lngLatToPoint(
|
||||
toLngLat(tileCenter.lng, tileCenter.lat),
|
||||
zoom,
|
||||
);
|
||||
const topHeight = centerPoint.y - NEPoint.y;
|
||||
const bottomHeight = SWPoint.y - centerPoint.y;
|
||||
// 跨日界线的情况
|
||||
let leftWidth;
|
||||
let rightWidth;
|
||||
if (tileCenter.lng - NE.lng > 0 || tileCenter.lng - SW.lng < 0) {
|
||||
const width =
|
||||
((Math.pow(2, zoom) * 256) / 360) * (180 - NE.lng) +
|
||||
((Math.pow(2, zoom) * 256) / 360) * (SW.lng + 180);
|
||||
if (tileCenter.lng - NE.lng > 0) {
|
||||
// 日界线在右侧
|
||||
leftWidth =
|
||||
((Math.pow(2, zoom) * 256) / 360) * (tileCenter.lng - NE.lng);
|
||||
rightWidth = width - leftWidth;
|
||||
} else {
|
||||
rightWidth =
|
||||
((Math.pow(2, zoom) * 256) / 360) * (SW.lng - tileCenter.lng);
|
||||
leftWidth = width - rightWidth;
|
||||
}
|
||||
} else {
|
||||
// 不跨日界线
|
||||
leftWidth = ((Math.pow(2, zoom) * 256) / 360) * (tileCenter.lng - SW.lng);
|
||||
rightWidth =
|
||||
((Math.pow(2, zoom) * 256) / 360) * (NE.lng - tileCenter.lng);
|
||||
}
|
||||
const pixelBounds = new Bounds(
|
||||
centerPoint.subtract(leftWidth, topHeight),
|
||||
centerPoint.add(rightWidth, bottomHeight),
|
||||
);
|
||||
return pixelBounds;
|
||||
}
|
||||
|
||||
public pruneTiles() {
|
||||
Object.keys(this.tileList).map((key) => {
|
||||
const c = this.tileList[key].coords;
|
||||
// 如果不是同一个缩放层级,则将瓦片设为不显示
|
||||
if (
|
||||
c[2] !== this.tileZoom ||
|
||||
!this.noPruneRange.contains(new Point(c[0], c[1]))
|
||||
) {
|
||||
this.tileList[key].current = false;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(this.tileList).map((key) => {
|
||||
const tile = this.tileList[key];
|
||||
tile.retain = tile.current;
|
||||
});
|
||||
|
||||
Object.keys(this.tileList).map((key) => {
|
||||
const tile = this.tileList[key];
|
||||
if (tile.current && !tile.active) {
|
||||
const [x, y, z] = key.split('_').map((v) => Number(v));
|
||||
|
||||
if (!this.retainParent(x, y, z, z - 5)) {
|
||||
this.retainChildren(x, y, z, z + 2);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.removeOutTiles();
|
||||
}
|
||||
|
||||
public requestTile(key: string) {
|
||||
const t = this.tileList[key];
|
||||
if (!t) {
|
||||
return;
|
||||
}
|
||||
let tile = this.tileCache.getTile(key);
|
||||
if (!tile) {
|
||||
const container = createLayerContainer(
|
||||
this.layer.sceneContainer as Container,
|
||||
);
|
||||
tile = new ImageTile(
|
||||
key,
|
||||
this.url,
|
||||
container,
|
||||
this.layer.sceneContainer as Container,
|
||||
);
|
||||
tile.name = key;
|
||||
|
||||
t.current = true;
|
||||
t.retain = true;
|
||||
t.active = true;
|
||||
|
||||
// 往 imageTileLayer 中添加子图层
|
||||
this.layer.layerChildren.push(tile.imageLayer);
|
||||
|
||||
this.tileCache.setTile(tile, key);
|
||||
|
||||
this.pruneTiles();
|
||||
this.layerService.renderLayers();
|
||||
} else {
|
||||
// Tip: show 方法就是将相应的瓦片图片添加到渲染队列
|
||||
tile.imageLayer.show();
|
||||
t.current = true;
|
||||
t.retain = true;
|
||||
t.active = true;
|
||||
|
||||
this.pruneTiles();
|
||||
}
|
||||
}
|
||||
|
||||
public retainParent(x: number, y: number, z: number, minZoom: number): any {
|
||||
const x2 = Math.floor(x / 2);
|
||||
const y2 = Math.floor(y / 2);
|
||||
const z2 = z - 1;
|
||||
const tile = this.tileList[[x2, y2, z2].join('_')];
|
||||
if (tile && tile.active) {
|
||||
tile.retain = true;
|
||||
return true;
|
||||
} else if (tile && tile.loaded) {
|
||||
tile.retain = true;
|
||||
}
|
||||
if (z2 > minZoom) {
|
||||
return this.retainParent(x2, y2, z2, minZoom);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public retainChildren(x: number, y: number, z: number, maxZoom: number) {
|
||||
for (let i = 2 * x; i < 2 * x + 2; i++) {
|
||||
for (let j = 2 * y; j < 2 * y + 2; j++) {
|
||||
const key = [i, j, z + 1].join('_');
|
||||
const tile = this.tileList[key];
|
||||
if (tile && tile.active) {
|
||||
tile.retain = true;
|
||||
continue;
|
||||
} else if (tile && tile.loaded) {
|
||||
tile.retain = true;
|
||||
}
|
||||
|
||||
if (z + 1 < maxZoom) {
|
||||
this.retainChildren(i, j, z + 1, maxZoom);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public destroyTile(tile: any) {
|
||||
const layerIndex = this.layer.layerChildren.indexOf(tile.imageLayer);
|
||||
if (layerIndex > -1) {
|
||||
this.layer.layerChildren.splice(layerIndex, 1);
|
||||
}
|
||||
|
||||
tile.imageLayer.emit('remove', null);
|
||||
tile.imageLayer.destroy();
|
||||
this.layerService.renderLayers();
|
||||
|
||||
// 清除 tileCache 中的存储 相当于 tileCache.setTile(tile, null)
|
||||
tile = null;
|
||||
}
|
||||
|
||||
public removeOutTiles() {
|
||||
for (const key in this.tileList) {
|
||||
if (!this.tileList[key].retain) {
|
||||
// Tip: 不需要显示的瓦片对象
|
||||
const tile = this.tileCache.getTile(key);
|
||||
// Tip: 若是网格对象存在
|
||||
if (tile) {
|
||||
// Tip: hide 方法就是将相应的瓦片图片从渲染队列中剔除
|
||||
tile.imageLayer.hide();
|
||||
}
|
||||
delete this.tileList[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public removeTiles() {
|
||||
this.layer.layerChildren.forEach((layer: any) => {
|
||||
layer.emit('remove', null);
|
||||
layer.destroy();
|
||||
});
|
||||
|
||||
this.layer.layerChildren = [];
|
||||
this.layerService.renderLayers();
|
||||
this.tileList = {};
|
||||
this.tileCache.destory();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* LRU Cache class with limit
|
||||
*
|
||||
* Update order for each get/set operation
|
||||
* Delete oldest when reach given limit
|
||||
*/
|
||||
|
||||
export default class LRUCache {
|
||||
public limit: number;
|
||||
public order: any[];
|
||||
public cache: any;
|
||||
public destroy: any;
|
||||
constructor(limit = 50, destroy = () => '') {
|
||||
this.limit = limit;
|
||||
this.destroy = destroy;
|
||||
this.order = [];
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.order.forEach((key: any) => {
|
||||
this.delete(key);
|
||||
});
|
||||
this.cache = {};
|
||||
// access/update order, first item is oldest, last item is newest
|
||||
this.order = [];
|
||||
}
|
||||
|
||||
public get(key: string) {
|
||||
const value = this.cache[key];
|
||||
if (value) {
|
||||
// update order
|
||||
this.deleteOrder(key);
|
||||
this.appendOrder(key);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public set(key: string, value: any) {
|
||||
if (!this.cache[key]) {
|
||||
// if reach limit, delete the oldest
|
||||
if (Object.keys(this.cache).length === this.limit) {
|
||||
this.delete(this.order[0]);
|
||||
}
|
||||
|
||||
this.cache[key] = value;
|
||||
this.appendOrder(key);
|
||||
} else {
|
||||
// if found in cache, delete the old one, insert new one to the first of list
|
||||
this.delete(key);
|
||||
|
||||
this.cache[key] = value;
|
||||
this.appendOrder(key);
|
||||
}
|
||||
}
|
||||
|
||||
public delete(key: string) {
|
||||
const value = this.cache[key];
|
||||
if (value) {
|
||||
this.deleteCache(key);
|
||||
this.deleteOrder(key);
|
||||
this.destroy(value, key);
|
||||
}
|
||||
}
|
||||
|
||||
public deleteCache(key: string) {
|
||||
delete this.cache[key];
|
||||
}
|
||||
|
||||
public deleteOrder(key: string) {
|
||||
const index = this.order.findIndex((o) => o === key);
|
||||
if (index >= 0) {
|
||||
this.order.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public appendOrder(key: string) {
|
||||
this.order.push(key);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import LRUCache from './lruCache';
|
||||
export default class TileCache {
|
||||
public cache: any;
|
||||
constructor(limit = 50, tileDestroy: any) {
|
||||
this.cache = new LRUCache(limit, tileDestroy);
|
||||
}
|
||||
|
||||
public getTile(key: string) {
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
public setTile(tile: any, key: string) {
|
||||
this.cache.set(key, tile);
|
||||
}
|
||||
public destory() {
|
||||
this.cache.clear();
|
||||
}
|
||||
public setNeedUpdate() {
|
||||
this.cache.order.forEach((key: string) => {
|
||||
const tile = this.cache.get(key);
|
||||
tile.needUpdate = true;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import BaseLayer from './core/BaseLayer';
|
|||
import './glsl.d';
|
||||
import HeatmapLayer from './heatmap';
|
||||
import ImageLayer from './image';
|
||||
import ImageTileLayer from './imagetile';
|
||||
import LineLayer from './line/index';
|
||||
import PointLayer from './point';
|
||||
import PolygonLayer from './polygon';
|
||||
|
@ -25,6 +26,7 @@ import RegisterStyleAttributePlugin from './plugins/RegisterStyleAttributePlugin
|
|||
import ShaderUniformPlugin from './plugins/ShaderUniformPlugin';
|
||||
import UpdateModelPlugin from './plugins/UpdateModelPlugin';
|
||||
import UpdateStyleAttributePlugin from './plugins/UpdateStyleAttributePlugin';
|
||||
|
||||
/**
|
||||
* 校验传入参数配置项的正确性
|
||||
* @see /dev-docs/ConfigSchemaValidation.md
|
||||
|
@ -137,6 +139,7 @@ export {
|
|||
LineLayer,
|
||||
CityBuildingLayer,
|
||||
ImageLayer,
|
||||
ImageTileLayer,
|
||||
RasterLayer,
|
||||
HeatmapLayer,
|
||||
EarthLayer,
|
||||
|
|
|
@ -459,6 +459,8 @@ export default class AMapService
|
|||
position,
|
||||
} = e.camera;
|
||||
const { lng, lat } = this.getCenter();
|
||||
// Tip: 触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
if (this.cameraChangedCallback) {
|
||||
// resync viewport
|
||||
// console.log('cameraHeight', height)
|
||||
|
|
|
@ -558,6 +558,8 @@ export default class AMapService
|
|||
// left, right, bottom, top
|
||||
// @ts-ignore
|
||||
} = this.map.customCoords?.getCameraParams();
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
// // @ts-ignore
|
||||
// console.log('this.map.customCoords.getCameraParams()', this.map.customCoords.getCameraParams())
|
||||
// const { left, right, bottom, top, near, far, position } = this.map.customCoords.getCameraParams();
|
||||
|
@ -620,6 +622,9 @@ export default class AMapService
|
|||
// left, right, bottom, top
|
||||
// @ts-ignore
|
||||
} = this.map.customCoords.getCameraParams();
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
|
||||
const { zoom } = e;
|
||||
// @ts-ignore
|
||||
const center = this.map.customCoords.getCenter() as [number, number];
|
||||
|
|
|
@ -337,6 +337,8 @@ export default class L7EarthService implements IEarthService<Map> {
|
|||
}
|
||||
|
||||
private handleCameraChanged = (e: any) => {
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
const DELAY_TIME = 2000;
|
||||
this.handleCameraChanging = true;
|
||||
if (this.handleCameraTimer) {
|
||||
|
|
|
@ -320,7 +320,8 @@ export default class L7MapService implements IMapService<Map> {
|
|||
private handleCameraChanged = () => {
|
||||
const { lat, lng } = this.map.getCenter();
|
||||
const { offsetCoordinate = true } = this.config;
|
||||
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
// resync
|
||||
this.viewport.syncWithMapCamera({
|
||||
bearing: this.map.getBearing(),
|
||||
|
|
|
@ -403,7 +403,8 @@ export default class MapboxService
|
|||
private handleCameraChanged = () => {
|
||||
// @see https://github.com/mapbox/mapbox-gl-js/issues/2572
|
||||
const { lat, lng } = this.map.getCenter().wrap();
|
||||
|
||||
// Tip: 统一触发地图变化事件
|
||||
this.emit('mapchange');
|
||||
// resync
|
||||
this.viewport.syncWithMapCamera({
|
||||
bearing: this.map.getBearing(),
|
||||
|
|
|
@ -162,7 +162,7 @@ class Scene
|
|||
// 为当前图层创建一个容器
|
||||
// TODO: 初始化的时候设置 容器
|
||||
const layerContainer = createLayerContainer(this.container);
|
||||
layer.setContainer(layerContainer);
|
||||
layer.setContainer(layerContainer, this.container);
|
||||
this.sceneService.addLayer(layer);
|
||||
}
|
||||
|
||||
|
@ -178,8 +178,8 @@ class Scene
|
|||
return this.layerService.getLayerByName(name);
|
||||
}
|
||||
|
||||
public removeLayer(layer: ILayer): void {
|
||||
this.layerService.remove(layer);
|
||||
public removeLayer(layer: ILayer, parentLayer?: ILayer): void {
|
||||
this.layerService.remove(layer, parentLayer);
|
||||
}
|
||||
|
||||
public removeAllLayer(): void {
|
||||
|
|
|
@ -207,6 +207,14 @@ export default class Source extends EventEmitter implements ISource {
|
|||
private excuteParser(): void {
|
||||
const parser = this.parser;
|
||||
const type: string = parser.type || 'geojson';
|
||||
// TODO: 图片瓦片地图组件只需要使用 url 参数
|
||||
if (type === 'imagetile') {
|
||||
this.data = {
|
||||
tileurl: this.originData,
|
||||
dataArray: [],
|
||||
};
|
||||
return;
|
||||
}
|
||||
const sourceParser = getParser(type);
|
||||
this.data = sourceParser(this.originData, parser);
|
||||
// 计算范围
|
||||
|
|
|
@ -137,7 +137,6 @@ export default class Aspace extends React.Component {
|
|||
let r = heightData[i * 4];
|
||||
let g = heightData[i * 4 + 1];
|
||||
let b = heightData[i * 4 + 2];
|
||||
|
||||
let h =
|
||||
-10000.0 +
|
||||
(r * 255.0 * 256.0 * 256.0 + g * 255.0 * 256.0 + b * 255.0) *
|
||||
|
@ -168,7 +167,6 @@ export default class Aspace extends React.Component {
|
|||
const antModel = gltf.scene;
|
||||
layer.adjustMeshToMap(antModel);
|
||||
layer.setMeshScale(antModel, 20, 20, 20);
|
||||
|
||||
layer.setObjectLngLat(
|
||||
antModel,
|
||||
[center.lng - 0.002, center.lat],
|
||||
|
@ -178,13 +176,9 @@ export default class Aspace extends React.Component {
|
|||
const animations = gltf.animations;
|
||||
if (animations && animations.length) {
|
||||
const mixer = new THREE.AnimationMixer(antModel);
|
||||
|
||||
const animation = animations[1];
|
||||
|
||||
const action = mixer.clipAction(animation);
|
||||
|
||||
action.play();
|
||||
|
||||
layer.addAnimateMixer(mixer);
|
||||
}
|
||||
antModel.rotation.y = Math.PI;
|
||||
|
@ -358,8 +352,7 @@ export default class Aspace extends React.Component {
|
|||
currentView.pitch = scene.getPitch();
|
||||
currentView.zoom = scene.getZoom();
|
||||
});
|
||||
// @ts-ignore
|
||||
scene?.map?.on('camerachange', (e: any) => {
|
||||
scene.getMapService().on('mapchange', (e: any) => {
|
||||
// @ts-ignore
|
||||
currentCamera = threeJSLayer.threeRenderService.getRenderCamera();
|
||||
currentView.pitch = scene.getPitch();
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
// @ts-ignore
|
||||
import { ImageTileLayer, Scene, PointLayer } from '@antv/l7';
|
||||
import { GaodeMap, GaodeMapV2, Map, Mapbox } from '@antv/l7-maps';
|
||||
import * as React from 'react';
|
||||
|
||||
export default class Amap2demo_imageTileLayer extends React.Component {
|
||||
// @ts-ignore
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new Map({
|
||||
center: [121.268, 30.3628],
|
||||
pitch: 0,
|
||||
style: 'normal',
|
||||
zoom: 10,
|
||||
viewMode: '3D',
|
||||
}),
|
||||
});
|
||||
this.scene = scene;
|
||||
|
||||
let originData = [
|
||||
{
|
||||
lng: 121.107846,
|
||||
lat: 30.267069,
|
||||
},
|
||||
{
|
||||
lng: 121.107,
|
||||
lat: 30.267069,
|
||||
},
|
||||
{
|
||||
lng: 121.107846,
|
||||
lat: 30.26718,
|
||||
},
|
||||
];
|
||||
|
||||
scene.on('loaded', () => {
|
||||
const layer = new ImageTileLayer({});
|
||||
layer
|
||||
.source(
|
||||
'http://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
|
||||
{
|
||||
parser: {
|
||||
type: 'imagetile',
|
||||
},
|
||||
},
|
||||
)
|
||||
.style({
|
||||
resolution: 'low', // low height
|
||||
// resolution: 'height'
|
||||
maxSourceZoom: 17,
|
||||
});
|
||||
|
||||
let pointlayer = new PointLayer()
|
||||
.source(originData, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'lng',
|
||||
y: 'lat',
|
||||
},
|
||||
})
|
||||
.shape('circle')
|
||||
.color('rgba(255, 0, 0, 1.0)')
|
||||
.size(10)
|
||||
.active(true);
|
||||
|
||||
scene.addLayer(pointlayer);
|
||||
scene.addLayer(layer);
|
||||
});
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ import Amap2demo_heatmap_hexagon_world from './components/amap2demo_heatmap_hexa
|
|||
import Amap2demo_heatmap_grid from "./components/amap2demo_heatmap_grid"
|
||||
|
||||
import Amap2demo_imageLayer from "./components/amap2demo_imagelayer"
|
||||
import Amap2demo_imageTileLayer from "./components/amap2demo_imageTileLayer"
|
||||
|
||||
import Amap2demo_rasterLayer from "./components/amap2demo_rasterlayer"
|
||||
|
||||
|
@ -99,7 +100,10 @@ storiesOf('地图方法', module)
|
|||
.add('高德地图2.0 heatmap3D/hexagon', () => <Amap2demo_heatmap_hexagon />)
|
||||
.add('高德地图2.0 heatmap/hexagon/world', () => <Amap2demo_heatmap_hexagon_world />)
|
||||
.add('高德地图2.0 heatmap3D/grid', () => <Amap2demo_heatmap_grid />)
|
||||
|
||||
.add('高德地图2.0 imageLayer', () => <Amap2demo_imageLayer />)
|
||||
.add('高德地图2.0 imageTileLayer', () => <Amap2demo_imageTileLayer />)
|
||||
|
||||
.add('高德地图2.0 rasterLayer', () => <Amap2demo_rasterLayer />)
|
||||
.add('高德地图2.0 citybuildLayer', () => <Amap2demo_citybuilding />)
|
||||
|
||||
|
|
Loading…
Reference in New Issue