From 0bcc822cdd5e7bace35ea60b427e54e4da73d2b8 Mon Sep 17 00:00:00 2001 From: YiQianYao <42212176+2912401452@users.noreply.github.com> Date: Mon, 27 Sep 2021 15:52:20 +0800 Subject: [PATCH] Shihui dev (#784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add getModelMatrix into viewport * feat: 新增地球模式 (初步构建) * feat: 完善地球交互 * style: lint style * feat: 调整地球图层缩放的方向 * style: lint style * feat: 增加地球模式的 pointLayer/fill 图层 * style: lint style * feat: 增加地球、太阳的简单运动系统,优化部分代码结构 --- package.json | 5 +- .../core/src/services/camera/CameraService.ts | 4 + .../src/services/camera/ICameraService.ts | 1 + .../core/src/services/layer/ILayerService.ts | 19 + packages/core/src/services/map/IMapService.ts | 10 + packages/layers/src/core/BaseLayer.ts | 8 + packages/layers/src/core/triangulation.ts | 173 +++++++- packages/layers/src/earth/index.ts | 33 ++ packages/layers/src/earth/models/base.ts | 179 ++++++++ .../layers/src/earth/shaders/base_frag.glsl | 13 + .../layers/src/earth/shaders/base_vert.glsl | 52 +++ packages/layers/src/index.ts | 3 + .../layers/src/plugins/ShaderUniformPlugin.ts | 3 +- packages/layers/src/point/models/fill.ts | 73 +++- .../layers/src/point/shaders/fill_vert.glsl | 14 +- packages/map/src/earthmap.ts | 375 ++++++++++++++++ packages/map/src/handler/IHandler.ts | 3 +- .../map/src/handler/blockable_map_event.ts | 7 +- packages/map/src/handler/box_zoom.ts | 8 +- packages/map/src/handler/click_zoom.ts | 3 +- .../map/src/handler/events/map_mouse_event.ts | 5 +- .../map/src/handler/events/map_touch_event.ts | 5 +- .../map/src/handler/events/map_wheel_event.ts | 7 +- packages/map/src/handler/handler_inertia.ts | 5 +- packages/map/src/handler/handler_manager.ts | 5 +- packages/map/src/handler/keyboard.ts | 3 +- packages/map/src/handler/map_event.ts | 6 +- packages/map/src/handler/scroll_zoom.ts | 8 +- packages/map/src/handler/tap/tap_zoom.ts | 5 +- packages/map/src/hash.ts | 9 +- packages/map/src/index.ts | 1 + packages/maps/src/amap/Viewport.ts | 4 + packages/maps/src/amap2/Viewport.ts | 4 + packages/maps/src/earth/Viewport.ts | 143 ++++++ packages/maps/src/earth/index.ts | 8 + packages/maps/src/earth/map.ts | 413 ++++++++++++++++++ packages/maps/src/earth/theme.ts | 23 + packages/maps/src/index.ts | 3 +- packages/maps/src/map/Viewport.ts | 4 + packages/maps/src/mapbox/Viewport.ts | 4 + packages/maps/src/version.ts | 1 + stories/customMap/components/earth.tsx | 409 +++++++++++++++++ stories/customMap/map.stories.tsx | 5 +- 43 files changed, 2019 insertions(+), 47 deletions(-) create mode 100644 packages/layers/src/earth/index.ts create mode 100644 packages/layers/src/earth/models/base.ts create mode 100644 packages/layers/src/earth/shaders/base_frag.glsl create mode 100644 packages/layers/src/earth/shaders/base_vert.glsl create mode 100644 packages/map/src/earthmap.ts create mode 100644 packages/maps/src/earth/Viewport.ts create mode 100644 packages/maps/src/earth/index.ts create mode 100644 packages/maps/src/earth/map.ts create mode 100644 packages/maps/src/earth/theme.ts create mode 100644 stories/customMap/components/earth.tsx diff --git a/package.json b/package.json index 5ef325b786..50cd30de2b 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@antv/gatsby-theme-antv": "^1.1.1", "@antv/l7-district": "^2.3.9", "@antv/l7-draw": "^2.3.40", - "@antv/l7-react": "^2.3.2", + "@antv/l7-react": "^2.3.3", "@babel/cli": "^7.6.4", "@babel/core": "^7.6.4", "@babel/plugin-proposal-decorators": "^7.6.0", @@ -196,5 +196,6 @@ }, "tnpm": { "mode": "yarn" - } + }, + "dependencies": {} } diff --git a/packages/core/src/services/camera/CameraService.ts b/packages/core/src/services/camera/CameraService.ts index f9d59d7315..fcf2dce2c1 100644 --- a/packages/core/src/services/camera/CameraService.ts +++ b/packages/core/src/services/camera/CameraService.ts @@ -63,6 +63,10 @@ export default class CameraService implements ICameraService { return this.jitteredProjectionMatrix || this.viewport.getProjectionMatrix(); } + public getModelMatrix(): number[] { + return this.viewport.getModelMatrix(); + } + public getViewMatrix(): number[] { return this.viewport.getViewMatrix(); } diff --git a/packages/core/src/services/camera/ICameraService.ts b/packages/core/src/services/camera/ICameraService.ts index e8463acb6d..e7309522cb 100644 --- a/packages/core/src/services/camera/ICameraService.ts +++ b/packages/core/src/services/camera/ICameraService.ts @@ -14,6 +14,7 @@ export const CameraUniform = { export interface IViewport { syncWithMapCamera(mapCamera: Partial): void; getProjectionMatrix(): number[]; + getModelMatrix(): number[]; getViewMatrix(): number[]; getViewMatrixUncentered(): number[]; getViewProjectionMatrixUncentered(): number[]; diff --git a/packages/core/src/services/layer/ILayerService.ts b/packages/core/src/services/layer/ILayerService.ts index fd782bba65..9784312ba1 100644 --- a/packages/core/src/services/layer/ILayerService.ts +++ b/packages/core/src/services/layer/ILayerService.ts @@ -71,6 +71,9 @@ export interface ILayerModel { initModels(): IModel[]; needUpdate(): boolean; clearModels(): void; + + // earth mode + setEarthTime?(time: number): void; } export interface IModelUniform { [key: string]: IUniform; @@ -261,6 +264,13 @@ export interface ILayer { // 增加加载模型的动画混合器 addAnimateMixer?(mixer: AnimationMixer): void; + + /** + * 地球模式相关的方法 + */ + + // 设置当前地球时间 控制太阳角度 + setEarthTime(time: number): void; } /** @@ -336,7 +346,16 @@ export interface ILayerConfig { * 开启光照 */ enableLighting: boolean; + + /** + * 动画参数 + */ animateOption: Partial; + + /** + * 地球模式参数 + */ + globelOtions: any; /** * layer point text 是否是 iconfont 模式 */ diff --git a/packages/core/src/services/map/IMapService.ts b/packages/core/src/services/map/IMapService.ts index f56285d6cc..d44b951785 100644 --- a/packages/core/src/services/map/IMapService.ts +++ b/packages/core/src/services/map/IMapService.ts @@ -94,6 +94,16 @@ export interface IMapService { // lngLatToCoords?(lnglatArray: any): any; getCustomCoordCenter?(): [number, number]; exportMap(type: 'jpg' | 'png'): string; + + // 地球模式下的地图方法/属性 + rotateY?( + option: + | { + force?: boolean; + reg?: number; + } + | undefined, + ): void; } export const MapServiceEvent = ['mapload']; diff --git a/packages/layers/src/core/BaseLayer.ts b/packages/layers/src/core/BaseLayer.ts index 1c0b5eac20..29e467f321 100644 --- a/packages/layers/src/core/BaseLayer.ts +++ b/packages/layers/src/core/BaseLayer.ts @@ -989,6 +989,14 @@ export default class BaseLayer extends EventEmitter } } + /** + * 继承空方法 + * @param time + */ + public setEarthTime(time: number) { + console.warn('empty fn'); + } + protected getConfigSchema() { throw new Error('Method not implemented.'); } diff --git a/packages/layers/src/core/triangulation.ts b/packages/layers/src/core/triangulation.ts index 59082aa963..5b87370196 100644 --- a/packages/layers/src/core/triangulation.ts +++ b/packages/layers/src/core/triangulation.ts @@ -1,7 +1,8 @@ import { IEncodeFeature } from '@antv/l7-core'; import { aProjectFlat, lngLatToMeters } from '@antv/l7-utils'; import earcut from 'earcut'; -import { vec3 } from 'gl-matrix'; +// @ts-ignore +import { mat4, vec3 } from 'gl-matrix'; import ExtrudePolyline from '../utils/extrude_polyline'; import { calculateCentroid } from '../utils/geo'; import extrudePolygon, { @@ -20,6 +21,10 @@ interface IGeometryCache { [key: string]: IExtrudeGeomety; } const GeometryCache: IGeometryCache = {}; + +// 地球网格半径 +const EARTH_RADIUS = 100; +const EARTH_SEGMENTS = 36; /** * 计算2D 填充点图顶点 * @param feature 映射feature @@ -33,6 +38,39 @@ export function PointFillTriangulation(feature: IEncodeFeature) { size: coordinates.length, }; } +/** + * 计算2D 填充点图顶点 (地球模式) + * @param feature 映射feature + */ +export function GlobelPointFillTriangulation(feature: IEncodeFeature) { + const coordinates = calculateCentroid(feature.coordinates); + const xyz = lglt2xyz(coordinates as [number, number]); + return { + vertices: [...xyz, ...xyz, ...xyz, ...xyz], + indices: [0, 1, 2, 2, 3, 0], + size: xyz.length, + }; +} + +function torad(deg: number) { + return (deg / 180) * Math.acos(-1); +} +/** + * 经纬度转xyz + * @param longitude 经度 + * @param latitude 纬度 + * @param radius 半径 + */ +function lglt2xyz(lnglat: [number, number]) { + // TODO: + Math.PI/2 是为了对齐坐标 + const lng = torad(lnglat[0]) + Math.PI / 2; + const lat = torad(lnglat[1]); + + const z = EARTH_RADIUS * Math.cos(lat) * Math.cos(lng); + const x = EARTH_RADIUS * Math.cos(lat) * Math.sin(lng); + const y = EARTH_RADIUS * Math.sin(lat); + return [x, y, z]; +} /** * 计算3D 拉伸点图 @@ -123,7 +161,6 @@ export function polygonTriangulation(feature: IEncodeFeature) { const { coordinates } = feature; const flattengeo = earcut.flatten(coordinates as number[][][]); const { vertices, dimensions, holes } = flattengeo; - return { indices: earcut(vertices, holes, dimensions), vertices, @@ -137,7 +174,6 @@ export function PolygonExtrudeTriangulation(feature: IEncodeFeature) { coordinates, true, ); - return { vertices: positions, // [ x, y, z, uv.x,uv.y ] indices: index, @@ -383,3 +419,134 @@ function addDir(dirX: number, dirY: number) { const y = (dirY + 1) / 2; return [x, y]; } + +/** + * 构建地球三角网格 + * @returns + */ +export function earthTriangulation() { + const mesh = primitiveSphere(EARTH_RADIUS, { segments: EARTH_SEGMENTS }); + const { positionsArr, indicesArr, normalArr } = mesh; + return { + vertices: positionsArr, + indices: indicesArr, + size: 5, + normals: normalArr, + }; +} + +/** + * 构建地球球体网格 + * @param radius + * @param opt + * @returns + */ +function primitiveSphere( + radius: number, + opt: { + segments: number; + }, +) { + const matRotY = mat4.create(); + const matRotZ = mat4.create(); + const up = vec3.fromValues(0, 1, 0); + const tmpVec3 = vec3.fromValues(0, 0, 0); + + opt = opt || {}; + radius = typeof radius !== 'undefined' ? radius : 1; + const segments = typeof opt.segments !== 'undefined' ? opt.segments : 32; + + const totalZRotationSteps = 2 + segments; + const totalYRotationSteps = 2 * totalZRotationSteps; + + const indices = []; + const indicesArr = []; + const positions = []; + const positionsArr = []; + const normals = []; + const normalArr = []; + const uvs = []; + + for ( + let zRotationStep = 0; + zRotationStep <= totalZRotationSteps; + zRotationStep++ + ) { + const normalizedZ = zRotationStep / totalZRotationSteps; + const angleZ = normalizedZ * Math.PI; + + for ( + let yRotationStep = 0; + yRotationStep <= totalYRotationSteps; + yRotationStep++ + ) { + const normalizedY = yRotationStep / totalYRotationSteps; + const angleY = normalizedY * Math.PI * 2; + + mat4.identity(matRotZ); + mat4.rotateZ(matRotZ, matRotZ, -angleZ); + + mat4.identity(matRotY); + mat4.rotateY(matRotY, matRotY, angleY); + + vec3.transformMat4(tmpVec3, up, matRotZ); + vec3.transformMat4(tmpVec3, tmpVec3, matRotY); + + vec3.scale(tmpVec3, tmpVec3, -radius); + + positions.push(tmpVec3.slice()); + positionsArr.push(...tmpVec3.slice()); + + vec3.normalize(tmpVec3, tmpVec3); + normals.push(tmpVec3.slice()); + normalArr.push(...tmpVec3.slice()); + + uvs.push([normalizedY, 1 - normalizedZ]); + + // position 和 uv 一起存储 + positionsArr.push(normalizedY, 1 - normalizedZ); + } + + if (zRotationStep > 0) { + const verticesCount = positions.length; + let firstIndex = verticesCount - 2 * (totalYRotationSteps + 1); + for ( + ; + firstIndex + totalYRotationSteps + 2 < verticesCount; + firstIndex++ + ) { + indices.push([ + firstIndex, + firstIndex + 1, + firstIndex + totalYRotationSteps + 1, + ]); + + indicesArr.push( + firstIndex, + firstIndex + 1, + firstIndex + totalYRotationSteps + 1, + ); + indices.push([ + firstIndex + totalYRotationSteps + 1, + firstIndex + 1, + firstIndex + totalYRotationSteps + 2, + ]); + indicesArr.push( + firstIndex + totalYRotationSteps + 1, + firstIndex + 1, + firstIndex + totalYRotationSteps + 2, + ); + } + } + } + + return { + cells: indices, + positions, + normals, + uvs, + positionsArr, + indicesArr, + normalArr, + }; +} diff --git a/packages/layers/src/earth/index.ts b/packages/layers/src/earth/index.ts new file mode 100644 index 0000000000..c3a3dec52a --- /dev/null +++ b/packages/layers/src/earth/index.ts @@ -0,0 +1,33 @@ +import BaseLayer from '../core/BaseLayer'; +import BaseEarthModel from './models/base'; + +export type EarthType = 'base'; +interface IEarthLayerStyleOptions { + setEarthTime(time: number): void; +} + +const EarthModels: { [key in EarthType]: any } = { + base: BaseEarthModel, +}; + +export default class EarthLayer extends BaseLayer { + public type: string = 'EarthLayer'; + + public buildModels() { + const shape = 'base'; + this.layerModel = new EarthModels[shape](this); + this.models = this.layerModel.initModels(); + } + + /** + * 设置当前地球时间 + * @param time + */ + public setEarthTime(time: number) { + if (this.layerModel && this.layerModel.setEarthTime) { + this.layerModel.setEarthTime(time); + } else { + console.error('请在 scene loaded 之后执行该方法!'); + } + } +} diff --git a/packages/layers/src/earth/models/base.ts b/packages/layers/src/earth/models/base.ts new file mode 100644 index 0000000000..314042aaae --- /dev/null +++ b/packages/layers/src/earth/models/base.ts @@ -0,0 +1,179 @@ +import { + AttributeType, + BlendType, + gl, + IEncodeFeature, + ILayerConfig, + IModel, + IModelUniform, + ITexture2D, +} from '@antv/l7-core'; + +import BaseModel, { styleOffset, styleSingle } from '../../core/BaseModel'; +import { earthTriangulation } from '../../core/triangulation'; + +import baseFrag from '../shaders/base_frag.glsl'; +import baseVert from '../shaders/base_vert.glsl'; + +export default class BaseEarthModel extends BaseModel { + protected texture: ITexture2D; + // T: 当前的地球时间 - 控制太阳的方位 + private earthTime: number = 3.4; + private sunX = 1000; + private sunY = 1000; + private sunZ = 1000; + private sunRadius = Math.sqrt( + this.sunX * this.sunX + this.sunY * this.sunY + this.sunZ * this.sunZ, + ); + + public getUninforms(): IModelUniform { + const { animateOption, globelOtions } = this.layer.getLayerConfig(); + if (animateOption?.enable) { + // @ts-ignore + // T: rotateY 方法只有在地球模式下存在 + this.mapService.rotateY({ + reg: 0.002, + }); + this.earthTime += 0.02; + + this.sunY = 10; + this.sunX = Math.cos(this.earthTime) * (this.sunRadius - this.sunY); + this.sunZ = Math.sin(this.earthTime) * (this.sunRadius - this.sunY); + } + + return { + u_ambientRatio: globelOtions?.ambientRatio || 0.6, // 环境光 + u_diffuseRatio: globelOtions?.diffuseRatio || 0.4, // 漫反射 + u_specularRatio: globelOtions?.specularRatio || 0.1, // 高光反射 + // u_sunLight: [120, 120, 120], + u_sunLight: [this.sunX, this.sunY, this.sunZ], + + u_texture: this.texture, + }; + } + + public setEarthTime(time: number) { + this.earthTime = time; + + this.sunY = 10; + this.sunX = Math.cos(this.earthTime) * (this.sunRadius - this.sunY); + this.sunZ = Math.sin(this.earthTime) * (this.sunRadius - this.sunY); + + this.layerService.renderLayers(); + } + + public initModels(): IModel[] { + const { globelOtions } = this.layer.getLayerConfig(); + if (globelOtions?.earthTime !== undefined) { + this.setEarthTime(globelOtions.earthTime); + } + + const source = this.layer.getSource(); + const { createTexture2D } = this.rendererService; + this.texture = createTexture2D({ + height: 0, + width: 0, + }); + source.data.images.then((imageData: HTMLImageElement[]) => { + this.texture = createTexture2D({ + data: imageData[0], + width: imageData[0].width, + height: imageData[0].height, + }); + this.layerService.renderLayers(); + }); + + return this.buildModels(); + } + + public clearModels() { + return ''; + } + + public buildModels(): IModel[] { + return [ + this.layer.buildLayerModel({ + moduleName: 'baseEarth', + vertexShader: baseVert, + fragmentShader: baseFrag, + triangulation: earthTriangulation, + depth: { enable: true }, + blend: this.getBlend(), + }), + ]; + } + + protected registerBuiltinAttributes() { + // point layer size; + this.styleAttributeService.registerStyleAttribute({ + name: 'size', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Size', + buffer: { + // give the WebGL driver a hint that this buffer may change + usage: gl.DYNAMIC_DRAW, + data: [], + type: gl.FLOAT, + }, + size: 1, + update: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + attributeIdx: number, + ) => { + const { size = 1 } = feature; + return Array.isArray(size) ? [size[0]] : [size as number]; + }, + }, + }); + + this.styleAttributeService.registerStyleAttribute({ + name: 'normal', + type: AttributeType.Attribute, + descriptor: { + name: 'a_Normal', + buffer: { + // give the WebGL driver a hint that this buffer may change + usage: gl.STATIC_DRAW, + data: [], + type: gl.FLOAT, + }, + size: 3, + update: ( + feature: IEncodeFeature, + featureIdx: number, + vertex: number[], + attributeIdx: number, + normal: number[], + ) => { + return normal; + }, + }, + }); + + 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]]; + }, + }, + }); + } +} diff --git a/packages/layers/src/earth/shaders/base_frag.glsl b/packages/layers/src/earth/shaders/base_frag.glsl new file mode 100644 index 0000000000..5e4a0aa10f --- /dev/null +++ b/packages/layers/src/earth/shaders/base_frag.glsl @@ -0,0 +1,13 @@ + +uniform sampler2D u_texture; + +varying vec2 v_texCoord; +varying float v_lightWeight; + + +void main() { + + vec4 color = texture2D(u_texture,vec2(v_texCoord.x,v_texCoord.y)); + color.xyz = color.xyz * v_lightWeight; + gl_FragColor = color; +} diff --git a/packages/layers/src/earth/shaders/base_vert.glsl b/packages/layers/src/earth/shaders/base_vert.glsl new file mode 100644 index 0000000000..87f6bae3cc --- /dev/null +++ b/packages/layers/src/earth/shaders/base_vert.glsl @@ -0,0 +1,52 @@ +// attribute vec4 a_Color; +attribute vec3 a_Position; +attribute vec3 a_Normal; +attribute vec2 a_Uv; +varying vec2 v_texCoord; + +// attribute vec2 a_Extrude; +// attribute float a_Size; +// attribute float a_Shape; + +uniform vec3 u_CameraPosition; +uniform mat4 u_ViewProjectionMatrix; +uniform mat4 u_ModelMatrix; +uniform float u_ambientRatio : 0.5; +uniform float u_diffuseRatio : 0.3; +uniform float u_specularRatio : 0.2; +uniform vec3 u_sunLight: [1.0, -10.5, 12.0]; + + + +float calc_lighting(vec4 pos) { + + vec3 worldPos = vec3(pos * u_ModelMatrix); + + vec3 worldNormal = a_Normal; + + // cal light weight + vec3 viewDir = normalize(u_CameraPosition - worldPos); + + vec3 lightDir = normalize(u_sunLight); + + vec3 halfDir = normalize(viewDir+lightDir); + // lambert + float lambert = dot(worldNormal, lightDir); + // specular + float specular = pow(max(0.0, dot(worldNormal, halfDir)), 32.0); + //sum to light weight + float lightWeight = u_ambientRatio + u_diffuseRatio * lambert + u_specularRatio * specular; + + return lightWeight; +} + +varying float v_lightWeight; +void main() { + + v_texCoord = a_Uv; + + float lightWeight = calc_lighting(vec4(a_Position, 1.0)); + v_lightWeight = lightWeight; + + gl_Position = u_ViewProjectionMatrix * u_ModelMatrix * vec4(a_Position, 1.0); +} diff --git a/packages/layers/src/index.ts b/packages/layers/src/index.ts index 26855ffd92..cc13eb3431 100644 --- a/packages/layers/src/index.ts +++ b/packages/layers/src/index.ts @@ -9,6 +9,8 @@ import PointLayer from './point'; import PolygonLayer from './polygon'; import RasterLayer from './raster'; +import EarthLayer from './earth'; + // import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin'; import DataMappingPlugin from './plugins/DataMappingPlugin'; import DataSourcePlugin from './plugins/DataSourcePlugin'; @@ -137,4 +139,5 @@ export { ImageLayer, RasterLayer, HeatmapLayer, + EarthLayer, }; diff --git a/packages/layers/src/plugins/ShaderUniformPlugin.ts b/packages/layers/src/plugins/ShaderUniformPlugin.ts index e23511fe4d..72b50f8b78 100644 --- a/packages/layers/src/plugins/ShaderUniformPlugin.ts +++ b/packages/layers/src/plugins/ShaderUniformPlugin.ts @@ -75,7 +75,8 @@ export default class ShaderUniformPlugin implements ILayerPlugin { // 其他参数,例如视口大小、DPR 等 u_ViewportSize: [width, height], u_DevicePixelRatio: window.devicePixelRatio, - u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + // u_ModelMatrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], + u_ModelMatrix: this.cameraService.getModelMatrix(), u_PickingBuffer: layer.getLayerConfig().pickingBuffer || 0, }); }); diff --git a/packages/layers/src/point/models/fill.ts b/packages/layers/src/point/models/fill.ts index eac7f3c838..ec1e7fc632 100644 --- a/packages/layers/src/point/models/fill.ts +++ b/packages/layers/src/point/models/fill.ts @@ -15,17 +15,24 @@ import BaseModel, { styleOffset, styleSingle, } from '../../core/BaseModel'; -import { PointFillTriangulation } from '../../core/triangulation'; +import { + GlobelPointFillTriangulation, + PointFillTriangulation, +} from '../../core/triangulation'; import pointFillFrag from '../shaders/fill_frag.glsl'; import pointFillVert from '../shaders/fill_vert.glsl'; import { isNumber, isString } from 'lodash'; + +import { mat4, vec3 } from 'gl-matrix'; + interface IPointLayerStyleOptions { opacity: styleSingle; strokeWidth: styleSingle; stroke: styleColor; strokeOpacity: styleSingle; offsets: styleOffset; + isGlobel?: boolean; } // 判断当前使用的 style 中的变量属性是否需要进行数据映射 export default class FillModel extends BaseModel { @@ -36,6 +43,8 @@ export default class FillModel extends BaseModel { strokeWidth = 0, stroke = 'rgba(0,0,0,0)', offsets = [0, 0], + // TODO: 判断当前图层是否为地球模式 + isGlobel = false, } = this.layer.getLayerConfig() as IPointLayerStyleOptions; if ( @@ -85,6 +94,7 @@ export default class FillModel extends BaseModel { }); } return { + u_globel: this.mapService.version === 'GLOBEL' ? 1 : 0, u_dataTexture: this.dataTexture, // 数据纹理 - 有数据映射的时候纹理中带数据,若没有任何数据映射时纹理是 [1] u_cellTypeLayout: this.getCellTypeLayout(), @@ -122,13 +132,18 @@ export default class FillModel extends BaseModel { } public buildModels(): IModel[] { + // TODO: 判断当前的点图层的模型是普通地图模式还是地球模式 + const isGlobel = this.mapService.version === 'GLOBEL'; return [ this.layer.buildLayerModel({ moduleName: 'pointfill', vertexShader: pointFillVert, fragmentShader: pointFillFrag, - triangulation: PointFillTriangulation, - depth: { enable: false }, + triangulation: isGlobel + ? GlobelPointFillTriangulation + : PointFillTriangulation, + // depth: { enable: false }, + depth: { enable: isGlobel }, blend: this.getBlend(), }), ]; @@ -142,6 +157,9 @@ export default class FillModel extends BaseModel { return [option.enable ? 0 : 1.0, option.speed || 1, option.rings || 3, 0]; } protected registerBuiltinAttributes() { + // TODO: 判断当前的点图层的模型是普通地图模式还是地球模式 + const isGlobel = this.mapService.version === 'GLOBEL'; + this.styleAttributeService.registerStyleAttribute({ name: 'extrude', type: AttributeType.Attribute, @@ -153,16 +171,57 @@ export default class FillModel extends BaseModel { data: [], type: gl.FLOAT, }, - size: 2, + size: 3, update: ( feature: IEncodeFeature, featureIdx: number, vertex: number[], attributeIdx: number, ) => { - const extrude = [1, 1, -1, 1, -1, -1, 1, -1]; - const extrudeIndex = (attributeIdx % 4) * 2; - return [extrude[extrudeIndex], extrude[extrudeIndex + 1]]; + let extrude; + // 地球模式 + if (isGlobel) { + const [x, y, z] = vertex; + const n1 = vec3.fromValues(0, 0, 1); + const n2 = vec3.fromValues(x, 0, z); + + const xzReg = + x >= 0 ? vec3.angle(n1, n2) : Math.PI * 2 - vec3.angle(n1, n2); + + const yReg = Math.PI * 2 - Math.asin(y / 100); + + const m = mat4.create(); + mat4.rotateY(m, m, xzReg); + mat4.rotateX(m, m, yReg); + + const v1 = vec3.fromValues(1, 1, 0); + vec3.transformMat4(v1, v1, m); + vec3.normalize(v1, v1); + + const v2 = vec3.fromValues(-1, 1, 0); + vec3.transformMat4(v2, v2, m); + vec3.normalize(v2, v2); + + const v3 = vec3.fromValues(-1, -1, 0); + vec3.transformMat4(v3, v3, m); + vec3.normalize(v3, v3); + + const v4 = vec3.fromValues(1, -1, 0); + vec3.transformMat4(v4, v4, m); + vec3.normalize(v4, v4); + + extrude = [...v1, ...v2, ...v3, ...v4]; + } else { + // 平面模式 + extrude = [1, 1, 0, -1, 1, 0, -1, -1, 0, 1, -1, 0]; + } + + const extrudeIndex = (attributeIdx % 4) * 3; + return [ + extrude[extrudeIndex], + extrude[extrudeIndex + 1], + extrude[extrudeIndex + 2], + ]; }, }, }); diff --git a/packages/layers/src/point/shaders/fill_vert.glsl b/packages/layers/src/point/shaders/fill_vert.glsl index c9addf7b89..7cd11daf46 100644 --- a/packages/layers/src/point/shaders/fill_vert.glsl +++ b/packages/layers/src/point/shaders/fill_vert.glsl @@ -1,11 +1,12 @@ attribute vec4 a_Color; attribute vec3 a_Position; -attribute vec2 a_Extrude; +attribute vec3 a_Extrude; attribute float a_Size; attribute float a_Shape; varying mat4 styleMappingMat; // 用于将在顶点着色器中计算好的样式值传递给片元 +uniform float u_globel; uniform mat4 u_ModelMatrix; uniform mat4 u_Mvp; @@ -29,7 +30,7 @@ uniform vec2 u_offsets; void main() { - vec2 extrude = a_Extrude; + vec3 extrude = a_Extrude; float shape_type = a_Shape; float newSize = setPickingSize(a_Size); @@ -123,10 +124,11 @@ void main() { float antialiasblur = 1.0 / u_DevicePixelRatio / (newSize + u_stroke_width); // construct point coords - v_data = vec4(extrude, antialiasblur,shape_type); + // TODP: /abs(extrude.x) 是为了兼容地球模式 + v_data = vec4(extrude.x/abs(extrude.x), extrude.y/abs(extrude.y), antialiasblur,shape_type); // vec2 offset = project_pixel(extrude * (newSize + u_stroke_width) + u_offsets); - vec2 offset = project_pixel(extrude * (newSize + u_stroke_width) + textrueOffsets); + vec2 offset = project_pixel(extrude.xy * (newSize + u_stroke_width) + textrueOffsets); vec4 project_pos = project_position(vec4(a_Position.xy, 0.0, 1.0)); // gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, project_pixel(setPickingOrder(0.0)), 1.0)); @@ -136,6 +138,10 @@ void main() { gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, project_pixel(setPickingOrder(0.0)), 1.0)); } + if(u_globel > 0.0) { + gl_Position = u_ViewProjectionMatrix * vec4(a_Position + extrude * newSize * 0.1, 1.0); + } + // gl_Position = project_common_position_to_clipspace(vec4(project_pos.xy + offset, 0.0, 1.0)); setPickingColor(a_PickingColor); diff --git a/packages/map/src/earthmap.ts b/packages/map/src/earthmap.ts new file mode 100644 index 0000000000..59bc1e9e0b --- /dev/null +++ b/packages/map/src/earthmap.ts @@ -0,0 +1,375 @@ +import { DOM } from '@antv/l7-utils'; +import { merge } from 'lodash'; +import Camera from './camera'; +import './css/l7.css'; +import LngLat, { LngLatLike } from './geo/lng_lat'; +import LngLatBounds, { LngLatBoundsLike } from './geo/lng_lat_bounds'; +// @ts-ignore +import Point, { PointLike } from './geo/point'; +import BoxZoomHandler from './handler/box_zoom'; +import HandlerManager from './handler/handler_manager'; +import KeyboardHandler from './handler/keyboard'; + +import ScrollZoomHandler from './handler/scroll_zoom'; +import DoubleClickZoomHandler from './handler/shim/dblclick_zoom'; +import DragPanHandler from './handler/shim/drag_pan'; +import DragRotateHandler from './handler/shim/drag_rotate'; +import TouchZoomRotateHandler from './handler/shim/touch_zoom_rotate'; +import { TouchPitchHandler } from './handler/touch'; +import Hash from './hash'; +import { IMapOptions } from './interface'; +import { renderframe } from './util'; +import { PerformanceUtils } from './utils/performance'; +import TaskQueue, { TaskID } from './utils/task_queue'; +type CallBack = (_: number) => void; +const defaultMinZoom = -2; +const defaultMaxZoom = 22; + +// the default values, but also the valid range +const defaultMinPitch = 0; +const defaultMaxPitch = 60; + +const DefaultOptions: IMapOptions = { + hash: false, + zoom: -1, + center: [112, 32], + pitch: 0, + bearing: 0, + interactive: true, + minZoom: defaultMinZoom, + maxZoom: defaultMaxZoom, + minPitch: defaultMinPitch, + maxPitch: defaultMaxPitch, + scrollZoom: true, + boxZoom: true, + dragRotate: true, + dragPan: true, + keyboard: true, + doubleClickZoom: true, + touchZoomRotate: true, + touchPitch: true, + bearingSnap: 7, + clickTolerance: 3, + pitchWithRotate: true, + trackResize: true, + renderWorldCopies: true, +}; +export class EarthMap extends Camera { + public doubleClickZoom: DoubleClickZoomHandler; + public dragRotate: DragRotateHandler; + public dragPan: DragPanHandler; + public touchZoomRotate: TouchZoomRotateHandler; + public scrollZoom: ScrollZoomHandler; + public keyboard: KeyboardHandler; + public touchPitch: TouchPitchHandler; + public boxZoom: BoxZoomHandler; + public handlers: HandlerManager; + + private container: HTMLElement; + private canvas: HTMLCanvasElement; + private canvasContainer: HTMLElement; + private renderTaskQueue: TaskQueue = new TaskQueue(); + private frame: { cancel: () => void } | null; + private trackResize: boolean = true; + private hash: Hash | undefined; + constructor(options: Partial) { + super(merge({}, DefaultOptions, options)); + this.initContainer(); + this.resize(); + this.handlers = new HandlerManager(this, this.options); + + if (typeof window !== 'undefined') { + window.addEventListener('online', this.onWindowOnline, false); + window.addEventListener('resize', this.onWindowResize, false); + window.addEventListener('orientationchange', this.onWindowResize, false); + } + + const hashName = + (typeof options.hash === 'string' && options.hash) || undefined; + if (options.hash) { + this.hash = new Hash(hashName).addTo(this) as Hash; + } + + // don't set position from options if set through hash + if (!this.hash || !this.hash.onHashChange()) { + this.jumpTo({ + center: options.center, + zoom: options.zoom, + bearing: options.bearing, + pitch: options.pitch, + }); + + if (options.bounds) { + this.resize(); + this.fitBounds( + options.bounds, + merge({}, options.fitBoundsOptions, { duration: 0 }), + ); + } + } + } + + public resize(eventData?: any) { + const dimensions = this.containerDimensions(); + const width = dimensions[0]; + const height = dimensions[1]; + + // this.resizeCanvas(width, height); + this.transform.resize(width, height); + const fireMoving = !this.moving; + if (fireMoving) { + this.stop(); + this.emit('movestart', new Event('movestart', eventData)); + this.emit('move', new Event('move', eventData)); + } + + this.emit('resize', new Event('resize', eventData)); + + if (fireMoving) { + this.emit('moveend', new Event('moveend', eventData)); + } + + return this; + } + + public getContainer() { + return this.container; + } + + public getCanvas() { + return this.canvas; + } + + public getCanvasContainer() { + return this.canvasContainer; + } + + public project(lngLat: LngLatLike) { + return this.transform.locationPoint(LngLat.convert(lngLat)); + } + + public unproject(point: PointLike) { + return this.transform.pointLocation(Point.convert(point)); + } + + public getBounds(): LngLatBounds { + return this.transform.getBounds(); + } + + public getMaxBounds(): LngLatBounds | null { + return this.transform.getMaxBounds(); + } + + public setMaxBounds(bounds: LngLatBoundsLike) { + this.transform.setMaxBounds(LngLatBounds.convert(bounds)); + } + + public setStyle(style: any) { + return; + } + public setMinZoom(minZoom?: number) { + minZoom = + minZoom === null || minZoom === undefined ? defaultMinZoom : minZoom; + if (minZoom >= defaultMinZoom && minZoom <= this.transform.maxZoom) { + this.transform.minZoom = minZoom; + if (this.getZoom() < minZoom) { + this.setZoom(minZoom); + } + + return this; + } else { + throw new Error( + `minZoom must be between ${defaultMinZoom} and the current maxZoom, inclusive`, + ); + } + } + + public getMinZoom() { + return this.transform.minZoom; + } + + public setMaxZoom(maxZoom?: number) { + maxZoom = + maxZoom === null || maxZoom === undefined ? defaultMaxZoom : maxZoom; + + if (maxZoom >= this.transform.minZoom) { + this.transform.maxZoom = maxZoom; + if (this.getZoom() > maxZoom) { + this.setZoom(maxZoom); + } + + return this; + } else { + throw new Error('maxZoom must be greater than the current minZoom'); + } + } + public getMaxZoom() { + return this.transform.maxZoom; + } + + public setMinPitch(minPitch?: number) { + minPitch = + minPitch === null || minPitch === undefined ? defaultMinPitch : minPitch; + + if (minPitch < defaultMinPitch) { + throw new Error( + `minPitch must be greater than or equal to ${defaultMinPitch}`, + ); + } + + if (minPitch >= defaultMinPitch && minPitch <= this.transform.maxPitch) { + this.transform.minPitch = minPitch; + if (this.getPitch() < minPitch) { + this.setPitch(minPitch); + } + + return this; + } else { + throw new Error( + `minPitch must be between ${defaultMinPitch} and the current maxPitch, inclusive`, + ); + } + } + + public getMinPitch() { + return this.transform.minPitch; + } + + public setMaxPitch(maxPitch?: number) { + maxPitch = + maxPitch === null || maxPitch === undefined ? defaultMaxPitch : maxPitch; + + if (maxPitch > defaultMaxPitch) { + throw new Error( + `maxPitch must be less than or equal to ${defaultMaxPitch}`, + ); + } + + if (maxPitch >= this.transform.minPitch) { + this.transform.maxPitch = maxPitch; + if (this.getPitch() > maxPitch) { + this.setPitch(maxPitch); + } + + return this; + } else { + throw new Error('maxPitch must be greater than the current minPitch'); + } + } + + public getMaxPitch() { + return this.transform.maxPitch; + } + + public getRenderWorldCopies() { + return this.transform.renderWorldCopies; + } + + public setRenderWorldCopies(renderWorldCopies?: boolean) { + this.transform.renderWorldCopies = !!renderWorldCopies; + } + + public remove() { + if (this.frame) { + this.frame.cancel(); + this.frame = null; + } + this.renderTaskQueue.clear(); + } + + public requestRenderFrame(cb: CallBack): TaskID { + this.update(); + return this.renderTaskQueue.add(cb); + } + + public cancelRenderFrame(id: TaskID) { + return this.renderTaskQueue.remove(id); + } + + public triggerRepaint() { + if (!this.frame) { + this.frame = renderframe((paintStartTimeStamp: number) => { + PerformanceUtils.frame(paintStartTimeStamp); + this.frame = null; + this.update(paintStartTimeStamp); + }); + } + } + + public update(time?: number) { + if (!this.frame) { + this.frame = renderframe((paintStartTimeStamp: number) => { + PerformanceUtils.frame(paintStartTimeStamp); + this.frame = null; + this.renderTaskQueue.run(time); + }); + } + } + + private initContainer() { + if (typeof this.options.container === 'string') { + this.container = window.document.getElementById( + this.options.container, + ) as HTMLElement; + if (!this.container) { + throw new Error(`Container '${this.options.container}' not found.`); + } + } else if (this.options.container instanceof HTMLElement) { + this.container = this.options.container; + } else { + throw new Error( + "Invalid type: 'container' must be a String or HTMLElement.", + ); + } + + const container = this.container; + container.classList.add('l7-map'); + + const canvasContainer = (this.canvasContainer = DOM.create( + 'div', + 'l7-canvas-container', + container, + )); + if (this.options.interactive) { + canvasContainer.classList.add('l7-interactive'); + } + + // this.canvas = DOM.create( + // 'canvas', + // 'l7-canvas', + // canvasContainer, + // ) as HTMLCanvasElement; + // this.canvas.setAttribute('tabindex', '-'); + // this.canvas.setAttribute('aria-label', 'Map'); + } + + private containerDimensions(): [number, number] { + let width = 0; + let height = 0; + if (this.container) { + width = this.container.clientWidth || 400; + height = this.container.clientHeight || 300; + } + return [width, height]; + } + + private resizeCanvas(width: number, height: number) { + const pixelRatio = DOM.DPR || 1; + this.canvas.width = pixelRatio * width; + this.canvas.height = pixelRatio * height; + + // Maintain the same canvas size, potentially downscaling it for HiDPI displays + this.canvas.style.width = `${width}px`; + this.canvas.style.height = `${height}px`; + } + + private onWindowOnline = () => { + this.update(); + }; + + private onWindowResize = (event: Event) => { + if (this.trackResize) { + this.resize({ originalEvent: event }).update(); + } + }; +} diff --git a/packages/map/src/handler/IHandler.ts b/packages/map/src/handler/IHandler.ts index 1dd11a64d1..d31c059144 100644 --- a/packages/map/src/handler/IHandler.ts +++ b/packages/map/src/handler/IHandler.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../earthmap'; import Point from '../geo/point'; import { Map } from '../map'; export interface IHandlerResult { @@ -8,7 +9,7 @@ export interface IHandlerResult { pitchDelta?: number; around?: Point | null; pinchAround?: Point | null; - cameraAnimation?: (map: Map) => any; + cameraAnimation?: (map: Map | EarthMap) => any; originalEvent?: any; // Makes the manager trigger a frame; allowing the handler to return multiple results over time (see scrollzoom). needsRenderFrame?: boolean; diff --git a/packages/map/src/handler/blockable_map_event.ts b/packages/map/src/handler/blockable_map_event.ts index 3ac38a7ffc..e0c1dacf93 100644 --- a/packages/map/src/handler/blockable_map_event.ts +++ b/packages/map/src/handler/blockable_map_event.ts @@ -1,18 +1,20 @@ // @ts-ignore +import { EarthMap } from '../earthmap'; import Point from '../geo/point'; import { Map } from '../map'; import { MapMouseEvent, MapTouchEvent, MapWheelEvent } from './events'; export default class BlockableMapEventHandler { - private map: Map; + private map: Map | EarthMap; private delayContextMenu: boolean; private contextMenuEvent: MouseEvent; - constructor(map: Map) { + constructor(map: Map | EarthMap) { this.map = map; } public reset() { this.delayContextMenu = false; + // @ts-ignore delete this.contextMenuEvent; } @@ -32,6 +34,7 @@ export default class BlockableMapEventHandler { 'contextmenu', new MapMouseEvent('contextmenu', this.map, this.contextMenuEvent), ); + // @ts-ignore delete this.contextMenuEvent; } } diff --git a/packages/map/src/handler/box_zoom.ts b/packages/map/src/handler/box_zoom.ts index 91b184dfba..bd92e3a49d 100644 --- a/packages/map/src/handler/box_zoom.ts +++ b/packages/map/src/handler/box_zoom.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../earthmap'; import Point from '../geo/point'; import { Map } from '../map'; import DOM from '../utils/dom'; @@ -9,7 +10,7 @@ import { Event } from './events/event'; * The bounding box is defined by clicking and holding `shift` while dragging the cursor. */ class BoxZoomHandler { - private map: Map; + private map: Map | EarthMap; private el: HTMLElement; private container: HTMLElement; private enabled: boolean; @@ -23,7 +24,7 @@ class BoxZoomHandler { * @private */ constructor( - map: Map, + map: Map | EarthMap, options: { clickTolerance: number; }, @@ -179,8 +180,9 @@ class BoxZoomHandler { } DOM.enableDrag(); - + // @ts-ignore delete this.startPos; + // @ts-ignore delete this.lastPos; } diff --git a/packages/map/src/handler/click_zoom.ts b/packages/map/src/handler/click_zoom.ts index d4aaf8d1ec..fcdaf87d59 100644 --- a/packages/map/src/handler/click_zoom.ts +++ b/packages/map/src/handler/click_zoom.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../earthmap'; import Point from '../geo/point'; import { Map } from '../map'; @@ -17,7 +18,7 @@ export default class ClickZoomHandler { public dblclick(e: MouseEvent, point: Point) { e.preventDefault(); return { - cameraAnimation: (map: Map) => { + cameraAnimation: (map: Map | EarthMap) => { map.easeTo( { duration: 300, diff --git a/packages/map/src/handler/events/map_mouse_event.ts b/packages/map/src/handler/events/map_mouse_event.ts index 32deb0e7ca..eb940b706b 100644 --- a/packages/map/src/handler/events/map_mouse_event.ts +++ b/packages/map/src/handler/events/map_mouse_event.ts @@ -1,6 +1,7 @@ // @ts-ignore // tslint:disable-next-line:no-submodule-imports import merge from 'lodash/merge'; +import { EarthMap } from '../../earthmap'; import LngLat from '../../geo/lng_lat'; import Point from '../../geo/point'; import { Map } from '../../map'; @@ -27,7 +28,7 @@ export default class MapMouseEvent extends Event { /** * The `Map` object that fired the event. */ - public target: Map; + public target: Map | EarthMap; /** * The DOM event which caused the map event. @@ -51,7 +52,7 @@ export default class MapMouseEvent extends Event { */ constructor( type: string, - map: Map, + map: Map | EarthMap, originalEvent: MouseEvent, data: any = {}, ) { diff --git a/packages/map/src/handler/events/map_touch_event.ts b/packages/map/src/handler/events/map_touch_event.ts index 722d43a5dd..9932405649 100644 --- a/packages/map/src/handler/events/map_touch_event.ts +++ b/packages/map/src/handler/events/map_touch_event.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../../earthmap'; import LngLat from '../../geo/lng_lat'; import Point from '../../geo/point'; import { Map } from '../../map'; @@ -13,7 +14,7 @@ export default class MapTouchEvent extends Event { /** * The `Map` object that fired the event. */ - public target: Map; + public target: Map | EarthMap; /** * The DOM event which caused the map event. @@ -53,7 +54,7 @@ export default class MapTouchEvent extends Event { /** * @private */ - constructor(type: string, map: Map, originalEvent: TouchEvent) { + constructor(type: string, map: Map | EarthMap, originalEvent: TouchEvent) { const touches = type === 'touchend' ? originalEvent.changedTouches diff --git a/packages/map/src/handler/events/map_wheel_event.ts b/packages/map/src/handler/events/map_wheel_event.ts index 214012c675..d3d9032e7a 100644 --- a/packages/map/src/handler/events/map_wheel_event.ts +++ b/packages/map/src/handler/events/map_wheel_event.ts @@ -1,9 +1,10 @@ +import { EarthMap } from '../../earthmap'; import { Map } from '../../map'; import { Event } from './event'; export interface IMapBoxZoomEvent { type: 'boxzoomstart' | 'boxzoomend' | 'boxzoomcancel'; - target: Map; + target: Map | EarthMap; originalEvent: MouseEvent; } export default class MapWheelEvent extends Event { @@ -22,12 +23,12 @@ export default class MapWheelEvent extends Event { /** * The `Map` object that fired the event. */ - public target: Map; + public target: Map | EarthMap; /** * @private */ - constructor(type: string, map: Map, originalEvent: WheelEvent) { + constructor(type: string, map: Map | EarthMap, originalEvent: WheelEvent) { super(type, { originalEvent }); this.defaultPrevented = false; } diff --git a/packages/map/src/handler/handler_inertia.ts b/packages/map/src/handler/handler_inertia.ts index e30b268310..e9705e39a2 100644 --- a/packages/map/src/handler/handler_inertia.ts +++ b/packages/map/src/handler/handler_inertia.ts @@ -3,6 +3,7 @@ import Point from '../geo/point'; // tslint:disable-next-line:no-submodule-imports import merge from 'lodash/merge'; +import { EarthMap } from '../earthmap'; import { Map } from '../map'; import { bezier, clamp, now } from '../util'; import { IDragPanOptions } from './shim/drag_pan'; @@ -54,13 +55,13 @@ export interface IInertiaOptions { export type InputEvent = MouseEvent | TouchEvent | KeyboardEvent | WheelEvent; export default class HandlerInertia { - private map: Map; + private map: Map | EarthMap; private inertiaBuffer: Array<{ time: number; settings: { [key: string]: any }; }>; - constructor(map: Map) { + constructor(map: Map | EarthMap) { this.map = map; this.clear(); } diff --git a/packages/map/src/handler/handler_manager.ts b/packages/map/src/handler/handler_manager.ts index 7c7b900ac4..203a28abc4 100644 --- a/packages/map/src/handler/handler_manager.ts +++ b/packages/map/src/handler/handler_manager.ts @@ -1,6 +1,7 @@ // @ts-ignore // tslint:disable-next-line: no-submodule-imports import merge from 'lodash/merge'; +import { EarthMap } from '../earthmap'; import Point from '../geo/point'; import { Map } from '../map'; import DOM from '../utils/dom'; @@ -62,7 +63,7 @@ export interface IHandlerOptions { } class HandlerManager { - private map: Map; + private map: Map | EarthMap; private el: HTMLElement; private handlers: Array<{ handlerName: string; @@ -82,7 +83,7 @@ class HandlerManager { [HTMLElement, string, void | { passive?: boolean; capture?: boolean }] >; - constructor(map: Map, options: IHandlerOptions) { + constructor(map: Map | EarthMap, options: IHandlerOptions) { this.map = map; this.el = this.map.getCanvasContainer(); this.handlers = []; diff --git a/packages/map/src/handler/keyboard.ts b/packages/map/src/handler/keyboard.ts index a5ad19d263..c4838099cf 100644 --- a/packages/map/src/handler/keyboard.ts +++ b/packages/map/src/handler/keyboard.ts @@ -1,3 +1,4 @@ +import { EarthMap } from '../earthmap'; import { Map } from '../map'; const defaultOptions = { @@ -106,7 +107,7 @@ class KeyboardHandler { } return { - cameraAnimation: (map: Map) => { + cameraAnimation: (map: Map | EarthMap) => { const zoom = map.getZoom(); map.easeTo( { diff --git a/packages/map/src/handler/map_event.ts b/packages/map/src/handler/map_event.ts index 783c5d2c65..40006b2b25 100644 --- a/packages/map/src/handler/map_event.ts +++ b/packages/map/src/handler/map_event.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../earthmap'; import Point from '../geo/point'; import { Map } from '../map'; import { MapMouseEvent, MapTouchEvent, MapWheelEvent } from './events'; @@ -6,14 +7,15 @@ import { MapMouseEvent, MapTouchEvent, MapWheelEvent } from './events'; export default class MapEventHandler { private mousedownPos: Point; private clickTolerance: number; - private map: Map; + private map: Map | EarthMap; - constructor(map: Map, options: { clickTolerance: number }) { + constructor(map: Map | EarthMap, options: { clickTolerance: number }) { this.map = map; this.clickTolerance = options.clickTolerance; } public reset() { + // @ts-ignore delete this.mousedownPos; } diff --git a/packages/map/src/handler/scroll_zoom.ts b/packages/map/src/handler/scroll_zoom.ts index fe440f2c41..d2a0fb32e0 100644 --- a/packages/map/src/handler/scroll_zoom.ts +++ b/packages/map/src/handler/scroll_zoom.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../earthmap'; import LngLat from '../geo/lng_lat'; import Point from '../geo/point'; import { Map } from '../map'; @@ -22,7 +23,7 @@ const maxScalePerFrame = 2; * The `ScrollZoomHandler` allows the user to zoom the map by scrolling. */ class ScrollZoomHandler { - private map: Map; + private map: Map | EarthMap; private el: HTMLElement; private enabled: boolean; private active: boolean; @@ -57,7 +58,7 @@ class ScrollZoomHandler { /** * @private */ - constructor(map: Map, handler: HandlerManager) { + constructor(map: Map | EarthMap, handler: HandlerManager) { this.map = map; this.el = map.getCanvasContainer(); this.handler = handler; @@ -287,7 +288,9 @@ class ScrollZoomHandler { this.finishTimeout = setTimeout(() => { this.zooming = false; this.handler.triggerRenderFrame(); + // @ts-ignore delete this.targetZoom; + // @ts-ignore delete this.finishTimeout; }, 200); } @@ -325,6 +328,7 @@ class ScrollZoomHandler { if (this.finishTimeout) { clearTimeout(this.finishTimeout); + // @ts-ignore delete this.finishTimeout; } diff --git a/packages/map/src/handler/tap/tap_zoom.ts b/packages/map/src/handler/tap/tap_zoom.ts index 040de7445b..bedec2a2a0 100644 --- a/packages/map/src/handler/tap/tap_zoom.ts +++ b/packages/map/src/handler/tap/tap_zoom.ts @@ -1,4 +1,5 @@ // @ts-ignore +import { EarthMap } from '../../earthmap'; import Point from '../../geo/point'; import { Map } from '../../map'; import TapRecognizer from './tap_recognizer'; @@ -48,7 +49,7 @@ export default class TapZoomHandler { e.preventDefault(); setTimeout(() => this.reset(), 0); return { - cameraAnimation: (map: Map) => + cameraAnimation: (map: Map | EarthMap) => map.easeTo( { duration: 300, @@ -63,7 +64,7 @@ export default class TapZoomHandler { e.preventDefault(); setTimeout(() => this.reset(), 0); return { - cameraAnimation: (map: Map) => + cameraAnimation: (map: Map | EarthMap) => map.easeTo( { duration: 300, diff --git a/packages/map/src/hash.ts b/packages/map/src/hash.ts index 6b7af9cd1c..e8581cb52a 100644 --- a/packages/map/src/hash.ts +++ b/packages/map/src/hash.ts @@ -1,6 +1,7 @@ // @ts-ignore // tslint:disable-next-line:no-submodule-imports import throttle from 'lodash/throttle'; +import { EarthMap } from './earthmap'; import { Map } from './map'; /* @@ -10,7 +11,7 @@ import { Map } from './map'; * @returns {Hash} `this` */ class Hash { - private map: Map; + private map: Map | EarthMap; private updateHash: () => number | void; private hashName?: string; @@ -20,17 +21,19 @@ class Hash { // Mobile Safari doesn't allow updating the hash more than 100 times per 30 seconds. this.updateHash = throttle(this.updateHashUnthrottled, (30 * 1000) / 100); } - public addTo(map: Map) { + public addTo(map: Map | EarthMap) { this.map = map; window.addEventListener('hashchange', this.onHashChange, false); + // @ts-ignore this.map.on('moveend', this.updateHash); return this; } public remove() { window.removeEventListener('hashchange', this.onHashChange, false); + // @ts-ignore this.map.off('moveend', this.updateHash); // clearTimeout(this.updateHash()); - + // @ts-ignore delete this.map; return this; } diff --git a/packages/map/src/index.ts b/packages/map/src/index.ts index 111a393c67..17645bd510 100644 --- a/packages/map/src/index.ts +++ b/packages/map/src/index.ts @@ -1 +1,2 @@ export * from './map'; +export * from './earthmap'; diff --git a/packages/maps/src/amap/Viewport.ts b/packages/maps/src/amap/Viewport.ts index b7fc3357c8..50c761138c 100644 --- a/packages/maps/src/amap/Viewport.ts +++ b/packages/maps/src/amap/Viewport.ts @@ -85,6 +85,10 @@ export default class Viewport implements IViewport { return this.projectionMatrix; } + public getModelMatrix(): number[] { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + } + public getViewMatrix(): number[] { // @ts-ignore return this.viewMatrix; diff --git a/packages/maps/src/amap2/Viewport.ts b/packages/maps/src/amap2/Viewport.ts index 6b19bd7d48..a718947f2e 100644 --- a/packages/maps/src/amap2/Viewport.ts +++ b/packages/maps/src/amap2/Viewport.ts @@ -97,6 +97,10 @@ export default class Viewport implements IViewport { return this.projectionMatrix; } + public getModelMatrix(): number[] { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + } + public getViewMatrix(): number[] { // @ts-ignore return this.viewMatrix; diff --git a/packages/maps/src/earth/Viewport.ts b/packages/maps/src/earth/Viewport.ts new file mode 100644 index 0000000000..428c357c9c --- /dev/null +++ b/packages/maps/src/earth/Viewport.ts @@ -0,0 +1,143 @@ +import { IMapCamera, IViewport } from '@antv/l7-core'; +import { mat4, vec3 } from 'gl-matrix'; +import WebMercatorViewport from 'viewport-mercator-project'; + +export default class Viewport implements IViewport { + // TODO: 初始化相机的姿态 看向地球 + private xzReg: number = -Math.PI * 0.6; + private yReg: number = Math.PI * 0.2; + // 默认的地球相机半径、地球相机缩放层级 + private earthCameraRadius: number = 200; + private earthCameraZoom: number = 1; + + private cameraPosition: vec3 = vec3.create(); + + private viewport: WebMercatorViewport; + + private projectionMatrix: mat4 = mat4.create(); + private modelMatrix: mat4 = mat4.create(); + private viewMatrix: mat4 = mat4.create(); + private viewProjectionMatrix: mat4 = mat4.create(); + private ViewProjectionMatrixUncentered: mat4 = mat4.create(); + private viewUncenteredMatrix: mat4 = mat4.create(); + + public syncWithMapCamera(mapCamera: Partial) { + const { viewportHeight = 1, viewportWidth = 1 } = mapCamera; + const aspect = viewportWidth / viewportHeight; + const near = 0.1; + const far = 10000; + const fov = 20; + + // 计算透视投影矩阵 projectionMatrix + mat4.perspective(this.projectionMatrix, fov, aspect, near, far); + // 计算相机矩阵 viewMatrix + const x = this.earthCameraRadius * Math.cos(this.xzReg); + const z = this.earthCameraRadius * Math.sin(this.xzReg); + const y = this.earthCameraRadius * Math.sin(this.yReg); + + this.cameraPosition = vec3.fromValues(x, y, z); + vec3.normalize(this.cameraPosition, this.cameraPosition); + vec3.multiply( + this.cameraPosition, + this.cameraPosition, + vec3.fromValues( + this.earthCameraRadius, + this.earthCameraRadius, + this.earthCameraRadius, + ), + ); + + vec3.scale(this.cameraPosition, this.cameraPosition, this.earthCameraZoom); + + const crossY = vec3.create(); + vec3.cross(crossY, this.cameraPosition, vec3.fromValues(0, 1, 0)); + + const up = vec3.fromValues(0, 1, 0); + vec3.cross(up, crossY, this.cameraPosition); + + const target = vec3.fromValues(0, 0, 0); + mat4.lookAt(this.viewMatrix, this.cameraPosition, target, up); + this.viewUncenteredMatrix = mat4.clone(this.viewMatrix); + + mat4.multiply( + this.viewProjectionMatrix, + this.projectionMatrix, + this.viewMatrix, + ); + mat4.multiply( + this.ViewProjectionMatrixUncentered, + this.projectionMatrix, + this.viewMatrix, + ); + } + + /** + * 旋转方法 Y 轴 + * @param r + */ + public rotateY(r: number) { + this.xzReg += r * Math.min(this.earthCameraZoom * this.earthCameraZoom, 1); + } + + /** + * 旋转方法 X 轴 + * @param r + */ + public rotateX(r: number) { + this.yReg += r * Math.min(this.earthCameraZoom * this.earthCameraZoom, 1); + } + + /** + * 缩放方法 + * @param z + */ + public scaleZoom(z: number) { + this.earthCameraZoom += z; + this.earthCameraZoom = Math.max(this.earthCameraZoom, 0.6); + } + + public getZoom(): number { + return 4; + } + + public getZoomScale(): number { + return Math.pow(2, this.getZoom()); + } + + public getCenter(): [number, number] { + return [0, 0]; + } + + public getProjectionMatrix(): number[] { + return this.projectionMatrix as number[]; + } + + public getModelMatrix(): number[] { + return this.modelMatrix as number[]; + } + + public getViewMatrix(): number[] { + return this.viewMatrix as number[]; + } + + public getViewMatrixUncentered(): number[] { + return this.viewMatrix as number[]; + } + public getViewProjectionMatrix(): number[] { + return this.viewProjectionMatrix as number[]; + } + + public getViewProjectionMatrixUncentered(): number[] { + return this.viewProjectionMatrix as number[]; + } + public getFocalDistance() { + return 1; + } + + public projectFlat( + lngLat: [number, number], + scale?: number | undefined, + ): [number, number] { + return this.viewport.projectFlat(lngLat, scale); + } +} diff --git a/packages/maps/src/earth/index.ts b/packages/maps/src/earth/index.ts new file mode 100644 index 0000000000..5fa4227acd --- /dev/null +++ b/packages/maps/src/earth/index.ts @@ -0,0 +1,8 @@ +import { Map } from '@antv/l7-map'; +import BaseMapWrapper from '../BaseMapWrapper'; +import MapService from './map'; +export default class MapboxWrapper extends BaseMapWrapper { + protected getServiceConstructor() { + return MapService; + } +} diff --git a/packages/maps/src/earth/map.ts b/packages/maps/src/earth/map.ts new file mode 100644 index 0000000000..cd9dc974ee --- /dev/null +++ b/packages/maps/src/earth/map.ts @@ -0,0 +1,413 @@ +/** + * MapboxService + */ +import { + Bounds, + CoordinateSystem, + ICoordinateSystemService, + IGlobalConfigService, + ILngLat, + IMapConfig, + IMapService, + IMercator, + IPoint, + IStatusOptions, + IViewport, + MapServiceEvent, + MapStyle, + TYPES, +} from '@antv/l7-core'; +import { EarthMap, Map } from '@antv/l7-map'; +import { DOM } from '@antv/l7-utils'; +import { inject, injectable } from 'inversify'; +import 'reflect-metadata'; +import { Version } from '../version'; +import Viewport from './Viewport'; +const EventMap: { + [key: string]: any; +} = { + mapmove: 'move', + camerachange: 'move', + zoomchange: 'zoom', + dragging: 'drag', +}; +import { MapTheme } from './theme'; + +const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12; +/** + * AMapService + */ +@injectable() +export default class L7MapService implements IMapService { + public version: string = Version.GLOBEL; + public map: Map; + + @inject(TYPES.MapConfig) + private readonly config: Partial; + + @inject(TYPES.IGlobalConfigService) + private readonly configService: IGlobalConfigService; + + @inject(TYPES.ICoordinateSystemService) + private readonly coordinateSystemService: ICoordinateSystemService; + + @inject(TYPES.IEventEmitter) + private eventEmitter: any; + private viewport: Viewport; + private markerContainer: HTMLElement; + private cameraChangedCallback: (viewport: IViewport) => void; + private $mapContainer: HTMLElement | null; + // T: 用于记录鼠标对相机的控制 + private handleCameraChanging: boolean; + private handleCameraTimer: any; + + // init + public addMarkerContainer(): void { + const container = this.map.getCanvasContainer(); + this.markerContainer = DOM.create('div', 'l7-marker-container', container); + this.markerContainer.setAttribute('tabindex', '-1'); + } + + public getMarkerContainer(): HTMLElement { + return this.markerContainer; + } + + // map event + public on(type: string, handle: (...args: any[]) => void): void { + if (MapServiceEvent.indexOf(type) !== -1) { + this.eventEmitter.on(type, handle); + } else { + // 统一事件名称 + this.map.on(EventMap[type] || type, handle); + } + } + public off(type: string, handle: (...args: any[]) => void): void { + this.map.off(EventMap[type] || type, handle); + } + + public getContainer(): HTMLElement | null { + return this.map.getContainer(); + } + + public getMapCanvasContainer(): HTMLElement { + return this.map.getCanvasContainer() as HTMLElement; + } + + public getSize(): [number, number] { + const size = this.map.transform; + return [size.width, size.height]; + } + // get mapStatus method + + public getType() { + return 'default'; + } + + public getZoom(): number { + return this.map.getZoom(); + } + + public setZoom(zoom: number) { + return this.map.setZoom(zoom); + } + + public getCenter(): ILngLat { + return this.map.getCenter(); + } + + public setCenter(lnglat: [number, number]): void { + this.map.setCenter(lnglat); + } + + public getPitch(): number { + return this.map.getPitch(); + } + + public getRotation(): number { + return this.map.getBearing(); + } + + public getBounds(): Bounds { + return this.map.getBounds().toArray() as Bounds; + } + + public getMinZoom(): number { + return this.map.getMinZoom(); + } + + public getMaxZoom(): number { + return this.map.getMaxZoom(); + } + + public setRotation(rotation: number): void { + this.map.setBearing(rotation); + } + + public zoomIn(option?: any, eventData?: any): void { + this.map.zoomIn(option, eventData); + } + public zoomOut(option?: any, eventData?: any): void { + this.map.zoomOut(option, eventData); + } + public setPitch(pitch: number) { + return this.map.setPitch(pitch); + } + + public panTo(p: [number, number]): void { + this.map.panTo(p); + } + + public panBy(pixel: [number, number]): void { + this.panTo(pixel); + } + + public fitBounds(bound: Bounds, fitBoundsOptions?: any): void { + this.map.fitBounds(bound, fitBoundsOptions); + } + + public setMaxZoom(max: number): void { + this.map.setMaxZoom(max); + } + + public setMinZoom(min: number): void { + this.map.setMinZoom(min); + } + public setMapStatus(option: Partial): void { + if (option.doubleClickZoom === true) { + this.map.doubleClickZoom.enable(); + } + if (option.doubleClickZoom === false) { + this.map.doubleClickZoom.disable(); + } + if (option.dragEnable === false) { + this.map.dragPan.disable(); + } + if (option.dragEnable === true) { + this.map.dragPan.enable(); + } + if (option.rotateEnable === false) { + this.map.dragRotate.disable(); + } + if (option.dragEnable === true) { + this.map.dragRotate.enable(); + } + if (option.keyboardEnable === false) { + this.map.keyboard.disable(); + } + if (option.keyboardEnable === true) { + this.map.keyboard.enable(); + } + if (option.zoomEnable === false) { + this.map.scrollZoom.disable(); + } + if (option.zoomEnable === true) { + this.map.scrollZoom.enable(); + } + } + + public setZoomAndCenter(zoom: number, center: [number, number]): void { + this.map.flyTo({ + zoom, + center, + }); + } + + public setMapStyle(style: any): void { + this.map.setStyle(this.getMapStyle(style)); + } + // TODO: 计算像素坐标 + public pixelToLngLat(pixel: [number, number]): ILngLat { + return this.map.unproject(pixel); + } + + public lngLatToPixel(lnglat: [number, number]): IPoint { + return this.map.project(lnglat); + } + + public containerToLngLat(pixel: [number, number]): ILngLat { + return this.map.unproject(pixel); + } + + public lngLatToContainer(lnglat: [number, number]): IPoint { + return this.map.project(lnglat); + } + public lngLatToMercator( + lnglat: [number, number], + altitude: number, + ): IMercator { + throw new Error('not implement'); + } + public getModelMatrix( + lnglat: [number, number], + altitude: number, + rotate: [number, number, number], + scale: [number, number, number] = [1, 1, 1], + origin: IMercator = { x: 0, y: 0, z: 0 }, + ): number[] { + throw new Error('not implement'); + } + + public async init(): Promise { + const { + id = 'map', + attributionControl = false, + style = 'light', + rotation = 0, + mapInstance, + ...rest + } = this.config; + + this.viewport = new Viewport(); + + this.$mapContainer = this.creatAmapContainer(id); + // @ts-ignore + this.map = new EarthMap({ + container: this.$mapContainer, + style: this.getMapStyle(style), + bearing: rotation, + ...rest, + }); + + this.map.on('load', this.handleCameraChanged); + this.map.on('move', this.handleCameraChanged); + + // 不同于高德地图,需要手动触发首次渲染 + this.handleCameraChanged({}); + } + + public destroy() { + // TODO: 销毁地图可视化层的容器 + this.$mapContainer?.parentNode?.removeChild(this.$mapContainer); + + this.eventEmitter.removeAllListeners(); + if (this.map) { + this.map.remove(); + this.$mapContainer = null; + } + } + public emit(name: string, ...args: any[]) { + this.eventEmitter.emit(name, ...args); + } + public once(name: string, ...args: any[]) { + this.eventEmitter.once(name, ...args); + } + + public getMapContainer() { + return this.$mapContainer; + } + + public exportMap(type: 'jpg' | 'png'): string { + const renderCanvas = this.map.getCanvas(); + const layersPng = + type === 'jpg' + ? (renderCanvas?.toDataURL('image/jpeg') as string) + : (renderCanvas?.toDataURL('image/png') as string); + return layersPng; + } + public onCameraChanged(callback: (viewport: IViewport) => void): void { + this.cameraChangedCallback = callback; + } + + /** + * 地球模式向外暴露的 Y 轴旋转方法 + * @returns + */ + public rotateY(option: { force: boolean; reg: number }) { + const { force = false, reg = 0.01 } = option || {}; + // TODO: 让旋转方法与 + if (this.handleCameraChanging && !force) { + return; + } + + if (this.viewport) { + this.viewport.rotateY(reg); + + this.viewport.syncWithMapCamera({ + bearing: this.map.getBearing(), + viewportHeight: this.map.transform.height, + pitch: this.map.getPitch(), + viewportWidth: this.map.transform.width, + zoom: this.map.getZoom(), + // mapbox 中固定相机高度为 viewport 高度的 1.5 倍 + cameraHeight: 0, + }); + } + } + + private handleCameraChanged = (e: any) => { + const DELAY_TIME = 2000; + this.handleCameraChanging = true; + if (this.handleCameraTimer) { + clearTimeout(this.handleCameraTimer); + } + this.handleCameraTimer = setTimeout(() => { + this.handleCameraChanging = false; + }, DELAY_TIME); + // 定义鼠标相机控制 + const rotateStep = 0.02; + if (e.type && e.originalEvent) { + if (e.originalEvent.type === 'wheel') { + this.viewport.scaleZoom( + 0.01 * Math.sign(e.originalEvent.wheelDelta) * -1, + ); + } + + if ( + Math.abs(e.originalEvent.movementX) > + Math.abs(e.originalEvent.movementY) + ) { + if (e.originalEvent.movementX > 0) { + this.viewport.rotateY(rotateStep); + } else if (e.originalEvent.movementX < 0) { + this.viewport.rotateY(-rotateStep); + } + } else { + if (e.originalEvent.movementY > 0) { + this.viewport.rotateX(rotateStep); + } else if (e.originalEvent.movementY < 0) { + this.viewport.rotateX(-rotateStep); + } + } + } + + const { offsetCoordinate = true } = this.config; + + // resync + this.viewport.syncWithMapCamera({ + bearing: this.map.getBearing(), + viewportHeight: this.map.transform.height, + pitch: this.map.getPitch(), + viewportWidth: this.map.transform.width, + zoom: this.map.getZoom(), + // mapbox 中固定相机高度为 viewport 高度的 1.5 倍 + cameraHeight: 0, + }); + // set coordinate system + if ( + this.viewport.getZoom() > LNGLAT_OFFSET_ZOOM_THRESHOLD && + offsetCoordinate + ) { + this.coordinateSystemService.setCoordinateSystem( + CoordinateSystem.LNGLAT_OFFSET, + ); + } else { + this.coordinateSystemService.setCoordinateSystem(CoordinateSystem.LNGLAT); + } + + this.cameraChangedCallback(this.viewport); + }; + + private creatAmapContainer(id: string | HTMLDivElement) { + let $wrapper = id as HTMLDivElement; + if (typeof id === 'string') { + $wrapper = document.getElementById(id) as HTMLDivElement; + } + return $wrapper; + } + private getMapStyle(name: MapStyle) { + if (typeof name !== 'string') { + return name; + } + return MapTheme[name] ? MapTheme[name] : name; + } +} diff --git a/packages/maps/src/earth/theme.ts b/packages/maps/src/earth/theme.ts new file mode 100644 index 0000000000..3b29aa499f --- /dev/null +++ b/packages/maps/src/earth/theme.ts @@ -0,0 +1,23 @@ +export const MapTheme: { + [key: string]: any; +} = { + light: 'mapbox://styles/zcxduo/ck2ypyb1r3q9o1co1766dex29', + dark: 'mapbox://styles/zcxduo/ck241p6413s0b1cpayzldv7x7', + normal: 'mapbox://styles/mapbox/streets-v11', + blank: { + version: 8, + // sprite: 'https://lzxue.github.io/font-glyphs/sprite/sprite', + // glyphs: + // 'https://gw.alipayobjects.com/os/antvdemo/assets/mapbox/glyphs/{fontstack}/{range}.pbf', + sources: {}, + layers: [ + { + id: 'background', + type: 'background', + layout: { + visibility: 'none', + }, + }, + ], + }, +}; diff --git a/packages/maps/src/index.ts b/packages/maps/src/index.ts index c3dc18c41a..bc4d542e0c 100644 --- a/packages/maps/src/index.ts +++ b/packages/maps/src/index.ts @@ -2,7 +2,8 @@ import GaodeMap from './amap/'; // import GaodeMapV1 from './amap/'; import GaodeMapV2 from './amap2/'; +import Earth from './earth/'; import Map from './map/'; import Mapbox from './mapbox/'; -export { GaodeMap, GaodeMapV2, Mapbox, Map }; +export { GaodeMap, GaodeMapV2, Mapbox, Map, Earth }; diff --git a/packages/maps/src/map/Viewport.ts b/packages/maps/src/map/Viewport.ts index 92796ce173..27f80593d1 100644 --- a/packages/maps/src/map/Viewport.ts +++ b/packages/maps/src/map/Viewport.ts @@ -45,6 +45,10 @@ export default class Viewport implements IViewport { return this.viewport.projectionMatrix; } + public getModelMatrix(): number[] { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + } + public getViewMatrix(): number[] { return this.viewport.viewMatrix; } diff --git a/packages/maps/src/mapbox/Viewport.ts b/packages/maps/src/mapbox/Viewport.ts index 92796ce173..27f80593d1 100644 --- a/packages/maps/src/mapbox/Viewport.ts +++ b/packages/maps/src/mapbox/Viewport.ts @@ -45,6 +45,10 @@ export default class Viewport implements IViewport { return this.viewport.projectionMatrix; } + public getModelMatrix(): number[] { + return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; + } + public getViewMatrix(): number[] { return this.viewport.viewMatrix; } diff --git a/packages/maps/src/version.ts b/packages/maps/src/version.ts index 66006cf1a3..7a257da34d 100644 --- a/packages/maps/src/version.ts +++ b/packages/maps/src/version.ts @@ -6,4 +6,5 @@ export enum Version { 'GAODE2.x' = 'GAODE2.x', 'MAPBOX' = 'MAPBOX', 'L7MAP' = 'L7MAP', + 'GLOBEL' = 'GLOBEL', } diff --git a/stories/customMap/components/earth.tsx b/stories/customMap/components/earth.tsx new file mode 100644 index 0000000000..91c5de503b --- /dev/null +++ b/stories/customMap/components/earth.tsx @@ -0,0 +1,409 @@ +// @ts-ignore +import { Scene } from '@antv/l7'; +import { PointLayer, EarthLayer } from '@antv/l7-layers'; +import { Earth } from '@antv/l7-maps'; +import * as React from 'react'; + +export default class ScaleComponent extends React.Component { + private scene: Scene; + + public componentWillUnmount() { + this.scene.destroy(); + } + + public async componentDidMount() { + const scene = new Scene({ + id: 'map', + map: new Earth({ + center: [120, 30], + pitch: 0, + zoom: 3, + }), + }); + + var d = [ + { lng: 127.657407, lat: 49.76027 }, + { lng: 129.397818, lat: 49.4406 }, + { lng: 130.582293, lat: 48.729687 }, + { lng: 130.987282, lat: 47.790132 }, + { lng: 132.506672, lat: 47.78897 }, + { lng: 133.373596, lat: 48.183442 }, + { lng: 135.026311, lat: 48.47823 }, + { lng: 134.500814, lat: 47.57844 }, + { lng: 134.112362, lat: 47.212467 }, + { lng: 133.769644, lat: 46.116927 }, + { lng: 133.097127, lat: 45.144066 }, + { lng: 131.883454, lat: 45.321162 }, + { lng: 131.025212, lat: 44.967953 }, + { lng: 131.288555, lat: 44.11152 }, + { lng: 131.144688, lat: 42.92999 }, + { lng: 130.633866, lat: 42.903015 }, + { lng: 130.640016, lat: 42.395009 }, + { lng: 129.994267, lat: 42.985387 }, + { lng: 129.596669, lat: 42.424982 }, + { lng: 128.052215, lat: 41.994285 }, + { lng: 128.208433, lat: 41.466772 }, + { lng: 127.343783, lat: 41.503152 }, + { lng: 126.869083, lat: 41.816569 }, + { lng: 126.182045, lat: 41.107336 }, + { lng: 125.079942, lat: 40.569824 }, + { lng: 124.265625, lat: 39.928493 }, + { lng: 122.86757, lat: 39.637788 }, + { lng: 122.131388, lat: 39.170452 }, + { lng: 121.054554, lat: 38.897471 }, + { lng: 121.585995, lat: 39.360854 }, + { lng: 121.376757, lat: 39.750261 }, + { lng: 122.168595, lat: 40.422443 }, + { lng: 121.640359, lat: 40.94639 }, + { lng: 120.768629, lat: 40.593388 }, + { lng: 119.639602, lat: 39.898056 }, + { lng: 119.023464, lat: 39.252333 }, + { lng: 118.042749, lat: 39.204274 }, + { lng: 117.532702, lat: 38.737636 }, + { lng: 118.059699, lat: 38.061476 }, + { lng: 118.87815, lat: 37.897325 }, + { lng: 118.911636, lat: 37.448464 }, + { lng: 119.702802, lat: 37.156389 }, + { lng: 120.823457, lat: 37.870428 }, + { lng: 121.711259, lat: 37.481123 }, + { lng: 122.357937, lat: 37.454484 }, + { lng: 122.519995, lat: 36.930614 }, + { lng: 121.104164, lat: 36.651329 }, + { lng: 120.637009, lat: 36.11144 }, + { lng: 119.664562, lat: 35.609791 }, + { lng: 119.151208, lat: 34.909859 }, + { lng: 120.227525, lat: 34.360332 }, + { lng: 120.620369, lat: 33.376723 }, + { lng: 121.229014, lat: 32.460319 }, + { lng: 121.908146, lat: 31.692174 }, + { lng: 121.891919, lat: 30.949352 }, + { lng: 121.264257, lat: 30.676267 }, + { lng: 121.503519, lat: 30.142915 }, + { lng: 122.092114, lat: 29.83252 }, + { lng: 121.938428, lat: 29.018022 }, + { lng: 121.684439, lat: 28.225513 }, + { lng: 121.125661, lat: 28.135673 }, + { lng: 120.395473, lat: 27.053207 }, + { lng: 119.585497, lat: 25.740781 }, + { lng: 118.656871, lat: 24.547391 }, + { lng: 117.281606, lat: 23.624501 }, + { lng: 115.890735, lat: 22.782873 }, + { lng: 114.763827, lat: 22.668074 }, + { lng: 114.152547, lat: 22.22376 }, + { lng: 113.80678, lat: 22.54834 }, + { lng: 113.241078, lat: 22.051367 }, + { lng: 111.843592, lat: 21.550494 }, + { lng: 110.785466, lat: 21.397144 }, + { lng: 110.444039, lat: 20.341033 }, + { lng: 109.889861, lat: 20.282457 }, + { lng: 109.627655, lat: 21.008227 }, + { lng: 109.864488, lat: 21.395051 }, + { lng: 108.522813, lat: 21.715212 }, + { lng: 108.05018, lat: 21.55238 }, + { lng: 107.04342, lat: 21.811899 }, + { lng: 106.567273, lat: 22.218205 }, + { lng: 106.725403, lat: 22.794268 }, + { lng: 105.811247, lat: 22.976892 }, + { lng: 105.329209, lat: 23.352063 }, + { lng: 104.476858, lat: 22.81915 }, + { lng: 103.504515, lat: 22.703757 }, + { lng: 102.706992, lat: 22.708795 }, + { lng: 102.170436, lat: 22.464753 }, + { lng: 101.652018, lat: 22.318199 }, + { lng: 101.80312, lat: 21.174367 }, + { lng: 101.270026, lat: 21.201652 }, + { lng: 101.180005, lat: 21.436573 }, + { lng: 101.150033, lat: 21.849984 }, + { lng: 100.416538, lat: 21.558839 }, + { lng: 99.983489, lat: 21.742937 }, + { lng: 99.240899, lat: 22.118314 }, + { lng: 99.531992, lat: 22.949039 }, + { lng: 98.898749, lat: 23.142722 }, + { lng: 98.660262, lat: 24.063286 }, + { lng: 97.60472, lat: 23.897405 }, + { lng: 97.724609, lat: 25.083637 }, + { lng: 98.671838, lat: 25.918703 }, + { lng: 98.712094, lat: 26.743536 }, + { lng: 98.68269, lat: 27.508812 }, + { lng: 98.246231, lat: 27.747221 }, + { lng: 97.911988, lat: 28.335945 }, + { lng: 97.327114, lat: 28.261583 }, + { lng: 96.248833, lat: 28.411031 }, + { lng: 96.586591, lat: 28.83098 }, + { lng: 96.117679, lat: 29.452802 }, + { lng: 95.404802, lat: 29.031717 }, + { lng: 94.56599, lat: 29.277438 }, + { lng: 93.413348, lat: 28.640629 }, + { lng: 92.503119, lat: 27.896876 }, + { lng: 91.696657, lat: 27.771742 }, + { lng: 91.258854, lat: 28.040614 }, + { lng: 90.730514, lat: 28.064954 }, + { lng: 90.015829, lat: 28.296439 }, + { lng: 89.47581, lat: 28.042759 }, + { lng: 88.814248, lat: 27.299316 }, + { lng: 88.730326, lat: 28.086865 }, + { lng: 88.120441, lat: 27.876542 }, + { lng: 86.954517, lat: 27.974262 }, + { lng: 85.82332, lat: 28.203576 }, + { lng: 85.011638, lat: 28.642774 }, + { lng: 84.23458, lat: 28.839894 }, + { lng: 83.898993, lat: 29.320226 }, + { lng: 83.337115, lat: 29.463732 }, + { lng: 82.327513, lat: 30.115268 }, + { lng: 81.525804, lat: 30.422717 }, + { lng: 81.111256, lat: 30.183481 }, + { lng: 79.721367, lat: 30.882715 }, + { lng: 78.738894, lat: 31.515906 }, + { lng: 78.458446, lat: 32.618164 }, + { lng: 79.176129, lat: 32.48378 }, + { lng: 79.208892, lat: 32.994395 }, + { lng: 78.811086, lat: 33.506198 }, + { lng: 78.912269, lat: 34.321936 }, + { lng: 77.837451, lat: 35.49401 }, + { lng: 76.192848, lat: 35.898403 }, + { lng: 75.896897, lat: 36.666806 }, + { lng: 75.158028, lat: 37.133031 }, + { lng: 74.980002, lat: 37.41999 }, + { lng: 74.829986, lat: 37.990007 }, + { lng: 74.864816, lat: 38.378846 }, + { lng: 74.257514, lat: 38.606507 }, + { lng: 73.928852, lat: 38.505815 }, + { lng: 73.675379, lat: 39.431237 }, + { lng: 73.960013, lat: 39.660008 }, + { lng: 73.822244, lat: 39.893973 }, + { lng: 74.776862, lat: 40.366425 }, + { lng: 75.467828, lat: 40.562072 }, + { lng: 76.526368, lat: 40.427946 }, + { lng: 76.904484, lat: 41.066486 }, + { lng: 78.187197, lat: 41.185316 }, + { lng: 78.543661, lat: 41.582243 }, + { lng: 80.11943, lat: 42.123941 }, + { lng: 80.25999, lat: 42.349999 }, + { lng: 80.18015, lat: 42.920068 }, + { lng: 80.866206, lat: 43.180362 }, + { lng: 79.966106, lat: 44.917517 }, + { lng: 81.947071, lat: 45.317027 }, + { lng: 82.458926, lat: 45.53965 }, + { lng: 83.180484, lat: 47.330031 }, + { lng: 85.16429, lat: 47.000956 }, + { lng: 85.720484, lat: 47.452969 }, + { lng: 85.768233, lat: 48.455751 }, + { lng: 86.598776, lat: 48.549182 }, + { lng: 87.35997, lat: 49.214981 }, + { lng: 87.751264, lat: 49.297198 }, + { lng: 88.013832, lat: 48.599463 }, + { lng: 88.854298, lat: 48.069082 }, + { lng: 90.280826, lat: 47.693549 }, + { lng: 90.970809, lat: 46.888146 }, + { lng: 90.585768, lat: 45.719716 }, + { lng: 90.94554, lat: 45.286073 }, + { lng: 92.133891, lat: 45.115076 }, + { lng: 93.480734, lat: 44.975472 }, + { lng: 94.688929, lat: 44.352332 }, + { lng: 95.306875, lat: 44.241331 }, + { lng: 95.762455, lat: 43.319449 }, + { lng: 96.349396, lat: 42.725635 }, + { lng: 97.451757, lat: 42.74889 }, + { lng: 99.515817, lat: 42.524691 }, + { lng: 100.845866, lat: 42.663804 }, + { lng: 101.83304, lat: 42.514873 }, + { lng: 103.312278, lat: 41.907468 }, + { lng: 104.522282, lat: 41.908347 }, + { lng: 104.964994, lat: 41.59741 }, + { lng: 106.129316, lat: 42.134328 }, + { lng: 107.744773, lat: 42.481516 }, + { lng: 109.243596, lat: 42.519446 }, + { lng: 110.412103, lat: 42.871234 }, + { lng: 111.129682, lat: 43.406834 }, + { lng: 111.829588, lat: 43.743118 }, + { lng: 111.667737, lat: 44.073176 }, + { lng: 111.348377, lat: 44.457442 }, + { lng: 111.873306, lat: 45.102079 }, + { lng: 112.436062, lat: 45.011646 }, + { lng: 113.463907, lat: 44.808893 }, + { lng: 114.460332, lat: 45.339817 }, + { lng: 115.985096, lat: 45.727235 }, + { lng: 116.717868, lat: 46.388202 }, + { lng: 117.421701, lat: 46.672733 }, + { lng: 118.874326, lat: 46.805412 }, + { lng: 119.66327, lat: 46.69268 }, + { lng: 119.772824, lat: 47.048059 }, + { lng: 118.866574, lat: 47.74706 }, + { lng: 118.064143, lat: 48.06673 }, + { lng: 117.295507, lat: 47.697709 }, + { lng: 116.308953, lat: 47.85341 }, + { lng: 115.742837, lat: 47.726545 }, + { lng: 115.485282, lat: 48.135383 }, + { lng: 116.191802, lat: 49.134598 }, + { lng: 116.678801, lat: 49.888531 }, + { lng: 117.879244, lat: 49.510983 }, + { lng: 119.288461, lat: 50.142883 }, + { lng: 119.279366, lat: 50.582908 }, + { lng: 120.18205, lat: 51.643566 }, + { lng: 120.738191, lat: 51.964115 }, + { lng: 120.725789, lat: 52.516226 }, + { lng: 120.177089, lat: 52.753886 }, + { lng: 121.003085, lat: 53.251401 }, + { lng: 122.245748, lat: 53.431726 }, + { lng: 123.571507, lat: 53.458804 }, + { lng: 125.068211, lat: 53.161045 }, + { lng: 125.946349, lat: 52.792799 }, + { lng: 126.564399, lat: 51.784255 }, + { lng: 126.939157, lat: 51.353894 }, + { lng: 127.287456, lat: 50.739797 }, + { lng: 127.657407, lat: 49.76027 }, + ]; + + let pointlayer = new PointLayer() + .source( + d, + // [ + // {"lng":10,"lat":0}, + // {"lng":20,"lat":0}, + // {"lng":30,"lat":0}, + // {"lng":40,"lat":0}, + // {"lng":50,"lat":0}, + // {"lng":60,"lat":0}, + // {"lng":70,"lat":0}, + // {"lng":80,"lat":0}, + // {"lng":90,"lat":0}, + + // {"lng":100,"lat":0}, + // {"lng":110,"lat":0}, + // {"lng":120,"lat":0}, + // {"lng":130,"lat":0}, + // {"lng":140,"lat":0}, + // {"lng":150,"lat":0}, + // {"lng":160,"lat":0}, + // {"lng":170,"lat":0}, + // {"lng":180,"lat":0}, + + // {"lng":190,"lat":0}, + // {"lng":200,"lat":0}, + // {"lng":210,"lat":0}, + // {"lng":220,"lat":0}, + // {"lng":230,"lat":0}, + // {"lng":240,"lat":0}, + // {"lng":250,"lat":0}, + // {"lng":260,"lat":0}, + // {"lng":270,"lat":0}, + + // {"lng":280,"lat":0}, + // {"lng":290,"lat":0}, + // {"lng":300,"lat":0}, + // {"lng":310,"lat":0}, + // {"lng":320,"lat":0}, + // {"lng":330,"lat":0}, + // {"lng":340,"lat":0}, + // {"lng":350,"lat":0}, + // {"lng":360,"lat":0}, + + // {"lng":0,"lat":0}, + + // {"lng":0,"lat":10}, + // {"lng":0,"lat":20}, + // {"lng":0,"lat":30}, + // {"lng":0,"lat":40}, + // {"lng":0,"lat":50}, + // {"lng":0,"lat":60}, + // {"lng":0,"lat":70}, + // {"lng":0,"lat":80}, + // {"lng":0,"lat":90}, + + // {"lng":0,"lat":-10}, + // {"lng":0,"lat":-20}, + // {"lng":0,"lat":-30}, + // {"lng":0,"lat":-40}, + // {"lng":0,"lat":-50}, + // {"lng":0,"lat":-60}, + // {"lng":0,"lat":-70}, + // {"lng":0,"lat":-80}, + // {"lng":0,"lat":-90}, + // ], + { + parser: { + type: 'json', + x: 'lng', + y: 'lat', + }, + }, + ) + .shape('circle') + // .shape('cylinder') + .color('#f00') + .size(10) + .style({ + opacity: 0.6, + }) + .active(true); + + // scene.addLayer(pointlayer); + + // let pointlayer = new PointLayer() + // .source( + // [ + // { + // lng: 120, + // lat: 30, + // }, + // ], + // { + // parser: { + // type: 'json', + // x: 'lng', + // y: 'lat', + // }, + // }, + // ) + // .shape('circle') + // .color('rgba(255, 0, 0, 1.0)') + // .size(200); + + const earthlayer = new EarthLayer() + .source( + 'https://gw.alipayobjects.com/mdn/rms_23a451/afts/img/A*3-3NSpqRqUoAAAAAAAAAAAAAARQnAQ', + { + parser: { + type: 'image', + extent: [121.168, 30.2828, 121.384, 30.4219], + }, + }, + ) + .color('#2E8AE6') + .shape('fill') + .style({ + opacity: 1.0, + radius: 40, + globelOtions: { + ambientRatio: 0.6, // 环境光 + diffuseRatio: 0.4, // 漫反射 + specularRatio: 0.1, // 高光反射 + // earthTime: 4.0 + earthTime: 0.1, + }, + }) + .animate(true); + // earthlayer.setEarthTime(4.0) + scene.on('loaded', () => { + scene.addLayer(earthlayer); + scene.addLayer(pointlayer); + + earthlayer.setEarthTime(4.0); + }); + } + + public render() { + return ( +
+ ); + } +} diff --git a/stories/customMap/map.stories.tsx b/stories/customMap/map.stories.tsx index c0dc53945d..44be6aeb4a 100644 --- a/stories/customMap/map.stories.tsx +++ b/stories/customMap/map.stories.tsx @@ -3,6 +3,9 @@ import * as React from 'react'; import Map from './components/Map'; import Map2 from './components/Map2'; +import Earth from './components/earth' // @ts-ignore -storiesOf('自定义地图', module).add('地图', () => ) +storiesOf('自定义地图', module) +.add('Earth', () => ) +.add('地图', () => ) .add('地图2', () => );