# 3D瓦片

## 使用


通过 [vjmap3d.loadPlugin3dtiles](https://vjmap.com/map3d/api/functions/loadPlugin3dtiles.html) 加载3dtiles插件库

或 通过 `vjmap3d.Plugins.loadPlugin("3dtiles")` 加载

```js
let tiles3d = await vjmap3d.loadPlugin3dtiles()
let offsetParent = new THREE.Group();
offsetParent.scale.set(0.1, 0.1, 0.1)
app.scene.add( offsetParent );
const url = 'https://vjmap.com/map3d/resources/3dtiles/demo/tileset.json';
let tiles = new tiles3d.TilesRenderer( url );
tiles.manager.addHandler(/\.gltf$/, vjmap3d.LoadManager.gltfLoader);
tiles.manager.addHandler(/\.drc$/, vjmap3d.LoadManager.dracoLoader);
tiles.errorTarget = 2;
offsetParent.add( tiles.group );
app.signal.onAppAfterUpdate.add(() => {
    tiles.setCamera( app.camera );
    tiles.setResolutionFromRenderer( app.camera, app.renderer );
    tiles.update()
})
```

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    },
    control: {
        initState: {
            cameraTarget: new THREE.Vector3(24, 0, 8),
			cameraPosition: new THREE.Vector3(32, 28, 24),
        }
    }
})
let tiles3d = await vjmap3d.loadPlugin3dtiles()
let offsetParent = new THREE.Group();
offsetParent.scale.set(0.1, 0.1, 0.1)
app.scene.add( offsetParent );
const url = 'https://vjmap.com/map3d/resources/3dtiles/demo/tileset.json';
let tiles = new tiles3d.TilesRenderer( url );
tiles.manager.addHandler(/\.gltf$/, vjmap3d.LoadManager.gltfLoader);
tiles.manager.addHandler(/\.drc$/, vjmap3d.LoadManager.dracoLoader);
tiles.errorTarget = 2;
offsetParent.add( tiles.group );
app.signal.onAppAfterUpdate.add(() => {
    tiles.setCamera( app.camera );
    tiles.setResolutionFromRenderer( app.camera, app.renderer );
    tiles.update()
})
</pre>
</mapFrame> 


## 自动叠加互联网地图(3D模式)

