<script>
export default {
  name: "AppThreeMap",
  label: "3D地图",
  data() {
    return {
      geoMap: null,
      layers: [],
      font: null,
      geoJson: null
    }
  },
  computed: {
    search: v => v.$marketBoard.search
  },
  methods: {
    loadLib() {
      const {$waitFor, THREE, $loadScript} = window
      return $waitFor(THREE).then(() => Promise.all([
        `http://10.0.97.209/presource/datascreen/js/three/js/controls/OrbitControls.js`,
        `http://10.0.97.209/presource/datascreen/js/three/js/renderers/CSS3DRenderer.js`,
        `http://10.0.97.209/presource/datascreen/js/three/js/loaders/FontLoader.js`,
        `http://10.0.97.209/presource/datascreen/js/three/js/geometries/TextGeometry.js`,
        '/presource/datascreen/js/turf.min.js'
      ].map(e => $loadScript('js', e))))
    },
    initMap() {
      const {THREE, d3, TWEEN, turf} = window
      const rootEl = this.$el
      const root = this
      const scale = 8

      class GeoMap {
        constructor() {
          this.cameraPosition = {x: 40, y: 0, z: 80}; // 相机位置
          this.scene = null; // 场景
          this.camera = null; // 相机
          this.renderer = null; // 渲染器
          this.CSS3DRenderer = null; // css渲染器
          this.controls = null; // 控制器
          this.mapGroup = new THREE.Group(); // 组
          this.mouse = new THREE.Vector2();
          this.font = null;
          this.tips = new THREE.Group()
          this.loop = 0
          this.markers = new THREE.Group();
          this.center = turf.center(root.geoJson).geometry.coordinates
        }

        /**
         * @desc 初始化
         * */
        init() {
          this.setScene();
          this.setCamera();
          // this.setAxes();
          this.setRenderer();
          this.setControl();
          this.setMapData(root.geoJson)
          this.setHoverPanel()
          this.animation();
          this.bindMouseEvent()
        }

        /**
         * @desc 动画循环
         * */
        animation() {
          requestAnimationFrame(this.animation.bind(this));
          this.loop = (this.loop * 100 + 2) % 100 * 0.01
          if (this.markers) {
            this.markers.children.forEach(e => {
              if (e.data.bakeStockAmt <= 0) {
                e.scale = {
                  x: Math.asin(this.loop) + 1,
                  y: Math.asin(this.loop) + 1,
                  z: Math.asin(this.loop) + 1
                };
              }
            })
          }
          TWEEN.update();
          this.controls.update();
          this.renderer.render(this.scene, this.camera);
          this.CSS3DRenderer.render(this.scene, this.camera);
        }

        /**
         * @desc 绘制地图
         * @params geojson
         * */
        setMapData(data) {
          const that = this
          const getLnglat = (arr, cb) => {
            arr?.map(e => {
              if (e.length === 2 && typeof e[0] === 'number' && typeof e[1] === 'number') {
                cb(e)
              } else {
                getLnglat(e, cb)
              }
            })
          }
          let vector3json = [],
              vector3border = []
          const maxBoundaryPoints = turf.union(data)
          getLnglat(maxBoundaryPoints.geometry.coordinates, p => {
            const lnglat = that.lnglatToVector3(p);
            const vector3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
            vector3border.push(vector3)
          })
          data.features.forEach(function (features, featuresIndex) {
            const areaItems = features.geometry.coordinates;
            features.properties.cp = that.lnglatToVector3(turf.center(features).geometry.coordinates);
            vector3json[featuresIndex] = {
              data: features.properties,
              mercator: []
            };
            areaItems.forEach(function (item, areaIndex) {
              vector3json[featuresIndex].mercator[areaIndex] = [];
              getLnglat(item, cp => {
                const lnglat = that.lnglatToVector3(cp);
                const vector3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
                vector3json[featuresIndex].mercator[areaIndex].push(vector3)
              })
            })
          });
          this.drawMap(vector3json, vector3border)
        }

        /**
         * @desc 绘制图形
         * @param data
         * */
        drawMap(data, border) {
          let that = this;
          this.mapGroup.position.y = 0;
          const extrudeSettings = {
            depth: 0.1,
            steps: 1,
            bevelSegments: 0,
            curveSegments: 1,
            bevelEnabled: false,
          };
          const canvas = document.createElement('canvas');
          canvas.width = 256;
          canvas.height = 256;
          const ctx = canvas.getContext('2d');

          const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
          gradient.addColorStop(0, 'rgba(61,127,255,0.35)');
          gradient.addColorStop(1, '#09E2F8');
          // gradient.addColorStop(0, 'rgba(61,127,255,0.01)'); // 结束颜色
          ctx.fillStyle = gradient;
          ctx.fillRect(0, 0, canvas.width, canvas.height);
          const blockMaterial = new THREE.MeshBasicMaterial({
            map: new THREE.CanvasTexture(canvas),
            transparent: true, wireframe: false
          });
          const lineMaterial = new THREE.LineBasicMaterial({color: '#97CAE6'});
          data.forEach(function (areaData) {
            let areaGroup = new THREE.Group();
            areaGroup.name = 'area';
            areaGroup._groupType = 'areaBlock';
            areaData.mercator.forEach(function (areaItem) {
              // Draw Line
              let lineGeometry = new THREE.BufferGeometry().setFromPoints(areaItem);
              let lineMesh = new THREE.Line(lineGeometry, lineMaterial);
              lineMesh.position.z = 0.101;
              areaGroup.add(lineMesh);
            });
            const {name, cp} = areaData.data
            that.setTips(name, cp[0], cp[1])
            // areaGroup.add(that.tipsSprite(areaData));
            that.mapGroup.add(areaGroup);
          });
          that.mapGroup.add(that.tips)
          const shape = new THREE.Shape(border);
          const areaGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
          const borderMaterial = new THREE.MeshBasicMaterial({color: "#1B4C92", transparent: true, opacity: 0.8});
          const mesh = new THREE.Mesh(areaGeometry, [blockMaterial, borderMaterial]);
          that.mapGroup.add(mesh)
        }

        transLayer(item = {}) {
          let {bakeStockAmt, longitude, latitude} = item
          longitude = Number(longitude || 0).toFixed(6);
          latitude = Number(latitude || 0).toFixed(6);
          const markerGeometry = new THREE.CircleGeometry(0.015, 32);
          const markerMaterial = new THREE.MeshBasicMaterial({
            wireframe: false,
            color: bakeStockAmt > 0 ? "#66FFFF" : "#FFD15C",
          });
          const marker = new THREE.Mesh(markerGeometry, markerMaterial);
          const lnglat = this.lnglatToVector3([longitude, latitude]);
          let v3 = new THREE.Vector3(lnglat[0], lnglat[1], lnglat[2]).multiplyScalar(1.2);
          marker.data = item
          marker.position.set(v3.x, v3.y, 0.11)
          return marker
        }

        addMarkers() {
          this.markers.clear()
          root.layers.map(layer => {
            const marker = this.transLayer(layer)
            this.markers.add(marker)
          })
        }

        lnglatToVector3(lnglat = []) {
          if (!this.projection) {
            this.projection = d3.geoMercator().center(this.center).scale(100).translate([0, 0]);
          }
          const [x, y] = this.projection([lnglat[0], lnglat[1]])
          const z = 0;
          return [y, x, z]
        }

        /**
         * @desc 创建场景
         * */
        setScene() {
          this.scene = new THREE.Scene();
          this.mapGroup.add(this.markers)
          this.mapGroup.scale.set(scale, scale, scale)
          this.mapGroup.position.set(0, 0, 0)
          this.scene.add(this.mapGroup);
        }

        /**
         * @desc 创建相机
         * */
        setCamera() {
          this.camera = new THREE.PerspectiveCamera(10, rootEl.offsetWidth / rootEl.offsetHeight, 1, 2000);
          this.camera.up.x = 0;
          this.camera.up.y = 0;
          this.camera.up.z = 1;
          this.camera.lookAt(0, 0, 0);
          this.scene.add(this.camera);
        }

        /**
         * @desc 创建渲染器
         * */
        setRenderer() {
          this.renderer = new THREE.WebGLRenderer({antialias: true});
          this.renderer.setPixelRatio(window.devicePixelRatio);
          this.renderer.sortObjects = true; // 渲染顺序
          this.renderer.setClearColor(0xffffff, 0);
          this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
          rootEl.appendChild(this.renderer.domElement);
          this.CSS3DRenderer = new THREE.CSS3DRenderer();
          this.CSS3DRenderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
          this.CSS3DRenderer.domElement.style.position = 'absolute';
          this.CSS3DRenderer.domElement.style.top = '0';
          this.CSS3DRenderer.domElement.style.left = '0';
          rootEl.appendChild(this.CSS3DRenderer.domElement);

          function onWindowResize() {
            this.camera.aspect = rootEl.offsetWidth / rootEl.offsetHeight;
            this.camera.updateProjectionMatrix();
            this.renderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
            this.CSS3DRenderer.setSize(rootEl.offsetWidth, rootEl.offsetHeight);
          }

          rootEl.addEventListener('resize', onWindowResize.bind(this), false);
        }

        /**
         * @desc 创建控制器
         * */
        setControl() {
          this.controls = new THREE.OrbitControls(this.camera, rootEl);
          this.controls.enableRotate = false
          this.camera.position.set(this.cameraPosition.x, this.cameraPosition.y, this.cameraPosition.z);
        }

        /**
         * @desc 创建一个xyz坐标轴
         * */
        setAxes() {
          const axes = new THREE.AxesHelper(100);
          this.scene.add(axes);
        }

        setTips(text, x, y) {
          const textGeometry = new THREE.TextGeometry(text, {
            font: root.font,
            size: 0.04,
            height: 0.02
          });
          textGeometry.center()
          textGeometry.rotateZ(Math.PI / 2)
          textGeometry.rotateY(Math.PI / 6)
          textGeometry.translate(x, y, 0.15)
          const textMaterial = new THREE.MeshBasicMaterial({color: 0xffffff, transparent: true, opacity: 0.8});
          const textMesh = new THREE.Mesh(textGeometry, textMaterial);
          this.tips.add(textMesh)
        }

        setHoverPanel() {
          this.hoverPanelElement = document.createElement('div')
          Object.entries({
            background: 'linear-gradient( 90deg, rgba(9,63,107,0.79) 0%, rgba(13,58,99,0.03) 100%)',
            borderRadius: '2px',
            color: '#fff',
            fontSize: '10px',
            lineHeight: '14px',
            position: 'absolute',
            top: 0,
            padding: '8px',
            opacity: 0,
          }).forEach(([key, value]) => {
            this.hoverPanelElement.style[key] = value
          })
          this.hoverPanel = new THREE.CSS3DObject(this.hoverPanelElement)
          this.scene.add(this.hoverPanel)
        }

        bindMouseEvent() {
          const raycaster = new THREE.Raycaster();
          const targets = this.markers?.children || []
          const onPointerMove = (event) => {
            const {offsetWidth: width, offsetHeight: height} = rootEl, {left, top} = rootEl.getBoundingClientRect();
            this.mouse.x = ((event.clientX - left) / width) * 2 - 1; //标准设备横坐标
            this.mouse.y = -((event.clientY - top) / height) * 2 + 1; //标准设备纵坐标
            raycaster.setFromCamera(this.mouse, this.camera);
            const intersects = raycaster.intersectObjects(targets)
            intersects.forEach(e => {
              if (e.object?.data) {
                this.showHoverPanel(e.object)
              }
            })
            if (intersects?.length <= 0) {
              this.hoverPanelElement.style.opacity = '0'
            }
          }
          const onClick = () => {
            // const {x, y} = this.mouse
            // const standardVector = new THREE.Vector3(x, y, 0.5); //标准设备坐标
            // //标准设备坐标转世界坐标
            // const worldVector = standardVector.unproject(this.camera);
            // const ray = worldVector.sub(this.camera.position).normalize();
            // 创建一个射线投射器
            raycaster.setFromCamera(this.mouse, this.camera);
            const intersects = raycaster.intersectObjects(targets)
            intersects.forEach(e => {
              if (e.object?.data) {
                const marker = e.object?.data
                root.$storeBoard.search.storeCode = marker.storeCode
                root.$marketBoard.screenId = 'a90522ef-869b-40ea-8542-d1fc9674a1e8'
              }
            })
          }
          rootEl.addEventListener('pointermove', onPointerMove);
          rootEl.addEventListener('click', onClick);
        }

        showHoverPanel({data: marker, position} = {}) {
          this.hoverPanelElement.style.opacity = '1'
          this.hoverPanelElement.innerHTML = [marker.storeName, `现烤库存金额：${marker.bakeStockAmt}`, `现烤销售机会：${marker.preSaleNum}`].join('<br/>')
          this.hoverPanel.scale.set(...Array(3).fill(0.04))
          this.hoverPanel.rotation.z = Math.PI / 2
          this.hoverPanel.rotation.y = Math.PI / 6
          this.hoverPanel.position.set(position.x * scale, position.y * scale, position.z * scale)
        }
      }

      return new GeoMap()
    },
    getData() {
      const {$http, $waitFor} = window
      const {groupCodeList, currentDate, hourNum} = this.search
      return $waitFor($http).then(() => Promise.all([
        $http.post("/data-boot/la/screen/marketBoard/storeReport", {
          groupCodeList, currentDate, hourNum
        }).then(res => {
          if (res?.data) {
            return this.layers = res.data || []
          }
        }),
        axios.get('http://10.0.97.209/blade-visual/map/data?id=1456').then(res => {
          if (res?.data) {
            return this.geoJson = res.data
          }
        })
      ]))
    }
  },
  watch: {
    search: {
      deep: true, handler() {
        this.getData().then(() => this.geoMap.addMarkers())
      }
    }
  },
  mounted() {
    this.loadLib().then(() => new Promise(resolve => {
      const loader = new THREE.FontLoader();
      loader.load("/presource/datascreen/js/three/fonts/HarmonyOS Sans SC_Regular.json", font => {
        this.font = font
        resolve()
      })
    })).then(() => {
      return !this.geoJson && this.getData()
    }).then(() => {
      this.geoMap = this.initMap();
      this.geoMap.init();
      this.geoMap.addMarkers()
    })
  }
}
</script>
<template>
  <section class="AppThreeMap"/>
</template>
<style>
.AppThreeMap {
  width: 100%;
  height: 100%;
  position: relative;
}
</style>
