地图区域大小随着父盒子大小变动,窗口缩放自动适配 每个区域显示不同颜色和高度,描边 每个区域显示名字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>