```js
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: false } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    },
    control: { leftButtonPan: true } // 设置为左键用于平移 (同时右键将用于旋转) 和地图2d使用习惯一样
})
let provider = new vjmap3d.MapProvider(
    [
        {
            url: "https://t2.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=YOUR_TK"
        }
    ],
    {
        rootTile: [10, 834, 444],
    }
);
let mapview = new vjmap3d.MapView({
    provider,
    baseScale: 5000
});
app.scene.add(mapview);
//
let tiles3d = await vjmap3d.loadPlugin3dtiles();
//
let url = 'https://vjmap.com/map3d/resources/3dtiles/baowei/tileset.json';
let meta = await vjmap3d.httpHelper.get(url);
let data = meta.data;
let box = data.root.boundingVolume.box;
let lngLat = tiles3d.get3dTilesLngLatHeight(box.slice(0, 3), data.root.transform); // box 前 3 个数是中心点；直接传整个 box 得到的不是真实中心
lngLat[2] = 0;// 不考虑高度
//
let worldPoint = mapview.lngLatToWorld(lngLat);
let marker2d = new vjmap3d.Marker2D({
    color: vjmap3d.render2d.color.random()
});
marker2d.setPosition(worldPoint);
marker2d.addTo(app);
//
const tiles = new tiles3d.TilesRenderer(url, {
    clearRootTransform: true
}); 
tiles.manager.addHandler(/\.gltf$/, vjmap3d.LoadManager.gltfLoader);
tiles.manager.addHandler(/\.drc$/, vjmap3d.LoadManager.dracoLoader);
tiles.errorTarget = 2;
tiles.onLoadModel = (scene) => {
    console.log(" --- scene --- ")
}
let offsetParent = new THREE.Group();
offsetParent.add(tiles.group)
//
let ent = vjmap3d.Entity.fromObject3d(offsetParent);
ent.position.copy(worldPoint);
let scale = 1 / mapview.worldResolution()
ent.scale.set(scale, scale, scale);
ent.rotation.set(-Math.PI / 2, 0, 0);
ent.position.y += 0.5;
ent.addTo(app);
//
app.signal.onAppAfterUpdate.add(() => {
    tiles.setCamera(app.camera);
    tiles.setResolutionFromRenderer(app.camera, app.renderer);
    tiles.update();
});
app.cameraControl.moveTo( worldPoint.x, worldPoint.y,  worldPoint.z, false)
app.cameraControl.zoom(9, false)
```

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: false } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    },
    control: { leftButtonPan: true } // 设置为左键用于平移 (同时右键将用于旋转) 和地图2d使用习惯一样
})
let provider = new vjmap3d.MapProvider(
    [
        {
            url: "https://t2.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=YOUR_TK"
        }
    ],
    {
        rootTile: [10, 834, 444],
    }
);
let mapview = new vjmap3d.MapView({
    provider,
    baseScale: 5000
});
app.scene.add(mapview);
//
let tiles3d = await vjmap3d.loadPlugin3dtiles();
//
let url = 'https://vjmap.com/map3d/resources/3dtiles/baowei/tileset.json';
let meta = await vjmap3d.httpHelper.get(url);
let data = meta.data;
let box = data.root.boundingVolume.box;
let lngLat = tiles3d.get3dTilesLngLatHeight(box.slice(0, 3), data.root.transform); // box 前 3 个数是中心点；直接传整个 box 得到的不是真实中心
lngLat[2] = 0;// 不考虑高度
//
let worldPoint = mapview.lngLatToWorld(lngLat);
let marker2d = new vjmap3d.Marker2D({
    color: vjmap3d.render2d.color.random()
});
marker2d.setPosition(worldPoint);
marker2d.addTo(app);
//
const tiles = new tiles3d.TilesRenderer(url, {
    clearRootTransform: true
}); 
tiles.manager.addHandler(/\.gltf$/, vjmap3d.LoadManager.gltfLoader);
tiles.manager.addHandler(/\.drc$/, vjmap3d.LoadManager.dracoLoader);
tiles.errorTarget = 2;
tiles.onLoadModel = (scene) => {
    console.log(" --- scene --- ")
}
let offsetParent = new THREE.Group();
offsetParent.add(tiles.group)
//
let ent = vjmap3d.Entity.fromObject3d(offsetParent);
ent.position.copy(worldPoint);
let scale = 1 / mapview.worldResolution()
ent.scale.set(scale, scale, scale);
ent.rotation.set(-Math.PI / 2, 0, 0);
ent.position.y += 0.5;
ent.addTo(app);
//
app.signal.onAppAfterUpdate.add(() => {
    tiles.setCamera(app.camera);
    tiles.setResolutionFromRenderer(app.camera, app.renderer);
    tiles.update();
});
app.cameraControl.moveTo( worldPoint.x, worldPoint.y,  worldPoint.z, false)
app.cameraControl.zoom(9, false)
</pre>
</mapFrame> 


## 自动叠加互联网地图(3D做为唯杰地图的一个图层展示)

