Maplibre GL JS 中文文档Maplibre GL JS 中文文档
介绍
插件
样式规范
API
案例
指南
介绍
插件
样式规范
API
案例
指南
  • 介绍
  • 插件
  • 样式规范
    • 弃用功能
    • 表达式
    • 字形
    • 图层
    • 光照
    • 投影
    • 根属性
    • 天空
    • 数据源
    • 精灵图
    • 状态
    • 地形
    • 过渡
    • 类型
  • API
    • media

      • 为 MapLibre GL JS 做贡献
    • Classes

      • AJAXError
      • Actor
      • AlphaImage
      • AttributionControl
      • BoxZoomHandler
      • CanonicalTileID
      • CanvasSource
      • CircleStyleLayer
      • ClickZoomHandler
      • CooperativeGesturesHandler
      • DEMData
      • Dispatcher
      • DoubleClickZoomHandler
      • DragPanHandler
      • DragRotateHandler
      • EdgeInsets
      • ErrorEvent
      • Event
      • Evented
      • FeatureIndex
      • FullscreenControl
      • GeoJSONFeature
      • GeoJSONSource
      • GlobeControl
      • Hash
      • HeatmapStyleLayer
      • ImageAtlas
      • ImageManager
      • ImageSource
      • KeyboardHandler
      • Layout<Props>
      • LngLat
      • LngLatBounds
      • LogoControl
      • Map
      • MapMouseEvent
      • MapTouchEvent
      • MapWheelEvent
      • Marker
      • MercatorCoordinate
      • NavigationControl
      • OverscaledTileID
      • Popup
      • RGBAImage
      • RasterDEMTileSource
      • RasterTileSource
      • ScaleControl
      • ScrollZoomHandler
      • Style
      • abstract StyleLayer
      • SubdivisionGranularityExpression
      • SubdivisionGranularitySetting
      • TapDragZoomHandler
      • TapZoomHandler
      • TerrainControl
      • ThrottledInvoker
      • Tile
      • TouchPanHandler
      • 抽象 TwoFingersTouchHandler
      • TwoFingersTouchPitchHandler
      • TwoFingersTouchRotateHandler
      • TwoFingersTouchZoomHandler
      • TwoFingersTouchZoomRotateHandler
      • VectorTileSource
      • VideoSource
      • WorkerPool
    • Enumerations

      • MessageType
      • ResourceType
      • TextFit
    • Functions

      • addProtocol()
      • addSourceType()
      • clearPrewarmedResources()
      • createTileMesh()
      • getMaxParallelImageRequests()
      • getRTLTextPluginStatus()
      • getVersion()
      • getWorkerCount()
      • getWorkerUrl()
      • importScriptInWorkers()
      • prewarm()
      • removeProtocol()
      • setMaxParallelImageRequests()
      • setWorkerCount()
      • setWorkerUrl()
    • Interfaces

      • ActorTarget
      • AttributeBinder
      • Bucket
      • CustomLayerInterface
      • Handler
      • IActor
      • IControl
      • MousePanHandler
      • MousePitchHandler
      • MouseRollHandler
      • MouseRotateHandler
      • Projection
      • Source
      • StyleImageInterface
      • Subscription
    • Type aliases

      • ActorMessage<T>
      • AddLayerObject
      • AddProtocolAction
      • Alignment
      • AnimationOptions
      • AroundCenterOptions
      • AttributionControlOptions
      • CalculateTileZoomFunction
      • CameraForBoundsOptions
      • CameraOptions
      • CameraUpdateTransformFunction()
      • CanvasSourceSpecification
      • CenterZoomBearing
      • CircleGranularity
      • ClusterIDAndSource
      • Complete<T>
      • Config
      • ControlPosition
      • Coordinates
      • CreateTileMeshOptions
      • CrossFaded<T>
      • CustomRenderMethod()
      • CustomRenderMethodInput
      • DEMEncoding
      • DashEntry
      • DistributiveKeys<T>
      • DistributiveOmit<T, K>
      • DragPanOptions
      • DragRotateHandlerOptions
      • EaseToOptions
      • ExpiryData
      • FeatureIdentifier
      • FitBoundsOptions
      • FlyToOptions
      • FullscreenControlOptions
      • GeoJSONFeatureDiff
      • GeoJSONFeatureId
      • GeoJSONSourceDiff
      • GeoJSONSourceOptions
      • GeoJSONWorkerOptions
      • GeoJSONWorkerSourceLoadDataResult
      • GeolocateControlOptions
      • GestureOptions
      • GetClusterLeavesParams
      • GetGlyphsParameters
      • GetGlyphsResponse
      • GetImagesParameters
      • GetImagesResponse
      • GetResourceResponse<T>
      • GlyphPosition
      • GlyphMetrics
      • GridKey
      • HandlerResult
      • IndicesType
      • JumpToOptions
      • Listener()
      • LngLatBoundsLike
      • LngLatLike
      • LoadGeoJSONParameters
      • LogoControlOptions
      • MapContextEvent
      • MapDataEvent
      • MapEventType
      • MapGeoJSONFeature
      • MapLayerEventType
      • MapLayerMouseEvent
      • MapLayerTouchEvent
      • MapLibreEvent
      • MapLibreZoomEvent
      • MapOptions
      • MapProjectionEvent
      • MapSourceDataType
      • MapSourceDataEvent
      • MapStyleDataEvent
      • MapStyleImageMissingEvent
      • MapTerrainEvent
      • MarkerOptions
      • MessageData
      • NavigationControlOptions
      • /api/type-aliases/Offset.html
      • OverlapMode
      • PaddingOptions
      • PluginState
      • PointLike
      • PointProjection
      • PopupOptions
      • PositionAnchor
      • PossiblyEvaluatedValue<T>
      • ProjectionData
      • ProjectionDataParams
      • QueryRenderedFeaturesOptions
      • QuerySourceFeatureOptions
      • RTLPluginStatus
      • Rect
      • RemoveSourceParams
      • RequestParameters
      • RequestResponseMessageMap
      • RequestTransformFunction()
      • RequireAtLeastOne<T>
      • ScaleControlOptions
      • Serialized
      • SerializedObject<S>
      • SerializedStructArray
      • SetClusterOptions
      • SourceClass()
      • SpriteOnDemandStyleImage
      • StyleGlyph
      • StyleImage
      • StyleImageData
      • StyleImageMetadata
      • StyleOptions
      • StyleSetterOptions
      • StyleSwapOptions
      • SymbolQuad
      • TileMesh
      • TileParameters
      • TileState
      • TransformStyleFunction()
      • Unit
      • UpdateImageOptions
      • UpdateLayersParamaeters
      • WorkerDEMTileParameters
      • WorkerTileParameters
      • WorkerTileResult
  • 案例
    • 3D建筑显示
    • 3D室内地图多边形挤出
    • 3D地形
    • 使用babylon.js添加3D模型
    • 使用three.js添加带阴影的3D模型
    • 在地形上使用three.js添加3D模型
    • 使用three.js添加3D模型
    • 添加默认标记
    • 添加云优化地理TIFF (COG)
    • 使用REST API添加DeckGL图层
    • 添加动画图像
    • 添加生成的图像
    • 动态生成并添加缺失图标到地图
    • 向地图添加可拉伸图片
    • 向地图添加图标
    • 动画线条
    • 绕点相机动画
    • 图像序列动画
    • 标记动画
    • 点动画
    • 沿路径点动画
    • 符号跟随鼠标动画
    • 更改版权信息位置
    • 自定义相机动画
    • 添加Canvas数据源
    • 使地图在点击符号时居中
    • 设置地面以上的中心点
    • 根据缩放级别更改建筑物颜色
    • 更改标签大小写
    • 检查是否支持WebGL
    • 使用自定义属性显示HTML集群
    • 创建和样式化集群
    • 使用按钮更改图层颜色
    • 添加等高线
    • 合作手势
    • 使用自定义标记图标
    • 添加自定义样式图层
    • 使用数据驱动属性设置线条样式
    • 禁用地图旋转
    • 禁用滚动缩放
    • 显示和样式丰富文本标签
    • 创建可拖动标记
    • 创建可拖动点
    • 绘制一个圆形
    • 使用备用图像
    • 使用addProtocol转换特性属性
    • 向多边形添加图案
    • 通过文本输入过滤符号
    • 通过切换列表过滤符号
    • 在图层内过滤
    • 将地图适配到边界框
    • 缓慢飞行到位置
    • 飞行到位置
    • 查看全屏地图
    • 使用游戏控制导航地图
    • 在标签下方添加新图层
    • 添加GeoJSON线条
    • 添加自定义标记
    • 添加GeoJSON多边形
    • 在地球仪上加载3D模型
    • 地球仪大气层
    • 简单自定义地球仪
    • 自定义瓦片地球仪
    • 在地球仪上显示填充挤出层
    • 使用矢量地图显示地球仪
    • 地球仪上缩放和行星大小的关系
    • 哈希路由
    • 地球仪上带地形高程的热力图
    • 创建热力图图层
    • 添加多方向山体阴影图层
    • 添加山体阴影图层
    • 创建悬停效果
    • 显示带地形高程的混合卫星地图
    • 显示非交互式地图
    • 跳转到一系列位置
    • 切换地图语言
    • 显示跨越180度子午线的线条
    • 使用表达式创建渐变线条
    • 添加实时数据
    • 实时更新地图要素
    • 查看本地GeoJSON文件(实验性)
    • 查看本地GeoJSON文件
    • 使用本地生成的表意文字
    • 定位用户位置
    • 细节层次控制
    • 添加栅格瓦片源
    • 使用Mapbox GL Draw绘制多边形
    • 支持从右到左文本
    • 使用 Terra-Draw 绘制几何图形
    • 测量距离
    • 获取鼠标指针坐标
    • 从单个GeoJSON源添加多个几何图形
    • 显示地图导航控件
    • 使用内边距偏移消失点
    • PMTiles 源和协议
    • 点击多边形显示信息
    • 点击显示弹出窗口
    • 悬停显示弹出窗口
    • 显示弹出窗口
    • 获取鼠标指针下的要素
    • 渲染世界副本
    • 限制地图平移到某个区域
    • 显示卫星地图
    • 基于滚动位置飞行到特定位置
    • 设置俯仰角和方位角
    • 为标记添加弹出窗口
    • 显示一个地图
    • 天空、雾和地形
    • 同步多个地图的移动
    • 创建时间轴滑块
    • 切换deck.gl图层
    • 切换交互功能
    • 可变标签放置
    • 可变偏移标签放置
    • 添加矢量瓦片源
    • 在地图上播放视频
    • 可视化人口密度
    • 添加WMS服务
    • 缩放至线要素
  • 指南
    • 优化MapLibre性能: 大型GeoJSON数据集的技巧
    • Leaflet迁移指南
    • Mapbox迁移指南
    • OpenLayers迁移指南

