添加政府,银行大屏,修改政府前后端代码
This commit is contained in:
223
datav-bank/src/hooks/map/useMapMarkedLightPillar.js
Normal file
223
datav-bank/src/hooks/map/useMapMarkedLightPillar.js
Normal file
@@ -0,0 +1,223 @@
|
||||
import * as THREE from 'three'
|
||||
import TWEEN from '@tweenjs/tween.js'
|
||||
import useCoord from '@/hooks/useCoord'
|
||||
import { deepMerge, random } from '@/utils'
|
||||
/**
|
||||
*
|
||||
* @param {object} {
|
||||
* pointTextureUrl:标记点的图片url
|
||||
* lightHaloTextureUrl:光圈的URL
|
||||
* lightPillarUrl:光柱的URL
|
||||
* scaleFactor:1 缩放系数,用来调整标记点和光圈的缩放大小
|
||||
* }
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
export default function useMarkedLightPillar(options) {
|
||||
const { geoSphereCoord } = useCoord()
|
||||
// 默认参数
|
||||
let defaultOptions = {
|
||||
pointTextureUrl: './assets/texture/标注.png',
|
||||
lightHaloTextureUrl: './assets/texture/标注光圈.png',
|
||||
lightPillarUrl: './assets/texture/光柱.png',
|
||||
scaleFactor: 1, // 缩放系数
|
||||
}
|
||||
defaultOptions = deepMerge(defaultOptions, options)
|
||||
// 纹理加载器
|
||||
const textureLoader = new THREE.TextureLoader()
|
||||
// 射线拾取对象
|
||||
const raycaster = new THREE.Raycaster()
|
||||
let containerWidth = window.width
|
||||
let containerHeight = window.height
|
||||
// 对象属性
|
||||
let getBoundingClientRect = null
|
||||
/**
|
||||
* 创建标记点
|
||||
* @param {*} R 地球半径,根据R来进行缩放
|
||||
* @returns
|
||||
*/
|
||||
const createPointMesh = () => {
|
||||
// 标记点:几何体,材质,
|
||||
const geometry = new THREE.PlaneBufferGeometry(1, 1)
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: textureLoader.load(defaultOptions.pointTextureUrl),
|
||||
color: 0x00ffff,
|
||||
side: THREE.DoubleSide,
|
||||
transparent: true,
|
||||
depthWrite: false, //禁止写入深度缓冲区数据
|
||||
})
|
||||
let mesh = new THREE.Mesh(geometry, material)
|
||||
mesh.name = 'createPointMesh'
|
||||
// 缩放
|
||||
const scale = 0.15 * defaultOptions.scaleFactor
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
return mesh
|
||||
}
|
||||
/**
|
||||
* 创建光圈
|
||||
* @param {*} R 地球半径,根据R来进行缩放
|
||||
* @returns
|
||||
*/
|
||||
const createLightHalo = () => {
|
||||
// 标记点:几何体,材质,
|
||||
const geometry = new THREE.PlaneBufferGeometry(1, 1)
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: textureLoader.load(defaultOptions.lightHaloTextureUrl),
|
||||
color: 0x00ffff,
|
||||
side: THREE.DoubleSide,
|
||||
opacity: 0,
|
||||
transparent: true,
|
||||
depthWrite: false, //禁止写入深度缓冲区数据
|
||||
})
|
||||
let mesh = new THREE.Mesh(geometry, material)
|
||||
mesh.name = 'createLightHalo'
|
||||
// 缩放
|
||||
const scale = 0.3 * defaultOptions.scaleFactor
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
// 动画延迟时间
|
||||
const delay = random(0, 2000)
|
||||
// 动画:透明度缩放动画
|
||||
mesh.tween1 = new TWEEN.Tween({ scale: scale, opacity: 0 })
|
||||
.to({ scale: scale * 1.5, opacity: 1 }, 1000)
|
||||
.delay(delay)
|
||||
.onUpdate(params => {
|
||||
let { scale, opacity } = params
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
mesh.material.opacity = opacity
|
||||
})
|
||||
mesh.tween2 = new TWEEN.Tween({ scale: scale * 1.5, opacity: 1 })
|
||||
.to({ scale: scale * 2, opacity: 0 }, 1000)
|
||||
.onUpdate(params => {
|
||||
let { scale, opacity } = params
|
||||
mesh.scale.set(scale, scale, scale)
|
||||
mesh.material.opacity = opacity
|
||||
})
|
||||
mesh.tween1.chain(mesh.tween2)
|
||||
mesh.tween2.chain(mesh.tween1)
|
||||
mesh.tween1.start()
|
||||
return mesh
|
||||
}
|
||||
/**
|
||||
* 创建光柱
|
||||
* @param {*} lon
|
||||
* @param {*} lat
|
||||
* @param {*} heightScaleFactor 光柱高度的缩放系数
|
||||
* @returns
|
||||
*/
|
||||
const createLightPillar = (lon, lat, heightScaleFactor = 1) => {
|
||||
let group = new THREE.Group()
|
||||
// 柱体高度
|
||||
const height = heightScaleFactor
|
||||
// 柱体的geo,6.19=柱体图片高度/宽度的倍数
|
||||
const geometry = new THREE.PlaneBufferGeometry(height / 6.219, height)
|
||||
// 柱体旋转90度,垂直于Y轴
|
||||
geometry.rotateX(Math.PI / 2)
|
||||
// 柱体的z轴移动高度一半对齐中心点
|
||||
geometry.translate(0, 0, height / 2)
|
||||
// 柱子材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: textureLoader.load(defaultOptions.lightPillarUrl),
|
||||
color: 0x00ffff,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
side: THREE.DoubleSide,
|
||||
})
|
||||
// 光柱01
|
||||
let light01 = new THREE.Mesh(geometry, material)
|
||||
light01.name = 'createLightPillar01'
|
||||
// 光柱02:复制光柱01
|
||||
let light02 = light01.clone()
|
||||
light02.name = 'createLightPillar02'
|
||||
// 光柱02,旋转90°,跟 光柱01交叉
|
||||
light02.rotateZ(Math.PI / 2)
|
||||
// 创建底部标点
|
||||
const bottomMesh = createPointMesh()
|
||||
// 创建光圈
|
||||
const lightHalo = createLightHalo()
|
||||
// 将光柱和标点添加到组里
|
||||
group.add(bottomMesh, lightHalo, light01, light02)
|
||||
// 设置组对象的姿态
|
||||
// group = setMeshQuaternion(group, R, lon, lat)
|
||||
group.position.set(lon, lat, 0)
|
||||
return group
|
||||
}
|
||||
/**
|
||||
* 设置光柱颜色
|
||||
* @param {*} group
|
||||
* @param {*} color
|
||||
*/
|
||||
const setLightPillarColor = (group, color) => {
|
||||
group.children.forEach(item => {
|
||||
item.material.color = new THREE.Color(color)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 设置网格的位置及姿态
|
||||
* @param {*} mesh
|
||||
* @param {*} R
|
||||
* @param {*} lon
|
||||
* @param {*} lat
|
||||
* @returns
|
||||
*/
|
||||
const setMeshQuaternion = (mesh, R, lon, lat) => {
|
||||
const { x, y, z } = geoSphereCoord(R, lon, lat)
|
||||
mesh.position.set(x, y, z)
|
||||
// 姿态设置
|
||||
// mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
|
||||
let meshVector = new THREE.Vector3(x, y, z).normalize()
|
||||
// mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
|
||||
let normal = new THREE.Vector3(0, 0, 1)
|
||||
// 四元数属性.quaternion表示mesh的角度状态
|
||||
//.setFromUnitVectors();计算两个向量之间构成的四元数值
|
||||
mesh.quaternion.setFromUnitVectors(normal, meshVector)
|
||||
return mesh
|
||||
}
|
||||
/**
|
||||
* 射线拾取,返回选中的mesh
|
||||
* @param {*} event
|
||||
* @param {*} container
|
||||
* @param {*} camera
|
||||
* @param {*} mesh // 光柱group
|
||||
* @returns
|
||||
*/
|
||||
const getRaycasterObj = (event, container, camera, mesh) => {
|
||||
//屏幕坐标转WebGL标准设备坐标
|
||||
if (!getBoundingClientRect) {
|
||||
getBoundingClientRect = container.getBoundingClientRect()
|
||||
containerWidth = container.offsetWidth
|
||||
containerHeight = container.offsetHeight
|
||||
}
|
||||
var x = ((event.clientX - getBoundingClientRect.left) / containerWidth) * 2 - 1
|
||||
var y = -((event.clientY - getBoundingClientRect.top) / containerHeight) * 2 + 1
|
||||
|
||||
//通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
|
||||
raycaster.setFromCamera(new THREE.Vector2(x, y), camera)
|
||||
//返回.intersectObjects()参数中射线选中的网格模型对象
|
||||
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
|
||||
var intersects = raycaster.intersectObjects(mesh)
|
||||
return intersects
|
||||
}
|
||||
/**
|
||||
* 选择光柱,返回选择的对象
|
||||
* @param {*} event
|
||||
* @param {*} container
|
||||
* @param {*} camera
|
||||
* @param {*} mesh
|
||||
* @returns 返回选择的对象
|
||||
*/
|
||||
const chooseLightPillar = (event, container, camera, mesh) => {
|
||||
event.preventDefault()
|
||||
// 获取拾取的对象数组
|
||||
let intersects = getRaycasterObj(event, container, camera, mesh)
|
||||
// 大于0说明,说明选中了mesh,返回对象
|
||||
if (intersects.length > 0) {
|
||||
return intersects[0]
|
||||
}
|
||||
return null
|
||||
}
|
||||
return {
|
||||
createLightPillar,
|
||||
setLightPillarColor,
|
||||
chooseLightPillar,
|
||||
}
|
||||
}
|
||||
80
datav-bank/src/hooks/useCSS2DRenderer.js
Normal file
80
datav-bank/src/hooks/useCSS2DRenderer.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { CSS2DObject, CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||
|
||||
export default function useCSS2DRender() {
|
||||
/**
|
||||
* 初始化CSS2D渲染器
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {HTMLElement} container - 容器元素
|
||||
* @returns {CSS2DRenderer} CSS2D渲染器实例
|
||||
*/
|
||||
const initCSS2DRender = (options, container) => {
|
||||
const { width, height } = options
|
||||
|
||||
// 确保尺寸有效,避免Canvas尺寸为0的错误
|
||||
const validWidth = Math.max(width || 800, 1)
|
||||
const validHeight = Math.max(height || 600, 1)
|
||||
|
||||
let css2dRender = new CSS2DRenderer() // 实例化css2d渲染器
|
||||
css2dRender.setSize(validWidth, validHeight) // 设置渲染器的尺寸
|
||||
css2dRender.domElement.style.position = 'absolute' // 设置定位位置
|
||||
css2dRender.domElement.style.left = '0px'
|
||||
css2dRender.domElement.style.top = '0px'
|
||||
css2dRender.domElement.style.width = validWidth + 'px'
|
||||
css2dRender.domElement.style.height = validHeight + 'px'
|
||||
css2dRender.domElement.style.zIndex = '10000' // 确保在WebGL canvas之上
|
||||
css2dRender.domElement.style.pointerEvents = 'none' // 设置不能背选中
|
||||
css2dRender.domElement.style.overflow = 'visible' // 确保内容可见
|
||||
container.appendChild(css2dRender.domElement) // 插入到容器当中
|
||||
console.log('CSS2D渲染器DOM元素已插入容器');
|
||||
console.log('容器尺寸:', validWidth, 'x', validHeight);
|
||||
console.log('DOM元素样式:', css2dRender.domElement.style.cssText);
|
||||
return css2dRender
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建2d标签
|
||||
* @param {*} name 标签内容
|
||||
* @param {*} className 标签class
|
||||
* @returns
|
||||
*/
|
||||
const create2DTag = (name = '', className = '') => {
|
||||
let tag = document.createElement('div')
|
||||
tag.innerHTML = name
|
||||
tag.className = className
|
||||
tag.style.pointerEvents = 'none'
|
||||
tag.style.visibility = 'hidden'
|
||||
tag.style.position = 'absolute'
|
||||
// 如果className不存在,用以下样式
|
||||
if (!className) {
|
||||
tag.style.padding = '10px'
|
||||
tag.style.color = '#fff'
|
||||
tag.style.fontSize = '12px'
|
||||
tag.style.textAlign = 'center'
|
||||
tag.style.background = 'rgba(0,0,0,0.6)'
|
||||
tag.style.borderRadius = '4px'
|
||||
}
|
||||
let label = new CSS2DObject(tag)
|
||||
/**
|
||||
* 标签显示,
|
||||
* @param {*} name 显示内容
|
||||
* @param {*} point 显示坐标
|
||||
*/
|
||||
label.show = (name, point) => {
|
||||
label.element.innerHTML = name
|
||||
label.element.style.visibility = 'visible'
|
||||
label.position.copy(point)
|
||||
}
|
||||
/**
|
||||
* 隐藏
|
||||
*/
|
||||
label.hide = () => {
|
||||
label.element.style.visibility = 'hidden'
|
||||
}
|
||||
return label
|
||||
}
|
||||
|
||||
return {
|
||||
initCSS2DRender,
|
||||
create2DTag
|
||||
}
|
||||
}
|
||||
68
datav-bank/src/hooks/useConversionStandardData.js
Normal file
68
datav-bank/src/hooks/useConversionStandardData.js
Normal file
@@ -0,0 +1,68 @@
|
||||
export default function useConversionStandardData() {
|
||||
/**
|
||||
* 转换GeoJSON数据格式
|
||||
* 将Polygon类型转换为MultiPolygon格式
|
||||
* @param {Object} geoData - GeoJSON数据
|
||||
* @returns {Object} 转换后的数据
|
||||
*/
|
||||
const transfromGeoJSON = (geoData) => {
|
||||
if (!geoData || !geoData.features) {
|
||||
return geoData;
|
||||
}
|
||||
|
||||
const transformedFeatures = geoData.features.map(feature => {
|
||||
if (feature.geometry && feature.geometry.type === 'Polygon') {
|
||||
// 将Polygon转换为MultiPolygon格式
|
||||
return {
|
||||
...feature,
|
||||
geometry: {
|
||||
...feature.geometry,
|
||||
coordinates: [feature.geometry.coordinates] // 包装在数组中
|
||||
}
|
||||
};
|
||||
}
|
||||
return feature;
|
||||
});
|
||||
|
||||
return {
|
||||
...geoData,
|
||||
features: transformedFeatures
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 转换道路GeoJSON数据格式
|
||||
* 将LineString类型转换为MultiLineString格式
|
||||
* @param {Object} geoData - GeoJSON道路数据
|
||||
* @returns {Object} 转换后的数据
|
||||
*/
|
||||
const transformGeoRoad = (geoData) => {
|
||||
if (!geoData || !geoData.features) {
|
||||
return geoData;
|
||||
}
|
||||
|
||||
const transformedFeatures = geoData.features.map(feature => {
|
||||
if (feature.geometry && feature.geometry.type === 'LineString') {
|
||||
// 将LineString转换为MultiLineString格式
|
||||
return {
|
||||
...feature,
|
||||
geometry: {
|
||||
...feature.geometry,
|
||||
coordinates: [feature.geometry.coordinates] // 包装在数组中
|
||||
}
|
||||
};
|
||||
}
|
||||
return feature;
|
||||
});
|
||||
|
||||
return {
|
||||
...geoData,
|
||||
features: transformedFeatures
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
transfromGeoJSON,
|
||||
transformGeoRoad
|
||||
};
|
||||
}
|
||||
92
datav-bank/src/hooks/useCoord.js
Normal file
92
datav-bank/src/hooks/useCoord.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import * as THREE from 'three'
|
||||
const useCoord = () => {
|
||||
/**
|
||||
* 生成墨卡托坐标
|
||||
* @param {*} longitude 经度
|
||||
* @param {*} latitude 纬度
|
||||
* @returns
|
||||
*/
|
||||
// 墨卡托坐标系:展开地球,赤道作为x轴,向东为x轴正方,本初子午线作为y轴,向北为y轴正方向。
|
||||
// 数字20037508.34是地球赤道周长的一半:地球半径6378137米,赤道周长2*PI*r = 2 * 20037508.3427892,墨卡托坐标x轴区间[-20037508.3427892,20037508.3427892]
|
||||
const geoMercatorCoord = (longitude, latitude) => {
|
||||
var E = longitude
|
||||
var N = latitude
|
||||
var x = (E * 20037508.34) / 180
|
||||
var y = Math.log(Math.tan(((90 + N) * Math.PI) / 360)) / (Math.PI / 180)
|
||||
y = (y * 20037508.34) / 180
|
||||
return {
|
||||
x: x, //墨卡托x坐标——对应经度
|
||||
y: y, //墨卡托y坐标——对应维度
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 生成球面坐标
|
||||
* @param {*} R 球半径
|
||||
* @param {*} longitude 经度
|
||||
* @param {*} latitude 纬度
|
||||
* @returns
|
||||
*/
|
||||
const geoSphereCoord = (R, longitude, latitude) => {
|
||||
var lon = (longitude * Math.PI) / 180 //转弧度值
|
||||
var lat = (latitude * Math.PI) / 180 //转弧度值
|
||||
lon = -lon // three.js坐标系z坐标轴对应经度-90度,而不是90度
|
||||
|
||||
// 经纬度坐标转球面坐标计算公式
|
||||
var x = R * Math.cos(lat) * Math.cos(lon)
|
||||
var y = R * Math.sin(lat)
|
||||
var z = R * Math.cos(lat) * Math.sin(lon)
|
||||
// 返回球面坐标
|
||||
return {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 计算包围盒
|
||||
* @param {*} group
|
||||
* @returns
|
||||
*/
|
||||
const getBoundingBox = group => {
|
||||
// 包围盒计算模型对象的大小和位置
|
||||
var box3 = new THREE.Box3()
|
||||
box3.expandByObject(group) // 计算模型包围盒
|
||||
var size = new THREE.Vector3()
|
||||
box3.getSize(size) // 计算包围盒尺寸
|
||||
var center = new THREE.Vector3()
|
||||
box3.getCenter(center) // 计算一个层级模型对应包围盒的几何体中心坐标
|
||||
return {
|
||||
box3,
|
||||
center,
|
||||
size,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置网格的位置及姿态
|
||||
* @param {*} mesh
|
||||
* @param {*} R
|
||||
* @param {*} lon
|
||||
* @param {*} lat
|
||||
* @returns
|
||||
*/
|
||||
const setMeshQuaternion = (mesh, R, lon, lat) => {
|
||||
const { x, y, z } = geoSphereCoord(R, lon, lat)
|
||||
mesh.position.set(x, y, z)
|
||||
// 姿态设置
|
||||
// mesh在球面上的法线方向(球心和球面坐标构成的方向向量)
|
||||
let meshVector = new THREE.Vector3(x, y, z).normalize()
|
||||
// mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1)
|
||||
let normal = new THREE.Vector3(0, 0, 1)
|
||||
// 四元数属性.quaternion表示mesh的角度状态
|
||||
//.setFromUnitVectors();计算两个向量之间构成的四元数值
|
||||
mesh.quaternion.setFromUnitVectors(normal, meshVector)
|
||||
return mesh
|
||||
}
|
||||
return {
|
||||
geoMercatorCoord,
|
||||
geoSphereCoord,
|
||||
getBoundingBox,
|
||||
setMeshQuaternion,
|
||||
}
|
||||
}
|
||||
export default useCoord
|
||||
143
datav-bank/src/hooks/useCountry.js
Normal file
143
datav-bank/src/hooks/useCountry.js
Normal file
@@ -0,0 +1,143 @@
|
||||
import * as THREE from 'three';
|
||||
import { Line2 } from 'three/examples/jsm/lines/Line2.js';
|
||||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js';
|
||||
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js';
|
||||
|
||||
export default function useCountry() {
|
||||
/**
|
||||
* 创建国家边界线
|
||||
* @param {Object} data - GeoJSON数据
|
||||
* @param {Object} options - 线条样式选项
|
||||
* @param {string} lineType - 线条类型 ('Line', 'LineLoop', 'LineSegments', 'Line2')
|
||||
* @returns {THREE.Object3D} 线条对象
|
||||
*/
|
||||
const createCountryFlatLine = (data, options = {}, lineType = 'Line') => {
|
||||
const group = new THREE.Group();
|
||||
|
||||
if (!data || !data.features) {
|
||||
return group;
|
||||
}
|
||||
|
||||
data.features.forEach(feature => {
|
||||
if (feature.geometry && feature.geometry.coordinates) {
|
||||
const coordinates = feature.geometry.coordinates;
|
||||
|
||||
coordinates.forEach(multiPolygon => {
|
||||
multiPolygon.forEach(polygon => {
|
||||
const points = [];
|
||||
|
||||
// 提取坐标点
|
||||
polygon.forEach(coord => {
|
||||
if (Array.isArray(coord) && coord.length >= 2) {
|
||||
points.push(new THREE.Vector3(coord[0], coord[1], 0));
|
||||
}
|
||||
});
|
||||
|
||||
if (points.length > 0) {
|
||||
const line = createLine(points, options, lineType);
|
||||
if (line) {
|
||||
group.add(line);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建线条对象
|
||||
* @param {Array<THREE.Vector3>} points - 点数组
|
||||
* @param {Object} options - 样式选项
|
||||
* @param {string} lineType - 线条类型
|
||||
* @returns {THREE.Object3D} 线条对象
|
||||
*/
|
||||
const createLine = (points, options = {}, lineType = 'Line') => {
|
||||
if (!points || points.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultOptions = {
|
||||
color: 0xffffff,
|
||||
linewidth: 0.001,
|
||||
transparent: true,
|
||||
opacity: 1,
|
||||
depthTest: true
|
||||
};
|
||||
|
||||
const finalOptions = { ...defaultOptions, ...options };
|
||||
|
||||
try {
|
||||
switch (lineType) {
|
||||
case 'Line2': {
|
||||
// 使用Line2创建更高质量的线条
|
||||
const geometry = new LineGeometry();
|
||||
const positions = [];
|
||||
|
||||
points.forEach(point => {
|
||||
positions.push(point.x, point.y, point.z);
|
||||
});
|
||||
|
||||
geometry.setPositions(positions);
|
||||
|
||||
const material = new LineMaterial({
|
||||
color: finalOptions.color,
|
||||
linewidth: finalOptions.linewidth,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
|
||||
// 设置分辨率
|
||||
material.resolution.set(window.innerWidth, window.innerHeight);
|
||||
|
||||
return new Line2(geometry, material);
|
||||
}
|
||||
|
||||
case 'LineLoop': {
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: finalOptions.color,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
return new THREE.LineLoop(geometry, material);
|
||||
}
|
||||
|
||||
case 'LineSegments': {
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: finalOptions.color,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
return new THREE.LineSegments(geometry, material);
|
||||
}
|
||||
|
||||
default: // 'Line'
|
||||
case 'Line': {
|
||||
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
||||
const material = new THREE.LineBasicMaterial({
|
||||
color: finalOptions.color,
|
||||
transparent: finalOptions.transparent,
|
||||
opacity: finalOptions.opacity,
|
||||
depthTest: finalOptions.depthTest
|
||||
});
|
||||
return new THREE.Line(geometry, material);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating line:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
createCountryFlatLine,
|
||||
createLine
|
||||
};
|
||||
}
|
||||
60
datav-bank/src/hooks/useFileLoader.js
Normal file
60
datav-bank/src/hooks/useFileLoader.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { ref } from 'vue';
|
||||
import * as THREE from 'three';
|
||||
|
||||
export default function useFileLoader() {
|
||||
const loading = ref(false);
|
||||
const progress = ref(0);
|
||||
const error = ref(null);
|
||||
|
||||
const requestData = async (url) => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
progress.value = 0;
|
||||
|
||||
try {
|
||||
const loader = new THREE.FileLoader();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.load(
|
||||
url,
|
||||
// onLoad
|
||||
(data) => {
|
||||
try {
|
||||
const jsonData = JSON.parse(data);
|
||||
loading.value = false;
|
||||
progress.value = 100;
|
||||
resolve(jsonData);
|
||||
} catch (parseError) {
|
||||
error.value = parseError;
|
||||
loading.value = false;
|
||||
reject(parseError);
|
||||
}
|
||||
},
|
||||
// onProgress
|
||||
(progressEvent) => {
|
||||
if (progressEvent.lengthComputable) {
|
||||
progress.value = (progressEvent.loaded / progressEvent.total) * 100;
|
||||
}
|
||||
},
|
||||
// onError
|
||||
(err) => {
|
||||
error.value = err;
|
||||
loading.value = false;
|
||||
reject(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
loading.value = false;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
loading,
|
||||
progress,
|
||||
error,
|
||||
requestData
|
||||
};
|
||||
}
|
||||
234
datav-bank/src/hooks/useMapMarkedLightPillar.js
Normal file
234
datav-bank/src/hooks/useMapMarkedLightPillar.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import * as THREE from 'three';
|
||||
import TWEEN from '@tweenjs/tween.js';
|
||||
|
||||
export default function useMapMarkedLightPillar(options = {}) {
|
||||
const defaultOptions = {
|
||||
scaleFactor: 1.0,
|
||||
color: 0x00ffff,
|
||||
opacity: 0.8
|
||||
};
|
||||
|
||||
const config = { ...defaultOptions, ...options };
|
||||
|
||||
/**
|
||||
* 创建标记点网格
|
||||
* @param {number} x - X坐标
|
||||
* @param {number} y - Y坐标
|
||||
* @param {number} scaleFactor - 缩放因子
|
||||
* @returns {THREE.Mesh} 标记点网格
|
||||
*/
|
||||
const createPointMesh = (x, y, scaleFactor = 1) => {
|
||||
const geometry = new THREE.SphereGeometry(0.05, 16, 16);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: config.color,
|
||||
transparent: true,
|
||||
opacity: config.opacity
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.position.set(x, y, 0);
|
||||
mesh.scale.set(scaleFactor, scaleFactor, scaleFactor);
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建光晕效果
|
||||
* @param {number} x - X坐标
|
||||
* @param {number} y - Y坐标
|
||||
* @param {number} scaleFactor - 缩放因子
|
||||
* @returns {THREE.Mesh} 光晕网格
|
||||
*/
|
||||
const createLightHalo = (x, y, scaleFactor = 1) => {
|
||||
// 创建光晕纹理 - 使用单色替代渐变
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 绘制圆形光晕,中心实色,边缘透明
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.6)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(128, 128, 64, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 外圈较淡的圆
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.2)';
|
||||
ctx.beginPath();
|
||||
ctx.arc(128, 128, 100, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
|
||||
const geometry = new THREE.PlaneGeometry(0.5, 0.5);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.6,
|
||||
depthTest: false
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.position.set(x, y, 0.01);
|
||||
mesh.scale.set(scaleFactor, scaleFactor, scaleFactor);
|
||||
|
||||
// 添加缩放动画
|
||||
const animate = () => {
|
||||
new TWEEN.Tween(mesh.scale)
|
||||
.to({ x: scaleFactor * 1.5, y: scaleFactor * 1.5, z: scaleFactor * 1.5 }, 2000)
|
||||
.easing(TWEEN.Easing.Sinusoidal.InOut)
|
||||
.yoyo(true)
|
||||
.repeat(Infinity)
|
||||
.start();
|
||||
};
|
||||
|
||||
animate();
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建光柱
|
||||
* @param {number} x - X坐标
|
||||
* @param {number} y - Y坐标
|
||||
* @param {number} heightScaleFactor - 高度缩放因子
|
||||
* @returns {THREE.Group} 光柱组
|
||||
*/
|
||||
const createLightPillar = (x, y, heightScaleFactor = 1) => {
|
||||
const group = new THREE.Group();
|
||||
|
||||
// 创建光柱纹理 - 使用单色替代渐变
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 64;
|
||||
canvas.height = 256;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 绘制实色光柱,中部较亮
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.8)';
|
||||
ctx.fillRect(0, 25, 64, 206); // 中部主体
|
||||
|
||||
// 上下边缘稍淡
|
||||
ctx.fillStyle = 'rgba(0, 255, 255, 0.4)';
|
||||
ctx.fillRect(0, 0, 64, 25); // 顶部
|
||||
ctx.fillRect(0, 231, 64, 25); // 底部
|
||||
|
||||
const texture = new THREE.CanvasTexture(canvas);
|
||||
|
||||
// 创建两个相交的平面形成光柱
|
||||
const height = 2 * heightScaleFactor;
|
||||
const width = 0.1;
|
||||
|
||||
// 第一个平面
|
||||
const geometry1 = new THREE.PlaneGeometry(width, height);
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
depthTest: false,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
const plane1 = new THREE.Mesh(geometry1, material);
|
||||
plane1.position.set(x, y, height / 2);
|
||||
|
||||
// 第二个平面(旋转90度)
|
||||
const plane2 = plane1.clone();
|
||||
plane2.rotation.z = Math.PI / 2;
|
||||
|
||||
// 底部光点
|
||||
const bottomMesh = createPointMesh(x, y, config.scaleFactor);
|
||||
|
||||
// 光晕效果
|
||||
const halo = createLightHalo(x, y, config.scaleFactor);
|
||||
|
||||
group.add(plane1);
|
||||
group.add(plane2);
|
||||
group.add(bottomMesh);
|
||||
group.add(halo);
|
||||
|
||||
return group;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置光柱颜色
|
||||
* @param {THREE.Group} lightPillar - 光柱组
|
||||
* @param {number} color - 颜色值
|
||||
*/
|
||||
const setLightPillarColor = (lightPillar, color) => {
|
||||
lightPillar.children.forEach(child => {
|
||||
if (child.material) {
|
||||
child.material.color.setHex(color);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置网格四元数(用于球面定位)
|
||||
* @param {THREE.Object3D} mesh - 网格对象
|
||||
* @param {number} lng - 经度
|
||||
* @param {number} lat - 纬度
|
||||
* @param {number} radius - 半径
|
||||
*/
|
||||
const setMeshQuaternion = (mesh, lng, lat, radius = 1) => {
|
||||
const phi = (90 - lat) * (Math.PI / 180);
|
||||
const theta = (lng + 180) * (Math.PI / 180);
|
||||
|
||||
const x = -(radius * Math.sin(phi) * Math.cos(theta));
|
||||
const z = radius * Math.sin(phi) * Math.sin(theta);
|
||||
const y = radius * Math.cos(phi);
|
||||
|
||||
mesh.position.set(x, y, z);
|
||||
mesh.lookAt(0, 0, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取射线检测对象
|
||||
* @param {THREE.Camera} camera - 相机
|
||||
* @param {THREE.Vector2} mouse - 鼠标位置
|
||||
* @param {Array} objects - 检测对象数组
|
||||
* @returns {Array} 相交对象数组
|
||||
*/
|
||||
const getRaycasterObj = (camera, mouse, objects) => {
|
||||
const raycaster = new THREE.Raycaster();
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
return raycaster.intersectObjects(objects, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* 选择光柱
|
||||
* @param {Event} event - 鼠标事件
|
||||
* @param {THREE.Camera} camera - 相机
|
||||
* @param {Array} lightPillars - 光柱数组
|
||||
* @param {HTMLElement} container - 容器元素
|
||||
* @returns {Object|null} 选中的光柱信息
|
||||
*/
|
||||
const chooseLightPillar = (event, camera, lightPillars, container) => {
|
||||
const rect = container.getBoundingClientRect();
|
||||
const mouse = new THREE.Vector2();
|
||||
|
||||
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
|
||||
const intersects = getRaycasterObj(camera, mouse, lightPillars);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
return {
|
||||
object: intersects[0].object,
|
||||
point: intersects[0].point,
|
||||
distance: intersects[0].distance
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
createPointMesh,
|
||||
createLightHalo,
|
||||
createLightPillar,
|
||||
setLightPillarColor,
|
||||
setMeshQuaternion,
|
||||
getRaycasterObj,
|
||||
chooseLightPillar
|
||||
};
|
||||
}
|
||||
182
datav-bank/src/hooks/useSequenceFrameAnimate.js
Normal file
182
datav-bank/src/hooks/useSequenceFrameAnimate.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import * as THREE from 'three';
|
||||
|
||||
export default function useSequenceFrameAnimate() {
|
||||
/**
|
||||
* 创建序列帧动画
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {string} options.image - 图片路径
|
||||
* @param {number} options.width - 图片宽度
|
||||
* @param {number} options.height - 图片高度
|
||||
* @param {number} options.frame - 总帧数
|
||||
* @param {number} options.column - 列数
|
||||
* @param {number} options.row - 行数
|
||||
* @param {number} options.speed - 播放速度
|
||||
* @returns {THREE.Mesh} 序列帧网格对象
|
||||
*/
|
||||
const createSequenceFrame = (options = {}) => {
|
||||
const {
|
||||
image,
|
||||
width = 256,
|
||||
height = 256,
|
||||
frame = 16,
|
||||
column = 4,
|
||||
row = 4,
|
||||
speed = 1
|
||||
} = options;
|
||||
|
||||
// 创建几何体
|
||||
const geometry = new THREE.PlaneGeometry(1, 1);
|
||||
|
||||
// 加载纹理
|
||||
const texture = new THREE.TextureLoader().load(image);
|
||||
texture.wrapS = THREE.RepeatWrapping;
|
||||
texture.wrapT = THREE.RepeatWrapping;
|
||||
|
||||
// 计算单帧的UV偏移
|
||||
const frameWidth = 1 / column;
|
||||
const frameHeight = 1 / row;
|
||||
|
||||
// 设置初始UV偏移
|
||||
texture.repeat.set(frameWidth, frameHeight);
|
||||
texture.offset.set(0, 1 - frameHeight); // 从顶部开始
|
||||
|
||||
// 创建材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
map: texture,
|
||||
transparent: true,
|
||||
alphaTest: 0.1,
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
// 创建网格
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 添加动画属性
|
||||
mesh.currentFrame = 0;
|
||||
mesh.frameCount = frame;
|
||||
mesh.column = column;
|
||||
mesh.row = row;
|
||||
mesh.speed = speed;
|
||||
mesh.frameWidth = frameWidth;
|
||||
mesh.frameHeight = frameHeight;
|
||||
mesh.frameTimer = 0;
|
||||
|
||||
// 更新序列帧的方法
|
||||
mesh.updateSequenceFrame = function() {
|
||||
this.frameTimer += this.speed;
|
||||
|
||||
if (this.frameTimer >= 60 / this.frameCount) { // 假设60FPS
|
||||
this.frameTimer = 0;
|
||||
this.currentFrame = (this.currentFrame + 1) % this.frameCount;
|
||||
|
||||
// 计算当前帧在纹理中的位置
|
||||
const col = this.currentFrame % this.column;
|
||||
const row = Math.floor(this.currentFrame / this.column);
|
||||
|
||||
// 更新UV偏移
|
||||
this.material.map.offset.set(
|
||||
col * this.frameWidth,
|
||||
1 - (row + 1) * this.frameHeight // 从顶部开始
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 设置帧的方法
|
||||
mesh.setFrame = function(frameIndex) {
|
||||
if (frameIndex >= 0 && frameIndex < this.frameCount) {
|
||||
this.currentFrame = frameIndex;
|
||||
|
||||
const col = frameIndex % this.column;
|
||||
const row = Math.floor(frameIndex / this.column);
|
||||
|
||||
this.material.map.offset.set(
|
||||
col * this.frameWidth,
|
||||
1 - (row + 1) * this.frameHeight
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// 播放动画的方法
|
||||
mesh.play = function() {
|
||||
this.isPlaying = true;
|
||||
};
|
||||
|
||||
// 暂停动画的方法
|
||||
mesh.pause = function() {
|
||||
this.isPlaying = false;
|
||||
};
|
||||
|
||||
// 停止动画的方法
|
||||
mesh.stop = function() {
|
||||
this.isPlaying = false;
|
||||
this.currentFrame = 0;
|
||||
this.setFrame(0);
|
||||
};
|
||||
|
||||
// 设置播放速度的方法
|
||||
mesh.setSpeed = function(newSpeed) {
|
||||
this.speed = newSpeed;
|
||||
};
|
||||
|
||||
// 默认开始播放
|
||||
mesh.isPlaying = true;
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建简单的粒子序列帧动画
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {THREE.Mesh} 粒子网格对象
|
||||
*/
|
||||
const createParticleSequenceFrame = (options = {}) => {
|
||||
const {
|
||||
color = 0x00ffff,
|
||||
size = 0.1,
|
||||
opacity = 0.8
|
||||
} = options;
|
||||
|
||||
// 创建简单的粒子几何体
|
||||
const geometry = new THREE.SphereGeometry(size, 8, 8);
|
||||
|
||||
// 创建材质
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: color,
|
||||
transparent: true,
|
||||
opacity: opacity
|
||||
});
|
||||
|
||||
// 创建网格
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
|
||||
// 添加简单的闪烁动画
|
||||
mesh.animationTimer = 0;
|
||||
mesh.baseOpacity = opacity;
|
||||
|
||||
mesh.updateSequenceFrame = function() {
|
||||
this.animationTimer += 0.1;
|
||||
const opacity = this.baseOpacity * (0.5 + 0.5 * Math.sin(this.animationTimer));
|
||||
this.material.opacity = opacity;
|
||||
};
|
||||
|
||||
return mesh;
|
||||
};
|
||||
|
||||
/**
|
||||
* 批量更新序列帧动画
|
||||
* @param {Array<THREE.Mesh>} meshes - 网格数组
|
||||
*/
|
||||
const updateSequenceFrames = (meshes) => {
|
||||
meshes.forEach(mesh => {
|
||||
if (mesh.updateSequenceFrame && mesh.isPlaying !== false) {
|
||||
mesh.updateSequenceFrame();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
createSequenceFrame,
|
||||
createParticleSequenceFrame,
|
||||
updateSequenceFrames
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user