mirror of https://gitee.com/antv-l7/antv-l7
feat(three.js layer): support rendering three.js meshes
This commit is contained in:
parent
bc22e02657
commit
e982ef89ba
|
@ -104,6 +104,7 @@
|
|||
"stylelint-config-standard": "^18.2.0",
|
||||
"stylelint-config-styled-components": "^0.1.1",
|
||||
"stylelint-processor-styled-components": "^1.3.2",
|
||||
"three": "^0.111.0",
|
||||
"ts-jest": "^24.0.2",
|
||||
"tslint": "^5.11.0",
|
||||
"tslint-config-prettier": "^1.15.0",
|
||||
|
|
|
@ -53,7 +53,6 @@ export default class LayerService implements ILayerService {
|
|||
|
||||
public renderLayers() {
|
||||
// TODO:脏检查,只渲染发生改变的 Layer
|
||||
//
|
||||
this.clear();
|
||||
this.layers
|
||||
.filter((layer) => layer.isVisible())
|
||||
|
|
|
@ -23,8 +23,6 @@ const bytesPerElementMap = {
|
|||
[gl.UNSIGNED_SHORT]: 2,
|
||||
};
|
||||
|
||||
let counter = 0;
|
||||
|
||||
/**
|
||||
* 每个 Layer 都拥有一个,用于管理样式属性的注册和更新
|
||||
*/
|
||||
|
@ -41,10 +39,6 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
|
||||
private attributes: IStyleAttribute[] = [];
|
||||
|
||||
private triangulation: Triangulation;
|
||||
|
||||
private c = counter++;
|
||||
|
||||
private featureLayout: {
|
||||
sizePerElement: number;
|
||||
elements: Array<{
|
||||
|
@ -177,16 +171,13 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
|
||||
public createAttributesAndIndices(
|
||||
features: IEncodeFeature[],
|
||||
triangulation?: Triangulation,
|
||||
triangulation: Triangulation,
|
||||
): {
|
||||
attributes: {
|
||||
[attributeName: string]: IAttribute;
|
||||
};
|
||||
elements: IElements;
|
||||
} {
|
||||
if (triangulation) {
|
||||
this.triangulation = triangulation;
|
||||
}
|
||||
const descriptors = this.attributes.map((attr) => {
|
||||
attr.resetDescriptor();
|
||||
return attr.descriptor;
|
||||
|
@ -204,7 +195,7 @@ export default class StyleAttributeService implements IStyleAttributeService {
|
|||
vertices: verticesForCurrentFeature,
|
||||
normals: normalsForCurrentFeature,
|
||||
size: vertexSize,
|
||||
} = this.triangulation(feature);
|
||||
} = triangulation(feature);
|
||||
indices.push(...indicesForCurrentFeature.map((i) => i + verticesNum));
|
||||
vertices.push(...verticesForCurrentFeature);
|
||||
if (normalsForCurrentFeature) {
|
||||
|
|
|
@ -11,6 +11,12 @@ export interface IPoint {
|
|||
y: number;
|
||||
}
|
||||
|
||||
export interface IMercator {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export interface IMapWrapper {
|
||||
setContainer(container: Container, id: string): void;
|
||||
}
|
||||
|
@ -58,6 +64,14 @@ export interface IMapService<RawMap = {}> {
|
|||
lngLatToPixel(lnglat: Point): IPoint;
|
||||
containerToLngLat(pixel: Point): ILngLat;
|
||||
lngLatToContainer(lnglat: Point): IPoint;
|
||||
lngLatToMercator(lnglat: [number, number], altitude: number): IMercator;
|
||||
getModelMatrix(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
rotate: [number, number, number],
|
||||
scale: [number, number, number],
|
||||
origin: IMercator,
|
||||
): number[];
|
||||
}
|
||||
|
||||
export const MapServiceEvent = ['mapload'];
|
||||
|
|
|
@ -54,6 +54,8 @@ export interface IRendererService {
|
|||
): void;
|
||||
getViewportSize(): { width: number; height: number };
|
||||
getContainer(): HTMLElement | null;
|
||||
getCanvas(): HTMLCanvasElement | null;
|
||||
getGLContext(): WebGLRenderingContext;
|
||||
viewport(size: { x: number; y: number; width: number; height: number }): void;
|
||||
readPixels(options: IReadPixelsOptions): Uint8Array;
|
||||
destroy(): void;
|
||||
|
|
|
@ -40,7 +40,8 @@
|
|||
"merge-json-schemas": "1.0.0",
|
||||
"polyline-miter-util": "^1.0.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"tapable": "^2.0.0-beta.8"
|
||||
"tapable": "^2.0.0-beta.8",
|
||||
"three": "^0.111.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/d3-array": "^2.0.0",
|
||||
|
|
|
@ -2,6 +2,8 @@ import {
|
|||
gl,
|
||||
IActiveOption,
|
||||
IAnimateOption,
|
||||
ICameraService,
|
||||
ICoordinateSystemService,
|
||||
IDataState,
|
||||
IEncodeFeature,
|
||||
IFontService,
|
||||
|
@ -108,6 +110,10 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
@lazyInject(TYPES.IShaderModuleService)
|
||||
protected readonly shaderModuleService: IShaderModuleService;
|
||||
|
||||
protected cameraService: ICameraService;
|
||||
|
||||
protected coordinateService: ICoordinateSystemService;
|
||||
|
||||
protected iconService: IIconService;
|
||||
|
||||
protected fontService: IFontService;
|
||||
|
@ -229,6 +235,12 @@ export default class BaseLayer<ChildLayerStyleOptions = {}> extends EventEmitter
|
|||
TYPES.IInteractionService,
|
||||
);
|
||||
this.mapService = this.container.get<IMapService>(TYPES.IMapService);
|
||||
this.cameraService = this.container.get<ICameraService>(
|
||||
TYPES.ICameraService,
|
||||
);
|
||||
this.coordinateService = this.container.get<ICoordinateSystemService>(
|
||||
TYPES.ICoordinateSystemService,
|
||||
);
|
||||
this.postProcessingPassFactory = this.container.get(
|
||||
TYPES.IFactoryPostProcessingPass,
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ import PointLayer from './point';
|
|||
import PolygonLayer from './polygon';
|
||||
import ImageLayer from './raster/image';
|
||||
import RasterLayer from './raster/raster';
|
||||
import ThreeJSLayer from './three';
|
||||
|
||||
import ConfigSchemaValidationPlugin from './plugins/ConfigSchemaValidationPlugin';
|
||||
import DataMappingPlugin from './plugins/DataMappingPlugin';
|
||||
|
@ -111,4 +112,5 @@ export {
|
|||
ImageLayer,
|
||||
RasterLayer,
|
||||
HeatmapLayer,
|
||||
ThreeJSLayer,
|
||||
};
|
||||
|
|
|
@ -64,7 +64,7 @@ export default class FeatureScalePlugin implements ILayerPlugin {
|
|||
this.caculateScalesForAttributes(attributes || [], dataArray);
|
||||
});
|
||||
|
||||
// 检测数据是不否需要更新
|
||||
// 检测数据是否需要更新
|
||||
layer.hooks.beforeRenderData.tap('FeatureScalePlugin', (flag) => {
|
||||
if (flag) {
|
||||
this.scaleOptions = layer.getScaleOptions();
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* inspired by threebox & Mapbox examples
|
||||
* @see https://github.com/peterqliu/threebox/blob/master/src/Threebox.js
|
||||
* @see https://github.com/peterqliu/threebox/blob/master/examples/Object3D.html
|
||||
*/
|
||||
import { IMercator } from '@antv/l7-core';
|
||||
import { Camera, Matrix4, Scene, WebGLRenderer } from 'three';
|
||||
import BaseLayer from '../core/BaseLayer';
|
||||
|
||||
export default class ThreeJSLayer extends BaseLayer<{
|
||||
onAddMeshes: (threeScene: Scene, layer: ThreeJSLayer) => void;
|
||||
}> {
|
||||
public name: string = 'ThreeJSLayer';
|
||||
|
||||
private scene: Scene;
|
||||
private camera: Camera;
|
||||
private renderer: WebGLRenderer;
|
||||
|
||||
// 地图中点墨卡托坐标
|
||||
private center: IMercator;
|
||||
|
||||
// 初始状态相机变换矩阵
|
||||
private cameraTransform: Matrix4;
|
||||
|
||||
/**
|
||||
* 根据模型
|
||||
*/
|
||||
public getModelMatrix(
|
||||
lnglat: [number, number],
|
||||
altitude: number = 0,
|
||||
rotation: [number, number, number] = [0, 0, 0],
|
||||
scale: [number, number, number] = [1, 1, 1],
|
||||
): Matrix4 {
|
||||
return new Matrix4().fromArray(
|
||||
this.mapService.getModelMatrix(
|
||||
lnglat,
|
||||
altitude,
|
||||
rotation,
|
||||
scale,
|
||||
this.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected getConfigSchema() {
|
||||
return {
|
||||
properties: {
|
||||
// opacity: {
|
||||
// type: 'altitude',
|
||||
// minimum: 0,
|
||||
// maximum: 100,
|
||||
// },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected buildModels() {
|
||||
const canvas = this.rendererService.getCanvas();
|
||||
const gl = this.rendererService.getGLContext();
|
||||
if (canvas && gl) {
|
||||
const center = this.mapService.getCenter();
|
||||
this.center = this.mapService.lngLatToMercator(
|
||||
[center.lng, center.lat],
|
||||
0,
|
||||
);
|
||||
const { x, y, z } = this.center;
|
||||
this.cameraTransform = new Matrix4().makeTranslation(x, y, z);
|
||||
|
||||
this.renderer = new WebGLRenderer({
|
||||
canvas,
|
||||
context: gl,
|
||||
antialias: true,
|
||||
});
|
||||
|
||||
// L7 负责 clear
|
||||
this.renderer.autoClear = false;
|
||||
// 是否需要 gamma correction?
|
||||
this.renderer.gammaOutput = true;
|
||||
this.renderer.gammaFactor = 2.2;
|
||||
|
||||
this.scene = new Scene();
|
||||
// 后续同步 L7 相机
|
||||
this.camera = new Camera();
|
||||
|
||||
const config = this.getLayerConfig();
|
||||
if (config && config.onAddMeshes) {
|
||||
config.onAddMeshes(this.scene, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected renderModels() {
|
||||
const { width, height } = this.rendererService.getViewportSize();
|
||||
this.renderer.setSize(width, height, false);
|
||||
|
||||
const gl = this.rendererService.getGLContext();
|
||||
gl.frontFace(gl.CCW);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
gl.cullFace(gl.FRONT);
|
||||
|
||||
// 同步相机
|
||||
const mercatorMatrix = new Matrix4().fromArray(
|
||||
// @ts-ignore
|
||||
this.mapService.map.transform.customLayerMatrix(),
|
||||
);
|
||||
|
||||
this.camera.projectionMatrix = mercatorMatrix.multiply(
|
||||
this.cameraTransform,
|
||||
);
|
||||
|
||||
this.renderer.state.reset();
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
}
|
||||
}
|
|
@ -8,12 +8,14 @@ import {
|
|||
ILngLat,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IMercator,
|
||||
IPoint,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { mat4, vec2, vec3 } from 'gl-matrix';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { IAMapEvent, IAMapInstance } from '../../typings/index';
|
||||
import { MapTheme } from './theme';
|
||||
|
@ -208,6 +210,27 @@ export default class AMapService
|
|||
};
|
||||
}
|
||||
|
||||
public lngLatToMercator(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
): IMercator {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
};
|
||||
}
|
||||
|
||||
public getModelMatrix(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
rotate: [number, number, number],
|
||||
scale: [number, number, number],
|
||||
origin: IMercator,
|
||||
): number[] {
|
||||
return (mat4.create() as unknown) as number[];
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
const {
|
||||
id,
|
||||
|
|
|
@ -8,23 +8,26 @@ import {
|
|||
ILngLat,
|
||||
IMapConfig,
|
||||
IMapService,
|
||||
IMercator,
|
||||
IPoint,
|
||||
IViewport,
|
||||
MapServiceEvent,
|
||||
TYPES,
|
||||
} from '@antv/l7-core';
|
||||
import { DOM } from '@antv/l7-utils';
|
||||
import { mat4, vec2, vec3 } from 'gl-matrix';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import mapboxgl, { IControl, Map } from 'mapbox-gl';
|
||||
import { IMapboxInstance } from '../../typings/index';
|
||||
import { MapTheme } from './theme';
|
||||
import Viewport from './Viewport';
|
||||
|
||||
const EventMap: {
|
||||
[key: string]: any;
|
||||
} = {
|
||||
mapmove: 'move',
|
||||
camerachange: 'move',
|
||||
};
|
||||
import { MapTheme } from './theme';
|
||||
|
||||
const LNGLAT_OFFSET_ZOOM_THRESHOLD = 12;
|
||||
|
||||
|
@ -172,6 +175,55 @@ export default class MapboxService
|
|||
return this.map.project(lnglat);
|
||||
}
|
||||
|
||||
public lngLatToMercator(
|
||||
lnglat: [number, number],
|
||||
altitude: number,
|
||||
): IMercator {
|
||||
const { x = 0, y = 0, z = 0 } = mapboxgl.MercatorCoordinate.fromLngLat(
|
||||
lnglat,
|
||||
altitude,
|
||||
);
|
||||
return { x, y, z };
|
||||
}
|
||||
|
||||
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[] {
|
||||
const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
|
||||
lnglat,
|
||||
altitude,
|
||||
);
|
||||
// @ts-ignore
|
||||
const meters = modelAsMercatorCoordinate.meterInMercatorCoordinateUnits();
|
||||
const modelMatrix = mat4.create();
|
||||
|
||||
mat4.translate(
|
||||
modelMatrix,
|
||||
modelMatrix,
|
||||
vec3.fromValues(
|
||||
modelAsMercatorCoordinate.x - origin.x,
|
||||
modelAsMercatorCoordinate.y - origin.y,
|
||||
modelAsMercatorCoordinate.z || 0 - origin.z,
|
||||
),
|
||||
);
|
||||
|
||||
mat4.scale(
|
||||
modelMatrix,
|
||||
modelMatrix,
|
||||
vec3.fromValues(meters * scale[0], -meters * scale[1], meters * scale[2]),
|
||||
);
|
||||
|
||||
mat4.rotateX(modelMatrix, modelMatrix, rotate[0]);
|
||||
mat4.rotateY(modelMatrix, modelMatrix, rotate[1]);
|
||||
mat4.rotateZ(modelMatrix, modelMatrix, rotate[2]);
|
||||
|
||||
return (modelMatrix as unknown) as number[];
|
||||
}
|
||||
|
||||
public async init(): Promise<void> {
|
||||
const {
|
||||
id = 'map',
|
||||
|
|
|
@ -167,6 +167,14 @@ export default class ReglRendererService implements IRendererService {
|
|||
return this.$container;
|
||||
};
|
||||
|
||||
public getCanvas = () => {
|
||||
return this.$container?.getElementsByTagName('canvas')[0] || null;
|
||||
};
|
||||
|
||||
public getGLContext = () => {
|
||||
return this.gl._gl;
|
||||
};
|
||||
|
||||
public destroy = () => {
|
||||
// @see https://github.com/regl-project/regl/blob/gh-pages/API.md#clean-up
|
||||
this.gl.destroy();
|
||||
|
|
|
@ -3,7 +3,9 @@ import * as React from 'react';
|
|||
import Arc2DLineDemo from './components/Arc2DLine';
|
||||
import ArcLineDemo from './components/Arcline';
|
||||
import Column from './components/column';
|
||||
import CustomThreeJSDemo from './components/CustomThreeJSLayer';
|
||||
import DataUpdate from './components/data_update';
|
||||
import GlTFThreeJSDemo from './components/GlTFThreeJSDemo';
|
||||
import HeatMapDemo from './components/HeatMap';
|
||||
import LineLayer from './components/Line';
|
||||
import PointDemo from './components/Point';
|
||||
|
@ -26,4 +28,6 @@ storiesOf('图层', module)
|
|||
.add('2D弧线', () => <Arc2DLineDemo />)
|
||||
.add('热力图', () => <HeatMapDemo />)
|
||||
.add('栅格', () => <RasterLayerDemo />)
|
||||
.add('图片', () => <ImageLayerDemo />);
|
||||
.add('图片', () => <ImageLayerDemo />)
|
||||
.add('Three.js 图层', () => <CustomThreeJSDemo />)
|
||||
.add('glTF 图层', () => <GlTFThreeJSDemo />);
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import { PointLayer, Scene, ThreeJSLayer } from '@antv/l7';
|
||||
import { GaodeMap, Mapbox } from '@antv/l7-maps';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
BackSide,
|
||||
BoxGeometry,
|
||||
DirectionalLight,
|
||||
Mesh,
|
||||
MeshLambertMaterial,
|
||||
Scene as ThreeScene,
|
||||
} from 'three';
|
||||
// @ts-ignore
|
||||
import data from '../data/data.json';
|
||||
|
||||
export default class ThreeJSLayerComponent extends React.Component {
|
||||
// @ts-ignore
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/basement_prod/d3564b06-670f-46ea-8edb-842f7010a7c6.json',
|
||||
);
|
||||
const pointsData = await response.json();
|
||||
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new Mapbox({
|
||||
center: [120.19382669582967, 30.258134],
|
||||
pitch: 60,
|
||||
rotation: 30,
|
||||
zoom: 16,
|
||||
}),
|
||||
});
|
||||
this.scene = scene;
|
||||
|
||||
// const pointLayer = new PointLayer({})
|
||||
// .source(pointsData, {
|
||||
// cluster: true,
|
||||
// })
|
||||
// .shape('circle')
|
||||
// .scale('point_count', {
|
||||
// type: 'quantile',
|
||||
// })
|
||||
// .size('point_count', [5, 10, 15, 20, 25])
|
||||
// .color('red')
|
||||
// .style({
|
||||
// opacity: 0.3,
|
||||
// strokeWidth: 1,
|
||||
// });
|
||||
// scene.addLayer(pointLayer);
|
||||
|
||||
const threeJSLayer = new ThreeJSLayer({
|
||||
enableMultiPassRenderer: false,
|
||||
onAddMeshes: (threeScene: ThreeScene, layer: ThreeJSLayer) => {
|
||||
// 添加光源
|
||||
const directionalLight1 = new DirectionalLight(0xffffff);
|
||||
directionalLight1.position.set(0, -70, 100).normalize();
|
||||
threeScene.add(directionalLight1);
|
||||
const directionalLight2 = new DirectionalLight(0xffffff);
|
||||
directionalLight2.position.set(0, 70, 100).normalize();
|
||||
threeScene.add(directionalLight2);
|
||||
|
||||
const geometry = new BoxGeometry(20, 20, 20);
|
||||
const redMaterial = new MeshLambertMaterial({
|
||||
color: 0xffffff,
|
||||
side: BackSide,
|
||||
});
|
||||
const cube = new Mesh(geometry, redMaterial);
|
||||
cube.applyMatrix(
|
||||
layer.getModelMatrix([120.19382669582967, 30.258134], 10, [0, 0, 0]),
|
||||
);
|
||||
cube.frustumCulled = false;
|
||||
threeScene.add(cube);
|
||||
},
|
||||
}).source(pointsData);
|
||||
scene.addLayer(threeJSLayer);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import { Scene, ThreeJSLayer } from '@antv/l7';
|
||||
import { Mapbox } from '@antv/l7-maps';
|
||||
import * as React from 'react';
|
||||
import { DirectionalLight, Scene as ThreeScene } from 'three';
|
||||
// tslint:disable-next-line:no-submodule-imports
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
|
||||
export default class GlTFThreeJSDemo extends React.Component {
|
||||
// @ts-ignore
|
||||
private scene: Scene;
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.scene.destroy();
|
||||
}
|
||||
|
||||
public async componentDidMount() {
|
||||
const response = await fetch(
|
||||
'https://gw.alipayobjects.com/os/basement_prod/893d1d5f-11d9-45f3-8322-ee9140d288ae.json',
|
||||
);
|
||||
const pointsData = await response.json();
|
||||
|
||||
const scene = new Scene({
|
||||
id: 'map',
|
||||
map: new Mapbox({
|
||||
center: [121.434765, 31.256735],
|
||||
pitch: 45,
|
||||
rotation: 30,
|
||||
zoom: 16,
|
||||
}),
|
||||
});
|
||||
this.scene = scene;
|
||||
|
||||
const threeJSLayer = new ThreeJSLayer({
|
||||
enableMultiPassRenderer: false,
|
||||
onAddMeshes: (threeScene: ThreeScene, layer: ThreeJSLayer) => {
|
||||
// 添加光源
|
||||
const directionalLight1 = new DirectionalLight(0xffffff);
|
||||
directionalLight1.position.set(0, -70, 100).normalize();
|
||||
threeScene.add(directionalLight1);
|
||||
const directionalLight2 = new DirectionalLight(0xffffff);
|
||||
directionalLight2.position.set(0, 70, 100).normalize();
|
||||
threeScene.add(directionalLight2);
|
||||
|
||||
// 使用 Three.js glTFLoader 加载模型
|
||||
const loader = new GLTFLoader();
|
||||
loader.load(
|
||||
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf',
|
||||
// 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/AnimatedCube/glTF/AnimatedCube.gltf',
|
||||
'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
|
||||
(gltf) => {
|
||||
// 根据 GeoJSON 数据放置模型
|
||||
layer.getSource().data.dataArray.forEach(({ coordinates }) => {
|
||||
const gltfScene = gltf.scene.clone();
|
||||
gltfScene.applyMatrix(
|
||||
// 生成模型矩阵
|
||||
layer.getModelMatrix(
|
||||
[coordinates[0], coordinates[1]], // 经纬度坐标
|
||||
0, // 高度,单位米
|
||||
[Math.PI / 2, 0, 0], // 沿 XYZ 轴旋转角度
|
||||
[5, 5, 5], // 沿 XYZ 轴缩放比例
|
||||
),
|
||||
);
|
||||
// 向场景中添加模型
|
||||
threeScene.add(gltfScene);
|
||||
});
|
||||
// 重绘图层
|
||||
layer.render();
|
||||
},
|
||||
);
|
||||
},
|
||||
}).source(pointsData, {
|
||||
parser: {
|
||||
type: 'json',
|
||||
x: 'longitude',
|
||||
y: 'latitude',
|
||||
},
|
||||
});
|
||||
scene.addLayer(threeJSLayer);
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div
|
||||
id="map"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue