vasturiano / globe.gl

UI component for Globe Data Visualization using ThreeJS/WebGL
https://vasturiano.github.io/globe.gl/example/world-population/
MIT License
1.99k stars 298 forks source link

is it possible to put 2d object above 3d globe.gl map #86

Closed seogki closed 10 months ago

seogki commented 2 years ago

I have manged to draw 3d globe map using

ConicPolygonBufferGeometry and GeoJsonGeometry implements to customThreeObject options

i have noticed that by scaling polygon object i can display curved polygon of country but i want to as flat is there any solution to this??

import * as THREE from "three";
import Globe from "globe.gl";
// import country_geo_info from "./country_geo_info.json";
import country_geo_info from "./admin_country.json";
import state_geo_info from "./admin_states.json";
import coord_geo_info from "./coord_geo_info.json";
import { ConicPolygonBufferGeometry } from "three-conic-polygon-geometry";
import { GeoJsonGeometry } from "three-geojson-geometry";
import { _objectSpread2, _toConsumableArray$1 } from "./coord.js";
import { FontLoader } from "three/examples/jsm/loaders/FontLoader";
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry";
import { TWEEN } from "three/examples/jsm/libs/tween.module.min";
import ARIAL_BOLD_FONT from "./Gothic A1_Bold.json";
import earcut from "earcut";
import _ from "lodash";
import * as d3 from "d3";
import * as turf from "@turf/turf";
import {
  BoxBufferGeometry,
  BufferGeometry,
  Line,
  LineBasicMaterial,
  Mesh,
  MeshBasicMaterial,
  ShapeBufferGeometry,
  SphereBufferGeometry,
  Vector2
} from "three";
import { result } from "lodash";
class EarthGlobe {
  GLOBE_RADIUS = 100;
  geo_info = [];
  label_rank = 2;
  constructor(element, globeImage, width, height) {
    this.element = element;
    this.globeImage = globeImage;
    this.earthGlobe = Globe();
    this.width = width;
    this.height = height;
    this.myFont = new FontLoader().parse(ARIAL_BOLD_FONT);
    this.mergeToSingleArray();
    this.createGlobe();
    this.setGlobeInfo();
  }

  mergeToSingleArray() {
    for (const feature of this.checkArray(country_geo_info)) {
      const { properties } = feature;
      const { GU_A3, ADMIN } = properties;
      const states = this.checkArray(state_geo_info).filter(
        item => item.properties.gu_a3 === GU_A3 || item.properties.admin === ADMIN
      );
      const details = this.checkArray(coord_geo_info).find(item => {
        if (item.iso3 === GU_A3 || item.name === ADMIN) return item;
      });
      feature.details = details;
      feature.states = states;
    }

    for (const feature of this.checkArray(country_geo_info)) {
      const { states, details } = feature;
      if (states === undefined || states.length < 1 || details === undefined) {
      }
    }
  }

  setGlobeInfo() {
    this.renderer = this.earthGlobe.renderer();
    this.camera = this.earthGlobe.camera();
    this.scene = this.earthGlobe.scene();
    this.scene.add(new THREE.AxesHelper(1000));
    this.controls = this.earthGlobe.controls();
    this.renderer.domElement.style.position = "absolute";
    this.raycaster = new THREE.Raycaster();
  }

  checkArray(data) {
    if (Array.isArray(data)) return data;
    return data.features;
  }

  createGlobe() {
    this.earthGlobe(this.element)
      .globeImageUrl(this.globeImage)
      .customLayerData(this.checkArray(country_geo_info))
      .customLayerLabel(null)
      .customThreeObject((d, _) => {
        return this.drawFirstLayer(d);
      })
      .onCustomLayerHover((obj, prevObj) => {})
      .onCustomLayerClick((obj, ev, coord) => {})
      .onZoom(
        _.throttle(coord => {
          const alt = coord.altitude;
          if (alt > 1.5) {
            //* LABEL RANK 3
            if (this.label_rank === 3) return;
            this.label_rank = 3;
            this.showHideByLabelRank();
            console.log("above 1.5");
          } else if (alt > 0.5) {
            if (this.label_rank === 4) return;
            this.label_rank = 4;
            this.showHideByLabelRank();
            console.log("above 0.8");
          } else if (alt > 0.2) {
            if (this.label_rank === 5) return;
            this.label_rank = 5;
            this.showHideByLabelRank(true);
            console.log("above 0.2");
          }
        }, 200)
      )
      .onGlobeReady(() => {});
  }

  showHideByLabelRank(isHigher) {
    for (const item of this.checkArray(country_geo_info)) {
      const obj = this.scene.getObjectByName(`${item.properties.ADMIN}_info`);
      if (isHigher) {
        if (obj && item.properties.LABELRANK >= this.label_rank) {
          obj.visible = true;
        }
      } else {
        if (obj && item.properties.LABELRANK <= this.label_rank) {
          obj.visible = true;
        } else {
          obj.visible = false;
        }
      }
    }
  }

  drawFirstLayer(d) {
    const { properties, geometry } = d;
    const first_group = this.createGroup(`${properties.ADMIN}_container`);
    const coord = geometry.coordinates;

    const polygon_group = this.createGroup(`${properties.ADMIN}_country`);
    const polygon_color = 0xff0000;
    const type = geometry.type;
    if (type === "Polygon") {
      this.drawPolygon(coord, polygon_color, true, polygon_group);
    } else if (type === "MultiPolygon") {
      const singlePolygons = this.addCoord(coord);
      for (const coords of singlePolygons) {
        this.drawPolygon(coords.coords, polygon_color, true, polygon_group);
      }
    }
    const box_obj = this.drawBoxHelper(polygon_group);
    const info_group = this.drawCountryName(properties, true, box_obj.boxSize);
    first_group.add(info_group);
    first_group.add(polygon_group);
    return first_group;
  }
  drawCountryName(properties, hasCircle, polygonBboxSize) {
    const alt = 0.01;
    const country_info_group = this.createGroup(`${properties.ADMIN}_info`);
    const { NAME_KO, LATITUDE, LONGITUDE, LABELRANK, MIN_LABEL, MAX_LABEL, MIN_ZOOM } = properties;
    let text;
    if (LABELRANK === 2) {
      text = this.drawText(NAME_KO, 1, 0xff0000, true);
    } else if (LABELRANK === 3) {
      text = this.drawText(NAME_KO, 1, 0xff0000, true);
    } else if (LABELRANK === 4) {
      text = this.drawText(NAME_KO, 0.8, 0xff0000, true);
    } else if (LABELRANK === 5) {
      text = this.drawText(NAME_KO, 0.3, 0xff0000, true);
    } else if (LABELRANK === 6) {
      text = this.drawText(NAME_KO, 0.2, 0xff0000, true);
    } else if (LABELRANK === 7) {
      text = this.drawText(NAME_KO, 0.1, 0xff0000, true);
    }
    country_info_group.add(text);
    if (LABELRANK !== 2 && LABELRANK !== 3) {
      country_info_group.visible = false;
    }

    const coord = this.earthGlobe.getCoords(LATITUDE, LONGITUDE, alt + 0.003);
    country_info_group.position.set(coord.x, coord.y, coord.z);
    country_info_group.lookAt(this.scene.localToWorld(new THREE.Vector3(0, 0, 0)));
    country_info_group.rotateY(Math.PI);
    country_info_group.rotateZ((-0 * Math.PI) / 180);
    country_info_group.scale.x = country_info_group.scale.y = country_info_group.scale.z = 1;

    return country_info_group;
  }
  drawPolygon(coord, color, hasStroke, polygon_group) {
    let alt = 0.01,
      resolution = 0.5;
    const shape = new ConicPolygonBufferGeometry(coord, 1, this.GLOBE_RADIUS, false, true, false, resolution);
    const mesh = this.drawMesh(shape, 0xffffff);
    const new_alt = 1 + alt + 0.001;
    if (hasStroke) {
      const line_alt = new_alt + 0.001;
      const line = this.drawLine(coord, resolution);
      line.scale.set(line_alt, line_alt, line_alt);
      polygon_group.add(line);
    }
    mesh.scale.set(new_alt, new_alt, new_alt);
    polygon_group.add(mesh);
  }
  drawText(text, size, color, isCenter) {
    const text_geometry = new TextGeometry(`${text}`, {
      font: this.myFont,
      size: size,
      height: 0,
      curveSegments: 3
    });

    const text_material = new THREE.MeshBasicMaterial({ color });
    if (isCenter) text_geometry.center();
    return new THREE.Mesh(text_geometry, text_material);
  }
  drawLine(coord, resolution) {
    const line_shape = new GeoJsonGeometry(
      {
        type: "Polygon",
        coordinates: coord
      },
      this.GLOBE_RADIUS,
      resolution
    );
    const line = new THREE.LineSegments(line_shape, new THREE.LineBasicMaterial({ color: 0x000000 }));

    return line;
  }
  addCoord(coord) {
    const singlePolygons = [];
    singlePolygons.push.apply(
      singlePolygons,
      _toConsumableArray$1(
        coord.map(function(coords, idx) {
          return _objectSpread2({
            coords: coords
          });
        })
      )
    );
    return singlePolygons;
  }
  createGroup(name) {
    const group = new THREE.Group();
    group.name = name;
    return group;
  }
  drawMesh(geometry, color) {
    const mesh = new THREE.Mesh(
      geometry,
      new THREE.MeshBasicMaterial({
        color: color,
        side: THREE.DoubleSide
      })
    );
    return mesh;
  }
  drawBoxHelper(object) {
    let bbox, boxSize, boxHelper;
    bbox = new THREE.Box3();
    boxSize = new THREE.Vector3();
    boxHelper = new THREE.Box3Helper(bbox, 0xff000);
    bbox.setFromObject(object);
    bbox.getSize(boxSize);

    return {
      bbox,
      boxSize,
      boxHelper
    };
  }
}

export default EarthGlobe;

I am very new to three.js with webgl and im trying to solve how to put 2d country map above 3d globe map whenever use click the center of each country point