```js
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {
            sat: {
                type: 'raster',
                tileSize: 256, // 天地图是 256 瓦片
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=YOUR_TK"],
            }
        },
        layers: [{
            id: 'sat',
            type: 'raster',
            source: 'sat'
        }]
    },
    center: prj.toLngLat([116.3912, 39.9073]),
    zoom: 10,
    pitch: 0,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
//
// 创建3d图层
let mapLayer = new vjmap3d.MapThreeLayer(map, {
    stat: {
        show: true,
        left: "0"
    }
});
map.addLayer(new vjmap.ThreeLayer({ context: mapLayer }))
let app = mapLayer.app;
// 注意：app.map 是 MapThreeLayer 运行时挂上的 2D 地图实例（仅地图集成模式存在；纯 3D 场景没有）
//
let tiles3d = await vjmap3d.loadPlugin3dtiles();
//
let url = 'https://vjmap.com/map3d/resources/3dtiles/baowei/tileset.json';
let meta = await vjmap3d.httpHelper.get(url);
let data = meta.data;
let box = data.root.boundingVolume.box;
let lngLat = tiles3d.get3dTilesLngLatHeight(box.slice(0, 3), data.root.transform); // box 前 3 个数是中心点；直接传整个 box 得到的不是真实中心
lngLat[2] = 0;// 不考虑高度
app.map.setCenter(lngLat);
app.map.setZoom(17);
let marker = new vjmap.Marker();
marker.setLngLat(lngLat);
marker.addTo(app.map)
const tiles = new tiles3d.TilesRenderer(url, {
    clearRootTransform: true
}); 
tiles.manager.addHandler(/\.gltf$/, vjmap3d.LoadManager.gltfLoader);
tiles.manager.addHandler(/\.drc$/, vjmap3d.LoadManager.dracoLoader);
tiles.errorTarget = 2;
tiles.onLoadModel = (scene) => {
    //console.log(" --- scene --- ")
}
let offsetParent = new THREE.Group();
offsetParent.add(tiles.group)
//
let ent = vjmap3d.Entity.fromObject3d(offsetParent);
ent.position.copy(vjmap3d.map2dUtils.projectToWorld(lngLat));
let scale = 1.0 / vjmap3d.map2dUtils.WorldResolution
ent.scale.set(scale, scale, scale);
ent.addTo(app);
app.signal.onAppAfterUpdate.add(() => {
    tiles.setCamera(app.camera);
    tiles.setResolutionFromRenderer(app.camera, app.renderer);
    tiles.update();
});
```
<mapFrame> 
<pre>
let svc = new vjmap.Service(env.serviceUrl, env.accessToken)
// 根据地图范围建立经纬度投影坐标系
let prj = new vjmap.LnglatProjection();
// 地图对象
let map = new vjmap.Map({
    container: 'map', // DIV容器ID
    style: {
        version: svc.styleVersion(),
        glyphs: svc.glyphsUrl(),
        sources: {
            sat: {
                type: 'raster',
                tileSize: 256, // 天地图是 256 瓦片
                tiles: ["https://t3.tianditu.gov.cn/DataServer?T=img_w&X={x}&Y={y}&L={z}&tk=YOUR_TK"],
            }
        },
        layers: [{
            id: 'sat',
            type: 'raster',
            source: 'sat'
        }]
    },
    center: prj.toLngLat([116.3912, 39.9073]),
    zoom: 10,
    pitch: 0,
    antialias: true, // 反锯齿
    renderWorldCopies: false // 不显示多屏地图
});
// 关联服务对象和投影对象
map.attach(svc, prj);
// 根据地图本身范围缩放地图至全图显示
//map.fitMapBounds();
await map.onLoad();
//
// 创建3d图层
let mapLayer = new vjmap3d.MapThreeLayer(map, {
    stat: {
        show: true,
        left: "0"
    }
});
map.addLayer(new vjmap.ThreeLayer({ context: mapLayer }))
let app = mapLayer.app;
// 注意：app.map 是 MapThreeLayer 运行时挂上的 2D 地图实例（仅地图集成模式存在；纯 3D 场景没有）
//
let tiles3d = await vjmap3d.loadPlugin3dtiles();
//
let url = 'https://vjmap.com/map3d/resources/3dtiles/baowei/tileset.json';
let meta = await vjmap3d.httpHelper.get(url);
let data = meta.data;
let box = data.root.boundingVolume.box;
let lngLat = tiles3d.get3dTilesLngLatHeight(box.slice(0, 3), data.root.transform); // box 前 3 个数是中心点；直接传整个 box 得到的不是真实中心
lngLat[2] = 0;// 不考虑高度
app.map.setCenter(lngLat);
app.map.setZoom(17);
let marker = new vjmap.Marker();
marker.setLngLat(lngLat);
marker.addTo(app.map)
const tiles = new tiles3d.TilesRenderer(url, {
    clearRootTransform: true
}); 
tiles.manager.addHandler(/\.gltf$/, vjmap3d.LoadManager.gltfLoader);
tiles.manager.addHandler(/\.drc$/, vjmap3d.LoadManager.dracoLoader);
tiles.errorTarget = 2;
tiles.onLoadModel = (scene) => {
    //console.log(" --- scene --- ")
}
let offsetParent = new THREE.Group();
offsetParent.add(tiles.group)
//
let ent = vjmap3d.Entity.fromObject3d(offsetParent);
ent.position.copy(vjmap3d.map2dUtils.projectToWorld(lngLat));
let scale = 1.0 / vjmap3d.map2dUtils.WorldResolution
ent.scale.set(scale, scale, scale);
ent.addTo(app);
app.signal.onAppAfterUpdate.add(() => {
    tiles.setCamera(app.camera);
    tiles.setResolutionFromRenderer(app.camera, app.renderer);
    tiles.update();
});
</pre>
</mapFrame> 





