1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

渋谷あたりをドローンで飛行してみる

Posted at

MapboxとCesiumを使ってドローンを飛行させてみます

MapboxのMVTはデータが新しく3Dビルディングのカバー率がかなり優秀です
Cesiumからは現在使用できないようです

Cesiumはデータの専門的な可視化を目的としているようで、データが正確です
OpenStreetMapのTilesを使用できます!

Styleはこちらを使っています

mapbox.gif

cesium.gif

  • Mapbox
import config from './config.js'
import { GUI } from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js'
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js'

let initialHeading = 0

mapboxgl.accessToken = config.accessToken

const BUILDING_LAYER_ID = '3d-buildings'
const MIN_ZOOM = 12

function createBuildingLayer() {
  return {
    id: BUILDING_LAYER_ID,
    source: 'composite',
    'source-layer': 'building',
    filter: ['==', 'extrude', 'true'],
    type: 'fill-extrusion',
    minzoom: MIN_ZOOM,
    paint: {
      'fill-extrusion-color': ['case', ['boolean', ['feature-state', 'select'], false], 'red', ['boolean', ['feature-state', 'hover'], false], 'lightblue', 'mediumaquamarine'],
      // use an 'interpolate' expression to add a smooth transition effect to the buildings as the user zooms in
      'fill-extrusion-height': ['interpolate', ['linear'], ['zoom'], MIN_ZOOM, 0, MIN_ZOOM + 0.05, ['get', 'height']],
      'fill-extrusion-base': ['interpolate', ['linear'], ['zoom'], MIN_ZOOM, 0, MIN_ZOOM + 0.05, ['get', 'min_height']],
      'fill-extrusion-opacity': 0.8,
    },
  }
}

function createModelLayer(callback) {
  return {
    id: '3d-model',
    type: 'custom',
    renderingMode: '3d',
    onAdd: (map, gl) =>
      tb.loadObj(
        {
          type: 'glb',
          obj: './CesiumDrone.glb',
          units: 'meters',
          scale: 1,
          rotation: { x: 90, y: 0, z: 0 },
          anchor: 'top',
        },
        callback,
      ),
    render: (gl, matrix) => tb.update(),
  }
}

let guiParameter = {
  buildings: true,
  acceleration: 5,
  inertia: 10,
}

let mixer = null
let clock = new THREE.Clock()

let stats = null
let gui = null
let render = false

export async function renderMap(keys, coords) {
  if (render) return
  render = true

  const { longitude, latitude } = coords
  const container = document.querySelector('.navigation-map')

  const mapContainer = document.createElement('div')
  container.appendChild(mapContainer)
  mapContainer.id = 'map'

  stats = new Stats()
  mapContainer.appendChild(stats.dom)
  stats.dom.style.position = 'absolute'

  const guiContainer = document.createElement('div')
  container.appendChild(guiContainer)
  gui = new GUI({ container: guiContainer, width: '100%' })

  const map = new mapboxgl.Map({
    container: mapContainer.id,
    style: `mapbox://styles/${config.username}/${config.styleId}`,
    zoom: 19,
    center: [longitude, latitude],
    pitch: 65,
    bearing: initialHeading,
    antialias: true, // create the gl context with MSAA antialiasing, so custom layers are antialiased
    // attributionControl: false,
  })
  new mapboxgl.Marker().setLngLat([longitude, latitude]).addTo(map)

  window.tb = new Threebox(map, map.getCanvas().getContext('webgl'), {
    // realSunlight: true,
    // enableSelectingObjects: true,
    // enableDraggingObjects: true,
    enableRotatingObjects: true,
    // enableTooltips: true,
  })
  // tb.setSunlight(new Date(2020, 6, 19, 23), map.getCenter())

  let drone
  const modelLoadedCallback = (model) => {
    drone = model.setCoords([longitude, latitude, 10])
    drone.setRotation({ x: 0, y: 0, z: -initialHeading }) //turn it to the initial street way
    drone.castShadow = true
    drone.selected = true
    drone.addEventListener(
      'ObjectChanged',
      (e) => {
        if (guiParameter.buildings) {
          let point = map.project(e.detail.object.coordinates) //here's the object already modified
          let features = map.queryRenderedFeatures(point, { layers: [BUILDING_LAYER_ID] })
          if (features.length > 0) {
            const { source, sourceLayer, id } = features[0]
            map.setFeatureState({ source, sourceLayer, id }, { select: true })
          }
        }
      },
      false,
    )

    if (drone.animations && drone.animations.length > 0) {
      mixer = new THREE.AnimationMixer(drone)
      drone.animations.forEach((clip, index) => {
        const action = mixer.clipAction(clip)
        action.play()
      })
    }

    tb.add(drone)
    tb.scene.add(new THREE.AmbientLight(0xffffff, 0.5))

    gui
      .add(guiParameter, 'buildings')
      .name('buildings')
      .onChange(() => {
        if (guiParameter.buildings) {
          if (!map.getLayer(BUILDING_LAYER_ID)) {
            map.addLayer(createBuildingLayer())
          }
        } else {
          if (map.getLayer(BUILDING_LAYER_ID)) {
            map.removeLayer(BUILDING_LAYER_ID)
          }
        }
        tb.map.repaint = true
      })
    gui.add(guiParameter, 'acceleration', 1, 16).step(0.5)
    gui.add(guiParameter, 'inertia', 1, 32).step(0.5)
    gui.close()

    animate()
  }

  map.on('style.load', () => {
    map.addLayer(createModelLayer(modelLoadedCallback))
    if (guiParameter.buildings) {
      if (!map.getLayer(BUILDING_LAYER_ID)) {
        map.addLayer(createBuildingLayer())
      }
    }
    map.getCanvas().focus()
  })

  map.once('load', () => {
    map.setPadding({ top: 180 })
  })

  const delta = 0.01
  let velocity = 0.0

  function toDegree(rad) {
    return (rad / Math.PI) * 180
  }

  function toRadian(deg) {
    return (deg * Math.PI) / 180
  }

  function animate() {
    requestAnimationFrame(animate)

    if (mixer) mixer.update(clock.getDelta())
    stats.update()

    let { inertia, acceleration } = guiParameter

    const newSpeed = calculateSpeed(keys, inertia, acceleration, velocity)

    if (newSpeed !== 0.0) {
      velocity += (newSpeed - velocity) * acceleration * delta
      drone.set({ worldTranslate: new THREE.Vector3(0, -velocity, 0) })
    }

    const options = {
      center: drone.coordinates,
      bearing: map.getBearing(),
      easing: (t) => t * (2 - t),
    }

    const deg = 1
    const rad = toRadian(deg)
    const zAxis = new THREE.Vector3(0, 0, 1)

    if (keys.a || keys.d) {
      const rotation = rad * (keys.d ? -1 : 1)
      drone.set({ quaternion: [zAxis, drone.rotation.z + rotation] })
      options.bearing = -toDegree(drone.rotation.z)
    }

    map.jumpTo(options)
  }

  function calculateSpeed(keys, inertia, acceleration, velocity) {
    let speed = 0.0

    if (!(keys.w || keys.s)) {
      if (velocity > 0) {
        speed = -inertia * delta
      } else if (velocity < 0) {
        speed = inertia * delta
      }
      if (velocity > -0.0008 && velocity < 0.0008) {
        speed = velocity = 0.0
      }
    }

    if (keys.w) {
      speed = acceleration * delta
    } else if (keys.s) {
      speed = -acceleration * delta
    }

    return speed
  }
}
  • Cesium
import config from './config.js'
import { GUI } from 'https://threejs.org/examples/jsm/libs/lil-gui.module.min.js'
import Stats from 'https://threejs.org/examples/jsm/libs/stats.module.js'

let guiParameter = {
  buildings: true,
  acceleration: 50,
  inertia: 3,
}

let stats = null
let gui = null
let render = false

