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