## 类型定义文件

```ts
/**
 * Documented 3d-tile state managed by the TilesRenderer* / used/usable in priority / traverseFunctions!
 */
declare interface Tile extends TileBase {
    parent: Tile;
    /**
     * Hierarchy Depth from the TileGroup
     */
    __depth: number;
    /**
     * The screen space error for this tile
     */
    __error: number;
    /**
     * How far is this tiles bounds from the nearest active Camera.
     * Expected to be filled in during calculateError implementations.
     */
    __distanceFromCamera: number;
    /**
     * This tile is currently active if:
     *  1: Tile content is loaded and ready to be made visible if needed
     */
    __active: boolean;
    /**
     * This tile is currently visible if:
     *  1: Tile content is loaded
     *  2: Tile is within a camera frustum
     *  3: Tile meets the SSE requirements
     */
    __visible: boolean;
    /**
     * Whether or not the tile was visited during the last update run.
     */
    __used: boolean;
    /**
     * Whether or not the tile was within the frustum on the last update run.
     */
    __inFrustum: boolean;
    /**
     * TODO: Document this if it is useful enough to be the default property in the LRU sorting.
     */
    __depthFromRenderedParent: number;
}

/**
 * 3d-tiles Tile object per spec:
 * (incomplete, expanding as features become supported by this package.)
 *
 * See spec for full schema: https://github.com/CesiumGS/3d-tiles/blob/master/specification/schema/tile.schema.json
 */
declare interface TileBase {
    boundingVolume: {
        /**
         * An array of 12 numbers that define an oriented bounding box. The first three elements define the x, y, and z
         * values for the center of the box. The next three elements (with indices 3, 4, and 5) define the x axis
         * direction and half-length. The next three elements (indices 6, 7, and 8) define the y axis direction and
         * half-length. The last three elements (indices 9, 10, and 11) define the z axis direction and half-length.
         */
        box?: number[];
        /**
         * An array of four numbers that define a bounding sphere. The first three elements define the x, y, and z
         * values for the center of the sphere. The last element (with index 3) defines the radius in meters.
         */
        sphere?: number[];
    };
    /**
     * The error, in meters, introduced if this tileset is not rendered. At runtime, the geometric error is used to compute screen space error (SSE), i.e., the error measured in pixels.
     */
    geometricError: number;
    children?: TileBase[];
    content?: {
        uri: string;
        /**
         * Dictionary object with content specific extension objects.
         */
        extensions?: Record<string, any>;
        extras?: Record<string, any>;
        url?: string;
    };
    /**
     * Dictionary object with tile specific extension objects.
     */
    extensions?: Record<string, any>;
    extras?: Record<string, any>;
    refine?: 'REPLACE' | 'ADD';
    transform?: number[];
}

declare namespace Tiles3dPluginType {
    export {
        get3dTilesLngLatHeight,
        TileBase,
        Tile,
        Tileset,
        TilesRendererBase,
        TilesGroup,
        TilesRenderer
    }
}
export { Tiles3dPluginType }
 /**
 * 通过3dtiles的boundingVolume和transform获取经纬度
 * @param box 3dtiles里面boundingVolume.box的值，默认取中心点。也可以直接传一个坐标[number,number,number]
 *   ⚠️ 实现把 12 个数的 box 当成 4 个点（中心+3 个半轴向量）求轴对齐包围盒的中心，一般**不是** box 的真实中心；
 *   需要准确位置时请传 box.slice(0, 3)（3D Tiles 的 box 前 3 个数就是中心点）
 */
declare function get3dTilesLngLatHeight(box: number[], matrix?: number[]): [number, number, number];

/**
 * A 3d-tiles tileset.
 *
 * Schema, see: https://github.com/CesiumGS/3d-tiles/blob/main/specification/schema/tileset.schema.json
 */
declare interface Tileset {
    /**
     * Metadata about the entire tileset.
     */
    asset: {
        /**
         * 3d-tiles version
         */
        version: string;
        /**
         * Application specific version
         */
        tilesetVersion?: string;
        /**
         * Dictionary object with extension-specific objects.
         */
        extensions?: Record<string, any>;
    };
    /**
     * The error, in meters, introduced if this tileset is not rendered. At runtime, the geometric error is used to compute screen space error (SSE), i.e., the error measured in pixels.
     */
    geometricError: Number;
    /**
     * The root tile.
     */
    root: TileBase;
    /**
     * Names of 3D Tiles extensions used somewhere in this tileset.
     */
    extensionsUsed?: string[];
    /**
     * Names of 3D Tiles extensions required to properly load this tileset.
     */
    extensionsRequired?: string[];
    /**
     * A dictionary object of metadata about per-feature properties.
     */
    properties?: Record<string, any>;
    /**
     * Dictionary object with extension-specific objects.
     */
    extensions?: Record<string, any>;
    extras?: Record<string, any>;
}

declare class TilesGroup extends Group_2 {
    tilesRenderer: TilesRenderer;
    constructor(tilesRenderer: TilesRenderer);
}

declare class TilesRenderer extends TilesRendererBase {
    constructor(url: String, options?: {
        clearRootTransform?: boolean;
    });
    autoDisableRendererCulling: Boolean;
    optimizeRaycast: Boolean;
    manager: LoadingManager;
    group: TilesGroup;
    getBoundingBox(box: Box3): Boolean;
    getOrientedBoundingBox(box: Box3, matrix: Matrix4): Boolean;
    getBoundingSphere(sphere: Sphere): Boolean;
    hasCamera(camera: Camera): Boolean;
    setCamera(camera: Camera): Boolean;
    deleteCamera(camera: Camera): Boolean;
    setResolution(camera: Camera, x: Number, y: Number): Boolean;
    setResolution(camera: Camera, resolution: Vector2): Boolean;
    setResolutionFromRenderer(camera: Camera, renderer: WebGLRenderer): Boolean;
    forEachLoadedModel(callback: (scene: Object3D, tile: Tile) => void): void;
    onLoadTileSet: ((tileSet: Tileset) => void) | null;
    onLoadModel: ((scene: Object3D, tile: Tile) => void) | null;
    onDisposeModel: ((scene: Object3D, tile: Tile) => void) | null;
    onTileVisibilityChange: ((scene: Object3D, tile: Tile, visible: boolean) => void) | null;
    addEventListener(type: String, cb: (e: Object) => void);
    hasEventListener(type: String, cb: (e: Object) => void);
    removeEventListener(type: String, cb: (e: Object) => void);
    dispatchEvent(e: Object);
}

declare class TilesRendererBase {
    readonly rootTileset: Object | null;
    readonly root: Object | null;
    errorTarget: Number;
    errorThreshold: Number;
    loadSiblings: Boolean;
    displayActiveTiles: Boolean;
    maxDepth: Number;
    stopAtEmptyTiles: Boolean;
    fetchOptions;
    /** function to preprocess the url for each individual tile */
    preprocessURL: ((uri: string | URL) => string) | null;
    lruCache;
    parseQueue;
    downloadQueue;
    constructor(url: String);
    update(): void;
    traverse(beforeCb: ((tile: Object, parent: Object, depth: Number) => Boolean) | null, afterCb: ((tile: Object, parent: Object, depth: Number) => Boolean) | null): void;
    dispose(): void;
    resetFailedTiles(): void;
}
```