MapboxとCesiumを使ってドローンを飛行させてみます
MapboxのMVTはデータが新しく3Dビルディングのカバー率がかなり優秀です
Cesiumからは現在使用できないようです
Cesiumはデータの専門的な可視化を目的としているようで、データが正確です
OpenStreetMapのTilesを使用できます!
Styleはこちらを使っています
- 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()
}