export async function renderMap(keys, coords) {
  if (render) return
  render = true

  const { longitude, latitude } = coords
  const container = document.querySelector('.navigation-map')

  const mapContainer = document.createElement('div')
  container.appendChild(mapContainer)
  mapContainer.id = 'map'

  stats = new Stats()
  mapContainer.appendChild(stats.dom)
  stats.dom.style.position = 'absolute'

  const guiContainer = document.createElement('div')
  container.appendChild(guiContainer)
  gui = new GUI({ container: guiContainer, width: '100%' })

  Cesium.Ion.defaultAccessToken = config.defaultAccessToken

  const viewer = new Cesium.Viewer(mapContainer.id, {
    animation: false,
    timeline: false,
    fullscreenButton: false,
    // creditContainer: document.createElement('div'),

    geocoder: false,
    homeButton: false,
    sceneModePicker: false,
    baseLayerPicker: false,
    navigationHelpButton: false,

    shouldAnimate: true,

    terrain: Cesium.Terrain.fromWorldTerrain(),
  })

  // Imagery -----------------
  viewer.imageryLayers.addImageryProvider(
    new Cesium.MapboxStyleImageryProvider({
      styleId: config.styleId,
      username: config.username,
      accessToken: config.accessToken,
      // tilesize: 256,
      // scaleFactor: true,
      // maximumLevel: 20,
    }),
  )

  // 3DBuildings -----------------
  const osmBuildings = await Cesium.createOsmBuildingsAsync()
  osmBuildings.style = new Cesium.Cesium3DTileStyle({
    color: "color('mediumaquamarine', 0.8)",
  })
  viewer.scene.primitives.add(osmBuildings)

  // HomePoint -----------------
  const homePosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, 95)
  viewer.entities.add({
    position: homePosition,
    point: { pixelSize: 10, color: Cesium.Color.RED },
  })

  // Drone -----------------
  const dronePosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, 100)
  // const heading = Cesium.Math.toRadians(-initialHeading)
  const heading = Cesium.Math.toRadians(0)
  const pitch = 0
  const roll = 0
  const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll)
  const droneOrientation = Cesium.Transforms.headingPitchRollQuaternion(dronePosition, hpr)
  const drone = viewer.entities.add({
    name: 'CesiumDrone',
    position: dronePosition,
    orientation: droneOrientation,
    model: { uri: './CesiumDrone.glb' },
  })

  // Camera(tracked) -----------------
  // viewer.trackedEntity = drone
  // viewer.flyTo(drone, {
  //   duration: 2,
  //   offset: new Cesium.HeadingPitchRange(Cesium.Math.toRadians(90 - initialHeading), Cesium.Math.toRadians(-30), 100),
  // })

  // Camera -----------------
  const cameraPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, 100)
  viewer.camera.setView({ destination: cameraPosition })

  const delta = 0.01
  let velocity = 0.0

  // Animation -----------------
  function animate() {
    stats.update()
    if (!drone || !Cesium.defined(drone.position)) return

    const dronePosition = drone.position.getValue(viewer.clock.currentTime)
    const center = Cesium.Cartesian3.clone(dronePosition)

    let { inertia, acceleration } = guiParameter

    const newSpeed = calculateSpeed(keys, inertia, acceleration, velocity)

    velocity += (newSpeed - velocity) * acceleration * delta

    let orientation = drone.orientation.getValue(viewer.clock.currentTime)

    if (keys.w || keys.s) {
      const position = drone.position.getValue(viewer.clock.currentTime)
      const forward = new Cesium.Cartesian3(1, 0, 0)
      const rotationMatrix = Cesium.Matrix3.fromQuaternion(orientation)
      const rotatedForward = Cesium.Matrix3.multiplyByVector(rotationMatrix, forward, new Cesium.Cartesian3())
      if (keys.w) {
        const movement = Cesium.Cartesian3.multiplyByScalar(rotatedForward, velocity, new Cesium.Cartesian3())
        drone.position = Cesium.Cartesian3.add(position, movement, new Cesium.Cartesian3())
      } else if (keys.s) {
        const movement = Cesium.Cartesian3.multiplyByScalar(rotatedForward, -velocity, new Cesium.Cartesian3())
        drone.position = Cesium.Cartesian3.subtract(position, movement, new Cesium.Cartesian3())
      }
    }

    if (keys.a || keys.d) {
      const turnSpeed = Cesium.Math.toRadians(0.5)
      let newHeading = null
      if (keys.a) {
        newHeading = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, turnSpeed, new Cesium.Quaternion())
      } else if (keys.d) {
        newHeading = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, -turnSpeed, new Cesium.Quaternion())
      }
      let q = Cesium.Quaternion.multiply(orientation, newHeading, new Cesium.Quaternion())
      drone.orientation = new Cesium.ConstantProperty(q)
    }

    viewer.trackedEntity = drone

    const droneOrientation = drone.orientation.getValue(viewer.clock.currentTime)
    const hpr = Cesium.HeadingPitchRoll.fromQuaternion(droneOrientation)
    let h = Cesium.Math.toRadians(-38.0 + Cesium.Math.toDegrees(hpr.heading))
    let p = Cesium.Math.toRadians(-20.0)
    let r = 100.0
    viewer.camera.lookAt(center, new Cesium.HeadingPitchRange(h, p, r))

    requestAnimationFrame(animate)
  }

  function calculateSpeed(keys, inertia, acceleration, velocity) {
    let speed = 0.0

    if (!(keys.w || keys.s)) {
      if (velocity > 0) {
        speed = -inertia * delta
      } else if (velocity < 0) {
        speed = inertia * delta
      }
      if (velocity > -0.0008 && velocity < 0.0008) {
        speed = velocity = 0.0
      }
    }

    if (keys.w) {
      speed = acceleration * delta
    } else if (keys.s) {
      speed = -acceleration * delta
    }

    return speed
  }

  function toggleBuildings() {
    if (guiParameter.buildings) {
    } else {
    }
  }

  gui.add(guiParameter, 'buildings').name('buildings').onChange(toggleBuildings)
  gui.add(guiParameter, 'acceleration', 1, 10).step(0.5)
  gui.add(guiParameter, 'inertia', 1, 5).step(0.5)
  gui.close()
  animate()
}

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?