在地形上使用three.js添加3D模型

使用自定义样式图层和three.js将3D模型添加到带有地形的地图上;

<!DOCTYPE html>
<html lang="en">

<head>
    <title>在地形上使用three.js添加3D模型</title>
    <meta property="og:description" content="使用自定义样式图层和three.js将3D模型添加到带有地形的地图上" />
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel='stylesheet' href='https://unpkg.com/maplibre-gl@5.5.0/dist/maplibre-gl.css' />
    <script src='https://unpkg.com/maplibre-gl@5.5.0/dist/maplibre-gl.js'></script>
    <style>
        body {
            margin: 0;
            padding: 0;
        }

        html,
        body,
        #map {
            height: 100%;
        }
    </style>
    <script type="importmap">
        {
            "imports": {
            "three": "https://cdn.jsdelivr.net/npm/three@0.169.0/build/three.module.js",
            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.169.0/examples/jsm/"
            }
        }
    </script>
</head>
<body>
<div id="map"></div>

<script type="module">
    import * as THREE from 'three';
    import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

        /**
         * 目标:
         * 给定两个已知的世界位置 `model1Location` 和 `model2Location`,
         * 在这些位置放置两个three.js对象,并位于地形的适当高度。
         */

        async function main() {

            const map = new maplibregl.Map({
                container: 'map',
                center: [11.5257, 47.668],
                zoom: 16.27,
                pitch: 60,
                bearing: -28.5,
                canvasContextAttributes: {antialias: true},
                style: {
                    version: 8,
                    layers: [
                        {
                            id: 'baseColor', // 隐藏地形瓦片的边缘,这些边缘有"墙"向下延伸到0。
                            type: 'background',
                            paint: {
                                'background-color': '#fff',
                                'background-opacity': 1.0,
                            },
                        }, {
                            id: 'hills',
                            type: 'hillshade',
                            source: 'hillshadeSource',
                            layout: {visibility: 'visible'},
                            paint: {'hillshade-shadow-color': '#473B24'}
                        }
                    ],
                    terrain: {
                        source: 'terrainSource',
                        exaggeration: 1,
                    },
                    sources: {
                        terrainSource: {
                            type: 'raster-dem',
                            url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
                            tileSize: 256
                        },
                        hillshadeSource: {
                            type: 'raster-dem',
                            url: 'https://demotiles.maplibre.org/terrain-tiles/tiles.json',
                            tileSize: 256
                        }
                    },
                }
            });

            /*
            * 帮助函数,用于从墨卡托坐标获取threejs场景坐标。
            * 这只是一个快速且简单的解决方案 - 如果点相距较远,它将无法正常工作,
            * 因为靠近北极的一米覆盖的墨卡托单位比赤道附近的一米多。
            */
            function calculateDistanceMercatorToMeters(from, to) {
                const mercatorPerMeter = from.meterInMercatorCoordinateUnits();
                // 墨卡托 x: 0=西, 1=东
                const dEast = to.x - from.x;
                const dEastMeter = dEast / mercatorPerMeter;
                // 墨卡托 y: 0=北, 1=南
                const dNorth = from.y - to.y;
                const dNorthMeter = dNorth / mercatorPerMeter;
                return {dEastMeter, dNorthMeter};
            }

            async function loadModel() {
                const loader = new GLTFLoader();
                const gltf = await loader.loadAsync('https://maplibre.org/maplibre-gl-js/docs/assets/34M_17/34M_17.gltf');
                const model = gltf.scene;
                return model;
            }

            // 已知位置。一旦地形加载完成,我们将推断这些位置的海拔高度。
            const sceneOrigin = new maplibregl.LngLat(11.5255, 47.6677);
            const model1Location = new maplibregl.LngLat(11.527, 47.6678);
            const model2Location = new maplibregl.LngLat(11.5249, 47.6676);

            // 3D模型的自定义图层配置,实现 `CustomLayerInterface`。
            const customLayer = {
                id: '3d-model',
                type: 'custom',
                renderingMode: '3d',

                onAdd(map, gl) {
                    /**
                     * 设置three.js场景。
                     * 我们放置model1和model2的方式使整个场景能够适应地形。
                     */

                    this.camera = new THREE.Camera();
                    this.scene = new THREE.Scene();
                    // 在threejs中,y指向上方 - 我们旋转场景使其y指向与maplibre的上方对齐。
                    this.scene.rotateX(Math.PI / 2);
                    // 在threejs中,z指向观察者 - 我们镜像它使z指向maplibre的北方。
                    this.scene.scale.multiply(new THREE.Vector3(1, 1, -1));
                    // 现在我们有一个场景,其坐标系为(x=东, y=上, z=北)

                    const light = new THREE.DirectionalLight(0xffffff);
                    // 接近中午时分 - 光线从东南方向照射。
                    light.position.set(50, 70, -30).normalize();
                    this.scene.add(light);

                    // 坐标轴辅助工具,显示threejs场景的方向。
                    const axesHelper = new THREE.AxesHelper(60);
                    this.scene.add(axesHelper);

                    // 获取模型海拔高度(米,高于海平面)
                    const sceneElevation = map.queryTerrainElevation(sceneOrigin) || 0;
                    const model1Elevation = map.queryTerrainElevation(model1Location) || 0;
                    const model2Elevation = map.queryTerrainElevation(model2Location) || 0;
                    const model1up = model1Elevation - sceneElevation;
                    const model2up = model2Elevation - sceneElevation;

                    // 获取模型相对于场景原点的x和y(单位为米)。
                    const sceneOriginMercator = maplibregl.MercatorCoordinate.fromLngLat(sceneOrigin);
                    const model1Mercator = maplibregl.MercatorCoordinate.fromLngLat(model1Location);
                    const model2Mercator = maplibregl.MercatorCoordinate.fromLngLat(model2Location);
                    const {dEastMeter: model1east, dNorthMeter: model1north} = calculateDistanceMercatorToMeters(sceneOriginMercator, model1Mercator);
                    const {dEastMeter: model2east, dNorthMeter: model2north} = calculateDistanceMercatorToMeters(sceneOriginMercator, model2Mercator);

                    model1.position.set(model1east, model1up, model1north);
                    model2.position.set(model2east, model2up, model2north);

                    this.scene.add(model1);
                    this.scene.add(model2);

                    // 使用MapLibre GL JS地图画布作为three.js的渲染目标。
                    this.renderer = new THREE.WebGLRenderer({
                        canvas: map.getCanvas(),
                        context: gl,
                        antialias: true
                    });

                    this.renderer.autoClear = false;
                },

                render(gl, args) {
                    const offsetFromCenterElevation = map.queryTerrainElevation(sceneOrigin) || 0;
                    const sceneOriginMercator = maplibregl.MercatorCoordinate.fromLngLat(sceneOrigin, offsetFromCenterElevation);

                    const sceneTransform = {
                        translateX: sceneOriginMercator.x,
                        translateY: sceneOriginMercator.y,
                        translateZ: sceneOriginMercator.z,
                        scale: sceneOriginMercator.meterInMercatorCoordinateUnits()
                    };

                    const m = new THREE.Matrix4().fromArray(args.defaultProjectionData.mainMatrix);
                    const l = new THREE.Matrix4()
                        .makeTranslation(sceneTransform.translateX, sceneTransform.translateY, sceneTransform.translateZ)
                        .scale(new THREE.Vector3(sceneTransform.scale, -sceneTransform.scale, sceneTransform.scale));

                    this.camera.projectionMatrix = m.multiply(l);
                    this.renderer.resetState();
                    this.renderer.render(this.scene, this.camera);
                    map.triggerRepaint();
                }
            };

            const results = await Promise.all([map.once('load'), loadModel()]);
            const model1 = results[1];
            const model2 = model1.clone();

            map.addLayer(customLayer);
        }

        main();
    </script>
</body>

</html>
Prev
使用three.js添加带阴影的3D模型
Next
使用three.js添加3D模型