需求点
地图区域大小随着父盒子大小变动,窗口缩放自动适配 每个区域显示不同颜色和高度,描边 每个区域显示名字label和icon 点击区域改变其透明度,并且弹窗显示信息窗口 点击点也可以 可以自由放大缩小,360度旋转
包
npm install d3@^ 7.8 .4
npm install lil- gui@^ 0.18 .1
npm install three@^ 0.152 .2
代码
< template> < div style= "width: 100%; height: 100vh; position: relative" > < div id= "map" style= "width: 100%; height: 100%" > < / div> < ! -- 点击市或者点显示信息窗口 -- > < div v- if = "city" class = "infoPop" : style= "`left:${left}px;top:${top}px`" > 我是{ { city } } < / div> < / div>
< / template> < script setup>
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 { ref, onMounted, onUnmounted } from "vue" ;
import * as d3 from "d3" ; const mapWidth = ref ( 0 ) ;
const mapHeight = ref ( 0 ) ;
const mapElement = ref ( null ) ;
const left = ref ( 0 ) ;
const top = ref ( 0 ) ;
const city = ref ( "" ) ; onMounted ( ( ) => { mapElement. value = document. getElementById ( "map" ) ; mapWidth. value = mapElement. value. clientWidth; mapHeight. value = mapElement. value. clientHeight; console. log ( mapWidth. value, mapHeight. value) ; const scene = new THREE. Scene ( ) ; const ambientLight = new THREE. AmbientLight ( 0xd4e7fd , 4 ) ; scene. add ( ambientLight) ; const directionalLight = new THREE. DirectionalLight ( 0xe8eaeb , 0.2 ) ; directionalLight. position. set ( 0 , 10 , 5 ) ; const directionalLight2 = directionalLight. clone ( ) ; directionalLight2. position. set ( 0 , 10 , - 5 ) ; const directionalLight3 = directionalLight. clone ( ) ; directionalLight3. position. set ( 5 , 10 , 0 ) ; const directionalLight4 = directionalLight. clone ( ) ; directionalLight4. position. set ( - 5 , 10 , 0 ) ; scene. add ( directionalLight) ; scene. add ( directionalLight2) ; scene. add ( directionalLight3) ; scene. add ( directionalLight4) ; const camera = new THREE. PerspectiveCamera ( 75 , mapWidth. value / mapHeight. value, 0.1 , 1000 ) ; camera. position. y = 8 ; camera. position. z = 8 ; const labelRenderer = new CSS2DRenderer ( ) ; labelRenderer. domElement. style. position = "absolute" ; labelRenderer. domElement. style. top = "0px" ; labelRenderer. domElement. style. pointerEvents = "none" ; labelRenderer. setSize ( mapWidth. value, mapHeight. value) ; document. getElementById ( "map" ) . appendChild ( labelRenderer. domElement) ; const renderer = new THREE. WebGLRenderer ( { alpha : true } ) ; renderer. setSize ( mapWidth. value, mapHeight. value) ; document. getElementById ( "map" ) . appendChild ( renderer. domElement) ; const controls = new OrbitControls ( camera, renderer. domElement) ; controls. update ( ) ; const animate = ( ) => { requestAnimationFrame ( animate) ; controls. update ( ) ; renderer. render ( scene, camera) ; labelRenderer. render ( scene, camera) ; } ; animate ( ) ; window. addEventListener ( "resize" , ( ) => { mapWidth. value = mapElement. value. clientWidth; mapHeight. value = mapElement. value. clientHeight; camera. aspect = mapWidth. value / mapHeight. value; camera. updateProjectionMatrix ( ) ; renderer. setSize ( mapWidth. value, mapHeight. value) ; labelRenderer. setSize ( mapWidth. value, mapHeight. value) ; } ) ; const url = "https://geo.datav.aliyun.com/areas_v3/bound/330000_full.json" ; fetch ( url) . then ( ( res ) => res. json ( ) ) . then ( ( data ) => { const map = createMap ( data) ; scene. add ( map) ; let intersect = null ; window. addEventListener ( "click" , ( event ) => { const mouse = new THREE. Vector2 ( ) ; mouse. x = ( event. clientX / mapWidth. value) * 2 - 1 ; mouse. y = - ( event. clientY / mapHeight. value) * 2 + 1 ; const raycaster = new THREE. Raycaster ( ) ; raycaster. setFromCamera ( mouse, camera) ; const intersects = raycaster. intersectObjects ( map. children) . filter ( ( item ) => item. object. type !== "Line" ) ; if ( intersects. length > 0 ) { if ( intersects[ 0 ] . object. type === "Mesh" ) { console. log ( intersects[ 0 ] . object. parent. name, intersects[ 0 ] . object. parent. adcode) ; city. value = intersects[ 0 ] . object. parent. name; left. value = event. clientX + 20 ; top. value = event. clientY + 20 ; if ( intersect) isAplha ( intersect, 1 ) ; intersect = intersects[ 0 ] . object. parent; isAplha ( intersect, 0.4 ) ; } if ( intersects[ 0 ] . object. type === "Sprite" ) { console. log ( intersects[ 0 ] . object) ; } } else { if ( intersect) isAplha ( intersect, 1 ) ; } function isAplha ( intersect, opacity ) { intersect. children. forEach ( ( item ) => { if ( item. type === "Mesh" ) { item. material. opacity = opacity; } } ) ; } } ) ; } ) ;
} ) ;
onUnmounted ( ( ) => { window. removeEventListener ( "resize" , resizeRenderer) ;
} ) ;
const offsetXY = d3. geoMercator ( ) ;
const createMap = ( data ) => { console. log ( data, 11111 ) ; const map = new THREE. Object3D ( ) ; const center = data. features[ 0 ] . properties. centroid; offsetXY. center ( center) . translate ( [ 0 , 0 ] ) ; data. features. forEach ( ( feature ) => { const unit = new THREE. Object3D ( ) ; const { centroid, center, name, adcode } = feature. properties; const { coordinates, type } = feature. geometry; const point = centroid || center || [ 0 , 0 ] ; const color = new THREE. Color ( ` hsl( ${ 233 } , ${ Math. random ( ) * 30 + 55 } %, ${ Math. random ( ) * 30 + 55 } %) ` ) . getHex ( ) ; const depth = Math. random ( ) * 0.3 + 0.3 ; const label = createLabel ( name, point, depth) ; const icon = createIcon ( center, depth) ; coordinates. forEach ( ( coordinate ) => { if ( type === "MultiPolygon" ) coordinate. forEach ( ( item ) => fn ( item) ) ; if ( type === "Polygon" ) fn ( coordinate) ; function fn ( coordinate ) { unit. name = name; unit. adcode = adcode; const mesh = createMesh ( coordinate, color, depth) ; const line = createLine ( coordinate, depth) ; unit. add ( mesh, ... line) ; } } ) ; map. add ( unit, label, icon) ; setCenter ( map) ; } ) ; const icon1 = createIcon ( [ 120.057576 , 29.697459 ] , 1.11 ) ; map. add ( icon1) ; return map;
} ;
const createMesh = ( data, color, depth, name ) => { const shape = new THREE. Shape ( ) ; data. forEach ( ( item, idx ) => { const [ x, y] = offsetXY ( item) ; if ( idx === 0 ) shape. moveTo ( x, - y) ; else shape. lineTo ( x, - y) ; } ) ; const extrudeSettings = { depth : depth, bevelEnabled : false , } ; const materialSettings = { color : color, emissive : 0x000000 , roughness : 0.45 , metalness : 0.8 , transparent : true , side : THREE . DoubleSide, } ; const geometry = new THREE. ExtrudeGeometry ( shape, extrudeSettings) ; const material = new THREE. MeshStandardMaterial ( materialSettings) ; const mesh = new THREE. Mesh ( geometry, material) ; return mesh;
} ;
const createLine = ( data, depth ) => { const points = [ ] ; data. forEach ( ( item ) => { const [ x, y] = offsetXY ( item) ; points. push ( new THREE. Vector3 ( x, - y, 0 ) ) ; } ) ; const lineGeometry = new THREE. BufferGeometry ( ) . setFromPoints ( points) ; const uplineMaterial = new THREE. LineBasicMaterial ( { color : 0xffffff } ) ; const downlineMaterial = new THREE. LineBasicMaterial ( { color : 0xffffff } ) ; const upLine = new THREE. Line ( lineGeometry, uplineMaterial) ; const downLine = new THREE. Line ( lineGeometry, downlineMaterial) ; downLine. position. z = - 0.0001 ; upLine. position. z = depth + 0.0001 ; return [ upLine, downLine] ;
} ;
const createLabel = ( name, point, depth ) => { const div = document. createElement ( "div" ) ; div. style. color = "#fff" ; div. style. fontSize = "12px" ; div. style. textShadow = "1px 1px 2px #047cd6" ; div. textContent = name; const label = new CSS2DObject ( div) ; label. scale. set ( 0.01 , 0.01 , 0.01 ) ; const [ x, y] = offsetXY ( point) ; label. position. set ( x, - y, depth) ; return label;
} ;
const createIcon = ( point, depth ) => { const url = new URL ( "../assets/icon.png" , import . meta. url) . href; const map = new THREE. TextureLoader ( ) . load ( url) ; const material = new THREE. SpriteMaterial ( { map : map, transparent : true , } ) ; const sprite = new THREE. Sprite ( material) ; const [ x, y] = offsetXY ( point) ; sprite. scale. set ( 0.3 , 0.3 , 0.3 ) ; sprite. position. set ( x, - y, depth + 0.2 ) ; sprite. renderOrder = 1 ; return sprite;
} ;
const setCenter = ( map ) => { map. rotation. x = - Math. PI / 2 ; const box = new THREE. Box3 ( ) . setFromObject ( map) ; const center = box. getCenter ( new THREE. Vector3 ( ) ) ; const offset = [ 0 , 0 ] ; map. position. x = map. position. x - center. x - offset[ 0 ] ; map. position. z = map. position. z - center. z - offset[ 1 ] ;
} ;
< / script> < style>
* { padding : 0 ; margin : 0 ; box- sizing: border- box;
}
#map { background- color: #d4e7fd;
}
. infoPop { position : absolute; left : 0 ; top : 0 ; background : #fff; border : 1px solid #03c3fd; border- radius: 10px; color : #333 ; padding : 20px; z- index: 9999 ;
}
< / style>