184 lines
6.8 KiB
JavaScript
184 lines
6.8 KiB
JavaScript
import * as THREE from 'three'
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
|
|
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
|
|
import gsap from 'gsap'
|
|
|
|
const PAEAMS = { DEPTH: 6, coordsMaxCounts: 500 }
|
|
const fetchJson = (url) => fetch(url).then((res) => res.json())
|
|
|
|
import chinajson from '@/assets/china.json?url'
|
|
export async function setScene(DOM) {
|
|
|
|
const scene = new THREE.Scene()
|
|
const camera = new THREE.PerspectiveCamera(50, DOM.clientWidth / DOM.clientHeight, 0.1, 100000000)
|
|
camera.position.set(0, 600, 200)
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, logarithmicDepthBuffer: true })
|
|
renderer.setSize(DOM.clientWidth, DOM.clientHeight)
|
|
DOM.appendChild(renderer.domElement)
|
|
const controls = new OrbitControls(camera, renderer.domElement)
|
|
controls.enableDamping = true
|
|
const light = new THREE.DirectionalLight(0xffffff, 1)
|
|
light.position.set(100, 100, 100)
|
|
scene.add(new THREE.AmbientLight(0xffffff, 1.5), light)
|
|
const css2DRender = new CSS2DRenderer()
|
|
css2DRender.setSize(DOM.clientWidth, DOM.clientHeight)
|
|
css2DRender.domElement.style.zIndex = 0
|
|
css2DRender.domElement.style.position = 'relative'
|
|
css2DRender.domElement.style.top = -DOM.clientHeight + 'px'
|
|
css2DRender.domElement.style.height = DOM.clientHeight + 'px'
|
|
css2DRender.domElement.style.width = DOM.clientWidth + 'px'
|
|
css2DRender.domElement.style.pointerEvents = 'none'
|
|
DOM.appendChild(css2DRender.domElement)
|
|
animate()
|
|
function animate() {
|
|
controls.update()
|
|
css2DRender.render(scene, camera)
|
|
requestAnimationFrame(animate)
|
|
renderer.render(scene, camera)
|
|
}
|
|
|
|
// 根级
|
|
const rootGeoJson = await fetchJson(chinajson)
|
|
const rootGroup = createGeo(rootGeoJson)
|
|
rootGroup.rotation.x = -Math.PI / 2
|
|
rootGroup.position.sub(new THREE.Box3().setFromObject(rootGroup).getCenter(new THREE.Vector3())) // 平移到原点
|
|
scene.add(rootGroup)
|
|
clickEvent(camera, controls, DOM, rootGroup) // 点击事件
|
|
|
|
}
|
|
|
|
// 点击事件
|
|
function clickEvent(camera, controls, DOM, rootGroup) {
|
|
|
|
const raycaster = new THREE.Raycaster()
|
|
const mouse = new THREE.Vector2()
|
|
const getLevelObj = (obj) => {
|
|
if (obj.info) return obj
|
|
if (obj.parent) return getLevelObj(obj.parent)
|
|
}
|
|
DOM.addEventListener('dblclick', async (e) => {
|
|
|
|
mouse.x = (e.clientX / DOM.clientWidth) * 2 - 1
|
|
mouse.y = -(e.clientY / DOM.clientHeight) * 2 + 1
|
|
raycaster.setFromCamera(mouse, camera)
|
|
const intersects = raycaster.intersectObjects(rootGroup.children, true)
|
|
if (intersects.length > 0) {
|
|
|
|
const item = getLevelObj(intersects[0].object)
|
|
const info = item.info
|
|
const url = new URL(`../assets/geojson/${info.properties.adcode}.json`, import.meta.url)
|
|
const json = await fetchJson(url.href)
|
|
if (!json.features.length) return
|
|
item.label.visible = false // 隐藏文字
|
|
|
|
// 子级组
|
|
const child_geo = createGeo(json)
|
|
gsap.to(child_geo.position, { z: PAEAMS.DEPTH, duration: 1.5, ease: 'power2.out' })
|
|
item.child_geo = child_geo
|
|
item.add(child_geo)
|
|
}
|
|
})
|
|
}
|
|
|
|
import textureMap from '@/assets/texture.png?url'
|
|
// 创建地图
|
|
const texture = new THREE.TextureLoader().load(textureMap)
|
|
texture.wrapT = THREE.RepeatWrapping
|
|
texture.repeat.y = 0.1
|
|
function animateTexture() {
|
|
texture.offset.y += 0.03
|
|
requestAnimationFrame(animateTexture)
|
|
}
|
|
animateTexture()
|
|
function createGeo(geoJson) {
|
|
|
|
const group = new THREE.Group()
|
|
group.info = geoJson
|
|
|
|
// 材质
|
|
|
|
const materials = [
|
|
new THREE.MeshStandardMaterial({ color: Math.random() * 0xffffff, transparent: true, metalness: 0.5, roughness: 0.5 }),
|
|
new THREE.MeshBasicMaterial({ map: texture, color: 0xffffff * Math.random() }),
|
|
]
|
|
// 遍历添加各个地块
|
|
geoJson.features.forEach((info) => {
|
|
|
|
// 省会/中心坐标转换
|
|
if (info?.properties.center) info.properties.centerCoord3 = coordToVector(info.properties.center, 3)
|
|
if (info?.properties?.centroid) info.properties.centroidCoord3 = coordToVector(info.properties.centroid, 3)
|
|
|
|
// 行政区块组
|
|
const areaGroup = new THREE.Group()
|
|
console.log(info.properties.adcode);
|
|
|
|
areaGroup.info = info
|
|
areaGroup.name = info.properties?.name
|
|
group.add(areaGroup)
|
|
|
|
if (info.geometry.type === 'MultiPolygon') info.geometry.coordinates.forEach((j) => j.forEach((z) => areaGroup.add(createShapeWithCoord(z, materials))))
|
|
else if (info.geometry.type === 'Polygon') info.geometry.coordinates.forEach((j) => areaGroup.add(createShapeWithCoord(j, materials)))
|
|
|
|
const div = document.createElement('div')
|
|
div.innerHTML = info.properties?.name
|
|
div.style.color = '#dbdbdb'
|
|
div.style.fontSize = '10px'
|
|
const css = new CSS2DObject(div)
|
|
|
|
// 判断位置
|
|
if (info?.properties?.centroidCoord3 || info?.properties?.centerCoord3) css.position.copy(info?.properties?.centroidCoord3 || info?.properties?.centerCoord3)
|
|
else {
|
|
const box = new THREE.Box3().setFromObject(areaGroup)
|
|
const center = box.getCenter(new THREE.Vector3())
|
|
css.position.copy(center)
|
|
}
|
|
|
|
css.position.z += PAEAMS.DEPTH || 2
|
|
areaGroup.label = css
|
|
areaGroup.attach(css)
|
|
|
|
})
|
|
|
|
return group
|
|
}
|
|
|
|
// 坐标转换
|
|
function coordToVector(coord, type = 2, scale = 10000) {
|
|
const [lng, lat] = coord
|
|
const x = lng * 20037508.34 / 180
|
|
let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180)
|
|
y = y * 20037508.34 / 180
|
|
return type === 2 ? new THREE.Vector2(x / scale, y / scale) : new THREE.Vector3(x / scale, y / scale, 0)
|
|
}
|
|
|
|
|
|
function createShapeWithCoord(coordinates, materials) {
|
|
|
|
while (coordinates.length > PAEAMS.coordsMaxCounts) coordinates = coordinates.filter((_, i) => i % 2 === 0)
|
|
|
|
// 参数
|
|
const path = new THREE.Path(coordinates.map((k) => coordToVector(k)))
|
|
const parameters = { bevelThickness: 0, bevelSize: 0, bevelOffset: 0, depth: PAEAMS.DEPTH || 2, bevelEnabled: true }
|
|
|
|
// 边缘
|
|
const emptyShape = new THREE.Shape()
|
|
emptyShape.holes.push(path)
|
|
const emptyGeometry = new THREE.ExtrudeGeometry(emptyShape, { depth: 0.0001, bevelEnabled: false })
|
|
const emptyMaterial = new THREE.MeshBasicMaterial({ color: 0xbbbaba, transparent: true, opacity: 1, wireframe: true })
|
|
const emptyMesh = new THREE.Mesh(emptyGeometry, emptyMaterial)
|
|
emptyMesh.position.z += parameters.depth + 0.01
|
|
|
|
// 地块
|
|
const shape = new THREE.Shape()
|
|
shape.curves.push(path)
|
|
const geometry = new THREE.ExtrudeGeometry(shape, parameters)
|
|
|
|
const mesh = new THREE.Mesh(geometry, materials)
|
|
mesh.add(emptyMesh)
|
|
|
|
return mesh
|
|
|
|
}
|
|
|
|
|