马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
实现思绪
- 首先是须要用canvas绘制一个2D的热力图,如果你还不会,请看json绘制热力图。
- 使用Threejs中的canvas贴图,将贴图贴在PlaneGeometry平面上。
- 使用着色东西质,更具json中的数据让平面模型 拔地而起。
- 使用Threejs内置的TWEEN,加上一个动画。
结果
结果如下:
具体代码
具体实现结果代码:
- import * as THREE from 'three';
- import * as TWEEN from 'three/examples/jsm/libs/tween.module.js';
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
- import * as d3geo from 'd3-geo'
- import trafficJSON from '../assets/json/traffic.json'
- export default (domId) => {
- /* ------------------------------初始化三件套--------------------------------- */
- const dom = document.getElementById(domId);
- const { innerHeight, innerWidth } = window
- const scene = new THREE.Scene();
- const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 2000);
- camera.position.set(-25, 288, 342);
- camera.lookAt(scene.position);
- const renderer = new THREE.WebGLRenderer({
- antialias: true,// 抗锯齿
- alpha: false,// 透明度
- powerPreference: 'high-performance',// 性能
- logarithmicDepthBuffer: true,// 深度缓冲
- })
- // renderer.setClearColor(0x000000, 0);// 设置背景色
- // renderer.clear();// 清除渲染器
- renderer.shadowMap.enabled = true;// 开启阴影
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;// 阴影类型
- renderer.outputEncoding = THREE.sRGBEncoding;// 输出编码
- renderer.toneMapping = THREE.ACESFilmicToneMapping;// 色调映射
- renderer.toneMappingExposure = 1;// 色调映射曝光
- renderer.physicallyCorrectLights = true;// 物理正确灯光
- renderer.setPixelRatio(devicePixelRatio);// 设置像素比
- renderer.setSize(innerWidth, innerHeight);// 设置渲染器大小
- dom.appendChild(renderer.domElement);
- // 重置大小
- window.addEventListener('resize', () => {
- const { innerHeight, innerWidth } = window
- camera.aspect = innerWidth / innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(innerWidth, innerHeight);
- })
- /* ------------------------------初始化工具--------------------------------- */
- const controls = new OrbitControls(camera, renderer.domElement) // 相机轨道控制器
- controls.enableDamping = true // 是否开启阻尼
- controls.dampingFactor = 0.05// 阻尼系数
- controls.panSpeed = -1// 平移速度
- const axesHelper = new THREE.AxesHelper(10);
- scene.add(axesHelper);
- /* ------------------------------正题--------------------------------- */
- let geoFun;// 地理投影函数
- let info = {
- max: Number.MIN_SAFE_INTEGER,
- min: Number.MAX_SAFE_INTEGER,
- maxlng: Number.MIN_SAFE_INTEGER,
- minlng: Number.MAX_SAFE_INTEGER,
- maxlat: Number.MIN_SAFE_INTEGER,
- minlat: Number.MAX_SAFE_INTEGER,
- data: []
- };
- // 初始化地理投影
- const initGeo = (size) => {
- geoFun = d3geo.geoMercator().scale(size || 100)
- }
- // 经纬度转像素坐标
- const latlng2px = (pos) => {
- if (pos[0] >= -180 && pos[0] <= 180 && pos[1] >= -90 && pos[1] <= 90) {
- return geoFun(pos);
- }
- return pos;
- };
- // 创建颜色
- const createColors = (option) => {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
- canvas.width = 256;
- canvas.height = 1;
- const grad = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
- for (let k in option.colors) {
- grad.addColorStop(k, option.colors[k]);
- }
- ctx.fillStyle = grad;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- return ctx.getImageData(0, 0, canvas.width, 1).data;
- }
- // 绘制圆
- const drawCircle = (ctx, option, item) => {
- let { lng, lat, value } = item;
- let x = lng - option.minlng + option.radius;
- let y = lat - option.minlat + option.radius;
- const grad = ctx.createRadialGradient(x, y, 0, x, y, option.radius);
- grad.addColorStop(0.0, 'rgba(0,0,0,1)');
- grad.addColorStop(1.0, 'rgba(0,0,0,0)');
- ctx.fillStyle = grad;
- ctx.beginPath();
- ctx.arc(x, y, option.radius, 0, 2 * Math.PI);
- ctx.closePath();
- ctx.globalAlpha = (value - option.min) / option.size;
- ctx.fill();
- }
- // 创建热力图
- const createHeatmap = (option) => {
- const canvas = document.createElement('canvas');
- canvas.width = option.width;
- canvas.height = option.height;
- const ctx = canvas.getContext('2d');
- option.size = option.max - option.min;
- option.data.forEach((item) => {
- drawCircle(ctx, option, item);
- });
- const colorData = createColors(option);
- const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
- for (let i = 3; i < imageData.data.length; i = i + 4) {
- let opacity = imageData.data[i];
- let offset = opacity * 4;
- //red
- imageData.data[i - 3] = colorData[offset];
- //green
- imageData.data[i - 2] = colorData[offset + 1];
- //blue
- imageData.data[i - 1] = colorData[offset + 2];
- }
- ctx.putImageData(imageData, 0, 0);
- return { canvas, option };
- }
- // 初始化
- const init = () => {
- initGeo(1000)
- // 处理数据
- trafficJSON.features.forEach((item) => {
- let pos = latlng2px(item.geometry.coordinates);// 经纬度转像素坐标
- let newitem = {
- lng: pos[0],
- lat: pos[1],
- value: item.properties.avg
- }
- info.max = Math.max(newitem.value, info.max);
- info.maxlng = Math.max(newitem.lng, info.maxlng);
- info.maxlat = Math.max(newitem.lat, info.maxlat);
- info.min = Math.min(newitem.value, info.min);
- info.minlng = Math.min(newitem.lng, info.minlng);
- info.minlat = Math.min(newitem.lat, info.minlat);
- info.data.push(newitem);
- })
- info.size = info.max - info.min;
- info.sizelng = info.maxlng - info.minlng;
- info.sizelat = info.maxlat - info.minlat;
- const radius = 50;
- const { canvas, option } = createHeatmap({
- width: info.sizelng + radius * 2,
- height: info.sizelng + radius * 2,
- colors: {
- 0.1: '#2A85B8',
- 0.2: '#16B0A9',
- 0.3: '#29CF6F',
- 0.4: '#5CE182',
- 0.5: '#7DF675',
- 0.6: '#FFF100',
- 0.7: '#FAA53F',
- 1: '#D04343'
- },
- radius,
- ...info
- })
- const map = new THREE.CanvasTexture(canvas);
- map.wrapS = THREE.RepeatWrapping;
- map.wrapT = THREE.RepeatWrapping;
- // 创建平面
- const geometry = new THREE.PlaneGeometry(
- option.width * 0.5,
- option.height * 0.5,
- 500,
- 500
- );
- const material = new THREE.ShaderMaterial({
- transparent: true,
- side: THREE.DoubleSide,
- uniforms: {
- map: { value: map },
- uHeight: { value: 100 },
- uOpacity: { value: 2.0 }
- },
- vertexShader: `
- uniform sampler2D map;
- uniform float uHeight;
- varying vec2 v_texcoord;
- void main(void)
- {
- v_texcoord = uv;
- float h=texture2D(map, v_texcoord).a*uHeight;
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position.x,position.y,h, 1.0 );
- }`,
- fragmentShader: `
- precision mediump float;
- uniform sampler2D map;
- uniform float uOpacity;
- varying vec2 v_texcoord;
- void main (void)
- {
- vec4 color= texture2D(map, v_texcoord);
- float a=color.a*uOpacity;
- gl_FragColor.rgb =color.rgb;
- gl_FragColor.a=a>1.0?1.0:a;
- }`
- });
- const plane = new THREE.Mesh(geometry, material);
- plane.rotateX(-Math.PI * 0.5);
- scene.add(plane);
- const tween = new TWEEN
- .Tween({ v: 0 })
- .to({ v: 100 }, 2000)
- .onUpdate(obj => {
- material.uniforms.uHeight.value = obj.v;
- })
- .easing(TWEEN.Easing.Quadratic.InOut)
- .start();
- TWEEN.add(tween);
- }
- init();
- /* ------------------------------动画函数--------------------------------- */
- const animation = () => {
- TWEEN.update();
- controls.update();// 如果不调用,就会很卡
- renderer.render(scene, camera);
- requestAnimationFrame(animation);
- }
- animation();
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |